Repository: exjam/wiiu-emu Branch: master Commit: e6c528a20a41 Files: 1742 Total size: 7.1 MB Directory structure: gitextract_agvsy06u/ ├── .clang-format ├── .github/ │ └── workflows/ │ └── ccpp.yml ├── .gitignore ├── .gitmodules ├── BUILDING.md ├── CMakeLists.txt ├── CMakeModules/ │ ├── FindCARES.cmake │ ├── FindFFMPEG.cmake │ ├── FindLibUV.cmake │ ├── FindSDL2.cmake │ ├── FindWAYLAND.cmake │ ├── FindXCB.cmake │ ├── GetGitRevisionDescription.cmake │ └── GetGitRevisionDescription.cmake.in ├── CMakePresets.json ├── LICENSE.md ├── README.md ├── libraries/ │ ├── CMakeLists.txt │ └── bin2c.cmake ├── resources/ │ ├── CMakeLists.txt │ ├── decaf-sdl.rc │ ├── fonts/ │ │ ├── DejaVuSansMono.LICENSE │ │ └── NotoSansCJK.LICENSE │ ├── hidpi.manifest │ └── resource.h ├── src/ │ ├── CMakeLists.txt │ ├── common/ │ │ ├── CMakeLists.txt │ │ ├── align.h │ │ ├── atomicqueue.h │ │ ├── bit_cast.h │ │ ├── bitfield.h │ │ ├── bitutils.h │ │ ├── byte_swap.h │ │ ├── byte_swap_array.h │ │ ├── cbool.h │ │ ├── configstorage.h │ │ ├── count_of.h │ │ ├── datahash.h │ │ ├── decaf_assert.h │ │ ├── enum_end.inl │ │ ├── enum_start.inl │ │ ├── enum_string_declare.inl │ │ ├── enum_string_define.inl │ │ ├── fastregionmap.h │ │ ├── fixed.h │ │ ├── floatutils.h │ │ ├── frameallocator.h │ │ ├── log.h │ │ ├── make_array.h │ │ ├── murmur3.h │ │ ├── pch.h │ │ ├── platform.h │ │ ├── platform_compiler.h │ │ ├── platform_debug.h │ │ ├── platform_dir.h │ │ ├── platform_exception.h │ │ ├── platform_fiber.h │ │ ├── platform_intrin.h │ │ ├── platform_memory.h │ │ ├── platform_socket.h │ │ ├── platform_stacktrace.h │ │ ├── platform_thread.h │ │ ├── platform_time.h │ │ ├── platform_winapi_string.h │ │ ├── pow.h │ │ ├── rangecombiner.h │ │ ├── src/ │ │ │ ├── assert.cpp │ │ │ ├── log.cpp │ │ │ ├── murmur3.cpp │ │ │ ├── platform_posix_debug.cpp │ │ │ ├── platform_posix_dir.cpp │ │ │ ├── platform_posix_exception.cpp │ │ │ ├── platform_posix_fiber.cpp │ │ │ ├── platform_posix_memory.cpp │ │ │ ├── platform_posix_socket.cpp │ │ │ ├── platform_posix_stacktrace.cpp │ │ │ ├── platform_posix_thread.cpp │ │ │ ├── platform_posix_time.cpp │ │ │ ├── platform_win_debug.cpp │ │ │ ├── platform_win_dir.cpp │ │ │ ├── platform_win_exception.cpp │ │ │ ├── platform_win_fiber.cpp │ │ │ ├── platform_win_memory.cpp │ │ │ ├── platform_win_socket.cpp │ │ │ ├── platform_win_stacktrace.cpp │ │ │ ├── platform_win_thread.cpp │ │ │ └── platform_win_time.cpp │ │ ├── structsize.h │ │ ├── strutils.h │ │ ├── teenyheap.h │ │ ├── tga_encoder.cpp │ │ ├── tga_encoder.h │ │ ├── type_list.h │ │ ├── type_traits.h │ │ ├── vulkan_hpp.h │ │ ├── xxhash.c │ │ └── xxhash.h │ ├── decaf-cli/ │ │ ├── CMakeLists.txt │ │ ├── config.cpp │ │ ├── config.h │ │ ├── decafcli.cpp │ │ ├── decafcli.h │ │ └── main.cpp │ ├── decaf-qt/ │ │ ├── CMakeLists.txt │ │ ├── resources/ │ │ │ └── resources.qrc │ │ ├── src/ │ │ │ ├── aboutdialog.h │ │ │ ├── debugger/ │ │ │ │ ├── addresstextdocumentwidget.cpp │ │ │ │ ├── addresstextdocumentwidget.h │ │ │ │ ├── breakpointsmodel.h │ │ │ │ ├── breakpointswindow.cpp │ │ │ │ ├── breakpointswindow.h │ │ │ │ ├── debugdata.cpp │ │ │ │ ├── debugdata.h │ │ │ │ ├── debuggershortcuts.h │ │ │ │ ├── debuggerwindow.cpp │ │ │ │ ├── debuggerwindow.h │ │ │ │ ├── disassemblywidget.cpp │ │ │ │ ├── disassemblywidget.h │ │ │ │ ├── disassemblywindow.cpp │ │ │ │ ├── disassemblywindow.h │ │ │ │ ├── functionsmodel.h │ │ │ │ ├── functionswindow.cpp │ │ │ │ ├── functionswindow.h │ │ │ │ ├── jitprofilingmodel.h │ │ │ │ ├── jitprofilingwindow.cpp │ │ │ │ ├── jitprofilingwindow.h │ │ │ │ ├── memorywidget.cpp │ │ │ │ ├── memorywidget.h │ │ │ │ ├── memorywindow.cpp │ │ │ │ ├── memorywindow.h │ │ │ │ ├── registerswindow.cpp │ │ │ │ ├── registerswindow.h │ │ │ │ ├── segmentsmodel.h │ │ │ │ ├── segmentswindow.cpp │ │ │ │ ├── segmentswindow.h │ │ │ │ ├── stackwidget.cpp │ │ │ │ ├── stackwidget.h │ │ │ │ ├── stackwindow.cpp │ │ │ │ ├── stackwindow.h │ │ │ │ ├── threadsmodel.h │ │ │ │ ├── threadswindow.cpp │ │ │ │ ├── threadswindow.h │ │ │ │ ├── voicesmodel.h │ │ │ │ ├── voiceswindow.cpp │ │ │ │ └── voiceswindow.h │ │ │ ├── decafinterface.cpp │ │ │ ├── decafinterface.h │ │ │ ├── erreuladriver.cpp │ │ │ ├── erreuladriver.h │ │ │ ├── inputdriver.cpp │ │ │ ├── inputdriver.h │ │ │ ├── inputeventfilter.h │ │ │ ├── main.cpp │ │ │ ├── mainwindow.cpp │ │ │ ├── mainwindow.h │ │ │ ├── renderwidget.cpp │ │ │ ├── renderwidget.h │ │ │ ├── settings/ │ │ │ │ ├── audiosettingswidget.cpp │ │ │ │ ├── audiosettingswidget.h │ │ │ │ ├── colourlineedit.h │ │ │ │ ├── contentsettingswidget.cpp │ │ │ │ ├── contentsettingswidget.h │ │ │ │ ├── debugsettingswidget.cpp │ │ │ │ ├── debugsettingswidget.h │ │ │ │ ├── displaysettingswidget.cpp │ │ │ │ ├── displaysettingswidget.h │ │ │ │ ├── inputsettingswidget.cpp │ │ │ │ ├── inputsettingswidget.h │ │ │ │ ├── loggingsettingswidget.cpp │ │ │ │ ├── loggingsettingswidget.h │ │ │ │ ├── settingsdialog.cpp │ │ │ │ ├── settingsdialog.h │ │ │ │ ├── settingswidget.h │ │ │ │ ├── systemsettingswidget.cpp │ │ │ │ └── systemsettingswidget.h │ │ │ ├── settings.cpp │ │ │ ├── settings.h │ │ │ ├── softwarekeyboarddriver.cpp │ │ │ ├── softwarekeyboarddriver.h │ │ │ ├── sounddriver.cpp │ │ │ ├── sounddriver.h │ │ │ ├── tgahandler.cpp │ │ │ ├── tgahandler.h │ │ │ ├── titlelistmodel.h │ │ │ ├── titlelistscanner.h │ │ │ ├── titlelistwidget.cpp │ │ │ └── titlelistwidget.h │ │ └── ui/ │ │ ├── about.ui │ │ ├── audiosettings.ui │ │ ├── contentsettings.ui │ │ ├── debugger/ │ │ │ ├── breakpointswindow.ui │ │ │ ├── debuggerwindow.ui │ │ │ ├── disassemblywindow.ui │ │ │ ├── functionswindow.ui │ │ │ ├── jitprofilingwindow.ui │ │ │ ├── memorywindow.ui │ │ │ ├── segmentswindow.ui │ │ │ ├── stackwindow.ui │ │ │ ├── threadswindow.ui │ │ │ └── voiceswindow.ui │ │ ├── debugsettings.ui │ │ ├── displaysettings.ui │ │ ├── inputsettings.ui │ │ ├── loggingsettings.ui │ │ ├── mainwindow.ui │ │ ├── settings.ui │ │ ├── systemsettings.ui │ │ └── titlelist.ui │ ├── decaf-sdl/ │ │ ├── CMakeLists.txt │ │ ├── clilog.h │ │ ├── config.cpp │ │ ├── config.h │ │ ├── decafsdl.cpp │ │ ├── decafsdl.h │ │ ├── decafsdl_input.cpp │ │ ├── decafsdl_posix.cpp │ │ ├── decafsdl_sound.cpp │ │ ├── decafsdl_sound.h │ │ ├── decafsdl_win.cpp │ │ └── main.cpp │ ├── decaf_buildinfo.h.in │ ├── decaf_game.h │ ├── libconfig/ │ │ ├── CMakeLists.txt │ │ ├── config_excmd.h │ │ ├── config_toml.h │ │ └── src/ │ │ ├── loader_excmd.cpp │ │ └── loader_toml.cpp │ ├── libcpu/ │ │ ├── CMakeLists.txt │ │ ├── address.h │ │ ├── be2_array.h │ │ ├── be2_atomic.h │ │ ├── be2_struct.h │ │ ├── be2_val.h │ │ ├── cpu.h │ │ ├── cpu.natvis │ │ ├── cpu_breakpoints.h │ │ ├── cpu_config.h │ │ ├── cpu_control.h │ │ ├── cpu_formatters.h │ │ ├── espresso/ │ │ │ ├── espresso_disassembler.cpp │ │ │ ├── espresso_disassembler.h │ │ │ ├── espresso_instruction.h │ │ │ ├── espresso_instruction_aliases.inl │ │ │ ├── espresso_instruction_definitions.inl │ │ │ ├── espresso_instruction_fields.inl │ │ │ ├── espresso_instructionid.h │ │ │ ├── espresso_instructionset.cpp │ │ │ ├── espresso_instructionset.h │ │ │ ├── espresso_registerformats.h │ │ │ └── espresso_spr.h │ │ ├── functionpointer.h │ │ ├── jit_stats.h │ │ ├── mem.h │ │ ├── memtrack.h │ │ ├── mmu.h │ │ ├── pointer.h │ │ ├── src/ │ │ │ ├── cpu.cpp │ │ │ ├── cpu_alarm.cpp │ │ │ ├── cpu_alarm.h │ │ │ ├── cpu_breakpoints.cpp │ │ │ ├── cpu_configstorage.cpp │ │ │ ├── cpu_configstorage.h │ │ │ ├── cpu_host_exception.cpp │ │ │ ├── cpu_host_exception.h │ │ │ ├── cpu_internal.h │ │ │ ├── cpu_interrupts.cpp │ │ │ ├── cpu_memtrack_posix.cpp │ │ │ ├── cpu_memtrack_win.cpp │ │ │ ├── cpu_mmu.cpp │ │ │ ├── cpu_systemcall.cpp │ │ │ ├── interpreter/ │ │ │ │ ├── interpreter.cpp │ │ │ │ ├── interpreter.h │ │ │ │ ├── interpreter_branch.cpp │ │ │ │ ├── interpreter_condition.cpp │ │ │ │ ├── interpreter_float.cpp │ │ │ │ ├── interpreter_float.h │ │ │ │ ├── interpreter_insreg.h │ │ │ │ ├── interpreter_integer.cpp │ │ │ │ ├── interpreter_internal.h │ │ │ │ ├── interpreter_loadstore.cpp │ │ │ │ ├── interpreter_pairedsingle.cpp │ │ │ │ └── interpreter_system.cpp │ │ │ ├── jit/ │ │ │ │ ├── binrec/ │ │ │ │ │ ├── jit_binrec.cpp │ │ │ │ │ ├── jit_binrec.h │ │ │ │ │ ├── jit_binrec_opt.cpp │ │ │ │ │ └── jit_binrec_verify.cpp │ │ │ │ ├── jit.cpp │ │ │ │ ├── jit.h │ │ │ │ ├── jit_backend.h │ │ │ │ ├── jit_codecache.cpp │ │ │ │ ├── jit_codecache.h │ │ │ │ └── jit_stats.cpp │ │ │ ├── memorymap.cpp │ │ │ ├── memorymap.h │ │ │ ├── statedbg.h │ │ │ ├── trace.cpp │ │ │ └── utils.h │ │ ├── state.h │ │ └── trace.h │ ├── libdecaf/ │ │ ├── CMakeLists.txt │ │ ├── decaf.h │ │ ├── decaf_config.h │ │ ├── decaf_content.h │ │ ├── decaf_debug_api.h │ │ ├── decaf_erreula.h │ │ ├── decaf_eventlistener.h │ │ ├── decaf_graphics.h │ │ ├── decaf_input.h │ │ ├── decaf_log.h │ │ ├── decaf_nullinputdriver.h │ │ ├── decaf_pm4replay.h │ │ ├── decaf_softwarekeyboard.h │ │ ├── decaf_sound.h │ │ └── src/ │ │ ├── cafe/ │ │ │ ├── cafe_ppc_interface.h │ │ │ ├── cafe_ppc_interface_invoke_guest.h │ │ │ ├── cafe_ppc_interface_invoke_host.h │ │ │ ├── cafe_ppc_interface_params.h │ │ │ ├── cafe_ppc_interface_trace_host.cpp │ │ │ ├── cafe_ppc_interface_trace_host.h │ │ │ ├── cafe_ppc_interface_varargs.h │ │ │ ├── cafe_stackobject.h │ │ │ ├── cafe_tinyheap.cpp │ │ │ ├── cafe_tinyheap.h │ │ │ ├── kernel/ │ │ │ │ ├── cafe_kernel.cpp │ │ │ │ ├── cafe_kernel.h │ │ │ │ ├── cafe_kernel_context.cpp │ │ │ │ ├── cafe_kernel_context.h │ │ │ │ ├── cafe_kernel_exception.cpp │ │ │ │ ├── cafe_kernel_exception.h │ │ │ │ ├── cafe_kernel_heap.cpp │ │ │ │ ├── cafe_kernel_heap.h │ │ │ │ ├── cafe_kernel_info.cpp │ │ │ │ ├── cafe_kernel_info.h │ │ │ │ ├── cafe_kernel_interrupts.cpp │ │ │ │ ├── cafe_kernel_interrupts.h │ │ │ │ ├── cafe_kernel_ipc.cpp │ │ │ │ ├── cafe_kernel_ipc.h │ │ │ │ ├── cafe_kernel_ipckdriver.cpp │ │ │ │ ├── cafe_kernel_ipckdriver.h │ │ │ │ ├── cafe_kernel_ipckdriverfifo.h │ │ │ │ ├── cafe_kernel_loader.cpp │ │ │ │ ├── cafe_kernel_loader.h │ │ │ │ ├── cafe_kernel_lock.cpp │ │ │ │ ├── cafe_kernel_lock.h │ │ │ │ ├── cafe_kernel_mcp.cpp │ │ │ │ ├── cafe_kernel_mcp.h │ │ │ │ ├── cafe_kernel_mmu.cpp │ │ │ │ ├── cafe_kernel_mmu.h │ │ │ │ ├── cafe_kernel_process.cpp │ │ │ │ ├── cafe_kernel_process.h │ │ │ │ ├── cafe_kernel_processid.cpp │ │ │ │ ├── cafe_kernel_processid.h │ │ │ │ ├── cafe_kernel_shareddata.cpp │ │ │ │ ├── cafe_kernel_shareddata.h │ │ │ │ ├── cafe_kernel_userdrivers.cpp │ │ │ │ └── cafe_kernel_userdrivers.h │ │ │ ├── libraries/ │ │ │ │ ├── avm/ │ │ │ │ │ ├── avm.cpp │ │ │ │ │ └── avm.h │ │ │ │ ├── cafe_hle.cpp │ │ │ │ ├── cafe_hle.h │ │ │ │ ├── cafe_hle_library.cpp │ │ │ │ ├── cafe_hle_library.h │ │ │ │ ├── cafe_hle_library_data.h │ │ │ │ ├── cafe_hle_library_function.h │ │ │ │ ├── cafe_hle_library_register.h │ │ │ │ ├── cafe_hle_library_symbol.h │ │ │ │ ├── cafe_hle_library_typeinfo.h │ │ │ │ ├── cafe_hle_stub.cpp │ │ │ │ ├── cafe_hle_stub.h │ │ │ │ ├── camera/ │ │ │ │ │ ├── camera.cpp │ │ │ │ │ ├── camera.h │ │ │ │ │ ├── camera_cam.cpp │ │ │ │ │ ├── camera_cam.h │ │ │ │ │ └── camera_enum.h │ │ │ │ ├── coreinit/ │ │ │ │ │ ├── coreinit.cpp │ │ │ │ │ ├── coreinit.h │ │ │ │ │ ├── coreinit_alarm.cpp │ │ │ │ │ ├── coreinit_alarm.h │ │ │ │ │ ├── coreinit_appio.cpp │ │ │ │ │ ├── coreinit_appio.h │ │ │ │ │ ├── coreinit_atomic.cpp │ │ │ │ │ ├── coreinit_atomic.h │ │ │ │ │ ├── coreinit_atomic64.cpp │ │ │ │ │ ├── coreinit_atomic64.h │ │ │ │ │ ├── coreinit_bsp.cpp │ │ │ │ │ ├── coreinit_bsp.h │ │ │ │ │ ├── coreinit_cache.cpp │ │ │ │ │ ├── coreinit_cache.h │ │ │ │ │ ├── coreinit_clipboard.cpp │ │ │ │ │ ├── coreinit_clipboard.h │ │ │ │ │ ├── coreinit_codegen.cpp │ │ │ │ │ ├── coreinit_codegen.h │ │ │ │ │ ├── coreinit_context.cpp │ │ │ │ │ ├── coreinit_context.h │ │ │ │ │ ├── coreinit_core.cpp │ │ │ │ │ ├── coreinit_core.h │ │ │ │ │ ├── coreinit_coroutine.cpp │ │ │ │ │ ├── coreinit_coroutine.h │ │ │ │ │ ├── coreinit_cosreport.cpp │ │ │ │ │ ├── coreinit_cosreport.h │ │ │ │ │ ├── coreinit_device.cpp │ │ │ │ │ ├── coreinit_device.h │ │ │ │ │ ├── coreinit_driver.cpp │ │ │ │ │ ├── coreinit_driver.h │ │ │ │ │ ├── coreinit_dynload.cpp │ │ │ │ │ ├── coreinit_dynload.h │ │ │ │ │ ├── coreinit_enum.h │ │ │ │ │ ├── coreinit_enum_string.cpp │ │ │ │ │ ├── coreinit_enum_string.h │ │ │ │ │ ├── coreinit_event.cpp │ │ │ │ │ ├── coreinit_event.h │ │ │ │ │ ├── coreinit_exception.cpp │ │ │ │ │ ├── coreinit_exception.h │ │ │ │ │ ├── coreinit_fastmutex.cpp │ │ │ │ │ ├── coreinit_fastmutex.h │ │ │ │ │ ├── coreinit_fiber.cpp │ │ │ │ │ ├── coreinit_fiber.h │ │ │ │ │ ├── coreinit_fs.cpp │ │ │ │ │ ├── coreinit_fs.h │ │ │ │ │ ├── coreinit_fs_client.cpp │ │ │ │ │ ├── coreinit_fs_client.h │ │ │ │ │ ├── coreinit_fs_cmd.cpp │ │ │ │ │ ├── coreinit_fs_cmd.h │ │ │ │ │ ├── coreinit_fs_cmdblock.cpp │ │ │ │ │ ├── coreinit_fs_cmdblock.h │ │ │ │ │ ├── coreinit_fs_cmdqueue.cpp │ │ │ │ │ ├── coreinit_fs_cmdqueue.h │ │ │ │ │ ├── coreinit_fs_driver.cpp │ │ │ │ │ ├── coreinit_fs_driver.h │ │ │ │ │ ├── coreinit_fs_statemachine.cpp │ │ │ │ │ ├── coreinit_fs_statemachine.h │ │ │ │ │ ├── coreinit_fsa.cpp │ │ │ │ │ ├── coreinit_fsa.h │ │ │ │ │ ├── coreinit_fsa_cmd.cpp │ │ │ │ │ ├── coreinit_fsa_cmd.h │ │ │ │ │ ├── coreinit_fsa_shim.cpp │ │ │ │ │ ├── coreinit_fsa_shim.h │ │ │ │ │ ├── coreinit_ghs.cpp │ │ │ │ │ ├── coreinit_ghs.h │ │ │ │ │ ├── coreinit_handle.cpp │ │ │ │ │ ├── coreinit_handle.h │ │ │ │ │ ├── coreinit_im.cpp │ │ │ │ │ ├── coreinit_im.h │ │ │ │ │ ├── coreinit_internal_idlock.cpp │ │ │ │ │ ├── coreinit_internal_idlock.h │ │ │ │ │ ├── coreinit_internal_queue.h │ │ │ │ │ ├── coreinit_interrupts.cpp │ │ │ │ │ ├── coreinit_interrupts.h │ │ │ │ │ ├── coreinit_ios.cpp │ │ │ │ │ ├── coreinit_ios.h │ │ │ │ │ ├── coreinit_ipcbufpool.cpp │ │ │ │ │ ├── coreinit_ipcbufpool.h │ │ │ │ │ ├── coreinit_ipcdriver.cpp │ │ │ │ │ ├── coreinit_ipcdriver.h │ │ │ │ │ ├── coreinit_lockedcache.cpp │ │ │ │ │ ├── coreinit_lockedcache.h │ │ │ │ │ ├── coreinit_log.cpp │ │ │ │ │ ├── coreinit_log.h │ │ │ │ │ ├── coreinit_mcp.cpp │ │ │ │ │ ├── coreinit_mcp.h │ │ │ │ │ ├── coreinit_memallocator.cpp │ │ │ │ │ ├── coreinit_memallocator.h │ │ │ │ │ ├── coreinit_memblockheap.cpp │ │ │ │ │ ├── coreinit_memblockheap.h │ │ │ │ │ ├── coreinit_memdefaultheap.cpp │ │ │ │ │ ├── coreinit_memdefaultheap.h │ │ │ │ │ ├── coreinit_memexpheap.cpp │ │ │ │ │ ├── coreinit_memexpheap.h │ │ │ │ │ ├── coreinit_memframeheap.cpp │ │ │ │ │ ├── coreinit_memframeheap.h │ │ │ │ │ ├── coreinit_memheap.cpp │ │ │ │ │ ├── coreinit_memheap.h │ │ │ │ │ ├── coreinit_memlist.cpp │ │ │ │ │ ├── coreinit_memlist.h │ │ │ │ │ ├── coreinit_memory.cpp │ │ │ │ │ ├── coreinit_memory.h │ │ │ │ │ ├── coreinit_memunitheap.cpp │ │ │ │ │ ├── coreinit_memunitheap.h │ │ │ │ │ ├── coreinit_messagequeue.cpp │ │ │ │ │ ├── coreinit_messagequeue.h │ │ │ │ │ ├── coreinit_mutex.cpp │ │ │ │ │ ├── coreinit_mutex.h │ │ │ │ │ ├── coreinit_osreport.cpp │ │ │ │ │ ├── coreinit_osreport.h │ │ │ │ │ ├── coreinit_overlayarena.cpp │ │ │ │ │ ├── coreinit_overlayarena.h │ │ │ │ │ ├── coreinit_rendezvous.cpp │ │ │ │ │ ├── coreinit_rendezvous.h │ │ │ │ │ ├── coreinit_scheduler.cpp │ │ │ │ │ ├── coreinit_scheduler.h │ │ │ │ │ ├── coreinit_screen.cpp │ │ │ │ │ ├── coreinit_screen.h │ │ │ │ │ ├── coreinit_screenfont.h │ │ │ │ │ ├── coreinit_semaphore.cpp │ │ │ │ │ ├── coreinit_semaphore.h │ │ │ │ │ ├── coreinit_snprintf.cpp │ │ │ │ │ ├── coreinit_snprintf.h │ │ │ │ │ ├── coreinit_spinlock.cpp │ │ │ │ │ ├── coreinit_spinlock.h │ │ │ │ │ ├── coreinit_systemheap.cpp │ │ │ │ │ ├── coreinit_systemheap.h │ │ │ │ │ ├── coreinit_systeminfo.cpp │ │ │ │ │ ├── coreinit_systeminfo.h │ │ │ │ │ ├── coreinit_systemmessagequeue.cpp │ │ │ │ │ ├── coreinit_systemmessagequeue.h │ │ │ │ │ ├── coreinit_taskqueue.cpp │ │ │ │ │ ├── coreinit_taskqueue.h │ │ │ │ │ ├── coreinit_thread.cpp │ │ │ │ │ ├── coreinit_thread.h │ │ │ │ │ ├── coreinit_time.cpp │ │ │ │ │ ├── coreinit_time.h │ │ │ │ │ ├── coreinit_userconfig.cpp │ │ │ │ │ └── coreinit_userconfig.h │ │ │ │ ├── dc/ │ │ │ │ │ ├── dc.cpp │ │ │ │ │ └── dc.h │ │ │ │ ├── dmae/ │ │ │ │ │ ├── dmae.cpp │ │ │ │ │ ├── dmae.h │ │ │ │ │ ├── dmae_enum.h │ │ │ │ │ ├── dmae_ring.cpp │ │ │ │ │ └── dmae_ring.h │ │ │ │ ├── drmapp/ │ │ │ │ │ ├── drmapp.cpp │ │ │ │ │ └── drmapp.h │ │ │ │ ├── erreula/ │ │ │ │ │ ├── erreula.cpp │ │ │ │ │ ├── erreula.h │ │ │ │ │ ├── erreula_enum.h │ │ │ │ │ ├── erreula_errorviewer.cpp │ │ │ │ │ └── erreula_errorviewer.h │ │ │ │ ├── ghs/ │ │ │ │ │ ├── cafe_ghs_enum.h │ │ │ │ │ ├── cafe_ghs_malloc.cpp │ │ │ │ │ ├── cafe_ghs_malloc.h │ │ │ │ │ ├── cafe_ghs_typeinfo.cpp │ │ │ │ │ └── cafe_ghs_typeinfo.h │ │ │ │ ├── gx2/ │ │ │ │ │ ├── gx2.cpp │ │ │ │ │ ├── gx2.h │ │ │ │ │ ├── gx2_addrlib.cpp │ │ │ │ │ ├── gx2_addrlib.h │ │ │ │ │ ├── gx2_aperture.cpp │ │ │ │ │ ├── gx2_aperture.h │ │ │ │ │ ├── gx2_cbpool.cpp │ │ │ │ │ ├── gx2_cbpool.h │ │ │ │ │ ├── gx2_clear.cpp │ │ │ │ │ ├── gx2_clear.h │ │ │ │ │ ├── gx2_contextstate.cpp │ │ │ │ │ ├── gx2_contextstate.h │ │ │ │ │ ├── gx2_counter.cpp │ │ │ │ │ ├── gx2_counter.h │ │ │ │ │ ├── gx2_debug.cpp │ │ │ │ │ ├── gx2_debug.h │ │ │ │ │ ├── gx2_debug_dds.cpp │ │ │ │ │ ├── gx2_debug_dds.h │ │ │ │ │ ├── gx2_debugcapture.cpp │ │ │ │ │ ├── gx2_debugcapture.h │ │ │ │ │ ├── gx2_display.cpp │ │ │ │ │ ├── gx2_display.h │ │ │ │ │ ├── gx2_displaylist.cpp │ │ │ │ │ ├── gx2_displaylist.h │ │ │ │ │ ├── gx2_draw.cpp │ │ │ │ │ ├── gx2_draw.h │ │ │ │ │ ├── gx2_enum.h │ │ │ │ │ ├── gx2_enum_string.cpp │ │ │ │ │ ├── gx2_enum_string.h │ │ │ │ │ ├── gx2_event.cpp │ │ │ │ │ ├── gx2_event.h │ │ │ │ │ ├── gx2_fence.cpp │ │ │ │ │ ├── gx2_fence.h │ │ │ │ │ ├── gx2_fetchshader.cpp │ │ │ │ │ ├── gx2_fetchshader.h │ │ │ │ │ ├── gx2_format.cpp │ │ │ │ │ ├── gx2_format.h │ │ │ │ │ ├── gx2_internal_flush.cpp │ │ │ │ │ ├── gx2_internal_flush.h │ │ │ │ │ ├── gx2_internal_gfd.cpp │ │ │ │ │ ├── gx2_internal_gfd.h │ │ │ │ │ ├── gx2_internal_pm4cap.cpp │ │ │ │ │ ├── gx2_internal_pm4cap.h │ │ │ │ │ ├── gx2_internal_writegatherptr.h │ │ │ │ │ ├── gx2_memory.cpp │ │ │ │ │ ├── gx2_memory.h │ │ │ │ │ ├── gx2_query.cpp │ │ │ │ │ ├── gx2_query.h │ │ │ │ │ ├── gx2_registers.cpp │ │ │ │ │ ├── gx2_registers.h │ │ │ │ │ ├── gx2_sampler.cpp │ │ │ │ │ ├── gx2_sampler.h │ │ │ │ │ ├── gx2_shaders.cpp │ │ │ │ │ ├── gx2_shaders.h │ │ │ │ │ ├── gx2_state.cpp │ │ │ │ │ ├── gx2_state.h │ │ │ │ │ ├── gx2_surface.cpp │ │ │ │ │ ├── gx2_surface.h │ │ │ │ │ ├── gx2_temp.cpp │ │ │ │ │ ├── gx2_temp.h │ │ │ │ │ ├── gx2_tessellation.cpp │ │ │ │ │ ├── gx2_tessellation.h │ │ │ │ │ ├── gx2_texture.cpp │ │ │ │ │ ├── gx2_texture.h │ │ │ │ │ ├── gx2r_buffer.cpp │ │ │ │ │ ├── gx2r_buffer.h │ │ │ │ │ ├── gx2r_displaylist.cpp │ │ │ │ │ ├── gx2r_displaylist.h │ │ │ │ │ ├── gx2r_draw.cpp │ │ │ │ │ ├── gx2r_draw.h │ │ │ │ │ ├── gx2r_memory.cpp │ │ │ │ │ ├── gx2r_memory.h │ │ │ │ │ ├── gx2r_resource.cpp │ │ │ │ │ ├── gx2r_resource.h │ │ │ │ │ ├── gx2r_shaders.cpp │ │ │ │ │ ├── gx2r_shaders.h │ │ │ │ │ ├── gx2r_surface.cpp │ │ │ │ │ └── gx2r_surface.h │ │ │ │ ├── h264/ │ │ │ │ │ ├── h264.cpp │ │ │ │ │ ├── h264.h │ │ │ │ │ ├── h264_bitstream.h │ │ │ │ │ ├── h264_decode.cpp │ │ │ │ │ ├── h264_decode.h │ │ │ │ │ ├── h264_decode_ffmpeg.cpp │ │ │ │ │ ├── h264_decode_ffmpeg.h │ │ │ │ │ ├── h264_decode_null.cpp │ │ │ │ │ ├── h264_decode_null.h │ │ │ │ │ ├── h264_enum.h │ │ │ │ │ ├── h264_stream.cpp │ │ │ │ │ └── h264_stream.h │ │ │ │ ├── lzma920/ │ │ │ │ │ ├── lzma920.cpp │ │ │ │ │ └── lzma920.h │ │ │ │ ├── mic/ │ │ │ │ │ ├── mic.cpp │ │ │ │ │ ├── mic.h │ │ │ │ │ ├── mic_mic.cpp │ │ │ │ │ └── mic_mic.h │ │ │ │ ├── nfc/ │ │ │ │ │ ├── nfc.cpp │ │ │ │ │ └── nfc.h │ │ │ │ ├── nio_prof/ │ │ │ │ │ ├── nio_prof.cpp │ │ │ │ │ └── nio_prof.h │ │ │ │ ├── nlibcurl/ │ │ │ │ │ ├── nlibcurl.cpp │ │ │ │ │ ├── nlibcurl.h │ │ │ │ │ ├── nlibcurl_curl.cpp │ │ │ │ │ ├── nlibcurl_curl.h │ │ │ │ │ ├── nlibcurl_easy.cpp │ │ │ │ │ └── nlibcurl_easy.h │ │ │ │ ├── nlibnss/ │ │ │ │ │ ├── nlibnss.cpp │ │ │ │ │ └── nlibnss.h │ │ │ │ ├── nlibnss2/ │ │ │ │ │ ├── nlibnss2.cpp │ │ │ │ │ └── nlibnss2.h │ │ │ │ ├── nn_ac/ │ │ │ │ │ ├── nn_ac.cpp │ │ │ │ │ ├── nn_ac.h │ │ │ │ │ ├── nn_ac_capi.cpp │ │ │ │ │ ├── nn_ac_capi.h │ │ │ │ │ ├── nn_ac_client.cpp │ │ │ │ │ ├── nn_ac_client.h │ │ │ │ │ ├── nn_ac_enum.h │ │ │ │ │ ├── nn_ac_service.cpp │ │ │ │ │ └── nn_ac_service.h │ │ │ │ ├── nn_acp/ │ │ │ │ │ ├── nn_acp.cpp │ │ │ │ │ ├── nn_acp.h │ │ │ │ │ ├── nn_acp_acpresult.cpp │ │ │ │ │ ├── nn_acp_acpresult.h │ │ │ │ │ ├── nn_acp_client.cpp │ │ │ │ │ ├── nn_acp_client.h │ │ │ │ │ ├── nn_acp_device.cpp │ │ │ │ │ ├── nn_acp_device.h │ │ │ │ │ ├── nn_acp_internal_driver.cpp │ │ │ │ │ ├── nn_acp_internal_driver.h │ │ │ │ │ ├── nn_acp_miscservice.cpp │ │ │ │ │ ├── nn_acp_miscservice.h │ │ │ │ │ ├── nn_acp_saveservice.cpp │ │ │ │ │ └── nn_acp_saveservice.h │ │ │ │ ├── nn_act/ │ │ │ │ │ ├── nn_act.cpp │ │ │ │ │ ├── nn_act.h │ │ │ │ │ ├── nn_act_accountloaderservice.cpp │ │ │ │ │ ├── nn_act_accountloaderservice.h │ │ │ │ │ ├── nn_act_accountmanagerservice.cpp │ │ │ │ │ ├── nn_act_accountmanagerservice.h │ │ │ │ │ ├── nn_act_client.cpp │ │ │ │ │ ├── nn_act_client.h │ │ │ │ │ ├── nn_act_clientstandardservice.cpp │ │ │ │ │ ├── nn_act_clientstandardservice.h │ │ │ │ │ ├── nn_act_serverstandardservice.cpp │ │ │ │ │ └── nn_act_serverstandardservice.h │ │ │ │ ├── nn_aoc/ │ │ │ │ │ ├── nn_aoc.cpp │ │ │ │ │ ├── nn_aoc.h │ │ │ │ │ ├── nn_aoc_enum.h │ │ │ │ │ ├── nn_aoc_lib.cpp │ │ │ │ │ └── nn_aoc_lib.h │ │ │ │ ├── nn_boss/ │ │ │ │ │ ├── nn_boss.cpp │ │ │ │ │ ├── nn_boss.h │ │ │ │ │ ├── nn_boss_account.cpp │ │ │ │ │ ├── nn_boss_account.h │ │ │ │ │ ├── nn_boss_almightystorage.cpp │ │ │ │ │ ├── nn_boss_almightystorage.h │ │ │ │ │ ├── nn_boss_almightytask.cpp │ │ │ │ │ ├── nn_boss_almightytask.h │ │ │ │ │ ├── nn_boss_client.cpp │ │ │ │ │ ├── nn_boss_client.h │ │ │ │ │ ├── nn_boss_dataname.cpp │ │ │ │ │ ├── nn_boss_dataname.h │ │ │ │ │ ├── nn_boss_enum.h │ │ │ │ │ ├── nn_boss_nbdltasksetting.cpp │ │ │ │ │ ├── nn_boss_nbdltasksetting.h │ │ │ │ │ ├── nn_boss_nettasksetting.cpp │ │ │ │ │ ├── nn_boss_nettasksetting.h │ │ │ │ │ ├── nn_boss_playloguploadtasksetting.cpp │ │ │ │ │ ├── nn_boss_playloguploadtasksetting.h │ │ │ │ │ ├── nn_boss_playreportsetting.cpp │ │ │ │ │ ├── nn_boss_playreportsetting.h │ │ │ │ │ ├── nn_boss_privilegedtask.cpp │ │ │ │ │ ├── nn_boss_privilegedtask.h │ │ │ │ │ ├── nn_boss_rawultasksetting.cpp │ │ │ │ │ ├── nn_boss_rawultasksetting.h │ │ │ │ │ ├── nn_boss_storage.cpp │ │ │ │ │ ├── nn_boss_storage.h │ │ │ │ │ ├── nn_boss_task.cpp │ │ │ │ │ ├── nn_boss_task.h │ │ │ │ │ ├── nn_boss_taskid.cpp │ │ │ │ │ ├── nn_boss_taskid.h │ │ │ │ │ ├── nn_boss_tasksetting.cpp │ │ │ │ │ ├── nn_boss_tasksetting.h │ │ │ │ │ ├── nn_boss_title.cpp │ │ │ │ │ ├── nn_boss_title.h │ │ │ │ │ ├── nn_boss_titleid.cpp │ │ │ │ │ └── nn_boss_titleid.h │ │ │ │ ├── nn_ccr/ │ │ │ │ │ ├── nn_ccr.cpp │ │ │ │ │ └── nn_ccr.h │ │ │ │ ├── nn_cmpt/ │ │ │ │ │ ├── nn_cmpt.cpp │ │ │ │ │ ├── nn_cmpt.h │ │ │ │ │ ├── nn_cmpt_enum.h │ │ │ │ │ ├── nn_cmpt_lib.cpp │ │ │ │ │ └── nn_cmpt_lib.h │ │ │ │ ├── nn_dlp/ │ │ │ │ │ ├── nn_dlp.cpp │ │ │ │ │ └── nn_dlp.h │ │ │ │ ├── nn_ec/ │ │ │ │ │ ├── nn_ec.cpp │ │ │ │ │ ├── nn_ec.h │ │ │ │ │ ├── nn_ec_catalog.cpp │ │ │ │ │ ├── nn_ec_catalog.h │ │ │ │ │ ├── nn_ec_itemlist.cpp │ │ │ │ │ ├── nn_ec_itemlist.h │ │ │ │ │ ├── nn_ec_lib.cpp │ │ │ │ │ ├── nn_ec_lib.h │ │ │ │ │ ├── nn_ec_memorymanager.cpp │ │ │ │ │ ├── nn_ec_memorymanager.h │ │ │ │ │ ├── nn_ec_money.cpp │ │ │ │ │ ├── nn_ec_money.h │ │ │ │ │ ├── nn_ec_noncopyable.h │ │ │ │ │ ├── nn_ec_query.cpp │ │ │ │ │ ├── nn_ec_query.h │ │ │ │ │ ├── nn_ec_result.h │ │ │ │ │ ├── nn_ec_rootobject.cpp │ │ │ │ │ ├── nn_ec_rootobject.h │ │ │ │ │ ├── nn_ec_shoppingcatalog.cpp │ │ │ │ │ └── nn_ec_shoppingcatalog.h │ │ │ │ ├── nn_fp/ │ │ │ │ │ ├── nn_fp.cpp │ │ │ │ │ ├── nn_fp.h │ │ │ │ │ ├── nn_fp_lib.cpp │ │ │ │ │ └── nn_fp_lib.h │ │ │ │ ├── nn_hai/ │ │ │ │ │ ├── nn_hai.cpp │ │ │ │ │ └── nn_hai.h │ │ │ │ ├── nn_hpad/ │ │ │ │ │ ├── nn_hpad.cpp │ │ │ │ │ └── nn_hpad.h │ │ │ │ ├── nn_idbe/ │ │ │ │ │ ├── nn_idbe.cpp │ │ │ │ │ └── nn_idbe.h │ │ │ │ ├── nn_ndm/ │ │ │ │ │ ├── nn_ndm.cpp │ │ │ │ │ ├── nn_ndm.h │ │ │ │ │ ├── nn_ndm_client.cpp │ │ │ │ │ └── nn_ndm_client.h │ │ │ │ ├── nn_nets2/ │ │ │ │ │ ├── nn_nets2.cpp │ │ │ │ │ └── nn_nets2.h │ │ │ │ ├── nn_nfp/ │ │ │ │ │ ├── nn_nfp.cpp │ │ │ │ │ ├── nn_nfp.h │ │ │ │ │ ├── nn_nfp_enum.h │ │ │ │ │ ├── nn_nfp_lib.cpp │ │ │ │ │ └── nn_nfp_lib.h │ │ │ │ ├── nn_nim/ │ │ │ │ │ ├── nn_nim.cpp │ │ │ │ │ ├── nn_nim.h │ │ │ │ │ ├── nn_nim_client.cpp │ │ │ │ │ └── nn_nim_client.h │ │ │ │ ├── nn_olv/ │ │ │ │ │ ├── nn_olv.cpp │ │ │ │ │ ├── nn_olv.h │ │ │ │ │ ├── nn_olv_downloadedcommunitydata.cpp │ │ │ │ │ ├── nn_olv_downloadedcommunitydata.h │ │ │ │ │ ├── nn_olv_downloadeddatabase.cpp │ │ │ │ │ ├── nn_olv_downloadeddatabase.h │ │ │ │ │ ├── nn_olv_downloadedpostdata.cpp │ │ │ │ │ ├── nn_olv_downloadedpostdata.h │ │ │ │ │ ├── nn_olv_downloadedtopicdata.cpp │ │ │ │ │ ├── nn_olv_downloadedtopicdata.h │ │ │ │ │ ├── nn_olv_init.cpp │ │ │ │ │ ├── nn_olv_init.h │ │ │ │ │ ├── nn_olv_initializeparam.cpp │ │ │ │ │ ├── nn_olv_initializeparam.h │ │ │ │ │ ├── nn_olv_uploadeddatabase.cpp │ │ │ │ │ ├── nn_olv_uploadeddatabase.h │ │ │ │ │ ├── nn_olv_uploadedpostdata.cpp │ │ │ │ │ └── nn_olv_uploadedpostdata.h │ │ │ │ ├── nn_pdm/ │ │ │ │ │ ├── nn_pdm.cpp │ │ │ │ │ ├── nn_pdm.h │ │ │ │ │ ├── nn_pdm_client.cpp │ │ │ │ │ ├── nn_pdm_client.h │ │ │ │ │ ├── nn_pdm_cosservice.cpp │ │ │ │ │ └── nn_pdm_cosservice.h │ │ │ │ ├── nn_save/ │ │ │ │ │ ├── nn_save.cpp │ │ │ │ │ ├── nn_save.h │ │ │ │ │ ├── nn_save_cmd.cpp │ │ │ │ │ ├── nn_save_cmd.h │ │ │ │ │ ├── nn_save_path.cpp │ │ │ │ │ └── nn_save_path.h │ │ │ │ ├── nn_sl/ │ │ │ │ │ ├── nn_sl.cpp │ │ │ │ │ ├── nn_sl.h │ │ │ │ │ ├── nn_sl_drctransferrer.cpp │ │ │ │ │ ├── nn_sl_drctransferrer.h │ │ │ │ │ ├── nn_sl_lib.cpp │ │ │ │ │ └── nn_sl_lib.h │ │ │ │ ├── nn_spm/ │ │ │ │ │ ├── nn_spm.cpp │ │ │ │ │ ├── nn_spm.h │ │ │ │ │ ├── nn_spm_client.cpp │ │ │ │ │ ├── nn_spm_client.h │ │ │ │ │ ├── nn_spm_extendedstorageservice.cpp │ │ │ │ │ └── nn_spm_extendedstorageservice.h │ │ │ │ ├── nn_temp/ │ │ │ │ │ ├── nn_temp.cpp │ │ │ │ │ ├── nn_temp.h │ │ │ │ │ ├── nn_temp_enum.h │ │ │ │ │ ├── nn_temp_tempdir.cpp │ │ │ │ │ └── nn_temp_tempdir.h │ │ │ │ ├── nn_uds/ │ │ │ │ │ ├── nn_uds.cpp │ │ │ │ │ ├── nn_uds.h │ │ │ │ │ └── nn_uds_api.cpp │ │ │ │ ├── nn_vctl/ │ │ │ │ │ ├── nn_vctl.cpp │ │ │ │ │ ├── nn_vctl.h │ │ │ │ │ ├── nn_vctl_client.cpp │ │ │ │ │ └── nn_vctl_client.h │ │ │ │ ├── nsysccr/ │ │ │ │ │ ├── nsysccr.cpp │ │ │ │ │ └── nsysccr.h │ │ │ │ ├── nsyshid/ │ │ │ │ │ ├── nsyshid.cpp │ │ │ │ │ └── nsyshid.h │ │ │ │ ├── nsyskbd/ │ │ │ │ │ ├── nsyskbd.cpp │ │ │ │ │ ├── nsyskbd.h │ │ │ │ │ ├── nsyskbd_enum.h │ │ │ │ │ ├── nsyskbd_kpr.cpp │ │ │ │ │ ├── nsyskbd_kpr.h │ │ │ │ │ ├── nsyskbd_skbd.cpp │ │ │ │ │ └── nsyskbd_skbd.h │ │ │ │ ├── nsysnet/ │ │ │ │ │ ├── nsysnet.cpp │ │ │ │ │ ├── nsysnet.h │ │ │ │ │ ├── nsysnet_endian.cpp │ │ │ │ │ ├── nsysnet_enum.h │ │ │ │ │ ├── nsysnet_nssl.cpp │ │ │ │ │ ├── nsysnet_nssl.h │ │ │ │ │ ├── nsysnet_socket_lib.cpp │ │ │ │ │ └── nsysnet_socket_lib.h │ │ │ │ ├── nsysuhs/ │ │ │ │ │ ├── nsysuhs.cpp │ │ │ │ │ └── nsysuhs.h │ │ │ │ ├── nsysuvd/ │ │ │ │ │ ├── nsysuvd.cpp │ │ │ │ │ └── nsysuvd.h │ │ │ │ ├── ntag/ │ │ │ │ │ ├── ntag.cpp │ │ │ │ │ └── ntag.h │ │ │ │ ├── padscore/ │ │ │ │ │ ├── padscore.cpp │ │ │ │ │ ├── padscore.h │ │ │ │ │ ├── padscore_enum.h │ │ │ │ │ ├── padscore_kpad.cpp │ │ │ │ │ ├── padscore_kpad.h │ │ │ │ │ ├── padscore_wpad.cpp │ │ │ │ │ └── padscore_wpad.h │ │ │ │ ├── proc_ui/ │ │ │ │ │ ├── proc_ui.cpp │ │ │ │ │ ├── proc_ui.h │ │ │ │ │ ├── proc_ui_enum.h │ │ │ │ │ ├── proc_ui_messages.cpp │ │ │ │ │ └── proc_ui_messages.h │ │ │ │ ├── snd_core/ │ │ │ │ │ └── snd_core.h │ │ │ │ ├── snd_user/ │ │ │ │ │ └── snd_user.h │ │ │ │ ├── sndcore2/ │ │ │ │ │ ├── sndcore2.cpp │ │ │ │ │ ├── sndcore2.h │ │ │ │ │ ├── sndcore2_ai.cpp │ │ │ │ │ ├── sndcore2_ai.h │ │ │ │ │ ├── sndcore2_config.cpp │ │ │ │ │ ├── sndcore2_config.h │ │ │ │ │ ├── sndcore2_constants.h │ │ │ │ │ ├── sndcore2_device.cpp │ │ │ │ │ ├── sndcore2_device.h │ │ │ │ │ ├── sndcore2_enum.h │ │ │ │ │ ├── sndcore2_rmt.cpp │ │ │ │ │ ├── sndcore2_rmt.h │ │ │ │ │ ├── sndcore2_voice.cpp │ │ │ │ │ ├── sndcore2_voice.h │ │ │ │ │ ├── sndcore2_vs.cpp │ │ │ │ │ └── sndcore2_vs.h │ │ │ │ ├── snduser2/ │ │ │ │ │ ├── snduser2.cpp │ │ │ │ │ ├── snduser2.h │ │ │ │ │ ├── snduser2_axart.cpp │ │ │ │ │ ├── snduser2_axart.h │ │ │ │ │ ├── snduser2_axfx.h │ │ │ │ │ ├── snduser2_axfx_chorus.cpp │ │ │ │ │ ├── snduser2_axfx_chorus.h │ │ │ │ │ ├── snduser2_axfx_chorusexp.cpp │ │ │ │ │ ├── snduser2_axfx_chorusexp.h │ │ │ │ │ ├── snduser2_axfx_delay.cpp │ │ │ │ │ ├── snduser2_axfx_delay.h │ │ │ │ │ ├── snduser2_axfx_delayexp.cpp │ │ │ │ │ ├── snduser2_axfx_delayexp.h │ │ │ │ │ ├── snduser2_axfx_hooks.cpp │ │ │ │ │ ├── snduser2_axfx_hooks.h │ │ │ │ │ ├── snduser2_axfx_multichreverb.cpp │ │ │ │ │ ├── snduser2_axfx_multichreverb.h │ │ │ │ │ ├── snduser2_axfx_reverbhi.cpp │ │ │ │ │ ├── snduser2_axfx_reverbhi.h │ │ │ │ │ ├── snduser2_axfx_reverbhiexp.cpp │ │ │ │ │ ├── snduser2_axfx_reverbhiexp.h │ │ │ │ │ ├── snduser2_axfx_reverbstd.cpp │ │ │ │ │ ├── snduser2_axfx_reverbstd.h │ │ │ │ │ ├── snduser2_axfx_reverbstdexp.cpp │ │ │ │ │ ├── snduser2_axfx_reverbstdexp.h │ │ │ │ │ ├── snduser2_enum.h │ │ │ │ │ ├── snduser2_mix.cpp │ │ │ │ │ ├── snduser2_mix.h │ │ │ │ │ ├── snduser2_sp.cpp │ │ │ │ │ └── snduser2_sp.h │ │ │ │ ├── swkbd/ │ │ │ │ │ ├── swkbd.cpp │ │ │ │ │ ├── swkbd.h │ │ │ │ │ ├── swkbd_enum.h │ │ │ │ │ ├── swkbd_keyboard.cpp │ │ │ │ │ └── swkbd_keyboard.h │ │ │ │ ├── sysapp/ │ │ │ │ │ ├── sysapp.cpp │ │ │ │ │ ├── sysapp.h │ │ │ │ │ ├── sysapp_callerargs.cpp │ │ │ │ │ ├── sysapp_callerargs.h │ │ │ │ │ ├── sysapp_enum.h │ │ │ │ │ ├── sysapp_title.cpp │ │ │ │ │ └── sysapp_title.h │ │ │ │ ├── tcl/ │ │ │ │ │ ├── tcl.cpp │ │ │ │ │ ├── tcl.h │ │ │ │ │ ├── tcl_aperture.cpp │ │ │ │ │ ├── tcl_aperture.h │ │ │ │ │ ├── tcl_driver.cpp │ │ │ │ │ ├── tcl_driver.h │ │ │ │ │ ├── tcl_enum.h │ │ │ │ │ ├── tcl_interrupthandler.cpp │ │ │ │ │ ├── tcl_interrupthandler.h │ │ │ │ │ ├── tcl_register.cpp │ │ │ │ │ ├── tcl_register.h │ │ │ │ │ ├── tcl_ring.cpp │ │ │ │ │ └── tcl_ring.h │ │ │ │ ├── tve/ │ │ │ │ │ ├── tve.cpp │ │ │ │ │ └── tve.h │ │ │ │ ├── uac/ │ │ │ │ │ ├── uac.cpp │ │ │ │ │ └── uac.h │ │ │ │ ├── uac_rpl/ │ │ │ │ │ ├── uac_rpl.cpp │ │ │ │ │ └── uac_rpl.h │ │ │ │ ├── usb_mic/ │ │ │ │ │ ├── usb_mic.cpp │ │ │ │ │ └── usb_mic.h │ │ │ │ ├── uvc/ │ │ │ │ │ ├── uvc.cpp │ │ │ │ │ └── uvc.h │ │ │ │ ├── uvd/ │ │ │ │ │ ├── uvd.cpp │ │ │ │ │ └── uvd.h │ │ │ │ ├── vpad/ │ │ │ │ │ ├── vpad.cpp │ │ │ │ │ ├── vpad.h │ │ │ │ │ ├── vpad_controller.cpp │ │ │ │ │ ├── vpad_controller.h │ │ │ │ │ ├── vpad_enum.h │ │ │ │ │ ├── vpad_gyro.cpp │ │ │ │ │ ├── vpad_gyro.h │ │ │ │ │ ├── vpad_motor.cpp │ │ │ │ │ └── vpad_motor.h │ │ │ │ ├── vpadbase/ │ │ │ │ │ ├── vpadbase.cpp │ │ │ │ │ ├── vpadbase.h │ │ │ │ │ ├── vpadbase_controller.cpp │ │ │ │ │ └── vpadbase_controller.h │ │ │ │ └── zlib125/ │ │ │ │ ├── zlib125.cpp │ │ │ │ ├── zlib125.h │ │ │ │ └── zlib125_zlib.cpp │ │ │ ├── loader/ │ │ │ │ ├── cafe_loader_basics.cpp │ │ │ │ ├── cafe_loader_basics.h │ │ │ │ ├── cafe_loader_bounce.cpp │ │ │ │ ├── cafe_loader_bounce.h │ │ │ │ ├── cafe_loader_elffile.cpp │ │ │ │ ├── cafe_loader_elffile.h │ │ │ │ ├── cafe_loader_entry.cpp │ │ │ │ ├── cafe_loader_entry.h │ │ │ │ ├── cafe_loader_error.cpp │ │ │ │ ├── cafe_loader_error.h │ │ │ │ ├── cafe_loader_flush.cpp │ │ │ │ ├── cafe_loader_flush.h │ │ │ │ ├── cafe_loader_globals.cpp │ │ │ │ ├── cafe_loader_globals.h │ │ │ │ ├── cafe_loader_heap.cpp │ │ │ │ ├── cafe_loader_heap.h │ │ │ │ ├── cafe_loader_init.cpp │ │ │ │ ├── cafe_loader_init.h │ │ │ │ ├── cafe_loader_iop.cpp │ │ │ │ ├── cafe_loader_iop.h │ │ │ │ ├── cafe_loader_ipcldriver.cpp │ │ │ │ ├── cafe_loader_ipcldriver.h │ │ │ │ ├── cafe_loader_ipcldriverfifo.h │ │ │ │ ├── cafe_loader_link.cpp │ │ │ │ ├── cafe_loader_link.h │ │ │ │ ├── cafe_loader_loaded_rpl.h │ │ │ │ ├── cafe_loader_log.h │ │ │ │ ├── cafe_loader_minfileinfo.cpp │ │ │ │ ├── cafe_loader_minfileinfo.h │ │ │ │ ├── cafe_loader_prep.cpp │ │ │ │ ├── cafe_loader_prep.h │ │ │ │ ├── cafe_loader_purge.cpp │ │ │ │ ├── cafe_loader_purge.h │ │ │ │ ├── cafe_loader_query.cpp │ │ │ │ ├── cafe_loader_query.h │ │ │ │ ├── cafe_loader_reloc.cpp │ │ │ │ ├── cafe_loader_reloc.h │ │ │ │ ├── cafe_loader_rpl.h │ │ │ │ ├── cafe_loader_setup.cpp │ │ │ │ ├── cafe_loader_setup.h │ │ │ │ ├── cafe_loader_shared.cpp │ │ │ │ ├── cafe_loader_shared.h │ │ │ │ ├── cafe_loader_utils.h │ │ │ │ ├── cafe_loader_zlib.cpp │ │ │ │ └── cafe_loader_zlib.h │ │ │ └── nn/ │ │ │ ├── cafe_nn_ipc_bufferallocator.cpp │ │ │ ├── cafe_nn_ipc_bufferallocator.h │ │ │ ├── cafe_nn_ipc_client.cpp │ │ │ ├── cafe_nn_ipc_client.h │ │ │ ├── cafe_nn_ipc_client_command.h │ │ │ └── cafe_nn_os_criticalsection.h │ │ ├── debug_api/ │ │ │ ├── debug_api_analyse.cpp │ │ │ ├── debug_api_cafe.cpp │ │ │ ├── debug_api_controller.cpp │ │ │ ├── debug_api_controller.h │ │ │ ├── debug_api_cpu.cpp │ │ │ ├── debug_api_memory.cpp │ │ │ └── debug_api_pm4.cpp │ │ ├── debugger/ │ │ │ ├── debugger.cpp │ │ │ ├── debugger.h │ │ │ ├── debugger_server.h │ │ │ ├── debugger_server_gdb.cpp │ │ │ ├── debugger_server_gdb.h │ │ │ └── debugger_server_gdb_xml.inl │ │ ├── decaf.cpp │ │ ├── decaf_configstorage.cpp │ │ ├── decaf_configstorage.h │ │ ├── decaf_erreula.cpp │ │ ├── decaf_eventlistener.cpp │ │ ├── decaf_events.h │ │ ├── decaf_graphics.cpp │ │ ├── decaf_input.cpp │ │ ├── decaf_log.cpp │ │ ├── decaf_nullinputdriver.cpp │ │ ├── decaf_slc.cpp │ │ ├── decaf_slc.h │ │ ├── decaf_softwarekeyboard.cpp │ │ ├── decaf_sound.cpp │ │ ├── input/ │ │ │ ├── input.cpp │ │ │ └── input.h │ │ ├── ios/ │ │ │ ├── acp/ │ │ │ │ ├── ios_acp.cpp │ │ │ │ ├── ios_acp.h │ │ │ │ ├── ios_acp_client_save.cpp │ │ │ │ ├── ios_acp_client_save.h │ │ │ │ ├── ios_acp_enum.h │ │ │ │ ├── ios_acp_log.h │ │ │ │ ├── ios_acp_main_server.cpp │ │ │ │ ├── ios_acp_main_server.h │ │ │ │ ├── ios_acp_metaxml.cpp │ │ │ │ ├── ios_acp_metaxml.h │ │ │ │ ├── ios_acp_nn_miscservice.cpp │ │ │ │ ├── ios_acp_nn_miscservice.h │ │ │ │ ├── ios_acp_nn_saveservice.cpp │ │ │ │ ├── ios_acp_nn_saveservice.h │ │ │ │ ├── ios_acp_nnsm.cpp │ │ │ │ ├── ios_acp_nnsm.h │ │ │ │ ├── ios_acp_nnsm_ipc.cpp │ │ │ │ ├── ios_acp_nnsm_ipc.h │ │ │ │ ├── ios_acp_pdm_cosservice.cpp │ │ │ │ ├── ios_acp_pdm_cosservice.h │ │ │ │ ├── ios_acp_pdm_server.cpp │ │ │ │ ├── ios_acp_pdm_server.h │ │ │ │ ├── ios_acp_spm_extendedstorageservice.cpp │ │ │ │ └── ios_acp_spm_extendedstorageservice.h │ │ │ ├── auxil/ │ │ │ │ ├── ios_auxil.cpp │ │ │ │ ├── ios_auxil.h │ │ │ │ ├── ios_auxil_config.cpp │ │ │ │ ├── ios_auxil_config.h │ │ │ │ ├── ios_auxil_enum.h │ │ │ │ ├── ios_auxil_im.h │ │ │ │ ├── ios_auxil_im_device.cpp │ │ │ │ ├── ios_auxil_im_device.h │ │ │ │ ├── ios_auxil_im_request.h │ │ │ │ ├── ios_auxil_im_response.h │ │ │ │ ├── ios_auxil_im_thread.cpp │ │ │ │ ├── ios_auxil_im_thread.h │ │ │ │ ├── ios_auxil_usr_cfg.h │ │ │ │ ├── ios_auxil_usr_cfg_device.cpp │ │ │ │ ├── ios_auxil_usr_cfg_device.h │ │ │ │ ├── ios_auxil_usr_cfg_fs.cpp │ │ │ │ ├── ios_auxil_usr_cfg_fs.h │ │ │ │ ├── ios_auxil_usr_cfg_ipc.cpp │ │ │ │ ├── ios_auxil_usr_cfg_ipc.h │ │ │ │ ├── ios_auxil_usr_cfg_request.h │ │ │ │ ├── ios_auxil_usr_cfg_service_thread.cpp │ │ │ │ ├── ios_auxil_usr_cfg_service_thread.h │ │ │ │ ├── ios_auxil_usr_cfg_thread.cpp │ │ │ │ ├── ios_auxil_usr_cfg_thread.h │ │ │ │ └── ios_auxil_usr_cfg_types.h │ │ │ ├── bsp/ │ │ │ │ ├── ios_bsp.cpp │ │ │ │ ├── ios_bsp.h │ │ │ │ ├── ios_bsp_bsp_request.h │ │ │ │ ├── ios_bsp_bsp_response.h │ │ │ │ └── ios_bsp_enum.h │ │ │ ├── crypto/ │ │ │ │ ├── ios_crypto.cpp │ │ │ │ ├── ios_crypto.h │ │ │ │ ├── ios_crypto_enum.h │ │ │ │ ├── ios_crypto_ipc.cpp │ │ │ │ ├── ios_crypto_ipc.h │ │ │ │ ├── ios_crypto_log.h │ │ │ │ ├── ios_crypto_request.h │ │ │ │ └── ios_crypto_types.h │ │ │ ├── fpd/ │ │ │ │ ├── ios_fpd.cpp │ │ │ │ ├── ios_fpd.h │ │ │ │ ├── ios_fpd_act_accountdata.cpp │ │ │ │ ├── ios_fpd_act_accountdata.h │ │ │ │ ├── ios_fpd_act_accountloaderservice.cpp │ │ │ │ ├── ios_fpd_act_accountloaderservice.h │ │ │ │ ├── ios_fpd_act_accountmanagerservice.cpp │ │ │ │ ├── ios_fpd_act_accountmanagerservice.h │ │ │ │ ├── ios_fpd_act_clientstandardservice.cpp │ │ │ │ ├── ios_fpd_act_clientstandardservice.h │ │ │ │ ├── ios_fpd_act_server.cpp │ │ │ │ ├── ios_fpd_act_server.h │ │ │ │ ├── ios_fpd_act_serverstandardservice.cpp │ │ │ │ ├── ios_fpd_act_serverstandardservice.h │ │ │ │ └── ios_fpd_log.h │ │ │ ├── fs/ │ │ │ │ ├── ios_fs.cpp │ │ │ │ ├── ios_fs.h │ │ │ │ ├── ios_fs_enum.h │ │ │ │ ├── ios_fs_fsa.h │ │ │ │ ├── ios_fs_fsa_async_task.cpp │ │ │ │ ├── ios_fs_fsa_async_task.h │ │ │ │ ├── ios_fs_fsa_device.cpp │ │ │ │ ├── ios_fs_fsa_device.h │ │ │ │ ├── ios_fs_fsa_ipc.cpp │ │ │ │ ├── ios_fs_fsa_ipc.h │ │ │ │ ├── ios_fs_fsa_request.h │ │ │ │ ├── ios_fs_fsa_response.h │ │ │ │ ├── ios_fs_fsa_thread.cpp │ │ │ │ ├── ios_fs_fsa_thread.h │ │ │ │ ├── ios_fs_fsa_types.h │ │ │ │ ├── ios_fs_log.h │ │ │ │ ├── ios_fs_mutex.cpp │ │ │ │ ├── ios_fs_mutex.h │ │ │ │ ├── ios_fs_service_thread.cpp │ │ │ │ └── ios_fs_service_thread.h │ │ │ ├── ios.cpp │ │ │ ├── ios.h │ │ │ ├── ios_alarm_thread.cpp │ │ │ ├── ios_alarm_thread.h │ │ │ ├── ios_device.h │ │ │ ├── ios_enum.h │ │ │ ├── ios_enum_string.cpp │ │ │ ├── ios_enum_string.h │ │ │ ├── ios_error.h │ │ │ ├── ios_handlemanager.h │ │ │ ├── ios_ipc.h │ │ │ ├── ios_network_thread.cpp │ │ │ ├── ios_network_thread.h │ │ │ ├── ios_stackobject.h │ │ │ ├── ios_worker_thread.cpp │ │ │ ├── ios_worker_thread.h │ │ │ ├── kernel/ │ │ │ │ ├── ios_kernel.cpp │ │ │ │ ├── ios_kernel.h │ │ │ │ ├── ios_kernel_debug.cpp │ │ │ │ ├── ios_kernel_debug.h │ │ │ │ ├── ios_kernel_enum.h │ │ │ │ ├── ios_kernel_hardware.cpp │ │ │ │ ├── ios_kernel_hardware.h │ │ │ │ ├── ios_kernel_heap.cpp │ │ │ │ ├── ios_kernel_heap.h │ │ │ │ ├── ios_kernel_ipc.cpp │ │ │ │ ├── ios_kernel_ipc.h │ │ │ │ ├── ios_kernel_ipc_thread.cpp │ │ │ │ ├── ios_kernel_ipc_thread.h │ │ │ │ ├── ios_kernel_messagequeue.cpp │ │ │ │ ├── ios_kernel_messagequeue.h │ │ │ │ ├── ios_kernel_otp.cpp │ │ │ │ ├── ios_kernel_otp.h │ │ │ │ ├── ios_kernel_process.cpp │ │ │ │ ├── ios_kernel_process.h │ │ │ │ ├── ios_kernel_resourcemanager.cpp │ │ │ │ ├── ios_kernel_resourcemanager.h │ │ │ │ ├── ios_kernel_scheduler.cpp │ │ │ │ ├── ios_kernel_scheduler.h │ │ │ │ ├── ios_kernel_semaphore.cpp │ │ │ │ ├── ios_kernel_semaphore.h │ │ │ │ ├── ios_kernel_thread.cpp │ │ │ │ ├── ios_kernel_thread.h │ │ │ │ ├── ios_kernel_threadqueue.cpp │ │ │ │ ├── ios_kernel_threadqueue.h │ │ │ │ ├── ios_kernel_timer.cpp │ │ │ │ └── ios_kernel_timer.h │ │ │ ├── mcp/ │ │ │ │ ├── ios_mcp.cpp │ │ │ │ ├── ios_mcp.h │ │ │ │ ├── ios_mcp_config.cpp │ │ │ │ ├── ios_mcp_config.h │ │ │ │ ├── ios_mcp_enum.h │ │ │ │ ├── ios_mcp_ipc.cpp │ │ │ │ ├── ios_mcp_ipc.h │ │ │ │ ├── ios_mcp_mcp.h │ │ │ │ ├── ios_mcp_mcp_device.cpp │ │ │ │ ├── ios_mcp_mcp_device.h │ │ │ │ ├── ios_mcp_mcp_request.h │ │ │ │ ├── ios_mcp_mcp_response.h │ │ │ │ ├── ios_mcp_mcp_thread.cpp │ │ │ │ ├── ios_mcp_mcp_thread.h │ │ │ │ ├── ios_mcp_mcp_types.h │ │ │ │ ├── ios_mcp_pm_thread.cpp │ │ │ │ ├── ios_mcp_pm_thread.h │ │ │ │ ├── ios_mcp_ppc_thread.cpp │ │ │ │ ├── ios_mcp_ppc_thread.h │ │ │ │ ├── ios_mcp_title.cpp │ │ │ │ └── ios_mcp_title.h │ │ │ ├── net/ │ │ │ │ ├── ios_net.cpp │ │ │ │ ├── ios_net.h │ │ │ │ ├── ios_net_ac_main_server.cpp │ │ │ │ ├── ios_net_ac_main_server.h │ │ │ │ ├── ios_net_ac_service.cpp │ │ │ │ ├── ios_net_ac_service.h │ │ │ │ ├── ios_net_enum.h │ │ │ │ ├── ios_net_log.h │ │ │ │ ├── ios_net_ndm_server.cpp │ │ │ │ ├── ios_net_ndm_server.h │ │ │ │ ├── ios_net_socket.h │ │ │ │ ├── ios_net_socket_async_task.cpp │ │ │ │ ├── ios_net_socket_async_task.h │ │ │ │ ├── ios_net_socket_device.cpp │ │ │ │ ├── ios_net_socket_device.h │ │ │ │ ├── ios_net_socket_request.h │ │ │ │ ├── ios_net_socket_response.h │ │ │ │ ├── ios_net_socket_thread.cpp │ │ │ │ ├── ios_net_socket_thread.h │ │ │ │ ├── ios_net_socket_types.h │ │ │ │ ├── ios_net_soshim.cpp │ │ │ │ ├── ios_net_soshim.h │ │ │ │ ├── ios_net_subsys.cpp │ │ │ │ └── ios_net_subsys.h │ │ │ ├── nim/ │ │ │ │ ├── ios_nim.cpp │ │ │ │ ├── ios_nim.h │ │ │ │ ├── ios_nim_boss_privilegedservice.cpp │ │ │ │ ├── ios_nim_boss_privilegedservice.h │ │ │ │ ├── ios_nim_boss_server.cpp │ │ │ │ ├── ios_nim_boss_server.h │ │ │ │ ├── ios_nim_log.h │ │ │ │ ├── ios_nim_nim_server.cpp │ │ │ │ └── ios_nim_nim_server.h │ │ │ ├── nn/ │ │ │ │ ├── ios_nn.cpp │ │ │ │ ├── ios_nn.h │ │ │ │ ├── ios_nn_criticalsection.cpp │ │ │ │ ├── ios_nn_criticalsection.h │ │ │ │ ├── ios_nn_ipc_server.cpp │ │ │ │ ├── ios_nn_ipc_server.h │ │ │ │ ├── ios_nn_ipc_server_command.h │ │ │ │ ├── ios_nn_recursivemutex.cpp │ │ │ │ ├── ios_nn_recursivemutex.h │ │ │ │ ├── ios_nn_thread.cpp │ │ │ │ ├── ios_nn_thread.h │ │ │ │ ├── ios_nn_tls.cpp │ │ │ │ └── ios_nn_tls.h │ │ │ ├── nsec/ │ │ │ │ ├── ios_nsec.cpp │ │ │ │ ├── ios_nsec.h │ │ │ │ ├── ios_nsec_enum.h │ │ │ │ ├── ios_nsec_log.h │ │ │ │ ├── ios_nsec_nssl.h │ │ │ │ ├── ios_nsec_nssl_certstore.cpp │ │ │ │ ├── ios_nsec_nssl_certstore.h │ │ │ │ ├── ios_nsec_nssl_device.cpp │ │ │ │ ├── ios_nsec_nssl_device.h │ │ │ │ ├── ios_nsec_nssl_request.h │ │ │ │ ├── ios_nsec_nssl_response.h │ │ │ │ ├── ios_nsec_nssl_thread.cpp │ │ │ │ ├── ios_nsec_nssl_thread.h │ │ │ │ └── ios_nsec_nssl_types.h │ │ │ ├── pad/ │ │ │ │ ├── ios_pad.cpp │ │ │ │ ├── ios_pad.h │ │ │ │ ├── ios_pad_btrm_device.cpp │ │ │ │ ├── ios_pad_btrm_device.h │ │ │ │ ├── ios_pad_btrm_request.h │ │ │ │ ├── ios_pad_btrm_response.h │ │ │ │ ├── ios_pad_enum.h │ │ │ │ └── ios_pad_log.h │ │ │ ├── test/ │ │ │ │ ├── ios_test.cpp │ │ │ │ └── ios_test.h │ │ │ └── usb/ │ │ │ ├── ios_usb.cpp │ │ │ └── ios_usb.h │ │ ├── nn/ │ │ │ ├── ac/ │ │ │ │ ├── nn_ac_result.h │ │ │ │ └── nn_ac_service.h │ │ │ ├── acp/ │ │ │ │ ├── nn_acp_enum.h │ │ │ │ ├── nn_acp_miscservice.h │ │ │ │ ├── nn_acp_result.h │ │ │ │ ├── nn_acp_saveservice.h │ │ │ │ └── nn_acp_types.h │ │ │ ├── act/ │ │ │ │ ├── nn_act_accountloaderservice.h │ │ │ │ ├── nn_act_accountmanagerservice.h │ │ │ │ ├── nn_act_clientstandardservice.h │ │ │ │ ├── nn_act_enum.h │ │ │ │ ├── nn_act_result.h │ │ │ │ ├── nn_act_serverstandardservice.h │ │ │ │ └── nn_act_types.h │ │ │ ├── boss/ │ │ │ │ ├── nn_boss_management_service.h │ │ │ │ ├── nn_boss_private_service.h │ │ │ │ ├── nn_boss_privileged_service.h │ │ │ │ ├── nn_boss_result.h │ │ │ │ ├── nn_boss_service.h │ │ │ │ ├── nn_boss_test_service.h │ │ │ │ └── nn_boss_types.h │ │ │ ├── dbg/ │ │ │ │ ├── nn_dbg_result_string.cpp │ │ │ │ └── nn_dbg_result_string.h │ │ │ ├── ffl/ │ │ │ │ └── nn_ffl_miidata.h │ │ │ ├── ios/ │ │ │ │ ├── nn_ios_error.cpp │ │ │ │ └── nn_ios_error.h │ │ │ ├── ipc/ │ │ │ │ ├── nn_ipc_command.h │ │ │ │ ├── nn_ipc_format.h │ │ │ │ ├── nn_ipc_managedbuffer.h │ │ │ │ ├── nn_ipc_result.h │ │ │ │ └── nn_ipc_service.h │ │ │ ├── nfp/ │ │ │ │ └── nn_nfp_result.h │ │ │ ├── nn_result.h │ │ │ ├── olv/ │ │ │ │ └── nn_olv_result.h │ │ │ ├── pdm/ │ │ │ │ ├── nn_pdm_cosservice.h │ │ │ │ └── nn_pdm_result.h │ │ │ └── spm/ │ │ │ └── nn_spm_extendedstorageservice.h │ │ ├── traceiter.h │ │ └── vfs/ │ │ ├── vfs_device.h │ │ ├── vfs_directoryiterator.h │ │ ├── vfs_error.h │ │ ├── vfs_filehandle.h │ │ ├── vfs_host_device.cpp │ │ ├── vfs_host_device.h │ │ ├── vfs_host_directoryiterator.cpp │ │ ├── vfs_host_directoryiterator.h │ │ ├── vfs_host_filehandle.cpp │ │ ├── vfs_host_filehandle.h │ │ ├── vfs_link_device.cpp │ │ ├── vfs_link_device.h │ │ ├── vfs_overlay_device.cpp │ │ ├── vfs_overlay_device.h │ │ ├── vfs_overlay_directoryiterator.cpp │ │ ├── vfs_overlay_directoryiterator.h │ │ ├── vfs_path.cpp │ │ ├── vfs_path.h │ │ ├── vfs_pathiterator.cpp │ │ ├── vfs_pathiterator.h │ │ ├── vfs_permissions.h │ │ ├── vfs_result.h │ │ ├── vfs_status.h │ │ ├── vfs_virtual_device.cpp │ │ ├── vfs_virtual_device.h │ │ ├── vfs_virtual_directory.h │ │ ├── vfs_virtual_directoryiterator.cpp │ │ ├── vfs_virtual_directoryiterator.h │ │ ├── vfs_virtual_file.h │ │ ├── vfs_virtual_filehandle.cpp │ │ ├── vfs_virtual_filehandle.h │ │ ├── vfs_virtual_mounteddevice.h │ │ └── vfs_virtual_node.h │ ├── libgfd/ │ │ ├── CMakeLists.txt │ │ ├── gfd.h │ │ ├── gfd_enum.h │ │ ├── gfd_gx2.h │ │ └── src/ │ │ ├── gfd_read.cpp │ │ └── gfd_write.cpp │ └── libgpu/ │ ├── CMakeLists.txt │ ├── gpu.h │ ├── gpu7_displaylayout.h │ ├── gpu7_tiling.h │ ├── gpu7_tiling_cpu.h │ ├── gpu7_tiling_vulkan.h │ ├── gpu_config.h │ ├── gpu_graphicsdriver.h │ ├── gpu_ih.h │ ├── gpu_memory.h │ ├── gpu_ringbuffer.h │ ├── gpu_tiling.h │ ├── gpu_vulkandriver.h │ ├── latte/ │ │ ├── latte_constants.h │ │ ├── latte_contextstate.h │ │ ├── latte_disassembler.h │ │ ├── latte_enum_as_string.cpp │ │ ├── latte_enum_as_string.h │ │ ├── latte_enum_cb.h │ │ ├── latte_enum_common.h │ │ ├── latte_enum_cp.h │ │ ├── latte_enum_db.h │ │ ├── latte_enum_pa.h │ │ ├── latte_enum_pm4.h │ │ ├── latte_enum_spi.h │ │ ├── latte_enum_sq.h │ │ ├── latte_enum_vgt.h │ │ ├── latte_formats.h │ │ ├── latte_instructions.h │ │ ├── latte_instructions_def.inl │ │ ├── latte_pm4.h │ │ ├── latte_pm4_commands.h │ │ ├── latte_pm4_reader.h │ │ ├── latte_pm4_sizer.h │ │ ├── latte_pm4_writer.h │ │ ├── latte_registers.h │ │ ├── latte_registers_cb.h │ │ ├── latte_registers_cp.h │ │ ├── latte_registers_db.h │ │ ├── latte_registers_pa.h │ │ ├── latte_registers_spi.h │ │ ├── latte_registers_sq.h │ │ ├── latte_registers_sx.h │ │ ├── latte_registers_ta.h │ │ ├── latte_registers_td.h │ │ └── latte_registers_vgt.h │ ├── src/ │ │ ├── gpu7_displaylayout.cpp │ │ ├── gpu7_tiling.cpp │ │ ├── gpu7_tiling_cpu.cpp │ │ ├── gpu7_tiling_vulkan.cpp │ │ ├── gpu_clock.h │ │ ├── gpu_configstorage.cpp │ │ ├── gpu_configstorage.h │ │ ├── gpu_event.cpp │ │ ├── gpu_event.h │ │ ├── gpu_graphicsdriver.cpp │ │ ├── gpu_ih.cpp │ │ ├── gpu_ringbuffer.cpp │ │ ├── gpu_tiling.cpp │ │ ├── latte/ │ │ │ ├── latte_decoders.h │ │ │ ├── latte_disassembler.cpp │ │ │ ├── latte_disassembler_alu.cpp │ │ │ ├── latte_disassembler_export.cpp │ │ │ ├── latte_disassembler_state.h │ │ │ ├── latte_disassembler_tex.cpp │ │ │ ├── latte_disassembler_vtx.cpp │ │ │ ├── latte_endian.h │ │ │ ├── latte_formats.cpp │ │ │ ├── latte_instructions.cpp │ │ │ └── latte_shaderparser.h │ │ ├── null/ │ │ │ ├── null_driver.cpp │ │ │ └── null_driver.h │ │ ├── pm4_processor.cpp │ │ ├── pm4_processor.h │ │ ├── spirv/ │ │ │ ├── spirv_alu_op2.cpp │ │ │ ├── spirv_alu_op3.cpp │ │ │ ├── spirv_alu_reduc.cpp │ │ │ ├── spirv_cf.cpp │ │ │ ├── spirv_export.cpp │ │ │ ├── spirv_helpers.cpp │ │ │ ├── spirv_pushconstants.h │ │ │ ├── spirv_shaderspvbuilder.h │ │ │ ├── spirv_spvbuilder.h │ │ │ ├── spirv_tex.cpp │ │ │ ├── spirv_translate.h │ │ │ ├── spirv_transpiler.cpp │ │ │ ├── spirv_transpiler.h │ │ │ └── spirv_vtx.cpp │ │ └── vulkan/ │ │ ├── vk_mem_alloc.cpp │ │ ├── vk_mem_alloc.h │ │ ├── vk_mem_alloc_decaf.h │ │ ├── vulkan_attribbuffers.cpp │ │ ├── vulkan_debug.cpp │ │ ├── vulkan_descs.h │ │ ├── vulkan_display.cpp │ │ ├── vulkan_displayshaders.h │ │ ├── vulkan_draw.cpp │ │ ├── vulkan_driver.cpp │ │ ├── vulkan_driver.h │ │ ├── vulkan_fences.cpp │ │ ├── vulkan_framebuffer.cpp │ │ ├── vulkan_indices.cpp │ │ ├── vulkan_memcache.cpp │ │ ├── vulkan_memtracker.h │ │ ├── vulkan_pipelinelayouts.cpp │ │ ├── vulkan_pipelines.cpp │ │ ├── vulkan_pm4.cpp │ │ ├── vulkan_renderpass.cpp │ │ ├── vulkan_samplers.cpp │ │ ├── vulkan_shaderbuffers.cpp │ │ ├── vulkan_shaders.cpp │ │ ├── vulkan_staging.cpp │ │ ├── vulkan_streamout.cpp │ │ ├── vulkan_surface.cpp │ │ ├── vulkan_swapchain.cpp │ │ ├── vulkan_textures.cpp │ │ ├── vulkan_tiling.cpp │ │ ├── vulkan_utils.cpp │ │ ├── vulkan_utils.h │ │ ├── vulkan_validate.cpp │ │ └── vulkan_viewscissor.cpp │ └── vulkan_shaders/ │ └── gpu7_tiling.comp.glsl ├── tests/ │ ├── CMakeLists.txt │ ├── cpu/ │ │ ├── CMakeLists.txt │ │ ├── data/ │ │ │ ├── input/ │ │ │ │ ├── add │ │ │ │ ├── addc │ │ │ │ ├── adde │ │ │ │ ├── addi │ │ │ │ ├── addic │ │ │ │ ├── addicx │ │ │ │ ├── addis │ │ │ │ ├── addme │ │ │ │ ├── addze │ │ │ │ ├── and │ │ │ │ ├── andc │ │ │ │ ├── andi │ │ │ │ ├── andis │ │ │ │ ├── cmp │ │ │ │ ├── cmpi │ │ │ │ ├── cmpl │ │ │ │ ├── cmpli │ │ │ │ ├── cntlzw │ │ │ │ ├── crand │ │ │ │ ├── crandc │ │ │ │ ├── creqv │ │ │ │ ├── crnand │ │ │ │ ├── crnor │ │ │ │ ├── cror │ │ │ │ ├── crorc │ │ │ │ ├── crxor │ │ │ │ ├── divw │ │ │ │ ├── divwu │ │ │ │ ├── eqv │ │ │ │ ├── extsb │ │ │ │ ├── extsh │ │ │ │ ├── fabs │ │ │ │ ├── fadd │ │ │ │ ├── fadds │ │ │ │ ├── fctiw │ │ │ │ ├── fctiwz │ │ │ │ ├── fdiv │ │ │ │ ├── fdivs │ │ │ │ ├── fmadd │ │ │ │ ├── fmadds │ │ │ │ ├── fmr │ │ │ │ ├── fmsub │ │ │ │ ├── fmsubs │ │ │ │ ├── fmul │ │ │ │ ├── fmuls │ │ │ │ ├── fnabs │ │ │ │ ├── fneg │ │ │ │ ├── fnmadd │ │ │ │ ├── fnmadds │ │ │ │ ├── fnmsub │ │ │ │ ├── fnmsubs │ │ │ │ ├── fres │ │ │ │ ├── frsp │ │ │ │ ├── fsel │ │ │ │ ├── fsub │ │ │ │ ├── fsubs │ │ │ │ ├── mulhw │ │ │ │ ├── mulhwu │ │ │ │ ├── mulli │ │ │ │ ├── mullw │ │ │ │ ├── nand │ │ │ │ ├── neg │ │ │ │ ├── nor │ │ │ │ ├── or │ │ │ │ ├── orc │ │ │ │ ├── ori │ │ │ │ ├── oris │ │ │ │ ├── rlwimi │ │ │ │ ├── rlwinm │ │ │ │ ├── rlwnm │ │ │ │ ├── slw │ │ │ │ ├── sraw │ │ │ │ ├── srawi │ │ │ │ ├── srw │ │ │ │ ├── subf │ │ │ │ ├── subfc │ │ │ │ ├── subfe │ │ │ │ ├── subfic │ │ │ │ ├── subfme │ │ │ │ ├── subfze │ │ │ │ ├── xor │ │ │ │ ├── xori │ │ │ │ └── xoris │ │ │ └── wiiu/ │ │ │ ├── add │ │ │ ├── addc │ │ │ ├── adde │ │ │ ├── addi │ │ │ ├── addic │ │ │ ├── addicx │ │ │ ├── addis │ │ │ ├── addme │ │ │ ├── addze │ │ │ ├── and │ │ │ ├── andc │ │ │ ├── andi │ │ │ ├── andis │ │ │ ├── cmp │ │ │ ├── cmpi │ │ │ ├── cmpl │ │ │ ├── cmpli │ │ │ ├── cntlzw │ │ │ ├── crand │ │ │ ├── crandc │ │ │ ├── creqv │ │ │ ├── crnand │ │ │ ├── crnor │ │ │ ├── cror │ │ │ ├── crorc │ │ │ ├── crxor │ │ │ ├── divw │ │ │ ├── divwu │ │ │ ├── eqv │ │ │ ├── extsb │ │ │ ├── extsh │ │ │ ├── fabs │ │ │ ├── fadd │ │ │ ├── fadds │ │ │ ├── fctiw │ │ │ ├── fctiwz │ │ │ ├── fdiv │ │ │ ├── fdivs │ │ │ ├── fmadd │ │ │ ├── fmadds │ │ │ ├── fmr │ │ │ ├── fmsub │ │ │ ├── fmsubs │ │ │ ├── fmul │ │ │ ├── fmuls │ │ │ ├── fnabs │ │ │ ├── fneg │ │ │ ├── fnmadd │ │ │ ├── fnmadds │ │ │ ├── fnmsub │ │ │ ├── fnmsubs │ │ │ ├── fres │ │ │ ├── frsp │ │ │ ├── fsel │ │ │ ├── fsub │ │ │ ├── fsubs │ │ │ ├── mulhw │ │ │ ├── mulhwu │ │ │ ├── mulli │ │ │ ├── mullw │ │ │ ├── nand │ │ │ ├── neg │ │ │ ├── nor │ │ │ ├── or │ │ │ ├── orc │ │ │ ├── ori │ │ │ ├── oris │ │ │ ├── rlwimi │ │ │ ├── rlwinm │ │ │ ├── rlwnm │ │ │ ├── slw │ │ │ ├── sraw │ │ │ ├── srawi │ │ │ ├── srw │ │ │ ├── subf │ │ │ ├── subfc │ │ │ ├── subfe │ │ │ ├── subfic │ │ │ ├── subfme │ │ │ ├── subfze │ │ │ ├── xor │ │ │ ├── xori │ │ │ └── xoris │ │ ├── fuzz-compare/ │ │ │ ├── fuzztests.cpp │ │ │ ├── fuzztests.h │ │ │ └── main.cpp │ │ ├── generator/ │ │ │ ├── client/ │ │ │ │ ├── code_test.s │ │ │ │ ├── console.c │ │ │ │ ├── console.h │ │ │ │ ├── loader.c │ │ │ │ ├── loader.h │ │ │ │ ├── program.c │ │ │ │ ├── program.h │ │ │ │ ├── sysfuncs.c │ │ │ │ └── sysfuncs.h │ │ │ ├── dataset/ │ │ │ │ ├── generator.cpp │ │ │ │ ├── generator_testlist.h │ │ │ │ └── generator_valuelist.h │ │ │ └── server/ │ │ │ └── server.cpp │ │ ├── libcpu/ │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ ├── runner-achurch/ │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ └── runner-generated/ │ │ ├── CMakeLists.txt │ │ ├── hardwaretests.cpp │ │ ├── hardwaretests.h │ │ └── main.cpp │ └── gpu/ │ ├── CMakeLists.txt │ └── tiling/ │ ├── CMakeLists.txt │ ├── addrlib_helpers.h │ ├── cpu_tiling_test.cpp │ ├── test_helpers.h │ ├── tiling_test.cpp │ ├── tiling_tests.h │ ├── vulkan_helpers.cpp │ ├── vulkan_helpers.h │ └── vulkan_tiling_test.cpp ├── tools/ │ ├── CMakeLists.txt │ ├── gfd-tool/ │ │ ├── CMakeLists.txt │ │ └── gfdtool.cpp │ ├── latte-assembler/ │ │ ├── CMakeLists.txt │ │ ├── resources/ │ │ │ ├── example_shader.psh │ │ │ └── example_shader.vsh │ │ └── src/ │ │ ├── assembler_alu.cpp │ │ ├── assembler_cf.cpp │ │ ├── assembler_common.cpp │ │ ├── assembler_exp.cpp │ │ ├── assembler_instructions.cpp │ │ ├── assembler_instructions.h │ │ ├── assembler_latte.cpp │ │ ├── assembler_parse.cpp │ │ ├── assembler_tex.cpp │ │ ├── gfd.cpp │ │ ├── gfd_comment_parser.h │ │ ├── gfd_psh_comment_parser.cpp │ │ ├── gfd_vsh_comment_parser.cpp │ │ ├── glsl_compiler.cpp │ │ ├── glsl_compiler.h │ │ ├── main.cpp │ │ ├── shader.h │ │ └── shader_assembler.h │ ├── pm4-replay/ │ │ ├── CMakeLists.txt │ │ ├── clilog.h │ │ ├── config.h │ │ ├── main.cpp │ │ ├── replay_parser.h │ │ ├── replay_parser_pm4.cpp │ │ ├── replay_parser_pm4.h │ │ ├── replay_ringbuffer.h │ │ ├── sdl_window.cpp │ │ └── sdl_window.h │ ├── pm4-replay-qt/ │ │ ├── CMakeLists.txt │ │ ├── decaf.cpp │ │ ├── decaf.h │ │ ├── main.cpp │ │ ├── mainwindow.cpp │ │ ├── mainwindow.h │ │ ├── replay.cpp │ │ ├── replay.h │ │ ├── replaycommandsmodel.cpp │ │ ├── replaycommandsmodel.h │ │ ├── replayrenderwidget.cpp │ │ ├── replayrenderwidget.h │ │ ├── replayrunner.cpp │ │ ├── replayrunner.h │ │ └── resources/ │ │ └── mainwindow.ui │ └── wiiu-rpc/ │ ├── CMakeLists.txt │ ├── client.py │ └── src/ │ ├── console.c │ ├── console.h │ ├── main.c │ ├── packet.c │ ├── packet.h │ ├── server.c │ └── server.h └── vcpkg.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -3 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: TopLevel AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: true AfterControlStatement: false AfterEnum: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: AfterColon BreakBeforeTernaryOperators: true # BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: AfterColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 0 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 3 ContinuationIndentWidth: 3 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Regroup IncludeCategories: - Regex: '^".*/.*' Priority: 2 - Regex: '^<.*' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: 'IDontWantAmainInclude' IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 3 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '^[A-Z]+_BEG' MacroBlockEnd: '^[A-Z]+_END' MaxEmptyLinesToKeep: 2 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never ... ================================================ FILE: .github/workflows/ccpp.yml ================================================ name: C/C++ CI on: [push, pull_request] jobs: windows-build: runs-on: windows-2019 strategy: fail-fast: false env: VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' VCPKG_BUILD_TYPE: 'release' VCPKG_ROOT: '${{github.workspace}}/libraries/vcpkg' OS: windows-2019 MSVC: msvc2019_64 COMPILER: cl VULKAN_VERSION: 1.3.211.0 QT_VERSION: 5.15.0 steps: - uses: actions/checkout@v2 with: submodules: true - name: Initialise run: mkdir build - name: "Fetch full history for vcpkg submodule" run: | cd libraries/vcpkg git fetch --unshallow git pull --all - name: 'Setup vcpkg' run: ./libraries/vcpkg/bootstrap-vcpkg.bat - name: 'Setup NuGet Credentials' shell: 'bash' run: > cd build && `../libraries/vcpkg/vcpkg fetch nuget | tail -n 1` sources add -source "https://nuget.pkg.github.com/decaf-emu/index.json" -storepasswordincleartext -name "GitHub" -username "decaf-emu" -password "${{ secrets.GITHUB_TOKEN }}" -Verbosity "detailed" - name: Load Cached Vulkan SDK id: cache-vulkan-windows uses: actions/cache@v1 with: path: C:/VulkanSDK/${{ env.VULKAN_VERSION }} key: ${{ runner.os }}-${{ env.VULKAN_VERSION }} - name: Install Vulkan SDK from web if: steps.cache-vulkan-windows.outputs.cache-hit != 'true' shell: powershell run: | mkdir "C:\\VulkanSDK" cd "C:\\VulkanSDK" Invoke-WebRequest "https://sdk.lunarg.com/sdk/download/${{ env.VULKAN_VERSION }}/windows/VulkanSDK-${{ env.VULKAN_VERSION }}-Installer.exe?u=" -OutFile "VulkanSDK.exe" Start-Process -FilePath VulkanSDK.exe -Wait -PassThru -ArgumentList @("in --al --da --ao --confirm-command"); cd "C:\\VulkanSDK\\${{ env.VULKAN_VERSION }}" Remove-Item -Force -Recurse Demos Remove-Item -Force -Recurse Templates Remove-Item -Force -Recurse Tools Remove-Item -Force maintenancetool.exe Remove-Item -Force Bin\\VkLayer* Remove-Item -Force Lib\\shaderc* dir - name: Load Cached Qt id: cache-qt-windows uses: actions/cache@v1 with: path: C:/Qt/${{ env.QT_VERSION }}/${{ env.MSVC }} key: ${{ runner.os }}-qt-${{ env.QT_VERSION }}-${{ env.MSVC }} - name: Install Qt from web if: steps.cache-qt-windows.outputs.cache-hit != 'true' shell: powershell run: | mkdir "C:\\Qt" cd "C:\\Qt" pip install aqtinstall cmd /c 'python 2>&1' -m aqt install ${{ env.QT_VERSION }} windows desktop win64_${{ env.MSVC }} dir - name: Setup Environment shell: powershell run: | echo "VULKAN_SDK=C:\\VulkanSDK\\${{ env.VULKAN_VERSION }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append echo "QTDIR=C:\\Qt\\${{ env.QT_VERSION }}\\${{ env.MSVC }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Configure shell: cmd run: | cd build cmake -DCMAKE_TOOLCHAIN_FILE=../libraries/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-release -DVCPKG_HOST_TRIPLET=x64-windows-release -DCMAKE_BUILD_TYPE=Release -DDECAF_BUILD_TOOLS=ON -DDECAF_VULKAN=ON -DDECAF_QT=ON -DCMAKE_PREFIX_PATH=%QTDIR% -DCMAKE_INSTALL_PREFIX=install .. - name: Build run: | cd build cmake --build . --config Release -j 2 --target install - uses: actions/upload-artifact@master with: name: decaf-emu-${{ env.OS }} path: build/install ubuntu-build: runs-on: ubuntu-20.04 strategy: fail-fast: false env: VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' VCPKG_BUILD_TYPE: 'release' VCPKG_ROOT: '${{github.workspace}}/libraries/vcpkg' OS: ubuntu-20.04 MSVC: msvc2019_64 COMPILER: gcc VERSION: 10 QT_VERSION: 5.15.0 steps: - uses: actions/checkout@v2 with: submodules: true - name: cache uses: actions/cache@v2 with: path: ~/.ccache key: build-ccache-${{github.run_id}} restore-keys: | build-ccache - name: Initialise run: | mkdir build - name: Install Dependencies run: | cd build wget -qO - http://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add - sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-focal.list http://packages.lunarg.com/vulkan/lunarg-vulkan-focal.list sudo apt-add-repository ppa:cginternals/ppa sudo apt-get update sudo apt-get install -y cmake ccache vulkan-sdk python3-setuptools mesa-common-dev libglu1-mesa-dev ninja-build libcurl4-openssl-dev libsdl2-dev libssl-dev zlib1g-dev libuv1-dev libc-ares-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev if [ "${{ env.COMPILER }}" = "gcc" ]; then sudo apt-get install -y g++-${{ env.VERSION }} else sudo apt-get install -y clang-${{ env.VERSION }} fi pip3 install wheel pip3 install aqtinstall python3 -m aqt install ${{ env.QT_VERSION }} linux desktop PELFVER=0.12 curl -sSfLO https://github.com/NixOS/patchelf/releases/download/${PELFVER}/patchelf-${PELFVER}.tar.bz2 tar xvf patchelf-${PELFVER}.tar.bz2 cd patchelf-${PELFVER}*/ ./configure make && sudo make install cd ../ - name: Setup Environment run: | if [ "${{ env.COMPILER }}" = "gcc" ]; then echo "CC=gcc-${{ env.VERSION }}" >> $GITHUB_ENV echo "CXX=g++-${{ env.VERSION }}" >> $GITHUB_ENV else echo "CC=clang-${{ env.VERSION }}" >> $GITHUB_ENV echo "CXX=clang++-${{ env.VERSION}}" >> $GITHUB_ENV fi echo "QTDIR=$PWD/build/${{ env.QT_VERSION }}/gcc_64" >> $GITHUB_ENV echo "VULKAN_SDK=$PWD/vulkan" >> $GITHUB_ENV - name: Configure run: | cd build cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DDECAF_BUILD_TOOLS=ON -DDECAF_VULKAN=ON -DDECAF_QT=ON -DCMAKE_PREFIX_PATH=$QTDIR -DCMAKE_INSTALL_PREFIX=install .. - name: Build run: | cd build cmake --build . --config Release -j 2 --target install - uses: actions/upload-artifact@master with: name: decaf-emu-${{ env.OS }} path: build/install create-release: needs: [windows-build, ubuntu-build] runs-on: "ubuntu-20.04" if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v2 - name: Download Artifacts uses: actions/download-artifact@v2 - name: Upload shell: bash run: | if [[ -e decaf-emu-appimage ]]; then mv decaf-emu-appimage artifacts else mkdir artifacts fi files=$(find . -name "decaf-emu-*" ! -iname "*.zip" -type d) for f in $files; do echo "Compressing $f" (cd $(basename $f) && zip -r ../artifacts/$(basename $f).zip *) done ls -al artifacts/ wget -c https://github.com/tcnksm/ghr/releases/download/v0.14.0/ghr_v0.14.0_linux_amd64.tar.gz tar xfv ghr_v0.14.0_linux_amd64.tar.gz ghr_v0.14.0_linux_amd64/ghr -u ${{ github.repository_owner }} -r decaf-emu -recreate -n 'decaf-emu CI builds' -b "Corresponding commit: ${{ github.sha }}" release artifacts/ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ build/ dump/ mlc/ slc/ hfio/ sdcard/ docs/ .vs/ *.txt !CMakeLists.txt *.elf *.rpx *.o !tests/hle/bin/*.rpx ================================================ FILE: .gitmodules ================================================ [submodule "libraries/pugixml"] path = libraries/pugixml url = https://github.com/zeux/pugixml.git [submodule "libraries/libbinrec"] path = libraries/libbinrec url = https://github.com/decaf-emu/libbinrec.git [submodule "libraries/spdlog"] path = libraries/spdlog url = https://github.com/gabime/spdlog.git [submodule "libraries/cereal"] path = libraries/cereal url = https://github.com/USCiLab/cereal.git [submodule "libraries/ovsocket"] path = libraries/ovsocket url = https://github.com/exjam/ovsocket.git [submodule "libraries/gsl-lite"] path = libraries/gsl-lite url = https://github.com/gsl-lite/gsl-lite [submodule "libraries/addrlib"] path = libraries/addrlib url = https://github.com/decaf-emu/addrlib.git [submodule "libraries/excmd"] path = libraries/excmd url = https://github.com/exjam/excmd [submodule "libraries/imgui"] path = libraries/imgui url = https://github.com/ocornut/imgui.git [submodule "libraries/cnl"] path = libraries/cnl url = https://github.com/johnmcfarlane/cnl [submodule "libraries/catch"] path = libraries/catch url = https://github.com/philsquared/Catch.git [submodule "libraries/cpp-peglib"] path = libraries/cpp-peglib url = https://github.com/yhirose/cpp-peglib.git [submodule "libraries/fmt"] path = libraries/fmt url = https://github.com/fmtlib/fmt.git [submodule "libraries/glslang"] path = libraries/glslang url = https://github.com/KhronosGroup/glslang.git [submodule "libraries/qtads"] path = libraries/qtads url = https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System.git [submodule "libraries/tomlplusplus"] path = libraries/tomlplusplus url = https://github.com/marzer/tomlplusplus.git [submodule "libraries/vcpkg"] path = libraries/vcpkg url = https://github.com/microsoft/vcpkg.git ================================================ FILE: BUILDING.md ================================================ # Building decaf-emu from source - [Windows](#windows) - [Linux](#Linux) - [MacOS](#MacOS) - [CMake](#CMake) - [Troubleshooting](#Troubleshooting) ## Windows ### Dependencies Required: - [Visual Studio 2019](https://visualstudio.microsoft.com/vs/community/) - [CMake](https://cmake.org/) - [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) Optional: - [Qt 5.15+ / 6+] (https://www.qt.io/download-qt-installer), disable by using `-DDECAF_QT=OFF` ### Building - `git clone --recursive https://github.com/decaf-emu/decaf-emu.git` Use cmake-gui to generate a VS project file: - Set `Where is the source code` to `[path to decaf-emu.git]` - Set `Where to build the binaries` to `[path to decaf-emu.git]/build` - Click `Add Entry` and set `Name: CMAKE_PREFIX_PATH`, `Type: PATH`, `Value` to a Qt5 or Qt6 installation directory, e.g. `Value: C:\Qt\5.15.2\msvc2019_64` - Click `Configure` - Ensure `Specify the generator for this project` is set to a version of Visual Studio installed on your computer - Select `Specify toolchain for cross-compiling` - Click `Next` - Set `Specify the toolchain file` to `[path to decaf-emu.git]/libraries/vcpkg/scripts/buildsystems/vcpkg.cmake` - Click `Finish` - Configure will run, which may take a while as vcpkg acquires the dependencies, if all works the console should say `Configuring done` - Click `Generate`, if all works the console should say `Generating done` - Click `Open Project` to open the generated project in Visual sStudio where you can develop and build. ## Linux ### Dependencies Required: - A modern C++17 friendly compiler such as g++9 - CMake Required dependencies which can be acquired from system or vcpkg: - c-ares - curl - ffmpeg - libuv - openssl - sdl2 - zlib For some systems, these can be installed with: - `apt install cmake libcurl4-openssl-dev libsdl2-dev libssl-dev zlib1g-dev libuv1-dev libc-ares-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev` Optional: - [Vulkan SDK](https://vulkan.lunarg.com/sdk/home), disable by using `-DDECAF_VULKAN=OFF` - [Qt 5.15+ / 6+] (https://www.qt.io/download-qt-installer), disable by using `-DDECAF_QT=OFF` For some systems, Qt can be installed with: - `apt install qtbase5-dev qtbase5-private-dev libqt5svg5-dev libqt5x11extras5-dev mesa-common-dev libglu1-mesa-dev` ### Building - `git clone --recursive https://github.com/decaf-emu/decaf-emu.git` - `cd decaf-emu` - `mkdir build` - `cd build` - `cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../` - `make` You might want to use `cmake -G Ninja <...>` and build with Ninja instead of Make for faster builds. ## MacOS Currently decaf-emu can build on MacOS using Xcode 11 although MoltenVK is missing crucial features which will prevent most games from rendering correctly, e.g. geometry shaders, transform feedback, logic op support, unrestricted depth range. This means the platform should be considered as unsupported. ## CMake Options interesting to users: - DECAF_FFMPEG - Build with ffmpeg which is used for decoding h264 videos - DECAF_QT - Build with Qt frontend. - DECAF_VULKAN - Build with Vulkan backend. Options interesting to developers: - DECAF_BUILD_TESTS - Build tests. - DECAF_BUILD_TOOLS - Build tools. - DECAF_GIT_VERSION - Set this to OFF to disable generating a header with current git version to avoid rebuilding decaf_log.cpp when you do commits locally. - DECAF_PCH - Enable / disable pch (requires CMake v3.16) - DECAF_JIT_PROFILING - Build with JIT profiling support. - DECAF_VALGRIND - Build with Valgrind ## Troubleshooting decaf-emu builds on github actions CI - so a good reference on how to build is always the CI script itself [.github/workflows/ccpp.yml](https://github.com/decaf-emu/decaf-emu/blob/master/.github/workflows/ccpp.yml) ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2) project(decaf-emu C CXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake OPTIONAL RESULT_VARIABLE USING_CONAN) if(USING_CONAN) conan_basic_setup(NO_OUTPUT_DIRS) endif() # Disable PCH by default on older versions of CMake if(${CMAKE_VERSION} VERSION_LESS "3.16.0") set(DECAF_PCH_DEFAULT OFF) message(WARNING "You are using a CMake which does not support PCH (<3.16). This will adversely affect compile times.") else() set(DECAF_PCH_DEFAULT ON) endif() option(DECAF_FFMPEG "Build with ffmpeg support" ON) option(DECAF_VULKAN "Build with Vulkan rendering support" ON) option(DECAF_QT "Build with Qt support" ON) option(DECAF_BUILD_TESTS "Build tests" OFF) option(DECAF_BUILD_TOOLS "Build tools" OFF) option(DECAF_GIT_VERSION "Generate a version header from git state" ON) option(DECAF_JIT_PROFILING "Build with JIT profiling support" OFF) option(DECAF_VALGRIND "Build with Valgrind support" OFF) option(DECAF_PCH "Build with precompiled headers" ${DECAF_PCH_DEFAULT}) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/obj) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/obj) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/obj) # Setup install directories include(GNUInstallDirs) if(WIN32) set(DECAF_INSTALL_BINDIR "${CMAKE_INSTALL_PREFIX}") set(DECAF_INSTALL_DOCSDIR "${CMAKE_INSTALL_PREFIX}") set(DECAF_INSTALL_RESOURCESDIR "${CMAKE_INSTALL_PREFIX}/resources") else() set(DECAF_INSTALL_BINDIR "${CMAKE_INSTALL_BINDIR}") set(DECAF_INSTALL_DOCSDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") set(DECAF_INSTALL_RESOURCESDIR "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/resources") endif() if(DECAF_JIT_PROFILING) add_definitions(-DDECAF_JIT_ALLOW_PROFILING) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") find_package(XCB QUIET) find_package(X11 QUIET) find_package(WAYLAND QUIET) if(${XCB_FOUND}) add_definitions(-DDECAF_PLATFORM_XCB) set(DECAF_PLATFORM_XCB TRUE) endif() if(${X11_FOUND}) add_definitions(-DDECAF_PLATFORM_XLIB) set(DECAF_PLATFORM_XLIB TRUE) endif() if(${WAYLAND_FOUND}) add_definitions(-DDECAF_PLATFORM_WAYLAND) set(DECAF_PLATFORM_WAYLAND TRUE) endif() endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_definitions(-DDECAF_PLATFORM_COCOA) set(DECAF_PLATFORM_COCOA TRUE) endif() find_package(Threads REQUIRED) if(VCPKG_TARGET_TRIPLET) find_package(c-ares CONFIG REQUIRED) find_package(CURL CONFIG REQUIRED) find_package(OpenSSL REQUIRED) find_package(SDL2 CONFIG REQUIRED) find_package(libuv CONFIG REQUIRED) find_package(ZLIB REQUIRED) set(CARES_LIBRARY c-ares::cares) set(CURL_LIBRARY CURL::libcurl) set(LIBUV_LIBRARY libuv::uv) set(OPENSSL_LIBRARY OpenSSL::SSL) set(SDL2_LIBRARY SDL2::SDL2) set(SDL2_MAIN_LIBRARY SDL2::SDL2main) set(ZLIB_LIBRARY ZLIB::ZLIB) else() find_package(CARES REQUIRED) find_package(CURL REQUIRED) find_package(LibUV REQUIRED) find_package(OpenSSL REQUIRED) find_package(SDL2 REQUIRED) find_package(ZLIB REQUIRED) set(CARES_LIBRARY CARES::CARES) set(CURL_LIBRARY CURL::libcurl) set(LIBUV_LIBRARY LibUV::LibUV) set(OPENSSL_LIBRARY OpenSSL::SSL) set(SDL2_LIBRARY SDL2::SDL2) set(SDL2_MAIN_LIBRARY SDL2::SDL2main) set(ZLIB_LIBRARY ZLIB::ZLIB) endif() if(DECAF_FFMPEG) find_package(FFMPEG REQUIRED) set(FFMPEG_LIBRARY FFMPEG::AVCODEC FFMPEG::AVFILTER FFMPEG::AVUTIL FFMPEG::SWSCALE) add_definitions(-DDECAF_FFMPEG) endif() # TODO: Remove this definitions as it is no longer optional add_definitions(-DDECAF_SDL) if(DECAF_VULKAN) find_package(Vulkan 1.1.106.0 REQUIRED) # Vulkan_INCLUDE_DIRS and Vulkan_LIBRARIES add_library(vulkan INTERFACE IMPORTED) set_target_properties(vulkan PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${Vulkan_INCLUDE_DIRS} INTERFACE_LINK_LIBRARIES ${Vulkan_LIBRARIES}) add_definitions(-DVULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL=0) if(MSVC) add_definitions(-DVK_USE_PLATFORM_WIN32_KHR) endif() if(DECAF_PLATFORM_XCB) add_definitions(-DVK_USE_PLATFORM_XCB_KHR) endif() if(DECAF_PLATFORM_XLIB) add_definitions(-DVK_USE_PLATFORM_XLIB_KHR) endif() if(DECAF_PLATFORM_WAYLAND) add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR) endif() if(DECAF_PLATFORM_COCOA) add_definitions(-DVK_USE_PLATFORM_MACOS_MVK) endif() add_definitions(-DDECAF_VULKAN) endif() if(DECAF_VALGRIND) add_definitions(-DDECAF_VALGRIND) endif() if(DECAF_QT) find_package(Qt6 COMPONENTS Core Concurrent Widgets Svg SvgWidgets Xml) if(NOT Qt6_FOUND) find_package(Qt5 5.15 COMPONENTS Core Concurrent Widgets Svg Xml REQUIRED) endif() set(QT_DIR Qt${QT_VERSION_MAJOR}_DIR) add_definitions(-DDECAF_QT) endif() # Build third party libraries add_subdirectory("libraries") # Setup compile options if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++latest") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /permissive-") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /FS") # Parallel source builds set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GA") # Optimises TLS access set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") # Lets be specific about warnings set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd26812") # Allow unscoped enums set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4201") # Allow the use of unnamed structs/unions set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4100") # Allow unreferenced formal parameters set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4366") # Allow unaligned uint64_t (permitted on x64) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4324") # Disable structure padding warnings set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4297") # Disable nothrow warning, VulkanSDK engineers dont know how to code # Link time code generation set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") add_definitions(-DNOMINMAX) add_definitions(-DUNICODE -D_UNICODE) # Disable warnings about using non-portable string function variants add_definitions(-D_CRT_SECURE_NO_WARNINGS) # Disable warnings about using deprecated std::wstring_convert add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) # Disable linker warnins about missing PDBs set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4099") # Set minimum windows version to enable newer APIs add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) else() add_definitions(-DDECAF_USE_STDLAYOUT_BITFIELD) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof") if(APPLE) add_definitions(-D_DARWIN_C_SOURCE) else() link_libraries(stdc++fs) endif() endif() # Macro to map filters to folder structure for MSVC projects macro(GroupSources groupname curdir) if(MSVC) file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) foreach(child ${children}) if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) GroupSources(${rootname} ${groupname}/${child} ${curdir}/${child}) else() string(REPLACE "/" "\\" safegroupname ${groupname}) source_group(${safegroupname} FILES ${PROJECT_SOURCE_DIR}/${rootdir}${curdir}/${child}) endif() endforeach() endif() endmacro() macro(AutoGroupPCHFiles) if(MSVC) source_group("CMake PCH" FILES "${PROJECT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/cmake_pch.hxx" "${PROJECT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/cmake_pch.cxx") endif() endmacro() if(DECAF_GIT_VERSION) # Generate build information include(GetGitRevisionDescription) function(get_timestamp _var) string(TIMESTAMP timestamp UTC) set(${_var} "${timestamp}" PARENT_SCOPE) endfunction() get_git_head_revision(GIT_REF_SPEC GIT_REV) git_describe(GIT_DESC --always --long --dirty) git_branch_name(GIT_BRANCH) get_timestamp(BUILD_DATE) set(BUILD_VERSION "0") if ($ENV{CI}) if ($ENV{TRAVIS}) set(BUILD_TAG $ENV{TRAVIS_TAG}) elseif($ENV{APPVEYOR}) set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) endif() if (BUILD_TAG) string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG}) if (${CMAKE_MATCH_COUNT} GREATER 0) set(BUILD_VERSION ${CMAKE_MATCH_1}) endif() endif() endif() else() set(GIT_REV "local") set(BUILD_NAME "decaf-emu") set(BUILD_VERSION "0") endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/decaf_buildinfo.h.in" "${CMAKE_CURRENT_BINARY_DIR}/generated/decaf_buildinfo.h" @ONLY) include_directories("${CMAKE_CURRENT_BINARY_DIR}/generated") add_subdirectory("src") add_subdirectory("resources") if(DECAF_BUILD_TOOLS) add_subdirectory("tools") endif() if(DECAF_BUILD_TESTS) enable_testing() add_subdirectory("tests") endif() ================================================ FILE: CMakeModules/FindCARES.cmake ================================================ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. # # This is a slight edited version of FindLIBUV.cmake :) #[=======================================================================[.rst: FindCARES --------- Find c-ares includes and library. Imported Targets ^^^^^^^^^^^^^^^^ An :ref:`imported target ` named ``CARES::CARES`` is provided if c-ares has been found. Result Variables ^^^^^^^^^^^^^^^^ This module defines the following variables: ``CARES_FOUND`` True if c-ares was found, false otherwise. ``CARES_INCLUDE_DIRS`` Include directories needed to include c-ares headers. ``CARES_LIBRARIES`` Libraries needed to link to c-ares. ``CARES_VERSION`` The version of c-ares found. ``CARES_VERSION_MAJOR`` The major version of c-ares. ``CARES_VERSION_MINOR`` The minor version of c-ares. ``CARES_VERSION_PATCH`` The patch version of c-ares. Cache Variables ^^^^^^^^^^^^^^^ This module uses the following cache variables: ``CARES_LIBRARY`` The location of the c-ares library file. ``CARES_INCLUDE_DIR`` The location of the c-ares include directory containing ``ares.h``. The cache variables should not be used by project code. They may be set by end users to point at c-ares components. #]=======================================================================] set(CARES_NAMES ${CARES_NAMES} cares) #----------------------------------------------------------------------------- find_library(CARES_LIBRARY NAMES ${CARES_NAMES} ) mark_as_advanced(CARES_LIBRARY) find_path(CARES_INCLUDE_DIR NAMES ares.h ) mark_as_advanced(CARES_INCLUDE_DIR) #----------------------------------------------------------------------------- # Extract version number if possible. set(_CARES_H_REGEX "#[ \t]*define[ \t]+ARES_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") if(CARES_INCLUDE_DIR AND EXISTS "${CARES_INCLUDE_DIR}/ares_version.h") file(STRINGS "${CARES_INCLUDE_DIR}/ares_version.h" _CARES_H REGEX "${_CARES_H_REGEX}") else() set(_CARES_H "") endif() foreach(c MAJOR MINOR PATCH) if(_CARES_H MATCHES "#[ \t]*define[ \t]+ARES_VERSION_${c}[ \t]+([0-9]+)") set(_CARES_VERSION_${c} "${CMAKE_MATCH_1}") else() unset(_CARES_VERSION_${c}) endif() endforeach() if(DEFINED _CARES_VERSION_MAJOR AND DEFINED _CARES_VERSION_MINOR) set(CARES_VERSION_MAJOR "${_CARES_VERSION_MAJOR}") set(CARES_VERSION_MINOR "${_CARES_VERSION_MINOR}") set(CARES_VERSION "${CARES_VERSION_MAJOR}.${CARES_VERSION_MINOR}") if(DEFINED _CARES_VERSION_PATCH) set(CARES_VERSION_PATCH "${_CARES_VERSION_PATCH}") set(CARES_VERSION "${CARES_VERSION}.${CARES_VERSION_PATCH}") else() unset(CARES_VERSION_PATCH) endif() else() set(CARES_VERSION_MAJOR "") set(CARES_VERSION_MINOR "") set(CARES_VERSION_PATCH "") set(CARES_VERSION "") endif() unset(_CARES_VERSION_MAJOR) unset(_CARES_VERSION_MINOR) unset(_CARES_VERSION_PATCH) unset(_CARES_H_REGEX) unset(_CARES_H) #----------------------------------------------------------------------------- include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(CARES FOUND_VAR CARES_FOUND REQUIRED_VARS CARES_LIBRARY CARES_INCLUDE_DIR VERSION_VAR CARES_VERSION ) set(CARES_FOUND ${CARES_FOUND}) #----------------------------------------------------------------------------- # Provide documented result variables and targets. if(CARES_FOUND) set(CARES_INCLUDE_DIRS ${CARES_INCLUDE_DIR}) set(CARES_LIBRARIES ${CARES_LIBRARY}) if(NOT TARGET CARES::CARES) add_library(CARES::CARES UNKNOWN IMPORTED) set_target_properties(CARES::CARES PROPERTIES IMPORTED_LOCATION "${CARES_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${CARES_INCLUDE_DIRS}" ) endif() endif() ================================================ FILE: CMakeModules/FindFFMPEG.cmake ================================================ macro(find_component COMPONENT LIBRARY HEADER) find_path(${COMPONENT}_INCLUDE_DIR NAMES "${HEADER}" HINTS "/usr/include/ffmpeg") find_library(${COMPONENT}_LIBRARY NAMES "${LIBRARY}") if(${COMPONENT}_INCLUDE_DIR AND ${COMPONENT}_LIBRARY) set(${COMPONENT}_FOUND TRUE) if(NOT TARGET FFMPEG::${COMPONENT}) add_library(FFMPEG::${COMPONENT} UNKNOWN IMPORTED) set_target_properties(FFMPEG::${COMPONENT} PROPERTIES IMPORTED_LOCATION "${${COMPONENT}_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${${COMPONENT}_INCLUDE_DIR}") endif() else() set(${COMPONENT}_FOUND FALSE) endif() endmacro() find_component(AVCODEC "avcodec" "libavcodec/avcodec.h") find_component(AVFILTER "avfilter" "libavfilter/avfilter.h") find_component(AVUTIL "avutil" "libavutil/avutil.h") find_component(SWSCALE "swscale" "libswscale/swscale.h") ================================================ FILE: CMakeModules/FindLibUV.cmake ================================================ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #[=======================================================================[.rst: FindLibUV --------- Find libuv includes and library. Imported Targets ^^^^^^^^^^^^^^^^ An :ref:`imported target ` named ``LibUV::LibUV`` is provided if libuv has been found. Result Variables ^^^^^^^^^^^^^^^^ This module defines the following variables: ``LibUV_FOUND`` True if libuv was found, false otherwise. ``LibUV_INCLUDE_DIRS`` Include directories needed to include libuv headers. ``LibUV_LIBRARIES`` Libraries needed to link to libuv. ``LibUV_VERSION`` The version of libuv found. ``LibUV_VERSION_MAJOR`` The major version of libuv. ``LibUV_VERSION_MINOR`` The minor version of libuv. ``LibUV_VERSION_PATCH`` The patch version of libuv. Cache Variables ^^^^^^^^^^^^^^^ This module uses the following cache variables: ``LibUV_LIBRARY`` The location of the libuv library file. ``LibUV_INCLUDE_DIR`` The location of the libuv include directory containing ``uv.h``. The cache variables should not be used by project code. They may be set by end users to point at libuv components. #]=======================================================================] #----------------------------------------------------------------------------- find_library(LibUV_LIBRARY NAMES uv uv_a libuv ) mark_as_advanced(LibUV_LIBRARY) find_path(LibUV_INCLUDE_DIR NAMES uv.h ) mark_as_advanced(LibUV_INCLUDE_DIR) #----------------------------------------------------------------------------- # Extract version number if possible. set(_LibUV_H_REGEX "#[ \t]*define[ \t]+UV_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") if(LibUV_INCLUDE_DIR AND EXISTS "${LibUV_INCLUDE_DIR}/uv-version.h") file(STRINGS "${LibUV_INCLUDE_DIR}/uv-version.h" _LibUV_H REGEX "${_LibUV_H_REGEX}") elseif(LibUV_INCLUDE_DIR AND EXISTS "${LibUV_INCLUDE_DIR}/uv/version.h") file(STRINGS "${LibUV_INCLUDE_DIR}/uv/version.h" _LibUV_H REGEX "${_LibUV_H_REGEX}") elseif(LibUV_INCLUDE_DIR AND EXISTS "${LibUV_INCLUDE_DIR}/uv.h") file(STRINGS "${LibUV_INCLUDE_DIR}/uv.h" _LibUV_H REGEX "${_LibUV_H_REGEX}") else() set(_LibUV_H "") endif() foreach(c MAJOR MINOR PATCH) if(_LibUV_H MATCHES "#[ \t]*define[ \t]+UV_VERSION_${c}[ \t]+([0-9]+)") set(_LibUV_VERSION_${c} "${CMAKE_MATCH_1}") else() unset(_LibUV_VERSION_${c}) endif() endforeach() if(DEFINED _LibUV_VERSION_MAJOR AND DEFINED _LibUV_VERSION_MINOR) set(LibUV_VERSION_MAJOR "${_LibUV_VERSION_MAJOR}") set(LibUV_VERSION_MINOR "${_LibUV_VERSION_MINOR}") set(LibUV_VERSION "${LibUV_VERSION_MAJOR}.${LibUV_VERSION_MINOR}") if(DEFINED _LibUV_VERSION_PATCH) set(LibUV_VERSION_PATCH "${_LibUV_VERSION_PATCH}") set(LibUV_VERSION "${LibUV_VERSION}.${LibUV_VERSION_PATCH}") else() unset(LibUV_VERSION_PATCH) endif() else() set(LibUV_VERSION_MAJOR "") set(LibUV_VERSION_MINOR "") set(LibUV_VERSION_PATCH "") set(LibUV_VERSION "") endif() unset(_LibUV_VERSION_MAJOR) unset(_LibUV_VERSION_MINOR) unset(_LibUV_VERSION_PATCH) unset(_LibUV_H_REGEX) unset(_LibUV_H) #----------------------------------------------------------------------------- include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUV FOUND_VAR LibUV_FOUND REQUIRED_VARS LibUV_LIBRARY LibUV_INCLUDE_DIR VERSION_VAR LibUV_VERSION ) set(LIBUV_FOUND ${LibUV_FOUND}) #----------------------------------------------------------------------------- # Provide documented result variables and targets. if(LibUV_FOUND) set(LibUV_INCLUDE_DIRS ${LibUV_INCLUDE_DIR}) set(LibUV_LIBRARIES ${LibUV_LIBRARY}) if(NOT TARGET LibUV::LibUV) add_library(LibUV::LibUV UNKNOWN IMPORTED) set_target_properties(LibUV::LibUV PROPERTIES IMPORTED_LOCATION "${LibUV_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${LibUV_INCLUDE_DIRS}" ) endif() endif() ================================================ FILE: CMakeModules/FindSDL2.cmake ================================================ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #.rst: # FindSDL2 # ------- # # Locate SDL2 library # # This module defines # # :: # # SDL2_LIBRARY, the name of the library to link against # SDL2_FOUND, if false, do not try to link to SDL # SDL2_INCLUDE_DIR, where to find SDL.h # SDL2_VERSION_STRING, human-readable string containing the version of SDL # # # # This module responds to the flag: # # :: # # SDL2_BUILDING_LIBRARY # If this is defined, then no SDL2_main will be linked in because # only applications need main(). # Otherwise, it is assumed you are building an application and this # module will attempt to locate and set the proper link flags # as part of the returned SDL2_LIBRARY variable. # # # # Don't forget to include SDLmain.h and SDLmain.m your project for the # OS X framework based version. (Other versions link to -lSDLmain which # this module will try to find on your behalf.) Also for OS X, this # module will automatically add the -framework Cocoa on your behalf. # # # # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your # configuration and no SDL2_LIBRARY, it means CMake did not find your SDL # library (SDL.dll, libsdl.so, SDL.framework, etc). Set # SDL2_LIBRARY_TEMP to point to your SDL library, and configure again. # Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this # value as appropriate. These values are used to generate the final # SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY # does not get created. # # # # $SDLDIR is an environment variable that would correspond to the # ./configure --prefix=$SDLDIR used in building SDL. l.e.galup 9-20-02 # # Modified by Eric Wing. Added code to assist with automated building # by using environmental variables and providing a more # controlled/consistent search behavior. Added new modifications to # recognize OS X frameworks and additional Unix paths (FreeBSD, etc). # Also corrected the header search path to follow "proper" SDL # guidelines. Added a search for SDLmain which is needed by some # platforms. Added a search for threads which is needed by some # platforms. Added needed compile switches for MinGW. # # On OSX, this will prefer the Framework version (if found) over others. # People will have to manually change the cache values of SDL2_LIBRARY to # override this selection or set the CMake environment # CMAKE_INCLUDE_PATH to modify the search paths. # # Note that the header path has changed from SDL/SDL.h to just SDL.h # This needed to change because "proper" SDL convention is #include # "SDL.h", not . This is done for portability reasons # because not all systems place things in SDL/ (see FreeBSD). if(NOT SDL2_DIR) set(SDL2_DIR "" CACHE PATH "SDL2 directory") endif() find_path(SDL2_INCLUDE_DIR SDL.h HINTS ENV SDLDIR ${SDL2_DIR} PATH_SUFFIXES SDL2 # path suffixes to search inside ENV{SDLDIR} include/SDL2 include ) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(VC_LIB_PATH_SUFFIX lib/x64) else() set(VC_LIB_PATH_SUFFIX lib/x86) endif() # SDL-1.1 is the name used by FreeBSD ports... # don't confuse it for the version number. find_library(SDL2_LIBRARY_TEMP NAMES SDL2 HINTS ENV SDLDIR ${SDL2_DIR} PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} ) # Hide this cache variable from the user, it's an internal implementation # detail. The documented library variable for the user is SDL2_LIBRARY # which is derived from SDL2_LIBRARY_TEMP further below. set_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL) if(NOT SDL2_BUILDING_LIBRARY) if(NOT SDL2_INCLUDE_DIR MATCHES ".framework") # Non-OS X framework versions expect you to also dynamically link to # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms # seem to provide SDLmain for compatibility even though they don't # necessarily need it. find_library(SDL2MAIN_LIBRARY NAMES SDL2main HINTS ENV SDLDIR ${SDL2_DIR} PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} PATHS /sw /opt/local /opt/csw /opt ) endif() endif() # SDL may require threads on your system. # The Apple build may not need an explicit flag because one of the # frameworks may already provide it. # But for non-OSX systems, I will use the CMake Threads package. if(NOT APPLE) find_package(Threads) endif() # MinGW needs an additional link flag, -mwindows # It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows if(MINGW) set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") endif() if(SDL2_LIBRARY_TEMP) # For SDLmain if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) list(FIND SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX) if(_SDL2_MAIN_INDEX EQUAL -1) set(SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARY_TEMP}) endif() unset(_SDL2_MAIN_INDEX) endif() # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. # CMake doesn't display the -framework Cocoa string in the UI even # though it actually is there if I modify a pre-used variable. # I think it has something to do with the CACHE STRING. # So I use a temporary variable until the end so I can set the # "real" variable in one-shot. if(APPLE) set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") endif() # For threads, as mentioned Apple doesn't need this. # In fact, there seems to be a problem if I used the Threads package # and try using this line, so I'm just skipping it entirely for OS X. if(NOT APPLE) set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) endif() # For MinGW library if(MINGW) set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) endif() if(WIN32 AND NOT WINDOWS_STORE AND NOT WINDOWS_PHONE) set(SDL2_LIBRARY_TEMP winmm imm32 version msimg32 ${SDL2_LIBRARY_TEMP}) endif(WIN32 AND NOT WINDOWS_STORE AND NOT WINDOWS_PHONE) # Set the final string here so the GUI reflects the final state. set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") endif() if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL2_version.h") file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+[0-9]+$") string(REGEX REPLACE "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) unset(SDL2_VERSION_MAJOR_LINE) unset(SDL2_VERSION_MINOR_LINE) unset(SDL2_VERSION_PATCH_LINE) unset(SDL2_VERSION_MAJOR) unset(SDL2_VERSION_MINOR) unset(SDL2_VERSION_PATCH) endif() set(SDL2_LIBRARIES ${SDL2_LIBRARY}) set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR}) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL REQUIRED_VARS SDL2_LIBRARIES SDL2_INCLUDE_DIRS VERSION_VAR SDL2_VERSION_STRING) mark_as_advanced(SDL2_LIBRARY SDL2_INCLUDE_DIR) ================================================ FILE: CMakeModules/FindWAYLAND.cmake ================================================ # Try to find Wayland on a Unix system # # This will define: # # WAYLAND_FOUND - True if Wayland is found # WAYLAND_LIBRARIES - Link these to use Wayland # WAYLAND_INCLUDE_DIR - Include directory for Wayland # WAYLAND_DEFINITIONS - Compiler flags for using Wayland # # In addition the following more fine grained variables will be defined: # # WAYLAND_CLIENT_FOUND WAYLAND_CLIENT_INCLUDE_DIR WAYLAND_CLIENT_LIBRARIES # WAYLAND_SERVER_FOUND WAYLAND_SERVER_INCLUDE_DIR WAYLAND_SERVER_LIBRARIES # WAYLAND_EGL_FOUND WAYLAND_EGL_INCLUDE_DIR WAYLAND_EGL_LIBRARIES # # Copyright (c) 2013 Martin Gräßlin # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. IF (NOT WIN32) IF (WAYLAND_INCLUDE_DIR AND WAYLAND_LIBRARIES) # In the cache already SET(WAYLAND_FIND_QUIETLY TRUE) ENDIF () # Use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls FIND_PACKAGE(PkgConfig) PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor) SET(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS}) FIND_PATH(WAYLAND_CLIENT_INCLUDE_DIR NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_PATH(WAYLAND_SERVER_INCLUDE_DIR NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_PATH(WAYLAND_EGL_INCLUDE_DIR NAMES wayland-egl.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_PATH(WAYLAND_CURSOR_INCLUDE_DIR NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS}) FIND_LIBRARY(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) FIND_LIBRARY(WAYLAND_EGL_LIBRARIES NAMES wayland-egl HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) FIND_LIBRARY(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) set(WAYLAND_INCLUDE_DIR ${WAYLAND_CLIENT_INCLUDE_DIR} ${WAYLAND_SERVER_INCLUDE_DIR} ${WAYLAND_EGL_INCLUDE_DIR} ${WAYLAND_CURSOR_INCLUDE_DIR}) set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES}) list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIR) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CLIENT DEFAULT_MSG WAYLAND_CLIENT_LIBRARIES WAYLAND_CLIENT_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_SERVER DEFAULT_MSG WAYLAND_SERVER_LIBRARIES WAYLAND_SERVER_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_EGL DEFAULT_MSG WAYLAND_EGL_LIBRARIES WAYLAND_EGL_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CURSOR DEFAULT_MSG WAYLAND_CURSOR_LIBRARIES WAYLAND_CURSOR_INCLUDE_DIR) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND DEFAULT_MSG WAYLAND_LIBRARIES WAYLAND_INCLUDE_DIR) MARK_AS_ADVANCED( WAYLAND_INCLUDE_DIR WAYLAND_LIBRARIES WAYLAND_CLIENT_INCLUDE_DIR WAYLAND_CLIENT_LIBRARIES WAYLAND_SERVER_INCLUDE_DIR WAYLAND_SERVER_LIBRARIES WAYLAND_EGL_INCLUDE_DIR WAYLAND_EGL_LIBRARIES WAYLAND_CURSOR_INCLUDE_DIR WAYLAND_CURSOR_LIBRARIES ) ENDIF () ================================================ FILE: CMakeModules/FindXCB.cmake ================================================ # - FindXCB # # Copyright (C) 2015 Valve Corporation find_package(PkgConfig) if(NOT XCB_FIND_COMPONENTS) set(XCB_FIND_COMPONENTS xcb) endif() include(FindPackageHandleStandardArgs) set(XCB_FOUND true) set(XCB_INCLUDE_DIRS "") set(XCB_LIBRARIES "") foreach(comp ${XCB_FIND_COMPONENTS}) # component name string(TOUPPER ${comp} compname) string(REPLACE "-" "_" compname ${compname}) # header name string(REPLACE "xcb-" "" headername xcb/${comp}.h) # library name set(libname ${comp}) pkg_check_modules(PC_${comp} QUIET ${comp}) find_path(${compname}_INCLUDE_DIR NAMES ${headername} HINTS ${PC_${comp}_INCLUDEDIR} ${PC_${comp}_INCLUDE_DIRS} ) find_library(${compname}_LIBRARY NAMES ${libname} HINTS ${PC_${comp}_LIBDIR} ${PC_${comp}_LIBRARY_DIRS} ) find_package_handle_standard_args(${comp} FOUND_VAR ${comp}_FOUND REQUIRED_VARS ${compname}_INCLUDE_DIR ${compname}_LIBRARY) mark_as_advanced(${compname}_INCLUDE_DIR ${compname}_LIBRARY) list(APPEND XCB_INCLUDE_DIRS ${${compname}_INCLUDE_DIR}) list(APPEND XCB_LIBRARIES ${${compname}_LIBRARY}) if(NOT ${comp}_FOUND) set(XCB_FOUND false) endif() endforeach() list(REMOVE_DUPLICATES XCB_INCLUDE_DIRS) ================================================ FILE: CMakeModules/GetGitRevisionDescription.cmake ================================================ # - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ ...]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar) set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(GIT_DIR "${GIT_PARENT_DIR}/.git") while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) # We have reached the root directory, we are not in git set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() set(GIT_DIR "${GIT_PARENT_DIR}/.git") endwhile() # check if this is a submodule if(NOT IS_DIRECTORY ${GIT_DIR}) file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) endif() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${GIT_DIR}/HEAD") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_branch_name _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --abbrev-ref HEAD WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_describe _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() #get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() #if(NOT hash) # set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) # return() #endif() # TODO sanitize #if((${ARGN}" MATCHES "&&") OR # (ARGN MATCHES "||") OR # (ARGN MATCHES "\\;")) # message("Please report the following error to the project!") # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") #endif() #message(STATUS "Arguments to execute_process: ${ARGN}") execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN} WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out --exact-match ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() ================================================ FILE: CMakeModules/GetGitRevisionDescription.cmake.in ================================================ # # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) set(HEAD_HASH "${HEAD_REF}") endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) if(EXISTS "@GIT_DATA@/head-ref") file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) else() set(HEAD_HASH "Unknown") endif() endif() ================================================ FILE: CMakePresets.json ================================================ { "version": 3, "cmakeMinimumRequired": { "major": 3, "minor": 21, "patch": 0 }, "configurePresets": [ { "name": "vcpkg", "displayName": "vcpkg build", "description": "Use vcpkg for dependencies", "toolchainFile": "${sourceDir}/libraries/vcpkg/scripts/buildsystems/vcpkg.cmake", "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } } ] } ================================================ FILE: LICENSE.md ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ [![Build status](https://github.com/decaf-emu/decaf-emu/workflows/C%2FC%2B%2B%20CI/badge.svg)](https://github.com/decaf-emu/decaf-emu/actions?workflow=C%2FC%2B%2B+CI) # decaf-emu Researching Wii U emulation. Licensed under the terms of the GNU General Public License, version 3 or later (GPLv3+). You can find us for developer discussion: - on discord using https://discord.gg/tPqFBnr

## Support None, this is an in-development project and user support is not provided. ## Building from Source See [BUILDING.md](BUILDING.md) ## Binaries The latest Windows and Linux binaries are available via [Actions artifacts](https://github.com/decaf-emu/decaf-emu/actions?query=branch%3Amaster+is%3Asuccess). You must be logged into GitHub in order to download the artifacts. MacOS builds are currently not provided due to complications with Vulkan. ## Running Run the `decaf-qt` executable, it is recommended to run the emulator from the root git directory so that it is able to access `resources/fonts/*`. Alternatively, set `resources_path` in the configuration file to point to the resources directory. Configuration files can be found at: - Windows - `%APPDATA%\decaf` - Linux - `~/.config/decaf` On Linux, a "Bus error" crash usually indicates an out-of-space error in the temporary directory. Set the `TMPDIR` environment variable to a directory on a filesystem with at least 2GB free. Additionally there is an SDL command line application which can be used by `./decaf-sdl play ` ================================================ FILE: libraries/CMakeLists.txt ================================================ # addrlib add_library(addrlib STATIC "addrlib/src/addrinterface.cpp" "addrlib/src/core/addrelemlib.cpp" "addrlib/src/core/addrlib.cpp" "addrlib/src/core/addrobject.cpp" "addrlib/src/r600/r600addrlib.cpp") set_target_properties(addrlib PROPERTIES FOLDER libraries) target_include_directories(addrlib PRIVATE "addrlib/src" PUBLIC "addrlib/include") # libbinrec set(BINREC_ENABLE_RTL_DEBUG_OPTIMIZE FALSE CACHE BOOL "Enable debug output from optimization passes") set(BINREC_ENABLE_ASSERT FALSE CACHE BOOL "Enable basic assertion checks") add_subdirectory(libbinrec) set_target_properties(binrec PROPERTIES FOLDER libraries) # catch add_library(catch2 INTERFACE IMPORTED GLOBAL) set_target_properties(catch2 PROPERTIES INTERFACE_COMPILE_DEFINITIONS "CATCH_CONFIG_ENABLE_BENCHMARKING" INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/catch/single_include/catch2") # cereal add_library(cereal INTERFACE IMPORTED GLOBAL) set_target_properties(cereal PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/cereal/include") # cnl add_library(cnl INTERFACE IMPORTED GLOBAL) set_target_properties(cnl PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/cnl/include") # excmd add_library(excmd INTERFACE IMPORTED GLOBAL) set_target_properties(excmd PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/excmd/src") # fmt add_subdirectory(fmt) set_target_properties(fmt PROPERTIES FOLDER libraries) # glslang if(DECAF_VULKAN OR DECAF_BUILD_TOOLS) set(BUILD_SHARED_LIBS OFF CACHE BOOL "glslang: BUILD_SHARED_LIBS" FORCE) set(BUILD_TESTING OFF CACHE BOOL "glslang: BUILD_TESTING" FORCE) set(ENABLE_HLSL OFF CACHE BOOL "glslang: ENABLE_HLSL" FORCE) set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "glslang: ENABLE_GLSLANG_BINARIES" FORCE) set(ENABLE_OPT ON CACHE BOOL "glslang: ENABLE_OPT" FORCE) set(ENABLE_SPVREMAPPER OFF CACHE BOOL "glslang: ENABLE_SPVREMAPPER" FORCE) set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "glslang: SKIP_GLSLANG_INSTALL" FORCE) add_subdirectory(glslang) macro(remove_flag_from_target _target _flag) get_target_property(_target_cxx_flags ${_target} COMPILE_OPTIONS) if(_target_cxx_flags) list(REMOVE_ITEM _target_cxx_flags ${_flag}) set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "${_target_cxx_flags}") endif() endmacro() # As we inherit spv::Builder in libgpu, we must remove -fno-rtti remove_flag_from_target(SPIRV -fno-rtti) endif() # gsl add_library(gsl INTERFACE IMPORTED GLOBAL) set_target_properties(gsl PROPERTIES INTERFACE_COMPILE_DEFINITIONS "GSL_THROWS_FOR_TESTING" INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/gsl-lite/include") # imgui add_library(imgui STATIC "imgui/imgui.cpp" "imgui/imgui_draw.cpp") set_target_properties(imgui PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/imgui" FOLDER libraries) # ovsocket add_library(ovsocket INTERFACE IMPORTED GLOBAL) set_target_properties(ovsocket PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/ovsocket/ovsocket") # peglib add_library(peglib INTERFACE IMPORTED GLOBAL) set_target_properties(peglib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/cpp-peglib") # pugixml add_library(pugixml STATIC "pugixml/src/pugixml.cpp") set_target_properties(pugixml PROPERTIES FOLDER libraries INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/pugixml/src") # spdlog set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "Use external fmt library instead of bundled" FORCE) add_subdirectory(spdlog) set_target_properties(spdlog PROPERTIES FOLDER libraries) # tomlplusplus add_library(tomlplusplus INTERFACE IMPORTED GLOBAL) set_target_properties(tomlplusplus PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/tomlplusplus/include") # Qt Advanced Docking System if(DECAF_QT) set(BUILD_STATIC TRUE CACHE BOOL "ADS: Build the static library") set(BUILD_EXAMPLES FALSE CACHE BOOL "ADS: Build the examples") set(ADS_VERSION "3.8.1" CACHE STRING "ADS: Version") add_subdirectory(qtads EXCLUDE_FROM_ALL) set_target_properties(qtadvanceddocking PROPERTIES INTERFACE_COMPILE_DEFINITIONS "ADS_STATIC" FOLDER libraries) endif() ================================================ FILE: libraries/bin2c.cmake ================================================ # https://github.com/wjakob/nanogui/blob/f9c3b7a/resources/bin2c.cmake # Copyright (c) 2016 Wenzel Jakob , All rights reserved. # BSD-3-Clause-LBNL (https://github.com/wjakob/nanogui/blob/f9c3b7a/LICENSE.txt) cmake_minimum_required (VERSION 2.8.12) # Create header for C file file(WRITE ${OUTPUT_C} "/* Autogenerated by bin2c */\n\n") file(APPEND ${OUTPUT_C} "#include \n\n") # Create header of H file file(WRITE ${OUTPUT_H} "/* Autogenerated by bin2c */\n\n") file(APPEND ${OUTPUT_H} "#pragma once\n") file(APPEND ${OUTPUT_H} "#include \n\n") string(REPLACE "," ";" INPUT_LIST ${INPUT_FILES}) # Iterate through binary files files foreach(bin ${INPUT_LIST}) # Get short filename string(REGEX MATCH "([^/]+)$" filename ${bin}) # Replace filename spaces & extension separator for C compatibility string(REGEX REPLACE "\\.| |-" "_" filename ${filename}) # Convert to lower case string(TOLOWER ${filename} filename) # Read hex data from file file(READ ${bin} filedata HEX) # Convert hex data for C compatibility string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) # Append data to c file file(APPEND ${OUTPUT_C} "uint8_t ${filename}[] = {${filedata}};\n\nuint32_t ${filename}_size = sizeof(${filename});\n\n") # Append extern definitions to h file file(APPEND ${OUTPUT_H} "extern uint8_t ${filename}[];\n\nextern uint32_t ${filename}_size;\n\n") endforeach() ================================================ FILE: resources/CMakeLists.txt ================================================ project(resources) set(Fonts fonts/CafeCn.ttf fonts/CafeKr.ttf fonts/CafeStd.ttf fonts/CafeTw.ttf fonts/DejaVuSansMono.ttf) set(Docs fonts/DejaVuSansMono.LICENSE fonts/NotoSansCJK.LICENSE ../README.md ../LICENSE.md) set(ResourceFiles ${Fonts}) add_custom_target(resources ALL SOURCES ${ResourceFiles}) foreach(ResourceFile ${ResourceFiles}) add_custom_command(TARGET resources PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/resources/${ResourceFile} $/resources/${ResourceFile}) endforeach() install(FILES ${Fonts} DESTINATION "${DECAF_INSTALL_RESOURCESDIR}/fonts") install(FILES ${Docs} DESTINATION "${DECAF_INSTALL_DOCSDIR}") ================================================ FILE: resources/fonts/DejaVuSansMono.LICENSE ================================================ Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) Bitstream Vera Fonts Copyright ------------------------------ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. Arev Fonts Copyright ------------------------------ Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. TeX Gyre DJV Math ----------------- Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski (on behalf of TeX users groups) are in public domain. Letters imported from Euler Fraktur from AMSfonts are (c) American Mathematical Society (see below). Bitstream Vera Fonts Copyright Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (“Fonts”) and associated documentation files (the “Font Software”), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words “Bitstream” or the word “Vera”. This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the “Bitstream Vera” names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of GNOME, the GNOME Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the GNOME Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. AMSFonts (v. 2.2) copyright The PostScript Type 1 implementation of the AMSFonts produced by and previously distributed by Blue Sky Research and Y&Y, Inc. are now freely available for general use. This has been accomplished through the cooperation of a consortium of scientific publishers with Blue Sky Research and Y&Y. Members of this consortium include: Elsevier Science IBM Corporation Society for Industrial and Applied Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) In order to assure the authenticity of these fonts, copyright will be held by the American Mathematical Society. This is not meant to restrict in any way the legitimate use of the fonts, such as (but not limited to) electronic distribution of documents containing these fonts, inclusion of these fonts into other public domain or commercial font collections or computer applications, use of the outline data to create derivative fonts and/or faces, etc. However, the AMS does require that the AMS copyright notice be removed from any derivative versions of the fonts which have been altered in any way. In addition, to ensure the fidelity of TeX documents using Computer Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, has requested that any alterations which yield different font metrics be given a different name. $Id$ ================================================ FILE: resources/fonts/NotoSansCJK.LICENSE ================================================ Copyright (c) , (), with Reserved Font Name . Copyright (c) , (), with Reserved Font Name . Copyright (c) , (). This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: resources/hidpi.manifest ================================================ true/pm permonitorv2,permonitor ================================================ FILE: src/CMakeLists.txt ================================================ include_directories(".") add_subdirectory(common) add_subdirectory(libconfig) add_subdirectory(libcpu) add_subdirectory(libdecaf) add_subdirectory(libgfd) add_subdirectory(libgpu) add_subdirectory(decaf-cli) add_subdirectory(decaf-sdl) if(DECAF_QT) add_subdirectory(decaf-qt) endif() ================================================ FILE: src/common/CMakeLists.txt ================================================ project(common) include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h *.inl) add_library(common STATIC ${SOURCE_FILES} ${HEADER_FILES}) GroupSources("Source Files" src) target_link_libraries(common cnl fmt::fmt gsl spdlog::spdlog) if(MSVC) target_link_libraries(common Dbghelp) elseif(UNIX AND NOT APPLE) target_link_libraries(common rt) endif() if(DECAF_PCH) target_precompile_headers(common PRIVATE "pch.h" ) AutoGroupPCHFiles() endif() ================================================ FILE: src/common/align.h ================================================ #pragma once #include template constexpr inline Type align_up(Type value, size_t alignment) { return static_cast((static_cast(value) + (alignment - 1)) & ~(alignment - 1)); } template constexpr inline Type align_down(Type value, size_t alignment) { return static_cast(static_cast(value) & ~(alignment - 1)); } template constexpr inline Type * align_up(Type *value, size_t alignment) { return reinterpret_cast((reinterpret_cast(value) + (alignment - 1)) & ~(alignment - 1)); } template constexpr inline Type * align_down(Type *value, size_t alignment) { return reinterpret_cast(reinterpret_cast(value) & ~(alignment - 1)); } template constexpr bool align_check(Type *value, size_t alignment) { return (reinterpret_cast(value) & (alignment - 1)) == 0; } template constexpr bool align_check(Type value, size_t alignment) { return (static_cast(value) & (alignment - 1)) == 0; } ================================================ FILE: src/common/atomicqueue.h ================================================ #pragma once #include /** * Multi-producer multi-consumer queue. * This is unsafe and full of assumptions, should be very careful about using it. * * This is not a safe queue, it does not check if the queue is full before * pushing, or if it is empty before popping. * * Size must be a power of two so you don't need to be careful for wrapping of * read / write position. * * Empty when writePos = readPos. * Full when writePos + 1 == readPos. */ template class alignas(64) AtomicQueue { static_assert(Size && ((Size & (Size - 1)) == 0), "N must be a power of two"); public: constexpr std::size_t capacity() const { return Size - 1; } bool wasFull() const { return (mWritePosition.load() + 1) == mReadPosition.load(); } bool wasEmpty() const { return mWritePosition.load() == mReadPosition.load(); } void push(Type value) { auto writePos = mWritePosition.fetch_add(1); mBuffer[writePos % Size] = value; } Type pop() { auto readPos = mReadPosition.fetch_add(1); return mBuffer[readPos % Size]; } private: alignas(64) std::atomic mWritePosition = 0; Type mBuffer[Size]; alignas(64) std::atomic mReadPosition = 0; }; /** * Single producer, single consumer queue. * Much safer than MultiAtomicQueue, preferred when only one thread is writing * and one thread is reading. * * Safe, can detect whether queue is full on push, or empty on pop. * * Does not require size to be power of two. * * Empty when read == write * Full when (write + 1) == read */ template class SingleAtomicQueue { public: constexpr std::size_t capacity() const { return Size - 1; } bool push(Type value) { const auto writePos = mWritePosition.load(std::memory_order_relaxed); const auto nextWritePos = (writePos + 1) % Size; if (nextWritePos == mReadPosition.load(std::memory_order_acquire)) { // Queue is full! return false; } mBuffer[writePos] = value; mWritePosition.store(writePos, std::memory_order_release); return true; } bool pop(Type &value) { const auto readPos = mReadPosition.load(std::memory_order_relaxed); if (readPos == mWritePosition.load(std::memory_order_acquire)) { // Queue is empty! return false; } value = mBuffer[readPos]; mReadPosition.store((readPos + 1) % Size, std::memory_order_release); return true; } private: alignas(64) std::atomic mWritePosition = 0; Type mBuffer[Size]; alignas(64) std::atomic mReadPosition = 0; }; ================================================ FILE: src/common/bit_cast.h ================================================ #pragma once #include #include #include // reinterpret_cast for value types template inline DstType bit_cast(const SrcType& src) { static_assert(sizeof(SrcType) == sizeof(DstType), "bit_cast must be between same sized types"); static_assert(std::is_trivially_copyable::value, "SrcType is not trivially copyable."); static_assert(std::is_trivially_copyable::value, "DstType is not trivially copyable."); DstType dst; std::memcpy(std::addressof(dst), std::addressof(src), sizeof(SrcType)); return dst; } ================================================ FILE: src/common/bitfield.h ================================================ #pragma once #include "bit_cast.h" #include "bitutils.h" #include "decaf_assert.h" #include #include template struct BitfieldHelper { using underlying_type = typename safe_underlying_type::type; using unsigned_underlying_type = typename std::make_unsigned::type; static const auto RelativeMask = static_cast((1ull << (Bits)) - 1); static const auto AbsoluteMask = static_cast(RelativeMask) << (Position); static inline ValueType get(BitfieldType bitfield) { auto value = static_cast((bitfield.value & AbsoluteMask) >> (Position)); if (std::is_signed::value) { value = sign_extend(value); } return bit_cast(value); } static inline BitfieldType set(BitfieldType bitfield, ValueType value) { auto uValue = bit_cast(value); if (std::is_signed::value) { uValue &= RelativeMask; } decaf_assert(uValue <= RelativeMask, fmt::format("{} <= {}", uValue, static_cast(RelativeMask))); bitfield.value &= ~AbsoluteMask; bitfield.value |= static_cast(uValue) << (Position); return bitfield; } }; // Specialise for float because of errors using make_unsigned on float type template struct BitfieldHelper { using ValueBitfield = BitfieldHelper; static float get(BitfieldType bitfield) { return bit_cast(ValueBitfield::get(bitfield)); } static inline BitfieldType set(BitfieldType bitfield, float floatValue) { return ValueBitfield::set(bitfield, bit_cast(floatValue)); } }; // Specialise for bool because of compiler warnings for static_cast(int) template struct BitfieldHelper { static const auto AbsoluteMask = (static_cast((1ull << (Bits)) - 1)) << (Position); static constexpr bool get(BitfieldType bitfield) { return !!(bitfield.value & AbsoluteMask); } static inline BitfieldType set(BitfieldType bitfield, bool value) { bitfield.value &= ~AbsoluteMask; bitfield.value |= (static_cast(value ? 1 : 0)) << (Position); return bitfield; } }; #ifndef DECAF_USE_STDLAYOUT_BITFIELD #define BITFIELD_BEG(Name, Type) \ union Name \ { \ using BitfieldType = Name; \ using StorageType = Type; \ Type value; \ explicit operator StorageType() { return value; } \ static inline Name get(Type v) { \ Name bitfield; \ bitfield.value = v; \ return bitfield; \ } #define BITFIELD_ENTRY(Pos, Size, ValueType, Name) \ private: struct { StorageType : Pos; StorageType _##Name : Size; }; \ public: inline ValueType Name() const { \ return BitfieldHelper::get(*this); \ } \ inline BitfieldType Name(ValueType fieldValue) const { \ return BitfieldHelper \ ::set(*this, fieldValue); \ } #else #define BITFIELD_BEG(Name, Type) \ union Name \ { \ using BitfieldType = Name; \ using StorageType = Type; \ Type value; \ explicit operator StorageType() { return value; } \ static inline Name get(Type v) { \ Name bitfield; \ bitfield.value = v; \ return bitfield; \ } #define BITFIELD_ENTRY(Pos, Size, ValueType, Name) \ inline ValueType Name() const { \ return BitfieldHelper::get(*this); \ } \ inline BitfieldType Name(ValueType fieldValue) const { \ return BitfieldHelper \ ::set(*this, fieldValue); \ } #endif #define BITFIELD_END \ }; ================================================ FILE: src/common/bitutils.h ================================================ #pragma once #include "platform.h" #include #include #include #ifdef PLATFORM_WINDOWS #include #endif // Gets the value of a bit template constexpr Type get_bit(Type src, unsigned bit) { return (src >> bit) & static_cast(1); } template constexpr Type get_bit(Type src) { return (src >> (bit)) & static_cast(1); } // Sets the value of a bit to 1 template constexpr Type set_bit(Type src, unsigned bit) { return src | (static_cast(1) << bit); } template constexpr Type set_bit(Type src) { return src | (static_cast(1) << (bit)); } // Flips the value of a bit to 1 template constexpr Type flip_bit(Type src, unsigned bit) { return src ^ (static_cast(1) << bit); } template constexpr Type flip_bit(Type src) { return src ^ (static_cast(1) << (bit)); } // Clears the value of a bit template constexpr Type clear_bit(Type src, unsigned bit) { return src & ~(static_cast(1) << bit); } template constexpr Type clear_bit(Type src) { return src & ~(static_cast(1) << (bit)); } // Sets the value of a bit to value template inline Type set_bit_value(Type src, unsigned bit, Type value) { src = clear_bit(src, bit); return src | ((value & static_cast(1)) << bit); } template inline Type set_bit_value(Type src, Type value) { src = clear_bit(src, bit); return src | (value << bit); } // Create a bitmask for bits template constexpr Type make_bitmask(Type bits) { return static_cast((1ull << bits) - 1); } template constexpr Type make_bitmask() { return static_cast((1ull << (bits)) - 1); } template<> constexpr uint32_t make_bitmask<32, uint32_t>() { return 0xffffffff; } template<> constexpr uint64_t make_bitmask<64, uint64_t>() { return 0xffffffffffffffffull; } // Creates a bitmask between begin and end template constexpr Type make_bitmask(Type begin, Type end) { return make_bitmask(end - begin + 1) << begin; } template constexpr Type make_bitmask() { return make_bitmask<(end) - (begin) + 1, Type>() << (begin); } // Creates a bitmask between mb and me inline uint32_t make_ppc_bitmask(int mb, int me) { uint32_t begin, end, mask; begin = 0xFFFFFFFF >> mb; end = me < 31 ? (0xFFFFFFFF >> (me + 1)) : 0; mask = begin ^ end; return (me < mb) ? ~mask : mask; } // Sign extend bits to int32_t template inline Type sign_extend(Type src, unsigned bits) { auto mask = make_bitmask(bits); src &= mask; if (get_bit(src, bits)) { return src | ~mask; } else { return src; } } template inline Type sign_extend(Type src) { auto mask = make_bitmask(); src &= mask; if (get_bit<(bits) - 1>(src)) { return src | ~mask; } else { return src; } } #ifdef PLATFORM_WINDOWS inline int clz(uint32_t bits) { unsigned long a; if (!_BitScanReverse(&a, bits)) { return 32; } else { return 31 - a; } } #else #define clz __builtin_clz #endif #ifdef PLATFORM_WINDOWS inline int clz64(uint64_t bits) { unsigned long a; if (!_BitScanReverse64(&a, bits)) { return 64; } else { return 63 - a; } } #else #define clz64 __builtin_clzll #endif inline bool bit_scan_reverse(unsigned long *out_position, uint32_t bits) { #ifdef PLATFORM_WINDOWS return !!_BitScanReverse(out_position, bits); #elif defined(PLATFORM_POSIX) if (bits == 0) { return false; } *out_position = 31 - __builtin_clz(bits); return true; #endif } #ifdef PLATFORM_WINDOWS #define bit_rotate_left _rotl #else inline uint32_t bit_rotate_left(uint32_t x, int shift) { shift &= 31; if (!shift) { return x; } return (x << shift) | (x >> (32 - shift)); } #endif #ifdef PLATFORM_WINDOWS #define bit_rotate_right _rotr #else inline uint32_t bit_rotate_right(uint32_t x, int shift) { shift &= 31; if (!shift) { return x; } return (x >> shift) | (x << (32 - shift)); } #endif // Return number of bits in type template struct bit_width { static constexpr size_t value = sizeof(Type) * CHAR_BIT; constexpr operator size_t() const { return value; } }; ================================================ FILE: src/common/byte_swap.h ================================================ #pragma once #include "platform.h" #include "bit_cast.h" #include #ifdef PLATFORM_LINUX #include #endif // Utility class to swap endian for types of size 1, 2, 4, 8 // other type sizes are not supported template struct byte_swap_t; template struct byte_swap_t { static Type swap(Type src) { return src; } }; template struct byte_swap_t { static Type swap(Type src) { #ifdef PLATFORM_WINDOWS return bit_cast(_byteswap_ushort(bit_cast(src))); #elif defined(PLATFORM_APPLE) // Apple has no 16-bit byteswap intrinsic const uint16_t data = bit_cast(src); return bit_cast((uint16_t)((data >> 8) | (data << 8))); #elif defined(PLATFORM_LINUX) return bit_cast(bswap_16(bit_cast(src))); #endif } }; template struct byte_swap_t { static Type swap(Type src) { #ifdef PLATFORM_WINDOWS return bit_cast(_byteswap_ulong(bit_cast(src))); #elif defined(PLATFORM_APPLE) return bit_cast(__builtin_bswap32(bit_cast(src))); #elif defined(PLATFORM_LINUX) return bit_cast(bswap_32(bit_cast(src))); #endif } }; template struct byte_swap_t { static Type swap(Type src) { #ifdef PLATFORM_WINDOWS return bit_cast(_byteswap_uint64(bit_cast(src))); #elif defined(PLATFORM_APPLE) return bit_cast(__builtin_bswap64(bit_cast(src))); #elif defined(PLATFORM_LINUX) return bit_cast(bswap_64(bit_cast(src))); #endif } }; // Swaps endian of src template inline Type byte_swap(Type src) { return byte_swap_t::swap(src); } ================================================ FILE: src/common/byte_swap_array.h ================================================ #pragma once #include "byte_swap.h" #include "platform.h" #include "platform_intrin.h" #include template static inline void byte_swap_unaligned(DataType *dst, const DataType *srcStart, const DataType *srcEnd) { for (auto *src = srcStart; src < srcEnd; ) { *dst++ = byte_swap(*src++); } } #ifdef PLATFORM_HAS_SSE3 template static inline void byte_swap_aligned(DataType *dst, const DataType *srcStart, const DataType *srcEnd) { auto sseDst = reinterpret_cast<__m128i *>(dst); auto sseSrc = reinterpret_cast(srcStart); auto sseSrcEnd = reinterpret_cast(srcEnd); static_assert(sizeof(DataType) == 2 || sizeof(DataType) == 4, "unexpected data type size for aligned byte swap"); __m128i sseMask; if constexpr (sizeof(DataType) == 2) { sseMask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); } else if constexpr (sizeof(DataType) == 4) { sseMask = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3); } while (sseSrc < sseSrcEnd) { _mm_storeu_si128(sseDst++, _mm_shuffle_epi8(_mm_loadu_si128(sseSrc++), sseMask)); } } #else template static inline void byte_swap_aligned(DataType *dst, const DataType *srcStart, const DataType *srcEnd) { byte_swap_unaligned(dst, srcStart, srcEnd); } #endif #ifdef PLATFORM_HAS_SSE3 template static inline void * byte_swap_to_scratch(const void *data, uint32_t numBytes, std::vector &scratch) { // We pad the output buffer to guarentee we can align it to any source address. scratch.resize(numBytes + 32); // Calculate some information about the indices auto swapSrc = reinterpret_cast(data); auto swapSrcEnd = swapSrc + (numBytes / sizeof(DataType)); // The source must be aligned at least to the swap boundary... decaf_check(swapSrc == align_up(swapSrc, sizeof(DataType))); // Align our destination exactly the same as the source auto unalignedOffset = reinterpret_cast(swapSrc) & 0xF; auto alignMatchedScratch = align_up(scratch.data(), 16) + unalignedOffset; auto swapDest = reinterpret_cast(alignMatchedScratch); // Calculate our aligned memory auto alignedSwapDest = align_up(swapDest, 16); auto alignedSwapSrc = align_up(swapSrc, 16); auto alignedSwapSrcEnd = align_down(swapSrcEnd, 16); auto alignedSize = alignedSwapSrcEnd - alignedSwapSrc; // Do the unaligned before portion byte_swap_unaligned(swapDest, swapSrc, alignedSwapSrc); // Do the aligned portion byte_swap_aligned(alignedSwapDest, alignedSwapSrc, alignedSwapSrcEnd); // Do the unaligned after portion byte_swap_unaligned(alignedSwapDest + alignedSize, alignedSwapSrc + alignedSize, swapSrcEnd); return alignMatchedScratch; } #else template static inline void * byte_swap_to_scratch(const void *data, uint32_t numBytes, std::vector& scratch) { scratch.resize(numBytes); auto swapDest = reinterpret_cast(scratch.data()); auto swapSrc = reinterpret_cast(data); auto swapSrcEnd = swapSrc + (numBytes / sizeof(DataType)); byte_swap_unaligned(swapDest, swapSrc, swapSrcEnd); return swapDest; } #endif ================================================ FILE: src/common/cbool.h ================================================ #pragma once #include #ifndef BOOL using BOOL = int32_t; #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif ================================================ FILE: src/common/configstorage.h ================================================ #pragma once #include #include #include #include template class ConfigStorage { public: using ChangeListener = std::function; using Settings = SettingsType; ConfigStorage() : mStorage(std::make_shared()) { } void set(std::shared_ptr settings) { std::lock_guard lock { mMutex }; mStorage = settings; for (auto &listener : mListeners) { listener(*settings); } } std::shared_ptr get() { std::lock_guard lock { mMutex }; return mStorage; } void addListener(ChangeListener listener) { std::lock_guard lock { mMutex }; mListeners.emplace_back(std::move(listener)); } private: std::mutex mMutex; std::shared_ptr mStorage; std::vector mListeners; }; ================================================ FILE: src/common/count_of.h ================================================ #pragma once #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) ================================================ FILE: src/common/datahash.h ================================================ #pragma once #define XXH_INLINE_ALL #define XXH_CPU_LITTLE_ENDIAN 1 #define XXH_STATIC_LINKING_ONLY #include "xxhash.h" #include #include #include class DataHash { public: inline DataHash() { } inline bool operator!=(const DataHash &rhs) const { return mHash != rhs.mHash; } inline bool operator==(const DataHash &rhs) const { return mHash == rhs.mHash; } inline DataHash& write(const void *data, size_t size) { mHash ^= XXH64(data, size, 0); return *this; } template inline DataHash& write(const std::vector &data) { static_assert(std::is_trivially_copyable::value, "Hashed types must be trivial"); #ifdef DECAF_USE_STDLAYOUT_BITFIELD // On vs2019 this fails without stdlayout bitfield, but non-stdlayout bitfield is // nicer for debugging in visual studio static_assert(std::has_unique_object_representations::value, "Hashed types must have unique object representations"); #endif return write(data.data(), data.size() * sizeof(T)); } template inline DataHash& write(const T &data) { static_assert(std::is_trivially_copyable::value, "Hashed types must be trivial"); #ifdef DECAF_USE_STDLAYOUT_BITFIELD // On vs2019 this fails without stdlayout bitfield, but non-stdlayout bitfield is // nicer for debugging in visual studio static_assert(std::has_unique_object_representations::value, "Hashed types must have unique object representations"); #endif return write(&data, sizeof(T)); } inline uint64_t value() const { return mHash; } static inline DataHash random() { static std::random_device r; static std::mt19937 gen(r()); static std::uniform_int_distribution unidist; DataHash hash; hash.mHash = unidist(gen); return hash; } private: uint64_t mHash = 0; }; namespace std { template <> struct hash { uint64_t operator()(const DataHash& x) const { return x.value(); } }; } // namespace std ================================================ FILE: src/common/decaf_assert.h ================================================ #pragma once #include #include "platform.h" #include "platform_compiler.h" #include "platform_stacktrace.h" #ifdef PLATFORM_WINDOWS #define decaf_handle_assert(x, e, m) \ if (!(x)) { \ assertFailed(__FILE__, __LINE__, e, m); \ __debugbreak(); \ abort(); \ } #define decaf_handle_warn_once_assert(x, e, m) \ if (!(x)) { \ static bool seen_this_error_before = false; \ if (!seen_this_error_before) { \ seen_this_error_before = true; \ assertWarnFailed(__FILE__, __LINE__, e, m); \ } \ } #define decaf_host_fault(f, t) \ hostFaultWithStackTrace(f, t); \ __debugbreak(); \ abort(); #else #define decaf_handle_assert(x, e, m) \ if (UNLIKELY(!(x))) { \ assertFailed(__FILE__, __LINE__, e, m); \ abort(); \ } #define decaf_handle_warn_once_assert(x, e, m) \ if (UNLIKELY(!(x))) { \ static bool seen_this_error_before = false; \ if (!seen_this_error_before) { \ seen_this_error_before = true; \ assertWarnFailed(__FILE__, __LINE__, e, m); \ } \ } #define decaf_host_fault(f, t) \ hostFaultWithStackTrace(f, t); \ abort(); #endif #define decaf_assert(x, m) \ decaf_handle_assert(x, #x, m) #define decaf_check(x) \ decaf_handle_assert(x, #x, "") #define decaf_check_warn_once(x) \ decaf_handle_warn_once_assert(x, #x, "") #define decaf_abort(m) \ decaf_handle_assert(false, "0", m) void assertFailed(const char *file, unsigned line, const char *expression, const std::string &message); void assertWarnFailed(const char *file, unsigned line, const char *expression, const std::string &message); void hostFaultWithStackTrace(const std::string &fault, platform::StackTrace *stackTrace); ================================================ FILE: src/common/enum_end.inl ================================================ #undef ENUM_BEG #undef ENUM_END #undef ENUM_VALUE #undef FLAGS_BEG #undef FLAGS_END #undef FLAGS_VALUE #undef ENUM_NAMESPACE_ENTER #undef ENUM_NAMESPACE_EXIT ================================================ FILE: src/common/enum_start.inl ================================================ #ifndef ENUM_BEG #include #include #define ENUM_BEG(name, type) namespace name##_ { enum Value : type { #endif #ifndef ENUM_END #define ENUM_END(name) }; }; using name = name##_::Value; #endif #ifndef ENUM_VALUE #define ENUM_VALUE(key, value) key = value, #endif #ifndef FLAGS_BEG #define FLAGS_BEG(name, type) namespace name##_ { enum Value : type { #endif #ifndef FLAGS_END #define FLAGS_END(E) }; \ inline Value operator | (Value lhs, Value rhs) { return static_cast(static_cast>(lhs) | static_cast>(rhs)); } \ inline Value operator & (Value lhs, Value rhs) { return static_cast(static_cast>(lhs) & static_cast>(rhs)); } \ inline Value operator ^ (Value lhs, Value rhs) { return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); } \ inline Value operator ~ (Value lhs) { return static_cast(~static_cast>(lhs)); } \ inline Value &operator |= (Value &lhs, Value rhs) { return (lhs = lhs | rhs); } \ inline Value &operator &= (Value &lhs, Value rhs) { return (lhs = lhs & rhs); } \ inline Value &operator ^= (Value &lhs, Value rhs) { return (lhs = lhs ^ rhs); } \ }; using E = E##_::Value; #endif #ifndef FLAGS_VALUE #define FLAGS_VALUE(key, value) key = value, #endif #ifndef ENUM_NAMESPACE_ENTER #define ENUM_NAMESPACE_ENTER(name) namespace name { #endif #ifndef ENUM_NAMESPACE_EXIT #define ENUM_NAMESPACE_EXIT(name) } #endif ================================================ FILE: src/common/enum_string_declare.inl ================================================ #define ENUM_BEG(name, type) \ std::string to_string(name enumValue); #define ENUM_VALUE(key, value) #define ENUM_END(name) #define FLAGS_BEG(name, type) \ std::string to_string(name enumValue); #define FLAGS_VALUE(key, value) #define FLAGS_END(name) ================================================ FILE: src/common/enum_string_define.inl ================================================ #define ENUM_BEG(name, type) \ std::string to_string(name enumValue) { \ using namespace name##_; \ switch (enumValue) { #define ENUM_VALUE(key, value) \ case key: \ return #key; #define ENUM_END(name) \ default: \ return std::to_string(static_cast(enumValue)); \ } \ } #define FLAGS_BEG(name, type) \ std::string to_string(name enumValue) { \ using namespace name##_; \ std::string out; #define FLAGS_VALUE(key, value) \ if (enumValue & value) { \ if (out.size()) { out += " | "; } \ out += #key; \ } #define FLAGS_END(name) \ return out; \ } ================================================ FILE: src/common/fastregionmap.h ================================================ #pragma once #include #include static_assert(sizeof(std::atomic) == sizeof(void*), "This class assumes std::atomic has no overhead"); template class FastRegionMap { public: FastRegionMap() : mData(nullptr) { clear(); } ~FastRegionMap() { // Currently we do not clean up due to atomics } // Note that there must be no readers or writers to call this void clear() { if (mData) { for (auto i = 0u; i < 0x100; ++i) { auto level1 = mData[i].load(); if (level1) { for (auto j = 0u; j < 0x100; ++j) { auto level2 = level1[j].load(); if (level2) { delete[] level2; } } delete[] level1; } } } else { mData = new std::atomic*>*>[0x100]; } memset(mData, 0, sizeof(void*) * 0x100); } Type find(uint32_t location) { auto index1 = (location & 0xFF000000) >> 24; auto index2 = (location & 0x00FF0000) >> 16; auto index3 = (location & 0x0000FFFC) >> 2; auto level1 = mData[index1].load(); if (!level1) { return nullptr; } auto level2 = level1[index2].load(); if (!level2) { return nullptr; } return level2[index3].load(); } void set(uint32_t location, Type data) { if (location & 0x3) { decaf_abort("Location was not power-of-two."); } auto index1 = (location & 0xFF000000) >> 24; auto index2 = (location & 0x00FF0000) >> 16; auto index3 = (location & 0x0000FFFC) >> 2; auto level1 = mData[index1].load(); if (!level1) { auto newTable = new std::atomic*>[0x100]; std::memset(newTable, 0, sizeof(void*) * 0x100); if (mData[index1].compare_exchange_strong(level1, newTable)) { level1 = newTable; } else { // compare_exchange updates level1 if we were pre-empted delete[] newTable; } } auto level2 = level1[index2].load(); if (!level2) { auto newTable = new std::atomic[0x4000]; std::memset(newTable, 0, sizeof(void*) * 0x4000); if (level1[index2].compare_exchange_strong(level2, newTable)) { level2 = newTable; } else { // compare_exchange updates level2 if we were preempted delete[] newTable; } } level2[index3].store(data); } private: std::atomic*>*>* mData; }; ================================================ FILE: src/common/fixed.h ================================================ #pragma once #include "bitfield.h" #include using ufixed_16_16_t = cnl::fixed_point; using sfixed_1_0_15_t = cnl::fixed_point; using ufixed_0_16_t = cnl::fixed_point; using ufixed_1_15_t = cnl::fixed_point; using ufixed_1_5_t = cnl::fixed_point; using ufixed_4_6_t = cnl::fixed_point; using sfixed_1_3_1_t = cnl::fixed_point; using sfixed_1_3_3_t = cnl::fixed_point; using sfixed_1_5_6_t = cnl::fixed_point; template struct UnwrapFixedPoint; template struct UnwrapFixedPoint> { using rep = Rep; static constexpr int exponent = Exponent; static constexpr int radix = Radix; }; template static constexpr FixedPointType fixed_from_data(typename UnwrapFixedPoint::rep data) { return cnl::from_rep::rep> {} (data); } template static constexpr Rep fixed_to_data(cnl::fixed_point data) { return cnl::to_rep>{}(data); } // Specialise of BitfieldHelper for fixed_point template struct BitfieldHelper, Position, Bits> { using FixedType = cnl::fixed_point; using ValueBitfield = BitfieldHelper; static FixedType get(BitfieldType bitfield) { return cnl::from_rep{}(ValueBitfield::get(bitfield)); } static inline BitfieldType set(BitfieldType bitfield, FixedType fixedValue) { return ValueBitfield::set(bitfield, cnl::to_rep{}(fixedValue)); } }; ================================================ FILE: src/common/floatutils.h ================================================ #pragma once #include "bit_cast.h" #include "bitutils.h" #include union FloatBitsSingle { static const unsigned exponent_min = 0; static const unsigned exponent_max = 0xff; float v; uint32_t uv; struct { uint32_t mantissa : 23; uint32_t exponent : 8; uint32_t sign : 1; }; struct { uint32_t : 22; uint32_t quiet : 1; uint32_t : 9; }; }; union FloatBitsDouble { static const uint64_t exponent_min = 0; static const uint64_t exponent_max = 0x7ff; double v; uint64_t uv; struct { uint64_t mantissa : 52; uint64_t exponent : 11; uint64_t sign : 1; }; struct { uint64_t : 51; uint64_t quiet : 1; uint64_t : 12; }; }; inline FloatBitsSingle get_float_bits(float v) { return { v }; } inline FloatBitsDouble get_float_bits(double v) { return { v }; } template inline bool is_negative(Type v) { return get_float_bits(v).sign; } template inline bool is_positive(Type v) { return !is_negative(v); } template inline bool is_zero(Type v) { auto b = get_float_bits(v); return b.exponent == 0 && b.mantissa == 0; } template inline bool is_positive_zero(Type v) { return is_positive(v) && is_zero(v); } template inline bool is_negative_zero(Type v) { return is_negative(v) && is_zero(v); } template inline bool is_normal(Type v) { auto d = get_float_bits(v); return d.exponent > d.exponent_min && d.exponent < d.exponent_max; } template inline bool is_denormal(Type v) { auto d = get_float_bits(v); return d.exponent == d.exponent_min && d.mantissa != 0; } template inline bool is_infinity(Type v) { auto d = get_float_bits(v); return d.exponent == d.exponent_max && d.mantissa == 0; } template inline bool is_positive_infinity(Type v) { return is_positive(v) && is_infinity(v); } template inline bool is_negative_infinity(Type v) { return is_negative(v) && is_infinity(v); } template inline bool is_nan(Type v) { auto d = get_float_bits(v); return d.exponent == d.exponent_max && d.mantissa != 0; } template inline bool is_quiet(Type v) { return !!get_float_bits(v).quiet; } template inline bool is_quiet_nan(Type v) { return is_nan(v) && is_quiet(v); } template inline bool is_signalling_nan(Type v) { return is_nan(v) && !is_quiet(v); } template Type make_quiet(Type v) { auto bits = get_float_bits(v); bits.quiet = 1; return bits.v; } template Type make_nan() { auto bits = get_float_bits(static_cast(0)); bits.exponent = bits.exponent_max; bits.quiet = 1; return bits.v; } inline uint64_t extend_float_nan_bits(uint32_t v) { return ((uint64_t)(v & 0xC0000000) << 32 | (v & 0x40000000 ? UINT64_C(7) : UINT64_C(0)) << 59 | (uint64_t)(v & 0x3FFFFFFF) << 29); } inline double extend_float(float v) { if (is_nan(v)) { return bit_cast(extend_float_nan_bits(bit_cast(v))); } else { return static_cast(v); } } inline uint32_t truncate_double_bits(uint64_t v) { return (v>>32 & 0xC0000000) | (v>>29 & 0x3FFFFFFF); } inline float truncate_double(double v) { const FloatBitsDouble bits = get_float_bits(v); if (bits.exponent <= 873) { return bit_cast(static_cast(bits.sign)<<31); } else if (bits.exponent <= 896) { uint32_t mantissa = static_cast(1<<23 | bits.mantissa>>29); return bit_cast(static_cast(bits.sign)<<31 | (mantissa >> (897 - bits.exponent))); } else if (bits.exponent >= 1151 && bits.exponent != 2047) { return bit_cast(static_cast(bits.sign)<<31 | 0x7F800000); } else { return bit_cast(truncate_double_bits(bits.uv)); } } ================================================ FILE: src/common/frameallocator.h ================================================ #pragma once #include #include "align.h" #include "decaf_assert.h" class FrameAllocator { public: FrameAllocator() : mBase(nullptr), mSize(0), mOffset(0) { } FrameAllocator(void *base, size_t size) : mBase(reinterpret_cast(base)), mSize(size), mOffset(0) { } bool empty() const { return mOffset == 0; } uint8_t * top() const { return mBase + mOffset; } void * allocate(size_t size, size_t alignment = 4) { // Ensure section alignment auto alignOffset = align_up(top(), alignment) - top(); size += alignOffset; // Check we have enough size decaf_check(mOffset + size <= mSize); // Allocate data auto result = top() + alignOffset; mOffset += size; return result; } void reset() { mOffset = 0; } protected: uint8_t *mBase; size_t mSize; size_t mOffset; }; ================================================ FILE: src/common/log.h ================================================ #pragma once #include #include #include /** * Looks and acts like a spdlog logger but without including the spdlog header. */ class Logger { public: enum class Level { trace = 0, debug = 1, info = 2, warn = 3, err = 4, critical = 5, off = 6 }; public: template inline void trace(const String &str, const Args & ... args) { log(Level::trace, fmt::format(str, args...)); } template inline void debug(const String &str, const Args & ... args) { log(Level::debug, fmt::format(str, args...)); } template inline void info(const String &str, const Args & ... args) { log(Level::info, fmt::format(str, args...)); } template inline void warn(const String &str, const Args & ... args) { log(Level::warn, fmt::format(str, args...)); } template inline void error(const String &str, const Args & ... args) { log(Level::err, fmt::format(str, args...)); } template inline void critical(const String &str, const Args & ... args) { log(Level::critical, fmt::format(str, args...)); } inline void trace(std::string_view msg) { log(Level::trace, msg); } inline void debug(std::string_view msg) { log(Level::debug, msg); } inline void info(std::string_view msg) { log(Level::info, msg); } inline void warn(std::string_view msg) { log(Level::warn, msg); } inline void error(std::string_view msg) { log(Level::err, msg); } inline void critical(std::string_view msg) { log(Level::critical, msg); } Logger &operator=(std::shared_ptr logger) { mLogger = logger; return *this; } Logger *operator->() { return this; } bool should_log(Level level); private: void log(Level lvl, std::string_view msg); private: std::shared_ptr mLogger; }; extern Logger gLog; ================================================ FILE: src/common/make_array.h ================================================ #pragma once #include template constexpr auto make_array(ValueTypes&&... values) -> std::array { return { { std::forward(values)... } }; } template static auto make_filled_array(const Type &value) { std::array a; a.fill(value); return a; } ================================================ FILE: src/common/murmur3.h ================================================ //----------------------------------------------------------------------------- // MurmurHash3 was written by Austin Appleby, and is placed in the // public domain. The author hereby disclaims copyright to this source // code. #ifndef _MURMURHASH3_H_ #define _MURMURHASH3_H_ #include #ifdef __cplusplus extern "C" { #endif //----------------------------------------------------------------------------- void MurmurHash3_x86_32(const void *key, int len, uint32_t seed, void *out); void MurmurHash3_x86_128(const void *key, int len, uint32_t seed, void *out); void MurmurHash3_x64_128(const void *key, int len, uint32_t seed, void *out); //----------------------------------------------------------------------------- #ifdef __cplusplus } #endif #endif // _MURMURHASH3_H_ ================================================ FILE: src/common/pch.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ================================================ FILE: src/common/platform.h ================================================ #pragma once #if defined(WIN32) || defined(_WIN32) || defined(_MSC_VER) #define PLATFORM_WINDOWS #elif __APPLE__ #define PLATFORM_APPLE #define PLATFORM_POSIX #define _XOPEN_SOURCE #elif __linux__ #define PLATFORM_LINUX #define PLATFORM_POSIX #endif ================================================ FILE: src/common/platform_compiler.h ================================================ #pragma once // Macro to indicate that a branch is likely or unlikely to be taken. #ifdef __GNUC__ // Includes Clang. #define LIKELY(x) __builtin_expect(!!(x), 1) #define UNLIKELY(x) __builtin_expect(!!(x), 0) #else #define LIKELY(x) (x) #define UNLIKELY(x) (x) #endif // Macros to force a function to always or never be inlined. #ifdef __GNUC__ #define ALWAYS_INLINE inline __attribute__((always_inline)) #define NEVER_INLINE __attribute__((noinline)) #elif defined(_MSC_VER) #define ALWAYS_INLINE __forceinline #define NEVER_INLINE __declspec(noinline) #else #define ALWAYS_INLINE //nothing #define NEVER_INLINE //nothing #endif // Macro to disable optimization when building with Clang on a function which // both performs floating-point operations and checks exception flags that // could be affected by those operations. This is required because LLVM is // unaware of the fact that floating-point operations can raise exceptions // (see http://llvm.org/bugs/show_bug.cgi?id=6050). Place this immediately // before the opening brace for the function. #ifdef __clang__ #define CLANG_FPU_BUG_WORKAROUND __attribute__((optnone)) #else #define CLANG_FPU_BUG_WORKAROUND //nothing #endif ================================================ FILE: src/common/platform_debug.h ================================================ #pragma once #include namespace platform { void debugBreak(); void debugLog(const std::string& message); } // namespace platform ================================================ FILE: src/common/platform_dir.h ================================================ #pragma once #include namespace platform { bool createDirectory(const std::string &path); bool createParentDirectories(const std::string &path); bool fileExists(const std::string &path); bool isFile(const std::string &path); bool isDirectory(const std::string &path); std::string getConfigDirectory(); } // namespace platform ================================================ FILE: src/common/platform_exception.h ================================================ #pragma once #include namespace platform { struct Fiber; struct Exception { enum Type { AccessViolation = 1, InvalidInstruction = 2, }; Exception(Type type_) : type(type_) { } Type type; }; struct AccessViolationException : Exception { AccessViolationException(uint64_t address_) : Exception(Exception::AccessViolation), address(address_) { } uint64_t address; }; struct InvalidInstructionException : Exception { InvalidInstructionException() : Exception(Exception::InvalidInstruction) { } }; typedef void (*ExceptionResumeFunc)(); using ExceptionHandler = std::function; // Can be returned from ExceptionHandler to indicate to resume // execute of current fiber. static ExceptionResumeFunc const HandledException = reinterpret_cast(static_cast(-1)); static ExceptionResumeFunc const UnhandledException = reinterpret_cast(static_cast(0)); bool installExceptionHandler(ExceptionHandler handler); } // namespace platform ================================================ FILE: src/common/platform_fiber.h ================================================ #pragma once #include namespace platform { struct Fiber; using FiberEntryPoint = std::function; Fiber * getThreadFiber(); Fiber * createFiber(FiberEntryPoint entry, void *entryParam); void destroyFiber(Fiber *fiber); void swapToFiber(Fiber *current, Fiber *target); } // namespace platform ================================================ FILE: src/common/platform_intrin.h ================================================ #pragma once #if defined(_MSC_VER) || defined(__SSE3__) #define PLATFORM_HAS_SSE3 #endif #if defined(_MSC_VER) #include #else #include #endif ================================================ FILE: src/common/platform_memory.h ================================================ #pragma once #include #include #include namespace platform { enum class ProtectFlags { NoAccess, ReadOnly, ReadWrite, ReadExecute, ReadWriteExecute }; using MapFileHandle = intptr_t; static constexpr MapFileHandle InvalidMapFileHandle = -1; size_t getSystemPageSize(); MapFileHandle createMemoryMappedFile(size_t size); MapFileHandle openMemoryMappedFile(const std::string &path, ProtectFlags flags, size_t *outSize); bool closeMemoryMappedFile(MapFileHandle handle); void * mapViewOfFile(MapFileHandle handle, ProtectFlags flags, size_t offset, size_t size, void *dst = nullptr); bool unmapViewOfFile(void *view, size_t size); bool reserveMemory(uintptr_t address, size_t size); bool freeMemory(uintptr_t address, size_t size); bool commitMemory(uintptr_t address, size_t size, ProtectFlags flags = ProtectFlags::ReadWrite); bool uncommitMemory(uintptr_t address, size_t size); bool protectMemory(uintptr_t address, size_t size, ProtectFlags flags); } // namespace platform ================================================ FILE: src/common/platform_socket.h ================================================ #pragma once #include "platform.h" #ifdef PLATFORM_WINDOWS #define WIN32_LEAN_AND_MEAN #include #include #else #include #include #include #include #include #endif namespace platform { #ifdef PLATFORM_WINDOWS using Socket = SOCKET; #else using Socket = int; #endif bool socketWouldBlock(int result); int socketSetBlocking(Socket socket, bool blocking); int socketClose(Socket socket); } // namespace platform ================================================ FILE: src/common/platform_stacktrace.h ================================================ #pragma once #include namespace platform { struct StackTrace; StackTrace * captureStackTrace(); void freeStackTrace(StackTrace *trace); std::string formatStackTrace(StackTrace *trace); void printStackTrace(StackTrace *trace); } // namespace platform ================================================ FILE: src/common/platform_thread.h ================================================ #pragma once #include #include namespace platform { void setThreadName(std::thread *thread, const std::string &name); void exitThread(int result); } // namespace platform ================================================ FILE: src/common/platform_time.h ================================================ #pragma once #include namespace platform { tm localtime(const std::time_t& time); time_t make_gm_time(std::tm time); } // namespace platform ================================================ FILE: src/common/platform_winapi_string.h ================================================ #pragma once #include "platform.h" #ifdef PLATFORM_WINDOWS #include #include #define WIN32_LEAN_AND_MEAN #include namespace platform { /** * Convert from UTF-8 string to UTF-16 string expected by Windows API. */ static inline std::wstring toWinApiString(const std::string_view &utf8) { auto result = std::wstring { }; auto size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), NULL, 0); result.resize(size); MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), result.data(), static_cast(result.size())); return result; } /** * Convert to UTF-8 string from UTF-16 string returned by Windows API. */ static inline std::string fromWinApiString(const std::wstring_view &utf16) { auto result = std::string { }; auto size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), NULL, 0, NULL, NULL); result.resize(size); WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), result.data(), static_cast(result.size()), NULL, NULL); return result; } } // namespace platform #endif // ifdef PLATFORM_WINDOWS ================================================ FILE: src/common/pow.h ================================================ #pragma once template inline Type Log2(Type x) { Type y = 0; while (x > 1) { x >>= 1; y++; } return y; } ================================================ FILE: src/common/rangecombiner.h ================================================ #pragma once // void(_ObjType object, _OffsetType offset, _SizeType size) template class RangeCombiner { public: RangeCombiner(_FunctorType functor) : _functor(functor) { } void push(_ObjType object, _OffsetType offset, _SizeType size) { if (_size > 0 && _object == object && _offset + _size == offset) { _size += size; } else { flush(); _object = object; _offset = offset; _size = size; } } void flush() { if (_size == 0) { return; } _functor(_object, _offset, _size); _object = {}; _offset = {}; _size = {}; } protected: _FunctorType _functor = nullptr; _ObjType _object = {}; _OffsetType _offset = {}; _SizeType _size = {}; }; template static inline RangeCombiner<_ObjType, _OffsetType, _SizeType, _FunctorType> makeRangeCombiner(_FunctorType functor) { return RangeCombiner<_ObjType, _OffsetType, _SizeType, _FunctorType>(functor); } ================================================ FILE: src/common/src/assert.cpp ================================================ #include "decaf_assert.h" #include "log.h" #include "platform.h" #include "platform_stacktrace.h" #include #include #include #ifdef PLATFORM_WINDOWS #include "platform_winapi_string.h" #define WIN32_LEAN_AND_MEAN #include #undef NDEBUG #include #endif void assertFailed(const char *file, unsigned line, const char *expression, const std::string &message) { auto stackTrace = platform::captureStackTrace(); auto trace = platform::formatStackTrace(stackTrace); platform::freeStackTrace(stackTrace); fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Assertion failed:\n"); fmt::format_to(std::back_inserter(out), "Expression: {}\n", expression); fmt::format_to(std::back_inserter(out), "File: {}\n", file); fmt::format_to(std::back_inserter(out), "Line: {}\n", line); if (!message.empty()) { fmt::format_to(std::back_inserter(out), "Message: {}\n", message); } if (trace.size()) { fmt::format_to(std::back_inserter(out), "Stacktrace:\n{}\n", trace); } out.push_back('\0'); gLog->critical("{}", out.data()); std::cerr << out.data() << std::endl; #ifdef PLATFORM_WINDOWS if (IsDebuggerPresent()) { OutputDebugStringW(platform::toWinApiString(out.data()).c_str()); } else { auto wmsg = platform::toWinApiString(message); auto expr = platform::toWinApiString(expression); if (!wmsg.empty()) { expr += L"\nMessage: "; expr += wmsg; } _wassert(expr.c_str(), platform::toWinApiString(file).c_str(), line); } #endif } void assertWarnFailed(const char *file, unsigned line, const char *expression, const std::string &message) { gLog->warn("Asserted `{}` ({}) at {}:{}", expression, message, file, line); } void hostFaultWithStackTrace(const std::string &fault, platform::StackTrace *stackTrace) { auto trace = platform::formatStackTrace(stackTrace); fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Encountered host cpu fault:\n"); fmt::format_to(std::back_inserter(out), "Fault: {}\n", fault); if (trace.size()) { fmt::format_to(std::back_inserter(out), "Stacktrace:\n{}\n", trace); } out.push_back('\0'); gLog->critical("{}", out.data()); std::cerr << out.data() << std::endl; #ifdef PLATFORM_WINDOWS if (IsDebuggerPresent()) { OutputDebugStringW(platform::toWinApiString(out.data()).c_str()); } else { MessageBoxW(NULL, platform::toWinApiString(fault).c_str(), L"Encountered host cpu fault", MB_OK | MB_ICONERROR); } #endif } ================================================ FILE: src/common/src/log.cpp ================================================ #include "log.h" #include Logger gLog; void Logger::log(Level lvl, std::string_view msg) { if (mLogger) { auto logger = reinterpret_cast(mLogger.get()); logger->log(static_cast(lvl), msg); } } bool Logger::should_log(Level level) { if (!mLogger) { return false; } auto logger = reinterpret_cast(mLogger.get()); return logger->should_log(static_cast(level)); } ================================================ FILE: src/common/src/murmur3.cpp ================================================ //----------------------------------------------------------------------------- // MurmurHash3 was written by Austin Appleby, and is placed in the public // domain. The author hereby disclaims copyright to this source code. // Note - The x86 and x64 versions do _not_ produce the same results, as the // algorithms are optimized for their respective platforms. You can still // compile and run any of them on any platform, but your performance with the // non-native version will be less than optimal. #include "murmur3.h" //----------------------------------------------------------------------------- // Platform-specific functions and macros #ifdef __GNUC__ #define FORCE_INLINE __attribute__((always_inline)) inline #else #define FORCE_INLINE inline #endif static FORCE_INLINE uint32_t rotl32(uint32_t x, int8_t r) { return (x << r) | (x >> (32 - r)); } static FORCE_INLINE uint64_t rotl64(uint64_t x, int8_t r) { return (x << r) | (x >> (64 - r)); } #define ROTL32(x,y) rotl32(x,y) #define ROTL64(x,y) rotl64(x,y) #define BIG_CONSTANT(x) (x##LLU) //----------------------------------------------------------------------------- // Block read - if your platform needs to do endian-swapping or can only // handle aligned reads, do the conversion here #define getblock(p, i) (p[i]) //----------------------------------------------------------------------------- // Finalization mix - force all bits of a hash block to avalanche static FORCE_INLINE uint32_t fmix32(uint32_t h) { h ^= h >> 16; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; return h; } //---------- static FORCE_INLINE uint64_t fmix64(uint64_t k) { k ^= k >> 33; k *= BIG_CONSTANT(0xff51afd7ed558ccd); k ^= k >> 33; k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); k ^= k >> 33; return k; } //----------------------------------------------------------------------------- void MurmurHash3_x86_32(const void * key, int len, uint32_t seed, void * out) { const uint8_t * data = (const uint8_t*)key; const int nblocks = len / 4; int i; uint32_t h1 = seed; uint32_t c1 = 0xcc9e2d51; uint32_t c2 = 0x1b873593; //---------- // body const uint32_t * blocks = (const uint32_t *)(data + nblocks * 4); for (i = -nblocks; i; i++) { uint32_t k1 = getblock(blocks, i); k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; h1 = ROTL32(h1, 13); h1 = h1 * 5 + 0xe6546b64; } //---------- // tail const uint8_t * tail = (const uint8_t*)(data + nblocks * 4); uint32_t k1 = 0; switch (len & 3) { case 3: k1 ^= tail[2] << 16; case 2: k1 ^= tail[1] << 8; case 1: k1 ^= tail[0]; k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; }; //---------- // finalization h1 ^= len; h1 = fmix32(h1); *(uint32_t*)out = h1; } //----------------------------------------------------------------------------- void MurmurHash3_x86_128(const void * key, const int len, uint32_t seed, void * out) { const uint8_t * data = (const uint8_t*)key; const int nblocks = len / 16; int i; uint32_t h1 = seed; uint32_t h2 = seed; uint32_t h3 = seed; uint32_t h4 = seed; uint32_t c1 = 0x239b961b; uint32_t c2 = 0xab0e9789; uint32_t c3 = 0x38b34ae5; uint32_t c4 = 0xa1e38b93; //---------- // body const uint32_t * blocks = (const uint32_t *)(data + nblocks * 16); for (i = -nblocks; i; i++) { uint32_t k1 = getblock(blocks, i * 4 + 0); uint32_t k2 = getblock(blocks, i * 4 + 1); uint32_t k3 = getblock(blocks, i * 4 + 2); uint32_t k4 = getblock(blocks, i * 4 + 3); k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; h1 = ROTL32(h1, 19); h1 += h2; h1 = h1 * 5 + 0x561ccd1b; k2 *= c2; k2 = ROTL32(k2, 16); k2 *= c3; h2 ^= k2; h2 = ROTL32(h2, 17); h2 += h3; h2 = h2 * 5 + 0x0bcaa747; k3 *= c3; k3 = ROTL32(k3, 17); k3 *= c4; h3 ^= k3; h3 = ROTL32(h3, 15); h3 += h4; h3 = h3 * 5 + 0x96cd1c35; k4 *= c4; k4 = ROTL32(k4, 18); k4 *= c1; h4 ^= k4; h4 = ROTL32(h4, 13); h4 += h1; h4 = h4 * 5 + 0x32ac3b17; } //---------- // tail const uint8_t * tail = (const uint8_t*)(data + nblocks * 16); uint32_t k1 = 0; uint32_t k2 = 0; uint32_t k3 = 0; uint32_t k4 = 0; switch (len & 15) { case 15: k4 ^= tail[14] << 16; case 14: k4 ^= tail[13] << 8; case 13: k4 ^= tail[12] << 0; k4 *= c4; k4 = ROTL32(k4, 18); k4 *= c1; h4 ^= k4; case 12: k3 ^= tail[11] << 24; case 11: k3 ^= tail[10] << 16; case 10: k3 ^= tail[9] << 8; case 9: k3 ^= tail[8] << 0; k3 *= c3; k3 = ROTL32(k3, 17); k3 *= c4; h3 ^= k3; case 8: k2 ^= tail[7] << 24; case 7: k2 ^= tail[6] << 16; case 6: k2 ^= tail[5] << 8; case 5: k2 ^= tail[4] << 0; k2 *= c2; k2 = ROTL32(k2, 16); k2 *= c3; h2 ^= k2; case 4: k1 ^= tail[3] << 24; case 3: k1 ^= tail[2] << 16; case 2: k1 ^= tail[1] << 8; case 1: k1 ^= tail[0] << 0; k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; }; //---------- // finalization h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; h1 = fmix32(h1); h2 = fmix32(h2); h3 = fmix32(h3); h4 = fmix32(h4); h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; ((uint32_t*)out)[0] = h1; ((uint32_t*)out)[1] = h2; ((uint32_t*)out)[2] = h3; ((uint32_t*)out)[3] = h4; } //----------------------------------------------------------------------------- void MurmurHash3_x64_128(const void * key, const int len, const uint32_t seed, void * out) { const uint8_t * data = (const uint8_t*)key; const int nblocks = len / 16; int i; uint64_t h1 = seed; uint64_t h2 = seed; uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); //---------- // body const uint64_t * blocks = (const uint64_t *)(data); for (i = 0; i < nblocks; i++) { uint64_t k1 = getblock(blocks, i * 2 + 0); uint64_t k2 = getblock(blocks, i * 2 + 1); k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1; h1 = ROTL64(h1, 27); h1 += h2; h1 = h1 * 5 + 0x52dce729; k2 *= c2; k2 = ROTL64(k2, 33); k2 *= c1; h2 ^= k2; h2 = ROTL64(h2, 31); h2 += h1; h2 = h2 * 5 + 0x38495ab5; } //---------- // tail const uint8_t * tail = (const uint8_t*)(data + nblocks * 16); uint64_t k1 = 0; uint64_t k2 = 0; switch (len & 15) { case 15: k2 ^= (uint64_t)(tail[14]) << 48; case 14: k2 ^= (uint64_t)(tail[13]) << 40; case 13: k2 ^= (uint64_t)(tail[12]) << 32; case 12: k2 ^= (uint64_t)(tail[11]) << 24; case 11: k2 ^= (uint64_t)(tail[10]) << 16; case 10: k2 ^= (uint64_t)(tail[9]) << 8; case 9: k2 ^= (uint64_t)(tail[8]) << 0; k2 *= c2; k2 = ROTL64(k2, 33); k2 *= c1; h2 ^= k2; case 8: k1 ^= (uint64_t)(tail[7]) << 56; case 7: k1 ^= (uint64_t)(tail[6]) << 48; case 6: k1 ^= (uint64_t)(tail[5]) << 40; case 5: k1 ^= (uint64_t)(tail[4]) << 32; case 4: k1 ^= (uint64_t)(tail[3]) << 24; case 3: k1 ^= (uint64_t)(tail[2]) << 16; case 2: k1 ^= (uint64_t)(tail[1]) << 8; case 1: k1 ^= (uint64_t)(tail[0]) << 0; k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1; }; //---------- // finalization h1 ^= len; h2 ^= len; h1 += h2; h2 += h1; h1 = fmix64(h1); h2 = fmix64(h2); h1 += h2; h2 += h1; ((uint64_t*)out)[0] = h1; ((uint64_t*)out)[1] = h2; } //----------------------------------------------------------------------------- ================================================ FILE: src/common/src/platform_posix_debug.cpp ================================================ #include "platform.h" #include "platform_debug.h" #ifdef PLATFORM_POSIX namespace platform { void debugBreak() { // TODO: Implement debug breaks for POSIX } void debugLog(const std::string& message) { // TODO: Implement IDE debug logging for POSIX } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_dir.cpp ================================================ #include "platform.h" #include "platform_dir.h" #ifdef PLATFORM_POSIX #include #include #include #include #include namespace platform { bool createDirectory(const std::string &path) { if (!createParentDirectories(path)) { return false; } return mkdir(path.c_str(), 0755) == 0 || (errno == EEXIST && isDirectory(path)); } bool createParentDirectories(const std::string &path) { auto slashPos = path.rfind('/'); if (slashPos == std::string::npos || slashPos == 0) { return true; } return createDirectory(path.substr(0, slashPos)); } bool fileExists(const std::string &path) { return access(path.c_str(), F_OK) != -1; } bool isFile(const std::string &path) { struct stat info; auto result = stat(path.c_str(), &info); if (result != 0) { return false; } return S_ISREG(info.st_mode); } bool isDirectory(const std::string &path) { struct stat info; auto result = stat(path.c_str(), &info); if (result != 0) { return false; } return S_ISDIR(info.st_mode); } std::string getConfigDirectory() { // TODO: will be different for Mac const char *home = getenv("HOME"); if (home && *home) { return fmt::format("{}/.config", home); } else { return "."; } } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_exception.cpp ================================================ #include #include "platform.h" #include "platform_exception.h" #include "platform_fiber.h" #include "log.h" #ifdef PLATFORM_POSIX #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include namespace platform { static std::vector sExceptionHandlers; static struct sigaction sSegvHandler; static struct sigaction sSystemSegvHandler; static struct sigaction sIllHandler; static struct sigaction sSystemIllHandler; static void dispatchException(Exception *exception, void *context, int signum, const struct sigaction *ourHandler, const struct sigaction *sysHandler) { // Reset to the original signal handler in case an exception handler // generates a signal of its own sigaction(signum, sysHandler, nullptr); static bool sInSignal = false; // Avoid recursive signal handling (in case an exception handler looking // at a SIGILL causes a SIGSEGV, for example) if (sInSignal) { return; } sInSignal = true; for (auto &handler : sExceptionHandlers) { auto func = handler(exception); if (func == UnhandledException) { // Exception unhandled, try another handler continue; } sInSignal = false; // Reinstall our signal handler sigaction(signum, ourHandler, nullptr); if (func == HandledException) { // Exception handled, resume execution return; } else { // Exception handled, switch execution to target function auto ctx = reinterpret_cast(context); #ifdef PLATFORM_APPLE ctx->uc_mcontext->__ss.__rip = reinterpret_cast(func); #else ctx->uc_mcontext.gregs[REG_RIP] = reinterpret_cast(func); #endif return; } } // No exception handlers, found, so re-run the failing instruction to // call the original signal handler return; } static void segvHandler(int signum, siginfo_t *info, void *context) { auto exception = AccessViolationException { reinterpret_cast(info->si_addr) }; dispatchException(&exception, context, signum, &sSegvHandler, &sSystemSegvHandler); } static void illHandler(int signum, siginfo_t *info, void *context) { auto exception = InvalidInstructionException { }; dispatchException(&exception, context, signum, &sIllHandler, &sSystemIllHandler); } bool installExceptionHandler(ExceptionHandler handler) { static bool addedHandlers = false; if (!addedHandlers) { sigemptyset(&sSegvHandler.sa_mask); // Set SA_RESETHAND so that a SEGV in the handler will terminate the // program rather than going into an infinite loop. sSegvHandler.sa_flags = SA_SIGINFO | SA_RESETHAND; sSegvHandler.sa_sigaction = segvHandler; if (sigaction(SIGSEGV, &sSegvHandler, &sSystemSegvHandler) != 0) { gLog->error("sigaction(SIGSEGV) failed: {}", strerror(errno)); return false; } sIllHandler = sSegvHandler; sIllHandler.sa_sigaction = illHandler; if (sigaction(SIGILL, &sIllHandler, &sSystemIllHandler) != 0) { gLog->error("sigaction(SIGILL) failed: {}", strerror(errno)); return false; } addedHandlers = true; } sExceptionHandlers.push_back(handler); return true; } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_fiber.cpp ================================================ #include "platform.h" #include "platform_fiber.h" #include "log.h" #ifdef PLATFORM_POSIX #include #include #include #include #ifdef DECAF_VALGRIND #include #endif namespace platform { static const size_t DefaultStackSize = 1024 * 1024; struct Fiber { ucontext_t context; FiberEntryPoint entry = nullptr; void *entryParam = nullptr; #ifdef DECAF_VALGRIND unsigned int valgrindStackId; #endif std::array stack; }; Fiber * getThreadFiber() { auto fiber = new Fiber(); return fiber; } static void fiberEntryPoint(Fiber *fiber) { fiber->entry(fiber->entryParam); } Fiber * createFiber(FiberEntryPoint entry, void *entryParam) { auto fiber = new Fiber(); fiber->entry = entry; fiber->entryParam = entryParam; #ifdef DECAF_VALGRIND fiber->valgrindStackId = VALGRIND_STACK_REGISTER(&fiber->stack[0], &fiber->stack[fiber->stack.size() - 1]); #endif getcontext(&fiber->context); fiber->context.uc_stack.ss_sp = &fiber->stack[0]; fiber->context.uc_stack.ss_size = fiber->stack.size(); fiber->context.uc_link = nullptr; makecontext(&fiber->context, reinterpret_cast(&fiberEntryPoint), 1, fiber); return fiber; } void destroyFiber(Fiber *fiber) { #ifdef DECAF_VALGRIND VALGRIND_STACK_DEREGISTER(fiber->valgrindStackId); #endif delete fiber; } void swapToFiber(Fiber *current, Fiber *target) { if (!current) { setcontext(&target->context); } else { swapcontext(¤t->context, &target->context); } } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_memory.cpp ================================================ #include "platform.h" #include "platform_memory.h" #include "log.h" #ifdef PLATFORM_POSIX #include #include #include #include #include #include #include #include #include #include namespace platform { static int flagsToProt(ProtectFlags flags) { switch (flags) { case ProtectFlags::ReadOnly: return PROT_READ; case ProtectFlags::ReadWrite: return PROT_READ | PROT_WRITE; case ProtectFlags::ReadExecute: return PROT_READ | PROT_EXEC; case ProtectFlags::ReadWriteExecute: return PROT_READ | PROT_WRITE | PROT_EXEC; case ProtectFlags::NoAccess: default: return PROT_WRITE; } } static int flagsToOpen(ProtectFlags flags) { switch (flags) { case ProtectFlags::ReadOnly: return O_RDONLY; case ProtectFlags::ReadWrite: return O_RDWR; case ProtectFlags::ReadExecute: return O_RDONLY; case ProtectFlags::ReadWriteExecute: return O_RDWR; case ProtectFlags::NoAccess: default: return PROT_WRITE; } } size_t getSystemPageSize() { return static_cast(sysconf(_SC_PAGESIZE)); } MapFileHandle createMemoryMappedFile(size_t size) { const char *tmpdir = getenv("TMPDIR"); if (!tmpdir || !*tmpdir) { tmpdir = "/tmp"; } const std::string pattern = fmt::format("{}/decafXXXXXX", tmpdir); char *path = strdup(pattern.c_str()); // Must be a modifiable char array. int old_umask = umask(0077); int fd = mkstemp(path); if (fd == -1) { gLog->error("createMemoryMappedFile({}) mkstemp failed with error: {}", size, errno); umask(old_umask); return InvalidMapFileHandle; } umask(old_umask); if (unlink(path) == -1) { gLog->error("createMemoryMappedFile({}) unlink failed with error: {}", size, errno); } free(path); #ifdef PLATFORM_APPLE if (ftruncate(fd, size) == -1) { #else if (ftruncate64(fd, size) == -1) { #endif gLog->error("createMemoryMappedFile({}) ftruncate64 failed with error: {}", size, errno); } return static_cast(fd); } MapFileHandle openMemoryMappedFile(const std::string &path, ProtectFlags flags, size_t *outSize) { // Only support READ ONLY for now decaf_check(flags == ProtectFlags::ReadOnly); struct stat st; if (stat(path.c_str(), &st) == -1) { gLog->error("openMemoryMappedFile(\"{}\") stat failed with error: {}", path, errno); return InvalidMapFileHandle; } auto fd = open(path.c_str(), flagsToOpen(flags), 0); if (fd == -1) { gLog->error("openMemoryMappedFile(\"{}\") open failed with error: {}", path, errno); return InvalidMapFileHandle; } *outSize = st.st_size; return static_cast(fd); } bool closeMemoryMappedFile(MapFileHandle handle) { if (close(static_cast(handle)) == -1) { gLog->error("closeMemoryMappedFile({}) close failed with error: {}", static_cast(handle), errno); return false; } return true; } void * mapViewOfFile(MapFileHandle handle, ProtectFlags flags, size_t offset, size_t size, void *dst) { auto prot = flagsToProt(flags); auto result = mmap(dst, size, prot, MAP_SHARED, static_cast(handle), offset); if (result == MAP_FAILED) { gLog->error("mapViewOfFile(offset: 0x{:X}, size: 0x{:X}, dst: {}) mmap failed with error: {}", offset, size, dst, errno); return nullptr; } if (result != dst) { gLog->error("mapViewOfFile(offset: 0x{:X}, size: 0x{:X}, dst: {}) mmap returned unexpected address: {}", offset, size, dst, result); munmap(result, size); return nullptr; } return result; } bool unmapViewOfFile(void *view, size_t size) { if (munmap(view, size) == -1) { gLog->error("unmapViewOfFile(view: 0x{:X}, size: 0x{:X}) munmap failed with error: {}", view, size, errno); return false; } return true; } bool reserveMemory(uintptr_t address, size_t size) { // On *nix systems, regions mapped with mmap are reserved only by default // and become automatically commited on first use. Because these pages // have no protection rights, they are forced to stay reserved. auto baseAddress = reinterpret_cast(address); auto result = mmap(baseAddress, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (result == MAP_FAILED) { gLog->debug("reserveMemory(address: 0x{:08X}, size: 0x{:X}) mmap failed with error: {}", address, size, errno); return false; } if (result != baseAddress) { gLog->debug("reserveMemory(address: 0x{:08X}, size: 0x{:X}) returned unexpected address: {}", address, size, result); munmap(result, size); return false; } return true; } bool freeMemory(uintptr_t address, size_t size) { auto baseAddress = reinterpret_cast(address); if (munmap(baseAddress, size) == -1) { gLog->error("freeMemory(address: 0x{:08X}, size: 0x{:X}) munmap failed with error: {}", address, size, errno); return false; } return true; } bool commitMemory(uintptr_t address, size_t size, ProtectFlags flags) { auto baseAddress = reinterpret_cast(address); if (mprotect(baseAddress, size, flagsToProt(flags)) == -1) { gLog->error("commitMemory(address: 0x{:08X}, size: 0x{:X}, flags: {}) mprotect failed with error: {}", address, size, static_cast(flags), errno); return false; } return true; } bool uncommitMemory(uintptr_t address, size_t size) { // On *nix systems, there is not really a way to forcibly uncommit // a particular region of code. We just lock it out. auto baseAddress = reinterpret_cast(address); if (mprotect(baseAddress, size, PROT_NONE) == -1) { gLog->error("uncommitMemory(address: {}, size: {}) mprotect failed with error: {}", address, size, errno); return false; } return true; } bool protectMemory(uintptr_t address, size_t size, ProtectFlags flags) { auto baseAddress = reinterpret_cast(address); if (mprotect(baseAddress, size, flagsToProt(flags)) == -1) { gLog->error("protectMemory(address: {}, size: {}, flags: {}) mprotect failed with error: {}", address, size, static_cast(flags), errno); return false; } return true; } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_socket.cpp ================================================ #include "platform_socket.h" #ifdef PLATFORM_POSIX #include #include namespace platform { bool socketWouldBlock(int result) { return (result == EWOULDBLOCK); } int socketSetBlocking(Socket socket, bool blocking) { auto fl = fcntl(socket, F_GETFL, 0); if (blocking) { fl &= ~O_NONBLOCK; } else { fl |= O_NONBLOCK; } return fcntl(socket, F_SETFL, fl); } int socketClose(Socket socket) { return close(socket); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_stacktrace.cpp ================================================ #include "decaf_assert.h" #include "platform.h" #include "platform_stacktrace.h" #ifdef PLATFORM_POSIX #include namespace platform { StackTrace * captureStackTrace() { return nullptr; } void freeStackTrace(StackTrace *) { } std::string formatStackTrace(StackTrace *trace) { return { }; } void printStackTrace(StackTrace *) { decaf_abort("POSIX support for stack tracing is not implemented"); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_thread.cpp ================================================ #include "platform.h" #include "platform_thread.h" #ifdef PLATFORM_POSIX #include #include namespace platform { void setThreadName(std::thread *thread, const std::string &name) { #ifndef PLATFORM_APPLE auto handle = thread->native_handle(); pthread_setname_np(handle, name.c_str()); #endif } void exitThread(int result) { auto res = reinterpret_cast(malloc(sizeof(int))); *res = result; pthread_exit(res); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_posix_time.cpp ================================================ #include "platform.h" #include "platform_time.h" #ifdef PLATFORM_POSIX #include namespace platform { tm localtime(const std::time_t& time) { std::tm tm_snapshot; localtime_r(&time, &tm_snapshot); return tm_snapshot; } time_t make_gm_time(std::tm time) { return timegm(&time); } } #endif ================================================ FILE: src/common/src/platform_win_debug.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_debug.h" #define WIN32_LEAN_AND_MEAN #include namespace platform { void debugBreak() { DebugBreak(); } void debugLog(const std::string& message) { OutputDebugStringA(message.c_str()); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_dir.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_dir.h" #include "platform_winapi_string.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include namespace platform { static bool isDriveName(const std::string &path) { return path.length() == 2 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':'; } bool createDirectory(const std::string &path) { if (!createParentDirectories(path)) { return false; } auto winPath = platform::toWinApiString(path); return _wmkdir(winPath.c_str()) == 0 || (errno == EEXIST && isDirectory(path)); } bool createParentDirectories(const std::string &path) { auto slashPos = path.find_last_of("/\\"); if (slashPos == std::string::npos || (slashPos == 2 && isDriveName(path.substr(0, 2))) || (path.find_first_not_of("/\\") == 2 // "\\server\path" syntax && path.find_first_of("/\\", 2) == slashPos)) { return true; } return createDirectory(path.substr(0, slashPos)); } bool fileExists(const std::string &path) { auto winPath = platform::toWinApiString(path); return _waccess_s(winPath.c_str(), 0) == 0; } bool isFile(const std::string &path) { auto winPath = platform::toWinApiString(path); struct _stat64 info; if (_wstat64(winPath.c_str(), &info)) { return false; } return !!(info.st_mode & _S_IFREG); } bool isDirectory(const std::string &path) { auto winPath = platform::toWinApiString(path); struct _stat64 info; if (_wstat64(winPath.c_str(), &info)) { return false; } return !!(info.st_mode & _S_IFDIR); } std::string getConfigDirectory() { PWSTR path; auto result = std::string { "." }; if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &path))) { result = platform::fromWinApiString(path); CoTaskMemFree(path); } return result; } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_exception.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_exception.h" #include "platform_fiber.h" #define WIN32_LEAN_AND_MEAN #include #include namespace platform { static std::vector gExceptionHandlers; LONG dispatchException(PEXCEPTION_POINTERS info, Exception *exception) { for (auto &handler : gExceptionHandlers) { auto func = handler(exception); if (func == UnhandledException) { // Exception unhandled, try another handler continue; } else if (func == HandledException) { // Exception handled, resume execution return EXCEPTION_CONTINUE_EXECUTION; } else { // Exception handled, jump to new function info->ContextRecord->Rip = reinterpret_cast(func); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; } static LONG CALLBACK exceptionHandler(PEXCEPTION_POINTERS info) { switch (info->ExceptionRecord->ExceptionCode) { case STATUS_ACCESS_VIOLATION: { auto address = info->ExceptionRecord->ExceptionInformation[1]; auto exception = AccessViolationException{ address }; return dispatchException(info, &exception); } break; case STATUS_ILLEGAL_INSTRUCTION: { auto exception = InvalidInstructionException{ }; return dispatchException(info, &exception); } break; } // Unhandled exception return EXCEPTION_CONTINUE_SEARCH; } bool installExceptionHandler(ExceptionHandler handler) { static bool addedHandler = false; if (!addedHandler) { AddVectoredExceptionHandler(0, exceptionHandler); addedHandler = true; } gExceptionHandlers.push_back(handler); return true; } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_fiber.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_fiber.h" #define WIN32_LEAN_AND_MEAN #include namespace platform { struct Fiber { LPVOID handle = nullptr; FiberEntryPoint entry = nullptr; void *entryParam = nullptr; }; Fiber * getThreadFiber() { auto fiber = new Fiber(); fiber->handle = ConvertThreadToFiber(NULL); return fiber; } static void __stdcall fiberEntryPoint(LPVOID lpFiberParameter) { auto fiber = reinterpret_cast(lpFiberParameter); fiber->entry(fiber->entryParam); } Fiber * createFiber(FiberEntryPoint entry, void *entryParam) { auto fiber = new Fiber(); fiber->handle = CreateFiber(0, &fiberEntryPoint, fiber); fiber->entry = entry; fiber->entryParam = entryParam; return fiber; } void destroyFiber(Fiber *fiber) { DeleteFiber(fiber->handle); delete fiber; } void swapToFiber(Fiber *current, Fiber *target) { SwitchToFiber(target->handle); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_memory.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "decaf_assert.h" #include "log.h" #include "platform_memory.h" #define WIN32_LEAN_AND_MEAN #include #include #include namespace platform { struct WindowsMapFileHandle { HANDLE fileHandle; HANDLE mappingHandle; }; static DWORD flagsToPageProtect(ProtectFlags flags) { switch (flags) { case ProtectFlags::ReadOnly: return PAGE_READONLY; case ProtectFlags::ReadWrite: return PAGE_READWRITE; case ProtectFlags::ReadExecute: return PAGE_EXECUTE_READ; case ProtectFlags::ReadWriteExecute: return PAGE_EXECUTE_READWRITE; case ProtectFlags::NoAccess: default: return PAGE_NOACCESS; } } static DWORD flagsToFileMap(ProtectFlags flags) { switch (flags) { case ProtectFlags::ReadOnly: return FILE_MAP_READ; case ProtectFlags::ReadWrite: return FILE_MAP_WRITE; case ProtectFlags::ReadExecute: return FILE_MAP_READ | FILE_MAP_EXECUTE; case ProtectFlags::ReadWriteExecute: return FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE; case ProtectFlags::NoAccess: default: return 0; } } size_t getSystemPageSize() { SYSTEM_INFO info; GetSystemInfo(&info); return static_cast(info.dwAllocationGranularity); } MapFileHandle createMemoryMappedFile(size_t size) { auto sizeLo = static_cast(size & 0xFFFFFFFFu); auto sizeHi = static_cast((size >> 32) & 0xFFFFFFFFu); auto handle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, SEC_COMMIT | PAGE_READWRITE, sizeHi, sizeLo, NULL); if (!handle) { gLog->error("createMemoryMappedFile(0x{:X}) failed with error: {}", size, GetLastError()); return InvalidMapFileHandle; } auto windowsHandle = new WindowsMapFileHandle { }; windowsHandle->mappingHandle = handle; windowsHandle->fileHandle = NULL; return reinterpret_cast(windowsHandle); } MapFileHandle openMemoryMappedFile(const std::string &path, ProtectFlags flags, size_t *outSize) { // Only support READ ONLY for now decaf_check(flags == ProtectFlags::ReadOnly); OFSTRUCT of; auto fileHandle = reinterpret_cast(static_cast(OpenFile(path.c_str(), &of, OF_READ))); if (!fileHandle) { gLog->error("openMemoryMappedFile(\"{}\") OpenFile failed with error: {}", path, GetLastError()); return InvalidMapFileHandle; } auto mapHandle = CreateFileMappingW(fileHandle, NULL, PAGE_READONLY, 0, 0, NULL); if (!mapHandle) { CloseHandle(fileHandle); gLog->error("openMemoryMappedFile(\"{}\") CreateFileMapping failed with error: {}", path, GetLastError()); return InvalidMapFileHandle; } if (outSize) { LARGE_INTEGER size; GetFileSizeEx(fileHandle, &size); *outSize = static_cast(size.QuadPart); } auto windowsHandle = new WindowsMapFileHandle { }; windowsHandle->mappingHandle = mapHandle; windowsHandle->fileHandle = fileHandle; return reinterpret_cast(windowsHandle); } bool closeMemoryMappedFile(MapFileHandle handle) { auto windowsHandle = reinterpret_cast(handle); if (windowsHandle->fileHandle) { if (!CloseHandle(windowsHandle->fileHandle)) { gLog->error("closeMemoryMappedFile({}) close file handle failed with error: {}", handle, GetLastError()); } windowsHandle->fileHandle = NULL; } if (windowsHandle->mappingHandle) { if (!CloseHandle(windowsHandle->mappingHandle)) { gLog->error("closeMemoryMappedFile({}) close map handle failed with error: {}", handle, GetLastError()); return false; } windowsHandle->mappingHandle = NULL; } delete windowsHandle; return true; } void * mapViewOfFile(MapFileHandle handle, ProtectFlags flags, size_t offset, size_t size, void *dst) { auto windowsHandle = reinterpret_cast(handle); auto access = flagsToFileMap(flags); auto offsetLo = static_cast(offset & 0xFFFFFFFFu); auto offsetHi = static_cast((offset >> 32) & 0xFFFFFFFFu); auto result = MapViewOfFileEx(reinterpret_cast(windowsHandle->mappingHandle), access, offsetHi, offsetLo, size, dst); if (result == nullptr) { gLog->error("mapViewOfFile(offset: 0x{:X}, size: 0x{:X}, dst: 0x{:X}) failed with error: {}", offset, size, dst, GetLastError()); } return result; } bool unmapViewOfFile(void *view, size_t size) { if (!UnmapViewOfFile(view)) { gLog->error("unmapViewOfFile(view: 0x{:X}, size: 0x{:X}) failed with error: {}", view, size, GetLastError()); return false; } return true; } bool reserveMemory(uintptr_t address, size_t size) { auto baseAddress = reinterpret_cast(address); auto result = VirtualAlloc(baseAddress, size, MEM_RESERVE, PAGE_NOACCESS); if (result != baseAddress) { return false; } return true; } bool freeMemory(uintptr_t address, size_t size) { auto baseAddress = reinterpret_cast(address); if (!VirtualFree(baseAddress, 0, MEM_RELEASE)) { gLog->error("freeMemory(address: {}, size: {}) failed with error: {}", address, size, GetLastError()); return false; } return true; } bool commitMemory(uintptr_t address, size_t size, ProtectFlags flags) { auto baseAddress = reinterpret_cast(address); auto result = VirtualAlloc(baseAddress, size, MEM_COMMIT, flagsToPageProtect(flags)); if (result != baseAddress) { gLog->error("commitMemory(address: 0x{:X}, size: 0x{:X}, flags: {}) failed with error: {}", address, size, static_cast(flags), GetLastError()); return false; } return true; } bool uncommitMemory(uintptr_t address, size_t size) { auto baseAddress = reinterpret_cast(address); if (!VirtualFree(baseAddress, size, MEM_DECOMMIT)) { gLog->error("uncommitMemory(address: 0x{:X}, size: 0x{:X}) failed with error: {}", address, size, GetLastError()); return false; } return true; } bool protectMemory(uintptr_t address, size_t size, ProtectFlags flags) { auto baseAddress = reinterpret_cast(address); DWORD oldProtect; if (!VirtualProtect(baseAddress, size, flagsToPageProtect(flags), &oldProtect)) { gLog->error("protectMemory(address: 0x{:X}, size: 0x{:X}, flags: {}) failed with error: {}", address, size, static_cast(flags), GetLastError()); return false; } return true; } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_socket.cpp ================================================ #include "platform_socket.h" #ifdef PLATFORM_WINDOWS namespace platform { bool socketWouldBlock(int result) { return (result < 0 && WSAGetLastError() == WSAEWOULDBLOCK); } int socketSetBlocking(Socket socket, bool blocking) { u_long iMode = blocking ? 0 : 1; return ioctlsocket(socket, FIONBIO, &iMode); } int socketClose(Socket socket) { return closesocket(socket); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_stacktrace.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_stacktrace.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include #include namespace platform { struct StackTrace { void *data[0x100]; uint16_t frames; }; struct MySymbol : SYMBOL_INFO { CHAR NameExt[MAX_SYM_NAME]; }; StackTrace * captureStackTrace() { auto trace = new StackTrace(); trace->frames = CaptureStackBackTrace(0, 0x100, trace->data, NULL); return trace; } void freeStackTrace(StackTrace *trace) { delete trace; } std::string formatStackTrace(StackTrace *trace) { auto process = GetCurrentProcess(); static bool symInitialise = false; if (!symInitialise) { SymInitialize(process, NULL, TRUE); symInitialise = true; } auto symbol = std::make_unique(); symbol->MaxNameLen = MAX_SYM_NAME; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); fmt::memory_buffer out; for (auto i = 0u; i < trace->frames; ++i) { SymFromAddr(process, (DWORD64)trace->data[i], 0, symbol.get()); fmt::format_to(std::back_inserter(out), "{}: {} - 0x{:X}x\n", trace->frames - i - 1, (const char*)symbol->Name, symbol->Address); } return out.data(); } void printStackTrace(StackTrace *trace) { auto str = formatStackTrace(trace); OutputDebugStringA(str.c_str()); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_thread.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_thread.h" #define WIN32_LEAN_AND_MEAN #include static const DWORD MS_VC_EXCEPTION = 0x406D1388; #pragma pack(push, 8) typedef struct tagTHREADNAME_INFO { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. } THREADNAME_INFO; #pragma pack(pop) namespace platform { void setThreadName(std::thread *thread, const std::string &threadName) { DWORD dwThreadID = ::GetThreadId(static_cast(thread->native_handle())); THREADNAME_INFO info; info.dwType = 0x1000; info.szName = threadName.c_str(); info.dwThreadID = dwThreadID; info.dwFlags = 0; __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); } __except (EXCEPTION_EXECUTE_HANDLER) { } } void exitThread(int result) { ExitThread(result); } } // namespace platform #endif ================================================ FILE: src/common/src/platform_win_time.cpp ================================================ #include "platform.h" #ifdef PLATFORM_WINDOWS #include "platform_time.h" namespace platform { tm localtime(const std::time_t& time) { std::tm tm_snapshot; localtime_s(&tm_snapshot, &time); return tm_snapshot; } time_t make_gm_time(std::tm time) { return _mkgmtime(&time); } } // namespace platform #endif ================================================ FILE: src/common/structsize.h ================================================ #pragma once #include #include "platform.h" // Workaround weird macro concat ## behaviour #define PP_CAT(a, b) PP_CAT_I(a, b) #define PP_CAT_I(a, b) PP_CAT_II(~, a ## b) #define PP_CAT_II(p, res) res // Ensure our structs are correct size & offsets to match WiiU #define CHECK_SIZE(Type, Size) \ static_assert(sizeof(Type) == Size, \ #Type " must be " #Size " bytes") #define CHECK_OFFSET(Type, Offset, Field) \ static_assert(offsetof(Type, Field) == Offset, \ #Type "::" #Field " must be at offset " #Offset) #define _CHECK_MEMBER_OFFSET_BEG \ void PP_CAT(__verifyMemberOffsets, __COUNTER__) () { #define _CHECK_MEMBER_OFFSET_END \ } #ifdef PLATFORM_WINDOWS #define CHECK_MEMBER_OFFSET_BEG _CHECK_MEMBER_OFFSET_BEG #define CHECK_MEMBER_OFFSET_END _CHECK_MEMBER_OFFSET_END #else #define CHECK_MEMBER_OFFSET_BEG \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"") \ _CHECK_MEMBER_OFFSET_BEG #define CHECK_MEMBER_OFFSET_END \ _CHECK_MEMBER_OFFSET_END \ _Pragma("GCC diagnostic pop") #endif // TODO: Figure out how to implement this, might be impossible? #define CHECK_BIT_OFFSET(Type, Offset, Field) // Allow us to easily add UNKNOWN / PADDING bytes into our structs, // generates unique variable names using __COUNTER__ #define UNKNOWN(Size) char PP_CAT(__unk, __COUNTER__) [Size] #define PADDING(Size) UNKNOWN(Size) #define UNKNOWN_ARGS void #define UNKNOWN_SIZE(x) //x ================================================ FILE: src/common/strutils.h ================================================ #pragma once #include "platform.h" #include #include #include #include #include #include #include #ifdef PLATFORM_WINDOWS #define strdup _strdup #endif // Replace all occurences of a character in a string inline void replace_all(std::string &source, char find, char replace) { std::string::size_type offset = 0; while ((offset = source.find(find, offset)) != std::string::npos) { source[offset] = replace; ++offset; } } // Split a string by a character inline void split_string(const std::string_view &source, char delimiter, std::vector &result) { std::string_view::size_type offset = 0, last = 0; if (source.at(0) == delimiter) { offset = last = 1; } while ((offset = source.find(delimiter, offset)) != std::string::npos) { if (offset - last > 0) { result.push_back(std::string { source.substr(last, offset - last) }); } last = ++offset; } result.push_back(std::string { source.substr(last, offset - last) }); } // Joins a vector of strings into one string using a delimeter template inline void join_string(IteratorType begin, IteratorType end, char delim, std::string &out) { bool first = true; for (auto itr = begin; itr != end; ++itr) { if (first) { out += *itr; first = false; } else { out += delim + *itr; } } } // Returns true if source begins with prefix inline bool begins_with(const std::string_view &source, const std::string_view &prefix) { if (prefix.size() > source.size()) { return false; } else { return std::equal(prefix.begin(), prefix.end(), source.begin()); } } // Returns true if source ends with suffix inline bool ends_with(const std::string_view &source, const std::string_view &suffix) { if (suffix.size() > source.size()) { return false; } else { return std::equal(suffix.rbegin(), suffix.rend(), source.rbegin()); } } // Case insensitive string compare inline bool iequals(const std::string_view &a, const std::string_view &b) { return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); } // A strncpy which does not warn on Windows inline void string_copy(char *dst, size_t dstSize, const char *src, size_t maxCount) { #ifdef PLATFORM_WINDOWS strncpy_s(dst, dstSize, src, maxCount); #else std::strncpy(dst, src, dstSize); #endif } inline void string_copy(char *dst, const char *src, size_t maxCount) { string_copy(dst, maxCount, src, maxCount); } template inline void string_copy(DstCharT *dst, size_t dstSize, const SrcCharT *src, size_t maxCount) { if (dstSize <= maxCount) { maxCount = dstSize - 1; } auto i = size_t { 0 }; while (src[i] && i < maxCount) { dst[i] = static_cast(src[i]); i++; } dst[i] = static_cast('\0'); } inline std::string_view trim_view(std::string_view str) { while (!str.empty() && std::isspace(str[0])) { str.remove_prefix(1); } while (!str.empty() && std::isspace(str.back())) { str.remove_suffix(1); } return str; } inline std::string trim(std::string_view str) { while (!str.empty() && std::isspace(str[0])) { str.remove_prefix(1); } while (!str.empty() && std::isspace(str.back())) { str.remove_suffix(1); } return std::string { str }; } ================================================ FILE: src/common/teenyheap.h ================================================ #pragma once #include "align.h" #include "decaf_assert.h" #include #include #include #include class TeenyHeap { private: struct MemoryBlock { uint8_t *start; size_t size; }; public: TeenyHeap() : mBuffer(nullptr), mSize(0) { } TeenyHeap(void *buffer, size_t size) { reset(buffer, size); } void reset(void *buffer, size_t size) { mAllocatedBlocks.clear(); mFreeBlocks.clear(); mBuffer = static_cast(buffer); mSize = size; mFreeBlocks.emplace_back(MemoryBlock { mBuffer, mSize }); } size_t getLargestFreeSize() { auto largest = size_t { 0 }; for (auto &block : mFreeBlocks) { largest = std::max(largest, block.size); } return largest; } size_t getTotalFreeSize() { auto total = size_t { 0 }; for (auto &block : mFreeBlocks) { total += block.size; } return total; } void * alloc(size_t size, size_t alignment = 4) { std::unique_lock lock(mMutex); auto adjSize = size; auto block = mFreeBlocks.begin(); for (block = mFreeBlocks.begin(); block != mFreeBlocks.end(); ++block) { auto alignedDiff = align_up(block->start, alignment) - block->start; if (block->size - alignedDiff >= adjSize) { adjSize += alignedDiff; break; } } if (block == mFreeBlocks.end()) { return nullptr; } auto start = block->start; block->start += adjSize; block->size -= adjSize; // Erase block if it gets too small if (block->size <= sizeof(MemoryBlock) + 4) { adjSize += block->size; mFreeBlocks.erase(block); } // Allocate block auto alignedStart = align_up(start, alignment); mAllocatedBlocks.emplace(alignedStart, MemoryBlock { start, adjSize }); return alignedStart; } void free(void *ptr) { std::unique_lock lock(mMutex); auto ucptr = static_cast(ptr); auto itr = mAllocatedBlocks.find(ucptr); decaf_check(itr != mAllocatedBlocks.end()); releaseBlock(itr->second); mAllocatedBlocks.erase(itr); } protected: void releaseBlock(MemoryBlock block) { auto blockStart = block.start; auto blockEnd = block.start + block.size; for (auto &free : mFreeBlocks) { auto freeStart = free.start; auto freeEnd = free.start + free.size; if (blockStart == freeEnd) { free.size += block.size; return; } if (blockEnd == freeStart) { free.start = blockStart; free.size += block.size; return; } } mFreeBlocks.emplace_back(block); } uint8_t *mBuffer; size_t mSize; std::vector mFreeBlocks; std::map mAllocatedBlocks; std::mutex mMutex; }; ================================================ FILE: src/common/tga_encoder.cpp ================================================ #include "log.h" #include "tga_encoder.h" #include namespace tga { bool writeFile(const std::string &filename, uint32_t bpp, uint32_t alphaBits, uint32_t width, uint32_t height, void *data) { std::ofstream out { filename, std::ofstream::binary }; if (!out.is_open()) { gLog->error("Could not open {} for writing", filename); return false; } FileHeader header; std::memset(&header, 0, sizeof(FileHeader)); header.imageType = FileHeader::TrueColour; header.width = static_cast(width); header.height = static_cast(height); header.bpp = static_cast(bpp); header.descriptor = alphaBits & 0b1111; out.write(reinterpret_cast(&header), sizeof(FileHeader)); out.write(reinterpret_cast(data), width * height * (bpp / 8)); return true; } } // namespace tga ================================================ FILE: src/common/tga_encoder.h ================================================ #pragma once #include #include namespace tga { #pragma pack(push, 1) struct FileHeader { enum ImageType : uint8_t { None = 0, ColourMapped = 1, TrueColour = 2, Grayscale = 3, ColourMappedRLE = 9, TrueColourRLE = 10, GrayscaleRLE = 11, }; uint8_t id; uint8_t colorMapType; uint8_t imageType; struct { uint16_t firstEntry; uint16_t numEntries; uint8_t entrySize; } colorMap; uint16_t x; uint16_t y; uint16_t width; uint16_t height; uint8_t bpp; uint8_t descriptor; }; #pragma pack(pop) bool writeFile(const std::string &filename, uint32_t bpp, uint32_t alphaBits, uint32_t width, uint32_t height, void *data); } // namespace tga ================================================ FILE: src/common/type_list.h ================================================ #pragma once template struct type_list { }; ================================================ FILE: src/common/type_traits.h ================================================ #pragma once #include // Same as std::underlying_type but works for non-enum Types template::value> struct safe_underlying_type : std::underlying_type { }; template struct safe_underlying_type { using type = T; }; // Maps bool value to a std::bool_constant type template struct is_true; template<> struct is_true : std::false_type { }; template<> struct is_true : std::true_type { }; ================================================ FILE: src/common/vulkan_hpp.h ================================================ #pragma once #include #ifdef VK_USE_PLATFORM_XLIB_KHR #include #include #endif #include #if defined(VK_USE_PLATFORM_XLIB_KHR) // Are you fucking serious X headers??? #undef None #undef Status #undef True #undef False #undef Bool #undef RootWindow #undef CurrentTime #undef Success #undef DestroyAll #undef COUNT #undef CREATE #undef DeviceAdded #undef DeviceRemoved #endif ================================================ FILE: src/common/xxhash.c ================================================ /* * xxHash - Fast Hash algorithm * Copyright (C) 2012-2016, Yann Collet * * BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You can contact the author at : * - xxHash homepage: http://www.xxhash.com * - xxHash source repository : https://github.com/Cyan4973/xxHash */ /* ************************************* * Tuning parameters ***************************************/ /*!XXH_FORCE_MEMORY_ACCESS : * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. * The below switch allow to select different access method for improved performance. * Method 0 (default) : use `memcpy()`. Safe and portable. * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. * It can generate buggy code on targets which do not support unaligned memory accesses. * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) * See http://stackoverflow.com/a/32095106/646947 for details. * Prefer these methods in priority order (0 > 1 > 2) */ #ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ # if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) # define XXH_FORCE_MEMORY_ACCESS 2 # elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ || defined(__ARM_ARCH_7S__) )) # define XXH_FORCE_MEMORY_ACCESS 1 # endif #endif /*!XXH_ACCEPT_NULL_INPUT_POINTER : * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault. * When this macro is enabled, xxHash actively checks input for null pointer. * It it is, result for null input pointers is the same as a null-length input. */ #ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */ # define XXH_ACCEPT_NULL_INPUT_POINTER 0 #endif /*!XXH_FORCE_NATIVE_FORMAT : * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. * Results are therefore identical for little-endian and big-endian CPU. * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. * Should endian-independence be of no importance for your application, you may set the #define below to 1, * to improve speed for Big-endian CPU. * This option has no impact on Little_Endian CPU. */ #ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ # define XXH_FORCE_NATIVE_FORMAT 0 #endif /*!XXH_FORCE_ALIGN_CHECK : * This is a minor performance trick, only useful with lots of very small keys. * It means : check for aligned/unaligned input. * The check costs one initial branch per hash; * set it to 0 when the input is guaranteed to be aligned, * or when alignment doesn't matter for performance. */ #ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ # if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) # define XXH_FORCE_ALIGN_CHECK 0 # else # define XXH_FORCE_ALIGN_CHECK 1 # endif #endif /* ************************************* * Includes & Memory related functions ***************************************/ /*! Modify the local functions below should you wish to use some other memory routines * for malloc(), free() */ #include static void* XXH_malloc(size_t s) { return malloc(s); } static void XXH_free(void* p) { free(p); } /*! and for memcpy() */ #include static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest, src, size); } #include /* assert */ #define XXH_STATIC_LINKING_ONLY #include "xxhash.h" /* ************************************* * Compiler Specific Options ***************************************/ #ifdef _MSC_VER /* Visual Studio */ # pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ # define FORCE_INLINE static __forceinline #else # if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ # ifdef __GNUC__ # define FORCE_INLINE static inline __attribute__((always_inline)) # else # define FORCE_INLINE static inline # endif # else # define FORCE_INLINE static # endif /* __STDC_VERSION__ */ #endif /* ************************************* * Basic Types ***************************************/ #ifndef MEM_MODULE # if !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # include typedef uint8_t BYTE; typedef uint16_t U16; typedef uint32_t U32; # else typedef unsigned char BYTE; typedef unsigned short U16; typedef unsigned int U32; # endif #endif #if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) /* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ static U32 XXH_read32(const void* memPtr) { return *(const U32*)memPtr; } #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) /* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ /* currently only defined for gcc and icc */ typedef union { U32 u32; } __attribute__((packed)) unalign; static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } #else /* portable and safe solution. Generally efficient. * see : http://stackoverflow.com/a/32095106/646947 */ static U32 XXH_read32(const void* memPtr) { U32 val; memcpy(&val, memPtr, sizeof(val)); return val; } #endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ /* **************************************** * Compiler-specific Functions and Macros ******************************************/ #define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) /* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ #if defined(_MSC_VER) # define XXH_rotl32(x,r) _rotl(x,r) # define XXH_rotl64(x,r) _rotl64(x,r) #else # define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) # define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) #endif #if defined(_MSC_VER) /* Visual Studio */ # define XXH_swap32 _byteswap_ulong #elif XXH_GCC_VERSION >= 403 # define XXH_swap32 __builtin_bswap32 #else static U32 XXH_swap32(U32 x) { return ((x << 24) & 0xff000000) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | ((x >> 24) & 0x000000ff); } #endif /* ************************************* * Architecture Macros ***************************************/ typedef enum { XXH_bigEndian = 0, XXH_littleEndian = 1 } XXH_endianess; /* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ #ifndef XXH_CPU_LITTLE_ENDIAN static int XXH_isLittleEndian(void) { const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ return one.c[0]; } # define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() #endif /* *************************** * Memory reads *****************************/ typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) { if (align == XXH_unaligned) return endian == XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); else return endian == XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); } FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) { return XXH_readLE32_align(ptr, endian, XXH_unaligned); } static U32 XXH_readBE32(const void* ptr) { return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); } /* ************************************* * Macros ***************************************/ #define XXH_STATIC_ASSERT(c) { enum { XXH_sa = 1/(int)(!!(c)) }; } /* use after variable declarations */ XXH_PUBLIC_API unsigned XXH_versionNumber(void) { return XXH_VERSION_NUMBER; } /* ******************************************************************* * 32-bit hash functions *********************************************************************/ static const U32 PRIME32_1 = 2654435761U; /* 0b10011110001101110111100110110001 */ static const U32 PRIME32_2 = 2246822519U; /* 0b10000101111010111100101001110111 */ static const U32 PRIME32_3 = 3266489917U; /* 0b11000010101100101010111000111101 */ static const U32 PRIME32_4 = 668265263U; /* 0b00100111110101001110101100101111 */ static const U32 PRIME32_5 = 374761393U; /* 0b00010110010101100110011110110001 */ static U32 XXH32_round(U32 seed, U32 input) { seed += input * PRIME32_2; seed = XXH_rotl32(seed, 13); seed *= PRIME32_1; return seed; } /* mix all bits */ static U32 XXH32_avalanche(U32 h32) { h32 ^= h32 >> 15; h32 *= PRIME32_2; h32 ^= h32 >> 13; h32 *= PRIME32_3; h32 ^= h32 >> 16; return(h32); } #define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) static U32 XXH32_finalize(U32 h32, const void* ptr, size_t len, XXH_endianess endian, XXH_alignment align) { const BYTE* p = (const BYTE*)ptr; #define PROCESS1 \ h32 += (*p++) * PRIME32_5; \ h32 = XXH_rotl32(h32, 11) * PRIME32_1 ; #define PROCESS4 \ h32 += XXH_get32bits(p) * PRIME32_3; \ p+=4; \ h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; switch (len & 15) /* or switch(bEnd - p) */ { case 12: PROCESS4; /* fallthrough */ case 8: PROCESS4; /* fallthrough */ case 4: PROCESS4; return XXH32_avalanche(h32); case 13: PROCESS4; /* fallthrough */ case 9: PROCESS4; /* fallthrough */ case 5: PROCESS4; PROCESS1; return XXH32_avalanche(h32); case 14: PROCESS4; /* fallthrough */ case 10: PROCESS4; /* fallthrough */ case 6: PROCESS4; PROCESS1; PROCESS1; return XXH32_avalanche(h32); case 15: PROCESS4; /* fallthrough */ case 11: PROCESS4; /* fallthrough */ case 7: PROCESS4; /* fallthrough */ case 3: PROCESS1; /* fallthrough */ case 2: PROCESS1; /* fallthrough */ case 1: PROCESS1; /* fallthrough */ case 0: return XXH32_avalanche(h32); } assert(0); return h32; /* reaching this point is deemed impossible */ } FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, XXH_endianess endian, XXH_alignment align) { const BYTE* p = (const BYTE*)input; const BYTE* bEnd = p + len; U32 h32; #if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) if (p == NULL) { len = 0; bEnd = p = (const BYTE*)(size_t)16; } #endif if (len >= 16) { const BYTE* const limit = bEnd - 15; U32 v1 = seed + PRIME32_1 + PRIME32_2; U32 v2 = seed + PRIME32_2; U32 v3 = seed + 0; U32 v4 = seed - PRIME32_1; do { v1 = XXH32_round(v1, XXH_get32bits(p)); p += 4; v2 = XXH32_round(v2, XXH_get32bits(p)); p += 4; v3 = XXH32_round(v3, XXH_get32bits(p)); p += 4; v4 = XXH32_round(v4, XXH_get32bits(p)); p += 4; } while (p < limit); h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); } else { h32 = seed + PRIME32_5; } h32 += (U32)len; return XXH32_finalize(h32, p, len & 15, endian, align); } XXH_PUBLIC_API unsigned int XXH32(const void* input, size_t len, unsigned int seed) { #if 0 /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ XXH32_state_t state; XXH32_reset(&state, seed); XXH32_update(&state, input, len); return XXH32_digest(&state); #else XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; if (XXH_FORCE_ALIGN_CHECK) { if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); else return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); } } if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); else return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); #endif } /*====== Hash streaming ======*/ XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) { return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); } XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) { XXH_free(statePtr); return XXH_OK; } XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) { memcpy(dstState, srcState, sizeof(*dstState)); } XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) { XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ memset(&state, 0, sizeof(state)); state.v1 = seed + PRIME32_1 + PRIME32_2; state.v2 = seed + PRIME32_2; state.v3 = seed + 0; state.v4 = seed - PRIME32_1; /* do not write into reserved, planned to be removed in a future version */ memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); return XXH_OK; } FORCE_INLINE XXH_errorcode XXH32_update_endian(XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) { if (input == NULL) #if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) return XXH_OK; #else return XXH_ERROR; #endif { const BYTE* p = (const BYTE*)input; const BYTE* const bEnd = p + len; state->total_len_32 += (unsigned)len; state->large_len |= (len >= 16) | (state->total_len_32 >= 16); if (state->memsize + len < 16) { /* fill in tmp buffer */ XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); state->memsize += (unsigned)len; return XXH_OK; } if (state->memsize) { /* some data left from previous update */ XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16 - state->memsize); { const U32* p32 = state->mem32; state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); } p += 16 - state->memsize; state->memsize = 0; } if (p <= bEnd - 16) { const BYTE* const limit = bEnd - 16; U32 v1 = state->v1; U32 v2 = state->v2; U32 v3 = state->v3; U32 v4 = state->v4; do { v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p += 4; v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p += 4; v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p += 4; v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p += 4; } while (p <= limit); state->v1 = v1; state->v2 = v2; state->v3 = v3; state->v4 = v4; } if (p < bEnd) { XXH_memcpy(state->mem32, p, (size_t)(bEnd - p)); state->memsize = (unsigned)(bEnd - p); } } return XXH_OK; } XXH_PUBLIC_API XXH_errorcode XXH32_update(XXH32_state_t* state_in, const void* input, size_t len) { XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH32_update_endian(state_in, input, len, XXH_littleEndian); else return XXH32_update_endian(state_in, input, len, XXH_bigEndian); } FORCE_INLINE U32 XXH32_digest_endian(const XXH32_state_t* state, XXH_endianess endian) { U32 h32; if (state->large_len) { h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18); } else { h32 = state->v3 /* == seed */ + PRIME32_5; } h32 += state->total_len_32; return XXH32_finalize(h32, state->mem32, state->memsize, endian, XXH_aligned); } XXH_PUBLIC_API unsigned int XXH32_digest(const XXH32_state_t* state_in) { XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH32_digest_endian(state_in, XXH_littleEndian); else return XXH32_digest_endian(state_in, XXH_bigEndian); } /*====== Canonical representation ======*/ /*! Default XXH result types are basic unsigned 32 and 64 bits. * The canonical representation follows human-readable write convention, aka big-endian (large digits first). * These functions allow transformation of hash result into and from its canonical format. * This way, hash values can be written into a file or buffer, remaining comparable across different systems. */ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); memcpy(dst, &hash, sizeof(*dst)); } XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) { return XXH_readBE32(src); } #ifndef XXH_NO_LONG_LONG /* ******************************************************************* * 64-bit hash functions *********************************************************************/ /*====== Memory access ======*/ #ifndef MEM_MODULE # define MEM_MODULE # if !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # include typedef uint64_t U64; # else /* if compiler doesn't support unsigned long long, replace by another 64-bit type */ typedef unsigned long long U64; # endif #endif #if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) /* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ static U64 XXH_read64(const void* memPtr) { return *(const U64*)memPtr; } #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) /* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ /* currently only defined for gcc and icc */ typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64; static U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; } #else /* portable and safe solution. Generally efficient. * see : http://stackoverflow.com/a/32095106/646947 */ static U64 XXH_read64(const void* memPtr) { U64 val; memcpy(&val, memPtr, sizeof(val)); return val; } #endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ #if defined(_MSC_VER) /* Visual Studio */ # define XXH_swap64 _byteswap_uint64 #elif XXH_GCC_VERSION >= 403 # define XXH_swap64 __builtin_bswap64 #else static U64 XXH_swap64(U64 x) { return ((x << 56) & 0xff00000000000000ULL) | ((x << 40) & 0x00ff000000000000ULL) | ((x << 24) & 0x0000ff0000000000ULL) | ((x << 8) & 0x000000ff00000000ULL) | ((x >> 8) & 0x00000000ff000000ULL) | ((x >> 24) & 0x0000000000ff0000ULL) | ((x >> 40) & 0x000000000000ff00ULL) | ((x >> 56) & 0x00000000000000ffULL); } #endif FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) { if (align == XXH_unaligned) return endian == XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); else return endian == XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); } FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) { return XXH_readLE64_align(ptr, endian, XXH_unaligned); } static U64 XXH_readBE64(const void* ptr) { return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); } /*====== xxh64 ======*/ static const U64 PRIME64_1 = 11400714785074694791ULL; /* 0b1001111000110111011110011011000110000101111010111100101010000111 */ static const U64 PRIME64_2 = 14029467366897019727ULL; /* 0b1100001010110010101011100011110100100111110101001110101101001111 */ static const U64 PRIME64_3 = 1609587929392839161ULL; /* 0b0001011001010110011001111011000110011110001101110111100111111001 */ static const U64 PRIME64_4 = 9650029242287828579ULL; /* 0b1000010111101011110010100111011111000010101100101010111001100011 */ static const U64 PRIME64_5 = 2870177450012600261ULL; /* 0b0010011111010100111010110010111100010110010101100110011111000101 */ static U64 XXH64_round(U64 acc, U64 input) { acc += input * PRIME64_2; acc = XXH_rotl64(acc, 31); acc *= PRIME64_1; return acc; } static U64 XXH64_mergeRound(U64 acc, U64 val) { val = XXH64_round(0, val); acc ^= val; acc = acc * PRIME64_1 + PRIME64_4; return acc; } static U64 XXH64_avalanche(U64 h64) { h64 ^= h64 >> 33; h64 *= PRIME64_2; h64 ^= h64 >> 29; h64 *= PRIME64_3; h64 ^= h64 >> 32; return h64; } #define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) static U64 XXH64_finalize(U64 h64, const void* ptr, size_t len, XXH_endianess endian, XXH_alignment align) { const BYTE* p = (const BYTE*)ptr; #define PROCESS1_64 \ h64 ^= (*p++) * PRIME64_5; \ h64 = XXH_rotl64(h64, 11) * PRIME64_1; #define PROCESS4_64 \ h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; \ p+=4; \ h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; #define PROCESS8_64 { \ U64 const k1 = XXH64_round(0, XXH_get64bits(p)); \ p+=8; \ h64 ^= k1; \ h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \ } switch (len & 31) { case 24: PROCESS8_64; /* fallthrough */ case 16: PROCESS8_64; /* fallthrough */ case 8: PROCESS8_64; return XXH64_avalanche(h64); case 28: PROCESS8_64; /* fallthrough */ case 20: PROCESS8_64; /* fallthrough */ case 12: PROCESS8_64; /* fallthrough */ case 4: PROCESS4_64; return XXH64_avalanche(h64); case 25: PROCESS8_64; /* fallthrough */ case 17: PROCESS8_64; /* fallthrough */ case 9: PROCESS8_64; PROCESS1_64; return XXH64_avalanche(h64); case 29: PROCESS8_64; /* fallthrough */ case 21: PROCESS8_64; /* fallthrough */ case 13: PROCESS8_64; /* fallthrough */ case 5: PROCESS4_64; PROCESS1_64; return XXH64_avalanche(h64); case 26: PROCESS8_64; /* fallthrough */ case 18: PROCESS8_64; /* fallthrough */ case 10: PROCESS8_64; PROCESS1_64; PROCESS1_64; return XXH64_avalanche(h64); case 30: PROCESS8_64; /* fallthrough */ case 22: PROCESS8_64; /* fallthrough */ case 14: PROCESS8_64; /* fallthrough */ case 6: PROCESS4_64; PROCESS1_64; PROCESS1_64; return XXH64_avalanche(h64); case 27: PROCESS8_64; /* fallthrough */ case 19: PROCESS8_64; /* fallthrough */ case 11: PROCESS8_64; PROCESS1_64; PROCESS1_64; PROCESS1_64; return XXH64_avalanche(h64); case 31: PROCESS8_64; /* fallthrough */ case 23: PROCESS8_64; /* fallthrough */ case 15: PROCESS8_64; /* fallthrough */ case 7: PROCESS4_64; /* fallthrough */ case 3: PROCESS1_64; /* fallthrough */ case 2: PROCESS1_64; /* fallthrough */ case 1: PROCESS1_64; /* fallthrough */ case 0: return XXH64_avalanche(h64); } /* impossible to reach */ assert(0); return 0; /* unreachable, but some compilers complain without it */ } FORCE_INLINE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, XXH_endianess endian, XXH_alignment align) { const BYTE* p = (const BYTE*)input; const BYTE* bEnd = p + len; U64 h64; #if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) if (p == NULL) { len = 0; bEnd = p = (const BYTE*)(size_t)32; } #endif if (len >= 32) { const BYTE* const limit = bEnd - 32; U64 v1 = seed + PRIME64_1 + PRIME64_2; U64 v2 = seed + PRIME64_2; U64 v3 = seed + 0; U64 v4 = seed - PRIME64_1; do { v1 = XXH64_round(v1, XXH_get64bits(p)); p += 8; v2 = XXH64_round(v2, XXH_get64bits(p)); p += 8; v3 = XXH64_round(v3, XXH_get64bits(p)); p += 8; v4 = XXH64_round(v4, XXH_get64bits(p)); p += 8; } while (p <= limit); h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); h64 = XXH64_mergeRound(h64, v1); h64 = XXH64_mergeRound(h64, v2); h64 = XXH64_mergeRound(h64, v3); h64 = XXH64_mergeRound(h64, v4); } else { h64 = seed + PRIME64_5; } h64 += (U64)len; return XXH64_finalize(h64, p, len, endian, align); } XXH_PUBLIC_API unsigned long long XXH64(const void* input, size_t len, unsigned long long seed) { #if 0 /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ XXH64_state_t state; XXH64_reset(&state, seed); XXH64_update(&state, input, len); return XXH64_digest(&state); #else XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; if (XXH_FORCE_ALIGN_CHECK) { if ((((size_t)input) & 7) == 0) { /* Input is aligned, let's leverage the speed advantage */ if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); else return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); } } if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); else return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); #endif } /*====== Hash Streaming ======*/ XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) { return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); } XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) { XXH_free(statePtr); return XXH_OK; } XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState) { memcpy(dstState, srcState, sizeof(*dstState)); } XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) { XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ memset(&state, 0, sizeof(state)); state.v1 = seed + PRIME64_1 + PRIME64_2; state.v2 = seed + PRIME64_2; state.v3 = seed + 0; state.v4 = seed - PRIME64_1; /* do not write into reserved, planned to be removed in a future version */ memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); return XXH_OK; } FORCE_INLINE XXH_errorcode XXH64_update_endian(XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) { if (input == NULL) #if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) return XXH_OK; #else return XXH_ERROR; #endif { const BYTE* p = (const BYTE*)input; const BYTE* const bEnd = p + len; state->total_len += len; if (state->memsize + len < 32) { /* fill in tmp buffer */ XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); state->memsize += (U32)len; return XXH_OK; } if (state->memsize) { /* tmp buffer is full */ XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32 - state->memsize); state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64 + 0, endian)); state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64 + 1, endian)); state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64 + 2, endian)); state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64 + 3, endian)); p += 32 - state->memsize; state->memsize = 0; } if (p + 32 <= bEnd) { const BYTE* const limit = bEnd - 32; U64 v1 = state->v1; U64 v2 = state->v2; U64 v3 = state->v3; U64 v4 = state->v4; do { v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p += 8; v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p += 8; v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p += 8; v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p += 8; } while (p <= limit); state->v1 = v1; state->v2 = v2; state->v3 = v3; state->v4 = v4; } if (p < bEnd) { XXH_memcpy(state->mem64, p, (size_t)(bEnd - p)); state->memsize = (unsigned)(bEnd - p); } } return XXH_OK; } XXH_PUBLIC_API XXH_errorcode XXH64_update(XXH64_state_t* state_in, const void* input, size_t len) { XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH64_update_endian(state_in, input, len, XXH_littleEndian); else return XXH64_update_endian(state_in, input, len, XXH_bigEndian); } FORCE_INLINE U64 XXH64_digest_endian(const XXH64_state_t* state, XXH_endianess endian) { U64 h64; if (state->total_len >= 32) { U64 const v1 = state->v1; U64 const v2 = state->v2; U64 const v3 = state->v3; U64 const v4 = state->v4; h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); h64 = XXH64_mergeRound(h64, v1); h64 = XXH64_mergeRound(h64, v2); h64 = XXH64_mergeRound(h64, v3); h64 = XXH64_mergeRound(h64, v4); } else { h64 = state->v3 /*seed*/ + PRIME64_5; } h64 += (U64)state->total_len; return XXH64_finalize(h64, state->mem64, (size_t)state->total_len, endian, XXH_aligned); } XXH_PUBLIC_API unsigned long long XXH64_digest(const XXH64_state_t* state_in) { XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) return XXH64_digest_endian(state_in, XXH_littleEndian); else return XXH64_digest_endian(state_in, XXH_bigEndian); } /*====== Canonical representation ======*/ XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); memcpy(dst, &hash, sizeof(*dst)); } XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) { return XXH_readBE64(src); } #endif /* XXH_NO_LONG_LONG */ ================================================ FILE: src/common/xxhash.h ================================================ /* xxHash - Extremely Fast Hash algorithm Header File Copyright (C) 2012-2016, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. You can contact the author at : - xxHash source repository : https://github.com/Cyan4973/xxHash */ /* Notice extracted from xxHash homepage : xxHash is an extremely fast Hash algorithm, running at RAM speed limits. It also successfully passes all tests from the SMHasher suite. Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) Name Speed Q.Score Author xxHash 5.4 GB/s 10 CrapWow 3.2 GB/s 2 Andrew MumurHash 3a 2.7 GB/s 10 Austin Appleby SpookyHash 2.0 GB/s 10 Bob Jenkins SBox 1.4 GB/s 9 Bret Mulvey Lookup3 1.2 GB/s 9 Bob Jenkins SuperFastHash 1.2 GB/s 1 Paul Hsieh CityHash64 1.05 GB/s 10 Pike & Alakuijala FNV 0.55 GB/s 5 Fowler, Noll, Vo CRC32 0.43 GB/s 9 MD5-32 0.33 GB/s 10 Ronald L. Rivest SHA1-32 0.28 GB/s 10 Q.Score is a measure of quality of the hash function. It depends on successfully passing SMHasher test set. 10 is a perfect score. A 64-bit version, named XXH64, is available since r35. It offers much better speed, but for 64-bit applications only. Name Speed on 64 bits Speed on 32 bits XXH64 13.8 GB/s 1.9 GB/s XXH32 6.8 GB/s 6.0 GB/s */ #ifndef XXHASH_H_5627135585666179 #define XXHASH_H_5627135585666179 1 #if defined (__cplusplus) extern "C" { #endif /* **************************** * Definitions ******************************/ #include /* size_t */ typedef enum { XXH_OK = 0, XXH_ERROR } XXH_errorcode; /* **************************** * API modifier ******************************/ /** XXH_INLINE_ALL (and XXH_PRIVATE_API) * This is useful to include xxhash functions in `static` mode * in order to inline them, and remove their symbol from the public list. * Inlining can offer dramatic performance improvement on small keys. * Methodology : * #define XXH_INLINE_ALL * #include "xxhash.h" * `xxhash.c` is automatically included. * It's not useful to compile and link it as a separate module. */ #if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) # ifndef XXH_STATIC_LINKING_ONLY # define XXH_STATIC_LINKING_ONLY # endif # if defined(__GNUC__) # define XXH_PUBLIC_API static __inline __attribute__((unused)) # elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) # define XXH_PUBLIC_API static inline # elif defined(_MSC_VER) # define XXH_PUBLIC_API static __inline # else /* this version may generate warnings for unused static functions */ # define XXH_PUBLIC_API static # endif #else # define XXH_PUBLIC_API /* do nothing */ #endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ /*! XXH_NAMESPACE, aka Namespace Emulation : * * If you want to include _and expose_ xxHash functions from within your own library, * but also want to avoid symbol collisions with other libraries which may also include xxHash, * * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). * * Note that no change is required within the calling program as long as it includes `xxhash.h` : * regular symbol name will be automatically translated by this header. */ #ifdef XXH_NAMESPACE # define XXH_CAT(A,B) A##B # define XXH_NAME2(A,B) XXH_CAT(A,B) # define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) # define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) # define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) # define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) # define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) # define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) # define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) # define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) # define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash) # define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical) # define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) # define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) # define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) # define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) # define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) # define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) # define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) # define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) # define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical) #endif /* ************************************* * Version ***************************************/ #define XXH_VERSION_MAJOR 0 #define XXH_VERSION_MINOR 6 #define XXH_VERSION_RELEASE 5 #define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) XXH_PUBLIC_API unsigned XXH_versionNumber(void); /*-********************************************************************** * 32-bit hash ************************************************************************/ typedef unsigned int XXH32_hash_t; /*! XXH32() : Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input". The memory between input & input+length must be valid (allocated and read-accessible). "seed" can be used to alter the result predictably. Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */ XXH_PUBLIC_API XXH32_hash_t XXH32(const void* input, size_t length, unsigned int seed); /*====== Streaming ======*/ typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed); XXH_PUBLIC_API XXH_errorcode XXH32_update(XXH32_state_t* statePtr, const void* input, size_t length); XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* statePtr); /* * Streaming functions generate the xxHash of an input provided in multiple segments. * Note that, for small input, they are slower than single-call functions, due to state management. * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. * * XXH state must first be allocated, using XXH*_createState() . * * Start a new hash by initializing state with a seed, using XXH*_reset(). * * Then, feed the hash state by calling XXH*_update() as many times as necessary. * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. * * Finally, a hash value can be produced anytime, by using XXH*_digest(). * This function returns the nn-bits hash as an int or long long. * * It's still possible to continue inserting input into the hash state after a digest, * and generate some new hashes later on, by calling again XXH*_digest(). * * When done, free XXH state space if it was allocated dynamically. */ /*====== Canonical representation ======*/ typedef struct { unsigned char digest[4]; } XXH32_canonical_t; XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); /* Default result type for XXH functions are primitive unsigned 32 and 64 bits. * The canonical representation uses human-readable write convention, aka big-endian (large digits first). * These functions allow transformation of hash result into and from its canonical format. * This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. */ #ifndef XXH_NO_LONG_LONG /*-********************************************************************** * 64-bit hash ************************************************************************/ typedef unsigned long long XXH64_hash_t; /*! XXH64() : Calculate the 64-bit hash of sequence of length "len" stored at memory address "input". "seed" can be used to alter the result predictably. This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark). */ XXH_PUBLIC_API XXH64_hash_t XXH64(const void* input, size_t length, unsigned long long seed); /*====== Streaming ======*/ typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed); XXH_PUBLIC_API XXH_errorcode XXH64_update(XXH64_state_t* statePtr, const void* input, size_t length); XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* statePtr); /*====== Canonical representation ======*/ typedef struct { unsigned char digest[8]; } XXH64_canonical_t; XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); #endif /* XXH_NO_LONG_LONG */ #ifdef XXH_STATIC_LINKING_ONLY /* ================================================================================================ This section contains declarations which are not guaranteed to remain stable. They may change in future versions, becoming incompatible with a different version of the library. These declarations should only be used with static linking. Never use them in association with dynamic linking ! =================================================================================================== */ /* These definitions are only present to allow * static allocation of XXH state, on stack or in a struct for example. * Never **ever** use members directly. */ #if !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # include struct XXH32_state_s { uint32_t total_len_32; uint32_t large_len; uint32_t v1; uint32_t v2; uint32_t v3; uint32_t v4; uint32_t mem32[4]; uint32_t memsize; uint32_t reserved; /* never read nor write, might be removed in a future version */ }; /* typedef'd to XXH32_state_t */ struct XXH64_state_s { uint64_t total_len; uint64_t v1; uint64_t v2; uint64_t v3; uint64_t v4; uint64_t mem64[4]; uint32_t memsize; uint32_t reserved[2]; /* never read nor write, might be removed in a future version */ }; /* typedef'd to XXH64_state_t */ # else struct XXH32_state_s { unsigned total_len_32; unsigned large_len; unsigned v1; unsigned v2; unsigned v3; unsigned v4; unsigned mem32[4]; unsigned memsize; unsigned reserved; /* never read nor write, might be removed in a future version */ }; /* typedef'd to XXH32_state_t */ # ifndef XXH_NO_LONG_LONG /* remove 64-bit support */ struct XXH64_state_s { unsigned long long total_len; unsigned long long v1; unsigned long long v2; unsigned long long v3; unsigned long long v4; unsigned long long mem64[4]; unsigned memsize; unsigned reserved[2]; /* never read nor write, might be removed in a future version */ }; /* typedef'd to XXH64_state_t */ # endif # endif #if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) # include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ #endif #endif /* XXH_STATIC_LINKING_ONLY */ #if defined (__cplusplus) } #endif #endif /* XXHASH_H_5627135585666179 */ ================================================ FILE: src/decaf-cli/CMakeLists.txt ================================================ project(decaf-cli) include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(decaf-cli ${SOURCE_FILES} ${HEADER_FILES}) target_link_libraries(decaf-cli common libconfig libdecaf excmd) install(TARGETS decaf-cli RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") if(COMMAND x_vcpkg_install_local_dependencies) x_vcpkg_install_local_dependencies(TARGETS decaf-cli DESTINATION "${DECAF_INSTALL_BINDIR}") endif() ================================================ FILE: src/decaf-cli/config.cpp ================================================ #include "config.h" namespace config { namespace system { int timeout_ms = 0; } // namespace system bool loadFrontendToml(const toml::table &config) { readValue(config, "system.timeout_ms", system::timeout_ms); return true; } bool saveFrontendToml(toml::table &config) { auto system = config.insert("system", toml::table()).first->second.as_table(); system->insert_or_assign("timeout_ms", system::timeout_ms); return true; } } // namespace config ================================================ FILE: src/decaf-cli/config.h ================================================ #pragma once #include #include #include namespace config { namespace system { extern int timeout_ms; } // namespace system bool loadFrontendToml(const toml::table &config); bool saveFrontendToml(toml::table &config); } // namespace config ================================================ FILE: src/decaf-cli/decafcli.cpp ================================================ #include "decafcli.h" #include "config.h" #include #include #include #include #include #include int DecafCLI::run(const std::string &gamePath) { int result = 0; // Setup drivers decaf::setGraphicsDriver(gpu::createGraphicsDriver(gpu::GraphicsDriverType::Null)); decaf::setInputDriver(new decaf::NullInputDriver { }); // Initialise emulator if (!decaf::initialise(gamePath)) { return -1; } // Start graphics thread auto graphicsThread = std::thread { [this]() { decaf::getGraphicsDriver()->run(); } }; // Setup timeout stuff std::atomic_bool running { true }; std::atomic_bool timedOut { false }; std::thread timeoutThread; std::condition_variable timeoutCV; if (config::system::timeout_ms) { auto timeout = std::chrono::system_clock::now() + std::chrono::milliseconds(config::system::timeout_ms); timeoutThread = std::thread { [&]() { std::mutex timeoutMutex; std::unique_lock lock { timeoutMutex }; timeoutCV.wait_until(lock, timeout, [&]() { return !running.load(); }); if (running) { timedOut.store(true); decaf::shutdown(); } } }; } // Start emulator decaf::start(); // Wait until program completes result = decaf::waitForExit(); // If we didn't timeout, wakeup timeout thread if (!timedOut.load()) { running.store(false); timeoutCV.notify_all(); } else { gCliLog->error("Application exceeded maxmimum execution time of {}ms.", config::system::timeout_ms); result = -1; } // Wait for timeout thread to exit if (timeoutThread.joinable()) { timeoutThread.join(); } // Wait for the GPU thread to exit if (graphicsThread.joinable()) { graphicsThread.join(); } return result; } ================================================ FILE: src/decaf-cli/decafcli.h ================================================ #pragma once #include #include #include #include using namespace decaf::input; extern std::shared_ptr gCliLog; class DecafCLI { public: int run(const std::string &gamePath); private: }; ================================================ FILE: src/decaf-cli/main.cpp ================================================ #include "config.h" #include "decafcli.h" #include #include #include #include #include #include #include #include #include #include #include std::shared_ptr gCliLog; using namespace decaf::input; static excmd::parser getCommandLineParser() { excmd::parser parser; using excmd::description; using excmd::optional; using excmd::default_value; using excmd::allowed; using excmd::value; parser.global_options() .add_option("v,version", description { "Show version." }) .add_option("h,help", description { "Show help." }); parser.add_command("help") .add_argument("help-command", optional {}, value {}); auto cli_options = parser.add_option_group("Cli Options") .add_option("config", description { "Specify path to configuration file." }, value {}) .add_option("timeout_ms", description { "How long to execute the game for before quitting." }, value {}); auto config_options = config::getExcmdGroups(parser); auto cmdPlay = parser.add_command("play") .add_option_group(cli_options) .add_argument("game directory", value {}); for (auto group : config_options) { cmdPlay.add_option_group(group); } return parser; } static std::string getPathBasename(const std::string &path) { auto pos = path.find_last_of("/\\"); if (!pos) { return path; } else { return path.substr(pos + 1); } } int start(excmd::parser &parser, excmd::option_state &options) { // Print version if (options.has("version")) { // TODO: print git hash std::cout << "Decaf Emulator version 0.0.1" << std::endl; std::exit(0); } // Print help if (options.empty() || options.has("help")) { if (options.has("help-command")) { std::cout << parser.format_help("decaf", options.get("help-command")) << std::endl; } else { std::cout << parser.format_help("decaf") << std::endl; } std::exit(0); } if (!options.has("play")) { return 0; } auto gamePath = options.get("game directory"); // Load config file std::string configPath, configError; if (options.has("config")) { configPath = options.get("config"); } else { decaf::createConfigDirectory(); configPath = decaf::makeConfigPath("cli-config.toml"); } auto cpuSettings = cpu::Settings { }; auto gpuSettings = gpu::Settings { }; auto decafSettings = decaf::Settings { }; // If config file does not exist, create a default one. if (!platform::fileExists(configPath)) { auto toml = toml::table(); config::saveToTOML(toml, cpuSettings); config::saveToTOML(toml, gpuSettings); config::saveToTOML(toml, decafSettings); config::saveFrontendToml(toml); std::ofstream out { configPath }; out << toml; } try { auto toml = toml::parse_file(configPath); config::loadFromTOML(toml, cpuSettings); config::loadFromTOML(toml, gpuSettings); config::loadFromTOML(toml, decafSettings); config::loadFrontendToml(toml); } catch (const toml::parse_error &ex) { configError = ex.what(); } config::loadFromExcmd(options, cpuSettings); config::loadFromExcmd(options, gpuSettings); config::loadFromExcmd(options, decafSettings); cpu::setConfig(cpuSettings); gpu::setConfig(gpuSettings); decaf::setConfig(decafSettings); // Allow command line options to override config if (options.has("timeout_ms")) { config::system::timeout_ms = options.get("timeout_ms"); } // Initialise libdecaf logger auto logFile = getPathBasename(gamePath); decaf::initialiseLogging(logFile); // Initialise frontend logger if (!decafSettings.log.to_stdout) { // Always do cli log to stdout gCliLog = decaf::makeLogger("decaf-cli", { std::make_shared() }); } else { gCliLog = decaf::makeLogger("decaf-cli"); } gCliLog->set_pattern("[%l] %v"); gCliLog->info("Game path {}", gamePath); if (configError.empty()) { gCliLog->info("Loaded config from {}", configPath); } else { gCliLog->error("Failed to parse config {}: {}", configPath, configError); } DecafCLI cli; return cli.run(gamePath); } int main(int argc, char **argv) { auto parser = getCommandLineParser(); excmd::option_state options; try { options = parser.parse(argc, argv); } catch (excmd::exception ex) { std::cout << "Error parsing options: " << ex.what() << std::endl; std::exit(-1); } return start(parser, options); } ================================================ FILE: src/decaf-qt/CMakeLists.txt ================================================ project(decaf-qt) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) include_directories("src") file(GLOB_RECURSE SOURCE_FILES src/*.cpp) file(GLOB_RECURSE HEADER_FILES src/*.h) file(GLOB_RECURSE UI_FILES ui/*.ui) file(GLOB_RECURSE QRC_FILES resources/*.qrc) if(MSVC) set(RESOURCE_FILES ${CMAKE_SOURCE_DIR}/resources/decaf-sdl.rc ${CMAKE_SOURCE_DIR}/resources/hidpi.manifest) else() set(RESOURCE_FILES "") endif() qt_wrap_ui(UIS_HDRS ${UI_FILES}) qt_add_resources(QT_RESOURCES ${QRC_FILES}) if(MSVC) source_group("Generated Files" FILES ${UIS_HDRS}) endif() add_executable(decaf-qt ${SOURCE_FILES} ${HEADER_FILES} ${UIS_HDRS} ${QT_RESOURCES} ${RESOURCE_FILES} ${QHEXVIEW_HEADERS} ${QHEXVIEW_SOURCES}) GroupSources("Source Files" src) GroupSources("UI Files" ui) GroupSources("Resource Files" resources) target_include_directories(decaf-qt PRIVATE ${SDL2_INCLUDE_DIRS}) target_link_libraries(decaf-qt common libconfig libcpu libdecaf ${EXCMD_LIBRARIES} ${SDL2_LIBRARIES} qtadvanceddocking Qt::Core Qt::Concurrent Qt::Svg Qt::Widgets Qt::Xml) if(Qt5Gui_PRIVATE_INCLUDE_DIRS) target_include_directories(decaf-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() if(Qt6Gui_PRIVATE_INCLUDE_DIRS) target_include_directories(decaf-qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() if(TARGET Qt::SvgWidgets) target_link_libraries(decaf-qt Qt::SvgWidgets) endif() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3") # Qt disapproves target_link_libraries(decaf-qt Setupapi) if(TARGET Qt::WinMain) target_link_libraries(decaf-qt Qt::WinMain) endif() set_target_properties(decaf-qt PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") # Find windeployqt binary in the same directory as qmake if(TARGET Qt::qmake AND NOT TARGET Qt::windeployqt) get_target_property(_qt_qmake_location Qt::qmake IMPORTED_LOCATION) execute_process( COMMAND "${_qt_qmake_location}" -query QT_INSTALL_PREFIX RESULT_VARIABLE return_code OUTPUT_VARIABLE qt_install_prefix OUTPUT_STRIP_TRAILING_WHITESPACE) set(imported_location "${qt_install_prefix}/bin/windeployqt.exe") if(EXISTS ${imported_location}) add_executable(Qt::windeployqt IMPORTED) set_target_properties(Qt::windeployqt PROPERTIES IMPORTED_LOCATION ${imported_location}) endif() endif() if(Qt5Gui_PRIVATE_INCLUDE_DIRS) # If Qt5 set(WINDEPLOYQT_OPTIONS --no-angle --no-opengl-sw --no-translations) else() # Else, assume Qt6 set(WINDEPLOYQT_OPTIONS --no-opengl-sw --no-translations) endif() # Windeployqt to the TARGET_FILE_DIR:decaf-qt add_custom_command(TARGET decaf-qt POST_BUILD COMMAND set PATH=%PATH%$${qt_install_prefix}/bin COMMAND Qt::windeployqt --dir "$" "$/$" ${WINDEPLOYQT_OPTIONS}) # Windeployqt to temporary directory which we can copy in install add_custom_command(TARGET decaf-qt POST_BUILD COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt" COMMAND set PATH=%PATH%$${qt_install_prefix}/bin COMMAND Qt::windeployqt --dir "${CMAKE_CURRENT_BINARY_DIR}/windeployqt" "$/$" ${WINDEPLOYQT_OPTIONS}) # copy deployment directory during installation install( DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/windeployqt/" DESTINATION ${DECAF_INSTALL_BINDIR}) endif() install(TARGETS decaf-qt RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") if(COMMAND x_vcpkg_install_local_dependencies) x_vcpkg_install_local_dependencies(TARGETS decaf-qt DESTINATION "${DECAF_INSTALL_BINDIR}") endif() ================================================ FILE: src/decaf-qt/resources/resources.qrc ================================================ ../../../resources/logo.svg ../../../resources/decaf.ico ../../../resources/decaf-debugger.ico icons/checkbox-blank-circle.svg icons/arrow-left.svg icons/arrow-right.svg icons/debug-step-into.svg icons/debug-step-out.svg icons/debug-step-over.svg icons/pause.svg icons/play.svg ================================================ FILE: src/decaf-qt/src/aboutdialog.h ================================================ #pragma once #include "ui_about.h" #include #include class AboutDialog : public QDialog { public: AboutDialog(QWidget *parent = nullptr) : QDialog(parent) { mUi.setupUi(this); mUi.iconWidget->load(QString(":/images/logo")); mUi.labelBuildInfo->setText( mUi.labelBuildInfo->text().arg(BUILD_FULLNAME, GIT_BRANCH, GIT_DESC, QString(BUILD_DATE).left(10))); } private: Ui::AboutDialog mUi; }; ================================================ FILE: src/decaf-qt/src/debugger/addresstextdocumentwidget.cpp ================================================ #include "addresstextdocumentwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include AddressTextDocumentWidget::AddressTextDocumentWidget(QWidget *parent) : QAbstractScrollArea(parent) { // Set to fixed width font auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont); if (!(font.styleHint() & QFont::Monospace)) { font.setFamily("Monospace"); font.setStyleHint(QFont::TypeWriter); } setFont(font); setMouseTracking(true); setCursor(Qt::IBeamCursor); mLineHeight = fontMetrics().height(); mCharacterWidth = fontMetrics().horizontalAdvance(' '); // Setup text document mTextDocument = new QTextDocument { this }; mTextDocument->setDefaultFont(font); mTextDocument->setUndoRedoEnabled(false); mTextDocument->setPlainText({}); mDocumentMargin = mTextDocument->documentMargin(); // Setup text formats mTextFormatSelection = QTextCharFormat { }; mTextFormatSelection.setBackground(Qt::darkGray); mTextFormatSelection.setForeground(Qt::white); mTextFormatHighlightedWord = QTextCharFormat { }; mTextFormatHighlightedWord.setBackground(Qt::yellow); // Setup cursor blink timer mBlinkTimer = new QTimer { this }; mBlinkTimer->setInterval(qApp->cursorFlashTime()); connect(mBlinkTimer, &QTimer::timeout, this, [this] { mBlinkCursorVisible = !mBlinkCursorVisible; viewport()->update(); }); } void AddressTextDocumentWidget::setBytesPerLine(int bytesPerLine) { mBytesPerLine = bytesPerLine; mNumLines = static_cast((static_cast(mEndAddress - mStartAddress) + 1) / mBytesPerLine); updateVerticalScrollBar(); viewport()->update(); } void AddressTextDocumentWidget::setAddressRange(VirtualAddress start, VirtualAddress end) { mStartAddress = start; mEndAddress = end; mNumLines = static_cast((static_cast(mEndAddress - mStartAddress) + 1) / mBytesPerLine); updateVerticalScrollBar(); viewport()->update(); } void AddressTextDocumentWidget::navigateToAddress(VirtualAddress address) { auto cursorAddress = cursorToAddress(getDocumentCursor()); mNavigationForwardStack = { }; if (mNavigationBackwardStack.empty() || mNavigationBackwardStack.back() != cursorAddress) { mNavigationBackwardStack.push_back(cursorAddress); } showAddress(address); } void AddressTextDocumentWidget::navigateBackward() { if (!mNavigationBackwardStack.empty()) { auto address = mNavigationBackwardStack.back(); auto cursorAddress = cursorToAddress(getDocumentCursor()); mNavigationBackwardStack.pop_back(); mNavigationForwardStack.push_back(cursorAddress); showAddress(address); } } void AddressTextDocumentWidget::navigateForward() { if (!mNavigationForwardStack.empty()) { auto address = mNavigationForwardStack.back(); auto cursorAddress = cursorToAddress(getDocumentCursor()); mNavigationForwardStack.pop_back(); mNavigationBackwardStack.push_back(cursorAddress); showAddress(address); } } void AddressTextDocumentWidget::copySelection() { auto selectionBegin = getDocumentSelectionBegin(); auto selectionEnd = getDocumentSelectionEnd(); auto selectionFirst = std::min(selectionBegin, selectionEnd); auto selectionLast = std::max(selectionBegin, selectionEnd); auto tempDocument = QTextDocument{ }; auto cursor = QTextCursor{ &tempDocument }; cursor.beginEditBlock(); updateTextDocument(cursor, selectionFirst.address, selectionLast.address, mBytesPerLine, false); cursor.endEditBlock(); cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::StartOfLine); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, selectionLast.cursorPosition); cursor.setPosition(selectionFirst.cursorPosition, QTextCursor::KeepAnchor); qApp->clipboard()->setText(cursor.selectedText()); } void AddressTextDocumentWidget::paintEvent(QPaintEvent *e) { auto palette = qApp->palette(); auto painter = QPainter { viewport() }; painter.setFont(font()); if (mNumLines == 0) { painter.fillRect(e->rect(), palette.brush(QPalette::Base)); return; } auto firstVisibleLine = verticalScrollBar()->value(); auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1; // Ensure text document is up to date updateTextDocument(false); // Get latest selections auto customSelections = getCustomSelections(mTextDocument); // Setup text document painting auto textDocumentRect = viewport()->rect(); auto textDocumentOffsetX = horizontalScrollBar()->value(); painter.translate(-textDocumentOffsetX, 0.0); textDocumentRect.moveTo({ textDocumentOffsetX, 0 }); auto paintContext = QAbstractTextDocumentLayout::PaintContext { }; paintContext.clip = textDocumentRect; paintContext.selections = customSelections + mHighlightedWordSelections; painter.setClipRect(textDocumentRect); // Process text selection auto selectionBegin = getDocumentSelectionBegin(); auto selectionEnd = getDocumentSelectionEnd(); if (selectionBegin != selectionEnd) { auto selectionFirst = std::min(selectionBegin, selectionEnd); auto selectionLast = std::max(selectionBegin, selectionEnd); auto selectionFirstLine = static_cast((selectionFirst.address - mStartAddress) / mBytesPerLine); auto selectionLastLine = static_cast((selectionLast.address - mStartAddress) / mBytesPerLine); if (selectionLastLine >= firstVisibleLine && lastVisibleLine >= selectionFirstLine) { QTextCursor selectionFirstCursor; QTextCursor selectionLastCursor; if (selectionFirstLine < firstVisibleLine) { selectionFirstCursor = QTextCursor { mTextDocument->firstBlock() }; selectionFirstCursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor); } else { selectionFirstCursor = QTextCursor { mTextDocument->findBlockByLineNumber(selectionFirstLine - firstVisibleLine) }; selectionFirstCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, selectionFirst.cursorPosition); } if (selectionLastLine > lastVisibleLine) { selectionLastCursor = QTextCursor { mTextDocument->lastBlock() }; selectionLastCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor); } else { selectionLastCursor = QTextCursor { mTextDocument->findBlockByLineNumber(selectionLastLine - firstVisibleLine) }; selectionLastCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, selectionLast.cursorPosition); } auto selection = std::move(selectionFirstCursor); selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, selectionLastCursor.position() - selection.position()); paintContext.selections.push_back({ selection, mTextFormatSelection }); } } mTextDocument->documentLayout()->draw(&painter, paintContext); // Draw cursor on top if there is no selection if (mBlinkCursorVisible) { auto cursor = getDocumentCursor(); auto cursorLine = static_cast((cursor.address - mStartAddress) / mBytesPerLine); if (cursorLine >= firstVisibleLine && cursorLine <= lastVisibleLine) { auto cursorX = mDocumentMargin + cursor.cursorPosition * mCharacterWidth; auto cursorY = mDocumentMargin + (cursorLine - firstVisibleLine) * mLineHeight; painter.setCompositionMode(QPainter::CompositionMode_Difference); painter.setPen(QPen{ Qt::GlobalColor::white, 2 }); painter.drawLine(cursorX, cursorY, cursorX, cursorY + mLineHeight); } } } void AddressTextDocumentWidget::resizeEvent(QResizeEvent *e) { updateHorizontalScrollBar(); updateVerticalScrollBar(); QAbstractScrollArea::resizeEvent(e); } void AddressTextDocumentWidget::focusInEvent(QFocusEvent *e) { QAbstractScrollArea::focusInEvent(e); mBlinkTimer->start(); mBlinkCursorVisible = true; viewport()->update(); } void AddressTextDocumentWidget::focusOutEvent(QFocusEvent *e) { QAbstractScrollArea::focusOutEvent(e); mBlinkTimer->stop(); mBlinkCursorVisible = false; viewport()->update(); } std::optional AddressTextDocumentWidget::mouseEventHitTest(QMouseEvent *e) { // Translate mouse position to document cursor auto documentLayout = mTextDocument->documentLayout(); auto documentOffset = QPoint { horizontalScrollBar()->value(), 0 }; auto cursorPos = documentLayout->hitTest(e->pos() + documentOffset, Qt::FuzzyHit); if (cursorPos < 0) { return { }; } auto cursor = QTextCursor { mTextDocument }; cursor.setPosition(cursorPos); // Translate mouse position to address auto firstVisibleLine = verticalScrollBar()->value(); auto viewportLine = static_cast(e->pos().y() - mDocumentMargin) / mLineHeight; if (viewportLine >= mNumLines - firstVisibleLine) { viewportLine = mNumLines - firstVisibleLine - 1; } else if (firstVisibleLine + viewportLine < 0) { viewportLine = -firstVisibleLine; } auto firstVisibleAddress = mStartAddress + static_cast(firstVisibleLine) * mBytesPerLine; auto address = firstVisibleAddress + static_cast(viewportLine * mBytesPerLine); return MouseHitTest { address, cursor }; } void AddressTextDocumentWidget::mousePressEvent(QMouseEvent *e) { auto handled = false; if (e->buttons() == Qt::LeftButton || e->buttons() == Qt::RightButton) { if (auto hit = mouseEventHitTest(e)) { auto cursor = DocumentCursor { }; cursor.address = hit->lineAddress; cursor.cursorPosition = hit->textCursor.positionInBlock(); if (e->buttons() == Qt::RightButton) { auto selectionBegin = getDocumentSelectionBegin(); auto selectionEnd = getDocumentSelectionEnd(); if (selectionBegin != selectionEnd) { auto selectionFirst = std::min(selectionBegin, selectionEnd); auto selectionLast = std::max(selectionBegin, selectionEnd); if (selectionFirst < cursor && cursor < selectionLast) { handled = true; } } } if (!handled) { // Update cursors setDocumentCursor(cursor); setDocumentSelectionBegin(cursor); setDocumentSelectionEnd(cursor); // Start cursor blink timer mBlinkTimer->start(); mBlinkCursorVisible = true; // Update the currently highlighted word auto character = mTextDocument->characterAt(hit->textCursor.position()); if (character.isSpace()) { mHighlightedWord.clear(); updateHighlightedWord(); } else { hit->textCursor.select(QTextCursor::WordUnderCursor); mHighlightedWord = hit->textCursor.selectedText(); updateHighlightedWord(); } viewport()->update(); handled = true; } } } if (handled) { e->accept(); } else { QAbstractScrollArea::mousePressEvent(e); } } void AddressTextDocumentWidget::mouseMoveEvent(QMouseEvent *e) { auto handled = false; if (e->buttons() & Qt::LeftButton) { if (auto hit = mouseEventHitTest(e)) { auto cursor = DocumentCursor { }; cursor.address = hit->lineAddress; cursor.cursorPosition = hit->textCursor.positionInBlock(); // Update cursors setDocumentCursor(cursor); setDocumentSelectionEnd(cursor); // Start cursor blink timer mBlinkTimer->start(); mBlinkCursorVisible = true; // Update the currently highlighted word auto character = mTextDocument->characterAt(hit->textCursor.position()); if (character.isSpace()) { mHighlightedWord.clear(); updateHighlightedWord(); } else { hit->textCursor.select(QTextCursor::WordUnderCursor); mHighlightedWord = hit->textCursor.selectedText(); updateHighlightedWord(); } // Ensure cursor is visible // TODO: Use a QTimer to rate limit this auto scrolling auto line = static_cast((cursor.address - mStartAddress) / mBytesPerLine); if (line > verticalScrollBar()->maximum()) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } else if (line < 0) { verticalScrollBar()->setValue(0); } else { auto firstLine = verticalScrollBar()->value(); auto lastLine = firstLine + verticalScrollBar()->pageStep(); if (line < firstLine) { verticalScrollBar()->setValue(line); } else if (line > lastLine) { verticalScrollBar()->setValue(line - verticalScrollBar()->pageStep() + 1); } } viewport()->update(); handled = true; } } if (handled) { e->accept(); } else { QAbstractScrollArea::mouseMoveEvent(e); } } void AddressTextDocumentWidget::mouseReleaseEvent(QMouseEvent *e) { auto handled = false; if (e->button() == Qt::BackButton) { navigateBackward(); handled = true; } else if (e->button() == Qt::ForwardButton) { navigateForward(); handled = true; } else if (e->button() == Qt::RightButton) { showContextMenu(e); handled = true; } if (handled) { e->accept(); } else { QAbstractScrollArea::mouseReleaseEvent(e); } } void AddressTextDocumentWidget::mouseDoubleClickEvent(QMouseEvent *e) { auto handled = false; if (e->buttons() & Qt::LeftButton) { if (auto hit = mouseEventHitTest(e)) { auto blockPosition = hit->textCursor.block().position(); hit->textCursor.select(QTextCursor::WordUnderCursor); setDocumentSelectionBegin({ hit->lineAddress, hit->textCursor.selectionStart() - blockPosition }); setDocumentSelectionEnd({ hit->lineAddress, hit->textCursor.selectionEnd() - blockPosition }); setDocumentCursor({ hit->lineAddress, hit->textCursor.selectionEnd() - blockPosition }); viewport()->update(); handled = true; } } if (handled) { e->accept(); } else { QAbstractScrollArea::mouseDoubleClickEvent(e); } } void AddressTextDocumentWidget::keyPressEvent(QKeyEvent *e) { auto cursor = getDocumentCursor(); auto cursorLine = static_cast((cursor.address - mStartAddress) / mBytesPerLine); auto firstVisibleLine = verticalScrollBar()->value(); auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1; if (cursorLine < firstVisibleLine || cursorLine > lastVisibleLine) { ensureCursorVisible(false); } auto moveToEndOfNewCursorLine = false; auto moveAnchor = true; auto moveAddressOffset = 0; auto textCursor = QTextCursor { mTextDocument }; auto textBlock = mTextDocument->findBlockByLineNumber(cursorLine - firstVisibleLine); auto endOfCurrentLine = textBlock.length(); textCursor.setPosition(textBlock.position() + cursor.cursorPosition); if (e->matches(QKeySequence::MoveToPreviousChar) || e->matches(QKeySequence::SelectPreviousChar)) { if (cursor.cursorPosition == 0) { moveAddressOffset = -mBytesPerLine; moveToEndOfNewCursorLine = true; } else { cursor.cursorPosition -= 1; } moveAnchor = e->matches(QKeySequence::MoveToPreviousChar); } else if (e->matches(QKeySequence::MoveToNextChar) || e->matches(QKeySequence::SelectNextChar)) { if (cursor.cursorPosition >= endOfCurrentLine) { moveAddressOffset = mBytesPerLine; cursor.cursorPosition = 0; } else { cursor.cursorPosition += 1; } moveAnchor = e->matches(QKeySequence::MoveToNextChar); } else if (e->matches(QKeySequence::MoveToStartOfLine) || e->matches(QKeySequence::SelectStartOfLine)) { cursor.cursorPosition = 0; moveAnchor = e->matches(QKeySequence::MoveToStartOfLine); } else if (e->matches(QKeySequence::MoveToEndOfLine) || e->matches(QKeySequence::SelectEndOfLine)) { cursor.cursorPosition = endOfCurrentLine; moveAnchor = e->matches(QKeySequence::MoveToEndOfLine); } else if (e->matches(QKeySequence::MoveToPreviousLine) || e->matches(QKeySequence::SelectPreviousLine)) { moveAddressOffset = -mBytesPerLine; moveAnchor = e->matches(QKeySequence::MoveToPreviousLine); } else if (e->matches(QKeySequence::MoveToNextLine) || e->matches(QKeySequence::SelectNextLine)) { moveAddressOffset = mBytesPerLine; moveAnchor = e->matches(QKeySequence::MoveToNextLine); } else if (e->matches(QKeySequence::MoveToPreviousPage) || e->matches(QKeySequence::SelectPreviousPage)) { moveAddressOffset = verticalScrollBar()->pageStep() * -mBytesPerLine; moveAnchor = e->matches(QKeySequence::MoveToPreviousPage); } else if (e->matches(QKeySequence::MoveToNextPage) || e->matches(QKeySequence::SelectNextPage)) { moveAddressOffset = verticalScrollBar()->pageStep() * mBytesPerLine; moveAnchor = e->matches(QKeySequence::MoveToNextPage); } else if (e->matches(QKeySequence::MoveToStartOfDocument) || e->matches(QKeySequence::SelectStartOfDocument)) { cursor.address = mStartAddress; cursor.cursorPosition = 0; moveAnchor = e->matches(QKeySequence::MoveToStartOfDocument); } else if (e->matches(QKeySequence::MoveToEndOfDocument) || e->matches(QKeySequence::SelectEndOfDocument)) { cursor.address = mEndAddress; moveToEndOfNewCursorLine = true; moveAnchor = e->matches(QKeySequence::MoveToEndOfDocument); } else if (e->matches(QKeySequence::MoveToPreviousWord) || e->matches(QKeySequence::SelectPreviousWord)) { if (!textCursor.movePosition(QTextCursor::PreviousWord)) { cursor.cursorPosition = 0; } else { cursor.cursorPosition = textCursor.positionInBlock(); } moveAnchor = e->matches(QKeySequence::MoveToPreviousWord); } else if (e->matches(QKeySequence::MoveToNextWord) || e->matches(QKeySequence::SelectNextWord)) { if (!textCursor.movePosition(QTextCursor::NextWord)) { cursor.cursorPosition = endOfCurrentLine; } else { cursor.cursorPosition = textCursor.positionInBlock(); } moveAnchor = e->matches(QKeySequence::MoveToNextWord); } else if (e->matches(QKeySequence::SelectAll)) { setDocumentSelectionBegin({ mStartAddress, 0 }); cursor.address = mEndAddress; moveToEndOfNewCursorLine = true; moveAnchor = false; } else if (e->matches(QKeySequence::Deselect)) { moveAnchor = true; } else if (e->matches(QKeySequence::Back)) { navigateBackward(); e->accept(); return; } else if (e->matches(QKeySequence::Forward)) { navigateForward(); e->accept(); return; } else if (e->matches(QKeySequence::Copy)) { copySelection(); e->accept(); return; } else { return QAbstractScrollArea::keyPressEvent(e); } if (moveAddressOffset) { if (moveAddressOffset < 0) { if (cursor.address - mStartAddress < static_cast(-moveAddressOffset)) { cursor.address = mStartAddress; cursor.cursorPosition = 0; } else { cursor.address += moveAddressOffset; } } else { if (mEndAddress - cursor.address < static_cast(moveAddressOffset)) { cursor.address = mEndAddress; moveToEndOfNewCursorLine = true; } else { cursor.address += moveAddressOffset; } } } if (moveAnchor) { setDocumentCursor(cursor); setDocumentSelectionBegin(cursor); setDocumentSelectionEnd(cursor); } else { setDocumentCursor(cursor); setDocumentSelectionEnd(cursor); } ensureCursorVisible(false); if (moveToEndOfNewCursorLine) { cursorLine = static_cast((cursor.address - mStartAddress) / mBytesPerLine); firstVisibleLine = verticalScrollBar()->value(); textBlock = mTextDocument->findBlockByLineNumber(cursorLine - firstVisibleLine); cursor.cursorPosition = textBlock.length(); setDocumentCursor(cursor); } mBlinkTimer->start(); mBlinkCursorVisible = true; viewport()->update(); e->accept(); } void AddressTextDocumentWidget::showAddress(VirtualAddress address) { setDocumentCursor(cursorFromAddress(address)); setDocumentSelectionBegin({}); setDocumentSelectionEnd({}); mHighlightedWord = QString { "%1" }.arg(address, 8, 16, QLatin1Char{ '0' }); if (!ensureCursorVisible(true)) { updateHighlightedWord(); viewport()->update(); } } bool AddressTextDocumentWidget::ensureCursorVisible(bool centerOnCursor) { auto cursor = getDocumentCursor(); auto address = cursor.address; auto targetLine = static_cast((address - mStartAddress) / mBytesPerLine); auto firstVisibleLine = verticalScrollBar()->value(); auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1; if (targetLine >= firstVisibleLine && targetLine <= lastVisibleLine) { // Already visible return false; } // Scroll so that the target line is 1/3rd of way down screen if (centerOnCursor) { targetLine -= verticalScrollBar()->pageStep() / 3; } else if (targetLine > lastVisibleLine) { targetLine -= verticalScrollBar()->pageStep() - 1; } if (targetLine < 0) { verticalScrollBar()->setValue(0); } else if (targetLine > verticalScrollBar()->maximum()) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } else { verticalScrollBar()->setValue(targetLine); } updateTextDocument(false); viewport()->update(); return true; } void AddressTextDocumentWidget::updateHorizontalScrollBar() { auto viewportWidth = viewport()->width(); if (mMaxDocumentWidth < viewportWidth) { horizontalScrollBar()->setMinimum(0); horizontalScrollBar()->setMaximum(0); horizontalScrollBar()->setValue(0); } else { horizontalScrollBar()->setMinimum(0); horizontalScrollBar()->setMaximum(mMaxDocumentWidth - viewportWidth); horizontalScrollBar()->setSingleStep(mCharacterWidth); horizontalScrollBar()->setPageStep(viewportWidth); } } void AddressTextDocumentWidget::updateVerticalScrollBar() { if (mNumLines == 0) { verticalScrollBar()->setMinimum(0); verticalScrollBar()->setMaximum(0); verticalScrollBar()->setValue(0); } else { auto margin = static_cast(mDocumentMargin * 2); auto pageLines = (viewport()->height() - margin) / mLineHeight; verticalScrollBar()->setMinimum(0); verticalScrollBar()->setMaximum(mNumLines - pageLines); verticalScrollBar()->setSingleStep(1); verticalScrollBar()->setPageStep(pageLines); } } void AddressTextDocumentWidget::updateHighlightedWord() { mHighlightedWordSelections.clear(); auto findCursor = QTextCursor { mTextDocument }; while (!findCursor.isNull() && !findCursor.atEnd()) { findCursor = mTextDocument->find(mHighlightedWord, findCursor, QTextDocument::FindWholeWords); if (!findCursor.isNull()) { auto selection = QAbstractTextDocumentLayout::Selection { }; selection.cursor = findCursor; selection.format = mTextFormatHighlightedWord; mHighlightedWordSelections.push_back(selection); } } } void AddressTextDocumentWidget::updateTextDocument(bool forceUpdate) { // Check if text document is representing current view auto firstVisibleLine = verticalScrollBar()->value(); auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1; auto firstVisibleLineAddress = static_cast(mStartAddress + (firstVisibleLine * mBytesPerLine)); auto lastVisibleLineAddress = static_cast(mStartAddress + (lastVisibleLine * mBytesPerLine)); if (!forceUpdate) { if (firstVisibleLineAddress == mTextDocumentFirstLineAddress && lastVisibleLineAddress == mTextDocumentLastLineAddress) { return; } } // Generate new text document mTextDocument->clear(); auto cursor = QTextCursor { mTextDocument }; cursor.beginEditBlock(); updateTextDocument(cursor, firstVisibleLineAddress, lastVisibleLineAddress, mBytesPerLine, true); cursor.endEditBlock(); // Update horizontal scroll mMaxDocumentWidth = std::max(mMaxDocumentWidth, mTextDocument->documentLayout()->documentSize().width()); updateHorizontalScrollBar(); // Search text for highlighted word updateHighlightedWord(); mTextDocumentFirstLineAddress = firstVisibleLineAddress; mTextDocumentLastLineAddress = lastVisibleLineAddress; } ================================================ FILE: src/decaf-qt/src/debugger/addresstextdocumentwidget.h ================================================ #pragma once #include #include #include #include #include #include #include class QTextDocument; class AddressTextDocumentWidget : public QAbstractScrollArea { protected: using VirtualAddress = decaf::debug::VirtualAddress; struct DocumentCursor { VirtualAddress address; int cursorPosition; bool operator <(const DocumentCursor &rhs) const { if (address < rhs.address) { return true; } else if (address > rhs.address) { return false; } return cursorPosition < rhs.cursorPosition; } bool operator !=(const DocumentCursor &rhs) const { return address != rhs.address || cursorPosition != rhs.cursorPosition; } }; struct MouseHitTest { VirtualAddress lineAddress; QTextCursor textCursor; }; public: AddressTextDocumentWidget(QWidget *parent = nullptr); void setAddressRange(VirtualAddress start, VirtualAddress end); void setBytesPerLine(int bytesPerLine); int getBytesPerLine() { return mBytesPerLine; }; VirtualAddress getStartAddress() { return mStartAddress; } VirtualAddress getEndAddress() { return mEndAddress; } void navigateToAddress(VirtualAddress address); void navigateBackward(); void navigateForward(); void copySelection(); protected: std::optional mouseEventHitTest(QMouseEvent *e); int characterWidth() { return mCharacterWidth; } int documentMargin() { return mDocumentMargin; } int lineHeight() { return mLineHeight; } void updateTextDocument(bool forceUpdate); protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *) override; void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void keyPressEvent(QKeyEvent *e) override; virtual void updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress, VirtualAddress lastLineAddress, int bytePerLine, bool forDisplay) = 0; virtual QVector getCustomSelections(QTextDocument *document) { return {}; } // We provide a default implementation for cursor / selection tracking which // works perfectly fine as long as bytesPerLine does not change. virtual DocumentCursor cursorFromAddress(VirtualAddress address) { return DocumentCursor { address, 0 }; } virtual VirtualAddress cursorToAddress(DocumentCursor cursor) { return cursor.address; } virtual DocumentCursor getDocumentCursor() { return mDefaultCursor; } virtual DocumentCursor getDocumentSelectionBegin() { return mDefaultSelectionBegin; } virtual DocumentCursor getDocumentSelectionEnd() { return mDefaultSelectionEnd; } virtual void setDocumentCursor(DocumentCursor cursor) { mDefaultCursor = cursor; } virtual void setDocumentSelectionBegin(DocumentCursor cursor) { mDefaultSelectionBegin = cursor; } virtual void setDocumentSelectionEnd(DocumentCursor cursor) { mDefaultSelectionEnd = cursor; } virtual void showContextMenu(QMouseEvent *e) { } private: void showAddress(VirtualAddress address); bool ensureCursorVisible(bool centerOnCursor); void updateHighlightedWord(); void updateHorizontalScrollBar(); void updateVerticalScrollBar(); private: int mLineHeight = 16; int mCharacterWidth = 16; int mBytesPerLine = 16; int mDocumentMargin = 0; int mNumLines = 0; // Address range VirtualAddress mStartAddress = 0; VirtualAddress mEndAddress = 0; // Text document QTextDocument *mTextDocument = nullptr; VirtualAddress mTextDocumentFirstLineAddress = 0; VirtualAddress mTextDocumentLastLineAddress = 0; qreal mMaxDocumentWidth = 0.0; // Text formats QTextCharFormat mTextFormatSelection; QTextCharFormat mTextFormatHighlightedWord; // Cursor bool mBlinkCursorVisible = false; QTimer *mBlinkTimer = nullptr; // Highlighted word QString mHighlightedWord; QVector mHighlightedWordSelections; // Navigation int mNavigationHistoryIndex = 0; size_t mNavigationHistoryMaxSize = 128; std::vector mNavigationBackwardStack; std::vector mNavigationForwardStack; // Default cursor implementation DocumentCursor mDefaultCursor = { }; DocumentCursor mDefaultSelectionBegin = { }; DocumentCursor mDefaultSelectionEnd = { }; }; ================================================ FILE: src/decaf-qt/src/debugger/breakpointsmodel.h ================================================ #pragma once #include #include #include "debugdata.h" class BreakpointsModel : public QAbstractTableModel { Q_OBJECT static constexpr const char *ColumnNames[] = { "Address", "Type", }; static constexpr int ColumnCount = static_cast(sizeof(ColumnNames) / sizeof(ColumnNames[0])); using CpuBreakpoint = decaf::debug::CpuBreakpoint; public: enum UserRoles { AddressRole = Qt::UserRole, }; BreakpointsModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } void setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &BreakpointsModel::debugDataChanged); } int rowCount(const QModelIndex &parent) const override { if (!mDebugData) { return 0; } return static_cast(mDebugData->breakpoints().size()); } int columnCount(const QModelIndex &parent) const override { return ColumnCount; } static QString getTypeString(CpuBreakpoint::Type type) { switch (type) { case CpuBreakpoint::Type::SingleFire: return tr("Single Fire"); case CpuBreakpoint::Type::MultiFire: return tr("Multi Fire"); default: return tr("Unknown"); } } QVariant data(const QModelIndex &index, int role) const override { if (!mDebugData || !index.isValid()) { return QVariant { }; } const auto &breakpoints = mDebugData->breakpoints(); if (index.row() >= breakpoints.size() || index.row() < 0) { return QVariant { }; } if (role == Qt::DisplayRole) { const auto &breakpoint = breakpoints[index.row()]; switch (index.column()) { case 0: return QString("%1").arg(static_cast(breakpoint.address), 8, 16, QChar { '0' }); case 1: return getTypeString(breakpoint.type); } } else if (role == AddressRole) { return breakpoints[index.row()].address; } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant{ }; } if (orientation == Qt::Horizontal) { if (section < ColumnCount) { return ColumnNames[section]; } } return QVariant { }; } private slots: void debugDataChanged() { auto newSize = static_cast(mDebugData->breakpoints().size()); if (newSize < mPreviousSize) { beginRemoveRows({}, newSize, mPreviousSize - 1); endRemoveRows(); } else if (newSize > mPreviousSize) { beginInsertRows({}, mPreviousSize, newSize - 1); endInsertRows(); } mPreviousSize = newSize; } private: DebugData *mDebugData = nullptr; int mPreviousSize = 0; }; ================================================ FILE: src/decaf-qt/src/debugger/breakpointswindow.cpp ================================================ #include "breakpointswindow.h" #include "ui_breakpointswindow.h" #include "debugdata.h" #include "breakpointsmodel.h" #include #include BreakpointsWindow::BreakpointsWindow(QWidget *parent) : QWidget(parent), ui(new Ui::BreakpointsWindow { }) { ui->setupUi(this); ui->tableView->installEventFilter(this); } BreakpointsWindow::~BreakpointsWindow() { delete ui; } void BreakpointsWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; mBreakpointsModel = new BreakpointsModel { this }; mBreakpointsModel->setDebugData(debugData); ui->tableView->setModel(mBreakpointsModel); auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1; ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2); ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); ui->tableView->update(); } void BreakpointsWindow::breakpointsViewDoubleClicked(const QModelIndex &index) { auto address = mBreakpointsModel->data(index, BreakpointsModel::AddressRole); if (!address.isValid()) { return; } navigateToTextAddress(address.value()); } bool BreakpointsWindow::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Delete) { auto index = ui->tableView->currentIndex(); if (index.isValid()) { auto address = mBreakpointsModel->data(index, BreakpointsModel::AddressRole); if (address.isValid()) { cpu::removeBreakpoint(address.value()); return true; } } } } return QObject::eventFilter(obj, event); } ================================================ FILE: src/decaf-qt/src/debugger/breakpointswindow.h ================================================ #pragma once #include namespace Ui { class BreakpointsWindow; } class DebugData; class BreakpointsModel; class BreakpointsWindow : public QWidget { Q_OBJECT public: explicit BreakpointsWindow(QWidget *parent = nullptr); ~BreakpointsWindow(); void setDebugData(DebugData *debugData); signals: void navigateToTextAddress(uint32_t address); protected slots: void breakpointsViewDoubleClicked(const QModelIndex &index); protected: bool eventFilter(QObject *obj, QEvent *event) override; private: Ui::BreakpointsWindow *ui; DebugData *mDebugData = nullptr; BreakpointsModel *mBreakpointsModel = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/debugdata.cpp ================================================ #include "debugdata.h" #include #include #include bool DebugData::update() { if (!decaf::debug::ready()) { return false; } mThreads.clear(); mSegments.clear(); mVoices.clear(); decaf::debug::sampleCafeThreads(mThreads); decaf::debug::sampleCafeMemorySegments(mSegments); decaf::debug::sampleCafeVoices(mVoices); decaf::debug::sampleCpuBreakpoints(mBreakpoints); cpu::jit::sampleStats(mJitStats); if (!mEntryHit) { if (decaf::debug::getLoadedModuleInfo(mLoadedModule)) { // TODO: Spawn thread to do analysis decaf::debug::analyseLoadedModules(mAnalyseDatabase); decaf::debug::analyseCode(mAnalyseDatabase, mLoadedModule.textAddr, mLoadedModule.textAddr + mLoadedModule.textSize); } mEntryHit = true; emit entry(); } auto paused = decaf::debug::isPaused(); auto pauseInitiatorCoreId = 0; auto pauseNia = VirtualAddress { 0 }; if (paused) { pauseInitiatorCoreId = decaf::debug::getPauseInitiatorCoreId(); pauseNia = decaf::debug::getPausedContext(pauseInitiatorCoreId)->nia; } if ((paused != mPaused) || (pauseInitiatorCoreId != mPauseInitiatorCoreId) || (pauseNia != mPauseNia)) { mPaused = paused; mPauseNia = pauseNia; mPauseInitiatorCoreId = pauseInitiatorCoreId; emit executionStateChanged(paused, pauseInitiatorCoreId, pauseNia); } auto captureState = decaf::debug::pm4CaptureState(); if (captureState != mPm4CaptureState) { mPm4CaptureState = captureState; emit pm4CaptureStateChanged(captureState); } auto jitProfilingMask = cpu::jit::getProfilingMask(); if (jitProfilingMask != mJitProfilingMask) { mJitProfilingMask = jitProfilingMask; emit jitProfilingStateChanged( jitProfilingMask & (1 << 0), jitProfilingMask & (1 << 1), jitProfilingMask & (1 << 2)); } emit dataChanged(); return true; } void DebugData::setActiveThreadIndex(int index) { mActiveThreadIndex = index; emit activeThreadIndexChanged(); } void DebugData::setJitProfilingState(bool core0, bool core1, bool core2) { cpu::jit::setProfilingMask(((core0 ? 1 : 0) << 0) | ((core1 ? 1 : 0) << 1) | ((core2 ? 1 : 0) << 2)); } ================================================ FILE: src/decaf-qt/src/debugger/debugdata.h ================================================ #pragma once #include #include #include #include #include class DebugData : public QObject { Q_OBJECT public: using AnalyseDatabase = decaf::debug::AnalyseDatabase; using CafeThread = decaf::debug::CafeThread; using CafeMemorySegment = decaf::debug::CafeMemorySegment; using CafeModuleInfo = decaf::debug::CafeModuleInfo; using CafeVoice = decaf::debug::CafeVoice; using CpuBreakpoint = decaf::debug::CpuBreakpoint; using JitStats = cpu::jit::JitStats; using Pm4CaptureState = decaf::debug::Pm4CaptureState; using VirtualAddress = decaf::debug::VirtualAddress; DebugData(QObject *parent = nullptr) : QObject(parent) { } int activeThreadIndex() const { return mActiveThreadIndex; } const CafeThread *activeThread() const { if (mActiveThreadIndex < 0 || mActiveThreadIndex >= mThreads.size()) { return nullptr; } return &mThreads[mActiveThreadIndex]; } const CafeModuleInfo &loadedModule() const { return mLoadedModule; } const AnalyseDatabase &analyseDatabase() const { return mAnalyseDatabase; } const JitStats &jitStats() const { return mJitStats; } const std::vector breakpoints() const { return mBreakpoints; } const CpuBreakpoint *getBreakpoint(VirtualAddress address) const { for (auto &breakpoint : mBreakpoints) { if (breakpoint.address == address) { return &breakpoint; } } return nullptr; } const std::vector &threads() const { return mThreads; } const std::vector &segments() const { return mSegments; } const std::vector &voices() const { return mVoices; } const CafeMemorySegment *segmentForAddress(VirtualAddress address) const { for (auto &segment : mSegments) { if (address >= segment.address && address - segment.address < segment.size) { return &segment; } } return nullptr; } bool paused() const { return mPaused; } bool update(); void setActiveThreadIndex(int index); void setJitProfilingState(bool core0, bool core1, bool core2); signals: void entry(); void dataChanged(); void activeThreadIndexChanged(); void pm4CaptureStateChanged(Pm4CaptureState state); void executionStateChanged(bool paused, int pauseInitiatorCoreId, VirtualAddress pauseNia); void jitProfilingStateChanged(bool core0, bool core1, bool core2); private: bool mEntryHit = false; int mActiveThreadIndex = -1; AnalyseDatabase mAnalyseDatabase = { }; CafeModuleInfo mLoadedModule = { }; std::vector mThreads; std::vector mSegments; std::vector mVoices; std::vector mBreakpoints; cpu::jit::JitStats mJitStats = { }; bool mPaused = false; int mPauseInitiatorCoreId = -1; VirtualAddress mPauseNia = 0u; Pm4CaptureState mPm4CaptureState = Pm4CaptureState::Disabled; unsigned mJitProfilingMask = 0u; }; ================================================ FILE: src/decaf-qt/src/debugger/debuggershortcuts.h ================================================ #pragma once #include struct DebuggerShortcuts { QAction *toggleBreakpoint; QAction *navigateForward; QAction *navigateBackward; QAction *navigateToAddress; QAction *navigateToOperand; }; ================================================ FILE: src/decaf-qt/src/debugger/debuggerwindow.cpp ================================================ #include "debuggerwindow.h" #include "ui_debuggerwindow.h" #include "debugdata.h" #include "breakpointswindow.h" #include "disassemblywindow.h" #include "functionswindow.h" #include "jitprofilingwindow.h" #include "memorywindow.h" #include "registerswindow.h" #include "segmentswindow.h" #include "stackwindow.h" #include "threadswindow.h" #include "voiceswindow.h" #include #include #include #include #include #include #include #include #include #include DebuggerWindow::DebuggerWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::DebuggerWindow { }) { connect(qApp, &QApplication::focusChanged, this, &DebuggerWindow::focusChanged); ui->setupUi(this); mDebuggerShortcuts.navigateBackward = ui->actionNavigateBackward; mDebuggerShortcuts.navigateForward = ui->actionNavigateForward; mDebuggerShortcuts.navigateToAddress = ui->actionNavigateToAddress; mDebuggerShortcuts.navigateToOperand = ui->actionNavigateToOperand; mDebuggerShortcuts.toggleBreakpoint = ui->actionToggleBreakpoint; mDockManager = new ads::CDockManager { this }; mDebugData = new DebugData { this }; connect(mDebugData, &DebugData::entry, this, &DebuggerWindow::onEntry); connect(mDebugData, &DebugData::pm4CaptureStateChanged, this, &DebuggerWindow::pm4CaptureStateChanged); connect(mDebugData, &DebugData::executionStateChanged, this, &DebuggerWindow::executionStateChanged); connect(mDebugData, &DebugData::activeThreadIndexChanged, this, &DebuggerWindow::activeThreadChanged); mBreakpointsDockWidget = new ads::CDockWidget{ tr("Breakpoints") }; mBreakpointsWindow = new BreakpointsWindow { mBreakpointsDockWidget }; mBreakpointsWindow->setDebugData(mDebugData); mBreakpointsDockWidget->setWidget(mBreakpointsWindow, ads::CDockWidget::ForceNoScrollArea); mDisassemblyDockWidget = new ads::CDockWidget { tr("Disassembly") }; mDisassemblyWindow = new DisassemblyWindow { &mDebuggerShortcuts, mDisassemblyDockWidget }; mDisassemblyWindow->setDebugData(mDebugData); mDisassemblyDockWidget->setWidget(mDisassemblyWindow, ads::CDockWidget::ForceNoScrollArea); mFunctionsDockWidget = new ads::CDockWidget { tr("Functions") }; mFunctionsWindow = new FunctionsWindow { mFunctionsDockWidget }; mFunctionsWindow->setDebugData(mDebugData); mFunctionsDockWidget->setWidget(mFunctionsWindow, ads::CDockWidget::ForceNoScrollArea); mJitProfilingDockWidget = new ads::CDockWidget { tr("JIT Profiling") }; mJitProfilingWindow = new JitProfilingWindow { mJitProfilingDockWidget }; mJitProfilingWindow->setDebugData(mDebugData); mJitProfilingDockWidget->setWidget(mJitProfilingWindow, ads::CDockWidget::ForceNoScrollArea); mMemoryDockWidget = new ads::CDockWidget { tr("Memory") }; mMemoryWindow = new MemoryWindow { &mDebuggerShortcuts, mRegistersDockWidget }; mMemoryDockWidget->setWidget(mMemoryWindow, ads::CDockWidget::ForceNoScrollArea); mRegistersDockWidget = new ads::CDockWidget { tr("Registers") }; mRegistersWindow = new RegistersWindow { mRegistersDockWidget }; mRegistersWindow->setDebugData(mDebugData); mRegistersDockWidget->setWidget(mRegistersWindow, ads::CDockWidget::ForceScrollArea); mSegmentsDockWidget = new ads::CDockWidget { tr("Segments") }; mSegmentsWindow = new SegmentsWindow { mSegmentsDockWidget }; mSegmentsWindow->setDebugData(mDebugData); mSegmentsDockWidget->setWidget(mSegmentsWindow, ads::CDockWidget::ForceNoScrollArea); mStackDockWidget = new ads::CDockWidget { tr("Stack") }; mStackWindow = new StackWindow { &mDebuggerShortcuts, mStackDockWidget }; mStackWindow->setDebugData(mDebugData); mStackDockWidget->setWidget(mStackWindow, ads::CDockWidget::ForceNoScrollArea); mThreadsDockWidget = new ads::CDockWidget { tr("Threads") }; mThreadsWindow = new ThreadsWindow { mThreadsDockWidget }; mThreadsWindow->setDebugData(mDebugData); mThreadsDockWidget->setWidget(mThreadsWindow, ads::CDockWidget::ForceNoScrollArea); mVoicesDockWidget = new ads::CDockWidget { tr("Voices") }; mVoicesWindow = new VoicesWindow { mVoicesDockWidget }; mVoicesWindow->setDebugData(mDebugData); mVoicesDockWidget->setWidget(mVoicesWindow, ads::CDockWidget::ForceNoScrollArea); connect(mBreakpointsWindow, &BreakpointsWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress); connect(mFunctionsWindow, &FunctionsWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress); connect(mJitProfilingWindow, &JitProfilingWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress); connect(mSegmentsWindow, &SegmentsWindow::navigateToDataAddress, this, &DebuggerWindow::gotoDataAddress); connect(mSegmentsWindow, &SegmentsWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress); connect(mStackWindow, &StackWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress); // Setup default dock layout { auto mainDockArea = mDockManager->addDockWidgetTab(ads::CenterDockWidgetArea, mDisassemblyDockWidget); mDockManager->addDockWidgetTabToArea(mMemoryDockWidget, mainDockArea); mDockManager->addDockWidgetTabToArea(mThreadsDockWidget, mainDockArea); mDockManager->addDockWidgetTabToArea(mSegmentsDockWidget, mainDockArea); mDockManager->addDockWidgetTabToArea(mVoicesDockWidget, mainDockArea); mDockManager->addDockWidgetTabToArea(mJitProfilingDockWidget, mainDockArea); mDockManager->addDockWidgetTabToArea(mBreakpointsDockWidget, mainDockArea); mDisassemblyDockWidget->dockAreaWidget()->setCurrentDockWidget(mDisassemblyDockWidget); auto sizePolicy = mainDockArea->sizePolicy(); sizePolicy.setHorizontalStretch(1); sizePolicy.setVerticalStretch(1); mainDockArea->setSizePolicy(sizePolicy); } { auto leftDockArea = mDockManager->addDockWidget(ads::LeftDockWidgetArea, mFunctionsDockWidget); } { auto rightDockArea = mDockManager->addDockWidget(ads::RightDockWidgetArea, mRegistersDockWidget); mDockManager->addDockWidget(ads::BottomDockWidgetArea, mStackDockWidget, rightDockArea); } // Setup shortcuts mBreakpointsDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+b")); mDisassemblyDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+i")); mMemoryDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+m")); mRegistersDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+r")); mSegmentsDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+s")); mStackDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+e")); mThreadsDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+t")); mVoicesDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+p")); mJitProfilingDockWidget->toggleViewAction()->setShortcut(tr("Ctrl+j")); // Add view toggles to menu ui->menuView->addAction(mBreakpointsDockWidget->toggleViewAction()); ui->menuView->addAction(mDisassemblyDockWidget->toggleViewAction()); ui->menuView->addAction(mFunctionsDockWidget->toggleViewAction()); ui->menuView->addAction(mThreadsDockWidget->toggleViewAction()); ui->menuView->addAction(mSegmentsDockWidget->toggleViewAction()); ui->menuView->addAction(mVoicesDockWidget->toggleViewAction()); ui->menuView->addAction(mRegistersDockWidget->toggleViewAction()); ui->menuView->addAction(mStackDockWidget->toggleViewAction()); ui->menuView->addAction(mMemoryDockWidget->toggleViewAction()); ui->menuView->addAction(mJitProfilingDockWidget->toggleViewAction()); // Create a timer to poll debug data from decaf mUpdateModelTimer = new QTimer { this }; connect(mUpdateModelTimer, SIGNAL(timeout()), this, SLOT(updateModel())); mUpdateModelTimer->start(100); } DebuggerWindow::~DebuggerWindow() { delete ui; } void DebuggerWindow::updateModel() { if (!mDebugData->update()) { return; } } void DebuggerWindow::gotoTextAddress(DebugData::VirtualAddress address) { mDisassemblyWindow->navigateToAddress(address); mDisassemblyDockWidget->dockAreaWidget()->setCurrentDockWidget(mDisassemblyDockWidget); } void DebuggerWindow::gotoDataAddress(DebugData::VirtualAddress address) { mMemoryWindow->navigateToAddress(address); mMemoryDockWidget->dockAreaWidget()->setCurrentDockWidget(mMemoryDockWidget); } void DebuggerWindow::onEntry() { auto textStartAddress = 0x02000000u; auto dataStartAddress = 0x10000000u; if (mDebugData->loadedModule().textAddr) { textStartAddress = mDebugData->loadedModule().textAddr; } if (mDebugData->loadedModule().dataAddr) { dataStartAddress = mDebugData->loadedModule().dataAddr; } if (!mDebugData->paused()) { gotoDataAddress(dataStartAddress); gotoTextAddress(textStartAddress); } } void DebuggerWindow::debugPause() { decaf::debug::pause(); } void DebuggerWindow::debugResume() { decaf::debug::resume(); } void DebuggerWindow::debugStepOver() { if (auto activeThread = mDebugData->activeThread()) { decaf::debug::stepOver(activeThread->coreId); } } void DebuggerWindow::debugStepInto() { if (auto activeThread = mDebugData->activeThread()) { decaf::debug::stepInto(activeThread->coreId); } } void DebuggerWindow::debugToggleBreakpoint() { mDisassemblyWindow->toggleBreakpointUnderCursor(); } void DebuggerWindow::setHleTraceEnabled(bool enabled) { auto config = *decaf::config(); config.log.hle_trace = enabled; decaf::setConfig(config); } void DebuggerWindow::setGpuShaderBinaryDumpOnly(bool enabled) { auto config = *gpu::config(); config.debug.dump_shader_binaries_only = enabled; gpu::setConfig(config); } void DebuggerWindow::setGpuShaderDumpEnabled(bool enabled) { auto config = *gpu::config(); config.debug.dump_shaders = enabled; gpu::setConfig(config); } void DebuggerWindow::setGx2ShaderDumpEnabled(bool enabled) { auto config = *decaf::config(); config.gx2.dump_shaders = enabled; decaf::setConfig(config); } void DebuggerWindow::setGx2TextureDumpEnabled(bool enabled) { auto config = *decaf::config(); config.gx2.dump_textures = enabled; decaf::setConfig(config); } void DebuggerWindow::setPm4TraceEnabled(bool enabled) { if (enabled) { decaf::debug::pm4CaptureBegin(); ui->actionPm4CaptureNextFrame->setEnabled(false); ui->actionPm4TraceEnabled->setEnabled(false); } else { decaf::debug::pm4CaptureEnd(); } } void DebuggerWindow::pm4CaptureNextFrame() { decaf::debug::pm4CaptureNextFrame(); ui->actionPm4CaptureNextFrame->setEnabled(false); ui->actionPm4TraceEnabled->setEnabled(false); } void DebuggerWindow::pm4CaptureStateChanged(decaf::debug::Pm4CaptureState state) { if (state == decaf::debug::Pm4CaptureState::Disabled) { ui->actionPm4CaptureNextFrame->setEnabled(true); ui->actionPm4TraceEnabled->setEnabled(true); ui->actionPm4TraceEnabled->setChecked(false); } else if (state == decaf::debug::Pm4CaptureState::Enabled) { ui->actionPm4CaptureNextFrame->setEnabled(false); ui->actionPm4TraceEnabled->setEnabled(true); ui->actionPm4TraceEnabled->setChecked(true); } else { ui->actionPm4CaptureNextFrame->setEnabled(false); ui->actionPm4TraceEnabled->setEnabled(false); ui->actionPm4TraceEnabled->setChecked(true); } } void DebuggerWindow::activeThreadChanged() { auto activeThread = mDebugData->activeThread(); if (!activeThread) { return; } if (mDebugData->paused()) { gotoTextAddress(activeThread->nia); } } void DebuggerWindow::executionStateChanged(bool paused, int pauseInitiatorCoreId, decaf::debug::VirtualAddress pauseNia) { if (!paused) { ui->actionResume->setEnabled(false); ui->actionPause->setEnabled(true); return; } ui->actionPause->setEnabled(false); auto canResume = !decaf::stopping(); ui->actionResume->setEnabled(canResume); ui->actionStepInto->setEnabled(canResume); ui->actionStepOver->setEnabled(canResume); // Set active thread to the one that initiated pause - this will lead to // activeThreadChanged being called and thus follow pauseNia implicitly auto &threads = mDebugData->threads(); for (auto i = 0u; i < threads.size(); ++i) { if (threads[i].coreId == pauseInitiatorCoreId) { mDebugData->setActiveThreadIndex(i); } } } void DebuggerWindow::navigateBackward() { if (isDockWidgetFocused(mDisassemblyWindow)) { mDisassemblyWindow->navigateBackward(); } else if (isDockWidgetFocused(mMemoryWindow)) { mMemoryWindow->navigateBackward(); } else if (isDockWidgetFocused(mStackWindow)) { mStackWindow->navigateBackward(); } } void DebuggerWindow::navigateForward() { if (isDockWidgetFocused(mDisassemblyWindow)) { mDisassemblyWindow->navigateForward(); } else if (isDockWidgetFocused(mMemoryWindow)) { mMemoryWindow->navigateForward(); } else if (isDockWidgetFocused(mStackWindow)) { mStackWindow->navigateForward(); } } void DebuggerWindow::navigateAddress() { auto address = 0u; while (true) { auto ok = false; auto text = QInputDialog::getText(this, tr("Navigate to Address"), tr("Address:"), QLineEdit::Normal, {}, &ok); if (!ok) { return; // Cancelled } address = text.toUInt(&ok, 16); if (!ok) { QMessageBox::warning(this, "Navigate to address", "Invalid address"); continue; // Try again } break; } if (isDockWidgetFocused(mDisassemblyWindow)) { mDisassemblyWindow->navigateToAddress(address); } else if (isDockWidgetFocused(mMemoryWindow)) { mMemoryWindow->navigateToAddress(address); } else if (isDockWidgetFocused(mStackWindow)) { mStackWindow->navigateToAddress(address); } } void DebuggerWindow::navigateOperand() { if (isDockWidgetFocused(mDisassemblyWindow)) { mDisassemblyWindow->navigateOperand(); } else if (isDockWidgetFocused(mStackWindow)) { mStackWindow->navigateOperand(); } } void DebuggerWindow::focusChanged(QWidget *old, QWidget *now) { const auto updateActionState = [](QAction *action) { for (auto widget : action->associatedWidgets()) { if (widget->hasFocus()) { action->setEnabled(true); return; } } action->setEnabled(false); }; updateActionState(ui->actionToggleBreakpoint); updateActionState(ui->actionNavigateForward); updateActionState(ui->actionNavigateBackward); updateActionState(ui->actionNavigateToAddress); updateActionState(ui->actionNavigateToOperand); } bool DebuggerWindow::isDockWidgetFocused(QWidget *dockWidget) { auto widget = QApplication::focusWidget(); while (widget) { if (widget == dockWidget) { return true; } widget = widget->parentWidget(); } return false; } ================================================ FILE: src/decaf-qt/src/debugger/debuggerwindow.h ================================================ #pragma once #include "debuggershortcuts.h" #include #include #include namespace Ui { class DebuggerWindow; } class DebugData; class QTimer; class BreakpointsWindow; class DisassemblyWindow; class FunctionsWindow; class JitProfilingWindow; class SegmentsWindow; class ThreadsWindow; class VoicesWindow; class RegistersWindow; class MemoryWindow; class StackWindow; class DebuggerWindow : public QMainWindow { Q_OBJECT public: explicit DebuggerWindow(QWidget *parent = 0); ~DebuggerWindow(); void gotoTextAddress(decaf::debug::VirtualAddress address); void gotoDataAddress(decaf::debug::VirtualAddress address); public slots: void updateModel(); void onEntry(); void debugPause(); void debugResume(); void debugStepOver(); void debugStepInto(); void debugToggleBreakpoint(); void setHleTraceEnabled(bool enabled); void setGpuShaderBinaryDumpOnly(bool enabled); void setGpuShaderDumpEnabled(bool enabled); void setGx2ShaderDumpEnabled(bool enabled); void setGx2TextureDumpEnabled(bool enabled); void setPm4TraceEnabled(bool enabled); void pm4CaptureNextFrame(); void pm4CaptureStateChanged(decaf::debug::Pm4CaptureState state); void activeThreadChanged(); void executionStateChanged(bool paused, int pauseInitiatorCoreId, decaf::debug::VirtualAddress pauseNia); void focusChanged(QWidget *old, QWidget *now); void navigateBackward(); void navigateForward(); void navigateAddress(); void navigateOperand(); private: bool isDockWidgetFocused(QWidget *widget); private: Ui::DebuggerWindow *ui; ads::CDockManager *mDockManager; DebuggerShortcuts mDebuggerShortcuts; DebugData *mDebugData; QTimer *mUpdateModelTimer; ads::CDockWidget *mBreakpointsDockWidget = nullptr; BreakpointsWindow *mBreakpointsWindow = nullptr; ads::CDockWidget *mDisassemblyDockWidget = nullptr; DisassemblyWindow *mDisassemblyWindow = nullptr; ads::CDockWidget *mFunctionsDockWidget = nullptr; FunctionsWindow *mFunctionsWindow = nullptr; ads::CDockWidget *mJitProfilingDockWidget = nullptr; JitProfilingWindow *mJitProfilingWindow = nullptr; ads::CDockWidget *mMemoryDockWidget = nullptr; MemoryWindow *mMemoryWindow = nullptr; ads::CDockWidget *mRegistersDockWidget = nullptr; RegistersWindow *mRegistersWindow = nullptr; ads::CDockWidget *mSegmentsDockWidget = nullptr; SegmentsWindow *mSegmentsWindow = nullptr; ads::CDockWidget *mStackDockWidget = nullptr; StackWindow *mStackWindow = nullptr; ads::CDockWidget *mThreadsDockWidget = nullptr; ThreadsWindow *mThreadsWindow = nullptr; ads::CDockWidget *mVoicesDockWidget = nullptr; VoicesWindow *mVoicesWindow = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/disassemblywidget.cpp ================================================ #include "disassemblywidget.h" #include #include #include #include #include #include DisassemblyWidget::DisassemblyWidget(QWidget *parent) : AddressTextDocumentWidget(parent) { setBytesPerLine(4); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); mTextFormats.breakpoint = QTextCharFormat { }; mTextFormats.breakpoint.setBackground(QColor { Qt::red }.lighter()); mTextFormats.currentInstruction = QTextCharFormat { }; mTextFormats.currentInstruction.setBackground(QColor { Qt::green }.lighter()); mTextFormats.lineAddress = QTextCharFormat { }; mTextFormats.lineAddress.setForeground(Qt::black); mTextFormats.instructionData = QTextCharFormat{ }; mTextFormats.instructionData.setForeground(Qt::gray); mTextFormats.instructionName = QTextCharFormat { }; mTextFormats.instructionName.setForeground(Qt::darkBlue); mTextFormats.registerName = QTextCharFormat { }; mTextFormats.registerName.setForeground(Qt::darkBlue); mTextFormats.punctuation = QTextCharFormat { }; mTextFormats.punctuation.setForeground(Qt::darkBlue); mTextFormats.branchAddress = QTextCharFormat { }; mTextFormats.branchAddress.setForeground(Qt::blue); mTextFormats.symbolName = QTextCharFormat { }; mTextFormats.symbolName.setForeground(Qt::blue); mTextFormats.numericValue = QTextCharFormat { }; mTextFormats.numericValue.setForeground(Qt::darkGreen); mTextFormats.invalid = QTextCharFormat { }; mTextFormats.invalid.setForeground(Qt::darkGray); mTextFormats.comment = QTextCharFormat{ }; mTextFormats.comment.setForeground(Qt::gray); mTextFormats.functionOutline = Qt::black; mTextFormats.branchDirectionArrow = Qt::darkBlue; mTextFormats.branchOutlineTrue = Qt::darkGreen; mTextFormats.branchOutlineFalse = Qt::darkRed; auto arrowSize = characterWidth() - 1; mTextFormats.branchDownArrowPath = QPainterPath { }; mTextFormats.branchDownArrowPath.moveTo(0, 0); mTextFormats.branchDownArrowPath.lineTo(arrowSize, 0); mTextFormats.branchDownArrowPath.lineTo(arrowSize / 2.0, arrowSize); mTextFormats.branchDownArrowPath.lineTo(0, 0); mTextFormats.branchDownArrowPath.translate(-arrowSize / 2.0, -arrowSize / 2.0); mTextFormats.branchUpArrowPath = QPainterPath { }; mTextFormats.branchUpArrowPath.moveTo(0, arrowSize); mTextFormats.branchUpArrowPath.lineTo(arrowSize, arrowSize); mTextFormats.branchUpArrowPath.lineTo(arrowSize / 2.0, 0); mTextFormats.branchUpArrowPath.lineTo(0, arrowSize); mTextFormats.branchUpArrowPath.translate(-arrowSize / 2.0, -arrowSize / 2.0); } void DisassemblyWidget::setDebugData(DebugData *debugData) { mDebugData = debugData; } void DisassemblyWidget::followSymbolUnderCursor() { followSymbolAtCursor(getDocumentCursor()); } void DisassemblyWidget::toggleBreakpointUnderCursor() { auto address = getDocumentCursor().address; if (decaf::debug::hasBreakpoint(address)) { decaf::debug::removeBreakpoint(address); } else { decaf::debug::addBreakpoint(address); } AddressTextDocumentWidget::updateTextDocument(true); } void DisassemblyWidget::followSymbolAtCursor(DocumentCursor cursor) { auto cacheIndex = (cursor.address - mCacheStartAddress) / 4; if (cacheIndex >= mTextCursorPositionCache.size()) { return; } auto &cursorPositionCache = mTextCursorPositionCache[cacheIndex]; auto &disassemblyCache = mDisassemblyCache[cacheIndex]; auto inRange = [](int cursorPosition, std::pair range) { return cursorPosition >= range.first && cursorPosition < range.second; }; for (auto i = 0u; i < cursorPositionCache.instructionArgs.size(); ++i) { auto argCursorPositions = cursorPositionCache.instructionArgs[i]; if (!inRange(cursor.cursorPosition, argCursorPositions)) { continue; } auto arg = disassemblyCache.disassembly.args[i]; if (arg.type == espresso::Disassembly::Argument::Address) { navigateToAddress(arg.address); } } if (inRange(cursor.cursorPosition, cursorPositionCache.referencedSymbol)) { navigateToAddress(disassemblyCache.referenceLookup->start); } } void DisassemblyWidget::paintEvent(QPaintEvent *e) { AddressTextDocumentWidget::paintEvent(e); auto painter = QPainter { viewport() }; painter.translate(QPoint { -horizontalScrollBar()->value(), 0 }); if (mVisibleColumns.functionOutline) { auto offset = documentMargin(); if (mVisibleColumns.lineAddress) { offset += (mTextCursorPositionCache[0].lineAddress.second + 1) * characterWidth(); } auto functionLineStartY = -1; auto functionLineX1 = offset + (characterWidth() / 2); auto functionLineX2 = functionLineX1 + characterWidth(); auto lineY = documentMargin() + lineHeight() / 2; painter.setPen(QPen { mTextFormats.functionOutline }); for (auto &item : mDisassemblyCache) { if (item.addressLookup.function) { if (item.addressLookup.function->start == item.address && item.addressLookup.function->end != 0xFFFFFFFF) { // Start of function painter.drawLine(functionLineX1, lineY, functionLineX2, lineY); functionLineStartY = lineY; } else if (item.addressLookup.function->end == item.address + 4) { // End of function painter.drawLine(functionLineX1, functionLineStartY, functionLineX1, lineY); painter.drawLine(functionLineX1, lineY, functionLineX2, lineY); functionLineStartY = -1; } else { // Inside function if (functionLineStartY == -1) { functionLineStartY = 0; } } } lineY += lineHeight(); } if (functionLineStartY != -1) { painter.drawLine(functionLineX1, functionLineStartY, functionLineX1, viewport()->height()); } } if (mVisibleColumns.branchOutline) { auto offset = documentMargin(); if (mVisibleColumns.instructionData) { offset += (mTextCursorPositionCache[0].instructionData.second + 1) * characterWidth(); } else { if (mVisibleColumns.lineAddress) { offset += (mTextCursorPositionCache[0].lineAddress.second + 1) * characterWidth(); } if (mVisibleColumns.functionOutline) { offset += (mPunctuation.functionOutline.size() + 1) * characterWidth(); } } auto lineY = documentMargin() + lineHeight() / 2; auto outlineX = offset + characterWidth() / 2; auto arrowLeftX = offset + characterWidth() * 2 + 2; auto cursor = getDocumentCursor(); auto ctr = 0u; auto cr = 0u; auto lr = 0u; if (auto activeThread = mDebugData->activeThread()) { ctr = activeThread->ctr; cr = activeThread->cr; lr = activeThread->lr; } for (auto &item : mDisassemblyCache) { if (item.disassembly.instruction && espresso::isBranchInstruction(item.disassembly.instruction->id)) { auto instr = qFromBigEndian(item.data.data()); auto info = espresso::disassembleBranchInfo(item.disassembly.instruction->id, instr, item.address, ctr, cr, lr); if (!info.isVariable && !info.isCall) { if (info.target > item.address) { painter.fillPath( mTextFormats.branchDownArrowPath.translated(arrowLeftX, lineY + 2), mTextFormats.branchDirectionArrow); } else { painter.fillPath( mTextFormats.branchUpArrowPath.translated(arrowLeftX, lineY + 2), mTextFormats.branchDirectionArrow); } if (cursor.address == item.address) { painter.setPen(QPen { info.conditionSatisfied ? mTextFormats.branchOutlineTrue : mTextFormats.branchOutlineFalse }); painter.drawLine(outlineX, lineY, outlineX + characterWidth(), lineY); if (info.target < mCacheStartAddress) { painter.drawLine(outlineX, 0, outlineX, lineY); } else { auto index = (info.target - mCacheStartAddress) / 4; if (index >= mDisassemblyCache.size()) { painter.drawLine(outlineX, lineY, outlineX, viewport()->height()); } else { auto targetLineY = documentMargin() + lineHeight() / 2 + lineHeight() * index; painter.drawLine(outlineX, lineY, outlineX, targetLineY); painter.drawLine(outlineX, targetLineY, outlineX + characterWidth(), targetLineY); } } } } } lineY += lineHeight(); } } } void DisassemblyWidget::mouseReleaseEvent(QMouseEvent *e) { auto handled = false; if ((e->modifiers() & Qt::ControlModifier) && e->button() == Qt::LeftButton) { if (auto hit = mouseEventHitTest(e)) { followSymbolAtCursor({ hit->lineAddress, hit->textCursor.positionInBlock() }); handled = true; } } if (!handled) { AddressTextDocumentWidget::mouseReleaseEvent(e); } } void DisassemblyWidget::keyPressEvent(QKeyEvent *e) { auto handled = false; if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { followSymbolUnderCursor(); handled = true; } if (!handled) { AddressTextDocumentWidget::keyPressEvent(e); } else { e->accept(); } } QVector DisassemblyWidget::getCustomSelections(QTextDocument *document) { auto line = 0; auto activeThread = mDebugData->activeThread(); mCustomSelectionsBuffer.clear(); for (auto &item : mDisassemblyCache) { if (activeThread && activeThread->nia == item.address) { auto selection = QAbstractTextDocumentLayout::Selection { }; selection.cursor = QTextCursor { document->findBlockByLineNumber(line) }; selection.cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); selection.format = mTextFormats.currentInstruction; mCustomSelectionsBuffer.push_back(selection); } else if (mDebugData->getBreakpoint(item.address)) { auto selection = QAbstractTextDocumentLayout::Selection { }; selection.cursor = QTextCursor { document->findBlockByLineNumber(line) }; selection.cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); selection.format = mTextFormats.breakpoint; mCustomSelectionsBuffer.push_back(selection); } ++line; } return mCustomSelectionsBuffer; } void DisassemblyWidget::updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress, VirtualAddress lastLineAddress, int bytesPerLine, bool forDisplay) { mCacheStartAddress = firstLineAddress; mTextCursorPositionCache.clear(); mDisassemblyCache.clear(); for (auto address = static_cast(firstLineAddress); address <= lastLineAddress; address += bytesPerLine) { auto &cursorPositionCache = mTextCursorPositionCache.emplace_back(); auto &item = mDisassemblyCache.emplace_back(); item.address = static_cast(address); item.valid = decaf::debug::readMemory(item.address, item.data.data(), bytesPerLine) == bytesPerLine; auto breakpoint = mDebugData->getBreakpoint(item.address); if (breakpoint) { item.data[3] = (breakpoint->savedCode >> 0) & 0xFF; item.data[2] = (breakpoint->savedCode >> 8) & 0xFF; item.data[1] = (breakpoint->savedCode >> 16) & 0xFF; item.data[0] = (breakpoint->savedCode >> 24) & 0xFF; } if (item.address != firstLineAddress) { cursor.insertBlock(); } if (mVisibleColumns.lineAddress) { cursorPositionCache.lineAddress.first = cursor.positionInBlock(); cursor.insertText( QString { "%1" }.arg(item.address, 8, 16, QLatin1Char { '0' }), !item.valid ? mTextFormats.invalid : mTextFormats.lineAddress); cursorPositionCache.lineAddress.second = cursor.positionInBlock(); cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation); } if (!item.valid) { continue; } if (mVisibleColumns.functionOutline) { cursor.insertText(mPunctuation.functionOutline, mTextFormats.punctuation); } if (mVisibleColumns.instructionData) { cursorPositionCache.instructionData.first = cursor.positionInBlock(); cursor.insertText(QString { "%1 %2 %3 %4" } .arg(item.data[0], 2, 16, QLatin1Char { '0' }) .arg(item.data[1], 2, 16, QLatin1Char { '0' }) .arg(item.data[2], 2, 16, QLatin1Char { '0' }) .arg(item.data[3], 2, 16, QLatin1Char { '0' }) .toUpper(), mTextFormats.instructionData); cursorPositionCache.instructionData.second = cursor.positionInBlock(); cursor.insertText(mPunctuation.afterInstructionData, mTextFormats.punctuation); } auto disassemblyValid = espresso::disassemble(qFromBigEndian(item.data.data()), item.disassembly, item.address); if (mVisibleColumns.branchOutline) { cursor.insertText(mPunctuation.branchOutline, mTextFormats.punctuation); } if (mVisibleColumns.instructionName) { cursorPositionCache.instructionName.first = cursor.positionInBlock(); if (!disassemblyValid) { cursor.insertText("???", mTextFormats.invalid); } else { cursor.insertText(QString::fromStdString(item.disassembly.name), mTextFormats.instructionName); } cursorPositionCache.instructionName.second = cursor.positionInBlock(); cursor.insertText( QString { ' ' }.repeated( std::max(0, mPunctuation.instructionNameWidth - static_cast(item.disassembly.name.size()))), mTextFormats.punctuation); } if (mVisibleColumns.instructionArgs) { auto beforeArgsPosition = cursor.position(); auto firstArg = true; for (const auto &arg : item.disassembly.args) { if (!firstArg) { cursor.insertText(QString { ", " }, mTextFormats.punctuation); } firstArg = false; auto &argCursorPosition = cursorPositionCache.instructionArgs.emplace_back(); argCursorPosition.first = cursor.positionInBlock(); switch (arg.type) { case espresso::Disassembly::Argument::Address: { auto lookup = decaf::debug::analyseLookupFunction(mDebugData->analyseDatabase(), arg.address); if (lookup && lookup->start == arg.address && !lookup->name.empty()) { item.referenceLookup = lookup; } cursor.insertText( QString { "@%1" }.arg(arg.address, 8, 16, QLatin1Char { '0' }), mTextFormats.branchAddress); break; } case espresso::Disassembly::Argument::Register: cursor.insertText( QString::fromStdString(arg.registerName), mTextFormats.registerName); break; case espresso::Disassembly::Argument::ValueUnsigned: if (arg.valueUnsigned > 9) { cursor.insertText( QString { "0x%1" }.arg(arg.valueUnsigned, 0, 16), mTextFormats.numericValue); } else { cursor.insertText( QString { "%1" }.arg(arg.valueUnsigned), mTextFormats.numericValue); } break; case espresso::Disassembly::Argument::ValueSigned: if (arg.valueSigned < -9) { cursor.insertText( QString { "-0x%1" }.arg(-arg.valueSigned, 0, 16), mTextFormats.numericValue); } else if (arg.valueSigned > 9) { cursor.insertText( QString { "0x%1" }.arg(arg.valueSigned, 0, 16), mTextFormats.numericValue); } else { cursor.insertText( QString { "%1" }.arg(arg.valueSigned), mTextFormats.numericValue); } break; case espresso::Disassembly::Argument::ConstantUnsigned: cursor.insertText( QString { "%1" }.arg(arg.constantUnsigned), mTextFormats.numericValue); break; case espresso::Disassembly::Argument::ConstantSigned: cursor.insertText( QString { "%1" }.arg(arg.constantSigned), mTextFormats.numericValue); break; default: cursor.insertText( QString { '?' }, mTextFormats.invalid); break; } argCursorPosition.second = cursor.positionInBlock(); } auto afterArgsPosition = cursor.position(); cursor.insertText( QString { ' ' }.repeated(std::max(0, mPunctuation.instructionArgsWidth - (afterArgsPosition - beforeArgsPosition))), mTextFormats.punctuation); } if (mVisibleColumns.referencedSymbol && item.referenceLookup) { cursorPositionCache.referencedSymbol.first = cursor.positionInBlock(); cursor.insertText( QString { "@%1" }.arg(QString::fromStdString(item.referenceLookup->name)), mTextFormats.symbolName); cursorPositionCache.referencedSymbol.second = cursor.positionInBlock(); cursor.insertText(mPunctuation.afterReferencedSymbol, mTextFormats.punctuation); } item.addressLookup = decaf::debug::analyseLookupAddress(mDebugData->analyseDatabase(), item.address); if (item.addressLookup.function && item.addressLookup.function->start == item.address && !item.addressLookup.function->name.empty()) { cursor.setCharFormat(mTextFormats.comment); cursor.insertText(mPunctuation.beforeComment); cursorPositionCache.commentFunctionName.first = cursor.positionInBlock(); cursor.insertText(QString::fromStdString(item.addressLookup.function->name)); cursorPositionCache.commentFunctionName.second = cursor.positionInBlock(); } } } ================================================ FILE: src/decaf-qt/src/debugger/disassemblywidget.h ================================================ #pragma once #include "addresstextdocumentwidget.h" #include "debugdata.h" #ifndef Q_MOC_RUN // moc struggles parsing espresso_disassembler.h on some platforms #include #endif #include #include #include #include #include #include #include class QTextDocument; class DisassemblyWidget : public AddressTextDocumentWidget { Q_OBJECT using VirtualAddress = DebugData::VirtualAddress; public: DisassemblyWidget(QWidget *parent = nullptr); void setDebugData(DebugData *debugData); public slots: void followSymbolUnderCursor(); void toggleBreakpointUnderCursor(); protected: void followSymbolAtCursor(DocumentCursor cursor); void paintEvent(QPaintEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void keyPressEvent(QKeyEvent *e) override; QVector getCustomSelections(QTextDocument *document) override; void updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress, VirtualAddress lastLineAddress, int bytePerLine, bool forDisplay) override; private: DebugData *mDebugData; VirtualAddress mCacheStartAddress; QVector mCustomSelectionsBuffer; // Cached disassembly information of current visible instructions struct DisassemblyCacheItem { bool valid = false; VirtualAddress address; std::array data; #ifndef Q_MOC_RUN espresso::Disassembly disassembly; #endif DebugData::AnalyseDatabase::Lookup addressLookup; const DebugData::AnalyseDatabase::Function *referenceLookup; }; std::vector mDisassemblyCache; // Cache of the cursor positions of current visible instructions struct TextCursorPositionCache { std::pair lineAddress; std::pair instructionData; std::pair instructionName; std::vector> instructionArgs; std::pair referencedSymbol; std::pair commentFunctionName; }; std::vector mTextCursorPositionCache; // Visible columns in disassembly struct { bool lineAddress = true; bool functionOutline = true; bool instructionData = true; bool branchOutline = true; bool instructionName = true; bool instructionArgs = true; bool referencedSymbol = true; bool functionName = true; } mVisibleColumns; // Punctuation between columns struct { QString afterLineAddress = " "; QString functionOutline = " "; QString afterInstructionData = " "; QString branchOutline = " "; int instructionNameWidth = 12; int instructionArgsWidth = 32; QString afterReferencedSymbol = " "; QString beforeComment = "# "; } mPunctuation; // Formatting for each data type struct { QTextCharFormat breakpoint; QTextCharFormat currentInstruction; QTextCharFormat lineAddress; QTextCharFormat instructionData; QTextCharFormat instructionName; QTextCharFormat registerName; QTextCharFormat punctuation; QTextCharFormat branchAddress; QTextCharFormat symbolName; QTextCharFormat numericValue; QTextCharFormat invalid; QTextCharFormat comment; QColor functionOutline; QColor branchOutlineTrue; QColor branchOutlineFalse; QColor branchDirectionArrow; QPainterPath branchUpArrowPath; QPainterPath branchDownArrowPath; } mTextFormats; }; ================================================ FILE: src/decaf-qt/src/debugger/disassemblywindow.cpp ================================================ #include "debugdata.h" #include "debuggershortcuts.h" #include "disassemblywindow.h" #include "ui_disassemblywindow.h" DisassemblyWindow::DisassemblyWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent) : QWidget(parent), ui(new Ui::DisassemblyWindow { }) { ui->setupUi(this); ui->disassemblyWidget->addAction(debuggerShortcuts->toggleBreakpoint); ui->disassemblyWidget->addAction(debuggerShortcuts->navigateBackward); ui->disassemblyWidget->addAction(debuggerShortcuts->navigateForward); ui->disassemblyWidget->addAction(debuggerShortcuts->navigateToAddress); ui->disassemblyWidget->addAction(debuggerShortcuts->navigateToOperand); } DisassemblyWindow::~DisassemblyWindow() { delete ui; } void DisassemblyWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; ui->disassemblyWidget->setDebugData(debugData); ui->disassemblyWidget->setAddressRange(0, 0xFFFFFFFF); ui->disassemblyWidget->navigateToAddress(0x02000000); } void DisassemblyWindow::navigateToAddress(uint32_t address) { ui->disassemblyWidget->navigateToAddress(address); } void DisassemblyWindow::navigateForward() { ui->disassemblyWidget->navigateForward(); } void DisassemblyWindow::navigateBackward() { ui->disassemblyWidget->navigateBackward(); } void DisassemblyWindow::navigateOperand() { ui->disassemblyWidget->followSymbolUnderCursor(); } void DisassemblyWindow::toggleBreakpointUnderCursor() { ui->disassemblyWidget->toggleBreakpointUnderCursor(); } ================================================ FILE: src/decaf-qt/src/debugger/disassemblywindow.h ================================================ #pragma once #include namespace Ui { class DisassemblyWindow; } class DebugData; struct DebuggerShortcuts; class DisassemblyWindow : public QWidget { Q_OBJECT public: explicit DisassemblyWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent = nullptr); ~DisassemblyWindow(); void setDebugData(DebugData *debugData); public slots: void navigateToAddress(uint32_t address); void navigateForward(); void navigateBackward(); void navigateOperand(); void toggleBreakpointUnderCursor(); private: Ui::DisassemblyWindow *ui; DebugData *mDebugData = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/functionsmodel.h ================================================ #pragma once #include #include "debugdata.h" class FunctionsModel : public QAbstractTableModel { Q_OBJECT enum Columns { Name, Start, Length, Segment, NumColumns, }; using AnalyseDatabase = DebugData::AnalyseDatabase; public: enum UserRoles { StartAddressRole = Qt::UserRole, }; FunctionsModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } void setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &FunctionsModel::debugDataChanged); } int rowCount(const QModelIndex &parent) const override { if (!mDebugData) { return 0; } return static_cast(mDebugData->analyseDatabase().functions.size()); } int columnCount(const QModelIndex &parent) const override { return NumColumns; } QVariant data(const QModelIndex &index, int role) const override { if (!mDebugData || !index.isValid()) { return QVariant{ }; } const auto &functions = mDebugData->analyseDatabase().functions; if (index.row() >= functions.size() || index.row() < 0) { return QVariant{ }; } if (role == Qt::DisplayRole) { const auto &func = functions[index.row()]; switch (index.column()) { case Columns::Name: return QString::fromStdString(func.name); case Columns::Start: return QString { "%1" }.arg(func.start, 8, 16, QChar { '0' }).toUpper(); case Columns::Length: return QString { "%1" }.arg(func.end - func.start, 8, 16, QChar { '0' }).toUpper(); case Columns::Segment: { auto segment = mDebugData->segmentForAddress(func.start); if (!segment) { return QString { "?" }; } return QString::fromStdString(segment->name); } } } else if (role == StartAddressRole) { return functions[index.row()].start; } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant{ }; } if (orientation == Qt::Horizontal) { switch (section) { case Columns::Name: return tr("Name"); case Columns::Start: return tr("Start"); case Columns::Length: return tr("Length"); case Columns::Segment: return tr("Segment"); } } return QVariant { }; } public slots: void debugDataChanged() { auto newSize = static_cast(mDebugData->analyseDatabase().functions.size()); if (newSize < mPreviousSize) { beginRemoveRows({}, newSize, mPreviousSize - 1); endRemoveRows(); } else if (newSize > mPreviousSize) { beginInsertRows({}, mPreviousSize, newSize - 1); endInsertRows(); } mPreviousSize = newSize; } private: DebugData *mDebugData = nullptr; int mPreviousSize = 0; }; ================================================ FILE: src/decaf-qt/src/debugger/functionswindow.cpp ================================================ #include "functionswindow.h" #include "functionsmodel.h" #include "debugdata.h" #include "ui_functionswindow.h" #include FunctionsWindow::FunctionsWindow(QWidget *parent) : QWidget(parent), ui(new Ui::FunctionsWindow { }) { ui->setupUi(this); } FunctionsWindow::~FunctionsWindow() { delete ui; } void FunctionsWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; mFunctionsModel = new FunctionsModel { this }; mFunctionsModel->setDebugData(mDebugData); mSortModel = new QSortFilterProxyModel { this }; mSortModel->setSourceModel(mFunctionsModel); mSortModel->setSortCaseSensitivity(Qt::CaseInsensitive); mSortModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mSortModel->setFilterKeyColumn(0); ui->tableView->setModel(mSortModel); auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1; ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2); ui->tableView->setSortingEnabled(true); ui->tableView->update(); connect(mDebugData, &DebugData::entry, [&]{ mFunctionsModel->debugDataChanged(); ui->tableView->sortByColumn(1, Qt::AscendingOrder); ui->tableView->resizeColumnToContents(1); ui->tableView->resizeColumnToContents(2); ui->tableView->resizeColumnToContents(3); }); } void FunctionsWindow::filterChanged(QString value) { mSortModel->setFilterFixedString(value); } void FunctionsWindow::functionsViewDoubleClicked(const QModelIndex &index) { auto functionIndex = mSortModel->mapToSource(index); auto startAddress = mFunctionsModel->data(functionIndex, FunctionsModel::StartAddressRole); if (!startAddress.isValid()) { return; } navigateToTextAddress(startAddress.value()); } ================================================ FILE: src/decaf-qt/src/debugger/functionswindow.h ================================================ #pragma once #include namespace Ui { class FunctionsWindow; } class DebugData; class FunctionsModel; class QSortFilterProxyModel; class FunctionsWindow : public QWidget { Q_OBJECT public: explicit FunctionsWindow(QWidget *parent = nullptr); ~FunctionsWindow(); void setDebugData(DebugData *debugData); signals: void navigateToTextAddress(uint32_t address); public slots: void filterChanged(QString value); void functionsViewDoubleClicked(const QModelIndex &index); private: Ui::FunctionsWindow *ui; DebugData *mDebugData = nullptr; FunctionsModel *mFunctionsModel = nullptr; QSortFilterProxyModel *mSortModel = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/jitprofilingmodel.h ================================================ #pragma once #include #include "debugdata.h" class JitProfilingModel : public QAbstractTableModel { Q_OBJECT static constexpr const char *ColumnNames[] = { "Address", "Native Code", "Time %", "Total Cycles", "Call Count", "Cycles/Call", }; static constexpr int ColumnCount = static_cast(sizeof(ColumnNames) / sizeof(ColumnNames[0])); public: enum UserRole { SortRole = Qt::UserRole, }; JitProfilingModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } void setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &JitProfilingModel::debugDataChanged); } int rowCount(const QModelIndex &parent) const override { if (!mDebugData) { return 0; } return static_cast(mDebugData->jitStats().compiledBlocks.size()); } int columnCount(const QModelIndex &parent) const override { return ColumnCount; } QVariant data(const QModelIndex &index, int role) const override { if (!mDebugData || !index.isValid()) { return QVariant { }; } const auto &jitStats = mDebugData->jitStats(); if (index.row() >= jitStats.compiledBlocks.size() || index.row() < 0) { return QVariant { }; } const auto totalTime = static_cast(jitStats.totalTimeInCodeBlocks); const auto &stats = jitStats.compiledBlocks[index.row()]; if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return QString { "%1" }.arg(static_cast(stats.address), 8, 16, QChar { '0' }); case 1: return QString { "%1" }.arg((quintptr)stats.code, QT_POINTER_SIZE * 2, 16, QChar{ '0' }); case 2: if (totalTime == 0) { return QLatin1String { "0%" }; } else { return QString{ "%1%" }.arg(100.0 * stats.profileData.time.load() / totalTime, 0, 'f', 2); } case 3: return QString { "%1" }.arg(stats.profileData.time.load()); case 4: return QString { "%1" }.arg(stats.profileData.count.load()); case 5: { auto count = stats.profileData.count.load(); auto time = stats.profileData.time.load(); return QString { "%1" }.arg(count ? (time + count / 2) / count : 0); } } } else if (role == SortRole) { switch (index.column()) { case 0: return stats.address; case 1: return reinterpret_cast(stats.code); case 2: case 3: return static_cast(stats.profileData.time.load()); case 4: return static_cast(stats.profileData.count.load()); case 5: { auto count = stats.profileData.count.load(); auto time = stats.profileData.time.load(); return count ? (time + count / 2) / count : 0ull; } } } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant { }; } if (orientation == Qt::Horizontal) { if (section < ColumnCount) { return ColumnNames[section]; } } return QVariant { }; } private slots: void debugDataChanged() { auto newSize = static_cast(mDebugData->jitStats().compiledBlocks.size()); if (newSize < mPreviousSize) { beginRemoveRows({}, newSize, mPreviousSize - 1); endRemoveRows(); } else if (newSize > mPreviousSize) { beginInsertRows({}, mPreviousSize, newSize - 1); endInsertRows(); } dataChanged(index(0, 0), index(newSize, ColumnCount)); mPreviousSize = newSize; } private: DebugData *mDebugData = nullptr; int mPreviousSize = 0; }; ================================================ FILE: src/decaf-qt/src/debugger/jitprofilingwindow.cpp ================================================ #include "jitprofilingwindow.h" #include "ui_jitprofilingwindow.h" #include "debugdata.h" #include "jitprofilingmodel.h" #include #include JitProfilingWindow::JitProfilingWindow(QWidget *parent) : QWidget(parent), ui(new Ui::JitProfilingWindow { }) { ui->setupUi(this); } JitProfilingWindow::~JitProfilingWindow() { delete ui; } void JitProfilingWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, [this]() { const auto &stats = mDebugData->jitStats(); ui->labelJitCodeSize->setText(QString{ "%1 mb" }.arg(stats.usedCodeCacheSize / 1.0e6, 0, 'f', 2)); ui->labelJitDataSize->setText(QString{ "%1 mb" }.arg(stats.usedDataCacheSize / 1.0e6, 0, 'f', 2)); }); mJitProfilingModel = new JitProfilingModel { this }; mJitProfilingModel->setDebugData(debugData); mSortModel = new QSortFilterProxyModel { this }; mSortModel->setSourceModel(mJitProfilingModel); mSortModel->setSortRole(JitProfilingModel::SortRole); ui->tableView->setModel(mSortModel); auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1; ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2); ui->tableView->setSortingEnabled(true); ui->tableView->update(); connect(mDebugData, &DebugData::entry, [&]{ ui->tableView->sortByColumn(3, Qt::AscendingOrder); ui->tableView->resizeColumnToContents(0); ui->tableView->resizeColumnToContents(1); ui->tableView->resizeColumnToContents(2); ui->tableView->resizeColumnToContents(3); ui->tableView->resizeColumnToContents(4); ui->tableView->resizeColumnToContents(5); }); } void JitProfilingWindow::clearProfileData() { cpu::jit::resetProfileStats(); } void JitProfilingWindow::setProfilingEnabled(bool enabled) { if (enabled) { mDebugData->setJitProfilingState( ui->checkBoxCore0->isChecked(), ui->checkBoxCore1->isChecked(), ui->checkBoxCore2->isChecked()); ui->pushButtonStartStop->setText(tr("Stop")); } else { mDebugData->setJitProfilingState(false, false, false); ui->pushButtonStartStop->setText(tr("Start")); } } void JitProfilingWindow::setCore0Mask(bool enabled) { if (ui->pushButtonStartStop->isChecked()) { mDebugData->setJitProfilingState( ui->checkBoxCore0->isChecked(), ui->checkBoxCore1->isChecked(), ui->checkBoxCore2->isChecked()); } } void JitProfilingWindow::setCore1Mask(bool enabled) { if (ui->pushButtonStartStop->isChecked()) { mDebugData->setJitProfilingState( ui->checkBoxCore0->isChecked(), ui->checkBoxCore1->isChecked(), ui->checkBoxCore2->isChecked()); } } void JitProfilingWindow::setCore2Mask(bool enabled) { if (ui->pushButtonStartStop->isChecked()) { mDebugData->setJitProfilingState( ui->checkBoxCore0->isChecked(), ui->checkBoxCore1->isChecked(), ui->checkBoxCore2->isChecked()); } } void JitProfilingWindow::tableViewDoubleClicked(QModelIndex index) { auto profileIndex = mSortModel->mapToSource(index); auto startAddress = mJitProfilingModel->data(profileIndex.siblingAtColumn(0), JitProfilingModel::SortRole); if (!startAddress.isValid()) { return; } navigateToTextAddress(startAddress.value()); } ================================================ FILE: src/decaf-qt/src/debugger/jitprofilingwindow.h ================================================ #pragma once #include namespace Ui { class JitProfilingWindow; } class DebugData; class JitProfilingModel; class QSortFilterProxyModel; class JitProfilingWindow : public QWidget { Q_OBJECT public: JitProfilingWindow(QWidget *parent = nullptr); ~JitProfilingWindow(); void setDebugData(DebugData *debugData); signals: void navigateToTextAddress(uint32_t address); public slots: void clearProfileData(); void setProfilingEnabled(bool enabled); void setCore0Mask(bool enabled); void setCore1Mask(bool enabled); void setCore2Mask(bool enabled); void tableViewDoubleClicked(QModelIndex index); private: Ui::JitProfilingWindow *ui; DebugData *mDebugData = nullptr; JitProfilingModel *mJitProfilingModel = nullptr; QSortFilterProxyModel *mSortModel = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/memorywidget.cpp ================================================ #include "memorywidget.h" #include #include #include #include #include #include MemoryWidget::MemoryWidget(QWidget *parent) : AddressTextDocumentWidget(parent) { setBytesPerLine(16); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); mTextFormats.lineAddress = QTextCharFormat { }; mTextFormats.lineAddress.setForeground(Qt::darkGray); mTextFormats.hexData = QTextCharFormat { }; mTextFormats.hexData.setForeground(QColor { 0, 0x80, 0x40 }); mTextFormats.textData = QTextCharFormat { }; mTextFormats.textData.setForeground(Qt::blue); mTextFormats.punctuation = QTextCharFormat { }; mTextFormats.punctuation.setForeground(Qt::darkBlue); mCopyAction = new QAction(tr("&Copy"), this); mCopyHexAction = new QAction(tr("Copy data as hex"), this); mCopyTextAction = new QAction(tr("Copy data as text"), this); connect(mCopyAction, &QAction::triggered, this, &MemoryWidget::copySelection); connect(mCopyHexAction, &QAction::triggered, this, &MemoryWidget::copySelectionAsHex); connect(mCopyTextAction, &QAction::triggered, this, &MemoryWidget::copySelectionAsText); } void MemoryWidget::setBytesPerLine(int bytesPerLine) { mAutoBytesPerLine = false; AddressTextDocumentWidget::setBytesPerLine(bytesPerLine); } void MemoryWidget::setAutoBytesPerLine(bool enabled) { mAutoBytesPerLine = enabled; updateAutoBytesPerLine(); } bool MemoryWidget::autoBytesPerLine() { return mAutoBytesPerLine; } void MemoryWidget::updateAutoBytesPerLine() { auto maxWidth = viewport()->width() - documentMargin() * 2; auto charactersPerByte = 0; auto charactersTotalOffset = 0; auto charWidth = characterWidth(); if (mVisibleColumns.lineAddress) { maxWidth -= 8 * charWidth; maxWidth -= mPunctuation.afterLineAddress.size() * charWidth; } if (mVisibleColumns.hexData) { charactersTotalOffset -= 1; charactersPerByte += 3; maxWidth -= mPunctuation.afterHexData.size() * charWidth; } if (mVisibleColumns.textData) { charactersPerByte++; } auto maxCharacters = maxWidth / characterWidth(); auto bytesPerLine = (maxCharacters - charactersTotalOffset) / charactersPerByte; AddressTextDocumentWidget::setBytesPerLine(std::max(4, bytesPerLine)); } void MemoryWidget::resizeEvent(QResizeEvent *e) { if (mAutoBytesPerLine) { updateAutoBytesPerLine(); } AddressTextDocumentWidget::resizeEvent(e); } void MemoryWidget::copySelectionAsHex() { if (mSelectionBegin != mSelectionEnd) { auto startAddress = std::min(mSelectionBegin.address, mSelectionEnd.address); auto endAddress = std::max(mSelectionBegin.address, mSelectionEnd.address); auto count = endAddress - startAddress; // Limit copy to 4 kb if (count <= 4 * 1024) { auto buffer = QByteArray { }; buffer.resize(count); buffer.resize(static_cast( decaf::debug::readMemory(startAddress, buffer.data(), buffer.size()))); qApp->clipboard()->setText(QString { buffer.toHex() }); } } } void MemoryWidget::copySelectionAsText() { if (mSelectionBegin != mSelectionEnd) { auto startAddress = std::min(mSelectionBegin.address, mSelectionEnd.address); auto endAddress = std::max(mSelectionBegin.address, mSelectionEnd.address); auto count = endAddress - startAddress; // Limit copy to 4 kb if (count <= 4 * 1024) { auto buffer = QByteArray { }; buffer.resize(count); buffer.resize(static_cast( decaf::debug::readMemory(startAddress, buffer.data(), buffer.size()))); auto textString = QString { }; for (auto c : buffer) { if (std::isprint(c)) { textString += c; } else { textString += '?'; } } qApp->clipboard()->setText(textString); } } } void MemoryWidget::keyPressEvent(QKeyEvent *e) { return AddressTextDocumentWidget::keyPressEvent(e); } void MemoryWidget::showContextMenu(QMouseEvent *e) { auto menu = QMenu { this }; menu.addAction(mCopyAction); menu.addAction(mCopyHexAction); menu.addAction(mCopyTextAction); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) menu.exec(e->globalPosition().toPoint()); #else menu.exec(e->globalPos()); #endif } MemoryWidget::MemoryCursor MemoryWidget::convertCursor(const DocumentCursor documentCursor) { auto memoryCursor = MemoryCursor { }; auto bytesPerLine = getBytesPerLine(); auto endAddress = getEndAddress(); // Convert document cursor into a non-document dependent memory cursor if (mVisibleColumns.hexData) { if (documentCursor.cursorPosition < mHexDataStartPosition) { memoryCursor.address = documentCursor.address; memoryCursor.type = MemoryCursor::Hex; memoryCursor.nibble = 0; } else if (documentCursor.cursorPosition >= mHexDataStartPosition && documentCursor.cursorPosition < mHexDataEndPosition) { auto byte = (documentCursor.cursorPosition - mHexDataStartPosition) / 3; if (byte >= bytesPerLine) { byte = bytesPerLine - 1; memoryCursor.nibble = 2; } else { memoryCursor.nibble = (documentCursor.cursorPosition - mHexDataStartPosition) % 3; } memoryCursor.address = documentCursor.address + byte; memoryCursor.type = MemoryCursor::Hex; } else if (documentCursor.cursorPosition >= mHexDataEndPosition && documentCursor.cursorPosition < mTextDataStartPosition) { memoryCursor.address = documentCursor.address + (bytesPerLine - 1); memoryCursor.type = MemoryCursor::Hex; memoryCursor.nibble = 2; } } if (mVisibleColumns.textData) { if (documentCursor.cursorPosition >= mTextDataStartPosition) { auto byte = documentCursor.cursorPosition - mTextDataStartPosition; if (byte >= bytesPerLine) { byte = bytesPerLine - 1; memoryCursor.nibble = 2; } if (static_cast(endAddress) - static_cast(documentCursor.address) < byte) { memoryCursor.address = endAddress; memoryCursor.nibble = 2; } else { memoryCursor.address = documentCursor.address + byte; } memoryCursor.type = MemoryCursor::Text; } } return memoryCursor; } MemoryWidget::DocumentCursor MemoryWidget::convertCursor(const MemoryWidget::MemoryCursor memoryCursor) { auto documentCursor = DocumentCursor { }; auto bytesPerLine = getBytesPerLine(); auto startAddress = getStartAddress(); auto lineStartAddress = startAddress + ((memoryCursor.address - startAddress) / bytesPerLine) * bytesPerLine; auto lineByte = memoryCursor.address - lineStartAddress; documentCursor.address = lineStartAddress; if (memoryCursor.type == MemoryCursor::Hex) { documentCursor.cursorPosition = mHexDataStartPosition + 3 * lineByte + memoryCursor.nibble; } else if (memoryCursor.type == MemoryCursor::Text) { documentCursor.cursorPosition = mTextDataStartPosition + lineByte; if (memoryCursor.nibble == 2) { documentCursor.cursorPosition++; } } return documentCursor; } MemoryWidget::MemoryCursor MemoryWidget::moveMemoryCursor(MemoryCursor memoryCursor, int byteOffset) { auto startAddress = getStartAddress(); auto endAddress = getEndAddress(); if (byteOffset < 0 && mCursor.address < startAddress - byteOffset) { memoryCursor.address = startAddress; memoryCursor.nibble = 0; } else if (byteOffset > 0 && mCursor.address > endAddress - byteOffset) { memoryCursor.address = endAddress; memoryCursor.nibble = 2; } else { memoryCursor.address += byteOffset; } return memoryCursor; } MemoryWidget::DocumentCursor MemoryWidget::cursorFromAddress(VirtualAddress address) { auto memoryCursor = MemoryCursor { }; if (mVisibleColumns.hexData) { memoryCursor.type = MemoryCursor::Hex; } else if (mVisibleColumns.textData) { memoryCursor.type = MemoryCursor::Text; } memoryCursor.address = address; return convertCursor(memoryCursor); } MemoryWidget::VirtualAddress MemoryWidget::cursorToAddress(DocumentCursor cursor) { return convertCursor(cursor).address; } MemoryWidget::DocumentCursor MemoryWidget::getDocumentCursor() { return convertCursor(mCursor); } MemoryWidget::DocumentCursor MemoryWidget::getDocumentSelectionBegin() { return convertCursor(mSelectionBegin); } MemoryWidget::DocumentCursor MemoryWidget::getDocumentSelectionEnd() { return convertCursor(mSelectionEnd); } void MemoryWidget::setDocumentCursor(DocumentCursor cursor) { mCursor = convertCursor(cursor); } void MemoryWidget::setDocumentSelectionBegin(DocumentCursor cursor) { mSelectionBegin = convertCursor(cursor); } void MemoryWidget::setDocumentSelectionEnd(DocumentCursor cursor) { mSelectionEnd = convertCursor(cursor); } static constexpr inline QChar toHexUpper(uint value) { return "0123456789ABCDEF"[value & 0xF]; } void MemoryWidget::updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress, VirtualAddress lastLineAddress, int bytesPerLine, bool forDisplay) { // TODO: Cache line cursor position for Address, Hex, Text auto buffer = std::vector { }; buffer.resize(bytesPerLine, 0); auto hexString = QString { }; hexString.resize(bytesPerLine * 3 - 1, ' '); auto textString = QString { }; textString.resize(bytesPerLine, '.'); auto pageSize = decaf::debug::getMemoryPageSize(); auto pageMask = ~(pageSize - 1); auto beginAddress = static_cast(firstLineAddress) ; auto endAddress = static_cast(lastLineAddress) + bytesPerLine; auto address = beginAddress; auto currentPage = beginAddress & pageMask; auto nextPage = currentPage + pageSize; auto currentPageValid = decaf::debug::isValidVirtualAddress(static_cast(currentPage)); auto nextPageValid = decaf::debug::isValidVirtualAddress(static_cast(nextPage)); // This code assumes you could only ever have 2 pages on a single line assert(pageSize > bytesPerLine); for (auto address = beginAddress; address < endAddress; address += bytesPerLine) { auto firstValidByte = -1; auto lastValidByte = -1; auto lineCrossesPage = (nextPage - address) < bytesPerLine; if (address != beginAddress) { cursor.insertBlock(); } if (mVisibleColumns.lineAddress) { mAddressStartPosition = cursor.positionInBlock(); cursor.insertText( QString { "%1" }.arg(address, 8, 16, QLatin1Char { '0' }), mTextFormats.lineAddress); mAddressEndPosition = cursor.positionInBlock(); cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation); } if (currentPageValid) { // Current page is valid, so first byte is valid firstValidByte = 0; } else if (lineCrossesPage && nextPageValid) { // Current page invalid, next page valid firstValidByte = static_cast(nextPage - address); } if (!lineCrossesPage) { if (currentPageValid) { // Whole line is on current valid page lastValidByte = bytesPerLine; } } else { if (nextPageValid) { // Next page is valid so assume we can finish the current line lastValidByte = bytesPerLine; } else if (currentPageValid) { // Current page is valid, next page is invalid lastValidByte = std::min(static_cast(nextPage - address), bytesPerLine); } } if (lineCrossesPage) { currentPage = nextPage; currentPageValid = nextPageValid; nextPage = currentPage + pageSize; nextPageValid = decaf::debug::isValidVirtualAddress(static_cast(nextPage)); } if (firstValidByte < 0 || lastValidByte < 0) { // Whole line is invalid hexString.fill(' '); textString.fill(' '); } else { decaf::debug::readMemory(address + firstValidByte, buffer.data() + firstValidByte, lastValidByte - firstValidByte); for (auto i = 0; i < firstValidByte; ++i) { hexString[i * 3 + 0] = ' '; hexString[i * 3 + 1] = ' '; textString[i] = ' '; } for (auto i = firstValidByte; i < lastValidByte; ++i) { hexString[i * 3 + 0] = toHexUpper(buffer[i] >> 4); hexString[i * 3 + 1] = toHexUpper(buffer[i] & 0xf); if (std::isprint(buffer[i])) { textString[i] = QChar { buffer[i] }; } else { textString[i] = '.'; } } for (auto i = lastValidByte; i < bytesPerLine; ++i) { hexString[i * 3 + 0] = ' '; hexString[i * 3 + 1] = ' '; textString[i] = ' '; } } if (mVisibleColumns.hexData) { mHexDataStartPosition = cursor.positionInBlock(); cursor.insertText(hexString, mTextFormats.hexData); mHexDataEndPosition = cursor.positionInBlock(); cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation); } if (mVisibleColumns.textData) { mTextDataStartPosition = cursor.positionInBlock(); cursor.insertText(textString, mTextFormats.textData); mTextDataEndPosition = cursor.positionInBlock(); } } } ================================================ FILE: src/decaf-qt/src/debugger/memorywidget.h ================================================ #pragma once #include "addresstextdocumentwidget.h" #include #include #include #include #include #include #include class QAction; class QTextDocument; class MemoryWidget : public AddressTextDocumentWidget { using VirtualAddress = uint32_t; struct MemoryCursor { enum Type { Invalid, Hex, Text, }; Type type = Invalid; VirtualAddress address = 0; int nibble = 0; bool operator !=(const MemoryCursor &rhs) const { return type != rhs.type || address != rhs.address || nibble != rhs.nibble; } bool operator <(const MemoryCursor &rhs) const { if (address > rhs.address) { return false; } else if (address < rhs.address) { return true; } else { return nibble < rhs.nibble; } } }; public: MemoryWidget(QWidget *parent = nullptr); void setBytesPerLine(int bytesPerLine); void setAutoBytesPerLine(bool enabled); bool autoBytesPerLine(); void copySelectionAsHex(); void copySelectionAsText(); private: void updateAutoBytesPerLine(); void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; MemoryCursor convertCursor(const DocumentCursor documentCursor); DocumentCursor convertCursor(const MemoryCursor memoryCursor); MemoryCursor moveMemoryCursor(MemoryCursor memoryCursor, int byteOffset); DocumentCursor cursorFromAddress(VirtualAddress address) override; VirtualAddress cursorToAddress(DocumentCursor cursor) override; DocumentCursor getDocumentCursor() override; DocumentCursor getDocumentSelectionBegin() override; DocumentCursor getDocumentSelectionEnd() override; void setDocumentCursor(DocumentCursor cursor) override; void setDocumentSelectionBegin(DocumentCursor cursor) override; void setDocumentSelectionEnd(DocumentCursor cursor) override; void showContextMenu(QMouseEvent *e) override; void updateTextDocument(QTextCursor cursor, VirtualAddress startAddress, VirtualAddress endAddress, int bytesPerLine, bool forDisplay) override; private: bool mAutoBytesPerLine = false; MemoryCursor mCursor = { }; MemoryCursor mSelectionBegin = { }; MemoryCursor mSelectionEnd = { }; int mAddressStartPosition = 0; int mAddressEndPosition = 0; int mHexDataStartPosition = 0; int mHexDataEndPosition = 0; int mTextDataStartPosition = 0; int mTextDataEndPosition = 0; struct { QTextCharFormat lineAddress; QTextCharFormat punctuation; QTextCharFormat hexData; QTextCharFormat textData; } mTextFormats; struct { QString afterLineAddress = " "; QString afterHexData = " "; } mPunctuation; struct { bool lineAddress = true; bool hexData = true; bool textData = true; } mVisibleColumns; QAction *mCopyAction; QAction *mCopyHexAction; QAction *mCopyTextAction; }; ================================================ FILE: src/decaf-qt/src/debugger/memorywindow.cpp ================================================ #include "debuggershortcuts.h" #include "memorywindow.h" #include "memorywidget.h" #include "ui_memorywindow.h" MemoryWindow::MemoryWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent) : QWidget(parent), ui(new Ui::MemoryWindow { }) { ui->setupUi(this); ui->widget->setAddressRange(0, 0xFFFFFFFF); ui->widget->navigateToAddress(0x02000000); ui->widget->setBytesPerLine(16); ui->widget->addAction(debuggerShortcuts->navigateBackward); ui->widget->addAction(debuggerShortcuts->navigateForward); ui->widget->addAction(debuggerShortcuts->navigateToAddress); } MemoryWindow::~MemoryWindow() { delete ui; } void MemoryWindow::navigateToAddress(uint32_t address) { ui->widget->navigateToAddress(address); } void MemoryWindow::navigateForward() { ui->widget->navigateForward(); } void MemoryWindow::navigateBackward() { ui->widget->navigateBackward(); } void MemoryWindow::addressChanged() { auto text = ui->lineEditAddress->text(); auto value = 0ull; auto valid = false; if (text.startsWith("0x")) { value = text.toULongLong(&valid, 0); } else { value = text.toULongLong(&valid, 16); } if (valid) { ui->widget->navigateToAddress(value); ui->widget->setFocus(); } } void MemoryWindow::columnsChanged() { if (ui->comboBoxColumns->currentIndex() == 0) { ui->widget->setAutoBytesPerLine(true); } else { auto valid = false; auto value = ui->comboBoxColumns->currentText().toInt(&valid, 0); if (valid) { ui->widget->setBytesPerLine(value); } } } ================================================ FILE: src/decaf-qt/src/debugger/memorywindow.h ================================================ #pragma once #include namespace Ui { class MemoryWindow; } struct DebuggerShortcuts; class MemoryWindow : public QWidget { Q_OBJECT public: MemoryWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent = nullptr); ~MemoryWindow(); public slots: void navigateToAddress(uint32_t address); void navigateForward(); void navigateBackward(); protected slots: void addressChanged(); void columnsChanged(); private: Ui::MemoryWindow *ui; }; ================================================ FILE: src/decaf-qt/src/debugger/registerswindow.cpp ================================================ #include "registerswindow.h" #include #include #include #include #include RegistersWindow::RegistersWindow(QWidget *parent) : QPlainTextEdit(parent) { // Set to fixed width font auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont); if (!(font.styleHint() & QFont::Monospace)) { font.setFamily("Monospace"); font.setStyleHint(QFont::TypeWriter); } setFont(font); setReadOnly(true); setWordWrapMode(QTextOption::NoWrap); mTextFormats.registerName = QTextCharFormat { }; mTextFormats.registerName.setForeground(Qt::darkBlue); mTextFormats.punctuation = QTextCharFormat { }; mTextFormats.punctuation.setForeground(Qt::darkBlue); mTextFormats.changedValue = QTextCharFormat { }; mTextFormats.changedValue.setForeground(Qt::red); generateDocument(); verticalScrollBar()->triggerAction(QScrollBar::SliderToMinimum); horizontalScrollBar()->triggerAction(QScrollBar::SliderToMinimum); } void RegistersWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &RegistersWindow::debugDataChanged); } template static QString toHexString(T value, int width) { return QString { "%1" }.arg(value, width / 4, 16, QLatin1Char { '0' }).toUpper(); } void RegistersWindow::generateDocument() { auto cursor = QTextCursor { document() }; cursor.beginEditBlock(); document()->clear(); auto registerNameWidth = 10; for (auto i = 0; i < 32; ++i) { if (i != 0) { cursor.insertBlock(); } cursor.insertText(QString { "R%1" }.arg(i, 2, 10, QLatin1Char { '0' }), mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.gpr[i] = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 32), mTextFormats.value); mRegisterCursors.gpr[i].end = cursor.positionInBlock(); } cursor.insertBlock(); cursor.insertBlock(); cursor.insertText(QString { "LR" }, mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.lr = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 32), mTextFormats.value); mRegisterCursors.lr.end = cursor.positionInBlock(); cursor.insertBlock(); cursor.insertText(QString { "CTR" }, mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.ctr = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 32), mTextFormats.value); mRegisterCursors.ctr.end = cursor.positionInBlock(); cursor.insertBlock(); cursor.insertText(QString { "XER" }, mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.xer = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 32), mTextFormats.value); mRegisterCursors.xer.end = cursor.positionInBlock(); cursor.insertBlock(); cursor.insertText(QString { "MSR" }, mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.msr = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 32), mTextFormats.value); mRegisterCursors.msr.end = cursor.positionInBlock(); cursor.insertBlock(); cursor.insertBlock(); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); cursor.insertText(QString { "O Z + -" }); for (auto i = 0; i < 8; ++i) { cursor.insertBlock(); cursor.insertText(QString { "CRF%1" }.arg(i), mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.crf[i] = { cursor.block(), cursor.positionInBlock() }; cursor.insertText("0 0 0 0", mTextFormats.value); mRegisterCursors.crf[i].end = cursor.positionInBlock(); } cursor.insertBlock(); for (auto i = 0; i < 32; ++i) { cursor.insertBlock(); cursor.insertText(QString { "F%1" }.arg(i, 2, 10, QLatin1Char { '0' }), mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.fpr[i] = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 64) + QString { " 0" }, mTextFormats.value); mRegisterCursors.fpr[i].end = cursor.positionInBlock(); } for (auto i = 0; i < 32; ++i) { cursor.insertBlock(); cursor.insertText(QString { "P%1" }.arg(i, 2, 10, QLatin1Char { '0' }), mTextFormats.registerName); cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock())); mRegisterCursors.psf[i] = { cursor.block(), cursor.positionInBlock() }; cursor.insertText(toHexString(0, 64) + QString { " 0" }, mTextFormats.value); mRegisterCursors.psf[i].end = cursor.positionInBlock(); } cursor.endEditBlock(); } void RegistersWindow::debugDataChanged() { auto thread = mDebugData->activeThread(); if (!thread) { return; } auto clearChangedHighlight = !mDebugData->paused() || mLastThreadState.nia != thread->nia; auto cursor = QTextCursor { document() }; cursor.beginEditBlock(); auto updateRegisterText = [&](RegisterCursor ®, auto lastValue, auto currentValue, auto toString) { cursor.setPosition(reg.block.position() + reg.start); cursor.setPosition(reg.block.position() + reg.end, QTextCursor::KeepAnchor); auto wasChangedValue = cursor.charFormat().foreground().color() == mTextFormats.changedValue.foreground().color(); if (lastValue != currentValue) { cursor.insertText(toString(currentValue), mTextFormats.changedValue); reg.end = cursor.selectionEnd() - reg.block.position(); } else if (clearChangedHighlight) { if (cursor.charFormat().foreground().color() == mTextFormats.changedValue.foreground().color()) { cursor.setCharFormat(mTextFormats.value); } } }; for (auto i = 0u; i < 32; ++i) { updateRegisterText(mRegisterCursors.gpr[i], mLastThreadState.gpr[i], thread->gpr[i], [](auto value) { return toHexString(value, 32); }); } updateRegisterText(mRegisterCursors.lr, mLastThreadState.lr, thread->lr, [](auto value) { return toHexString(value, 32); }); updateRegisterText(mRegisterCursors.ctr, mLastThreadState.ctr, thread->ctr, [](auto value) { return toHexString(value, 32); }); updateRegisterText(mRegisterCursors.xer, mLastThreadState.xer, thread->xer, [](auto value) { return toHexString(value, 32); }); updateRegisterText(mRegisterCursors.msr, mLastThreadState.msr, thread->msr, [](auto value) { return toHexString(value, 32); }); for (auto i = 0; i < 8; ++i) { auto lastValue = (mLastThreadState.cr >> ((7 - i) * 4)) & 0xF; auto value = (thread->cr >> ((7 - i) * 4)) & 0xF; updateRegisterText(mRegisterCursors.crf[i], lastValue, value, [](auto value) { return QString { "%1 %2 %3 %4" } .arg((value >> 0) & 1) .arg((value >> 1) & 1) .arg((value >> 2) & 1) .arg((value >> 3) & 1); }); } for (auto i = 0u; i < 32; ++i) { updateRegisterText(mRegisterCursors.fpr[i], mLastThreadState.fpr[i], thread->fpr[i], [](auto value) { return QString { "%1 %2" } .arg(toHexString(*reinterpret_cast(&value), 64).toUpper()) .arg(value); }); } for (auto i = 0u; i < 32; ++i) { updateRegisterText(mRegisterCursors.psf[i], mLastThreadState.ps1[i], thread->ps1[i], [](auto value) { return QString { "%1 %2" } .arg(toHexString(*reinterpret_cast(&value), 64).toUpper()) .arg(value); }); } cursor.endEditBlock(); mLastThreadState = *thread; } ================================================ FILE: src/decaf-qt/src/debugger/registerswindow.h ================================================ #pragma once #include #include #include #include #include #include "debugdata.h" class RegistersWindow : public QPlainTextEdit { Q_OBJECT struct RegisterCursor { QTextBlock block; int start; int end; }; public: RegistersWindow(QWidget *parent = nullptr); void setDebugData(DebugData *debugData); private slots: void debugDataChanged(); private: void generateDocument(); private: DebugData *mDebugData = nullptr; decaf::debug::CafeThread mLastThreadState = { }; struct RegisterCursors { std::array gpr; RegisterCursor lr; RegisterCursor ctr; RegisterCursor xer; RegisterCursor msr; std::array crf; std::array fpr; std::array psf; } mRegisterCursors; struct { QTextCharFormat registerName; QTextCharFormat punctuation; QTextCharFormat value; QTextCharFormat changedValue; } mTextFormats; }; ================================================ FILE: src/decaf-qt/src/debugger/segmentsmodel.h ================================================ #pragma once #include #include #include "debugdata.h" class SegmentsModel : public QAbstractTableModel { Q_OBJECT static constexpr const char *ColumnNames[] = { "Name", "Start", "End", "R", "W", "X", "Align", }; static constexpr int ColumnCount = static_cast(sizeof(ColumnNames) / sizeof(ColumnNames[0])); using CafeMemorySegment = DebugData::CafeMemorySegment; public: enum UserRoles { AddressRole = Qt::UserRole, ExecuteRole, }; SegmentsModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } void setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &SegmentsModel::debugDataChanged); } int rowCount(const QModelIndex &parent) const override { if (!mDebugData) { return 0; } return static_cast(mDebugData->segments().size()); } int columnCount(const QModelIndex &parent) const override { return ColumnCount; } QVariant data(const QModelIndex &index, int role) const override { if (!mDebugData || !index.isValid()) { return QVariant{ }; } const auto &segments = mDebugData->segments(); if (index.row() >= segments.size() || index.row() < 0) { return QVariant { }; } if (role == Qt::DisplayRole) { const auto &segment = segments[index.row()]; switch (index.column()) { case 0: return QString::fromStdString(segment.name); case 1: return QString("%1").arg(static_cast(segment.address), 8, 16, QChar { '0' }); case 2: return QString("%1").arg(static_cast(segment.address + segment.size), 8, 16, QChar { '0' }); case 3: return segment.read ? "R" : "."; case 4: return segment.write ? "W" : "."; case 5: return segment.execute ? "X" : "."; case 6: return QString("0x%1").arg(static_cast(segment.align), 0, 16, QChar { '0' }); } } else if (role == AddressRole) { return segments[index.row()].address; } else if (role == ExecuteRole) { return segments[index.row()].execute; } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant{ }; } if (orientation == Qt::Horizontal) { if (section < ColumnCount) { return ColumnNames[section]; } } return QVariant { }; } private slots: void debugDataChanged() { auto newSize = static_cast(mDebugData->segments().size()); if (newSize < mPreviousSize) { beginRemoveRows({}, newSize, mPreviousSize - 1); endRemoveRows(); } else if (newSize > mPreviousSize) { beginInsertRows({}, mPreviousSize, newSize - 1); endInsertRows(); } mPreviousSize = newSize; } private: DebugData *mDebugData = nullptr; int mPreviousSize = 0; }; ================================================ FILE: src/decaf-qt/src/debugger/segmentswindow.cpp ================================================ #include "segmentswindow.h" #include "ui_segmentswindow.h" #include "debugdata.h" #include "segmentsmodel.h" SegmentsWindow::SegmentsWindow(QWidget *parent) : QWidget(parent), ui(new Ui::SegmentsWindow { }) { ui->setupUi(this); } SegmentsWindow::~SegmentsWindow() { delete ui; } void SegmentsWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; mSegmentsModel = new SegmentsModel { this }; mSegmentsModel->setDebugData(debugData); ui->tableView->setModel(mSegmentsModel); auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1; ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2); ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(6, QHeaderView::ResizeToContents); ui->tableView->update(); } void SegmentsWindow::segmentsViewDoubleClicked(const QModelIndex &index) { auto address = mSegmentsModel->data(index, SegmentsModel::AddressRole); auto executable = mSegmentsModel->data(index, SegmentsModel::ExecuteRole); if (!address.isValid() || !executable.isValid()) { return; } if (executable.toBool()) { navigateToTextAddress(address.value()); } else { navigateToDataAddress(address.value()); } } ================================================ FILE: src/decaf-qt/src/debugger/segmentswindow.h ================================================ #pragma once #include #include namespace Ui { class SegmentsWindow; } class DebugData; class SegmentsModel; class SegmentsWindow : public QWidget { Q_OBJECT public: explicit SegmentsWindow(QWidget *parent = nullptr); ~SegmentsWindow(); void setDebugData(DebugData *debugData); signals: void navigateToDataAddress(uint32_t address); void navigateToTextAddress(uint32_t address); protected slots: void segmentsViewDoubleClicked(const QModelIndex &index); private: Ui::SegmentsWindow *ui; DebugData *mDebugData = nullptr; SegmentsModel *mSegmentsModel = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/stackwidget.cpp ================================================ #include "stackwidget.h" #include #include #include #include #include #include StackWidget::StackWidget(QWidget *parent) : AddressTextDocumentWidget(parent) { setBytesPerLine(4); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); mTextFormats.invalid = QTextCharFormat { }; mTextFormats.invalid.setForeground(Qt::gray); mTextFormats.lineAddress = QTextCharFormat { }; mTextFormats.lineAddress.setForeground(Qt::black); mTextFormats.data = QTextCharFormat { }; mTextFormats.data.setForeground(Qt::gray); mTextFormats.symbolName = QTextCharFormat { }; mTextFormats.symbolName.setForeground(Qt::blue); mTextFormats.referencedAscii = QTextCharFormat { }; mTextFormats.referencedAscii.setForeground(Qt::gray); mTextFormats.currentLine = QTextCharFormat { }; mTextFormats.currentLine.setBackground(QColor { Qt::green }.lighter()); mTextFormats.stackOutline = Qt::darkBlue; mTextFormats.backchainOutline = Qt::black; } void StackWidget::setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &StackWidget::dataChanged); connect(mDebugData, &DebugData::activeThreadIndexChanged, this, &StackWidget::activeThreadChanged); } void StackWidget::dataChanged() { AddressTextDocumentWidget::updateTextDocument(true); viewport()->update(); } void StackWidget::activeThreadChanged() { if (auto activeThread = mDebugData->activeThread()) { setAddressRange(activeThread->stackEnd, activeThread->stackStart); navigateToAddress(activeThread->gpr[1]); AddressTextDocumentWidget::updateTextDocument(true); viewport()->update(); } } void StackWidget::navigateOperand() { auto address = getDocumentCursor().address; auto data = std::array { }; auto symbolNameBuffer = std::array { }; auto moduleNameBuffer = std::array { }; auto valid = decaf::debug::readMemory(address, data.data(), data.size()) == data.size(); auto value = qFromBigEndian(data.data()); if (value > 0 && value < 0x10000000) { auto symbolDistance = uint32_t{ 0 }; auto symbolFound = decaf::debug::findClosestSymbol( value, &symbolDistance, symbolNameBuffer.data(), static_cast(symbolNameBuffer.size()), moduleNameBuffer.data(), static_cast(moduleNameBuffer.size())); if (symbolFound && moduleNameBuffer[0]) { emit navigateToTextAddress(value); } } } void StackWidget::paintEvent(QPaintEvent *e) { AddressTextDocumentWidget::paintEvent(e); auto painter = QPainter { viewport() }; painter.translate(QPoint { -horizontalScrollBar()->value(), 0 }); if (mVisibleColumns.outline) { auto offset = documentMargin(); if (mVisibleColumns.lineAddress) { offset += 9 * characterWidth(); } auto startAddress = getStartAddress(); auto firstVisibleLine = verticalScrollBar()->value(); auto lastVisibleLine = firstVisibleLine + (verticalScrollBar()->pageStep() - 1); auto currentLineStartY = -1; auto currentLineIsBackchain = false; auto lineX1 = offset + (characterWidth() / 2); auto lineX2 = lineX1 + characterWidth(); auto lineY = documentMargin() + lineHeight() / 2; auto itr = mStackFrames.lower_bound(startAddress + firstVisibleLine * 4); if (itr != mStackFrames.end()) { for (auto i = firstVisibleLine; i < lastVisibleLine; ++i) { auto address = startAddress + i * 4; if (address >= itr->second.end) { ++itr; } if (itr == mStackFrames.end()) { break; } if (address == itr->second.start) { // Start of frame painter.setPen(mTextFormats.stackOutline); painter.drawLine(lineX1, lineY, lineX2, lineY); currentLineStartY = lineY; currentLineIsBackchain = false; } else if (address == itr->second.end - 12) { // End of frame painter.setPen(mTextFormats.stackOutline); painter.drawLine(lineX1, currentLineStartY, lineX1, lineY); painter.drawLine(lineX1, lineY, lineX2, lineY); currentLineStartY = lineY; currentLineIsBackchain = true; } else if (address == itr->second.end - 4) { // End of back chain painter.setPen(mTextFormats.backchainOutline); painter.drawLine(lineX1, currentLineStartY, lineX1, lineY); painter.drawLine(lineX1, lineY, lineX2, lineY); currentLineStartY = -1; } else if (address > itr->second.start && address < itr->second.end) { // Inside stack frame if (currentLineStartY == -1) { currentLineStartY = 0; currentLineIsBackchain = false; } } lineY += lineHeight(); } } if (currentLineStartY != -1) { painter.setPen(QPen { currentLineIsBackchain ? mTextFormats.backchainOutline : mTextFormats.stackOutline }); painter.drawLine(lineX1, currentLineStartY, lineX1, viewport()->height()); } } } void StackWidget::keyPressEvent(QKeyEvent *e) { auto handled = false; if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { navigateOperand(); handled = true; } if (!handled) { AddressTextDocumentWidget::keyPressEvent(e); } else { e->accept(); } } QVector StackWidget::getCustomSelections(QTextDocument *document) { if (auto activeThread = mDebugData->activeThread()) { mStackCurrentAddress = activeThread->gpr[1]; } mCustomSelectionsBuffer.clear(); // Essentially we want to select the line with mStackCurrentAddress on if (mStackCurrentAddress >= mStackFirstVisibleAddress && mStackCurrentAddress < mStackLastVisibleAddress) { auto line = (mStackCurrentAddress - mStackFirstVisibleAddress) / 4; auto block = document->findBlockByLineNumber(line); auto selection = QAbstractTextDocumentLayout::Selection{ }; selection.cursor = QTextCursor{ document->findBlockByLineNumber(line) }; selection.cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); selection.format = mTextFormats.currentLine; mCustomSelectionsBuffer.push_back(selection); } return mCustomSelectionsBuffer; } void StackWidget::updateStackFrames() { if (auto activeThread = mDebugData->activeThread()) { mStackCurrentAddress = activeThread->gpr[1]; } auto frame = StackFrame { }; auto address = mStackCurrentAddress + 8; auto startAddress = getEndAddress(); auto frameEnd = std::array { }; mStackFrames.clear(); while (true) { if (address < mStackCurrentAddress || !decaf::debug::isValidVirtualAddress(address)) { // If we somehow jump outside of our valid range, lets stop break; } decaf::debug::readMemory(address - 8, frameEnd.data(), 4); frame.start = address; frame.end = qFromBigEndian(frameEnd.data()) + 8; if (mStackFrames.find(frame.end) != mStackFrames.end()) { // Prevent an infinite loop with a buggy stack! break; } if (frame.end > startAddress) { // Stop when we go outside of the stack break; } mStackFrames.emplace(frame.end, frame); address = frame.end; } } void StackWidget::updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress, VirtualAddress lastLineAddress, int bytesPerLine, bool forDisplay) { auto data = std::array { }; auto symbolNameBuffer = std::array { }; auto moduleNameBuffer = std::array { }; if (forDisplay) { mStackFirstVisibleAddress = firstLineAddress; mStackLastVisibleAddress = lastLineAddress; updateStackFrames(); } for (auto address = static_cast(firstLineAddress); address <= lastLineAddress; address += bytesPerLine) { auto valid = decaf::debug::readMemory(address, data.data(), data.size()) == data.size(); if (address != firstLineAddress) { cursor.insertBlock(); } if (mVisibleColumns.lineAddress) { cursor.insertText( QString { "%1" }.arg(address, 8, 16, QLatin1Char{ '0' }), valid ? mTextFormats.lineAddress : mTextFormats.invalid); cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation); } if (mVisibleColumns.outline) { cursor.insertText(mPunctuation.outline, mTextFormats.punctuation); } if (mVisibleColumns.data) { cursor.insertText(QString { "%1 %2 %3 %4" } .arg(data[0], 2, 16, QLatin1Char { '0' }) .arg(data[1], 2, 16, QLatin1Char { '0' }) .arg(data[2], 2, 16, QLatin1Char { '0' }) .arg(data[3], 2, 16, QLatin1Char { '0' }) .toUpper(), mTextFormats.data); cursor.insertText(mPunctuation.afterData, mTextFormats.punctuation); } if (mVisibleColumns.dataReference) { auto value = qFromBigEndian(data.data()); if (value > 0 && value < 0x10000000) { auto symbolDistance = uint32_t { 0 }; auto symbolFound = decaf::debug::findClosestSymbol( value, &symbolDistance, symbolNameBuffer.data(), static_cast(symbolNameBuffer.size()), moduleNameBuffer.data(), static_cast(moduleNameBuffer.size())); if (symbolFound && moduleNameBuffer[0]) { cursor.insertText( QString { "%1::%2+0x%3" } .arg(moduleNameBuffer.data()) .arg(symbolNameBuffer.data()) .arg(symbolDistance, 0, 16), mTextFormats.symbolName); } } else if (value >= 0x10000000) { // TODO: Try read ASCII ? } } } } ================================================ FILE: src/decaf-qt/src/debugger/stackwidget.h ================================================ #pragma once #include "addresstextdocumentwidget.h" #include "debugdata.h" #include #include #include class QTextDocument; class StackWidget : public AddressTextDocumentWidget { Q_OBJECT using VirtualAddress = DebugData::VirtualAddress; struct StackFrame { VirtualAddress start; VirtualAddress end; }; public: StackWidget(QWidget *parent = nullptr); void setDebugData(DebugData *debugData); public slots: void navigateOperand(); signals: void navigateToTextAddress(uint32_t address); protected: void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; QVector getCustomSelections(QTextDocument *document) override; void updateStackFrames(); void updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress, VirtualAddress lastLineAddress, int bytePerLine, bool forDisplay) override; private slots: void activeThreadChanged(); void dataChanged(); private: DebugData *mDebugData; VirtualAddress mStackCurrentAddress; VirtualAddress mStackFirstVisibleAddress; VirtualAddress mStackLastVisibleAddress; QVector mCustomSelectionsBuffer; // Cached stack frames std::map mStackFrames; // Visible columns in disassembly struct { bool lineAddress = true; bool outline = true; bool data = true; bool dataReference = true; } mVisibleColumns; // Punctuation between columns struct { QString afterLineAddress = " "; QString outline = " "; QString afterData = " "; } mPunctuation; // Formatting for each data type struct { QTextCharFormat invalid; QTextCharFormat punctuation; QTextCharFormat lineAddress; QTextCharFormat currentLine; QTextCharFormat data; QTextCharFormat symbolName; QTextCharFormat referencedAscii; QColor stackOutline; QColor backchainOutline; } mTextFormats; }; ================================================ FILE: src/decaf-qt/src/debugger/stackwindow.cpp ================================================ #include "debuggershortcuts.h" #include "stackwindow.h" #include "ui_stackwindow.h" StackWindow::StackWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent) : QWidget(parent), ui(new Ui::StackWindow { }) { ui->setupUi(this); ui->stackWidget->addAction(debuggerShortcuts->navigateBackward); ui->stackWidget->addAction(debuggerShortcuts->navigateForward); ui->stackWidget->addAction(debuggerShortcuts->navigateToAddress); ui->stackWidget->addAction(debuggerShortcuts->navigateToOperand); connect(ui->stackWidget, &StackWidget::navigateToTextAddress, this, &StackWindow::navigateToTextAddress); } StackWindow::~StackWindow() { delete ui; } void StackWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; ui->stackWidget->setDebugData(mDebugData); connect(mDebugData, &DebugData::dataChanged, this, &StackWindow::updateStatus); connect(mDebugData, &DebugData::activeThreadIndexChanged, this, &StackWindow::updateStatus); } void StackWindow::navigateToAddress(uint32_t address) { ui->stackWidget->navigateToAddress(address); } void StackWindow::navigateForward() { ui->stackWidget->navigateForward(); } void StackWindow::navigateBackward() { ui->stackWidget->navigateBackward(); } void StackWindow::navigateOperand() { ui->stackWidget->navigateOperand(); } void StackWindow::updateStatus() { auto start = DebugData::VirtualAddress { 0 }; auto end = DebugData::VirtualAddress { 0 }; auto current = DebugData::VirtualAddress { 0 }; if (auto activeThread = mDebugData->activeThread()) { start = activeThread->stackStart; end = activeThread->stackEnd; current = activeThread->gpr[1]; } ui->labelStatus->setText( QString { "Showing stack from %1 to %2. Current %3" } .arg(start, 8, 16, QLatin1Char { '0' }) .arg(end, 8, 16, QLatin1Char { '0' }) .arg(current, 8, 16, QLatin1Char{ '0' })); } ================================================ FILE: src/decaf-qt/src/debugger/stackwindow.h ================================================ #pragma once #include #include "debugdata.h" namespace Ui { class StackWindow; } class QAbstractItemModel; struct DebuggerShortcuts; class StackWindow : public QWidget { Q_OBJECT public: StackWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent = nullptr); ~StackWindow(); void setDebugData(DebugData *debugData); signals: void navigateToTextAddress(uint32_t address); public slots: void navigateToAddress(uint32_t address); void navigateForward(); void navigateBackward(); void navigateOperand(); protected slots: void updateStatus(); private: Ui::StackWindow *ui; DebugData *mDebugData = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/threadsmodel.h ================================================ #pragma once #include #include "debugdata.h" class ThreadsModel : public QAbstractTableModel { Q_OBJECT static constexpr const char *ColumnNames[] = { "ID", "Name", "NIA", "State", "Priority", "Affinity", "Core", "Core Time" }; static constexpr int ColumnCount = static_cast(sizeof(ColumnNames) / sizeof(ColumnNames[0])); using CafeThread = DebugData::CafeThread; public: enum UserRoles { IdRole = Qt::UserRole, }; ThreadsModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } void setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &ThreadsModel::debugDataChanged); } int rowCount(const QModelIndex &parent) const override { if (!mDebugData) { return 0; } return static_cast(mDebugData->threads().size()); } int columnCount(const QModelIndex &parent) const override { return ColumnCount; } static QString getThreadStateName(CafeThread::ThreadState state) { switch (state) { case CafeThread::Inactive: return tr("Inactive"); case CafeThread::Ready: return tr("Ready"); case CafeThread::Running: return tr("Running"); case CafeThread::Waiting: return tr("Waiting"); case CafeThread::Moribund: return tr("Moribund"); default: return tr("Invalid"); } } QVariant data(const QModelIndex &index, int role) const override { if (!mDebugData || !index.isValid()) { return QVariant { }; } auto &threads = mDebugData->threads(); if (index.row() >= threads.size() || index.row() < 0) { return QVariant { }; } const auto &threadInfo = threads[index.row()]; if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return QString("%1").arg(threadInfo.id); case 1: return QString::fromStdString(threadInfo.name); case 2: return QString("%1").arg(static_cast(threadInfo.nia), 8, 16, QChar{ '0' }); case 3: return getThreadStateName(threadInfo.state); case 4: return QString("%1").arg(threadInfo.priority); case 5: return QString("%1").arg(threadInfo.affinity, 3, 2, QChar{ '0' }); case 6: if (threadInfo.coreId == -1) { return QString(); } else { return QString("%1").arg(threadInfo.coreId); } case 7: return QString("%1").arg(threadInfo.executionTime.count()); } } else if (role == IdRole) { return threadInfo.id; } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant { }; } if (orientation == Qt::Horizontal) { if (section < ColumnCount) { return ColumnNames[section]; } } return QVariant { }; } private slots: void debugDataChanged() { auto newSize = static_cast(mDebugData->threads().size()); if (newSize < mPreviousSize) { beginRemoveRows({}, newSize, mPreviousSize - 1); endRemoveRows(); } else if (newSize > mPreviousSize) { beginInsertRows({}, mPreviousSize, newSize - 1); endInsertRows(); } dataChanged(index(0, 0), index(newSize, ColumnCount)); mPreviousSize = newSize; } private: DebugData *mDebugData = nullptr; int mPreviousSize = 0; }; ================================================ FILE: src/decaf-qt/src/debugger/threadswindow.cpp ================================================ #include "threadswindow.h" #include "ui_threadswindow.h" #include "debugdata.h" #include "threadsmodel.h" ThreadsWindow::ThreadsWindow(QWidget *parent) : QWidget(parent), ui(new Ui::ThreadsWindow { }) { ui->setupUi(this); } ThreadsWindow::~ThreadsWindow() { delete ui; } void ThreadsWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; mThreadsModel = new ThreadsModel { this }; mThreadsModel->setDebugData(debugData); ui->tableView->setModel(mThreadsModel); auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1; ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2); ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(6, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents); ui->tableView->update(); } void ThreadsWindow::threadsViewDoubleClicked(const QModelIndex &index) { if (index.isValid()) { mDebugData->setActiveThreadIndex(index.row()); } } ================================================ FILE: src/decaf-qt/src/debugger/threadswindow.h ================================================ #pragma once #include namespace Ui { class ThreadsWindow; } class DebugData; class ThreadsModel; class ThreadsWindow : public QWidget { Q_OBJECT public: explicit ThreadsWindow(QWidget *parent = nullptr); ~ThreadsWindow(); void setDebugData(DebugData *debugData); protected slots: void threadsViewDoubleClicked(const QModelIndex &index); private: Ui::ThreadsWindow *ui; DebugData *mDebugData = nullptr; ThreadsModel *mThreadsModel = nullptr; }; ================================================ FILE: src/decaf-qt/src/debugger/voicesmodel.h ================================================ #pragma once #include #include #include "debugdata.h" class VoicesModel : public QAbstractTableModel { Q_OBJECT static constexpr const char *ColumnNames[] = { "ID", "State", "Format", "Stream", "Address", "Current Offset", "End Offset", "Loop Offset", "Loop Mode", }; static constexpr int ColumnCount = static_cast(sizeof(ColumnNames) / sizeof(ColumnNames[0])); using CafeVoice = DebugData::CafeVoice; public: VoicesModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } void setDebugData(DebugData *debugData) { mDebugData = debugData; connect(mDebugData, &DebugData::dataChanged, this, &VoicesModel::debugDataChanged); } int rowCount(const QModelIndex &parent) const override { if (!mDebugData) { return 0; } return static_cast(mDebugData->voices().size()); } int columnCount(const QModelIndex &parent) const override { return ColumnCount; } static QString getStateString(CafeVoice::State state) { switch (state) { case CafeVoice::State::Stopped: return tr("Stopped"); case CafeVoice::State::Playing: return tr("Playing"); default: return tr("Unknown"); } } static QString getFormatString(CafeVoice::Format format) { switch (format) { case CafeVoice::Format::ADPCM: return tr("ADPCM"); case CafeVoice::Format::LPCM16: return tr("LPCM16"); case CafeVoice::Format::LPCM8: return tr("LPCM8"); default: return tr("Unknown"); } } static QString getVoiceTypeString(CafeVoice::VoiceType type) { switch (type) { case CafeVoice::VoiceType::Default: return tr("Default"); case CafeVoice::VoiceType::Streaming: return tr("Streaming"); default: return tr("Unknown"); } } QVariant data(const QModelIndex &index, int role) const override { if (!mDebugData || !index.isValid()) { return QVariant{ }; } const auto &voices = mDebugData->voices(); if (index.row() >= voices.size() || index.row() < 0) { return QVariant{ }; } if (role == Qt::DisplayRole) { const auto &voice = voices.at(index.row()); switch (index.column()) { case 0: return QString("%1").arg(voice.index); case 1: return getStateString(voice.state); case 2: return getFormatString(voice.format); case 3: return getVoiceTypeString(voice.type); case 4: return QString("%1").arg(static_cast(voice.data), 8, 16, QChar{ '0' }); case 5: return QString("%1").arg(voice.currentOffset); case 6: return QString("%1").arg(voice.endOffset); case 7: return QString("%1").arg(voice.loopOffset); case 8: return voice.loopingEnabled ? tr("Looping") : QString(""); } } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant{ }; } if (orientation == Qt::Horizontal) { if (section < ColumnCount) { return ColumnNames[section]; } } return QVariant { }; } private: void debugDataChanged() { auto newSize = static_cast(mDebugData->voices().size()); if (newSize < mPreviousSize) { beginRemoveRows({}, newSize, mPreviousSize - 1); endRemoveRows(); } else if (newSize > mPreviousSize) { beginInsertRows({}, mPreviousSize, newSize - 1); endInsertRows(); } dataChanged(index(0, 0), index(newSize, ColumnCount)); mPreviousSize = newSize; } private: DebugData *mDebugData = nullptr; int mPreviousSize = 0; }; ================================================ FILE: src/decaf-qt/src/debugger/voiceswindow.cpp ================================================ #include "voiceswindow.h" #include "ui_voiceswindow.h" #include "debugdata.h" #include "voicesmodel.h" VoicesWindow::VoicesWindow(QWidget *parent) : QWidget(parent), ui(new Ui::VoicesWindow { }) { ui->setupUi(this); } VoicesWindow::~VoicesWindow() { delete ui; } void VoicesWindow::setDebugData(DebugData *debugData) { mDebugData = debugData; mVoicesModel = new VoicesModel { this }; mVoicesModel->setDebugData(debugData); ui->tableView->setModel(mVoicesModel); auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1; ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2); ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); for (int i = 1; i < ui->tableView->horizontalHeader()->count(); ++i) { ui->tableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch); } ui->tableView->update(); } ================================================ FILE: src/decaf-qt/src/debugger/voiceswindow.h ================================================ #pragma once #include namespace Ui { class VoicesWindow; } class DebugData; class VoicesModel; class VoicesWindow : public QWidget { Q_OBJECT public: explicit VoicesWindow(QWidget *parent = nullptr); ~VoicesWindow(); void setDebugData(DebugData *debugData); private: Ui::VoicesWindow *ui; DebugData *mDebugData = nullptr; VoicesModel *mVoicesModel = nullptr; }; ================================================ FILE: src/decaf-qt/src/decafinterface.cpp ================================================ #include "decafinterface.h" #include "inputdriver.h" #include "sounddriver.h" #include "settings.h" #include #include #include #include #include #include #include DecafInterface::DecafInterface(SettingsStorage *settingsStorage, InputDriver *inputDriver, SoundDriver *soundDriver) : mInputDriver(inputDriver), mSettingsStorage(settingsStorage), mSoundDriver(soundDriver) { decaf::addEventListener(this); decaf::debug::setPauseCallback([&]() { this->debugInterrupt(); }); QObject::connect(mSettingsStorage, &SettingsStorage::settingsChanged, this, &DecafInterface::settingsChanged); settingsChanged(); } void DecafInterface::settingsChanged() { auto settings = mSettingsStorage->get(); decaf::setConfig(settings->decaf); gpu::setConfig(settings->gpu); cpu::setConfig(settings->cpu); } void DecafInterface::startLogging() { decaf::initialiseLogging("decaf-qt"); } void DecafInterface::startGame(QString path) { mStarted = true; decaf::setInputDriver(mInputDriver); decaf::setSoundDriver(mSoundDriver); gLog->info("Config path {}", mSettingsStorage->path()); decaf::initialise(path.toLocal8Bit().constData()); decaf::start(); } void DecafInterface::shutdown() { if (mStarted) { decaf::shutdown(); mStarted = false; } } void DecafInterface::onGameLoaded(const decaf::GameInfo &info) { emit titleLoaded(info.titleId, QString::fromStdString(info.executable)); } ================================================ FILE: src/decaf-qt/src/decafinterface.h ================================================ #pragma once #include "settings.h" #include #include #include #include #include class InputDriver; class SettingsStorage; class SoundDriver; class DecafInterface : public QObject, public decaf::EventListener { Q_OBJECT public: DecafInterface(SettingsStorage *settingsStorage, InputDriver *inputDriver, SoundDriver *soundDriver); public slots: void startLogging(); void startGame(QString path); void shutdown(); void settingsChanged(); signals: void titleLoaded(quint64 id, const QString &name); void debugInterrupt(); protected: void onGameLoaded(const decaf::GameInfo &info) override; private: bool mStarted = false; InputDriver *mInputDriver = nullptr; SettingsStorage *mSettingsStorage = nullptr; SoundDriver *mSoundDriver = nullptr; }; ================================================ FILE: src/decaf-qt/src/erreuladriver.cpp ================================================ #include "erreuladriver.h" ErrEulaDriver::ErrEulaDriver(QObject *parent) : QObject(parent) { } void ErrEulaDriver::onOpenErrorCode(int32_t errorCode) { emit openWithErrorCode(errorCode); } void ErrEulaDriver::onOpenErrorMessage(std::u16string message, std::u16string button1, std::u16string button2) { emit openWithMessage(QString::fromStdU16String(message), QString::fromStdU16String(button1), QString::fromStdU16String(button2)); } void ErrEulaDriver::onClose() { emit close(); } ================================================ FILE: src/decaf-qt/src/erreuladriver.h ================================================ #pragma once #include #include class QInputDialog; class QWidget; class ErrEulaDriver : public QObject, public decaf::ErrEulaDriver { Q_OBJECT public: ErrEulaDriver(QObject *parent); signals: void openWithErrorCode(int32_t errorCode); void openWithMessage(QString message, QString button1, QString button2); void close(); private: void onOpenErrorCode(int32_t errorCode) override; void onOpenErrorMessage(std::u16string message, std::u16string button1, std::u16string button2) override; void onClose() override; }; ================================================ FILE: src/decaf-qt/src/inputdriver.cpp ================================================ #include "inputdriver.h" #include #include #include #include InputDriver::InputDriver(SettingsStorage *settingsStorage, QObject *parent) : QObject(parent), mSettingsStorage(settingsStorage) { QObject::connect(mSettingsStorage, &SettingsStorage::settingsChanged, this, &InputDriver::settingsChanged); settingsChanged(); // Startup SDL SDL_Init(SDL_INIT_HAPTIC | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER); // Start a timer for polling for SDL events QTimer::singleShot(100, Qt::PreciseTimer, this, SLOT(update())); // Clear keyboard state mKeyboardState.fill(false); } InputDriver::~InputDriver() { SDL_Quit(); } static inline float translateAxisValue(Sint16 value) { if (value >= 0) { return value / static_cast(SDL_JOYSTICK_AXIS_MAX); } else { return value / static_cast(-(SDL_JOYSTICK_AXIS_MIN)); } } void InputDriver::update() { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_JOYDEVICEADDED: { auto joystick = SDL_JoystickOpen(event.jdevice.which); auto guid = SDL_JoystickGetGUID(joystick); auto instanceId = SDL_JoystickInstanceID(joystick); { std::unique_lock lock { mConfigurationMutex }; auto connected = ConnectedJoystick { }; connected.joystick = joystick; connected.guid = guid; connected.instanceId = instanceId; connected.duplicateId = 0; for (auto &others : mJoysticks) { if (others.guid == connected.guid) { connected.duplicateId = std::max(connected.duplicateId, others.duplicateId + 1); } } for (auto &controller : mConfiguration.controllers) { for (auto &input : controller.inputs) { if (input.joystickGuid == guid && input.joystickDuplicateId == connected.duplicateId) { input.joystick = joystick; input.joystickInstanceId = instanceId; } } } mJoysticks.push_back(connected); } emit joystickConnected(instanceId, guid, SDL_JoystickName(joystick)); break; } case SDL_JOYDEVICEREMOVED: { auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which); if (joystick) { joystickDisconnected(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick)); { std::unique_lock lock { mConfigurationMutex }; for (auto itr = mJoysticks.begin(); itr != mJoysticks.end(); ++itr) { if (itr->joystick == joystick) { itr = mJoysticks.erase(itr); } } for (auto &controller : mConfiguration.controllers) { for (auto &input : controller.inputs) { if (input.joystick == joystick) { input.joystick = nullptr; } } } } SDL_JoystickClose(joystick); } break; } case SDL_JOYBUTTONDOWN: { if (mButtonEventsEnabled) { auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which); if (joystick) { joystickButtonDown(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick), event.jbutton.button); } } break; } case SDL_JOYAXISMOTION: { if (mButtonEventsEnabled) { auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which); if (joystick) { joystickAxisMotion(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick), event.jaxis.axis, translateAxisValue(event.jaxis.value)); } } break; } case SDL_JOYHATMOTION: { if (mButtonEventsEnabled) { auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which); if (joystick) { joystickHatMotion(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick), event.jhat.hat, event.jhat.value); } } break; } // I don't think we actually care about game controllers? case SDL_CONTROLLERDEVICEADDED: qDebug("SDL_CONTROLLERDEVICEADDED"); break; case SDL_CONTROLLERDEVICEREMAPPED: qDebug("SDL_CONTROLLERDEVICEREMAPPED"); break; case SDL_CONTROLLERDEVICEREMOVED: qDebug("SDL_CONTROLLERDEVICEREMOVED"); break; case SDL_CONTROLLERBUTTONDOWN: qDebug("SDL_CONTROLLERBUTTONDOWN"); break; } } QTimer::singleShot(10, Qt::PreciseTimer, this, SLOT(update())); } const char * getConfigButtonName(ButtonType type) { switch (type) { case ButtonType::A: return "button_a"; case ButtonType::B: return "button_b"; case ButtonType::X: return "button_x"; case ButtonType::Y: return "button_y"; case ButtonType::R: return "button_r"; case ButtonType::L: return "button_l"; case ButtonType::ZR: return "button_zr"; case ButtonType::ZL: return "button_zl"; case ButtonType::Plus: return "button_plus"; case ButtonType::Minus: return "button_minus"; case ButtonType::Home: return "button_home"; case ButtonType::Sync: return "button_sync"; case ButtonType::DpadUp: return "dpad_up"; case ButtonType::DpadDown: return "dpad_down"; case ButtonType::DpadLeft: return "dpad_left"; case ButtonType::DpadRight: return "dpad_right"; case ButtonType::LeftStickPress: return "left_stick_press"; case ButtonType::LeftStickUp: return "left_stick_up"; case ButtonType::LeftStickDown: return "left_stick_down"; case ButtonType::LeftStickLeft: return "left_stick_left"; case ButtonType::LeftStickRight: return "left_stick_right"; case ButtonType::RightStickPress: return "right_stick_press"; case ButtonType::RightStickUp: return "right_stick_up"; case ButtonType::RightStickDown: return "right_stick_down"; case ButtonType::RightStickLeft: return "right_stick_left"; case ButtonType::RightStickRight: return "right_stick_right"; default: return ""; } } void InputDriver::keyPressEvent(int key) { std::unique_lock lock { mConfigurationMutex }; mKeyboardState[key & 0xFF] = true; } void InputDriver::keyReleaseEvent(int key) { std::unique_lock lock { mConfigurationMutex }; mKeyboardState[key & 0xFF] = false; } void InputDriver::gamepadTouchEvent(bool down, float x, float y) { std::unique_lock lock { mConfigurationMutex }; mTouchDown = down; mTouchX = x; mTouchY = y; } void InputDriver::settingsChanged() { auto settings = mSettingsStorage->get(); std::unique_lock lock { mConfigurationMutex }; mConfiguration = settings->input; // Map to correct SDL joystick for (auto &controller : mConfiguration.controllers) { for (auto &input : controller.inputs) { if (input.source == InputConfiguration::Input::JoystickAxis || input.source == InputConfiguration::Input::JoystickButton || input.source == InputConfiguration::Input::JoystickHat) { for (auto &connected : mJoysticks) { if (input.joystickInstanceId != -1) { if (connected.instanceId == input.joystickInstanceId) { input.joystick = connected.joystick; input.joystickDuplicateId = connected.duplicateId; } } else if (connected.guid == input.joystickGuid && connected.duplicateId == input.joystickDuplicateId) { input.joystick = connected.joystick; input.joystickInstanceId = connected.instanceId; } } } } } // TODO: Should probably allow a way to remember what VPAD / WPAD index is // assigned to each controller. // Update VPAD / WPAD assignment auto vpad = 0; auto wpad = 0; for (auto &controller : mConfiguration.controllers) { if (controller.type == ControllerType::Gamepad) { if (vpad < mConfiguration.vpad.size()) { mConfiguration.vpad[vpad++] = &controller; } } else if (controller.type != ControllerType::Invalid) { if (wpad < mConfiguration.wpad.size()) { mConfiguration.wpad[wpad++] = &controller; } } } } void InputDriver::sampleVpadController(int channel, decaf::input::vpad::Status &status) { std::unique_lock lock { mConfigurationMutex }; if (channel < 0 || channel > mConfiguration.vpad.size()) { status.connected = false; return; } auto getButtonState = [&](InputConfiguration::Controller *controller, ButtonType type) -> uint32_t { auto &input = controller->inputs[static_cast(type)]; if (input.source == InputConfiguration::Input::KeyboardKey) { if (input.id < 0) { return 0; } return !!mKeyboardState[input.id & 0xFF]; } else if (input.source == InputConfiguration::Input::JoystickButton) { if (!input.joystick) { return 0; } return !!SDL_JoystickGetButton(input.joystick, input.id); } else if (input.source == InputConfiguration::Input::JoystickHat) { if (!input.joystick) { return 0; } return !!(SDL_JoystickGetHat(input.joystick, input.id) & input.hatValue); } else if (input.source == InputConfiguration::Input::JoystickAxis) { if (!input.joystick) { return 0; } // Button is considered pressed if axis value is >= 0.5f or <= -0.5f auto value = SDL_JoystickGetAxis(input.joystick, input.id); if (input.invert && value >= SDL_JOYSTICK_AXIS_MAX / 2) { return 1; } else if (!input.invert && value < SDL_JOYSTICK_AXIS_MIN / 2) { return 1; } else { return 0; } } return 0; }; auto getAxisValue = [&](InputConfiguration::Controller *controller, ButtonType type) -> float { auto &input = controller->inputs[static_cast(type)]; if (input.source == InputConfiguration::Input::KeyboardKey) { if (input.id < 0) { return 0.0f; } return mKeyboardState[input.id & 0xFF] ? 1.0f : 0.0f; } else if (input.source == InputConfiguration::Input::JoystickButton) { if (!input.joystick) { return 0.0f; } return SDL_JoystickGetButton(input.joystick, input.id) ? 1.0f : 0.0f; } else if (input.source == InputConfiguration::Input::JoystickHat) { if (!input.joystick) { return 0.0f; } return (SDL_JoystickGetHat(input.joystick, input.id) & input.hatValue) ? 1.0f : 0.0f; } else if (input.source == InputConfiguration::Input::JoystickAxis) { if (!input.joystick) { return 0.0f; } auto value = SDL_JoystickGetAxis(input.joystick, input.id); if (!input.invert && value > 0) { return static_cast(value) / static_cast(SDL_JOYSTICK_AXIS_MAX); } else if (input.invert && value < 0) { return static_cast(value) / static_cast(SDL_JOYSTICK_AXIS_MIN); } else { return 0.0f; } } return 0.0f; }; auto controller = mConfiguration.vpad[channel]; if (!controller) { status.connected = false; return; } status.connected = true; status.buttons.sync = getButtonState(controller, ButtonType::Sync); status.buttons.home = getButtonState(controller, ButtonType::Home); status.buttons.minus = getButtonState(controller, ButtonType::Minus); status.buttons.plus = getButtonState(controller, ButtonType::Plus); status.buttons.r = getButtonState(controller, ButtonType::R); status.buttons.l = getButtonState(controller, ButtonType::L); status.buttons.zr = getButtonState(controller, ButtonType::ZR); status.buttons.zl = getButtonState(controller, ButtonType::ZL); status.buttons.down = getButtonState(controller, ButtonType::DpadDown); status.buttons.up = getButtonState(controller, ButtonType::DpadUp); status.buttons.right = getButtonState(controller, ButtonType::DpadRight); status.buttons.left = getButtonState(controller, ButtonType::DpadLeft); status.buttons.x = getButtonState(controller, ButtonType::X); status.buttons.y = getButtonState(controller, ButtonType::Y); status.buttons.a = getButtonState(controller, ButtonType::A); status.buttons.b = getButtonState(controller, ButtonType::B); status.buttons.stickR = getButtonState(controller, ButtonType::RightStickDown); status.buttons.stickL = getButtonState(controller, ButtonType::LeftStickDown); status.leftStickX = getAxisValue(controller, ButtonType::LeftStickRight); status.leftStickX -= getAxisValue(controller, ButtonType::LeftStickLeft); status.leftStickY = getAxisValue(controller, ButtonType::LeftStickUp); status.leftStickY -= getAxisValue(controller, ButtonType::LeftStickDown); status.rightStickX = getAxisValue(controller, ButtonType::RightStickRight); status.rightStickX -= getAxisValue(controller, ButtonType::RightStickLeft); status.rightStickY = getAxisValue(controller, ButtonType::RightStickUp); status.rightStickY -= getAxisValue(controller, ButtonType::RightStickDown); status.touch.down = mTouchDown; status.touch.x = mTouchX; status.touch.y = mTouchY; } void InputDriver::sampleWpadController(int channel, decaf::input::wpad::Status &status) { } ================================================ FILE: src/decaf-qt/src/inputdriver.h ================================================ #pragma once #include "settings.h" #include #include #include #include #include class QKeyEvent; class SettingsStorage; struct ConnectedJoystick { SDL_Joystick *joystick = nullptr; SDL_JoystickGUID guid; SDL_JoystickID instanceId; // This is for when we have multiple controllers with the same guid int duplicateId = 0; }; static inline bool operator ==(const SDL_JoystickGUID &lhs, const SDL_JoystickGUID &rhs) { return memcmp(&lhs, &rhs, sizeof(SDL_JoystickGUID)) == 0; } class InputDriver : public QObject, public decaf::InputDriver { Q_OBJECT public: InputDriver(SettingsStorage *settingsStorage, QObject *parent = nullptr); ~InputDriver(); void enableButtonEvents() { mButtonEventsEnabled = true; } void disableButtonEvents() { mButtonEventsEnabled = false; } void keyPressEvent(int key); void keyReleaseEvent(int key); void gamepadTouchEvent(bool down, float x, float y); signals: void joystickConnected(SDL_JoystickID id, SDL_JoystickGUID guid, const char *name); void joystickDisconnected(SDL_JoystickID id, SDL_JoystickGUID guid); void joystickButtonDown(SDL_JoystickID id, SDL_JoystickGUID guid, int button); void joystickAxisMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int axis, float value); void joystickHatMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int hat, int value); void configurationUpdated(); private slots: void update(); void settingsChanged(); private: void sampleVpadController(int channel, decaf::input::vpad::Status &status) override; void sampleWpadController(int channel, decaf::input::wpad::Status &status) override; private: SettingsStorage *mSettingsStorage; bool mButtonEventsEnabled = false; std::mutex mConfigurationMutex; InputConfiguration mConfiguration; std::vector mJoysticks; bool mTouchDown = false; float mTouchX = 0.0f; float mTouchY = 0.0f; std::array mKeyboardState; }; ================================================ FILE: src/decaf-qt/src/inputeventfilter.h ================================================ #pragma once #include #include #include class InputEventFilter : public QObject { Q_OBJECT public: InputEventFilter(QObject *parent) : QObject(parent) { } bool enabled() { return mEnabled; } void enable() { mEnabled = true; } void disable() { mEnabled = false; } signals: void caughtKeyPress(int key); protected: bool eventFilter(QObject *obj, QEvent *event) { if (mEnabled) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); emit caughtKeyPress(keyEvent->key()); return true; } } return QObject::eventFilter(obj, event); } private: bool mEnabled = false; }; ================================================ FILE: src/decaf-qt/src/main.cpp ================================================ #include "mainwindow.h" #include "decafinterface.h" #include "inputdriver.h" #include "sounddriver.h" #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { QCoreApplication::setOrganizationName("decaf-emu"); QCoreApplication::setOrganizationDomain("decaf-emu.com"); QCoreApplication::setApplicationName("decaf-qt"); auto app = QApplication { argc, argv }; decaf::createConfigDirectory(); auto settings = SettingsStorage { decaf::makeConfigPath("config.toml") }; auto inputDriver = InputDriver { &settings }; auto soundDriver = SoundDriver { &settings }; auto decafInterface = DecafInterface { &settings, &inputDriver, &soundDriver }; auto mainWindow = MainWindow { &settings, &decafInterface, &inputDriver }; mainWindow.show(); return app.exec(); } ================================================ FILE: src/decaf-qt/src/mainwindow.cpp ================================================ #include "mainwindow.h" #include "aboutdialog.h" #include "debugger/debuggerwindow.h" #include "decafinterface.h" #include "erreuladriver.h" #include "renderwidget.h" #include "softwarekeyboarddriver.h" #include "settings/settingsdialog.h" #include "titlelistwidget.h" #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(SettingsStorage *settingsStorage, DecafInterface *decafInterface, InputDriver *inputDriver, QWidget *parent) : QMainWindow(parent), mSettingsStorage(settingsStorage), mDecafInterface(decafInterface), mInputDriver(inputDriver), mErrEulaDriver(new ErrEulaDriver { this }), mErrEulaDialog(new QMessageBox { this }), mSoftwareKeyboardDriver(new SoftwareKeyboardDriver { this }), mSoftwareKeyboardInputDialog(new QInputDialog { this }) { // Setup UI mUi.setupUi(this); mTitleListWiget = new TitleListWidget { mSettingsStorage, this }; setCentralWidget(mTitleListWiget); connect(mTitleListWiget, &TitleListWidget::launchTitle, this, &MainWindow::loadFile); connect(mTitleListWiget, &TitleListWidget::statusMessage, this, &MainWindow::showStatusMessage); connect(decafInterface, &DecafInterface::titleLoaded, this, &MainWindow::titleLoaded); connect(decafInterface, &DecafInterface::debugInterrupt, this, &MainWindow::debugInterrupt); // Setup status bar mStatusTimer = new QTimer(this); mUi.statusBar->addPermanentWidget(mStatusFrameRate = new QLabel()); mUi.statusBar->addPermanentWidget(mStatusFrameTime = new QLabel()); connect(mStatusTimer, SIGNAL(timeout()), this, SLOT(updateStatusBar())); // Setup settings connect(mSettingsStorage, &SettingsStorage::settingsChanged, this, &MainWindow::settingsChanged); settingsChanged(); QShortcut *shortcut = new QShortcut(QKeySequence("F11"), this); connect(shortcut, &QShortcut::activated, this, &MainWindow::toggleFullScreen); connect(mErrEulaDriver, &ErrEulaDriver::openWithErrorCode, this, &MainWindow::erreulaOpenWithErrorCode); connect(mErrEulaDriver, &ErrEulaDriver::openWithMessage, this, &MainWindow::erreulaOpenWithMessage); connect(mErrEulaDriver, &ErrEulaDriver::close, this, &MainWindow::erreulaClose); decaf::setErrEulaDriver(mErrEulaDriver); connect(mSoftwareKeyboardDriver, &SoftwareKeyboardDriver::open, this, &MainWindow::softwareKeyboardOpen); connect(mSoftwareKeyboardDriver, &SoftwareKeyboardDriver::close, this, &MainWindow::softwareKeyboardClose); connect(mSoftwareKeyboardDriver, &SoftwareKeyboardDriver::inputStringChanged, this, &MainWindow::softwareKeyboardInputStringChanged); connect(mSoftwareKeyboardInputDialog, &QInputDialog::finished, this, &MainWindow::softwareKeyboardInputFinished); decaf::setSoftwareKeyboardDriver(mSoftwareKeyboardDriver); // Create recent file actions mRecentFilesSeparator = mUi.menuFile->insertSeparator(mUi.actionExit); mRecentFilesSeparator->setVisible(false); for (auto i = 0u; i < mRecentFileActions.size(); ++i) { mRecentFileActions[i] = new QAction(this); mRecentFileActions[i]->setVisible(false); QObject::connect(mRecentFileActions[i], &QAction::triggered, this, &MainWindow::openRecentFile); mUi.menuFile->insertAction(mRecentFilesSeparator, mRecentFileActions[i]); } updateRecentFileActions(); } void MainWindow::softwareKeyboardOpen(QString defaultText) { mSoftwareKeyboardInputDialog->setLabelText("Software Keyboard Input"); mSoftwareKeyboardInputDialog->show(); } void MainWindow::softwareKeyboardClose() { mSoftwareKeyboardInputDialog->close(); } void MainWindow::softwareKeyboardInputStringChanged(QString text) { mSoftwareKeyboardInputDialog->setTextValue(text); } void MainWindow::softwareKeyboardInputFinished(int result) { if (result == QDialog::Accepted) { mSoftwareKeyboardDriver->acceptInput(mSoftwareKeyboardInputDialog->textValue()); } else { mSoftwareKeyboardDriver->rejectInput(); } } void MainWindow::erreulaOpenWithErrorCode(int32_t errorCode) { mErrEulaDialog->setWindowTitle("ErrEula"); mErrEulaDialog->setIcon(QMessageBox::Critical); mErrEulaDialog->setText(QString("Error code: %1").arg(errorCode)); mErrEulaButton1 = nullptr; mErrEulaButton2 = nullptr; for (auto button : mErrEulaDialog->buttons()) { mErrEulaDialog->removeButton(button); delete button; } mErrEulaDialog->setStandardButtons(QMessageBox::Ok); mErrEulaDialog->show(); } void MainWindow::erreulaOpenWithMessage(QString message, QString button1, QString button2) { mErrEulaDialog->setWindowTitle("ErrEula"); mErrEulaDialog->setIcon(QMessageBox::Critical); mErrEulaDialog->setText(QString("Error message: %1").arg(message)); mErrEulaButton1 = nullptr; mErrEulaButton2 = nullptr; for (auto button : mErrEulaDialog->buttons()) { mErrEulaDialog->removeButton(button); delete button; } if (button2.isEmpty()) { if (button1.isEmpty()) { mErrEulaDialog->setStandardButtons(QMessageBox::Ok); } else { mErrEulaButton1 = reinterpret_cast( mErrEulaDialog->addButton(button1, QMessageBox::AcceptRole)); } } else { mErrEulaButton1 = reinterpret_cast( mErrEulaDialog->addButton(button1, QMessageBox::AcceptRole)); mErrEulaButton2 = reinterpret_cast( mErrEulaDialog->addButton(button2, QMessageBox::RejectRole)); } mErrEulaDialog->show(); } void MainWindow::erreulaClose() { mErrEulaDialog->close(); } void MainWindow::erreulaButtonClicked(QAbstractButton *button) { if (button == mErrEulaButton1) { mErrEulaDriver->button1Clicked(); } else if (button == mErrEulaButton2) { mErrEulaDriver->button2Clicked(); } else { mErrEulaDriver->buttonClicked(); } } void MainWindow::settingsChanged() { auto settings = mSettingsStorage->get(); if (settings->gpu.display.viewMode == gpu::DisplaySettings::Split) { mUi.actionViewSplit->setChecked(true); } else if (settings->gpu.display.viewMode == gpu::DisplaySettings::TV) { mUi.actionViewTV->setChecked(true); } else if (settings->gpu.display.viewMode == gpu::DisplaySettings::Gamepad1) { mUi.actionViewGamepad1->setChecked(true); } else if (settings->gpu.display.viewMode == gpu::DisplaySettings::Gamepad2) { mUi.actionViewGamepad2->setChecked(true); } if (settings->ui.titleListMode == UiSettings::TitleList) { mUi.actionViewTitleList->setChecked(true); } else if(settings->ui.titleListMode == UiSettings::TitleGrid) { mUi.actionViewTitleGrid->setChecked(true); } } void MainWindow::titleLoaded(quint64 id, const QString &name) { setWindowTitle(QString("decaf-qt - %1").arg(name)); } void MainWindow::debugInterrupt() { openDebugger(); } bool MainWindow::loadFile(QString path) { // Ensure file exists before trying to load if (!QFileInfo { path }.exists()) { QMessageBox::warning(this, "File not found", "Could not find selected file: " + path); // Delete path from recent files auto settings = QSettings {}; auto files = settings.value("recentFileList").toStringList(); files.removeAll(path); settings.setValue("recentFileList", files); updateRecentFileActions(); return false; } // You only get one chance to run a game out here buddy. mUi.actionOpen->setDisabled(true); for (auto i = 0u; i < mRecentFileActions.size(); ++i) { mRecentFileActions[i]->setDisabled(true); } // Change main widget to render widget mTitleListWiget->deleteLater(); mTitleListWiget = nullptr; mRenderWidget = new RenderWidget { mInputDriver, this }; setCentralWidget(mRenderWidget); // Update status bar mUi.statusBar->clearMessage(); mStatusTimer->start(500); // Start the game mDecafInterface->startLogging(); mRenderWidget->startGraphicsDriver(); mDecafInterface->startGame(path); // Update recent files list { auto settings = QSettings { }; auto files = settings.value("recentFileList").toStringList(); files.removeAll(path); files.prepend(path); while (files.size() > MaxRecentFiles) { files.removeLast(); } settings.setValue("recentFileList", files); } updateRecentFileActions(); return true; } void MainWindow::openFile() { auto fileName = QFileDialog::getOpenFileName(this, tr("Open Application"), "", tr("RPX Files (*.rpx);;cos.xml (cos.xml);;")); if (!fileName.isEmpty()) { loadFile(fileName); } } void MainWindow::openRecentFile() { auto action = qobject_cast(sender()); if (action) { loadFile(action->data().toString()); } } void MainWindow::updateRecentFileActions() { auto settings = QSettings { }; auto files = settings.value("recentFileList").toStringList(); auto numRecentFiles = qMin(files.size(), MaxRecentFiles); for (int i = 0; i < numRecentFiles; ++i) { auto text = QString { "&%1 %2" }.arg(i + 1).arg(QFileInfo { files[i] }.fileName()); mRecentFileActions[i]->setText(text); mRecentFileActions[i]->setData(files[i]); mRecentFileActions[i]->setVisible(true); } for (int j = numRecentFiles; j < MaxRecentFiles; ++j) { mRecentFileActions[j]->setVisible(false); } mRecentFilesSeparator->setVisible(numRecentFiles > 0); } void MainWindow::exit() { close(); } void MainWindow::setViewModeSplit() { auto settings = *mSettingsStorage->get(); settings.gpu.display.viewMode = gpu::DisplaySettings::Split; mSettingsStorage->set(settings); } void MainWindow::setViewModeTV() { auto settings = *mSettingsStorage->get(); settings.gpu.display.viewMode = gpu::DisplaySettings::TV; mSettingsStorage->set(settings); } void MainWindow::setViewModeGamepad1() { auto settings = *mSettingsStorage->get(); settings.gpu.display.viewMode = gpu::DisplaySettings::Gamepad1; mSettingsStorage->set(settings); } void MainWindow::setTitleListModeList() { auto settings = *mSettingsStorage->get(); settings.ui.titleListMode= UiSettings::TitleList; mSettingsStorage->set(settings); } void MainWindow::setTitleListModeGrid() { auto settings = *mSettingsStorage->get(); settings.ui.titleListMode = UiSettings::TitleGrid; mSettingsStorage->set(settings); } void MainWindow::toggleFullScreen() { if (mUi.menuBar->isVisible()) { mUi.menuBar->hide(); mUi.statusBar->hide(); showFullScreen(); } else { mUi.menuBar->show(); mUi.statusBar->show(); showNormal(); } } void MainWindow::openDebugger() { if (mDebuggerWindow) { mDebuggerWindow->show(); return; } mDebuggerWindow = new DebuggerWindow { }; mDebuggerWindow->setAttribute(Qt::WA_DeleteOnClose); mDebuggerWindow->show(); connect(mDebuggerWindow, &DebuggerWindow::destroyed, [this]() { mDebuggerWindow = nullptr; }); } void MainWindow::openSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Default, this }; dialog.exec(); } void MainWindow::openDebugSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Debug, this }; dialog.exec(); } void MainWindow::openInputSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Input, this }; dialog.exec(); } void MainWindow::openLoggingSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Logging, this }; dialog.exec(); } void MainWindow::openSystemSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::System, this }; dialog.exec(); } void MainWindow::openAudioSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Audio, this }; dialog.exec(); } void MainWindow::openDisplaySettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Display, this }; dialog.exec(); } void MainWindow::openContentSettings() { SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Content, this }; dialog.exec(); } void MainWindow::openAboutDialog() { AboutDialog dialog { this }; dialog.exec(); } void MainWindow::closeEvent(QCloseEvent *event) { #if 0 QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Are you sure?", tr("A game is running, are you sure you want to exit?\n"), QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes); if (resBtn != QMessageBox::Yes) { event->ignore(); return; } #endif if (mDebuggerWindow) { mDebuggerWindow->close(); } mDecafInterface->shutdown(); event->accept(); } void MainWindow::updateStatusBar() { if (auto gpuDriver = decaf::getGraphicsDriver()) { auto debugInfo = gpuDriver->getDebugInfo(); mStatusFrameRate->setText( QString("FPS: %1") .arg(debugInfo->averageFps, 2, 'f', 0)); mStatusFrameTime->setText( QString("Frametime: %1ms") .arg(debugInfo->averageFrameTimeMS, 7, 'f', 3)); } else { mStatusFrameRate->setText(""); mStatusFrameTime->setText(""); } } void MainWindow::showStatusMessage(QString message, int timeout) { mUi.statusBar->showMessage(message, timeout); } ================================================ FILE: src/decaf-qt/src/mainwindow.h ================================================ #pragma once #include "ui_mainwindow.h" #include #include class DebuggerWindow; class DecafInterface; class ErrEulaDriver; class InputDriver; class RenderWidget; class SettingsStorage; class SoftwareKeyboardDriver; class TitleListWidget; class QAbstractButton; class QAction; class QInputDialog; class QLabel; class QMessageBox; class QTimer; class MainWindow : public QMainWindow { Q_OBJECT static constexpr int MaxRecentFiles = 5; public: explicit MainWindow(SettingsStorage *settingsStorage, DecafInterface *decafInterface, InputDriver *inputDriver, QWidget* parent = nullptr); private slots: void openFile(); void exit(); void setViewModeGamepad1(); void setViewModeSplit(); void setViewModeTV(); void setTitleListModeList(); void setTitleListModeGrid(); void toggleFullScreen(); void openDebugger(); void openSettings(); void openDebugSettings(); void openInputSettings(); void openLoggingSettings(); void openRecentFile(); void openSystemSettings(); void openAudioSettings(); void openDisplaySettings(); void openContentSettings(); void openAboutDialog(); void debugInterrupt(); void titleLoaded(quint64 id, const QString &name); void settingsChanged(); void softwareKeyboardOpen(QString defaultText); void softwareKeyboardClose(); void softwareKeyboardInputStringChanged(QString text); void softwareKeyboardInputFinished(int result); void erreulaOpenWithErrorCode(int32_t errorCode); void erreulaOpenWithMessage(QString message, QString button1, QString button2); void erreulaClose(); void erreulaButtonClicked(QAbstractButton *button); void updateStatusBar(); void showStatusMessage(QString message, int timeout); bool loadFile(QString path); protected: void closeEvent(QCloseEvent *event) override; void updateRecentFileActions(); private: SettingsStorage *mSettingsStorage = nullptr; RenderWidget *mRenderWidget = nullptr; TitleListWidget *mTitleListWiget = nullptr; DecafInterface *mDecafInterface = nullptr; InputDriver *mInputDriver = nullptr; ErrEulaDriver *mErrEulaDriver = nullptr; QMessageBox *mErrEulaDialog = nullptr; QAbstractButton *mErrEulaButton1 = nullptr; QAbstractButton *mErrEulaButton2 = nullptr; SoftwareKeyboardDriver *mSoftwareKeyboardDriver = nullptr; QInputDialog *mSoftwareKeyboardInputDialog = nullptr; DebuggerWindow *mDebuggerWindow = nullptr; Ui::MainWindow mUi; QTimer *mStatusTimer = nullptr; QLabel *mStatusFrameRate = nullptr; QLabel *mStatusFrameTime = nullptr; QAction *mRecentFilesSeparator = nullptr; std::array mRecentFileActions; }; ================================================ FILE: src/decaf-qt/src/renderwidget.cpp ================================================ #include "inputdriver.h" #include "renderwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include RenderWidget::RenderWidget(InputDriver *inputDriver, QWidget *parent) : mInputDriver(inputDriver), QWidget(parent) { setAutoFillBackground(false); setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_NoSystemBackground); setAttribute(Qt::WA_NativeWindow); setFocusPolicy(Qt::StrongFocus); setFocus(); } RenderWidget::~RenderWidget() { if (mGraphicsDriver) { mRenderThread.join(); delete mGraphicsDriver; } } void RenderWidget::startGraphicsDriver() { auto wsi = gpu::WindowSystemInfo { }; auto platformName = QGuiApplication::platformName(); if (platformName == QStringLiteral("windows")) { wsi.type = gpu::WindowSystemType::Windows; } else if (platformName == QStringLiteral("cocoa")) { wsi.type = gpu::WindowSystemType::Cocoa; } else if (platformName == QStringLiteral("xcb")) { wsi.type = gpu::WindowSystemType::X11; } else if (platformName == QStringLiteral("wayland")) { wsi.type = gpu::WindowSystemType::Wayland; } auto window = windowHandle(); #if defined(WIN32) || defined(__APPLE__) wsi.renderSurface = reinterpret_cast(window->winId()); #else auto pni = QGuiApplication::platformNativeInterface(); wsi.displayConnection = pni->nativeResourceForWindow("display", window); if (wsi.type == gpu::WindowSystemType::Wayland) { wsi.renderSurface = pni->nativeResourceForWindow("surface", window); } else { wsi.renderSurface = reinterpret_cast(window->winId()); } #endif wsi.renderSurfaceScale = window->devicePixelRatio(); mGraphicsDriver = gpu::createGraphicsDriver(); mGraphicsDriver->setWindowSystemInfo(wsi); mRenderThread = std::thread { [this]() { mGraphicsDriver->run(); } }; decaf::setGraphicsDriver(mGraphicsDriver); } QPaintEngine * RenderWidget::paintEngine() const { return nullptr; } bool RenderWidget::event(QEvent *event) { switch (event->type()) { case QEvent::Paint: return false; case QEvent::WinIdChange: if (mGraphicsDriver) { mGraphicsDriver->windowHandleChanged(reinterpret_cast(winId())); } break; case QEvent::KeyPress: { auto keyEvent = static_cast(event); mInputDriver->keyPressEvent(keyEvent->key()); return true; } case QEvent::KeyRelease: { auto keyEvent = static_cast(event); mInputDriver->keyReleaseEvent(keyEvent->key()); return true; } case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { auto mouseEvent = static_cast(event); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto mousePosition = mouseEvent->position(); auto mouseX = mousePosition.x(); auto mouseY = mousePosition.y(); #else auto mouseX = static_cast(mouseEvent->x()); auto mouseY = static_cast(mouseEvent->y()); #endif auto layout = gpu7::getDisplayLayout(static_cast(width()), static_cast(height())); auto touchEvent = gpu7::translateDisplayTouch(layout, mouseX, mouseY); if (touchEvent.screen != gpu7::DisplayTouchEvent::None) { mInputDriver->gamepadTouchEvent( !!(mouseEvent->buttons() & Qt::LeftButton), touchEvent.x, touchEvent.y); } return true; } case QEvent::Resize: { auto resizeEvent = static_cast(event); auto newSize = resizeEvent->size(); auto ratio = devicePixelRatio(); if (mGraphicsDriver) { mGraphicsDriver->windowSizeChanged(newSize.width() * ratio, newSize.height() * ratio); } break; } } return QWidget::event(event); } ================================================ FILE: src/decaf-qt/src/renderwidget.h ================================================ #pragma once #include #include namespace gpu { class GraphicsDriver; } class InputDriver; class RenderWidget : public QWidget { public: RenderWidget(InputDriver *inputDriver, QWidget *parent = nullptr); ~RenderWidget(); void startGraphicsDriver(); protected: QPaintEngine *paintEngine() const override; bool event(QEvent *event) override; private: gpu::GraphicsDriver *mGraphicsDriver = nullptr; InputDriver *mInputDriver = nullptr; std::thread mRenderThread; }; ================================================ FILE: src/decaf-qt/src/settings/audiosettingswidget.cpp ================================================ #include "audiosettingswidget.h" AudioSettingsWidget::AudioSettingsWidget(QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f) { mUi.setupUi(this); } void AudioSettingsWidget::loadSettings(const Settings &settings) { mUi.checkBoxPlaybackEnabled->setChecked(settings.sound.playbackEnabled); } void AudioSettingsWidget::saveSettings(Settings &settings) { settings.sound.playbackEnabled = mUi.checkBoxPlaybackEnabled->isChecked(); } ================================================ FILE: src/decaf-qt/src/settings/audiosettingswidget.h ================================================ #pragma once #include "ui_audiosettings.h" #include "settingswidget.h" class AudioSettingsWidget : public SettingsWidget { Q_OBJECT public: AudioSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~AudioSettingsWidget() = default; void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private: Ui::AudioSettingsWidget mUi; }; ================================================ FILE: src/decaf-qt/src/settings/colourlineedit.h ================================================ #pragma once #include #include #include class ColourLineEdit : public QLineEdit { public: ColourLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) { installEventFilter(this); setReadOnly(true); setColour(QColor { Qt::black }); } bool eventFilter(QObject*, QEvent* ev) { if (ev->type() == QEvent::MouseButtonPress) { auto color = QColorDialog::getColor(mColour, this, tr("Select color")); if (color.isValid()) { setColour(color); } return false; } return false; } void setColour(const QColor &colour) { mColour = colour; auto palette = QPalette { }; palette.setColor(QPalette::Base, mColour); palette.setColor(QPalette::Text, getTextColor()); setPalette(palette); setText(mColour.name()); } QColor getColour() const { return mColour; } private: QColor getTextColor() { double luminance = 1 - (0.299 * mColour.red() + 0.587 * mColour.green() + 0.114 * mColour.blue()) / 255; if (luminance < 0.5) { return QColor { Qt::black }; } else { return QColor { Qt::white }; } } private: QColor mColour; }; ================================================ FILE: src/decaf-qt/src/settings/contentsettingswidget.cpp ================================================ #include "contentsettingswidget.h" #include ContentSettingsWidget::ContentSettingsWidget(QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f) { mUi.setupUi(this); } void ContentSettingsWidget::loadSettings(const Settings &settings) { mUi.lineEditMlcPath->setText(QString::fromStdString(settings.decaf.system.mlc_path)); mUi.lineEditSlcPath->setText(QString::fromStdString(settings.decaf.system.slc_path)); mUi.lineEditSdcardPath->setText(QString::fromStdString(settings.decaf.system.sdcard_path)); mUi.lineEditHfioPath->setText(QString::fromStdString(settings.decaf.system.hfio_path)); mUi.lineEditResourcesPath->setText(QString::fromStdString(settings.decaf.system.resources_path)); mUi.lineEditOtpPath->setText(QString::fromStdString(settings.decaf.system.otp_path)); mUi.listWidgetTitleDirectories->clear(); for (const auto &path : settings.decaf.system.title_directories) { mUi.listWidgetTitleDirectories->addItem(QString::fromStdString(path)); } mUi.lineEditMlcPath->setCursorPosition(0); mUi.lineEditSlcPath->setCursorPosition(0); mUi.lineEditSdcardPath->setCursorPosition(0); mUi.lineEditHfioPath->setCursorPosition(0); mUi.lineEditResourcesPath->setCursorPosition(0); mUi.lineEditOtpPath->setCursorPosition(0); mUi.listWidgetTitleDirectories->setCurrentRow(0); } void ContentSettingsWidget::saveSettings(Settings &settings) { settings.decaf.system.mlc_path = mUi.lineEditMlcPath->text().toStdString(); settings.decaf.system.slc_path = mUi.lineEditSlcPath->text().toStdString(); settings.decaf.system.sdcard_path = mUi.lineEditSdcardPath->text().toStdString(); settings.decaf.system.hfio_path = mUi.lineEditHfioPath->text().toStdString(); settings.decaf.system.resources_path = mUi.lineEditResourcesPath->text().toStdString(); settings.decaf.system.otp_path = mUi.lineEditOtpPath->text().toStdString(); settings.decaf.system.title_directories.clear(); for (auto i = 0; i < mUi.listWidgetTitleDirectories->count(); ++i) { settings.decaf.system.title_directories.emplace_back( mUi.listWidgetTitleDirectories->item(i)->text().toStdString()); } } void ContentSettingsWidget::browseHfioPath() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), mUi.lineEditHfioPath->text()); if (!path.isEmpty()) { mUi.lineEditHfioPath->setText(path); } } void ContentSettingsWidget::browseMlcPath() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), mUi.lineEditMlcPath->text()); if (!path.isEmpty()) { mUi.lineEditMlcPath->setText(path); } } void ContentSettingsWidget::browseOtpPath() { auto path = QFileDialog::getOpenFileName(this, tr("Open otp.bin"), mUi.lineEditOtpPath->text()); if (!path.isEmpty()) { mUi.lineEditOtpPath->setText(path); } } void ContentSettingsWidget::browseResourcesPath() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), mUi.lineEditResourcesPath->text()); if (!path.isEmpty()) { mUi.lineEditResourcesPath->setText(path); } } void ContentSettingsWidget::browseSdcardPath() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), mUi.lineEditSdcardPath->text()); if (!path.isEmpty()) { mUi.lineEditSdcardPath->setText(path); } } void ContentSettingsWidget::browseSlcPath() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), mUi.lineEditSlcPath->text()); if (!path.isEmpty()) { mUi.lineEditSlcPath->setText(path); } } void ContentSettingsWidget::addTitleDirectory() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory")); if (!path.isEmpty()) { mUi.listWidgetTitleDirectories->addItem(path); } } void ContentSettingsWidget::removeTitleDirectory() { qDeleteAll(mUi.listWidgetTitleDirectories->selectedItems()); } ================================================ FILE: src/decaf-qt/src/settings/contentsettingswidget.h ================================================ #pragma once #include "ui_contentsettings.h" #include "settingswidget.h" class ContentSettingsWidget : public SettingsWidget { Q_OBJECT public: ContentSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~ContentSettingsWidget() = default; void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private slots: void browseHfioPath(); void browseMlcPath(); void browseOtpPath(); void browseResourcesPath(); void browseSdcardPath(); void browseSlcPath(); void addTitleDirectory(); void removeTitleDirectory(); private: Ui::ContentSettingsWidget mUi; }; ================================================ FILE: src/decaf-qt/src/settings/debugsettingswidget.cpp ================================================ #include "debugsettingswidget.h" #include DebugSettingsWidget::DebugSettingsWidget(QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f) { mUi.setupUi(this); mUi.lineEditGdbServerPort->setValidator(new QIntValidator(0, 65535)); } void DebugSettingsWidget::loadSettings(const Settings &settings) { mUi.checkBoxBreakEntry->setChecked(settings.decaf.debugger.break_on_entry); mUi.checkBoxBreakExit->setChecked(settings.decaf.debugger.break_on_exit); mUi.checkBoxGdbServerEnabled->setChecked(settings.decaf.debugger.gdb_stub); mUi.lineEditGdbServerPort->setText(QString("%1").arg(settings.decaf.debugger.gdb_stub_port)); } void DebugSettingsWidget::saveSettings(Settings &settings) { settings.decaf.debugger.break_on_entry = mUi.checkBoxBreakEntry->isChecked(); settings.decaf.debugger.break_on_exit = mUi.checkBoxBreakExit->isChecked(); settings.decaf.debugger.gdb_stub = mUi.checkBoxGdbServerEnabled->isChecked(); settings.decaf.debugger.gdb_stub_port = mUi.lineEditGdbServerPort->text().toInt(); } ================================================ FILE: src/decaf-qt/src/settings/debugsettingswidget.h ================================================ #pragma once #include "ui_debugsettings.h" #include "settingswidget.h" class DebugSettingsWidget : public SettingsWidget { Q_OBJECT public: DebugSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~DebugSettingsWidget() = default; void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private: Ui::DebugSettingsWidget mUi; }; ================================================ FILE: src/decaf-qt/src/settings/displaysettingswidget.cpp ================================================ #include "displaysettingswidget.h" #include DisplaySettingsWidget::DisplaySettingsWidget(QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f) { mUi.setupUi(this); mUi.comboBoxTitleListMode->addItem(tr("Title List"), static_cast(UiSettings::TitleList)); mUi.comboBoxTitleListMode->addItem(tr("Title Grid"), static_cast(UiSettings::TitleGrid)); mUi.comboBoxViewMode->addItem(tr("Split"), static_cast(gpu::DisplaySettings::Split)); mUi.comboBoxViewMode->addItem(tr("TV"), static_cast(gpu::DisplaySettings::TV)); mUi.comboBoxViewMode->addItem(tr("Gamepad 1"), static_cast(gpu::DisplaySettings::Gamepad1)); mUi.comboBoxViewMode->addItem(tr("Gamepad 2"), static_cast(gpu::DisplaySettings::Gamepad2)); mUi.lineEditSplitSeparation->setValidator(new QDoubleValidator { }); } void DisplaySettingsWidget::loadSettings(const Settings &settings) { auto index = mUi.comboBoxTitleListMode->findData(static_cast(settings.ui.titleListMode)); if (index != -1) { mUi.comboBoxTitleListMode->setCurrentIndex(index); } else { mUi.comboBoxTitleListMode->setCurrentIndex(0); } index = mUi.comboBoxViewMode->findData(static_cast(settings.gpu.display.viewMode)); if (index != -1) { mUi.comboBoxViewMode->setCurrentIndex(index); } else { mUi.comboBoxViewMode->setCurrentIndex(2); } mUi.checkBoxMaintainAspectRatio->setChecked( settings.gpu.display.maintainAspectRatio); mUi.lineEditSplitSeparation->setText( QString { "%1" }.arg(settings.gpu.display.splitSeperation, 0, 'g', 2)); mUi.lineEditBackgroundColour->setColour(QColor { settings.gpu.display.backgroundColour[0], settings.gpu.display.backgroundColour[1], settings.gpu.display.backgroundColour[2], }); } void DisplaySettingsWidget::saveSettings(Settings &settings) { settings.ui.titleListMode = static_cast(mUi.comboBoxTitleListMode->currentData().toInt()); settings.gpu.display.viewMode = static_cast(mUi.comboBoxViewMode->currentData().toInt()); settings.gpu.display.maintainAspectRatio = mUi.checkBoxMaintainAspectRatio->isChecked(); settings.gpu.display.splitSeperation = mUi.lineEditSplitSeparation->text().toDouble(); auto colour = mUi.lineEditBackgroundColour->getColour(); settings.gpu.display.backgroundColour[0] = colour.red(); settings.gpu.display.backgroundColour[1] = colour.green(); settings.gpu.display.backgroundColour[2] = colour.blue(); } ================================================ FILE: src/decaf-qt/src/settings/displaysettingswidget.h ================================================ #pragma once #include "ui_displaysettings.h" #include "settingswidget.h" class DisplaySettingsWidget : public SettingsWidget { Q_OBJECT public: DisplaySettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~DisplaySettingsWidget() = default; void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private: Ui::DisplaySettingsWidget mUi; }; ================================================ FILE: src/decaf-qt/src/settings/inputsettingswidget.cpp ================================================ #include "inputsettingswidget.h" #include "inputeventfilter.h" #include "inputdriver.h" #include #include #include static const char * getButtonName(ButtonType type) { switch (type) { case ButtonType::A: return "A"; case ButtonType::B: return "B"; case ButtonType::X: return "X"; case ButtonType::Y: return "Y"; case ButtonType::R: return "R"; case ButtonType::L: return "L"; case ButtonType::ZR: return "ZR"; case ButtonType::ZL: return "ZL"; case ButtonType::Plus: return "Plus"; case ButtonType::Minus: return "Minus"; case ButtonType::Home: return "Home"; case ButtonType::Sync: return "Sync"; case ButtonType::DpadUp: return "Dpad Up"; case ButtonType::DpadDown: return "Dpad Down"; case ButtonType::DpadLeft: return "Dpad Left"; case ButtonType::DpadRight: return "Dpad Right"; case ButtonType::LeftStickPress: return "Left Stick Press"; case ButtonType::LeftStickUp: return "Left Stick Up"; case ButtonType::LeftStickDown: return "Left Stick Down"; case ButtonType::LeftStickLeft: return "Left Stick Left"; case ButtonType::LeftStickRight: return "Left Stick Right"; case ButtonType::RightStickPress: return "Right Stick Press"; case ButtonType::RightStickUp: return "Right Stick Up"; case ButtonType::RightStickDown: return "Right Stick Down"; case ButtonType::RightStickLeft: return "Right Stick Left"; case ButtonType::RightStickRight: return "Right Stick Right"; default: return ""; } } InputSettingsWidget::InputSettingsWidget(InputDriver *inputDriver, QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f), mInputDriver(inputDriver) { mUi.setupUi(this); // Setup filter so we can catch keyboard input mInputEventFilter = new InputEventFilter(this); qApp->installEventFilter(mInputEventFilter); connect(mInputEventFilter, &InputEventFilter::caughtKeyPress, this, &InputSettingsWidget::caughtKeyPress); connect(mInputDriver, &InputDriver::joystickConnected, this, &InputSettingsWidget::joystickConnected); connect(mInputDriver, &InputDriver::joystickDisconnected, this, &InputSettingsWidget::joystickDisconnected); connect(mInputDriver, &InputDriver::joystickButtonDown, this, &InputSettingsWidget::joystickButton); connect(mInputDriver, &InputDriver::joystickAxisMotion, this, &InputSettingsWidget::joystickAxisMotion); connect(mInputDriver, &InputDriver::joystickHatMotion, this, &InputSettingsWidget::joystickHatMotion); } InputSettingsWidget::~InputSettingsWidget() { qApp->removeEventFilter(mInputEventFilter); delete mInputEventFilter; } void InputSettingsWidget::loadSettings(const Settings &settings) { mInputConfiguration = settings.input; mUi.controllerList->setUpdatesEnabled(false); mUi.controllerList->clear(); auto index = 0; auto gamepad = 0; auto wiimote = 0; auto pro = 0; auto classic = 0; for (auto &controller : mInputConfiguration.controllers) { switch (controller.type) { case ControllerType::Invalid: mUi.controllerList->addItem(QString("New Controller %1").arg(index)); break; case ControllerType::Gamepad: mUi.controllerList->addItem(QString("Gamepad %1").arg(gamepad++)); break; case ControllerType::WiiMote: mUi.controllerList->addItem(QString("Wiimote %1").arg(wiimote++)); break; case ControllerType::ProController: mUi.controllerList->addItem(QString("Pro Controller %1").arg(pro++)); break; case ControllerType::ClassicController: mUi.controllerList->addItem(QString("Classic Controller %1").arg(classic++)); break; } ++index; } mUi.controllerList->setUpdatesEnabled(true); mUi.controllerList->setCurrentRow(0); } void InputSettingsWidget::saveSettings(Settings &settings) { settings.input = mInputConfiguration; } void InputSettingsWidget::addController() { auto id = mInputConfiguration.controllers.size(); mInputConfiguration.controllers.push_back({}); auto item = new QListWidgetItem(QString("New Controller %1").arg(id)); mUi.controllerList->addItem(item); mUi.controllerList->setCurrentItem(item); } void InputSettingsWidget::removeController() { mInputConfiguration.controllers.erase(mInputConfiguration.controllers.begin() + mUi.controllerList->currentRow()); delete mUi.controllerList->takeItem(mUi.controllerList->currentRow()); } void InputSettingsWidget::editController(int index) { if (index == -1) { mUi.controllerType->clear(); return; } auto type = mInputConfiguration.controllers[index].type; mUi.controllerType->setUpdatesEnabled(false); mUi.controllerType->clear(); mUi.controllerType->addItem("Unconfigured", QVariant::fromValue(static_cast(ControllerType::Invalid))); mUi.controllerType->addItem("Wii U Gamepad", QVariant::fromValue(static_cast(ControllerType::Gamepad))); mUi.controllerType->addItem("Wiimote", QVariant::fromValue(static_cast(ControllerType::WiiMote))); mUi.controllerType->addItem("Pro Controller", QVariant::fromValue(static_cast(ControllerType::ProController))); mUi.controllerType->addItem("Classic Controller", QVariant::fromValue(static_cast(ControllerType::ClassicController))); mUi.controllerType->setCurrentIndex(mUi.controllerType->findData(QVariant::fromValue(static_cast(type)))); mUi.controllerType->setUpdatesEnabled(true); } void InputSettingsWidget::assignButton(QPushButton *button, ButtonType type) { if (button->isChecked()) { if (mAssignButton && mAssignButton != button) { mAssignButton->setChecked(false); } mInputEventFilter->enable(); mInputDriver->enableButtonEvents(); mAssignButtonType = type; mAssignButton = button; } else if (mAssignButton == button) { mAssignButtonType = type; mAssignButton = nullptr; mInputEventFilter->disable(); mInputDriver->disableButtonEvents(); } } static QString inputToText(const InputConfiguration::Input &input) { switch (input.source) { case InputConfiguration::Input::KeyboardKey: return QString("Keyboard key %1") .arg(QKeySequence(input.id).toString()); case InputConfiguration::Input::JoystickButton: return QString("Joystick %1 button %2") .arg(input.joystickInstanceId) .arg(input.id); case InputConfiguration::Input::JoystickAxis: return QString("Joystick %1 axis %2 %3") .arg(input.joystickInstanceId) .arg(input.id) .arg(input.invert ? "negative" : "positive"); case InputConfiguration::Input::JoystickHat: { const char *direction = ""; if (input.hatValue == SDL_HAT_UP) { direction = "up"; } else if (input.hatValue == SDL_HAT_LEFT) { direction = "left"; } else if (input.hatValue == SDL_HAT_RIGHT) { direction = "right"; } else if (input.hatValue == SDL_HAT_DOWN) { direction = "down"; } return QString("Joystick %1 Hat %2 %3") .arg(input.joystickInstanceId) .arg(input.id) .arg(direction); } default: return {}; } } void InputSettingsWidget::caughtKeyPress(int key) { // TODO: Assign mAssignButtonType to key! if (mAssignButton) { auto index = mUi.controllerList->currentIndex().row(); auto &controller = mInputConfiguration.controllers[index]; auto &input = controller.inputs[static_cast(mAssignButtonType)]; input.source = InputConfiguration::Input::KeyboardKey; input.id = key; mAssignButton->setText(inputToText(input)); mAssignButton->setChecked(false); } } void InputSettingsWidget::joystickConnected(SDL_JoystickID id, SDL_JoystickGUID guid, const char *name) { mJoysticks.push_back({ id, guid, name }); } void InputSettingsWidget::joystickDisconnected(SDL_JoystickID id, SDL_JoystickGUID guid) { } void InputSettingsWidget::joystickButton(SDL_JoystickID id, SDL_JoystickGUID guid, int button) { if (mAssignButton) { auto index = mUi.controllerList->currentIndex().row(); auto &controller = mInputConfiguration.controllers[index]; auto &input = controller.inputs[static_cast(mAssignButtonType)]; input.source = InputConfiguration::Input::JoystickButton; input.id = button; input.joystickInstanceId = id; input.joystickGuid = guid; mAssignButton->setText(inputToText(input)); mAssignButton->setChecked(false); } } void InputSettingsWidget::joystickAxisMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int axis, float value) { if (value > -0.5f && value < 0.5) { // Ignore until axis has a decent value return; } if (mAssignButton) { auto index = mUi.controllerList->currentIndex().row(); auto &controller = mInputConfiguration.controllers[index]; auto &input = controller.inputs[static_cast(mAssignButtonType)]; input.source = InputConfiguration::Input::JoystickAxis; input.id = axis; input.joystickInstanceId = id; input.joystickGuid = guid; input.invert = (value < 0); mAssignButton->setText(inputToText(input)); mAssignButton->setChecked(false); } } void InputSettingsWidget::joystickHatMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int hat, int value) { const char *direction = nullptr; if (value == SDL_HAT_UP) { direction = "up"; } else if (value == SDL_HAT_LEFT) { direction = "left"; } else if (value == SDL_HAT_RIGHT) { direction = "right"; } else if (value == SDL_HAT_DOWN) { direction = "down"; } else { // Only allow one direction return; } if (mAssignButton) { auto index = mUi.controllerList->currentIndex().row(); auto &controller = mInputConfiguration.controllers[index]; auto &input = controller.inputs[static_cast(mAssignButtonType)]; input.source = InputConfiguration::Input::JoystickHat; input.id = hat; input.hatValue = value; input.joystickInstanceId = id; input.joystickGuid = guid; mAssignButton->setText(inputToText(input)); mAssignButton->setChecked(false); } } void InputSettingsWidget::controllerTypeChanged(int typeIndex) { mUi.buttonList->setUpdatesEnabled(false); qDeleteAll(mUi.buttonList->findChildren("", Qt::FindDirectChildrenOnly)); mUi.buttonList->setUpdatesEnabled(true); if (typeIndex == -1) { return; } auto type = static_cast(mUi.controllerType->itemData(typeIndex).toInt()); auto index = mUi.controllerList->currentIndex().row(); auto &controller = mInputConfiguration.controllers[index]; controller.type = type; mUi.buttonList->setUpdatesEnabled(false); if (type == ControllerType::Gamepad) { auto buttonLayout = reinterpret_cast(mUi.buttonList->layout()); auto addButton = [&](ButtonType type) { auto &input = controller.inputs[static_cast(type)]; auto button = new QPushButton(inputToText(input)); button->setCheckable(true); connect(button, &QPushButton::toggled, std::bind(&InputSettingsWidget::assignButton, this, button, type)); buttonLayout->addRow(getButtonName(type), button); }; addButton(ButtonType::A); addButton(ButtonType::B); addButton(ButtonType::X); addButton(ButtonType::Y); addButton(ButtonType::R); addButton(ButtonType::L); addButton(ButtonType::ZR); addButton(ButtonType::ZL); addButton(ButtonType::Plus); addButton(ButtonType::Minus); addButton(ButtonType::Home); addButton(ButtonType::Sync); addButton(ButtonType::DpadUp); addButton(ButtonType::DpadDown); addButton(ButtonType::DpadLeft); addButton(ButtonType::DpadRight); addButton(ButtonType::LeftStickPress); addButton(ButtonType::LeftStickUp); addButton(ButtonType::LeftStickDown); addButton(ButtonType::LeftStickLeft); addButton(ButtonType::LeftStickRight); addButton(ButtonType::RightStickPress); addButton(ButtonType::RightStickUp); addButton(ButtonType::RightStickDown); addButton(ButtonType::RightStickLeft); addButton(ButtonType::RightStickRight); } mUi.buttonList->setUpdatesEnabled(true); } ================================================ FILE: src/decaf-qt/src/settings/inputsettingswidget.h ================================================ #pragma once #include "ui_inputsettings.h" #include "inputdriver.h" #include "settingswidget.h" #include #include #include #include class InputEventFilter; class SdlEventLoop; struct JoystickInfo { SDL_JoystickID id = -1; SDL_JoystickGUID guid; const char *name = nullptr; }; class InputSettingsWidget : public SettingsWidget { Q_OBJECT public: InputSettingsWidget(InputDriver *inputDriver, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~InputSettingsWidget(); void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private slots: void addController(); void removeController(); void editController(int index); void controllerTypeChanged(int index); void assignButton(QPushButton *button, ButtonType type); void caughtKeyPress(int key); void joystickConnected(SDL_JoystickID id, SDL_JoystickGUID guid, const char *name); void joystickDisconnected(SDL_JoystickID id, SDL_JoystickGUID guid); void joystickButton(SDL_JoystickID id, SDL_JoystickGUID guid, int key); void joystickAxisMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int axis, float value); void joystickHatMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int hat, int value); private: Ui::InputSettingsWidget mUi; QVector mJoysticks; InputEventFilter *mInputEventFilter; ButtonType mAssignButtonType; QPushButton *mAssignButton = nullptr; InputDriver *mInputDriver = nullptr; InputConfiguration mInputConfiguration; }; ================================================ FILE: src/decaf-qt/src/settings/loggingsettingswidget.cpp ================================================ #include "loggingsettingswidget.h" #include static const char *LogLevels[] = { "trace", "debug", "info", "notice", "warning", "error", "critical", "alert", "emerg", "off" }; LoggingSettingsWidget::LoggingSettingsWidget(QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f) { mUi.setupUi(this); for (auto &level : LogLevels) { mUi.comboBoxLogLevel->addItem(level); } } void LoggingSettingsWidget::loadSettings(const Settings &settings) { mUi.checkBoxAsynchronous->setChecked(settings.decaf.log.async); mUi.checkBoxBranchTracing->setChecked(settings.decaf.log.branch_trace); mUi.checkBoxHleTrace->setChecked(settings.decaf.log.hle_trace); mUi.checkBoxHleTraceReturnValue->setChecked(settings.decaf.log.hle_trace_res); mUi.checkBoxOutputFile->setChecked(settings.decaf.log.to_file); mUi.checkBoxOutputStdout->setChecked(settings.decaf.log.to_stdout); mUi.lineEditLogDirectory->setText(QString::fromStdString(settings.decaf.log.directory)); mUi.lineEditLogDirectory->setCursorPosition(0); int index = mUi.comboBoxLogLevel->findText(QString::fromStdString(settings.decaf.log.level)); if (index != -1) { mUi.comboBoxLogLevel->setCurrentIndex(index); } else { mUi.comboBoxLogLevel->setCurrentIndex(1); } mUi.listWidgetHleTraceFilters->clear(); for (auto &filter : settings.decaf.log.hle_trace_filters) { auto item = new QListWidgetItem(QString::fromStdString(filter)); item->setFlags(item->flags() | Qt::ItemIsEditable); mUi.listWidgetHleTraceFilters->addItem(item); } } void LoggingSettingsWidget::saveSettings(Settings &settings) { settings.decaf.log.async = mUi.checkBoxAsynchronous->isChecked(); settings.decaf.log.branch_trace = mUi.checkBoxBranchTracing->isChecked(); settings.decaf.log.hle_trace = mUi.checkBoxHleTrace->isChecked(); settings.decaf.log.hle_trace_res = mUi.checkBoxHleTraceReturnValue->isChecked(); settings.decaf.log.to_file = mUi.checkBoxOutputFile->isChecked(); settings.decaf.log.to_stdout = mUi.checkBoxOutputStdout->isChecked(); settings.decaf.log.directory = mUi.lineEditLogDirectory->text().toStdString(); settings.decaf.log.level = mUi.comboBoxLogLevel->currentText().toStdString(); settings.decaf.log.hle_trace_filters.clear(); for (auto i = 0; i < mUi.listWidgetHleTraceFilters->count(); ++i) { settings.decaf.log.hle_trace_filters.emplace_back(mUi.listWidgetHleTraceFilters->item(i)->text().toStdString()); } } void LoggingSettingsWidget::addTraceFilter() { auto item = new QListWidgetItem(QString::fromStdString("+.*")); item->setFlags(item->flags() | Qt::ItemIsEditable); mUi.listWidgetHleTraceFilters->addItem(item); mUi.listWidgetHleTraceFilters->editItem(item); } void LoggingSettingsWidget::removeTraceFilter() { delete mUi.listWidgetHleTraceFilters->currentItem(); } void LoggingSettingsWidget::browseLogPath() { auto path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), mUi.lineEditLogDirectory->text()); if (!path.isEmpty()) { mUi.lineEditLogDirectory->setText(path); } } ================================================ FILE: src/decaf-qt/src/settings/loggingsettingswidget.h ================================================ #pragma once #include "ui_loggingsettings.h" #include "settingswidget.h" class LoggingSettingsWidget : public SettingsWidget { Q_OBJECT public: LoggingSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~LoggingSettingsWidget() = default; void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private slots: void addTraceFilter(); void removeTraceFilter(); void browseLogPath(); private: Ui::LoggingSettingsWidget mUi; }; ================================================ FILE: src/decaf-qt/src/settings/settingsdialog.cpp ================================================ #include "settingsdialog.h" #include "settings.h" #include "audiosettingswidget.h" #include "contentsettingswidget.h" #include "debugsettingswidget.h" #include "displaysettingswidget.h" #include "inputsettingswidget.h" #include "loggingsettingswidget.h" #include "systemsettingswidget.h" SettingsDialog::SettingsDialog(SettingsStorage *settingsStorage, InputDriver *inputDriver, SettingsTab tab, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f), mSettingsStorage(settingsStorage) { mUi.setupUi(this); auto inputSettings = new InputSettingsWidget { inputDriver }; mUi.tabWidget->addTab(inputSettings, tr("Input")); mSettings.push_back(inputSettings); auto contentSettings = new ContentSettingsWidget { }; mUi.tabWidget->addTab(contentSettings, tr("Content")); mSettings.push_back(contentSettings); auto systemSettings = new SystemSettingsWidget { }; mUi.tabWidget->addTab(systemSettings, tr("System")); mSettings.push_back(systemSettings); auto displaySettings = new DisplaySettingsWidget { }; mUi.tabWidget->addTab(displaySettings, tr("Display")); mSettings.push_back(displaySettings); auto audioSettings = new AudioSettingsWidget { }; mUi.tabWidget->addTab(audioSettings, tr("Audio")); mSettings.push_back(audioSettings); auto loggingSettings = new LoggingSettingsWidget { }; mUi.tabWidget->addTab(loggingSettings, tr("Logging")); mSettings.push_back(loggingSettings); auto debugSettings = new DebugSettingsWidget { }; mUi.tabWidget->addTab(debugSettings, tr("Debug")); mSettings.push_back(debugSettings); // Load all the settings auto settings = mSettingsStorage->get(); for (auto &settingsWidget : mSettings) { settingsWidget->loadSettings(*settings); } if (tab == SettingsTab::Default || tab == SettingsTab::Input) { mUi.tabWidget->setCurrentWidget(inputSettings); } else if (tab == SettingsTab::Content) { mUi.tabWidget->setCurrentWidget(contentSettings); } else if (tab == SettingsTab::System) { mUi.tabWidget->setCurrentWidget(systemSettings); } else if (tab == SettingsTab::Display) { mUi.tabWidget->setCurrentWidget(displaySettings); } else if (tab == SettingsTab::Audio) { mUi.tabWidget->setCurrentWidget(audioSettings); } else if (tab == SettingsTab::Logging) { mUi.tabWidget->setCurrentWidget(loggingSettings); } else if (tab == SettingsTab::Debug) { mUi.tabWidget->setCurrentWidget(debugSettings); } } void SettingsDialog::buttonBoxClicked(QAbstractButton *button) { auto role = mUi.buttonBox->buttonRole(button); auto settings = mSettingsStorage->get(); // On Reset we should load settings if (role == QDialogButtonBox::ResetRole) { for (auto &settingsWidget : mSettings) { settingsWidget->loadSettings(*settings); } } // On Accept or Apply we should save settings if (role == QDialogButtonBox::AcceptRole || role == QDialogButtonBox::ApplyRole) { // Update config with settings auto updatedSettings = *settings; for (auto &settingsWidget : mSettings) { settingsWidget->saveSettings(updatedSettings); } mSettingsStorage->set(updatedSettings); } if (role == QDialogButtonBox::AcceptRole) { accept(); } else if (role == QDialogButtonBox::RejectRole) { reject(); } } ================================================ FILE: src/decaf-qt/src/settings/settingsdialog.h ================================================ #pragma once #include "ui_settings.h" #include "settingswidget.h" #include #include class DecafInterface; class InputDriver; class SettingsWidget; class SettingsStorage; class QAbstractButton; enum class SettingsTab { Default = 0, System, Content, Input, Display, Audio, Logging, Debug, }; class SettingsDialog : public QDialog { Q_OBJECT public: SettingsDialog(SettingsStorage *settingsStorage, InputDriver *inputDriver, SettingsTab openTab = SettingsTab::Default, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~SettingsDialog() = default; private slots: void buttonBoxClicked(QAbstractButton *button); private: Ui::SettingsDialog mUi; QVector mSettings; SettingsStorage *mSettingsStorage; }; ================================================ FILE: src/decaf-qt/src/settings/settingswidget.h ================================================ #pragma once #include "settings.h" #include class SettingsWidget : public QWidget { public: SettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QWidget(parent, f) {} virtual ~SettingsWidget() = default; virtual void loadSettings(const Settings &settings) = 0; virtual void saveSettings(Settings &settings) = 0; private: }; ================================================ FILE: src/decaf-qt/src/settings/systemsettingswidget.cpp ================================================ #include "systemsettingswidget.h" #include static constexpr const char *LibraryList[] = { "avm.rpl", "camera.rpl", "coreinit.rpl", "dc.rpl", "dmae.rpl", "drmapp.rpl", "erreula.rpl", "gx2.rpl", "h264.rpl", "lzma920.rpl", "mic.rpl", "nfc.rpl", "nio_prof.rpl", "nlibcurl.rpl", "nlibnss2.rpl", "nlibnss.rpl", "nn_ac.rpl", "nn_acp.rpl", "nn_act.rpl", "nn_aoc.rpl", "nn_boss.rpl", "nn_ccr.rpl", "nn_cmpt.rpl", "nn_dlp.rpl", "nn_ec.rpl", "nn_fp.rpl", "nn_hai.rpl", "nn_hpad.rpl", "nn_idbe.rpl", "nn_ndm.rpl", "nn_nets2.rpl", "nn_nfp.rpl", "nn_nim.rpl", "nn_olv.rpl", "nn_pdm.rpl", "nn_save.rpl", "nn_sl.rpl", "nn_spm.rpl", "nn_temp.rpl", "nn_uds.rpl", "nn_vctl.rpl", "nsysccr.rpl", "nsyshid.rpl", "nsyskbd.rpl", "nsysnet.rpl", "nsysuhs.rpl", "nsysuvd.rpl", "ntag.rpl", "padscore.rpl", "proc_ui.rpl", "sndcore2.rpl", "snd_core.rpl", "snduser2.rpl", "snd_user.rpl", "swkbd.rpl", "sysapp.rpl", "tcl.rpl", "tve.rpl", "uac.rpl", "uac_rpl.rpl", "usb_mic.rpl", "uvc.rpl", "uvd.rpl", "vpadbase.rpl", "vpad.rpl", "zlib125.rpl", }; static constexpr const char *DisabledLibraryList[] = { "coreinit.rpl", "gx2.rpl", "tcl.rpl", }; using SystemRegion = decaf::SystemRegion; SystemSettingsWidget::SystemSettingsWidget(QWidget *parent, Qt::WindowFlags f) : SettingsWidget(parent, f) { mUi.setupUi(this); for (auto library : LibraryList) { auto item = new QListWidgetItem(library); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); if (std::find(std::begin(DisabledLibraryList), std::end(DisabledLibraryList), library) != std::end(DisabledLibraryList)) { item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } mUi.listWidgetLleWhitelist->addItem(item); } mUi.comboBoxRegion->addItem(tr("Japan"), static_cast(SystemRegion::Japan)); mUi.comboBoxRegion->addItem(tr("USA"), static_cast(SystemRegion::USA)); mUi.comboBoxRegion->addItem(tr("Europe"), static_cast(SystemRegion::Europe)); mUi.comboBoxRegion->addItem(tr("Unknown8"), static_cast(SystemRegion::Unknown8)); mUi.comboBoxRegion->addItem(tr("China"), static_cast(SystemRegion::China)); mUi.comboBoxRegion->addItem(tr("Korea"), static_cast(SystemRegion::Korea)); mUi.comboBoxRegion->addItem(tr("Taiwan"), static_cast(SystemRegion::Taiwan)); mUi.lineEditTimeScale->setValidator(new QDoubleValidator(0.01, 100, 2, this)); } void SystemSettingsWidget::loadSettings(const Settings &settings) { int index = mUi.comboBoxRegion->findData(static_cast(settings.decaf.system.region)); if (index != -1) { mUi.comboBoxRegion->setCurrentIndex(index); } else { mUi.comboBoxRegion->setCurrentIndex(2); } for (auto i = 0; i < mUi.listWidgetLleWhitelist->count(); ++i) { auto item = mUi.listWidgetLleWhitelist->item(i); auto name = item->text().toStdString(); if (std::find(settings.decaf.system.lle_modules.begin(), settings.decaf.system.lle_modules.end(), name) != settings.decaf.system.lle_modules.end()) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } } } void SystemSettingsWidget::saveSettings(Settings &settings) { settings.decaf.system.time_scale = mUi.lineEditTimeScale->text().toDouble(); settings.decaf.system.region = static_cast(mUi.comboBoxRegion->currentData().toInt()); settings.decaf.system.lle_modules.clear(); for (auto i = 0; i < mUi.listWidgetLleWhitelist->count(); ++i) { auto item = mUi.listWidgetLleWhitelist->item(i); if (item->checkState() == Qt::Checked) { settings.decaf.system.lle_modules.emplace_back(item->text().toStdString()); } } } ================================================ FILE: src/decaf-qt/src/settings/systemsettingswidget.h ================================================ #pragma once #include "ui_systemsettings.h" #include "settingswidget.h" class SystemSettingsWidget : public SettingsWidget { Q_OBJECT public: SystemSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~SystemSettingsWidget() = default; void loadSettings(const Settings &settings) override; void saveSettings(Settings &settings) override; private: Ui::SystemSettingsWidget mUi; }; ================================================ FILE: src/decaf-qt/src/settings.cpp ================================================ #include "settings.h" #include "inputdriver.h" #include #include #include static bool loadFromTOML(const toml::table &config, InputConfiguration &inputConfiguration); static bool saveToTOML(toml::table &config, const InputConfiguration &inputConfiguration); static bool loadFromTOML(const toml::table &config, SoundSettings &soundSettings); static bool saveToTOML(toml::table &config, const SoundSettings &soundSettings); static bool loadFromTOML(const toml::table &config, UiSettings &uiSettings); static bool saveToTOML(toml::table &config, const UiSettings &uiSettings); bool loadSettings(const std::string &path, Settings &settings) { try { toml::table toml = toml::parse_file(path); config::loadFromTOML(toml, settings.cpu); config::loadFromTOML(toml, settings.decaf); config::loadFromTOML(toml, settings.gpu); loadFromTOML(toml, settings.input); loadFromTOML(toml, settings.sound); loadFromTOML(toml, settings.ui); return true; } catch (const toml::parse_error &) { return false; } } bool saveSettings(const std::string &path, const Settings &settings) { toml::table toml; try { // Read current file and modify that toml = toml::parse_file(path); } catch (const toml::parse_error &) { } // Update it config::saveToTOML(toml, settings.decaf); config::saveToTOML(toml, settings.cpu); config::saveToTOML(toml, settings.gpu); saveToTOML(toml, settings.input); saveToTOML(toml, settings.sound); saveToTOML(toml, settings.ui); // Write to file std::ofstream out { path }; if (!out.is_open()) { return false; } out << toml; return true; } static const char * getConfigButtonName(ButtonType type) { switch (type) { case ButtonType::A: return "button_a"; case ButtonType::B: return "button_b"; case ButtonType::X: return "button_x"; case ButtonType::Y: return "button_y"; case ButtonType::R: return "button_r"; case ButtonType::L: return "button_l"; case ButtonType::ZR: return "button_zr"; case ButtonType::ZL: return "button_zl"; case ButtonType::Plus: return "button_plus"; case ButtonType::Minus: return "button_minus"; case ButtonType::Home: return "button_home"; case ButtonType::Sync: return "button_sync"; case ButtonType::DpadUp: return "dpad_up"; case ButtonType::DpadDown: return "dpad_down"; case ButtonType::DpadLeft: return "dpad_left"; case ButtonType::DpadRight: return "dpad_right"; case ButtonType::LeftStickPress: return "left_stick_press"; case ButtonType::LeftStickUp: return "left_stick_up"; case ButtonType::LeftStickDown: return "left_stick_down"; case ButtonType::LeftStickLeft: return "left_stick_left"; case ButtonType::LeftStickRight: return "left_stick_right"; case ButtonType::RightStickPress: return "right_stick_press"; case ButtonType::RightStickUp: return "right_stick_up"; case ButtonType::RightStickDown: return "right_stick_down"; case ButtonType::RightStickLeft: return "right_stick_left"; case ButtonType::RightStickRight: return "right_stick_right"; default: return ""; } } bool loadFromTOML(const toml::table &config, InputConfiguration &inputConfiguration) { auto controllers = config.at_path("input.controller").as_array(); if (!controllers) { return true; } for (const auto &controllerConfig : *controllers) { auto controllerConfigTable = controllerConfig.as_table(); if (!controllerConfigTable) { continue; } auto &controller = inputConfiguration.controllers.emplace_back(); auto controllerType = controllerConfigTable->get_as("type"); if (!controllerType) { continue; } else if (**controllerType == "gamepad") { controller.type = ControllerType::Gamepad; } else if (**controllerType == "wiimote") { controller.type = ControllerType::WiiMote; } else if (**controllerType == "pro") { controller.type = ControllerType::ProController; } else if (**controllerType == "classic") { controller.type = ControllerType::ClassicController; } else { continue; } auto readInputConfig = [](const toml::table &controllerConfig, InputConfiguration::Controller &controller, ButtonType buttonType) { auto &input = controller.inputs[static_cast(buttonType)]; auto buttonConfig = controllerConfig.get_as(getConfigButtonName(buttonType)); if (buttonConfig) { if (auto guid = buttonConfig->get_as("sdl_joystick_guid"); guid) { input.joystickGuid = SDL_JoystickGetGUIDFromString(guid->get().c_str()); } if (auto id = buttonConfig->get_as("sdl_joystick_duplicate_id"); id) { input.joystickDuplicateId = id->get(); } if (auto key = buttonConfig->get_as("key"); key) { input.source = InputConfiguration::Input::KeyboardKey; input.id = key->get(); } if (auto button = buttonConfig->get_as("button"); button) { input.source = InputConfiguration::Input::JoystickButton; input.id = button->get(); } if (auto axis = buttonConfig->get_as("axis"); axis) { input.source = InputConfiguration::Input::JoystickAxis; input.id = axis->get(); } if (auto hat = buttonConfig->get_as("hat"); hat) { input.source = InputConfiguration::Input::JoystickHat; input.id = hat->get(); input.hatValue = 0; } if (auto hatValue = buttonConfig->get_as("hat_value"); hatValue) { input.hatValue = hatValue->get(); } if (auto invert = buttonConfig->get_as("invert"); invert) { input.invert = invert->get(); } } }; for (auto i = 0u; i < static_cast(ButtonType::MaxButtonType); ++i) { readInputConfig(*controllerConfigTable, controller, static_cast(i)); } } return true; } bool saveToTOML(toml::table &config, const InputConfiguration &inputConfiguration) { auto input = config.insert("input", toml::table()).first->second.as_table(); auto controllers = toml::array(); if (input->contains("controller")) { input->erase("controller"); } for (auto &controller : inputConfiguration.controllers) { auto controllerConfig = toml::table(); if (controller.type == ControllerType::Gamepad) { controllerConfig.insert_or_assign("type", "gamepad"); } else if (controller.type == ControllerType::WiiMote) { controllerConfig.insert_or_assign("type", "wiimote"); } else if (controller.type == ControllerType::ProController) { controllerConfig.insert_or_assign("type", "pro"); } else if (controller.type == ControllerType::ClassicController) { controllerConfig.insert_or_assign("type", "classic"); } else { continue; } for (auto i = 0u; i < static_cast(ButtonType::MaxButtonType); ++i) { auto buttonType = static_cast(i); auto &input = controller.inputs[i]; auto inputConfig = toml::table(); if (input.source == InputConfiguration::Input::Unassigned) { continue; } else if (input.source == InputConfiguration::Input::KeyboardKey) { inputConfig.insert_or_assign("key", input.id); } else if (input.source == InputConfiguration::Input::JoystickAxis) { char guidBuffer[33]; SDL_JoystickGetGUIDString(input.joystickGuid, guidBuffer, 33); inputConfig.insert_or_assign("sdl_joystick_guid", guidBuffer); inputConfig.insert_or_assign("sdl_joystick_duplicate_id", input.joystickDuplicateId); inputConfig.insert_or_assign("axis", input.id); inputConfig.insert_or_assign("invert", input.invert); } else if (input.source == InputConfiguration::Input::JoystickButton) { char guidBuffer[33]; SDL_JoystickGetGUIDString(input.joystickGuid, guidBuffer, 33); inputConfig.insert_or_assign("sdl_joystick_guid", guidBuffer); inputConfig.insert_or_assign("sdl_joystick_duplicate_id", input.joystickDuplicateId); inputConfig.insert_or_assign("button", input.id); } else if (input.source == InputConfiguration::Input::JoystickHat) { char guidBuffer[33]; SDL_JoystickGetGUIDString(input.joystickGuid, guidBuffer, 33); inputConfig.insert_or_assign("sdl_joystick_guid", guidBuffer); inputConfig.insert_or_assign("sdl_joystick_duplicate_id", input.joystickDuplicateId); inputConfig.insert_or_assign("hat", input.id); inputConfig.insert_or_assign("hat_value", input.hatValue); } controllerConfig.insert_or_assign(getConfigButtonName(buttonType), inputConfig); } controllers.push_back(std::move(controllerConfig)); } input->insert_or_assign("controller", std::move(controllers)); return true; } bool loadFromTOML(const toml::table &config, SoundSettings &soundSettings) { config::readValue(config, "sound.playback_enabled", soundSettings.playbackEnabled); return true; } bool saveToTOML(toml::table &config, const SoundSettings &soundSettings) { auto sound = config.insert("sound", toml::table()).first->second.as_table(); sound->insert_or_assign("playback_enabled", soundSettings.playbackEnabled); return true; } static const char * translateTitleListMode(UiSettings::TitleListMode mode) { if (mode == UiSettings::TitleListMode::TitleList) { return "list"; } else if (mode == UiSettings::TitleListMode::TitleGrid) { return "grid"; } return ""; } static std::optional translateTitleListMode(const std::string &text) { if (text == "list") { return UiSettings::TitleListMode::TitleList; } else if (text == "grid") { return UiSettings::TitleListMode::TitleGrid; } return { }; } bool loadFromTOML(const toml::table &config, UiSettings &uiSettings) { std::string titleListModeText; config::readValue(config, "ui.title_list_mode", titleListModeText); if (auto mode = translateTitleListMode(titleListModeText); mode) { uiSettings.titleListMode = *mode; } return true; } bool saveToTOML(toml::table &config, const UiSettings &uiSettings) { auto ui = config.insert("ui", toml::table()).first->second.as_table(); if (auto text = translateTitleListMode(uiSettings.titleListMode); text) { ui->insert_or_assign("title_list_mode", text); } return true; } ================================================ FILE: src/decaf-qt/src/settings.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include enum class ButtonType { A, B, X, Y, R, L, ZR, ZL, Plus, Minus, Home, Sync, DpadUp, DpadDown, DpadLeft, DpadRight, LeftStickPress, LeftStickUp, LeftStickDown, LeftStickLeft, LeftStickRight, RightStickPress, RightStickUp, RightStickDown, RightStickLeft, RightStickRight, MaxButtonType, }; enum class ControllerType { Invalid, Gamepad, WiiMote, ProController, ClassicController, }; struct UiSettings { enum TitleListMode { TitleList, TitleGrid, }; TitleListMode titleListMode = TitleList; }; struct InputConfiguration { struct Input { enum Source { Unassigned, KeyboardKey, JoystickButton, JoystickAxis, JoystickHat, }; Source source = Source::Unassigned; int id = -1; // Only valid for Source==Joystick* SDL_JoystickGUID joystickGuid; int joystickDuplicateId = 0; // For when there are multiple controllers with the same GUID // Only valid for Type==JoystickAxis bool invert = false; // TODO: Do we want to allow invert on everything? // TODO: deadzone? etc // Only valid for Source==JoystickHat int hatValue = -1; // Not loaded from config file, assigned on controller connect! SDL_Joystick *joystick = nullptr; SDL_JoystickID joystickInstanceId = -1; }; struct Controller { ControllerType type = ControllerType::Invalid; std::array(ButtonType::MaxButtonType)> inputs { }; }; std::vector controllers; std::array wpad = { nullptr, nullptr, nullptr, nullptr }; std::array vpad = { nullptr, nullptr }; }; struct SoundSettings { //! Whether audio playback is enabled. bool playbackEnabled = false; }; struct Settings { decaf::Settings decaf = { }; cpu::Settings cpu = { }; gpu::Settings gpu = { }; UiSettings ui = { }; InputConfiguration input = { }; SoundSettings sound = { }; }; bool loadSettings(const std::string &path, Settings &settings); bool saveSettings(const std::string &path, const Settings &settings); class SettingsStorage : public QObject { Q_OBJECT public: SettingsStorage(std::string path) : mSettingsStorage(std::make_shared()), mPath(std::move(path)) { loadSettings(mPath, *mSettingsStorage); } std::string path() { std::lock_guard lock { mMutex }; return mPath; } std::shared_ptr get() { std::lock_guard lock { mMutex }; return mSettingsStorage; } void set(const Settings &settings) { mMutex.lock(); mSettingsStorage = std::make_shared(settings); saveSettings(mPath, settings); mMutex.unlock(); emit settingsChanged(); } signals: void settingsChanged(); private: std::mutex mMutex; std::string mPath; std::shared_ptr mSettingsStorage; }; ================================================ FILE: src/decaf-qt/src/softwarekeyboarddriver.cpp ================================================ #include "softwarekeyboarddriver.h" SoftwareKeyboardDriver::SoftwareKeyboardDriver(QObject *parent) : QObject(parent) { } void SoftwareKeyboardDriver::acceptInput(QString text) { decaf::SoftwareKeyboardDriver::setInputString(text.toStdU16String()); decaf::SoftwareKeyboardDriver::accept(); } void SoftwareKeyboardDriver::rejectInput() { decaf::SoftwareKeyboardDriver::reject(); } void SoftwareKeyboardDriver::onOpen(std::u16string defaultText) { emit open(QString::fromStdU16String(defaultText)); } void SoftwareKeyboardDriver::onClose() { emit close(); } void SoftwareKeyboardDriver::onInputStringChanged(std::u16string text) { emit inputStringChanged(QString::fromStdU16String(text)); } ================================================ FILE: src/decaf-qt/src/softwarekeyboarddriver.h ================================================ #pragma once #include #include class QInputDialog; class QWidget; class SoftwareKeyboardDriver : public QObject, public decaf::SoftwareKeyboardDriver { Q_OBJECT public: SoftwareKeyboardDriver(QObject *parent); void acceptInput(QString text); void rejectInput(); signals: void open(QString text); void close(); void inputStringChanged(QString text); private: void onOpen(std::u16string defaultText) override; void onClose() override; void onInputStringChanged(std::u16string text) override; }; ================================================ FILE: src/decaf-qt/src/sounddriver.cpp ================================================ #include "sounddriver.h" #ifdef QT_MULTIMEDIA_LIB bool SoundDriver::start(unsigned outputRate, unsigned numChannels) { QAudioFormat format; format.setChannelCount(numChannels); format.setCodec("audio/pcm"); format.setSampleRate(outputRate); format.setSampleSize(16); format.setSampleType(QAudioFormat::SignedInt); format.setByteOrder(QAudioFormat::LittleEndian); QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); if (!info.isFormatSupported(format)) { qWarning() << "Raw audio format not supported by backend, cannot play audio."; return false; } mAudioOutput = new QAudioOutput(format, this); mAudioIo = mAudioOutput->start(); return true; } void SoundDriver::output(int16_t *samples, unsigned numSamples) { mAudioIo->write(reinterpret_cast(samples), numSamples * sizeof(int16_t)); } void SoundDriver::stop() { mAudioOutput->stop(); delete mAudioOutput; mAudioOutput = nullptr; mAudioIo = nullptr; } #else #include #include #include SoundDriver::SoundDriver(SettingsStorage *settingsStorage) : mSettingsStorage(settingsStorage) { QObject::connect(mSettingsStorage, &SettingsStorage::settingsChanged, this, &SoundDriver::settingsChanged); settingsChanged(); } SoundDriver::~SoundDriver() { SDL_CloseAudio(); } void SoundDriver::settingsChanged() { auto settings = mSettingsStorage->get(); if (mPlaybackEnabled != settings->sound.playbackEnabled) { mPlaybackEnabled = settings->sound.playbackEnabled; if (settings->sound.playbackEnabled) { SDL_PauseAudio(0); } else { SDL_PauseAudio(1); } } } bool SoundDriver::start(unsigned outputRate, unsigned numChannels) { auto mFrameLength = 30; // TODO: Config mNumChannelsIn = numChannels; mNumChannelsOut = std::min(numChannels, 2u); // TODO: support surround output mOutputFrameLen = mFrameLength * (outputRate / 1000); // Set up the ring buffer with enough space for 3 output frames of audio mOutputBuffer.resize(mOutputFrameLen * mNumChannelsOut * 3); mBufferWritePos = 0; mBufferReadPos = 0; SDL_AudioSpec audiospec; audiospec.format = AUDIO_S16LSB; audiospec.freq = outputRate; audiospec.channels = static_cast(mNumChannelsOut); audiospec.samples = static_cast(mOutputFrameLen); audiospec.callback = sdlCallback; audiospec.userdata = this; if (SDL_OpenAudio(&audiospec, nullptr) != 0) { return false; } SDL_PauseAudio(0); return true; } void SoundDriver::output(int16_t *samples, unsigned numSamples) { if (!mPlaybackEnabled) { return; } // Discard channels from the input if necessary. if (mNumChannelsIn != mNumChannelsOut) { decaf_check(mNumChannelsOut < mNumChannelsIn); for (auto sample = 1u; sample < numSamples; ++sample) { for (auto channel = 0u; channel < mNumChannelsOut; channel++) { samples[sample * mNumChannelsOut + channel] = samples[sample * mNumChannelsIn + channel]; } } } // Copy to the output buffer, ignoring the possibility of overrun // (which should never happen anyway). auto numSamplesOut = static_cast(numSamples * mNumChannelsOut); while (mBufferWritePos + numSamplesOut >= mOutputBuffer.size()) { auto samplesToCopy = mOutputBuffer.size() - mBufferWritePos; std::memcpy(&mOutputBuffer[mBufferWritePos], samples, samplesToCopy * 2); mBufferWritePos = 0; samples += samplesToCopy; numSamplesOut -= samplesToCopy; } std::memcpy(&mOutputBuffer[mBufferWritePos], samples, numSamplesOut * 2); mBufferWritePos += numSamplesOut; } void SoundDriver::stop() { SDL_CloseAudio(); } void SoundDriver::sdlCallback(void *instance_, Uint8 *stream_, int size) { SoundDriver *instance = reinterpret_cast(instance_); int16_t *stream = reinterpret_cast(stream_); decaf_check(size >= 0); decaf_check(size % (2 * instance->mNumChannelsOut) == 0); auto numSamples = static_cast(size) / 2; auto samplesAvail = (instance->mBufferWritePos + instance->mOutputBuffer.size() - instance->mBufferReadPos) % instance->mOutputBuffer.size(); if (samplesAvail < numSamples) { // Rather than outputting the partial frame, output a full frame of // silence to give audio generation a chance to catch up. std::memset(stream, 0, size); } else { decaf_check(instance->mBufferReadPos + numSamples <= instance->mOutputBuffer.size()); std::memcpy(stream, &instance->mOutputBuffer[instance->mBufferReadPos], size); instance->mBufferReadPos = (instance->mBufferReadPos + numSamples) % instance->mOutputBuffer.size(); } } #endif ================================================ FILE: src/decaf-qt/src/sounddriver.h ================================================ #pragma once #include "settings.h" #ifdef QT_MULTIMEDIA_LIB #include #include #include #include class SoundDriver : public QObject, public decaf::SoundDriver { public: ~SoundDriver() override = default; bool start(unsigned outputRate, unsigned numChannels) override; void output(int16_t *samples, unsigned numSamples) override; void stop() override; private: QAudioOutput *mAudioOutput; QIODevice *mAudioIo; }; #else #include #include #include #include #include #include class SettingsStorage; class SoundDriver : public QObject, public decaf::SoundDriver { Q_OBJECT public: SoundDriver(SettingsStorage *settingsStorage); ~SoundDriver() override; private: bool start(unsigned outputRate, unsigned numChannels) override; void output(int16_t *samples, unsigned numSamples) override; void stop() override; private slots: void settingsChanged(); private: SettingsStorage *mSettingsStorage = nullptr; std::atomic mPlaybackEnabled { true }; // Number of channels of data we receive in output() unsigned mNumChannelsIn = 0; // Number of channels we send to the audio device unsigned mNumChannelsOut = 0; // Number of samples (per channel) in an output frame unsigned mOutputFrameLen = 0; // Output buffer (ring buffer): written by output(), read by SDL callback std::vector mOutputBuffer = { }; // Index of next sample (array element) to write size_t mBufferWritePos = 0u; // Index of next sample (array element) to read size_t mBufferReadPos = 0u; static void sdlCallback(void *instance, Uint8 *stream, int size); }; #endif ================================================ FILE: src/decaf-qt/src/tgahandler.cpp ================================================ /* This file is part of the KDE project Copyright (C) 2003 Dominik Seichter Copyright (C) 2004 Ignacio Castao This program is free software; you can redistribute it and/or modify it under the terms of the Lesser GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* this code supports: * reading: * uncompressed and run length encoded indexed, grey and color tga files. * image types 1, 2, 3, 9, 10 and 11. * only RGB color maps with no more than 256 colors. * pixel formats 8, 16, 24 and 32. * writing: * uncompressed true color tga files */ #include "tgahandler.h" #include #include #include #include typedef quint32 uint; typedef quint16 ushort; typedef quint8 uchar; namespace // Private. { // Header format of saved files. uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; enum TGAType { TGA_TYPE_INDEXED = 1, TGA_TYPE_RGB = 2, TGA_TYPE_GREY = 3, TGA_TYPE_RLE_INDEXED = 9, TGA_TYPE_RLE_RGB = 10, TGA_TYPE_RLE_GREY = 11 }; #define TGA_INTERLEAVE_MASK 0xc0 #define TGA_INTERLEAVE_NONE 0x00 #define TGA_INTERLEAVE_2WAY 0x40 #define TGA_INTERLEAVE_4WAY 0x80 #define TGA_ORIGIN_MASK 0x30 #define TGA_ORIGIN_LEFT 0x00 #define TGA_ORIGIN_RIGHT 0x10 #define TGA_ORIGIN_LOWER 0x00 #define TGA_ORIGIN_UPPER 0x20 /** Tga Header. */ struct TgaHeader { uchar id_length; uchar colormap_type; uchar image_type; ushort colormap_index; ushort colormap_length; uchar colormap_size; ushort x_origin; ushort y_origin; ushort width; ushort height; uchar pixel_size; uchar flags; enum { SIZE = 18 }; // const static int SIZE = 18; }; static QDataStream &operator>> (QDataStream &s, TgaHeader &head) { s >> head.id_length; s >> head.colormap_type; s >> head.image_type; s >> head.colormap_index; s >> head.colormap_length; s >> head.colormap_size; s >> head.x_origin; s >> head.y_origin; s >> head.width; s >> head.height; s >> head.pixel_size; s >> head.flags; /*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type; qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size; qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/ return s; } static bool IsSupported(const TgaHeader &head) { if (head.image_type != TGA_TYPE_INDEXED && head.image_type != TGA_TYPE_RGB && head.image_type != TGA_TYPE_GREY && head.image_type != TGA_TYPE_RLE_INDEXED && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) { return false; } if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) { if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) { return false; } } if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) { if (head.colormap_type != 0) { return false; } } if (head.width == 0 || head.height == 0) { return false; } if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { return false; } return true; } struct Color555 { ushort b : 5; ushort g : 5; ushort r : 5; }; struct TgaHeaderInfo { bool rle; bool pal; bool rgb; bool grey; TgaHeaderInfo(const TgaHeader &tga) : rle(false), pal(false), rgb(false), grey(false) { switch (tga.image_type) { case TGA_TYPE_RLE_INDEXED: rle = true; Q_FALLTHROUGH(); // no break is intended! case TGA_TYPE_INDEXED: pal = true; break; case TGA_TYPE_RLE_RGB: rle = true; Q_FALLTHROUGH(); // no break is intended! case TGA_TYPE_RGB: rgb = true; break; case TGA_TYPE_RLE_GREY: rle = true; Q_FALLTHROUGH(); // no break is intended! case TGA_TYPE_GREY: grey = true; break; default: // Error, unknown image type. break; } } }; static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img) { // Create image. img = QImage(tga.width, tga.height, QImage::Format_RGB32); TgaHeaderInfo info(tga); // Bits 0-3 are the numbers of alpha bits (can be zero!) const int numAlphaBits = tga.flags & 0xf; // However alpha exists only in the 32 bit format. if ((tga.pixel_size == 32) && (tga.flags & 0xf)) { img = QImage(tga.width, tga.height, QImage::Format_ARGB32); if (numAlphaBits > 8) { return false; } } uint pixel_size = (tga.pixel_size / 8); qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size; if (size < 1) { // qDebug() << "This TGA file is broken with size " << size; return false; } // Read palette. static const int max_palette_size = 768; char palette[max_palette_size]; if (info.pal) { // @todo Support palettes in other formats! const int palette_size = 3 * tga.colormap_length; if (palette_size > max_palette_size) { return false; } const int dataRead = s.readRawData(palette, palette_size); if (dataRead < 0) { return false; } if (dataRead < max_palette_size) { memset(&palette[dataRead], 0, max_palette_size - dataRead); } } // Allocate image. uchar *const image = reinterpret_cast(malloc(size)); if (!image) { return false; } bool valid = true; if (info.rle) { // Decode image. char *dst = (char *)image; qint64 num = size; while (num > 0) { if (s.atEnd()) { valid = false; break; } // Get packet header. uchar c; s >> c; uint count = (c & 0x7f) + 1; num -= count * pixel_size; if (num < 0) { valid = false; break; } if (c & 0x80) { // RLE pixels. assert(pixel_size <= 8); char pixel[8]; const int dataRead = s.readRawData(pixel, pixel_size); if (dataRead < (int)pixel_size) { memset(&pixel[dataRead], 0, pixel_size - dataRead); } do { memcpy(dst, pixel, pixel_size); dst += pixel_size; } while (--count); } else { // Raw pixels. count *= pixel_size; const int dataRead = s.readRawData(dst, count); if (dataRead < 0) { free(image); return false; } if ((uint)dataRead < count) { memset(&dst[dataRead], 0, count - dataRead); } dst += count; } } } else { // Read raw image. const int dataRead = s.readRawData((char *)image, size); if (dataRead < 0) { free(image); return false; } if (dataRead < size) { memset(&image[dataRead], 0, size - dataRead); } } if (!valid) { free(image); return false; } // Convert image to internal format. int y_start, y_step, y_end; if (tga.flags & TGA_ORIGIN_UPPER) { y_start = 0; y_step = 1; y_end = tga.height; } else { y_start = tga.height - 1; y_step = -1; y_end = -1; } uchar *src = image; for (int y = y_start; y != y_end; y += y_step) { QRgb *scanline = (QRgb *) img.scanLine(y); if (info.pal) { // Paletted. for (int x = 0; x < tga.width; x++) { uchar idx = *src++; scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]); } } else if (info.grey) { // Greyscale. for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(*src, *src, *src); src++; } } else { // True Color. if (tga.pixel_size == 16) { for (int x = 0; x < tga.width; x++) { Color555 c = *reinterpret_cast(src); scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2)); src += 2; } } else if (tga.pixel_size == 24) { for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(src[2], src[1], src[0]); src += 3; } } else if (tga.pixel_size == 32) { for (int x = 0; x < tga.width; x++) { // ### TODO: verify with images having really some alpha data const uchar alpha = (src[3] << (8 - numAlphaBits)); scanline[x] = qRgba(src[2], src[1], src[0], alpha); src += 4; } } } } // Free image. free(image); return true; } } // namespace TGAHandler::TGAHandler() { } bool TGAHandler::canRead() const { if (canRead(device())) { setFormat("tga"); return true; } return false; } bool TGAHandler::read(QImage *outImage) { //qDebug() << "Loading TGA file!"; QDataStream s(device()); s.setByteOrder(QDataStream::LittleEndian); // Read image header. TgaHeader tga; s >> tga; s.device()->seek(TgaHeader::SIZE + tga.id_length); // Check image file format. if (s.atEnd()) { // qDebug() << "This TGA file is not valid."; return false; } // Check supported file types. if (!IsSupported(tga)) { // qDebug() << "This TGA file is not supported."; return false; } QImage img; bool result = LoadTGA(s, tga, img); if (result == false) { // qDebug() << "Error loading TGA file."; return false; } *outImage = img; return true; } bool TGAHandler::write(const QImage &image) { QDataStream s(device()); s.setByteOrder(QDataStream::LittleEndian); const QImage &img = image; const bool hasAlpha = (img.format() == QImage::Format_ARGB32); for (int i = 0; i < 12; i++) { s << targaMagic[i]; } // write header s << quint16(img.width()); // width s << quint16(img.height()); // height s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4) for (int y = 0; y < img.height(); y++) for (int x = 0; x < img.width(); x++) { const QRgb color = img.pixel(x, y); s << quint8(qBlue(color)); s << quint8(qGreen(color)); s << quint8(qRed(color)); if (hasAlpha) { s << quint8(qAlpha(color)); } } return true; } bool TGAHandler::canRead(QIODevice *device) { if (!device) { qWarning("TGAHandler::canRead() called with no device"); return false; } qint64 oldPos = device->pos(); QByteArray head = device->read(TgaHeader::SIZE); int readBytes = head.size(); if (device->isSequential()) { for (int pos = readBytes - 1; pos >= 0; --pos) { device->ungetChar(head[pos]); } } else { device->seek(oldPos); } if (readBytes < TgaHeader::SIZE) { return false; } QDataStream stream(head); stream.setByteOrder(QDataStream::LittleEndian); TgaHeader tga; stream >> tga; return IsSupported(tga); } ================================================ FILE: src/decaf-qt/src/tgahandler.h ================================================ /* This file is part of the KDE project Copyright (C) 2003 Dominik Seichter This program is free software; you can redistribute it and/or modify it under the terms of the Lesser GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #ifndef KIMG_TGA_P_H #define KIMG_TGA_P_H #include class TGAHandler : public QImageIOHandler { public: TGAHandler(); bool canRead() const override; bool read(QImage *image) override; bool write(const QImage &image) override; static bool canRead(QIODevice *device); }; #endif // KIMG_TGA_P_H ================================================ FILE: src/decaf-qt/src/titlelistmodel.h ================================================ #pragma once #include #include #include #include #include struct TitleInfo { QString code_path; QString argstr; qulonglong title_id; int title_version; QString longname_en; QString publisher_en; QPixmap icon; }; class TitleListModel : public QAbstractTableModel { Q_OBJECT public: static constexpr auto TitleTypeRole = Qt::UserRole; static constexpr auto TitlePathRole = Qt::UserRole + 1; static constexpr auto TitleIdRole = Qt::UserRole + 2; TitleListModel(QObject *parent = nullptr) : QAbstractTableModel(parent) { } ~TitleListModel() { qDeleteAll(mTitles); } void clear() { beginResetModel(); qDeleteAll(mTitles); mTitles.clear(); endResetModel(); } int rowCount(const QModelIndex &parent) const override { if (parent.isValid()) { return 0; } return mTitles.size(); } int columnCount(const QModelIndex &parent) const override { return 4; } static QString getTypeString(qulonglong titleId) { switch (decaf::getTitleTypeFromID(titleId)) { case decaf::TitleType::Application: return tr("Application"); case decaf::TitleType::Demo: return tr("Demo"); case decaf::TitleType::Data: return tr("Data"); case decaf::TitleType::DLC: return tr("DLC"); case decaf::TitleType::Update: return tr("Update"); default: return tr("Unknown"); } } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return QVariant { }; } if (index.row() >= mTitles.size() || index.row() < 0) { return QVariant{ }; } auto &title = *mTitles[index.row()]; if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return title.longname_en; case 1: return title.publisher_en; case 2: return getTypeString(title.title_id); case 3: return QDir { title.code_path }.filePath(title.argstr); default: return QVariant { }; } } else if (role == Qt::DecorationRole) { if (index.column() == 0) { return title.icon; } } else if (role == TitleTypeRole) { return getTypeString(title.title_id); } else if (role == TitlePathRole) { return QDir { title.code_path }.filePath(title.argstr); } else if (role == TitleIdRole) { return title.title_id; } return QVariant { }; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (role != Qt::DisplayRole) { return QVariant{ }; } if (orientation == Qt::Horizontal) { switch (section) { case 0: return tr("Name"); case 1: return tr("Publisher"); case 2: return tr("Type"); case 3: return tr("Path"); } } return QVariant { }; } public slots: void addTitle(TitleInfo *info) { beginInsertRows({}, mTitles.size(), mTitles.size()); mTitles.push_back(info); endInsertRows(); } private: QVector mTitles; }; ================================================ FILE: src/decaf-qt/src/titlelistscanner.h ================================================ #pragma once #include #include #include #include #include #include "titlelistmodel.h" #include "tgahandler.h" class TitleScanner : public QObject { Q_OBJECT public: void cancel() { mCancel.store(true); } void scanDirectoryList(QStringList directories) { for (auto scanDirectory : directories) { auto itr = QDirIterator { scanDirectory, { "*.rpx" }, QDir::Files | QDir::Readable, QDirIterator::Subdirectories }; if (mCancel.load()) { break; } while (itr.hasNext() && !mCancel.load()) { auto titleInfo = new TitleInfo { }; auto rpx = QFileInfo { itr.next() }; auto codeDirectory = QDir { rpx.path() }; titleInfo->code_path = codeDirectory.path(); titleInfo->argstr = rpx.fileName(); auto appXml = QFile { codeDirectory.filePath("app.xml") }; if (appXml.open(QIODevice::ReadOnly)) { auto document = QDomDocument { }; if (document.setContent(&appXml)) { auto app = document.documentElement(); if (app.tagName() == "app") { titleInfo->title_id = app.firstChildElement("title_id").text().trimmed().toULongLong(nullptr, 16); titleInfo->title_version = app.firstChildElement("title_version").text().trimmed().toInt(nullptr, 16); } } } auto cosXml = QFile { codeDirectory.filePath("cos.xml") }; if (cosXml.open(QIODevice::ReadOnly)) { auto document = QDomDocument { }; if (document.setContent(&cosXml)) { auto cos = document.documentElement(); if (cos.tagName() == "app") { titleInfo->argstr = cos.firstChildElement("argstr").text().trimmed(); } } } auto metaDirectory = QDir { codeDirectory.filePath("../meta") }; if (metaDirectory.exists()) { auto metaXml = QFile { metaDirectory.filePath("meta.xml") }; if (metaXml.open(QIODevice::ReadOnly)) { auto document = QDomDocument { }; if (document.setContent(&metaXml)) { auto meta = document.documentElement(); if (meta.tagName() == "menu") { titleInfo->longname_en = meta.firstChildElement("longname_en").text().trimmed(); titleInfo->publisher_en = meta.firstChildElement("publisher_en").text().trimmed(); } } } auto iconTex = QFile { metaDirectory.filePath("iconTex.tga") }; if (iconTex.open(QIODevice::ReadOnly)) { auto tgaHandler = TGAHandler { }; auto image = QImage { }; tgaHandler.setDevice(&iconTex); if (tgaHandler.read(&image)) { titleInfo->icon = QPixmap::fromImage(image); } } } emit titleFound(titleInfo); } } mCancel = false; emit scanFinished(); } signals: void titleFound(TitleInfo *info); void scanFinished(); private: std::atomic_bool mCancel { false }; }; ================================================ FILE: src/decaf-qt/src/titlelistwidget.cpp ================================================ #include "settings.h" #include "titlelistwidget.h" #include "titlelistmodel.h" #include "titlelistscanner.h" #include "ui_titlelist.h" #include #include #include #include #include #include #include #include class TitleSortFilterProxyModel : public QSortFilterProxyModel { public: TitleSortFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { } bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override { auto titleId = sourceModel()->data( sourceModel()->index(sourceRow, 0, sourceParent), TitleListModel::TitleIdRole).toULongLong(); if (decaf::isSystemTitle(titleId)) { if (!showSystemTitles) { return false; } } else { if (!showNonSystemTitles) { return false; } } switch (decaf::getTitleTypeFromID(titleId)) { case decaf::TitleType::Application: return showApplications; case decaf::TitleType::Demo: return showDemos; case decaf::TitleType::Data: return showData; case decaf::TitleType::DLC: return showDLC; case decaf::TitleType::Update: return showUpdates; default: return showUnknown; } } bool showApplications = true; bool showDemos = true; bool showData = false; bool showDLC = false; bool showUpdates = false; bool showNonSystemTitles = true; bool showSystemTitles = true; bool showUnknown = false; }; TitleListWidget::TitleListWidget(SettingsStorage *settingsStorage, QWidget *parent) : QWidget(parent), mSettingsStorage(settingsStorage), mStackedLayout(new QStackedLayout { this }) { mStackedLayout->setContentsMargins(0, 0, 0, 0); mTitleScanner = new TitleScanner(); mTitleScanner->moveToThread(&mScanThread); mScanThread.start(); mProxyModel = new TitleSortFilterProxyModel { this }; mTitleListModel = new TitleListModel { this }; mProxyModel->setSourceModel(mTitleListModel); mTitleList = new QTreeView { }; mTitleList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); mTitleList->setSortingEnabled(true); mTitleList->setModel(mProxyModel); mTitleList->header()->setSortIndicator(0, Qt::AscendingOrder); mTitleList->header()->setStretchLastSection(false); mTitleList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); mTitleGrid = new QListView { }; mTitleGrid->setViewMode(QListView::IconMode); mTitleGrid->setModel(mProxyModel); mTitleGrid->setWrapping(true); mTitleGrid->setWordWrap(true); mTitleGrid->setResizeMode(QListView::Adjust); mStackedLayout->addWidget(mTitleList); mStackedLayout->addWidget(mTitleGrid); mCopyPathAction = new QAction(tr("Copy title path"), this); connect(mCopyPathAction, &QAction::triggered, [&]() { auto data = QVariant { }; if (mStackedLayout->currentIndex() == 0) { data = mTitleList->model()->data(mTitleList->currentIndex(), TitleListModel::TitlePathRole); } else if (mStackedLayout->currentIndex() == 1) { data = mTitleGrid->model()->data(mTitleGrid->currentIndex(), TitleListModel::TitlePathRole); } if (data.isValid()) { auto path = data.toString(); if (!path.isEmpty()) { qApp->clipboard()->setText(path); } } }); for (auto widget : { (QWidget *)mTitleGrid , (QWidget *)mTitleList }) { widget->addAction(mCopyPathAction); widget->setContextMenuPolicy(Qt::ActionsContextMenu); } connect(&mScanThread, &QThread::finished, mTitleScanner, &QObject::deleteLater); connect(this, &TitleListWidget::scanDirectoryList, mTitleScanner, &TitleScanner::scanDirectoryList); connect(mTitleScanner, &TitleScanner::titleFound, mTitleListModel, &TitleListModel::addTitle); connect(mTitleScanner, &TitleScanner::scanFinished, this, &TitleListWidget::titleScanFinished); connect(mTitleList, &QListView::doubleClicked, [&](const QModelIndex &index) { auto data = mTitleList->model()->data(index, TitleListModel::TitlePathRole); if (data.isValid()) { auto path = data.toString(); if (!path.isEmpty()) { launchTitle(path); } } }); connect(mTitleGrid, &QListView::doubleClicked, [&](const QModelIndex &index) { auto data = mTitleGrid->model()->data(index, TitleListModel::TitlePathRole); if (data.isValid()) { auto path = data.toString(); if (!path.isEmpty()) { launchTitle(path); } } }); connect(mSettingsStorage, &SettingsStorage::settingsChanged, this, &TitleListWidget::settingsChanged); settingsChanged(); } TitleListWidget::~TitleListWidget() { mScanThread.quit(); mScanThread.wait(); } void TitleListWidget::settingsChanged() { if (mSettingsStorage->get()->ui.titleListMode == UiSettings::TitleGrid) { mStackedLayout->setCurrentIndex(1); } else { mStackedLayout->setCurrentIndex(0); } startTitleScan(); } void TitleListWidget::startTitleScan() { auto settings = mSettingsStorage->get(); auto directoryList = QStringList { }; if (!settings->decaf.system.mlc_path.empty()) { directoryList.push_back(QString::fromStdString(settings->decaf.system.mlc_path) + "/sys/title"); directoryList.push_back(QString::fromStdString(settings->decaf.system.mlc_path) + "/usr/title"); } for (const auto &dir : settings->decaf.system.title_directories) { directoryList.push_back(QString::fromStdString(dir)); } if (mCurrentDirectoryList == directoryList) { return; } if (mScanRunning) { if (!mScanRequested) { mTitleScanner->cancel(); mScanRequested = true; } return; } mScanRunning = true; mScanRequested = false; mCurrentDirectoryList = directoryList; mTitleListModel->clear(); emit statusMessage("Scanning for titles...", 0); scanDirectoryList(mCurrentDirectoryList); } void TitleListWidget::titleScanFinished() { mScanRunning = false; if (mScanRequested) { startTitleScan(); } emit statusMessage( QString("Scanning complete, found %1 titles") .arg(mTitleListModel->rowCount({})), 0); } ================================================ FILE: src/decaf-qt/src/titlelistwidget.h ================================================ #pragma once #include #include #include class SettingsStorage; class TitleScanner; class TitleListModel; class TitleSortFilterProxyModel; class QStackedLayout; class QTreeView; class QListView; class TitleListWidget : public QWidget { Q_OBJECT public: TitleListWidget(SettingsStorage *settingsStorage, QWidget *parent = nullptr); ~TitleListWidget(); void startTitleScan(); signals: void scanDirectoryList(QStringList directories); void launchTitle(QString path); void statusMessage(QString message, int timeout); protected slots: void settingsChanged(); void titleScanFinished(); private: QStackedLayout *mStackedLayout = nullptr; QTreeView *mTitleList = nullptr; QListView *mTitleGrid = nullptr; SettingsStorage *mSettingsStorage = nullptr; QThread mScanThread; TitleScanner *mTitleScanner = nullptr; TitleListModel *mTitleListModel = nullptr; TitleSortFilterProxyModel *mProxyModel = nullptr; bool mScanRunning = false; bool mScanRequested = false; QStringList mCurrentDirectoryList; QAction *mCopyPathAction; }; ================================================ FILE: src/decaf-qt/ui/about.ui ================================================ AboutDialog 0 0 501 150 About decaf-emu 0 0 0 22 <html><head/><body><p>%1 | %2-%3 (%4) </p></body></html> false Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -4 <h1>decaf-emu</h1> <a href="https://github.com/decaf-emu">github</a> | <a href="https://github.com/decaf-emu/decaf-emu/blob/master/LICENSE.md">license</a> Qt::RichText Qt::AlignCenter true Qt::Horizontal QDialogButtonBox::Ok QSvgWidget QWidget
qsvgwidget.h
1
buttonBox accepted() AboutDialog accept() 248 254 157 274 buttonBox rejected() AboutDialog reject() 316 260 286 274
================================================ FILE: src/decaf-qt/ui/audiosettings.ui ================================================ AudioSettingsWidget 0 0 400 300 Form Playback Audio playback enabled Qt::Vertical 20 40 ================================================ FILE: src/decaf-qt/ui/contentsettings.ui ================================================ ContentSettingsWidget 0 0 453 374 Form System Directories ... /dev/hfio01 ... ... Resources ... otp.bin ... /dev/sdcard01 /dev/mlc01 ... /dev/slc01 Title Search Directores Add Remove pushButtonAddTitleDirectory clicked() ContentSettingsWidget addTitleDirectory() 119 351 226 186 pushButtonOtpPath clicked() ContentSettingsWidget browseOtpPath() 411 136 226 186 pushButtonHfioPath clicked() ContentSettingsWidget browseHfioPath() 411 115 226 186 pushButtonMlcPath clicked() ContentSettingsWidget browseMlcPath() 411 52 226 186 pushButtonRemoveTitleDirectory clicked() ContentSettingsWidget removeTitleDirectory() 333 351 226 186 pushButtonSdcardPath clicked() ContentSettingsWidget browseSdcardPath() 411 94 226 186 pushButtonResourcesPath clicked() ContentSettingsWidget browseResourcesPath() 411 31 226 186 pushButtonSlcPath clicked() ContentSettingsWidget browseSlcPath() 411 73 226 186 browseResourcesPath() browseMlcPath() browseSlcPath() browseSdcardPath() browseHfioPath() browseOtpPath() addTitleDirectory() removeTitleDirectory() ================================================ FILE: src/decaf-qt/ui/debugger/breakpointswindow.ui ================================================ BreakpointsWindow 0 0 512 440 Form 0 0 0 0 0 QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel 3 3 3 3 tableView doubleClicked(QModelIndex) BreakpointsWindow breakpointsViewDoubleClicked(QModelIndex) 255 211 255 219 breakpointsViewDoubleClicked(QModelIndex) ================================================ FILE: src/decaf-qt/ui/debugger/debuggerwindow.ui ================================================ DebuggerWindow 0 0 800 600 decaf-emu Debugger :/images/debugger-icon:/images/debugger-icon 0 0 800 18 &File &View &Debug Graphics Navigate toolBar_3 TopToolBarArea false toolBar TopToolBarArea false toolBar_2 TopToolBarArea false &Close Qt::WidgetWithChildrenShortcut :/icons/pause:/icons/pause &Pause Ctrl+Cancel true false :/icons/play:/icons/play &Resume F5 false :/icons/debug-step-over:/icons/debug-step-over Step &Over F10 false :/icons/debug-step-into:/icons/debug-step-into Step &Into F11 true &HLE Trace Enabled PM4 &Capture Next Frame true PM4 Trace Enabled true GX2 Texture Dump Enabled true GX2 Shader Dump Enabled true GPU Shader Dump Enabled true GPU Shader Binary Dump Only :/icons/checkbox-blank-circle:/icons/checkbox-blank-circle To&ggle Breakpoint F9 Qt::WidgetWithChildrenShortcut :/icons/arrow-right:/icons/arrow-right Navigate forward Alt+Left Qt::WidgetWithChildrenShortcut :/icons/arrow-left:/icons/arrow-left Navigate backward Alt+Right Qt::WidgetWithChildrenShortcut Navigate to address G Qt::WidgetWithChildrenShortcut Navigate to operand Return Qt::WidgetWithChildrenShortcut actionClose triggered() DebuggerWindow close() -1 -1 399 299 actionGpuShaderBinaryDumpOnly triggered(bool) DebuggerWindow setGpuShaderBinaryDumpOnly(bool) -1 -1 399 299 actionGpuShaderDumpEnabled triggered(bool) DebuggerWindow setGpuShaderDumpEnabled(bool) -1 -1 399 299 actionGx2ShaderDumpEnabled triggered(bool) DebuggerWindow setGx2ShaderDumpEnabled(bool) -1 -1 399 299 actionGx2TextureDumpEnabled triggered(bool) DebuggerWindow setGx2TextureDumpEnabled(bool) -1 -1 399 299 actionHleTraceEnabled triggered(bool) DebuggerWindow setHleTraceEnabled(bool) -1 -1 399 299 actionPause triggered() DebuggerWindow debugPause() -1 -1 399 299 actionResume triggered() DebuggerWindow debugResume() -1 -1 399 299 actionPm4CaptureNextFrame triggered() DebuggerWindow pm4CaptureNextFrame() -1 -1 399 299 actionPm4TraceEnabled triggered(bool) DebuggerWindow setPm4TraceEnabled(bool) -1 -1 399 299 actionStepInto triggered() DebuggerWindow debugStepInto() -1 -1 399 299 actionStepOver triggered() DebuggerWindow debugStepOver() -1 -1 399 299 actionToggleBreakpoint triggered() DebuggerWindow debugToggleBreakpoint() -1 -1 399 299 actionNavigateBackward triggered() DebuggerWindow navigateBackward() -1 -1 399 299 actionNavigateForward triggered() DebuggerWindow navigateForward() -1 -1 399 299 actionNavigateToAddress triggered() DebuggerWindow navigateAddress() -1 -1 399 299 actionNavigateToOperand triggered() DebuggerWindow navigateOperand() -1 -1 399 299 debugPause() debugResume() debugStepOver() debugStepInto() setHleTraceEnabled(bool) pm4CaptureNextFrame() setPm4TraceEnabled(bool) setGx2TextureDumpEnabled(bool) setGx2ShaderDumpEnabled(bool) setGpuShaderDumpEnabled(bool) setGpuShaderBinaryDumpOnly(bool) debugToggleBreakpoint() navigateBackward() navigateForward() navigateAddress() navigateOperand() ================================================ FILE: src/decaf-qt/ui/debugger/disassemblywindow.ui ================================================ DisassemblyWindow 0 0 557 442 Form 0 0 0 0 0 0 1 3 3 3 3 DisassemblyWidget QWidget
debugger/disassemblywidget.h
1
================================================ FILE: src/decaf-qt/ui/debugger/functionswindow.ui ================================================ FunctionsWindow 0 0 416 339 Functions 0 0 0 0 0 QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel false Qt::NoPen true 3 3 3 3 Filter: 1 0 lineEditFilter textChanged(QString) FunctionsWindow filterChanged(QString) 217 286 199 149 tableView doubleClicked(QModelIndex) FunctionsWindow functionsViewDoubleClicked(QModelIndex) 207 156 207 169 filterChanged(QString) functionsViewDoubleClicked(QModelIndex) ================================================ FILE: src/decaf-qt/ui/debugger/jitprofilingwindow.ui ================================================ JitProfilingWindow 0 0 565 441 JIT Profiling Core 0 Core 1 Core 2 Qt::Horizontal 40 20 Start true Clear Total JIT Data Size: Total JIT Code Size: IBeamCursor 0.00 mb Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse IBeamCursor 0.00 mb Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse QAbstractItemView::SingleSelection QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel checkBoxCore0 clicked(bool) JitProfilingWindow setCore0Mask(bool) 37 20 281 245 checkBoxCore1 clicked(bool) JitProfilingWindow setCore1Mask(bool) 98 20 281 245 checkBoxCore2 clicked(bool) JitProfilingWindow setCore2Mask(bool) 159 20 281 245 pushButtonClear clicked() JitProfilingWindow clearProfileData() 516 21 281 245 pushButtonStartStop clicked(bool) JitProfilingWindow setProfilingEnabled(bool) 435 21 281 245 tableView doubleClicked(QModelIndex) JitProfilingWindow tableViewDoubleClicked(QModelIndex) 282 335 282 220 setProfilingEnabled(bool) clearProfileData() setCore0Mask(bool) setCore1Mask(bool) setCore2Mask(bool) tableViewDoubleClicked(QModelIndex) ================================================ FILE: src/decaf-qt/ui/debugger/memorywindow.ui ================================================ MemoryWindow 0 0 646 442 Memory 0 0 0 0 9 9 9 Address: Columns: 100 0 true 3 Auto 4 8 16 32 64 0 1 MemoryWidget QWidget
debugger/memorywidget.h
1
comboBoxColumns currentTextChanged(QString) MemoryWindow columnsChanged() 585 19 322 220 lineEditAddress returnPressed() MemoryWindow addressChanged() 268 19 322 220 columnsChanged() addressChanged()
================================================ FILE: src/decaf-qt/ui/debugger/segmentswindow.ui ================================================ SegmentsWindow 0 0 512 439 Segments 0 0 0 0 0 QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel 3 3 3 3 tableView doubleClicked(QModelIndex) SegmentsWindow segmentsViewDoubleClicked(QModelIndex) 255 210 255 219 segmentsViewDoubleClicked(QModelIndex) ================================================ FILE: src/decaf-qt/ui/debugger/stackwindow.ui ================================================ StackWindow 0 0 658 601 Stack 6 0 0 0 0 0 1 6 3 3 3 3 1 0 StackWidget QWidget
debugger/stackwidget.h
1
threadChanged(int) displayModeChanged(int)
================================================ FILE: src/decaf-qt/ui/debugger/threadswindow.ui ================================================ ThreadsWindow 0 0 512 440 Threads 0 0 0 0 0 QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel 3 3 3 3 tableView doubleClicked(QModelIndex) ThreadsWindow threadsViewDoubleClicked(QModelIndex) 255 210 255 219 threadsViewDoubleClicked(QModelIndex) ================================================ FILE: src/decaf-qt/ui/debugger/voiceswindow.ui ================================================ VoicesWindow 0 0 512 440 Form 0 0 0 0 0 QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel 3 3 3 3 ================================================ FILE: src/decaf-qt/ui/debugsettings.ui ================================================ DebugSettingsWidget 0 0 398 293 Form Debug Options GDB Server Port Break on main entry point Break on exit GDB server enabled Qt::Vertical 20 40 ================================================ FILE: src/decaf-qt/ui/displaysettings.ui ================================================ DisplaySettingsWidget 0 0 428 305 Form Title List 0 0 View Mode Game Maintain Aspect Ratio Background Colour 0 0 View Mode 0 0 Split Separation Qt::Vertical 20 140 ColourLineEdit QLineEdit
settings/colourlineedit.h
================================================ FILE: src/decaf-qt/ui/inputsettings.ui ================================================ InputSettingsWidget 0 0 636 357 Form 0 0 0 0 200 16777215 0 0 0 0 0 Add 0 0 Remove 0 0 QLayout::SetDefaultConstraint Controller Type QFrame::StyledPanel QFrame::Sunken true 0 0 406 296 addController clicked() InputSettingsWidget addController() 60 444 393 243 removeController clicked() InputSettingsWidget removeController() 153 444 393 243 controllerType currentIndexChanged(int) InputSettingsWidget controllerTypeChanged(int) 524 28 393 243 controllerList currentRowChanged(int) InputSettingsWidget editController(int) 106 221 393 243 addController() removeController() controllerTypeChanged(int) editController(int) ================================================ FILE: src/decaf-qt/ui/loggingsettings.ui ================================================ LoggingSettingsWidget 0 0 393 400 Form Log Options Output to file Asynchronous Log File Directory 2 0 0 0 ... Branch tracing Output to stdout Log Level CafeOS HLE Function Call Tracing Enabled Trace return value Add Remove pushButtonAddTraceFilter clicked() LoggingSettingsWidget addTraceFilter() 103 370 195 196 pushButtonRemoveTraceFilter clicked() LoggingSettingsWidget removeTraceFilter() 287 370 195 196 pushButtonLogDirectory clicked() LoggingSettingsWidget browseLogPath() 349 103 195 196 browseLogPath() addTraceFilter() removeTraceFilter() ================================================ FILE: src/decaf-qt/ui/mainwindow.ui ================================================ MainWindow 0 0 675 465 100 100 decaf-qt :/images/icon:/images/icon 0 0 675 22 &File &Options &Help &View &Tools &Open E&xit &Input Settings &About &Settings S&ystem Settings &Debug Settings &Log Settings &Debugger Audio Settings Display Settings Content Settings true Title List true Title Grid Full Screen true true Split true TV true Gamepad 1 true false Gamepad 2 true actionInputSettings triggered() MainWindow openInputSettings() -1 -1 337 232 actionOpen triggered() MainWindow openFile() -1 -1 337 232 actionAbout triggered() MainWindow openAboutDialog() -1 -1 337 232 actionExit triggered() MainWindow exit() -1 -1 337 232 actionViewGamepad1 triggered() MainWindow setViewModeGamepad1() -1 -1 337 232 actionViewFullScreen triggered() MainWindow toggleFullScreen() -1 -1 337 232 actionViewSplit triggered() MainWindow setViewModeSplit() -1 -1 337 232 actionViewTV triggered() MainWindow setViewModeTV() -1 -1 337 232 actionLogSettings triggered() MainWindow openLoggingSettings() -1 -1 337 232 actionDebugSettings triggered() MainWindow openDebugSettings() -1 -1 337 232 actionSettings triggered() MainWindow openSettings() -1 -1 337 232 actionSystemSettings triggered() MainWindow openSystemSettings() -1 -1 337 232 actionDebugger triggered() MainWindow openDebugger() -1 -1 337 232 actionContentSettings triggered() MainWindow openContentSettings() -1 -1 337 232 actionAudioSettings triggered() MainWindow openAudioSettings() -1 -1 337 232 actionDisplaySettings triggered() MainWindow openDisplaySettings() -1 -1 337 232 actionViewTitleList triggered() MainWindow setTitleListModeList() -1 -1 337 232 actionViewTitleGrid triggered() MainWindow setTitleListModeGrid() -1 -1 337 232 openInputSettings() openFile() openAboutDialog() exit() setViewModeSplit() setViewModeTV() setViewModeGamepad1() openLoggingSettings() openSettings() openDebugSettings() openSystemSettings() openDebugger() openDisplaySettings() openAudioSettings() openContentSettings() setTitleListModeList() setTitleListModeGrid() toggleFullScreen() ================================================ FILE: src/decaf-qt/ui/settings.ui ================================================ SettingsDialog 0 0 697 493 Settings -1 Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset buttonBox clicked(QAbstractButton*) SettingsDialog buttonBoxClicked(QAbstractButton*) 248 254 157 274 buttonBoxClicked(QAbstractButton*) ================================================ FILE: src/decaf-qt/ui/systemsettings.ui ================================================ SystemSettingsWidget 0 0 454 320 Form Options Region Time Scale CafeOS LLE Whitelist ================================================ FILE: src/decaf-qt/ui/titlelist.ui ================================================ TitleListWidget 0 0 609 447 Form 0 0 0 0 false QAbstractItemView::ScrollPerPixel true false ================================================ FILE: src/decaf-sdl/CMakeLists.txt ================================================ project(decaf-sdl) include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) if(MSVC) set(RESOURCE_FILES ${CMAKE_SOURCE_DIR}/resources/decaf-sdl.rc ${CMAKE_SOURCE_DIR}/resources/hidpi.manifest) else() set(RESOURCE_FILES "") endif() add_executable(decaf-sdl ${SOURCE_FILES} ${HEADER_FILES} ${RESOURCE_FILES}) target_include_directories(decaf-sdl PRIVATE ${SDL2_INCLUDE_DIRS}) target_link_libraries(decaf-sdl common libconfig libdecaf excmd ${SDL2_LIBRARIES}) if(MSVC) set_target_properties(decaf-sdl PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") target_link_libraries(decaf-sdl Setupapi) endif() if(DECAF_PCH) target_precompile_headers(decaf-sdl PRIVATE ) AutoGroupPCHFiles() endif() install(TARGETS decaf-sdl RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") if(COMMAND x_vcpkg_install_local_dependencies) x_vcpkg_install_local_dependencies(TARGETS decaf-sdl DESTINATION "${DECAF_INSTALL_BINDIR}") endif() ================================================ FILE: src/decaf-sdl/clilog.h ================================================ #pragma once #include extern std::shared_ptr gCliLog; ================================================ FILE: src/decaf-sdl/config.cpp ================================================ #include "config.h" #include namespace config { namespace input { std::vector devices; std::string vpad0 = "default_keyboard"; } // namespace input namespace sound { unsigned frame_length = 30; } // namespace sound namespace test { //! Maximum time to run for before termination in milliseconds. int timeout_ms = -1; //! Maximum number of frames to render before termination. int timeout_frames = -1; //! Whether to dump rendered DRC frames to file; bool dump_drc_frames = false; //! Whether to dump rendered TV frames to file; bool dump_tv_frames = false; //! What directory to place dumped frames in. std::string dump_frames_dir = "frames"; } // namespace test void setupDefaultInputDevices() { auto foundKeyboardDevice = false; auto foundJoystickDevice = false; for (auto &device : input::devices) { if (device.type == input::ControllerType::Keyboard) { foundKeyboardDevice = true; } else if (device.type == input::ControllerType::Joystick) { foundJoystickDevice = true; } } // Setup default keyboard device if (!foundKeyboardDevice) { input::InputDevice device; device.id = "default_keyboard"; device.type = input::Keyboard; device.device_name = ""; device.button_up = SDL_SCANCODE_UP; device.button_down = SDL_SCANCODE_DOWN; device.button_left = SDL_SCANCODE_LEFT; device.button_right = SDL_SCANCODE_RIGHT; device.button_a = SDL_SCANCODE_X; device.button_b = SDL_SCANCODE_Z; device.button_x = SDL_SCANCODE_S; device.button_y = SDL_SCANCODE_A; device.button_trigger_r = SDL_SCANCODE_E; device.button_trigger_l = SDL_SCANCODE_W; device.button_trigger_zr = SDL_SCANCODE_R; device.button_trigger_zl = SDL_SCANCODE_Q; device.button_stick_l = SDL_SCANCODE_KP_0; device.button_stick_r = SDL_SCANCODE_KP_ENTER; device.button_plus = SDL_SCANCODE_1; device.button_minus = SDL_SCANCODE_2; device.button_home = SDL_SCANCODE_3; device.button_sync = SDL_SCANCODE_4; input::InputDeviceKeyboard keyboard; keyboard.left_stick_up = SDL_SCANCODE_KP_8; keyboard.left_stick_down = SDL_SCANCODE_KP_2; keyboard.left_stick_left = SDL_SCANCODE_KP_4; keyboard.left_stick_right = SDL_SCANCODE_KP_6; keyboard.right_stick_up = -1; keyboard.right_stick_down = -1; keyboard.right_stick_left = -1; keyboard.right_stick_right = -1; device.typeExtra = keyboard; input::devices.push_back(device); } // Setup default joystick device if (!foundJoystickDevice) { input::InputDevice device; device.id = "default_joystick"; device.device_name = ""; device.type = input::Joystick; device.button_up = -2; device.button_down = -2; device.button_left = -2; device.button_right = -2; device.button_a = -2; device.button_b = -2; device.button_x = -2; device.button_y = -2; device.button_trigger_r = -2; device.button_trigger_l = -2; device.button_trigger_zr = -2; device.button_trigger_zl = -2; device.button_stick_l = -2; device.button_stick_r = -2; device.button_plus = -2; device.button_minus = -2; device.button_home = -2; device.button_sync = -2; input::InputDeviceJoystick joystick; joystick.left_stick_x = -2; joystick.left_stick_x_invert = false; joystick.left_stick_y = -2; joystick.left_stick_y_invert = false; joystick.right_stick_x = -2; joystick.right_stick_x_invert = false; joystick.right_stick_y = -2; joystick.right_stick_y_invert = false; device.typeExtra = joystick; input::devices.push_back(device); } } bool loadFrontendToml(const toml::table &config) { // input auto devices = config.at_path("input.devices").as_array(); input::devices.clear(); if (devices) { for (auto itr = devices->begin(); itr != devices->end(); ++itr) { auto cfgDevice = itr->as_table(); config::input::InputDevice device; auto type = cfgDevice->get_as("type"); if (!type) { continue; } else if (type->get() == "keyboard") { device.type = config::input::ControllerType::Keyboard; } else if (type->get() == "joystick") { device.type = config::input::ControllerType::Joystick; } else { continue; } readValue(*cfgDevice, "id", device.id); readValue(*cfgDevice, "device_name", device.device_name); readValue(*cfgDevice, "button_up", device.button_up); readValue(*cfgDevice, "button_down", device.button_down); readValue(*cfgDevice, "button_left", device.button_left); readValue(*cfgDevice, "button_right", device.button_right); readValue(*cfgDevice, "button_a", device.button_a); readValue(*cfgDevice, "button_b", device.button_b); readValue(*cfgDevice, "button_x", device.button_x); readValue(*cfgDevice, "button_y", device.button_y); readValue(*cfgDevice, "button_trigger_r", device.button_trigger_r); readValue(*cfgDevice, "button_trigger_l", device.button_trigger_l); readValue(*cfgDevice, "button_trigger_zr", device.button_trigger_zr); readValue(*cfgDevice, "button_trigger_zl", device.button_trigger_zl); readValue(*cfgDevice, "button_stick_l", device.button_stick_l); readValue(*cfgDevice, "button_stick_r", device.button_stick_r); readValue(*cfgDevice, "button_plus", device.button_plus); readValue(*cfgDevice, "button_minus", device.button_minus); readValue(*cfgDevice, "button_home", device.button_home); readValue(*cfgDevice, "button_sync", device.button_sync); if (device.type == config::input::ControllerType::Keyboard) { auto keyboard = config::input::InputDeviceKeyboard {}; readValue(*cfgDevice, "left_stick_up", keyboard.left_stick_up); readValue(*cfgDevice, "left_stick_down", keyboard.left_stick_down); readValue(*cfgDevice, "left_stick_left", keyboard.left_stick_left); readValue(*cfgDevice, "left_stick_right", keyboard.left_stick_right); readValue(*cfgDevice, "right_stick_up", keyboard.right_stick_up); readValue(*cfgDevice, "right_stick_down", keyboard.right_stick_down); readValue(*cfgDevice, "right_stick_left", keyboard.right_stick_left); readValue(*cfgDevice, "right_stick_right", keyboard.right_stick_right); device.typeExtra = keyboard; } else if (device.type == config::input::ControllerType::Joystick) { auto joystick = config::input::InputDeviceJoystick {}; readValue(*cfgDevice, "left_stick_x", joystick.left_stick_x); readValue(*cfgDevice, "left_stick_x_invert", joystick.left_stick_x_invert); readValue(*cfgDevice, "left_stick_y", joystick.left_stick_y); readValue(*cfgDevice, "left_stick_y_invert", joystick.left_stick_y_invert); readValue(*cfgDevice, "right_stick_x", joystick.right_stick_x); readValue(*cfgDevice, "right_stick_x_invert", joystick.right_stick_x_invert); readValue(*cfgDevice, "right_stick_y", joystick.right_stick_y); readValue(*cfgDevice, "right_stick_y_invert", joystick.right_stick_y_invert); device.typeExtra = joystick; } config::input::devices.push_back(device); } } readValue(config, "input.vpad0", config::input::vpad0); setupDefaultInputDevices(); // sound readValue(config, "sound.frame_length", config::sound::frame_length); // test readValue(config, "test.timeout_ms", config::test::timeout_ms); readValue(config, "test.timeout_frames", config::test::timeout_frames); readValue(config, "test.dump_drc_frames", config::test::dump_drc_frames); readValue(config, "test.dump_tv_frames", config::test::dump_tv_frames); readValue(config, "test.dump_frames_dir", config::test::dump_frames_dir); return true; } bool saveFrontendToml(toml::table &config) { setupDefaultInputDevices(); // input auto input = config.insert("input", toml::table()).first->second.as_table(); input->insert_or_assign("vpad0", config::input::vpad0); auto devices = config.insert("devices", toml::array()).first->second.as_array(); for (const auto &device : config::input::devices) { auto cfgDevice = toml::table(); if (device.type == config::input::ControllerType::Joystick) { cfgDevice.insert_or_assign("type", "joystick"); } else if (device.type == config::input::ControllerType::Keyboard) { cfgDevice.insert_or_assign("type", "keyboard"); } else { cfgDevice.insert_or_assign("type", "unknown"); } cfgDevice.insert_or_assign("id", device.id); cfgDevice.insert_or_assign("device_name", device.device_name); cfgDevice.insert_or_assign("button_up", device.button_up); cfgDevice.insert_or_assign("button_down", device.button_down); cfgDevice.insert_or_assign("button_left", device.button_left); cfgDevice.insert_or_assign("button_right", device.button_right); cfgDevice.insert_or_assign("button_a", device.button_a); cfgDevice.insert_or_assign("button_b", device.button_b); cfgDevice.insert_or_assign("button_x", device.button_x); cfgDevice.insert_or_assign("button_y", device.button_y); cfgDevice.insert_or_assign("button_trigger_r", device.button_trigger_r); cfgDevice.insert_or_assign("button_trigger_l", device.button_trigger_l); cfgDevice.insert_or_assign("button_trigger_zr", device.button_trigger_zr); cfgDevice.insert_or_assign("button_trigger_zl", device.button_trigger_zl); cfgDevice.insert_or_assign("button_stick_l", device.button_stick_l); cfgDevice.insert_or_assign("button_stick_r", device.button_stick_r); cfgDevice.insert_or_assign("button_plus", device.button_plus); cfgDevice.insert_or_assign("button_minus", device.button_minus); cfgDevice.insert_or_assign("button_home", device.button_home); cfgDevice.insert_or_assign("button_sync", device.button_sync); if (device.type == config::input::ControllerType::Keyboard) { const auto &keyboard = std::get(device.typeExtra); cfgDevice.insert_or_assign("left_stick_up", keyboard.left_stick_up); cfgDevice.insert_or_assign("left_stick_down", keyboard.left_stick_down); cfgDevice.insert_or_assign("left_stick_left", keyboard.left_stick_left); cfgDevice.insert_or_assign("left_stick_right", keyboard.left_stick_right); cfgDevice.insert_or_assign("right_stick_up", keyboard.right_stick_up); cfgDevice.insert_or_assign("right_stick_down", keyboard.right_stick_down); cfgDevice.insert_or_assign("right_stick_left", keyboard.right_stick_left); cfgDevice.insert_or_assign("right_stick_right", keyboard.right_stick_right); } else if (device.type == config::input::ControllerType::Joystick) { const auto &joystick = std::get(device.typeExtra); cfgDevice.insert_or_assign("left_stick_x", joystick.left_stick_x); cfgDevice.insert_or_assign("left_stick_x_invert", joystick.left_stick_x_invert); cfgDevice.insert_or_assign("left_stick_y", joystick.left_stick_y); cfgDevice.insert_or_assign("left_stick_y_invert", joystick.left_stick_y_invert); cfgDevice.insert_or_assign("right_stick_x", joystick.right_stick_x); cfgDevice.insert_or_assign("right_stick_x_invert", joystick.right_stick_x_invert); cfgDevice.insert_or_assign("right_stick_y", joystick.right_stick_y); cfgDevice.insert_or_assign("right_stick_y_invert", joystick.right_stick_y_invert); } devices->push_back(cfgDevice); } // sound auto sound = config.insert("sound", toml::table()).first->second.as_table(); sound->insert_or_assign("frame_length", config::sound::frame_length); // test auto test = config.insert("test", toml::table()).first->second.as_table(); test->insert_or_assign("timeout_ms", config::test::timeout_ms); test->insert_or_assign("timeout_frames", config::test::timeout_frames); test->insert_or_assign("dump_drc_frames", config::test::dump_drc_frames); test->insert_or_assign("dump_tv_frames", config::test::dump_tv_frames); test->insert_or_assign("dump_frames_dir", config::test::dump_frames_dir); return true; } } // namespace config ================================================ FILE: src/decaf-sdl/config.h ================================================ #pragma once #include #include #include #include namespace config { namespace input { enum ControllerType { None, Keyboard, Joystick, }; /* * For keyboard input, each entry is an SDL_SCANCODE_ *constant; for * joystick input, each entry is the button number, or -2 to let SDL * choose an appropriate button. In both cases, -1 means nothing is * assigned. */ struct InputDeviceKeyboard { int left_stick_up = -1; int left_stick_down = -1; int left_stick_left = -1; int left_stick_right = -1; int right_stick_up = -1; int right_stick_down = -1; int right_stick_left = -1; int right_stick_right = -1; }; struct InputDeviceJoystick { int left_stick_x = -1; bool left_stick_x_invert = false; int left_stick_y = -1; bool left_stick_y_invert = false; int right_stick_x = -1; bool right_stick_x_invert = false; int right_stick_y = -1; bool right_stick_y_invert = false; }; struct InputDevice { ControllerType type; std::string id; std::string device_name; int button_up = -1; int button_down = -1; int button_left = -1; int button_right = -1; int button_a = -1; int button_b = -1; int button_x = -1; int button_y = -1; int button_trigger_r = -1; int button_trigger_l = -1; int button_trigger_zr = -1; int button_trigger_zl = -1; int button_stick_l = -1; int button_stick_r = -1; int button_plus = -1; int button_minus = -1; int button_home = -1; int button_sync = -1; std::variant typeExtra; }; extern std::vector devices; extern std::string vpad0; } // namespace input namespace sound { // Frame length factor for audio data. // Default is 30 (x 48 = 1440 for 48kHz) extern unsigned frame_length; } // namespace sound namespace test { //! Maximum time to run for before termination in milliseconds. extern int timeout_ms; //! Maximum number of frames to render before termination. extern int timeout_frames; //! Whether to dump rendered DRC frames to file; extern bool dump_drc_frames; //! Whether to dump rendered TV frames to file; extern bool dump_tv_frames; //! What directory to place dumped frames in. extern std::string dump_frames_dir; } // namespace test void setupDefaultInputDevices(); bool loadFrontendToml(const toml::table &config); bool saveFrontendToml(toml::table &config); } // namespace config ================================================ FILE: src/decaf-sdl/decafsdl.cpp ================================================ #include "clilog.h" #include "config.h" #include "decafsdl.h" #include #include #include #include #include #include static std::string sActiveGfx = "NOGFX"; void setWindowIcon(SDL_Window *window); DecafSDL::~DecafSDL() { } bool DecafSDL::initCore() { if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) { gCliLog->error("Failed to initialize SDL: {}", SDL_GetError()); return false; } return true; } bool DecafSDL::initGraphics() { auto videoInitialised = false; #ifdef SDL_VIDEO_DRIVER_X11 if (!videoInitialised) { videoInitialised = SDL_VideoInit("x11") == 0; if (!videoInitialised) { gCliLog->error("Failed to initialize SDL Video with x11: {}", SDL_GetError()); } } #endif #ifdef SDL_VIDEO_DRIVER_WAYLAND if (!videoInitialised) { videoInitialised = SDL_VideoInit("wayland") == 0; if (!videoInitialised) { gCliLog->error("Failed to initialize SDL Video with wayland: {}", SDL_GetError()); } } #endif #ifdef SDL_VIDEO_DRIVER_COCOA if (!videoInitialised) { videoInitialised = SDL_VideoInit("cocoa") == 0; if (!videoInitialised) { gCliLog->error("Failed to initialize SDL Video with cocoa: {}", SDL_GetError()); } } #endif if (!videoInitialised) { if (SDL_VideoInit(NULL) != 0) { gCliLog->error("Failed to initialize SDL Video: {}", SDL_GetError()); return false; } } gCliLog->info("Using SDL video driver {}", SDL_GetCurrentVideoDriver()); mGraphicsDriver = gpu::createGraphicsDriver(); if (!mGraphicsDriver) { return false; } switch (mGraphicsDriver->type()) { case gpu::GraphicsDriverType::Vulkan: sActiveGfx = "Vulkan"; break; case gpu::GraphicsDriverType::Null: sActiveGfx = "Null"; break; default: sActiveGfx = "Unknown"; } return true; } bool DecafSDL::initSound() { if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { gCliLog->error("Failed to initialize SDL audio: {}", SDL_GetError()); return false; } mSoundDriver = new DecafSDLSound; return true; } static Uint32 windowTitleTimerCallback(Uint32 interval, void *param) { auto event = SDL_Event { 0 }; event.type = static_cast(reinterpret_cast(param)); event.user.code = 0; event.user.data1 = nullptr; event.user.data2 = nullptr; SDL_PushEvent(&event); return interval; } bool DecafSDL::run(const std::string &gamePath) { auto shouldQuit = false; // Setup some basic window stuff mWindow = SDL_CreateWindow("Decaf", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WindowWidth, WindowHeight, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE); setWindowIcon(mWindow); if (gpu::config()->display.screenMode == gpu::DisplaySettings::Fullscreen) { SDL_SetWindowFullscreen(mWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); } mDecafEventId = SDL_RegisterEvents(2); if (mDecafEventId != -1) { mUpdateWindowTitleEventId = mDecafEventId + 1; mWindowTitleTimerId = SDL_AddTimer(100, &windowTitleTimerCallback, reinterpret_cast(static_cast(mUpdateWindowTitleEventId))); } // Setup graphics driver auto wsi = gpu::WindowSystemInfo { }; auto sysWmInfo = SDL_SysWMinfo { }; SDL_VERSION(&sysWmInfo.version); if (!SDL_GetWindowWMInfo(mWindow, &sysWmInfo)) { gCliLog->error("SDL_GetWindowWMInfo failed: {}", SDL_GetError()); } switch (sysWmInfo.subsystem) { #ifdef SDL_VIDEO_DRIVER_WINDOWS case SDL_SYSWM_WINDOWS: wsi.type = gpu::WindowSystemType::Windows; wsi.renderSurface = static_cast(sysWmInfo.info.win.window); break; #endif #ifdef SDL_VIDEO_DRIVER_X11 case SDL_SYSWM_X11: wsi.type = gpu::WindowSystemType::X11; wsi.renderSurface = reinterpret_cast(sysWmInfo.info.x11.window); wsi.displayConnection = static_cast(sysWmInfo.info.x11.display); break; #endif #ifdef SDL_VIDEO_METAL case SDL_SYSWM_COCOA: wsi.type = gpu::WindowSystemType::Cocoa; wsi.renderSurface = static_cast(SDL_Metal_CreateView(mWindow)); break; #endif #ifdef SDL_VIDEO_DRIVER_WAYLAND case SDL_SYSWM_WAYLAND: wsi.type = gpu::WindowSystemType::Wayland; wsi.renderSurface = static_cast(sysWmInfo.info.wl.surface); wsi.displayConnection = static_cast(sysWmInfo.info.wl.display); break; #endif default: decaf_abort(fmt::format("Unsupported SDL window subsystem {}", sysWmInfo.subsystem)); } mGraphicsDriver->setWindowSystemInfo(wsi); mGraphicsThread = std::thread { [this]() { mGraphicsDriver->run(); } }; decaf::setGraphicsDriver(mGraphicsDriver); // Set input provider decaf::setInputDriver(this); decaf::addEventListener(this); openInputDevices(); // Set sound driver decaf::setSoundDriver(mSoundDriver); // Initialise emulator if (!decaf::initialise(gamePath)) { return false; } // Start emulator decaf::start(); auto event = SDL_Event { }; while (!shouldQuit && !decaf::stopping() && SDL_WaitEvent(&event)) { switch (event.type) { case SDL_WINDOWEVENT: if (event.window.event == SDL_WINDOWEVENT_CLOSE) { shouldQuit = true; } else if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { mGraphicsDriver->windowSizeChanged(event.window.data1, event.window.data2); } break; case SDL_KEYUP: if (event.key.keysym.sym == SDLK_TAB) { mToggleDRC = !mToggleDRC; } if (event.key.keysym.sym == SDLK_ESCAPE) { shouldQuit = true; } break; case SDL_QUIT: shouldQuit = true; break; } if (event.type == mDecafEventId) { auto eventType = static_cast(event.user.code); if (eventType == decaf::EventType::GameLoaded) { auto info = reinterpret_cast(event.user.data1); mGameInfo = *info; delete info; } } else if (event.type == mUpdateWindowTitleEventId) { fmt::memory_buffer title; fmt::format_to(std::back_inserter(title), "decaf-sdl -"); if (mGameInfo.titleId) { fmt::format_to(std::back_inserter(title), " {:08X}-{:08X}", mGameInfo.titleId >> 32, mGameInfo.titleId & 0xFFFFFFFF); } if (!mGameInfo.executable.empty()) { fmt::format_to(std::back_inserter(title), " {}", mGameInfo.executable); } fmt::format_to(std::back_inserter(title), " ({} {} fps)", sActiveGfx, static_cast(mGraphicsDriver->getDebugInfo()->averageFps)); auto titleStr = std::string { title.data(), title.size() }; SDL_SetWindowTitle(mWindow, titleStr.c_str()); } } // Shut down decaf decaf::shutdown(); // Shut down graphics mGraphicsDriver->stop(); mGraphicsThread.join(); return true; } void DecafSDL::onGameLoaded(const decaf::GameInfo &info) { if (mDecafEventId != -1) { auto event = SDL_Event { 0 }; event.type = mDecafEventId; event.user.code = static_cast(decaf::EventType::GameLoaded); event.user.data1 = new decaf::GameInfo(info); SDL_PushEvent(&event); } } ================================================ FILE: src/decaf-sdl/decafsdl.h ================================================ #pragma once #include "decafsdl_sound.h" #include #include #include using namespace decaf::input; namespace gpu { class GraphicsDriver; } namespace config::input { struct InputDevice; } // namespace config::input class DecafSDL : public decaf::InputDriver, public decaf::EventListener { static const auto WindowWidth = 1420; static const auto WindowHeight = 768; public: ~DecafSDL(); bool initCore(); bool initGraphics(); bool initSound(); bool run(const std::string &gamePath); private: void openInputDevices(); void sampleVpadController(int channel, vpad::Status &status) override; void sampleWpadController(int channel, wpad::Status &status) override; // Events virtual void onGameLoaded(const decaf::GameInfo &info) override; protected: DecafSDLSound *mSoundDriver = nullptr; SDL_Window *mWindow = nullptr; gpu::GraphicsDriver *mGraphicsDriver = nullptr; std::thread mGraphicsThread; const config::input::InputDevice *mVpad0Config = nullptr; SDL_GameController *mVpad0Controller = nullptr; bool mToggleDRC = false; Uint32 mDecafEventId = static_cast(-1); Uint32 mUpdateWindowTitleEventId = static_cast(-1); SDL_TimerID mWindowTitleTimerId = -1; decaf::GameInfo mGameInfo; }; ================================================ FILE: src/decaf-sdl/decafsdl_input.cpp ================================================ #include "decafsdl.h" #include "clilog.h" #include "config.h" #include #include void DecafSDL::openInputDevices() { mVpad0Config = nullptr; mVpad0Controller = nullptr; for (const auto &device : config::input::devices) { if (config::input::vpad0.compare(device.id) != 0) { continue; } if (device.type == config::input::Joystick) { auto numJoysticks = SDL_NumJoysticks(); for (int i = 0; i < numJoysticks; ++i) { if (!SDL_IsGameController(i)) { continue; } auto controller = SDL_GameControllerOpen(i); if (!controller) { gCliLog->error("Failed to open game controller {}: {}", i, SDL_GetError()); continue; } auto name = SDL_GameControllerName(controller); if (!device.device_name.empty() && device.device_name.compare(name) != 0) { SDL_GameControllerClose(controller); continue; } mVpad0Controller = controller; break; } if (!mVpad0Controller) { continue; } } mVpad0Config = &device; break; } if (!mVpad0Config) { gCliLog->warn("No input device found for gamepad (VPAD0)"); } } void DecafSDL::sampleVpadController(int channel, vpad::Status &status) { auto device = mVpad0Config; if (!device || channel > 0 || channel < 0) { status.connected = false; return; } if (device->type == config::input::Keyboard) { auto numKeys = 0; const auto keyboardState = SDL_GetKeyboardState(&numKeys); const auto getState = [keyboardState, numKeys](int scancode) -> uint32_t { if (scancode < 0 || scancode > numKeys) { return 0; } else { return keyboardState[scancode] ? 1 : 0; } }; status.connected = true; status.buttons.sync = getState(device->button_sync); status.buttons.home = getState(device->button_home); status.buttons.minus = getState(device->button_minus); status.buttons.plus = getState(device->button_plus); status.buttons.r = getState(device->button_trigger_r); status.buttons.l = getState(device->button_trigger_l); status.buttons.zr = getState(device->button_trigger_zr); status.buttons.zl = getState(device->button_trigger_zl); status.buttons.down = getState(device->button_down); status.buttons.up = getState(device->button_up); status.buttons.right = getState(device->button_right); status.buttons.left = getState(device->button_left); status.buttons.y = getState(device->button_y); status.buttons.x = getState(device->button_x); status.buttons.b = getState(device->button_b); status.buttons.a = getState(device->button_a); status.buttons.stickR = getState(device->button_stick_r); status.buttons.stickL = getState(device->button_stick_l); status.leftStickX = 0.0f; status.leftStickY = 0.0f; status.rightStickX = 0.0f; status.rightStickY = 0.0f; auto &deviceKeyboard = std::get(device->typeExtra); if (getState(deviceKeyboard.left_stick_up)) { status.leftStickY += 1.0f; } if (getState(deviceKeyboard.left_stick_down)) { status.leftStickY -= 1.0f; } if (getState(deviceKeyboard.left_stick_left)) { status.leftStickX -= 1.0f; } if (getState(deviceKeyboard.left_stick_right)) { status.leftStickX += 1.0f; } if (getState(deviceKeyboard.right_stick_up)) { status.rightStickY += 1.0f; } if (getState(deviceKeyboard.right_stick_down)) { status.rightStickY -= 1.0f; } if (getState(deviceKeyboard.right_stick_left)) { status.rightStickX -= 1.0f; } if (getState(deviceKeyboard.right_stick_right)) { status.rightStickX += 1.0f; } status.touch.down = false; int mouseX, mouseY; if (SDL_GetMouseState(&mouseX, &mouseY) & SDL_BUTTON(SDL_BUTTON_LEFT)) { // Calculate screen position auto displayLayout = gpu7::DisplayLayout { }; auto windowWidth = 0, windowHeight = 0; SDL_GetWindowSize(mWindow, &windowWidth, &windowHeight); gpu7::updateDisplayLayout(displayLayout, static_cast(windowWidth), static_cast(windowHeight)); // Check that mouse is inside DRC screen if (displayLayout.drc.visible) { auto drcLeft = displayLayout.drc.x; auto drcTop = displayLayout.drc.y; auto drcRight = drcLeft + displayLayout.drc.width; auto drcBottom = drcTop + displayLayout.drc.height; if (mouseX >= drcLeft && mouseX <= drcRight && mouseY >= drcTop && mouseY <= drcBottom) { status.touch.down = true; status.touch.x = (mouseX - drcLeft) / displayLayout.drc.width; status.touch.y = (mouseY - drcTop) / displayLayout.drc.height; } } } } else if (mVpad0Controller && device->type == config::input::Joystick) { auto controller = mVpad0Controller; auto joystick = SDL_GameControllerGetJoystick(controller); const auto getState = [controller, joystick](int button, SDL_GameControllerButton name) -> uint32_t { if (button >= 0) { return SDL_JoystickGetButton(joystick, button) ? 1 : 0; } else if (button == -2 && name != SDL_CONTROLLER_BUTTON_INVALID) { return SDL_GameControllerGetButton(controller, name); } else { return 0; } }; const auto getAxis = [controller, joystick](int index, SDL_GameControllerAxis name) -> float { auto value = 0; if (index >= 0) { value = SDL_JoystickGetAxis(joystick, index); } else if (index == -2 && name != SDL_CONTROLLER_AXIS_INVALID) { value = SDL_GameControllerGetAxis(controller, name); } if (value < 0) { return value / 32768.0f; } else { return value / 32767.0f; } }; status.connected = true; status.buttons.sync = getState(device->button_sync, SDL_CONTROLLER_BUTTON_INVALID); status.buttons.home = getState(device->button_home, SDL_CONTROLLER_BUTTON_GUIDE); status.buttons.minus = getState(device->button_minus, SDL_CONTROLLER_BUTTON_BACK); status.buttons.plus = getState(device->button_plus, SDL_CONTROLLER_BUTTON_START); status.buttons.r = getState(device->button_trigger_r, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); status.buttons.l = getState(device->button_trigger_l, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); status.buttons.zr = getAxis(device->button_trigger_zr, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > 0.5f; status.buttons.zl = getAxis(device->button_trigger_zl, SDL_CONTROLLER_AXIS_TRIGGERLEFT) > 0.5f; status.buttons.down = getState(device->button_down, SDL_CONTROLLER_BUTTON_DPAD_DOWN); status.buttons.up = getState(device->button_up, SDL_CONTROLLER_BUTTON_DPAD_UP); status.buttons.right = getState(device->button_right, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); status.buttons.left = getState(device->button_left, SDL_CONTROLLER_BUTTON_DPAD_LEFT); status.buttons.y = getState(device->button_y, SDL_CONTROLLER_BUTTON_Y); status.buttons.x = getState(device->button_x, SDL_CONTROLLER_BUTTON_X); status.buttons.stickR = getState(device->button_stick_r, SDL_CONTROLLER_BUTTON_RIGHTSTICK); status.buttons.stickL = getState(device->button_stick_l, SDL_CONTROLLER_BUTTON_LEFTSTICK); // Yes A and B are purposefully swapped. status.buttons.b = getState(device->button_b, SDL_CONTROLLER_BUTTON_A); status.buttons.a = getState(device->button_a, SDL_CONTROLLER_BUTTON_B); auto &deviceJoystick = std::get(device->typeExtra); status.leftStickX = getAxis(deviceJoystick.left_stick_x, SDL_CONTROLLER_AXIS_LEFTX); status.leftStickY = getAxis(deviceJoystick.left_stick_y, SDL_CONTROLLER_AXIS_LEFTY); if (deviceJoystick.left_stick_x_invert) { status.leftStickX = -status.leftStickX; } if (deviceJoystick.left_stick_y_invert) { status.leftStickY = -status.leftStickY; } status.rightStickX = getAxis(deviceJoystick.right_stick_x, SDL_CONTROLLER_AXIS_RIGHTX); status.rightStickY = getAxis(deviceJoystick.right_stick_y, SDL_CONTROLLER_AXIS_RIGHTY); if (deviceJoystick.right_stick_x_invert) { status.rightStickX = -status.rightStickX; } if (deviceJoystick.right_stick_y_invert) { status.rightStickY = -status.rightStickY; } } else { status.connected = false; } } void DecafSDL::sampleWpadController(int channel, wpad::Status &status) { status.type = wpad::BaseControllerType::Disconnected; } ================================================ FILE: src/decaf-sdl/decafsdl_posix.cpp ================================================ #include #ifdef PLATFORM_POSIX #include void setWindowIcon(SDL_Window *window) { } #endif ================================================ FILE: src/decaf-sdl/decafsdl_sound.cpp ================================================ #include "decafsdl_sound.h" #include "config.h" #include #include #include DecafSDLSound::DecafSDLSound() { } DecafSDLSound::~DecafSDLSound() { SDL_CloseAudio(); } bool DecafSDLSound::start(unsigned outputRate, unsigned numChannels) { if (!mLog) { mLog = decaf::makeLogger("sdl-sound"); } mNumChannelsIn = numChannels; mNumChannelsOut = std::min(numChannels, 2u); // TODO: support surround output mOutputFrameLen = config::sound::frame_length * (outputRate / 1000); // Set up the ring buffer with enough space for 3 output frames of audio mOutputBuffer.resize(mOutputFrameLen * mNumChannelsOut * 3); mBufferWritePos = 0; mBufferReadPos = 0; SDL_AudioSpec audiospec; audiospec.format = AUDIO_S16LSB; audiospec.freq = outputRate; audiospec.channels = static_cast(mNumChannelsOut); audiospec.samples = static_cast(mOutputFrameLen); audiospec.callback = sdlCallback; audiospec.userdata = this; if (SDL_OpenAudio(&audiospec, nullptr) != 0) { mLog->error("Failed to open audio device: {}", SDL_GetError()); return false; } SDL_PauseAudio(0); return true; } void DecafSDLSound::output(int16_t *samples, unsigned numSamples) { // Discard channels from the input if necessary. if (mNumChannelsIn != mNumChannelsOut) { decaf_check(mNumChannelsOut < mNumChannelsIn); for (auto sample = 1u; sample < numSamples; ++sample) { for (auto channel = 0u; channel < mNumChannelsOut; channel++) { samples[sample * mNumChannelsOut + channel] = samples[sample * mNumChannelsIn + channel]; } } } // Copy to the output buffer, ignoring the possibility of overrun // (which should never happen anyway). auto numSamplesOut = static_cast(numSamples * mNumChannelsOut); while (mBufferWritePos + numSamplesOut >= mOutputBuffer.size()) { auto samplesToCopy = mOutputBuffer.size() - mBufferWritePos; std::memcpy(&mOutputBuffer[mBufferWritePos], samples, samplesToCopy * 2); mBufferWritePos = 0; samples += samplesToCopy; numSamplesOut -= samplesToCopy; } std::memcpy(&mOutputBuffer[mBufferWritePos], samples, numSamplesOut * 2); mBufferWritePos += numSamplesOut; } void DecafSDLSound::stop() { SDL_CloseAudio(); } void DecafSDLSound::sdlCallback(void *instance_, Uint8 *stream_, int size) { DecafSDLSound *instance = reinterpret_cast(instance_); int16_t *stream = reinterpret_cast(stream_); decaf_check(size >= 0); decaf_check(size % (2 * instance->mNumChannelsOut) == 0); auto numSamples = static_cast(size) / 2; auto samplesAvail = (instance->mBufferWritePos + instance->mOutputBuffer.size() - instance->mBufferReadPos) % instance->mOutputBuffer.size(); if (samplesAvail < numSamples) { // Rather than outputting the partial frame, output a full frame of // silence to give audio generation a chance to catch up. std::memset(stream, 0, size); } else { decaf_check(instance->mBufferReadPos + numSamples <= instance->mOutputBuffer.size()); std::memcpy(stream, &instance->mOutputBuffer[instance->mBufferReadPos], size); instance->mBufferReadPos = (instance->mBufferReadPos + numSamples) % instance->mOutputBuffer.size(); } } ================================================ FILE: src/decaf-sdl/decafsdl_sound.h ================================================ #pragma once #include #include #include #include class DecafSDLSound : public decaf::SoundDriver { public: DecafSDLSound(); ~DecafSDLSound() override; bool start(unsigned outputRate, unsigned numChannels) override; void output(int16_t *samples, unsigned numSamples) override; void stop() override; private: std::shared_ptr mLog; unsigned mNumChannelsIn; // Number of channels of data we receive in output() unsigned mNumChannelsOut; // Number of channels we send to the audio device unsigned mOutputFrameLen; // Number of samples (per channel) in an output frame // Output buffer (ring buffer): written by output(), read by SDL callback std::vector mOutputBuffer; size_t mBufferWritePos; // Index of next sample (array element) to write size_t mBufferReadPos; // Index of next sample (array element) to read static void sdlCallback(void *instance, Uint8 *stream, int size); }; ================================================ FILE: src/decaf-sdl/decafsdl_win.cpp ================================================ #include #ifdef PLATFORM_WINDOWS #define WIN32_LEAN_AND_MEAN #include #include #include extern "C" { __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } void setWindowIcon(SDL_Window *window) { auto handle = ::GetModuleHandle(nullptr); auto icon = ::LoadIcon(handle, L"IDI_MAIN_ICON"); if (icon != nullptr) { SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); if (SDL_GetWindowWMInfo(window, &wminfo) == 1) { auto hwnd = wminfo.info.win.window; ::SetClassLongPtr(hwnd, GCLP_HICON, reinterpret_cast(icon)); } } } #endif ================================================ FILE: src/decaf-sdl/main.cpp ================================================ #include "clilog.h" #include "config.h" #include "decafsdl.h" #include #include #include #include #include #include #include #include #include #include #include std::shared_ptr gCliLog; using namespace decaf::input; static excmd::parser getCommandLineParser() { excmd::parser parser; using excmd::description; using excmd::optional; using excmd::default_value; using excmd::allowed; using excmd::value; using excmd::make_default_value; parser.global_options() .add_option("v,version", description { "Show version." }) .add_option("h,help", description { "Show help." }); parser.add_command("help") .add_argument("help-command", optional {}, value {}); auto frontend_options = parser.add_option_group("Frontend Options") .add_option("config", description { "Specify path to configuration file." }, value {}) .add_option("force-sync", description { "Force rendering to sync with gpu flips." }) .add_option("display-layout", description { "Set the window display layout." }, default_value { "split" }, allowed { { "split", "toggle" } }) .add_option("display-mode", description { "Set the window display mode." }, default_value { "windowed" }, allowed { { "windowed", "fullscreen" } }) .add_option("display-stretch", description { "Enable display stretching, aspect ratio will not be maintained." }) .add_option("sound", description { "Enable sound output." }); auto input_options = parser.add_option_group("Input Options") .add_option("vpad0", description { "Select the input device for VPAD0." }, make_default_value(config::input::vpad0)); auto test_options = parser.add_option_group("Test Options") .add_option("timeout-ms", description { "Maximum time to run for before termination in milliseconds." }, make_default_value(config::test::timeout_ms)) .add_option("timeout-frames", description { "Maximum number of frames to render before termination." }, make_default_value(config::test::timeout_frames)) .add_option("dump-drc-frames", description { "Dump rendered DRC frames to file." }) .add_option("dump-tv-frames", description { "Dump rendered TV frames to file." }) .add_option("dump-frames-dir", description { "Folder to place dumped frames in" }, make_default_value(config::test::dump_frames_dir)); auto config_options = config::getExcmdGroups(parser); auto cmdPlay = parser.add_command("play") .add_option_group(frontend_options) .add_option_group(input_options) .add_argument("target", value {}); auto cmdTest = parser.add_command("test") .add_option_group(frontend_options) .add_option_group(test_options) .add_argument("target", value {}); for (auto group : config_options) { cmdPlay.add_option_group(group); cmdTest.add_option_group(group); } return parser; } static std::string getPathBasename(const std::string &path) { auto pos = path.find_last_of("/\\"); if (!pos) { return path; } else { return path.substr(pos + 1); } } static int start(excmd::parser &parser, excmd::option_state &options) { // Print version if (options.has("version")) { // TODO: print git hash std::cout << "Decaf Emulator version 0.0.1" << std::endl; std::exit(0); } // Print help if (options.empty() || options.has("help")) { if (options.has("help-command")) { std::cout << parser.format_help("decaf", options.get("help-command")) << std::endl; } else { std::cout << parser.format_help("decaf") << std::endl; } std::exit(0); } auto target = options.get("target"); // Load config file std::string configPath, configError; if (options.has("config")) { configPath = options.get("config"); } else { decaf::createConfigDirectory(); configPath = decaf::makeConfigPath("config.toml"); } auto cpuSettings = cpu::Settings { }; auto gpuSettings = gpu::Settings { }; auto decafSettings = decaf::Settings { }; // If config file does not exist, create a default one. if (!platform::fileExists(configPath)) { auto toml = toml::table(); config::saveToTOML(toml, cpuSettings); config::saveToTOML(toml, gpuSettings); config::saveToTOML(toml, decafSettings); config::saveFrontendToml(toml); std::ofstream out { configPath }; out << toml; } try { auto toml = toml::parse_file(configPath); config::loadFromTOML(toml, cpuSettings); config::loadFromTOML(toml, gpuSettings); config::loadFromTOML(toml, decafSettings); config::loadFrontendToml(toml); } catch (const toml::parse_error &ex) { configError = ex.what(); } config::loadFromExcmd(options, cpuSettings); config::loadFromExcmd(options, gpuSettings); config::loadFromExcmd(options, decafSettings); cpu::setConfig(cpuSettings); decaf::setConfig(decafSettings); gpu::setConfig(gpuSettings); // Now handle frontend config options if (options.has("vpad0")) { config::input::vpad0 = options.get("vpad0"); } if (options.has("timeout-ms")) { config::test::timeout_ms = options.get("timeout-ms"); } if (options.has("timeout-frames")) { config::test::timeout_frames = options.get("timeout-frames"); } if (options.has("dump-drc-frames")) { config::test::dump_drc_frames = true; } if (options.has("dump-tv-frames")) { config::test::dump_tv_frames = true; } if (options.has("dump-frames-dir")) { config::test::dump_frames_dir = options.get("dump-frames-dir"); } // Initialise libdecaf logger auto logFile = getPathBasename(target); decaf::initialiseLogging(logFile); // Initialise frontend logger if (!decafSettings.log.to_stdout) { // Always do cli log to stdout gCliLog = decaf::makeLogger("decaf-cli", { std::make_shared() }); } else { gCliLog = decaf::makeLogger("decaf-cli"); } gCliLog->set_pattern("[%l] %v"); gCliLog->info("Target {}", target); if (configError.empty()) { gCliLog->info("Loaded config from {}", configPath); } else { gCliLog->error("Failed to parse config {}: {}", configPath, configError); } DecafSDL sdl; if (!sdl.initCore()) { gCliLog->error("Failed to initialise SDL"); return -1; } if (!sdl.initGraphics()) { gCliLog->error("Failed to initialise graphics backend."); return -1; } if (options.has("sound") && !sdl.initSound()) { gCliLog->error("Failed to initialise SDL sound"); return -1; } if (!sdl.run(target)) { gCliLog->error("Failed to start game"); return -1; } return 0; } #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { auto parser = getCommandLineParser(); excmd::option_state options; if (AttachConsole(ATTACH_PARENT_PROCESS)) { FILE *dumbFuck; freopen_s(&dumbFuck, "CONOUT$", "w", stdout); freopen_s(&dumbFuck, "CONOUT$", "w", stderr); } try { options = parser.parse(lpCmdLine); } catch (excmd::exception ex) { std::cout << "Error parsing options: " << ex.what() << std::endl; std::exit(-1); } return start(parser, options); } #else int main(int argc, char **argv) { auto parser = getCommandLineParser(); excmd::option_state options; try { options = parser.parse(argc, argv); } catch (excmd::exception ex) { std::cout << "Error parsing options: " << ex.what() << std::endl; std::exit(-1); } return start(parser, options); } #endif ================================================ FILE: src/decaf_buildinfo.h.in ================================================ #pragma once #define GIT_REV "@GIT_REV@" #define GIT_BRANCH "@GIT_BRANCH@" #define GIT_DESC "@GIT_DESC@" #define BUILD_NAME "@REPO_NAME@" #define BUILD_DATE "@BUILD_DATE@" #define BUILD_FULLNAME "@BUILD_FULLNAME@" #define BUILD_VERSION "@BUILD_VERSION@" ================================================ FILE: src/decaf_game.h ================================================ #pragma once #include #include namespace decaf { struct GameInfo { std::string executable; uint64_t titleId; }; } // namespace decaf ================================================ FILE: src/libconfig/CMakeLists.txt ================================================ project(libconfig) include_directories(".") include_directories("src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_library(libconfig STATIC ${SOURCE_FILES} ${HEADER_FILES}) GroupSources("Source Files" src) target_link_libraries(libconfig common libcpu libdecaf libgpu tomlplusplus excmd) ================================================ FILE: src/libconfig/config_excmd.h ================================================ #pragma once #include #include #include #include #include namespace config { std::vector getExcmdGroups(excmd::parser &parser); bool loadFromExcmd(excmd::option_state &options, cpu::Settings &cpuSettings); bool loadFromExcmd(excmd::option_state &options, decaf::Settings &decafSettings); bool loadFromExcmd(excmd::option_state &options, gpu::Settings &gpuSettings); } // namespace config ================================================ FILE: src/libconfig/config_toml.h ================================================ #pragma once #include #include #include #include #include #include #include #include namespace config { bool loadFromTOML(const toml::table &config, cpu::Settings &cpuSettings); bool loadFromTOML(const toml::table &config, decaf::Settings &decafSettings); bool loadFromTOML(const toml::table &config, gpu::Settings &gpuSettings); bool saveToTOML(toml::table &config, const cpu::Settings &cpuSettings); bool saveToTOML(toml::table &config, const decaf::Settings &decafSettings); bool saveToTOML(toml::table &config, const gpu::Settings &gpuSettings); template void readValue(const toml::table &config, const char *name, Type &value) { auto node = config.at_path(name); if constexpr (std::is_same_v) { if (auto ptr = node.as_boolean()) { value = ptr->get(); } } else if constexpr (std::is_integral_v::type>) { if (auto ptr = node.as_integer()) { value = static_cast(ptr->get()); } } else if constexpr (std::is_floating_point_v::type>) { if (auto ptr = node.as_floating_point()) { value = static_cast(ptr->get()); } } else if constexpr (std::is_same_v) { if (auto ptr = node.as_string()) { value = static_cast(ptr->get()); } } else { static_assert( std::is_same_v || std::is_integral_v::type> || std::is_floating_point_v::type> || std::is_same_v, "Invalid type passed to readValue"); } } } // namespace config ================================================ FILE: src/libconfig/src/loader_excmd.cpp ================================================ #include "config_excmd.h" #include #include #include #include #include #include namespace config { std::vector getExcmdGroups(excmd::parser &parser) { std::vector groups; using excmd::description; using excmd::optional; using excmd::default_value; using excmd::allowed; using excmd::value; auto gpu_options = parser.add_option_group("GPU Options") .add_option("gpu-debug", description { "Enable extra gpu debug info." }); groups.push_back(gpu_options.group); auto display_options = parser.add_option_group("Display Options") .add_option("background-colour", description { "Background colour." }, default_value { "153,51,51" }) .add_option("display-backend", description { "Which display backend to use." }, default_value { "vulkan" }, allowed { { "vulkan", "null", } }) .add_option("screen-mode", description { "Screen display mode." }, default_value { "windowed" }, allowed { { "windowed", "fullscreen" } }) .add_option("split-separation", default_value { 5.0 }, description { "Gap between screens when view-mode is split." }) .add_option("stretch-screen", description { "Stretch the screen to fill window rather than maintaining aspect ratio." }) .add_option("view-mode", description { "View display mode." }, default_value { "split" }, allowed { { "split", "tv", "gamepad1", "gamepad2" } }); groups.push_back(display_options.group); auto jit_options = parser.add_option_group("JIT Options") .add_option("jit", description { "Enable the JIT engine." }) .add_option("no-jit", description { "Disable the JIT engine." }) .add_option("jit-fast-math", description { "Enable JIT floating-point optimizations which may not exactly match PowerPC behavior. May not work for all games." }, default_value { "full" }, allowed { { "full", "no-fpscr" } }) .add_option("jit-opt-level", description { "Set the JIT optimization level. Higher levels give better performance but may cause longer translation delays. Level 3 may not work for all games." }, default_value { 1 }, allowed { { 0, 1, 2, 3 } }) .add_option("jit-verify", description { "Verify JIT implementation against interpreter." }) .add_option("jit-verify-addr", description { "Select single code block for JIT verification." }, default_value { 0 }); groups.push_back(jit_options.group); auto log_options = parser.add_option_group("Log Options") .add_option("log-async", description { "Enable asynchronous logging." }) .add_option("log-dir", description{ "Directory where log file will be written." }, value {}) .add_option("log-file", description { "Enable logging to file." }) .add_option("log-stdout", description { "Enable logging to stdout." }) .add_option("log-level", description { "Only display logs with severity equal to or greater than this level." }, default_value { "debug" }, allowed { { "trace", "debug", "info", "notice", "warning", "error", "critical", "alert", "emerg", "off" } }); groups.push_back(log_options.group); auto sys_options = parser.add_option_group("System Options") .add_option("region", description { "Set the system region." }, default_value { "US" }, allowed { { "EUR", "JAP", "US" } }) .add_option("resources-path", description { "Path to Decaf resource files." }, value {}) .add_option("content-path", description { "Sets which path to mount to /vol/content, only used for standalone rpx files." }, value {}) .add_option("hfio-path", description { "Sets which path to mount to /dev/hfio01." }, value {}) .add_option("mlc-path", description { "Sets which path to mount to /dev/mlc01." }, value {}) .add_option("otp-path", description { "Path to otp.bin." }, value {}) .add_option("sdcard-path", description { "Sets which path to mount to /dev/sdcard01." }, value {}) .add_option("slc-path", description { "Sets which path to mount to /dev/slc01." }, value {}) .add_option("time-scale", description { "Time scale factor for emulated clock." }, default_value { 1.0 }); groups.push_back(sys_options.group); return groups; } bool loadFromExcmd(excmd::option_state &options, decaf::Settings &decafSettings) { if (options.has("log-stdout")) { decafSettings.log.to_stdout = true; } if (options.has("log-file")) { decafSettings.log.to_file = true; } if (options.has("log-dir")) { decafSettings.log.directory = options.get("log-dir"); } if (options.has("log-async")) { decafSettings.log.async = true; } if (options.has("log-level")) { decafSettings.log.level = options.get("log-level"); } if (options.has("region")) { auto region = options.get("region"); if (iequals(region, "japan") == 0) { decafSettings.system.region = decaf::SystemRegion::Japan; } else if (iequals(region, "usa") == 0) { decafSettings.system.region = decaf::SystemRegion::USA; } else if (iequals(region, "europe") == 0) { decafSettings.system.region = decaf::SystemRegion::Europe; } else if (iequals(region, "china") == 0) { decafSettings.system.region = decaf::SystemRegion::China; } else if (iequals(region, "korea") == 0) { decafSettings.system.region = decaf::SystemRegion::Korea; } else if (iequals(region, "taiwan") == 0) { decafSettings.system.region = decaf::SystemRegion::Taiwan; } } if (options.has("resources-path")) { decafSettings.system.resources_path = options.get("resources-path"); } if (options.has("content-path")) { decafSettings.system.content_path = options.get("content-path"); } if (options.has("hfio-path")) { decafSettings.system.hfio_path = options.get("hfio-path"); } if (options.has("mlc-path")) { decafSettings.system.mlc_path = options.get("mlc-path"); } if (options.has("sdcard-path")) { decafSettings.system.sdcard_path = options.get("sdcard-path"); } if (options.has("slc-path")) { decafSettings.system.slc_path = options.get("slc-path"); } if (options.has("otp-path")) { decafSettings.system.otp_path = options.get("otp-path"); } if (options.has("time-scale")) { decafSettings.system.time_scale = options.get("time-scale"); } return true; } bool loadFromExcmd(excmd::option_state &options, cpu::Settings &cpuSettings) { if (options.has("jit")) { cpuSettings.jit.enabled = true; } else if (options.has("no-jit")) { cpuSettings.jit.enabled = false; } if (options.has("jit-verify")) { cpuSettings.jit.verify = true; } if (options.has("jit-verify-addr")) { cpuSettings.jit.verifyAddress = options.get("jit-verify-addr"); } if (options.has("jit-opt-level")) { auto level = options.get("jit-opt-level"); cpuSettings.jit.optimisationFlags.clear(); if (level >= 1) { cpuSettings.jit.optimisationFlags.push_back("BASIC"); cpuSettings.jit.optimisationFlags.push_back("DECONDITION"); cpuSettings.jit.optimisationFlags.push_back("DSE"); cpuSettings.jit.optimisationFlags.push_back("FOLD_CONSTANTS"); cpuSettings.jit.optimisationFlags.push_back("PPC_FORWARD_LOADS"); cpuSettings.jit.optimisationFlags.push_back("PPC_PAIRED_LWARX_STWCX"); cpuSettings.jit.optimisationFlags.push_back("X86_BRANCH_ALIGNMENT"); cpuSettings.jit.optimisationFlags.push_back("X86_CONDITION_CODES"); cpuSettings.jit.optimisationFlags.push_back("X86_FIXED_REGS"); cpuSettings.jit.optimisationFlags.push_back("X86_FORWARD_CONDITIONS"); cpuSettings.jit.optimisationFlags.push_back("X86_STORE_IMMEDIATE"); } if (level >= 2) { cpuSettings.jit.optimisationFlags.push_back("CHAIN"); // Skip DEEP_DATA_FLOW if verifying because its whole purpose is // to eliminate dead stores to registers. if (!cpuSettings.jit.verify) { cpuSettings.jit.optimisationFlags.push_back("DEEP_DATA_FLOW"); } cpuSettings.jit.optimisationFlags.push_back("PPC_TRIM_CR_STORES"); cpuSettings.jit.optimisationFlags.push_back("PPC_USE_SPLIT_FIELDS"); cpuSettings.jit.optimisationFlags.push_back("X86_ADDRESS_OPERANDS"); cpuSettings.jit.optimisationFlags.push_back("X86_MERGE_REGS"); } if (level >= 3) { cpuSettings.jit.optimisationFlags.push_back("PPC_CONSTANT_GQRS"); cpuSettings.jit.optimisationFlags.push_back("PPC_DETECT_FCFI_EMUL"); cpuSettings.jit.optimisationFlags.push_back("PPC_FAST_FCTIW"); } } if (options.has("jit-fast-math")) { // PPC_NO_FPSCR_STATE by itself (--jit-fast-math=no-fpscr) is safe to // use with --jit-verify as long as the game doesn't actually look // at FPSCR status bits. Other optimization flags may result in // verification differences even if the generated code is correct. cpuSettings.jit.optimisationFlags.push_back("PPC_NO_FPSCR_STATE"); if (options.get("jit-fast-math").compare("full") == 0) { cpuSettings.jit.optimisationFlags.push_back("DSE_FP"); cpuSettings.jit.optimisationFlags.push_back("FOLD_FP_CONSTANTS"); cpuSettings.jit.optimisationFlags.push_back("NATIVE_IEEE_NAN"); cpuSettings.jit.optimisationFlags.push_back("NATIVE_IEEE_UNDERFLOW"); cpuSettings.jit.optimisationFlags.push_back("PPC_ASSUME_NO_SNAN"); cpuSettings.jit.optimisationFlags.push_back("PPC_FAST_FMADDS"); cpuSettings.jit.optimisationFlags.push_back("PPC_FAST_FMULS"); cpuSettings.jit.optimisationFlags.push_back("PPC_FAST_STFS"); cpuSettings.jit.optimisationFlags.push_back("PPC_FNMADD_ZERO_SIGN"); cpuSettings.jit.optimisationFlags.push_back("PPC_IGNORE_FPSCR_VXFOO"); cpuSettings.jit.optimisationFlags.push_back("PPC_NATIVE_RECIPROCAL"); cpuSettings.jit.optimisationFlags.push_back("PPC_PS_STORE_DENORMALS"); cpuSettings.jit.optimisationFlags.push_back("PPC_SINGLE_PREC_INPUTS"); } } return true; } bool loadFromExcmd(excmd::option_state &options, gpu::Settings &gpuSettings) { if (options.has("gpu-debug")) { gpuSettings.debug.debug_enabled = true; } if (options.has("background-colour")) { auto colour = std::vector { }; split_string(options.get("background-colour"), ',', colour); if (colour.size() == 3) { gpuSettings.display.backgroundColour[0] = std::atoi(colour[0].c_str()); gpuSettings.display.backgroundColour[1] = std::atoi(colour[1].c_str()); gpuSettings.display.backgroundColour[2] = std::atoi(colour[2].c_str()); } } if (options.has("display-backend")) { auto mode = options.get("screen-mode"); if (mode.compare("vulkan") == 0) { gpuSettings.display.backend = gpu::DisplaySettings::Vulkan; } else if (mode.compare("null") == 0) { gpuSettings.display.backend = gpu::DisplaySettings::Null; } } if (options.has("screen-mode")) { auto mode = options.get("screen-mode"); if (mode.compare("windowed") == 0) { gpuSettings.display.screenMode = gpu::DisplaySettings::Windowed; } else if (mode.compare("fullscreen") == 0) { gpuSettings.display.screenMode = gpu::DisplaySettings::Fullscreen; } } if (options.has("stretch-screen")) { gpuSettings.display.maintainAspectRatio = false; } if (options.has("split-separation")) { gpuSettings.display.splitSeperation = options.get("split-separation"); } if (options.has("view-mode")) { auto layout = options.get("view-mode"); if (layout.compare("split") == 0) { gpuSettings.display.viewMode = gpu::DisplaySettings::Split; } else if (layout.compare("tv") == 0) { gpuSettings.display.viewMode = gpu::DisplaySettings::TV; } else if (layout.compare("gamepad1") == 0) { gpuSettings.display.viewMode = gpu::DisplaySettings::Gamepad1; } else if (layout.compare("gamepad2") == 0) { gpuSettings.display.viewMode = gpu::DisplaySettings::Gamepad2; } } return true; } } // namespace config ================================================ FILE: src/libconfig/src/loader_toml.cpp ================================================ #include "config_toml.h" #include #include namespace config { template inline void readArray(const toml::table &config, const char *name, std::vector &value) { auto ptr = config.at_path(name).as_array(); if (ptr) { value.clear(); for (auto itr = ptr->begin(); itr != ptr->end(); ++itr) { value.push_back(itr->template as()->get()); } } } static const char * translateDisplayBackend(gpu::DisplaySettings::Backend backend) { if (backend == gpu::DisplaySettings::Null) { return "null"; } else if (backend == gpu::DisplaySettings::Vulkan) { return "vulkan"; } return ""; } static std::optional translateDisplayBackend(const std::string &text) { if (text == "null") { return gpu::DisplaySettings::Null; } else if (text == "vulkan") { return gpu::DisplaySettings::Vulkan; } return { }; } static const char * translateScreenMode(gpu::DisplaySettings::ScreenMode mode) { if (mode == gpu::DisplaySettings::Windowed) { return "windowed"; } else if (mode == gpu::DisplaySettings::Fullscreen) { return "fullscreen"; } return ""; } static std::optional translateScreenMode(const std::string &text) { if (text == "windowed") { return gpu::DisplaySettings::Windowed; } else if (text == "fullscreen") { return gpu::DisplaySettings::Fullscreen; } return { }; } static const char * translateViewMode(gpu::DisplaySettings::ViewMode mode) { if (mode == gpu::DisplaySettings::TV) { return "tv"; } else if (mode == gpu::DisplaySettings::Gamepad1) { return "gamepad1"; } else if (mode == gpu::DisplaySettings::Gamepad2) { return "gamepad2"; } else if (mode == gpu::DisplaySettings::Split) { return "split"; } return ""; } static std::optional translateViewMode(const std::string &text) { if (text == "tv") { return gpu::DisplaySettings::TV; } else if (text == "gamepad1") { return gpu::DisplaySettings::Gamepad1; } else if (text == "gamepad2") { return gpu::DisplaySettings::Gamepad2; } else if (text == "split") { return gpu::DisplaySettings::Split; } return { }; } bool loadFromTOML(const toml::table &config, cpu::Settings &cpuSettings) { readValue(config, "mem.writetrack", cpuSettings.memory.writeTrackEnabled); readValue(config, "jit.enabled", cpuSettings.jit.enabled); readValue(config, "jit.verify", cpuSettings.jit.verify); readValue(config, "jit.verify_addr", cpuSettings.jit.verifyAddress); readValue(config, "jit.code_cache_size_mb", cpuSettings.jit.codeCacheSizeMB); readValue(config, "jit.data_cache_size_mb", cpuSettings.jit.dataCacheSizeMB); readArray(config, "jit.opt_flags", cpuSettings.jit.optimisationFlags); readValue(config, "jit.rodata_read_only", cpuSettings.jit.rodataReadOnly); return true; } bool loadFromTOML(const toml::table &config, decaf::Settings &decafSettings) { readValue(config, "debugger.enabled", decafSettings.debugger.enabled); readValue(config, "debugger.break_on_entry", decafSettings.debugger.break_on_entry); readValue(config, "debugger.break_on_exit", decafSettings.debugger.break_on_exit); readValue(config, "debugger.gdb_stub", decafSettings.debugger.gdb_stub); readValue(config, "debugger.gdb_stub_port", decafSettings.debugger.gdb_stub_port); readValue(config, "gx2.dump_textures", decafSettings.gx2.dump_textures); readValue(config, "gx2.dump_shaders", decafSettings.gx2.dump_shaders); readValue(config, "log.async", decafSettings.log.async); readValue(config, "log.branch_trace", decafSettings.log.branch_trace); readValue(config, "log.directory", decafSettings.log.directory); readValue(config, "log.hle_trace", decafSettings.log.hle_trace); readValue(config, "log.hle_trace_res", decafSettings.log.hle_trace_res); readArray(config, "log.hle_trace_filters", decafSettings.log.hle_trace_filters); readValue(config, "log.level", decafSettings.log.level); readValue(config, "log.to_file", decafSettings.log.to_file); readValue(config, "log.to_stdout", decafSettings.log.to_stdout); auto logLevels = config.at_path("log.levels").as_table(); if (logLevels) { decafSettings.log.levels.clear(); for (auto item : *logLevels) { if (auto level = item.second.as_string()) { decafSettings.log.levels.emplace_back(item.first, **level); } } } readValue(config, "sound.dump_sounds", decafSettings.sound.dump_sounds); readValue(config, "system.region", decafSettings.system.region); readValue(config, "system.hfio_path", decafSettings.system.hfio_path); readValue(config, "system.mlc_path", decafSettings.system.mlc_path); readValue(config, "system.otp_path", decafSettings.system.otp_path); readValue(config, "system.resources_path", decafSettings.system.resources_path); readValue(config, "system.sdcard_path", decafSettings.system.sdcard_path); readValue(config, "system.slc_path", decafSettings.system.slc_path); readValue(config, "system.content_path", decafSettings.system.content_path); readValue(config, "system.time_scale", decafSettings.system.time_scale); readArray(config, "system.lle_modules", decafSettings.system.lle_modules); readValue(config, "system.dump_hle_rpl", decafSettings.system.dump_hle_rpl); readArray(config, "system.title_directories", decafSettings.system.title_directories); return true; } bool loadFromTOML(const toml::table &config, gpu::Settings &gpuSettings) { readValue(config, "gpu.debug", gpuSettings.debug.debug_enabled); readValue(config, "gpu.dump_shaders", gpuSettings.debug.dump_shaders); readValue(config, "gpu.dump_shader_binaries_only", gpuSettings.debug.dump_shader_binaries_only); auto display = config.get_as("display"); if (display) { if (auto text = display->get_as("backend"); text) { if (auto backend = translateDisplayBackend(**text); backend) { gpuSettings.display.backend = *backend; } } if (auto text = display->get_as("screen_mode"); text) { if (auto screenMode = translateScreenMode(**text); screenMode) { gpuSettings.display.screenMode = *screenMode; } } if (auto text = display->get_as("view_mode"); text) { if (auto viewMode = translateViewMode(**text); viewMode) { gpuSettings.display.viewMode = *viewMode; } } if (auto maintainAspectRatio = display->get_as("maintain_aspect_ratio"); maintainAspectRatio) { gpuSettings.display.maintainAspectRatio = **maintainAspectRatio; } if (auto splitSeperation = display->get_as("split_seperation"); splitSeperation) { gpuSettings.display.splitSeperation = **splitSeperation; } if (auto backgroundColour = display->get_as("background_colour"); backgroundColour && backgroundColour->size() >= 3) { gpuSettings.display.backgroundColour = { static_cast(**backgroundColour->at(0).as_integer()), static_cast(**backgroundColour->at(1).as_integer()), static_cast(**backgroundColour->at(2).as_integer()) }; } } return true; } bool saveToTOML(toml::table &config, const cpu::Settings &cpuSettings) { auto jit = config.insert("jit", toml::table()).first->second.as_table(); jit->insert_or_assign("enabled", cpuSettings.jit.enabled); jit->insert_or_assign("verify", cpuSettings.jit.verify); jit->insert_or_assign("verify_addr", cpuSettings.jit.verifyAddress); jit->insert_or_assign("code_cache_size_mb", cpuSettings.jit.codeCacheSizeMB); jit->insert_or_assign("data_cache_size_mb", cpuSettings.jit.dataCacheSizeMB); jit->insert_or_assign("rodata_read_only", cpuSettings.jit.rodataReadOnly); auto opt_flags = toml::array(); for (auto &flag : cpuSettings.jit.optimisationFlags) { opt_flags.push_back(flag); } jit->insert_or_assign("opt_flags", opt_flags); return true; } bool saveToTOML(toml::table &config, const decaf::Settings &decafSettings) { // debugger auto debugger = config.insert("debugger", toml::table()).first->second.as_table(); debugger->insert_or_assign("enabled", decafSettings.debugger.enabled); debugger->insert_or_assign("break_on_entry", decafSettings.debugger.break_on_entry); debugger->insert_or_assign("break_on_exit", decafSettings.debugger.break_on_exit); debugger->insert_or_assign("gdb_stub", decafSettings.debugger.gdb_stub); debugger->insert_or_assign("gdb_stub_port", decafSettings.debugger.gdb_stub_port); // gx2 auto gx2 = config.insert("gx2", toml::table()).first->second.as_table(); gx2->insert_or_assign("dump_textures", decafSettings.gx2.dump_textures); gx2->insert_or_assign("dump_shaders", decafSettings.gx2.dump_shaders); // log auto log = config.insert("log", toml::table()).first->second.as_table(); log->insert_or_assign("async", decafSettings.log.async); log->insert_or_assign("branch_trace", decafSettings.log.branch_trace); log->insert_or_assign("directory", decafSettings.log.directory); log->insert_or_assign("hle_trace", decafSettings.log.hle_trace); log->insert_or_assign("hle_trace_res", decafSettings.log.hle_trace_res); log->insert_or_assign("level", decafSettings.log.level); log->insert_or_assign("to_file", decafSettings.log.to_file); log->insert_or_assign("to_stdout", decafSettings.log.to_stdout); auto levels = toml::table(); for (auto item : decafSettings.log.levels) { levels.insert_or_assign(item.first, item.second); } log->insert_or_assign("levels", std::move(levels)); auto hle_trace_filters = toml::array(); for (auto &filter : decafSettings.log.hle_trace_filters) { hle_trace_filters.push_back(filter); } log->insert_or_assign("hle_trace_filters", std::move(hle_trace_filters)); // sound auto sound = config.insert("sound", toml::table()).first->second.as_table(); sound->insert_or_assign("dump_sounds", decafSettings.sound.dump_sounds); // system auto system = config.insert("system", toml::table()).first->second.as_table(); system->insert_or_assign("region", static_cast(decafSettings.system.region)); system->insert_or_assign("hfio_path", decafSettings.system.hfio_path); system->insert_or_assign("mlc_path", decafSettings.system.mlc_path); system->insert_or_assign("otp_path", decafSettings.system.otp_path); system->insert_or_assign("resources_path", decafSettings.system.resources_path); system->insert_or_assign("sdcard_path", decafSettings.system.sdcard_path); system->insert_or_assign("slc_path", decafSettings.system.slc_path); system->insert_or_assign("content_path", decafSettings.system.content_path); system->insert_or_assign("time_scale", decafSettings.system.time_scale); auto lle_modules = toml::array(); for (auto &name : decafSettings.system.lle_modules) { lle_modules.push_back(name); } system->insert_or_assign("lle_modules", std::move(lle_modules)); auto title_directories = toml::array(); for (auto &name : decafSettings.system.title_directories) { title_directories.push_back(name); } system->insert_or_assign("title_directories", std::move(title_directories)); return true; } bool saveToTOML(toml::table &config, const gpu::Settings &gpuSettings) { // gpu auto gpu = config.insert("gpu", toml::table()).first->second.as_table(); gpu->insert_or_assign("debug", gpuSettings.debug.debug_enabled); gpu->insert_or_assign("dump_shaders", gpuSettings.debug.dump_shaders); gpu->insert_or_assign("dump_shader_binaries_only", gpuSettings.debug.dump_shader_binaries_only); // display auto display = config.insert("display", toml::table()).first->second.as_table(); display->insert_or_assign("backend", translateDisplayBackend(gpuSettings.display.backend)); display->insert_or_assign("screen_mode", translateScreenMode(gpuSettings.display.screenMode)); display->insert_or_assign("view_mode", translateViewMode(gpuSettings.display.viewMode)); display->insert_or_assign("maintain_aspect_ratio", gpuSettings.display.maintainAspectRatio); display->insert_or_assign("split_seperation", gpuSettings.display.splitSeperation); auto backgroundColour = toml::array(); backgroundColour.push_back(gpuSettings.display.backgroundColour[0]); backgroundColour.push_back(gpuSettings.display.backgroundColour[1]); backgroundColour.push_back(gpuSettings.display.backgroundColour[2]); display->insert_or_assign("background_colour", std::move(backgroundColour)); return true; } } // namespace config ================================================ FILE: src/libcpu/CMakeLists.txt ================================================ project(libcpu) include_directories(".") include_directories("src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) if(MSVC) add_library(libcpu STATIC ${SOURCE_FILES} ${HEADER_FILES} "cpu.natvis") else() add_library(libcpu STATIC ${SOURCE_FILES} ${HEADER_FILES}) endif() GroupSources("Header Files/expresso" espresso) GroupSources("Source Files" src) target_link_libraries(libcpu common binrec) if(MSVC) target_link_libraries(libcpu ntdll) else() target_link_libraries(libcpu m ${CMAKE_THREAD_LIBS_INIT}) endif() if(DECAF_PCH) target_precompile_headers(libcpu PRIVATE ) AutoGroupPCHFiles() endif() ================================================ FILE: src/libcpu/address.h ================================================ #pragma once #include #include #include namespace cpu { template class Address { public: using StorageType = uint32_t; constexpr Address() = default; constexpr explicit Address(StorageType address) : mAddress(address) { } explicit operator bool() const { return !!mAddress; } template::value>::type> explicit operator OtherType() const { return static_cast(mAddress); } constexpr bool operator == (const Address &other) const { return mAddress == other.mAddress; } constexpr bool operator != (const Address &other) const { return mAddress != other.mAddress; } constexpr bool operator >= (const Address &other) const { return mAddress >= other.mAddress; } constexpr bool operator <= (const Address &other) const { return mAddress <= other.mAddress; } constexpr bool operator > (const Address &other) const { return mAddress > other.mAddress; } constexpr bool operator < (const Address &other) const { return mAddress < other.mAddress; } constexpr Address &operator += (ptrdiff_t value) { mAddress = static_cast(mAddress + value); return *this; } constexpr Address &operator -= (ptrdiff_t value) { mAddress = static_cast(mAddress - value); return *this; } constexpr Address &operator &= (StorageType value) { mAddress = mAddress & value; return *this; } constexpr Address &operator ^= (StorageType value) { mAddress = mAddress ^ value; return *this; } constexpr Address operator + (ptrdiff_t value) const { return Address { static_cast(mAddress + value) }; } constexpr Address operator - (ptrdiff_t value) const { return Address { static_cast(mAddress - value) }; } constexpr ptrdiff_t operator - (const Address &other) const { return mAddress - other.mAddress; } constexpr Address operator & (StorageType value) const { return Address { static_cast(mAddress & value) }; } constexpr Address operator ^ (StorageType value) const { return Address { static_cast(mAddress ^ value) }; } constexpr StorageType operator % (StorageType value) const { return mAddress % value; } constexpr StorageType operator / (StorageType value) const { return mAddress / value; } constexpr StorageType operator << (StorageType value) const { return mAddress << value; } constexpr StorageType operator >> (StorageType value) const { return mAddress >> value; } constexpr StorageType getAddress() const { return mAddress; } private: StorageType mAddress = 0; }; template struct AddressRange { using address_type = Address; AddressRange(address_type start, uint32_t size) : start(start), size(size) { } AddressRange(address_type start, address_type end) : start(start), size(static_cast(end - start + 1)) { } constexpr bool contains(AddressRange &other) const { return (start <= other.start && start + size >= other.start + other.size); } address_type start; uint32_t size; }; class Physical; class Virtual; using PhysicalAddress = Address; using VirtualAddress = Address; using PhysicalAddressRange = AddressRange; using VirtualAddressRange = AddressRange; } // namespace cpu template constexpr inline cpu::Address align_up(cpu::Address value, size_t alignment) { return cpu::Address { align_up(value.getAddress(), alignment) }; } template constexpr inline cpu::Address align_down(cpu::Address value, size_t alignment) { return cpu::Address { align_down(value.getAddress(), alignment) }; } // Custom formatters for fmtlib namespace fmt { inline namespace v8 { template struct formatter; } template struct formatter, Char, void>; } // namespace fmt ================================================ FILE: src/libcpu/be2_array.h ================================================ #pragma once #include #include #include #include #include #include #include "be2_val.h" #include "pointer.h" #include "common/fixed.h" template struct be2_struct; // Equivalent to std:true_type if type T is a virt_ptr or phys_ptr or virt_func_ptr. template struct is_cpu_ptr : std::false_type { }; template struct is_cpu_ptr> : std::true_type { }; template struct is_cpu_ptr> : std::true_type { }; // Equivalent to std:true_type if type T is a cnl::fixed_point template struct is_fixed_point : std::false_type { }; template struct is_fixed_point> : std::true_type { }; // Detects the actual type to be used for be2_array members. template struct be2_array_item_type; template struct be2_array_item_type::value || std::is_enum::value || is_cpu_ptr>::value || is_fixed_point>::value>::type> { using type = be2_val; }; template struct be2_array_item_type>::value && !is_fixed_point>::value && std::is_class::value && !std::is_union::value>::type> { using type = be2_struct; }; template struct be2_array_item_type::value>::type> { using type = be2_val; }; template class be2_array_iterator; template class be2_array_const_iterator; // BigEndianValue but for arrays. template class be2_array { public: using raw_value_type = Type; using be2_value_type = typename be2_array_item_type::type; using size_type = uint32_t; using index_type = size_t; using difference_type = std::ptrdiff_t; using iterator = be2_array_iterator; using const_iterator = be2_array_const_iterator; be2_array() = default; template be2_array(const char (&src)[N], typename std::enable_if= N && std::is_same::value>::type * = 0) { std::copy_n(src, N, mValues); } template be2_array(std::string_view src, typename std::enable_if::value>::type * = 0) { auto count = (src.size() < Size) ? src.size() : Size - 1; std::copy_n(src.begin(), count, mValues); mValues[src.size()] = char { 0 }; } be2_array(std::initializer_list list) { if (list.size() > Size) { throw std::out_of_range("invalid be2_array initializer_list"); } auto i = 0u; for (auto &item : list) { mValues[i] = item; } } be2_array(const std::array &other) { for (auto i = 0u; i < Size; ++i) { mValues[i] = other[i]; } } template= N>::type> be2_array &operator =(const Type (&src)[N]) { std::copy_n(src, N, mValues); return *this; } template::value>::type> be2_array &operator =(const std::string_view &src) { auto count = (src.size() < Size) ? src.size() : Size - 1; std::copy_n(src.begin(), count, mValues); mValues[count] = char { 0 }; return *this; } constexpr auto &at(index_type pos) { if (pos >= Size) { throw std::out_of_range("invalid be2_array subscript"); } return mValues[pos]; } constexpr const auto &at(index_type pos) const { if (pos >= Size) { throw std::out_of_range("invalid be2_array subscript"); } return mValues[pos]; } constexpr auto &operator[](index_type pos) { return mValues[pos]; } constexpr const auto &operator[](index_type pos) const { return mValues[pos]; } constexpr auto &front() { return mValues[0]; } constexpr const auto &front() const { return mValues[0]; } constexpr auto &back() { return mValues[Size - 1]; } constexpr const auto &back() const { return mValues[Size - 1]; } constexpr bool empty() const { return Size > 0; } constexpr size_type size() const { return Size; } constexpr size_type max_size() const { return Size; } constexpr void fill(const raw_value_type &value) { for (auto i = 0u; i < Size; ++i) { mValues[i] = value; } } constexpr auto begin() { return iterator { *this, 0 }; } constexpr auto end() { return iterator { *this, Size }; } constexpr auto cbegin() { return const_iterator { *this, 0 }; } constexpr auto cend() { return const_iterator { *this, Size }; } std::array value() const { std::array result; for (auto i = 0u; i < Size; ++i) { result[i] = mValues[i]; } return result; } operator std::array() const { return value(); } // Please use virt_addrof or phys_addrof instead auto operator &() = delete; protected: be2_value_type mValues[Size]; }; template class be2_array_iterator { using array_type = be2_array; using size_type = typename array_type::size_type; public: be2_array_iterator(be2_array &arr, size_type index) : mIndex(index), mArray(arr) { } constexpr auto &operator*() { return mArray[mIndex]; } be2_array_iterator &operator++() { mIndex++; return *this; } bool operator !=(be2_array_iterator &other) const { return mIndex != other.mIndex; } private: size_type mIndex; array_type &mArray; }; template class be2_const_array_iterator { using array_type = const be2_array; using size_type = typename array_type::size_type; public: be2_const_array_iterator(array_type &arr, size_type index) : mArray(arr), mIndex(index) { } constexpr const auto &operator*() { return mArray[mIndex]; } be2_const_array_iterator &operator++() { mIndex++; return *this; } bool operator !=(be2_const_array_iterator &other) const { return mIndex != other.mIndex; } private: size_type mIndex; array_type &mArray; }; ================================================ FILE: src/libcpu/be2_atomic.h ================================================ #pragma once #include #include #include template class be2_atomic { public: static_assert(std::atomic::is_always_lock_free); static_assert(sizeof(std::atomic) == sizeof(Type)); static_assert(sizeof(Type) == 1 || sizeof(Type) == 2 || sizeof(Type) == 4 || sizeof(Type) == 8); using value_type = Type; be2_atomic() = default; value_type load(std::memory_order order = std::memory_order_seq_cst) const { return byte_swap(mStorage.load(order)); } void store(value_type value, std::memory_order order = std::memory_order_seq_cst) { mStorage.store(byte_swap(value), order); } value_type exchange(value_type value, std::memory_order order = std::memory_order_seq_cst) { return byte_swap(mStorage.exchange(byte_swap(value), order)); } bool compare_exchange_weak(value_type &expected, value_type desired, std::memory_order order = std::memory_order_seq_cst) { auto expectedValue = byte_swap(expected); auto result = mStorage.compare_exchange_weak(expectedValue, byte_swap(desired), order); expected = byte_swap(expectedValue); return result; } bool compare_exchange_weak(value_type &expected, value_type desired, std::memory_order success, std::memory_order failure) { auto expectedValue = byte_swap(expected); auto result = mStorage.compare_exchange_weak(expectedValue, byte_swap(desired), success, failure); expected = byte_swap(expectedValue); return result; } bool compare_exchange_strong(value_type &expected, value_type desired, std::memory_order order = std::memory_order_seq_cst) { auto expectedValue = byte_swap(expected); auto result = mStorage.compare_exchange_strong(expectedValue, byte_swap(desired), order); expected = byte_swap(expectedValue); return result; } bool compare_exchange_strong(value_type &expected, value_type desired, std::memory_order success, std::memory_order failure) { auto expectedValue = byte_swap(expected); auto result = mStorage.compare_exchange_strong(expectedValue, byte_swap(desired), success, failure); expected = byte_swap(expectedValue); return result; } value_type fetch_add(value_type addValue) { auto oldValue = load(std::memory_order_relaxed); auto newValue = oldValue + addValue; while (!compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_relaxed)) { newValue = oldValue + addValue; } return oldValue; } value_type fetch_sub(value_type subValue) { auto oldValue = load(std::memory_order_relaxed); auto newValue = oldValue - subValue; while (!compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_relaxed)) { newValue = oldValue - subValue; } return oldValue; } value_type fetch_and(value_type subValue) { auto oldValue = load(std::memory_order_relaxed); auto newValue = oldValue & subValue; while (!compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_relaxed)) { newValue = oldValue & subValue; } return oldValue; } value_type fetch_or(value_type subValue) { auto oldValue = load(std::memory_order_relaxed); auto newValue = oldValue | subValue; while (!compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_relaxed)) { newValue = oldValue | subValue; } return oldValue; } value_type fetch_xor(value_type subValue) { auto oldValue = load(std::memory_order_relaxed); auto newValue = oldValue ^ subValue; while (!compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_relaxed)) { newValue = oldValue ^ subValue; } return oldValue; } private: std::atomic mStorage; }; ================================================ FILE: src/libcpu/be2_struct.h ================================================ #pragma once #include "address.h" #include "be2_array.h" #include "be2_val.h" #include "functionpointer.h" #include "pointer.h" #include "mmu.h" #include #include #include #include using virt_addr = cpu::VirtualAddress; using phys_addr = cpu::PhysicalAddress; using virt_addr_range = cpu::VirtualAddressRange; using phys_addr_range = cpu::PhysicalAddressRange; template using virt_ptr = cpu::Pointer; template using phys_ptr = cpu::Pointer; template using virt_func_ptr = cpu::FunctionPointer; template using phys_func_ptr = cpu::FunctionPointer; template using be2_ptr = be2_val>; template using be2_virt_ptr = be2_ptr; template using be2_phys_ptr = be2_val>; template using be2_virt_func_ptr = be2_val>; template using be2_phys_func_ptr = be2_val>; /* * be2_struct is a wrapper intended to be used around struct value type members * in structs which reside in PPC memory. This is so we can use members with * virt_addrof or phys_addrof. */ template struct be2_struct : public Type { using value_type = std::remove_cv_t; using value_type::value_type; using value_type::operator =; // Please use virt_addrof or phys_addrof instead auto operator &() = delete; }; // reinterpret_cast for virt_addr to virt_ptr template::value>::type> inline auto virt_cast(virt_addr src) { return cpu::pointer_cast_impl::cast(src); } // reinterpret_cast for virt_ptr to virt_ptr or virt_addr template::value || std::is_same::value>::type> inline auto virt_cast(const virt_ptr &src) { return cpu::pointer_cast_impl::cast(src); } // reinterpret_cast for be2_ptr to virt_ptr or virt_addr template::value || std::is_same::value>::type> inline auto virt_cast(const be2_virt_ptr &src) { return cpu::pointer_cast_impl::cast(src); } // reinterpret_cast for phys_addr to phys_ptr template::value>::type> inline auto phys_cast(phys_addr src) { return cpu::pointer_cast_impl::cast(src); } // reinterpret_cast for phys_ptr to phys_ptr or phys_addr template::value || std::is_same::value>::type> inline auto phys_cast(const phys_ptr &src) { return cpu::pointer_cast_impl::cast(src); } // reinterpret_cast for be2_ptr to phys_ptr or phys_addr template::value || std::is_same::value>::type> inline auto phys_cast(const be2_phys_ptr &src) { return cpu::pointer_cast_impl::cast(src); } // reinterpret_cast for virt_addr to virt_func_ptr template inline auto virt_func_cast(virt_addr src) { return cpu::func_pointer_cast_impl::cast(src); } // reinterpret_cast for virt_func_ptr to virt_addr template::value>::type> inline auto virt_func_cast(virt_func_ptr src) { return cpu::func_pointer_cast_impl::cast(src); } // reinterpret_cast for be2_virt_func_ptr to virt_addr template::value>::type> inline auto virt_func_cast(be2_virt_func_ptr src) { return cpu::func_pointer_cast_impl::cast(src); } // reinterpret_cast for phys_addr to phys_func_ptr template inline auto phys_func_cast(phys_addr src) { return cpu::func_pointer_cast_impl::cast(src); } // reinterpret_cast for phys_func_ptr to phys_addr template::value>::type> inline auto phys_func_cast(phys_func_ptr src) { return cpu::func_pointer_cast_impl::cast(src); } // reinterpret_cast for be2_phys_func_ptr to phys_addr template::value>::type> inline auto phys_func_cast(be2_phys_func_ptr src) { return cpu::func_pointer_cast_impl::cast(src); } /** * Returns a virt_ptr to a big endian value type. * * The address of the object should be in virtual memory, * as in virtualMemoryBase < &ref < virtualMemoryEnd. */ template inline virt_ptr virt_addrof(const be2_struct &ref) { return virt_cast(cpu::translate(std::addressof(ref))); } template inline virt_ptr virt_addrof(const be2_val &ref) { return virt_cast(cpu::translate(std::addressof(ref))); } template virt_ptr virt_addrof(const be2_array &ref) { return virt_cast(cpu::translate(std::addressof(ref))); } /** * Returns a phys_ptr to a big endian value type. * * The address of the object should be in physical memory, * as in physicalMemoryBase < &ref < physicalMemoryEnd. * * Will not auto translate a virtual address to a physical address, for that * you should use cpu::virtualToPhysicalAddress(virt_addrof(x)). */ template inline phys_ptr phys_addrof(const be2_struct &ref) { return phys_cast(cpu::translatePhysical(std::addressof(ref))); } template inline phys_ptr phys_addrof(const be2_val &ref) { return phys_cast(cpu::translatePhysical(std::addressof(ref))); } template inline phys_ptr phys_addrof(const be2_array &ref) { return phys_cast(cpu::translatePhysical(std::addressof(ref))); } /** * Used to translate the this pointer to a virt_ptr. */ template inline virt_ptr virt_this(Type *self) { return virt_cast(cpu::translate(self)); } /** * Used to translate the this pointer to a virt_ptr. */ template inline phys_ptr phys_this(Type *self) { return phys_cast(cpu::translatePhysical(self)); } // Equivalent to std:true_type if type T is a virt_ptr. template struct is_virt_ptr : std::false_type { }; template struct is_virt_ptr> : std::true_type { }; // Equivalent to std:true_type if type T is a virt_func_ptr. template struct is_virt_func_ptr : std::false_type { }; template struct is_virt_func_ptr> : std::true_type { }; // Equivalent to std:true_type if type T is a phys_ptr. template struct is_phys_ptr : std::false_type { }; template struct is_phys_ptr> : std::true_type { }; template constexpr inline cpu::Pointer align_up(cpu::Pointer value, size_t alignment) { auto address = cpu::pointer_cast_impl::cast(value); address = static_cast(align_up(address.getAddress(), alignment)); return cpu::pointer_cast_impl::cast(address); } template constexpr inline cpu::Pointer align_down(cpu::Pointer value, size_t alignment) { auto address = cpu::pointer_cast_impl::cast(value); address = static_cast(align_down(address.getAddress(), alignment)); return cpu::pointer_cast_impl::cast(address); } template constexpr inline Type align_up(be2_val value, size_t alignment) { return align_up(value.value(), alignment); } template constexpr inline Type align_down(be2_val value, size_t alignment) { return align_down(value.value(), alignment); } ================================================ FILE: src/libcpu/be2_val.h ================================================ #pragma once #include #include template class be2_val { public: static_assert(!std::is_array::value, "be2_val invalid type: array"); static_assert(!std::is_pointer::value, "be2_val invalid type: pointer"); static_assert(sizeof(Type) == 1 || sizeof(Type) == 2 || sizeof(Type) == 4 || sizeof(Type) == 8, "be2_val invalid type size"); using value_type = Type; be2_val() = default; template::value || std::is_convertible::value>::type> be2_val(const OtherType &other) { if constexpr (std::is_constructible::value) { setValue(value_type { other }); } else { setValue(static_cast(other)); } } template::value || std::is_convertible::value>::type> be2_val(OtherType &&other) { if constexpr (std::is_constructible::value) { setValue(value_type { std::forward(other) }); } else { setValue(static_cast(std::forward(other))); } } template::value || std::is_constructible::value>::type> be2_val(const be2_val &other) { if constexpr (std::is_constructible::value) { setValue(value_type { other.value() }); } else { setValue(static_cast(other.value())); } } template::value || std::is_constructible::value>::type> be2_val(be2_val &&other) { if constexpr (std::is_constructible::value) { setValue(value_type { other.value() }); } else { setValue(static_cast(other.value())); } } value_type value() const { return byte_swap(mStorage); } void setValue(value_type value) { mStorage = byte_swap(value); } operator value_type() const { return value(); } template::value || std::is_constructible::value >::type> explicit operator bool() const { return static_cast(value()); } template::value || std::is_constructible::value || std::is_convertible::type>::value >::type> explicit operator OtherType() const { return static_cast(value()); } template::value || std::is_convertible::value>::type> be2_val & operator =(const OtherType &other) { if constexpr (std::is_constructible::value) { setValue(value_type { other }); } else { setValue(static_cast(other)); } return *this; } template::value || std::is_convertible::value>::type> be2_val & operator =(OtherType &&other) { if constexpr (std::is_constructible::value) { setValue(value_type { std::forward(other) }); } else { setValue(static_cast(std::forward(other))); } return *this; } template::value || std::is_constructible::value>::type> be2_val & operator =(const be2_val &other) { if constexpr (std::is_constructible::value) { setValue(value_type { other.value() }); } else { setValue(static_cast(other.value())); } return *this; } template::value || std::is_constructible::value>::type> be2_val & operator =(be2_val &&other) { if constexpr (std::is_constructible::value) { setValue(value_type { other.value() }); } else { setValue(static_cast(other.value())); } return *this; } template auto operator ==(const OtherType &other) const -> decltype(std::declval().operator ==(std::declval())) { return value() == other; } template auto operator !=(const OtherType &other) const -> decltype(std::declval().operator !=(std::declval())) { return value() != other; } template auto operator >=(const OtherType &other) const -> decltype(std::declval().operator >=(std::declval())) { return value() >= other; } template auto operator <=(const OtherType &other) const -> decltype(std::declval().operator <=(std::declval())) { return value() <= other; } template auto operator >(const OtherType &other) const -> decltype(std::declval().operator >(std::declval())) { return value() > other; } template auto operator <(const OtherType &other) const -> decltype(std::declval().operator <(std::declval())) { return value() < other; } template auto operator +() const -> decltype(std::declval(). operator+()) { return +value(); } template auto operator -() const -> decltype(std::declval(). operator-()) { return -value(); } template auto operator +(const OtherType &other) const -> decltype(std::declval().operator +(std::declval())) { return value() + other; } template auto operator -(const OtherType &other)const -> decltype(std::declval().operator -(std::declval())) { return value() - other; } template auto operator *(const OtherType &other) const -> decltype(std::declval().operator *(std::declval())) { return value() * other; } template auto operator /(const OtherType &other) const -> decltype(std::declval().operator /(std::declval())) { return value() / other; } template auto operator %(const OtherType &other) const -> decltype(std::declval().operator %(std::declval())) { return value() % other; } template auto operator |(const OtherType &other) const -> decltype(std::declval().operator |(std::declval())) { return value() | other; } template auto operator &(const OtherType &other) const -> decltype(std::declval().operator &(std::declval())) { return value() & other; } template auto operator ^(const OtherType &other) const -> decltype(std::declval().operator ^(std::declval())) { return value() ^ other; } template auto operator <<(const OtherType &other) const -> decltype(std::declval().operator <<(std::declval())) { return value() << other; } template auto operator >>(const OtherType &other) const -> decltype(std::declval().operator >>(std::declval())) { return value() >> other; } template() + std::declval())> be2_val &operator +=(const OtherType &other) { *this = static_cast(value() + other); return *this; } template() - std::declval())> be2_val &operator -=(const OtherType &other) { *this = static_cast(value() - other); return *this; } template() * std::declval())> be2_val &operator *=(const OtherType &other) { *this = static_cast(value() * other); return *this; } template() / std::declval())> be2_val &operator /=(const OtherType &other) { *this = static_cast(value() / other); return *this; } template() % std::declval())> be2_val &operator %=(const OtherType &other) { *this = static_cast(value() % other); return *this; } template() | std::declval())> be2_val &operator |=(const OtherType &other) { *this = static_cast(value() | other); return *this; } template() & std::declval())> be2_val &operator &=(const OtherType &other) { *this = static_cast(value() & other); return *this; } template() ^ std::declval())> be2_val &operator ^=(const OtherType &other) { *this = static_cast(value() ^ other); return *this; } template() << std::declval())> be2_val &operator <<=(const OtherType &other) { *this = value() << other; return *this; } template() >> std::declval())> be2_val &operator >>=(const OtherType &other) { *this = value() >> other; return *this; } template() + 1)> be2_val &operator ++() { setValue(value() + 1); return *this; } template() + 1)> be2_val operator ++(int) { auto before = *this; setValue(value() + 1); return before; } template() - 1)> be2_val &operator --() { setValue(value() - 1); return *this; } template() - 1)> be2_val operator --(int) { auto before = *this; setValue(value() - 1); return before; } template auto operator [](const IndexType &index) -> decltype(std::declval().operator [](std::declval())) { return value().operator [](index); } template auto operator [](const IndexType &index) const -> decltype(std::declval().operator [](std::declval())) { return value().operator [](index); } template auto operator ->() -> decltype(std::declval().operator ->()) { return value().operator ->(); } template auto operator ->() const -> decltype(std::declval().operator ->()) { return value().operator ->(); } template auto operator *() -> decltype(std::declval().operator *()) { return value().operator *(); } template auto operator *() const -> decltype(std::declval().operator *()) { return value().operator ->(); } // Helper to access FunctionPointer::getAddress template auto getAddress() const -> decltype(std::declval().getAddress()) { return value().getAddress(); } // Helper to access Pointer::get template auto get() const -> decltype(std::declval().get()) { return value().get(); } // Helper to access Pointer::getRawPointer template auto getRawPointer() const -> decltype(std::declval().getRawPointer()) { return value().getRawPointer(); } // Please use virt_addrof or phys_addrof instead auto operator &() = delete; private: value_type mStorage; }; // Custom formatters for fmtlib namespace fmt { inline namespace v8 { template struct formatter; // Disable stream operator detection for be2_val namespace internal { template class is_streamable; template class is_streamable, Char> : public std::false_type { }; } } // Custom formatter defined in cpu_formatters.h template struct formatter, Char, void>; } // namespace fmt ================================================ FILE: src/libcpu/cpu.h ================================================ #pragma once #include "mem.h" #include "state.h" #include "cpu_control.h" #include "be2_struct.h" #include #include #include #include #include #include struct Tracer; namespace cpu { enum class jit_mode { disabled, enabled, verify }; static const uint32_t CALLBACK_ADDR = 0xFBADCDE0; using EntrypointHandler = std::function; using InterruptHandler = void (*)(Core *core, uint32_t interrupt_flags); using SegfaultHandler = void(*)(Core *core, uint32_t address, platform::StackTrace *hostStackTrace); using BranchTraceHandler = void(*)(Core *core, uint32_t target); using SystemCallHandler = Core * (*)(Core *core, uint32_t id); void initialise(); void setCoreEntrypointHandler(EntrypointHandler handler); void setInterruptHandler(InterruptHandler handler); void setSegfaultHandler(SegfaultHandler handler); void setBranchTraceHandler(BranchTraceHandler handler); void setUnknownSystemCallHandler(SystemCallHandler handler); uint32_t registerSystemCallHandler(SystemCallHandler handler); uint32_t registerIllegalSystemCall(); void start(); void join(); void halt(); using Tracer = ::Tracer; Tracer * allocTracer(size_t size); void freeTracer(Tracer *tracer); namespace this_core { void setTracer(Tracer *tracer); } // namespace this_core } // namespace cpu ================================================ FILE: src/libcpu/cpu.natvis ================================================ {mStorage} (not set) 0xCCCC (not set) 0xCDCD (in freed object) 0xDDDD (in freed object) 0xEEFE (heap buffer overrun) 0xFDFD (heap buffer overrun) 0xABAB {($T1)((((*(uint16_t*)&mStorage)&0xFF)<<8)|(((*(uint16_t*)&mStorage)&0xFF00)>>8))} ({($T1)((((*(uint16_t*)&mStorage)&0xFF)<<8)|(((*(uint16_t*)&mStorage)&0xFF00)>>8)),4x}) (not set) {($T1)0xCCCCCCCC} (not set) {($T1)0xCDCDCDCD} (in freed object) {($T1)0xDDDDDDDD} (in freed object) {($T1)0xEEFEEEFE} (heap buffer overrun) {($T1)0xFDFDFDFD} (heap buffer overrun) {($T1)0xABABABAB} {($T1)((((*(uint32_t*)&mStorage)&0xFF)<<24)|(((*(uint32_t*)&mStorage)&0xFF00)<<8)|(((*(uint32_t*)&mStorage)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage)&0xFF000000)>>24))} ({((((*(uint32_t*)&mStorage)&0xFF)<<24)|(((*(uint32_t*)&mStorage)&0xFF00)<<8)|(((*(uint32_t*)&mStorage)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage)&0xFF000000)>>24)),8x}) mStorage ($T1)((((*(uint16_t*)&mStorage)&0xFF)<<8)|(((*(uint16_t*)&mStorage)&0xFF00)>>8)) ($T1)((((*(uint32_t*)&mStorage)&0xFF)<<24)|(((*(uint32_t*)&mStorage)&0xFF00)<<8)|(((*(uint32_t*)&mStorage)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage)&0xFF000000)>>24)) NULL (not set) (not set) (in freed object) (in freed object) (heap buffer overrun) (heap buffer overrun) {($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)mAddress.mAddress)} ($T1*)(nullptr) ($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)mAddress.mAddress) NULL (not set) (not set) (in freed object) (in freed object) (heap buffer overrun) (heap buffer overrun) {($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)mAddress.mAddress)} ($T1*)(nullptr) ($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)mAddress.mAddress) NULL (not set) (not set) (in freed object) (in freed object) (heap buffer overrun) (heap buffer overrun) {($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)((((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF)<<24)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF00)<<8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF000000)>>24)))} ($T1*)(nullptr) ($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)((((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF)<<24)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF00)<<8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF000000)>>24))) NULL (not set) (not set) (in freed object) (in freed object) (heap buffer overrun) (heap buffer overrun) {($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)((((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF)<<24)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF00)<<8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF000000)>>24)))} ($T1*)(nullptr) ($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)((((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF)<<24)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF00)<<8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF0000)>>8)|(((*(uint32_t*)&mStorage.mAddress.mAddress)&0xFF000000)>>24))) infinity -infinity (not set) (not set) (in freed object) (in freed object) (heap buffer overrun) (heap buffer overrun) { (1-(2*((*(unsigned *)(&mStorage) & 0x80) >> 7))) * (((long long)65536 << ((((((*(unsigned *)(&mStorage) & 0xFF) << 24) | ((*(unsigned *)(&mStorage) & 0xFF00) << 8) | ((*(unsigned *)(&mStorage) & 0xFF0000) >> 8) | ((*(unsigned *)(&mStorage) & 0xFF000000) >> 24)) & 0x7F800000) >> 23) - 127)) / 65536.0f) * (((((*(unsigned *)(&mStorage) & 0xFF) << 24) | ((*(unsigned *)(&mStorage) & 0xFF00) << 8) | ((*(unsigned *)(&mStorage) & 0xFF0000) >> 8) | ((*(unsigned *)(&mStorage) & 0xFF000000) >> 24)) & 0x7FFFFF) * 0.00000011920928955078125 + 1) ,g} ================================================ FILE: src/libcpu/cpu_breakpoints.h ================================================ #pragma once #include #include #include namespace cpu { struct Breakpoint { enum Type : uint32_t { SingleFire, MultiFire, }; //! Breakpoint type. Type type; //! Address of breakpoint. uint32_t address; //! Code at address before we inserted a TW instruction. uint32_t savedCode; }; using BreakpointList = std::vector; void addBreakpoint(uint32_t address, Breakpoint::Type type); void removeBreakpoint(uint32_t address); bool testBreakpoint(uint32_t address); bool hasBreakpoints(); bool hasBreakpoint(uint32_t address); std::shared_ptr getBreakpoints(); uint32_t getBreakpointSavedCode(uint32_t address); } // namespace cpu ================================================ FILE: src/libcpu/cpu_config.h ================================================ #pragma once #include #include #include #include namespace cpu { struct JitSettings { //! Enable usage of jit bool enabled = true; //! Use JIT in verification mode where it compares execution to interpreter bool verify = false; //! Select a single block (starting address) for verification (0 = verify everything) uint32_t verifyAddress = 0u; //! JIT code cache size in megabytes unsigned int codeCacheSizeMB = 1024; //! JIT data cache size in megabytes unsigned int dataCacheSizeMB = 512; //! List of JIT optimizations to enable std::vector optimisationFlags = { "BASIC", "DECONDITION", "DSE", "FOLD_CONSTANTS", "PPC_PAIRED_LWARX_STWCX", "X86_BRANCH_ALIGNMENT", "X86_CONDITION_CODES", "X86_FIXED_REGS", "X86_FORWARD_CONDITIONS", "X86_STORE_IMMEDIATE", }; //! Treat .rodata sections as read-only regardless of RPL/RPX flags bool rodataReadOnly = true; }; struct MemorySettings { //! Whether page guards for write tracking is enabled or not. bool writeTrackEnabled = false; }; struct Settings { JitSettings jit; MemorySettings memory; }; std::shared_ptr config(); void setConfig(const Settings &settings); } // namespace cpu ================================================ FILE: src/libcpu/cpu_control.h ================================================ #pragma once #include "state.h" #include namespace cpu { const uint32_t SRESET_INTERRUPT = 1 << 0; const uint32_t GENERIC_INTERRUPT = 1 << 1; const uint32_t ALARM_INTERRUPT = 1 << 2; const uint32_t DBGBREAK_INTERRUPT = 1 << 3; const uint32_t GPU7_INTERRUPT = 1 << 4; const uint32_t IPC_INTERRUPT = 1 << 5; const uint32_t PROGRAM_INTERRUPT = 1 << 6; const uint32_t INTERRUPT_MASK = 0xFFFFFFFF; const uint32_t NONMASKABLE_INTERRUPTS = SRESET_INTERRUPT; const uint32_t InvalidCoreId = 0xFF; std::chrono::steady_clock::time_point tbToTimePoint(uint64_t ticks); void clearInstructionCache(); void invalidateInstructionCache(uint32_t address, uint32_t size); void addJitReadOnlyRange(uint32_t address, uint32_t size); void interrupt(int core_idx, uint32_t flags); namespace this_core { void checkInterrupts(); void waitForInterrupt(); void waitNextInterrupt(std::chrono::steady_clock::time_point until = { }); uint32_t interruptMask(); uint32_t setInterruptMask(uint32_t mask); void clearInterrupt(uint32_t flags); void setNextAlarm(std::chrono::steady_clock::time_point alarm_time); void resume(); void executeSub(); cpu::Core* state(); uint32_t id(); } // namespace this_core } // namespace cpu ================================================ FILE: src/libcpu/cpu_formatters.h ================================================ #pragma once #include "address.h" #include "be2_val.h" #include "pointer.h" #include namespace fmt { /* * fmtlib formatter for cpu::Address */ template struct formatter, Char, void> { template constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } template auto format(const cpu::Address &addr, FormatContext &ctx) { return format_to(ctx.out(), "0x{:08X}", addr.getAddress()); } }; /* * fmtlib formatter for be2_val */ template struct formatter, Char, void> : formatter::type, Char, void> { using value_type = typename safe_underlying_type::type; template constexpr auto parse(ParseContext &ctx) { return formatter::parse(ctx); } template auto format(const be2_val &val, FormatContext &ctx) { return formatter::format(val, ctx); } }; /* * fmtlib formatter for cpu::FunctionPointer */ template struct formatter, Char, void> { template constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } template auto format(const cpu::FunctionPointer &ptr, FormatContext &ctx) { auto addr = cpu::func_pointer_cast_impl::cast(ptr); return format_to(ctx.out(), "{:08X}", static_cast(addr)); } }; /* * fmtlib formatter for cpu::Pointer */ template inline auto format_escaped_string(OutputIt iter, const char *data) { iter = format_to(iter, "\""); auto hasMoreBytes = true; for (auto i = 0; i < 128; ++i) { auto c = data[i]; if (c == 0) { hasMoreBytes = false; break; } if (c >= ' ' && c <= '~' && c != '\\' && c != '"') { iter = format_to(iter, "{}", c); } else { switch (c) { case '"': iter = format_to(iter, "\\\""); break; case '\\': iter = format_to(iter, "\\\\"); break; case '\t': iter = format_to(iter, "\\t"); break; case '\r': iter = format_to(iter, "\\r"); break; case '\n': iter = format_to(iter, "\\n"); break; default: iter = format_to(iter, "\\x{:02x}", c); break; } } } if (!hasMoreBytes) { iter = format_to(iter, "\""); } else { iter = format_to(iter, "\"..."); } return iter; } template struct formatter, Char, void> { template constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } template auto format(const cpu::Pointer &ptr, FormatContext &ctx) { if (!ptr) { return format_to(ctx.out(), ""); } else { auto bytes = ptr.getRawPointer(); return format_escaped_string(ctx.out(), bytes); } } }; template struct formatter, Char, void> { template constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } template auto format(const cpu::Pointer &ptr, FormatContext &ctx) { if (!ptr) { return format_to(ctx.out(), ""); } else { const char *bytes = ptr.getRawPointer(); return format_escaped_string(ctx.out(), bytes); } } }; template struct formatter, Char, void> { template constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } template auto format(const cpu::Pointer &ptr, FormatContext &ctx) { auto addr = cpu::pointer_cast_impl::cast(ptr); return format_to(ctx.out(), "0x{:08X}", static_cast(addr)); } }; } // namespace fmt ================================================ FILE: src/libcpu/espresso/espresso_disassembler.cpp ================================================ #include "espresso_disassembler.h" #include "espresso_instructionset.h" #include #include #include #include #include namespace espresso { static bool disassembleField(Disassembly::Argument &result, uint32_t cia, Instruction instr, InstructionInfo *data, InstructionField field) { switch (field) { case InstructionField::bd: result.type = Disassembly::Argument::Address; result.address = sign_extend<16>(instr.bd << 2) + (instr.aa ? 0 : cia); break; case InstructionField::bi: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.bi; break; case InstructionField::bo: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.bo; break; case InstructionField::crbA: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.crbA; break; case InstructionField::crbB: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.crbB; break; case InstructionField::crbD: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.crbD; break; case InstructionField::crfD: result.type = Disassembly::Argument::Register; result.registerName = "crf" + std::to_string(instr.crfD); break; case InstructionField::crfS: result.type = Disassembly::Argument::Register; result.registerName = "crf" + std::to_string(instr.crfS); break; case InstructionField::crm: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.crm; break; case InstructionField::d: result.type = Disassembly::Argument::ValueSigned; result.valueSigned = sign_extend<16>(instr.d); break; case InstructionField::fm: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.fm; break; case InstructionField::frA: result.type = Disassembly::Argument::Register; result.registerName = "f" + std::to_string(instr.frA); break; case InstructionField::frB: result.type = Disassembly::Argument::Register; result.registerName = "f" + std::to_string(instr.frB); break; case InstructionField::frC: result.type = Disassembly::Argument::Register; result.registerName = "f" + std::to_string(instr.frC); break; case InstructionField::frD: result.type = Disassembly::Argument::Register; result.registerName = "f" + std::to_string(instr.frD); break; case InstructionField::frS: result.type = Disassembly::Argument::Register; result.registerName = "f" + std::to_string(instr.frS); break; case InstructionField::i: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.i; break; case InstructionField::imm: result.type = Disassembly::Argument::ValueUnsigned; result.valueUnsigned = instr.imm; break; case InstructionField::kcn: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.kcn; break; case InstructionField::li: result.type = Disassembly::Argument::Address; result.address = sign_extend<26>(instr.li << 2) + (instr.aa ? 0 : cia); break; case InstructionField::mb: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.mb; break; case InstructionField::me: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.me; break; case InstructionField::nb: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.nb; break; case InstructionField::qd: result.type = Disassembly::Argument::ValueSigned; result.valueSigned = sign_extend<12>(instr.qd); break; case InstructionField::rA: result.type = Disassembly::Argument::Register; result.registerName = "r" + std::to_string(instr.rA); break; case InstructionField::rB: result.type = Disassembly::Argument::Register; result.registerName = "r" + std::to_string(instr.rB); break; case InstructionField::rD: result.type = Disassembly::Argument::Register; result.registerName = "r" + std::to_string(instr.rD); break; case InstructionField::rS: result.type = Disassembly::Argument::Register; result.registerName = "r" + std::to_string(instr.rS); break; case InstructionField::sh: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.sh; break; case InstructionField::simm: result.type = Disassembly::Argument::ValueSigned; result.valueSigned = sign_extend<16>(instr.simm); break; case InstructionField::sr: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.sr; break; case InstructionField::spr: // TODO: Real SPR name result.type = Disassembly::Argument::Register; result.registerName = "spr" + std::to_string(((instr.spr << 5) & 0x3E0) | ((instr.spr >> 5) & 0x1F)); break; case InstructionField::to: result.type = Disassembly::Argument::ConstantUnsigned; result.constantUnsigned = instr.to; break; case InstructionField::tbr: result.type = Disassembly::Argument::Register; result.registerName = "tbr" + std::to_string(instr.spr); break; case InstructionField::uimm: result.type = Disassembly::Argument::ValueUnsigned; result.valueUnsigned = instr.uimm; break; // Ignore opcode fields case InstructionField::opcd: case InstructionField::xo1: case InstructionField::xo2: case InstructionField::xo3: case InstructionField::xo4: // Ignore name modifiers case InstructionField::aa: case InstructionField::lk: case InstructionField::oe: case InstructionField::rc: // Ignore marker fields case InstructionField::PS: case InstructionField::XERO: case InstructionField::XERSO: case InstructionField::XERC: case InstructionField::CR0: case InstructionField::CR1: case InstructionField::FCRISI: case InstructionField::FCRIDI: case InstructionField::FCRSNAN: case InstructionField::FCRZDZ: case InstructionField::FPRF: case InstructionField::AOE: case InstructionField::ARC: case InstructionField::LR: case InstructionField::CTR: case InstructionField::FPSCR: case InstructionField::RSRV: return false; default: result.type = Disassembly::Argument::Invalid; break; } return true; } std::string disassemblyToText(const Disassembly &dis) { auto text = dis.name; for (auto &arg : dis.args) { if (&arg == &dis.args[0]) { text += " "; } else { text += ", "; } text += disassemblyArgumentToText(arg); } return text; } std::string disassemblyArgumentToText(const Disassembly::Argument &arg) { std::stringstream ss; switch (arg.type) { case Disassembly::Argument::Address: ss << "@" << std::setfill('0') << std::setw(8) << std::hex << std::uppercase << arg.address; return ss.str(); case Disassembly::Argument::Register: return arg.registerName; case Disassembly::Argument::ValueUnsigned: if (arg.valueUnsigned > 9) { ss << "0x" << std::hex << arg.valueUnsigned; } else { ss << arg.valueUnsigned; } return ss.str(); case Disassembly::Argument::ValueSigned: if (arg.valueSigned < -9) { ss << "-0x" << std::hex << -arg.valueSigned; } else if (arg.valueSigned > 9) { ss << "0x" << std::hex << arg.valueSigned; } else { ss << arg.valueSigned; } return ss.str(); case Disassembly::Argument::ConstantUnsigned: return std::to_string(arg.constantUnsigned); case Disassembly::Argument::ConstantSigned: return std::to_string(arg.constantSigned); case Disassembly::Argument::Invalid: return std::string("???"); } return std::string(); } static void checkBranchConditionAlias(Instruction instr, Disassembly &dis) { auto bi = instr.bi % 4; auto bo = instr.bo; auto name = std::string {}; // Check for unconditional branch if (bo == 20 && bi == 0) { // Remove bo, bi from args dis.args.erase(dis.args.begin(), dis.args.begin() + 2); if (dis.instruction->id == InstructionID::bcctr) { dis.name = "bctr"; } else if (dis.instruction->id == InstructionID::bclr) { dis.name = "blr"; } else if (dis.instruction->id == InstructionID::bc) { dis.name = "b"; } return; } if (bo == 12 && bi == 0) { name = "blt"; } else if (bo == 4 && bi == 1) { name = "ble"; } else if (bo == 12 && bi == 2) { name = "beq"; } else if (bo == 4 && bi == 0) { name = "bge"; } else if (bo == 12 && bi == 1) { name = "bgt"; } else if (bo == 4 && bi == 2) { name = "bne"; } else if (bo == 12 && bi == 3) { name = "bso"; } else if (bo == 4 && bi == 3) { name = "bns"; } if (!name.empty()) { // Remove bo, bi from args dis.args.erase(dis.args.begin(), dis.args.begin() + 2); // Add crS argument auto cr = Disassembly::Argument { }; cr.type = Disassembly::Argument::Register; cr.registerName = "cr" + std::to_string(instr.bi / 4); dis.args.push_back(cr); // Update name dis.name = name; } } bool disassemble(Instruction instr, Disassembly &dis, uint32_t address) { auto data = decodeInstruction(instr); if (!data) { return false; } auto alias = findInstructionAlias(data, instr); dis.name = alias ? alias->name : data->name; dis.instruction = data; dis.address = address; auto args = std::vector { }; args.reserve(16); for (auto &field : data->write) { // Skip arguments that are in read list as well if (std::find(data->read.begin(), data->read.end(), field) != data->read.end()) { continue; } // Add only unique arguements if (std::find(args.begin(), args.end(), field) != args.end()) { continue; } // Ignore trace only fields for disassembly if (isInstructionFieldMarker(field)) { continue; } args.push_back(field); } for (auto &field : data->read) { // Add only unique arguements if (std::find(args.begin(), args.end(), field) != args.end()) { continue; } args.push_back(field); } for (auto &field : args) { auto arg = Disassembly::Argument { }; // If we have an alias, then skip the LHS field of each alias comparison if (alias) { auto skipField = false; for (auto &op : alias->opcode) { if (field == op.field) { skipField = true; break; } } if (skipField) { continue; } } if (disassembleField(arg, dis.address, instr, data, field)) { dis.args.push_back(arg); } } // Check for bc alias if (data->id == InstructionID::bc || data->id == InstructionID::bcctr || data->id == InstructionID::bclr) { checkBranchConditionAlias(instr, dis); } for (auto &field : data->flags) { if (field == InstructionField::aa && instr.aa) { dis.name += 'a'; } else if (field == InstructionField::lk && instr.lk) { dis.name += 'l'; } else if (field == InstructionField::oe && instr.oe) { dis.name += 'o'; } else if (field == InstructionField::rc && instr.rc) { dis.name += '.'; } } return true; } // TODO: Maybe these enums should be moved to a common header rather than // duplicated from interpreter enum BoBits { CtrValue = 1, NoCheckCtr = 2, CondValue = 3, NoCheckCond = 4 }; enum BcFlags { BcCheckCtr = 1 << 0, BcCheckCond = 1 << 1, BcBranchLR = 1 << 2, BcBranchCTR = 1 << 3 }; template static BranchInfo disassembleBranchInfoBX(Instruction instr, uint32_t address, uint32_t ctr, uint32_t cr, uint32_t lr) { auto info = BranchInfo { }; info.isVariable = false; info.isCall = instr.lk; info.isConditional = false; info.conditionSatisfied = true; info.target = 0xFFFFFFFF; auto bo = instr.bo; if constexpr (flags & BcCheckCtr) { if (!get_bit(bo)) { info.isConditional = true; auto ctb = static_cast(ctr - 1 != 0); auto ctv = get_bit(bo); if (!(ctb ^ ctv)) { info.conditionSatisfied = false; } } } if constexpr (!!(flags & BcCheckCond)) { if (!get_bit(bo)) { info.isConditional = true; auto crb = get_bit(cr, 31 - instr.bi); auto crv = get_bit(bo); if (crb != crv) { info.conditionSatisfied = false; } } } if constexpr (!!(flags & BcBranchCTR)) { info.isVariable = true; if (ctr) { info.target = ctr & ~0x3; } } else if constexpr (!!(flags & BcBranchLR)) { info.isVariable = true; if (lr) { info.target = lr & ~0x3; } } else { info.target = sign_extend<16>(instr.bd << 2); if (!instr.aa) { info.target += address; } } return info; } BranchInfo disassembleBranchInfo(InstructionID id, Instruction ins, uint32_t address, uint32_t ctr, uint32_t cr, uint32_t lr) { auto info = BranchInfo { }; if (id == InstructionID::b) { info.isVariable = false; info.isCall = ins.lk; info.isConditional = false; info.conditionSatisfied = true; info.target = sign_extend<26>(ins.li << 2); if (!ins.aa) { info.target += address; } } else if (id == InstructionID::bc) { info = disassembleBranchInfoBX(ins, address, ctr, cr, lr); } else if (id == InstructionID::bcctr) { info = disassembleBranchInfoBX(ins, address, ctr, cr, lr); } else if (id == InstructionID::bclr) { info = disassembleBranchInfoBX(ins, address, ctr, cr, lr); } return info; } } // namespace espresso ================================================ FILE: src/libcpu/espresso/espresso_disassembler.h ================================================ #pragma once #include #include #include #include "espresso_instruction.h" #include "espresso_instructionid.h" namespace espresso { struct InstructionInfo; struct BranchInfo { bool isVariable; uint32_t target; bool isConditional; bool conditionSatisfied; bool isCall; }; struct Disassembly { struct Argument { enum Type { Invalid, Address, Register, ValueUnsigned, ValueSigned, ConstantUnsigned, ConstantSigned }; Type type; std::string registerName; union { uint32_t address; uint32_t constantUnsigned; int32_t constantSigned; uint32_t valueUnsigned; int32_t valueSigned; }; }; uint32_t address; InstructionInfo *instruction; std::string name; std::vector args; }; bool disassemble(Instruction bin, Disassembly &out, uint32_t address); std::string disassemblyToText(const Disassembly &dis); std::string disassemblyArgumentToText(const Disassembly::Argument &arg); BranchInfo disassembleBranchInfo(InstructionID id, Instruction ins, uint32_t address, uint32_t ctr, uint32_t cr, uint32_t lr); } // namespace espresso ================================================ FILE: src/libcpu/espresso/espresso_instruction.h ================================================ #pragma once #include namespace espresso { union Instruction { Instruction() : value(0) { } constexpr Instruction(uint32_t value_) : value(value_) { } uint32_t value; operator uint32_t() const { return value; } #define FLD(x, y, z, ...) \ struct { \ uint32_t : (31 - z); \ uint32_t x : (z - y + 1); \ uint32_t : (y); \ }; #define MRKR(...) #include "espresso_instruction_fields.inl" #undef FLD #undef MRKR }; } // namespace espresso ================================================ FILE: src/libcpu/espresso/espresso_instruction_aliases.inl ================================================ INSA(li, addi, (rA == 0)) INSA(lis, addis, (rA == 0)) INSA(mflr, mfspr, (spr == 8)) INSA(mfctr, mfspr, (spr == 9)) INSA(mfxer, mfspr, (spr == 1)) INSA(mtlr, mtspr, (spr == 8)) INSA(mtctr, mtspr, (spr == 9)) INSA(mtxer, mtspr, (spr == 1)) INSA(mtcr, mtcrf, (crm == 0xFF)) INSA(mr, or_, (rS == rB)) INSA(nop, ori, (rA == 0, rS == 0, uimm == 0)) INSA(not, nor, (rS == rB)) ================================================ FILE: src/libcpu/espresso/espresso_instruction_definitions.inl ================================================ //INS(name, write, read, flags, opcodes, fullname) // Intger Arithmetic INS(add, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 266), "Add") INS(addc, (rD, XERC), (rA, rB), (oe, rc), (opcd == 31, xo2 == 10), "Add with Carry") INS(adde, (rD), (rA, rB, XERC), (oe, rc), (opcd == 31, xo2 == 138), "Add Extended") INS(addi, (rD), (rA, simm), (), (opcd == 14), "Add Immediate") INS(addic, (rD, XERC), (rA, simm), (), (opcd == 12), "Add Immediate with Carry") INS(addicx, (rD, XERC), (rA, simm), (AOE, ARC), (opcd == 13), "Add Immediate with Carry and Record") INS(addis, (rD), (rA, simm), (), (opcd == 15), "Add Immediate Shifted") INS(addme, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 234, !_16_20), "Add to Minus One Extended") INS(addze, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 202, !_16_20), "Add to Zero Extended") INS(divw, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 491), "Divide Word") INS(divwu, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 459), "Divide Word Unsigned") INS(mulhw, (rD), (rA, rB), (rc), (opcd == 31, xo2 == 75), "Multiply High Word") INS(mulhwu, (rD), (rA, rB), (rc), (opcd == 31, xo2 == 11), "Multiply High Word Unsigned") INS(mulli, (rD), (rA, simm), (), (opcd == 7), "Multiply Low Immediate") INS(mullw, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 235), "Multiply Low Word") INS(neg, (rD), (rA), (oe, rc), (opcd == 31, xo2 == 104, !_16_20), "Negate") INS(subf, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 40), "Subtract From") INS(subfc, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 8), "Subtract From with Carry") INS(subfe, (rD), (rA, rB, XERC), (oe, rc), (opcd == 31, xo2 == 136), "Subtract From Extended") INS(subfic, (rD, XERC), (rA, simm), (), (opcd == 8), "Subtract From Immediate with Carry") INS(subfme, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 232, !_16_20), "Subtract From Minus One Extended") INS(subfze, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 200, !_16_20), "Subtract From Zero Extended") // Integer Compare INS(cmp, (crfD), (rA, rB, XERSO), (l), (opcd == 31, xo1 == 0, !_9, !_31), "Compare") INS(cmpi, (crfD), (rA, simm, XERSO), (l), (opcd == 11, !_9), "Compare Immediate") INS(cmpl, (crfD), (rA, rB, XERSO), (l), (opcd == 31, xo1 == 32, !_9, !_31), "Compare Logical") INS(cmpli, (crfD), (rA, uimm, XERSO), (l), (opcd == 10, !_9), "Compare Logical Immediate") // Integer Logical INS(and_, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 28), "AND") INS(andc, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 60), "AND with Complement") INS(andi, (rA), (rS, uimm), (AOE, ARC), (opcd == 28), "AND Immediate") INS(andis, (rA), (rS, uimm), (AOE, ARC), (opcd == 29), "AND Immediate Shifted") INS(cntlzw, (rA), (rS), (rc), (opcd == 31, xo1 == 26, !_16_20), "Count Leading Zeroes Word") INS(eqv, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 284), "Equivalent") INS(extsb, (rA), (rS), (rc), (opcd == 31, xo1 == 954, !_16_20), "Extend Sign Byte") INS(extsh, (rA), (rS), (rc), (opcd == 31, xo1 == 922, !_16_20), "Extend Sign Half Word") INS(nand, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 476), "NAND") INS(nor, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 124), "NOR") INS(or_, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 444), "OR") INS(orc, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 412), "OR with Complement") INS(ori, (rA), (rS, uimm), (), (opcd == 24), "OR Immediate") INS(oris, (rA), (rS, uimm), (), (opcd == 25), "OR Immediate Shifted") INS(xor_, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 316), "XOR") INS(xori, (rA), (rS, uimm), (), (opcd == 26), "XOR Immediate") INS(xoris, (rA), (rS, uimm), (), (opcd == 27), "XOR Immediate Shifted") // Integer Rotate INS(rlwimi, (rA), (rA, rS, sh, mb, me), (rc), (opcd == 20), "Rotate Left Word Immediate then Mask Insert") INS(rlwinm, (rA), (rS, sh, mb, me), (rc), (opcd == 21), "Rotate Left Word Immediate then AND with Mask") INS(rlwnm, (rA), (rS, rB, mb, me), (rc), (opcd == 23), "Rotate Left Word then AND with Mask") // Integer Shift INS(slw, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 24), "Shift Left Word") INS(sraw, (rA, XERC), (rS, rB), (rc), (opcd == 31, xo1 == 792), "Shift Right Arithmetic Word") INS(srawi, (rA, XERC), (rS, sh), (rc), (opcd == 31, xo1 == 824), "Shift Right Arithmetic Word Immediate") INS(srw, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 536), "Shift Right Word") // Floating-Point Arithmetic INS(fadd, (frD, FCRISI, FCRSNAN), (frA, frB), (frc), (opcd == 63, xo4 == 21), "Floating Add") INS(fadds, (frD, FCRISI, FCRSNAN), (frA, frB), (frc), (opcd == 59, xo4 == 21), "Floating Add Single") INS(fdiv, (frD, FCRZDZ, FCRIDI, FCRSNAN), (frA, frB), (frc), (opcd == 63, xo4 == 18), "Floating Divide") INS(fdivs, (frD), (frA, frB), (frc), (opcd == 59, xo4 == 18), "Floating Divide Single") INS(fmul, (frD), (frA, frC), (frc), (opcd == 63, xo4 == 25), "Floating Multiply") INS(fmuls, (frD), (frA, frC), (frc), (opcd == 59, xo4 == 25), "Floating Multiply Single") INS(fres, (frD), (frB), (frc), (opcd == 59, xo4 == 24), "Floating Reciprocal Estimate Single") INS(frsqrte, (frD), (frB), (frc), (opcd == 63, xo4 == 26), "Floating Reciprocal Square Root Estimate") INS(fsub, (frD), (frA, frB), (frc), (opcd == 63, xo4 == 20), "Floating Sub") INS(fsubs, (frD), (frA, frB), (frc), (opcd == 59, xo4 == 20), "Floating Sub Single") INS(fsel, (frD), (frA, frB, frC), (frc), (opcd == 63, xo4 == 23), "Floating Select") // Floating-Point Multiply-Add INS(fmadd, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 29), "Floating Multiply-Add") INS(fmadds, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 29), "Floating Multiply-Add Single") INS(fmsub, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 28), "Floating Multiply-Sub") INS(fmsubs, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 28), "Floating Multiply-Sub Single") INS(fnmadd, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 31), "Floating Negative Multiply-Add") INS(fnmadds, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 31), "Floating Negative Multiply-Add Single") INS(fnmsub, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 30), "Floating Negative Multiply-Sub") INS(fnmsubs, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 30), "Floating Negative Multiply-Sub Single") // Floating-Point Rounding and Conversion INS(fctiw, (frD), (frB), (rc), (opcd == 63, xo1 == 14), "Floating Convert to Integer Word") INS(fctiwz, (frD), (frB), (rc), (opcd == 63, xo1 == 15), "Floating Convert to Integer Word with Round toward Zero") INS(frsp, (frD), (frB), (rc), (opcd == 63, xo1 == 12), "Floating Round to Single") // Floating-Point Compare INS(fcmpo, (crfD), (frA, frB), (), (opcd == 63, xo1 == 32, !_9_10, !_31), "Floating Compare Ordered") INS(fcmpu, (crfD), (frA, frB), (), (opcd == 63, xo1 == 0, !_9_10, !_31), "Floating Compare Unordered") // Floating-Point Status and Control Register INS(mcrfs, (crfD), (crfS), (), (opcd == 63, xo1 == 64, !_9_10, !_14_15, !_16_20, !_31), "") INS(mffs, (frD), (), (rc), (opcd == 63, xo1 == 583, !_11_15, !_16_20), "") INS(mtfsb0, (), (crfD), (rc), (opcd == 63, xo1 == 70, !_11_15, !_16_20), "") INS(mtfsb1, (), (crfD), (rc), (opcd == 63, xo1 == 38, !_11_15, !_16_20), "") INS(mtfsf, (), (fm, frB), (rc), (opcd == 63, xo1 == 711, !_6, !_15), "") INS(mtfsfi, (crfD), (), (rc, imm), (opcd == 63, xo1 == 134, !_9_10, !_11_15, !_20), "") // Integer Load INS(lbz, (rD), (rA, d), (), (opcd == 34), "Load Byte and Zero") INS(lbzu, (rD, rA), (rA, d), (), (opcd == 35), "Load Byte and Zero with Update") INS(lbzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 87, !_31), "Load Byte and Zero Indexed") INS(lbzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 119, !_31), "Load Byte and Zero with Update Indexed") INS(lha, (rD), (rA, d), (), (opcd == 42), "Load Half Word Algebraic") INS(lhau, (rD, rA), (rA, d), (), (opcd == 43), "Load Half Word Algebraic with Update") INS(lhax, (rD), (rA, rB), (), (opcd == 31, xo1 == 343, !_31), "Load Half Word Algebraic Indexed") INS(lhaux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 375, !_31), "Load Half Word Algebraic with Update Indexed") INS(lhz, (rD), (rA, d), (), (opcd == 40), "Load Half Word and Zero") INS(lhzu, (rD, rA), (rA, d), (), (opcd == 41), "Load Half Word and Zero with Update") INS(lhzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 279, !_31), "Load Half Word and Zero Indexed") INS(lhzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 311, !_31), "Load Half Word and Zero with Update Indexed") INS(lwz, (rD), (rA, d), (), (opcd == 32), "Load Word and Zero") INS(lwzu, (rD, rA), (rA, d), (), (opcd == 33), "Load Word and Zero with Update") INS(lwzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 23, !_31), "Load Word and Zero Indexed") INS(lwzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 55, !_31), "Load Word and Zero with Update Indexed") // Integer Store INS(stb, (), (rS, rA, d), (), (opcd == 38), "Store Byte") INS(stbu, (rA), (rS, rA, d), (), (opcd == 39), "Store Byte with Update") INS(stbx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 215, !_31), "Store Byte Indexed") INS(stbux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 247, !_31), "Store Byte with Update Indexed") INS(sth, (), (rS, rA, d), (), (opcd == 44), "Store Half Word") INS(sthu, (rA), (rS, rA, d), (), (opcd == 45), "Store Half Word with Update") INS(sthx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 407, !_31), "Store Half Word Indexed") INS(sthux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 439, !_31), "Store Half Word with Update Indexed") INS(stw, (), (rS, rA, d), (), (opcd == 36), "Store Word") INS(stwu, (rA), (rS, rA, d), (), (opcd == 37), "Store Word with Update") INS(stwx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 151, !_31), "Store Word Indexed") INS(stwux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 183, !_31), "Store Word with Update Indexed") // Integer Load and Store with Byte Reverse INS(lhbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 790, !_31), "Load Half Word Byte-Reverse Indexed") INS(lwbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 534, !_31), "Load Word Byte-Reverse Indexed") INS(sthbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 918, !_31), "Store Half Word Byte-Reverse Indexed") INS(stwbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 662, !_31), "Store Word Byte-Reverse Indexed") // Integer Load and Store Multiple INS(lmw, (rD), (rA, d), (), (opcd == 46), "Load Multiple Words") INS(stmw, (), (rS, rA, d), (), (opcd == 47), "Store Multiple Words") // Integer Load and Store String INS(lswi, (rD), (rA, nb), (), (opcd == 31, xo1 == 597, !_31), "Load String Word Immediate") INS(lswx, (rD), (rA, rB), (), (opcd == 31, xo1 == 533, !_31), "Load String Word Indexed") INS(stswi, (), (rS, rA, nb), (), (opcd == 31, xo1 == 725, !_31), "Store String Word Immediate") INS(stswx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 661, !_31), "Store String Word Indexed") // Memory Synchronisation INS(eieio, (), (), (), (opcd == 31, xo1 == 854, !_6_10, !_11_15, !_16_20, !_31), "Enforce In-Order Execution of I/O") INS(isync, (), (), (), (opcd == 19, xo1 == 150, !_6_10, !_11_15, !_16_20, !_31), "Instruction Synchronise") INS(lwarx, (rD, RSRV), (rA, rB), (), (opcd == 31, xo1 == 20, !_31), "Load Word and Reserve Indexed") INS(stwcx, (RSRV), (rS, rA, rB), (), (opcd == 31, xo1 == 150, _31 == 1), "Store Word Conditional Indexed") INS(sync, (), (), (l), (opcd == 31, xo1 == 598, !_6_9, !_11_15, !_16_20, !_31), "Synchronise") // Floating-Point Load INS(lfd, (frD), (rA, d), (), (opcd == 50), "Load Floating-Point Double") INS(lfdu, (frD, rA), (rA, d), (), (opcd == 51), "Load Floating-Point Double with Update") INS(lfdx, (frD), (rA, rB), (), (opcd == 31, xo1 == 599, !_31), "Load Floating-Point Double Indexed") INS(lfdux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 631, !_31), "Load Floating-Point Double with Update Indexed") INS(lfs, (frD), (rA, d), (), (opcd == 48), "Load Floating-Point Single") INS(lfsu, (frD, rA), (rA, d), (), (opcd == 49), "Load Floating-Point Single with Update") INS(lfsx, (frD), (rA, rB), (), (opcd == 31, xo1 == 535, !_31), "Load Floating-Point Single Indexed") INS(lfsux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 567, !_31), "Load Floating-Point Single with Update Indexed") // Floating-Point Store INS(stfd, (), (frS, rA, d), (), (opcd == 54), "Store Floating-Point Double") INS(stfdu, (rA), (frS, rA, d), (), (opcd == 55), "Store Floating-Point Double with Update") INS(stfdx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 727, !_31), "Store Floating-Point Double Indexed") INS(stfdux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 759, !_31), "Store Floating-Point Double with Update Indexed") INS(stfiwx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 983, !_31), "Store Floating-Point as Integer Word Indexed") INS(stfs, (), (frS, rA, d), (), (opcd == 52), "Store Floating-Point Single") INS(stfsu, (rA), (frS, rA, d), (), (opcd == 53), "Store Floating-Point Single with Update") INS(stfsx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 663, !_31), "Store Floating-Point Single Indexed") INS(stfsux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 695, !_31), "Store Floating-Point Single with Update Indexed") // Floating-Point Move INS(fabs, (frD), (frB), (rc), (opcd == 63, xo1 == 264, !_11_15), "Floating Absolute Value") INS(fmr, (frD), (frB), (rc), (opcd == 63, xo1 == 72, !_11_15), "Floating Move Register") INS(fnabs, (frD), (frB), (rc), (opcd == 63, xo1 == 136, !_11_15), "Floating Negative Absolute Value") INS(fneg, (frD), (frB), (rc), (opcd == 63, xo1 == 40, !_11_15), "Floating Negate") // Branch INS(b, (), (li), (aa, lk), (opcd == 18), "Branch") INS(bc, (bo), (bi, bd), (aa, lk), (opcd == 16), "Branch Conditional") INS(bcctr, (bo), (bi, CTR), (lk), (opcd == 19, xo1 == 528, !_16_20), "Branch Conditional to CTR") INS(bclr, (bo), (bi, LR), (lk), (opcd == 19, xo1 == 16, !_16_20), "Branch Conditional to LR") // Condition Register Logical INS(crand, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 257, !_31), "Condition Register AND") INS(crandc, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 129, !_31), "Condition Register AND with Complement") INS(creqv, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 289, !_31), "Condition Register Equivalent") INS(crnand, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 225, !_31), "Condition Register NAND") INS(crnor, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 33, !_31), "Condition Register NOR") INS(cror, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 449, !_31), "Condition Register OR") INS(crorc, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 417, !_31), "Condition Register OR with Complement") INS(crxor, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 193, !_31), "Condition Register XOR") INS(mcrf, (crfD), (crfS), (), (opcd == 19, xo1 == 0, !_9_10, !_14_15, !_16_20, !_31), "Move Condition Register Field") // System Linkage INS(rfi, (), (), (), (opcd == 19, xo1 == 50, !_6_10, !_11_15, !_16_20, !_31), "") INS(kc, (), (kcn), (), (opcd == 17, _31 == 1), "krncall") // Must come before sc for proper table setup INS(sc, (), (), (), (opcd == 17, !_6_10, !_11_15, !_16_29, _30 == 1, !_31), "Syscall") // Trap INS(tw, (), (to, rA, rB), (), (opcd == 31, xo1 == 4, !_31), "") INS(twi, (), (to, rA, simm), (), (opcd == 3), "") // Processor Control INS(mcrxr, (crfD), (XERO), (), (opcd == 31, xo1 == 512, !_9_10, !_11_15, !_16_20, !_31), "Move to Condition Register from XERO") // mfcr requires bit 11 to be 0 (if 1, it's the mfocrf instruction), but the // Espresso ignores bit 11 and treats mfocrf as mfcr. INS(mfcr, (rD), (), (), (opcd == 31, xo1 == 19, !_20, !_31), "Move from Condition Register") INS(mfmsr, (rD), (), (), (opcd == 31, xo1 == 83, !_11_15, !_16_20, !_31), "Move from Machine State Register") INS(mfspr, (rD), (spr), (), (opcd == 31, xo1 == 339, !_31), "Move from Special Purpose Register") INS(mftb, (rD), (tbr), (), (opcd == 31, xo1 == 371, !_31), "Move from Time Base Register") // mtcrf requires bit 11 to be 0 (if 1, it's the mtocrf instruction), but the // Espresso ignores bit 11 and treats mtocrf as mtcrf. INS(mtcrf, (crm), (rS), (), (opcd == 31, xo1 == 144, !_20, !_31), "Move to Condition Register Fields") INS(mtmsr, (), (rS), (), (opcd == 31, xo1 == 146, !_11_15, !_16_20, !_31), "Move to Machine State Register") INS(mtspr, (spr), (rS), (), (opcd == 31, xo1 == 467, !_31), "Move to Special Purpose Register") // Cache Management INS(dcbf, (), (rA, rB), (), (opcd == 31, xo1 == 86, !_6_10, !_31), "") INS(dcbi, (), (rA, rB), (), (opcd == 31, xo1 == 470, !_6_10, !_31), "") INS(dcbst, (), (rA, rB), (), (opcd == 31, xo1 == 54, !_6_10, !_31), "") INS(dcbt, (), (rA, rB), (), (opcd == 31, xo1 == 278, !_6_10, !_31), "") INS(dcbtst, (), (rA, rB), (), (opcd == 31, xo1 == 246, !_6_10, !_31), "") INS(dcbz, (), (rA, rB), (), (opcd == 31, xo1 == 1014, !_6_10, !_31), "") INS(icbi, (), (rA, rB), (), (opcd == 31, xo1 == 982, !_6_10, !_31), "") INS(dcbz_l, (), (rA, rB), (), (opcd == 4, xo1 == 1014, !_6_10, !_31), "") // Segment Register Manipulation INS(mfsr, (rD), (sr), (), (opcd == 31, xo1 == 595, !_11, !_16_20, !_31), "Move from Segment Register") INS(mfsrin, (rD), (rB), (), (opcd == 31, xo1 == 659, !_11_15, !_31), "Move from Segment Register Indirect") INS(mtsr, (), (rD, sr), (), (opcd == 31, xo1 == 210, !_11, !_16_20, !_31), "Move to Segment Register") INS(mtsrin, (), (rD, rB), (), (opcd == 31, xo1 == 242, !_11_15, !_31), "Move to Segment Register Indirect") // Lookaside Buffer Management INS(tlbie, (), (rB), (), (opcd == 31, xo1 == 306, !_6_10, !_11_15, !_31), "") INS(tlbsync, (), (), (), (opcd == 31, xo1 == 566, !_6_10, !_11_15, !_16_20, !_31), "") // External Control INS(eciwx, (rD), (rA, rB), (), (opcd == 31, xo1 == 310, !_31), "") INS(ecowx, (rD), (rA, rB), (), (opcd == 31, xo1 == 438, !_31), "") // Paired-Single Load and Store INS(psq_l, (frD), (rA, qd), (w, i), (opcd == 56), "Paired Single Load") INS(psq_lu, (frD), (rA, qd), (w, i), (opcd == 57), "Paired Single Load with Update") INS(psq_lx, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 6, !_31), "Paired Single Load Indexed") INS(psq_lux, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 38, !_31), "Paired Single Load with Update Indexed") INS(psq_st, (frD), (rA, qd), (w, i), (opcd == 60), "Paired Single Store") INS(psq_stu, (frD), (rA, qd), (w, i), (opcd == 61), "Paired Single Store with Update") INS(psq_stx, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 7, !_31), "Paired Single Store Indexed") INS(psq_stux, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 39, !_31), "Paired Single Store with Update Indexed") // Paired-Single Floating Point Arithmetic INS(ps_add, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 21), "Paired Single Add") INS(ps_div, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 18), "Paired Single Divide") INS(ps_mul, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 25), "Paired Single Multiply") INS(ps_sub, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 20), "Paired Single Subtract") INS(ps_abs, (frD), (frB), (rc), (opcd == 4, xo1 == 264, !_11_15), "Paired Single Absolute") INS(ps_nabs, (frD), (frB), (rc), (opcd == 4, xo1 == 136, !_11_15), "Paired Single Negate Absolute") INS(ps_neg, (frD), (frB), (rc), (opcd == 4, xo1 == 40, !_11_15), "Paired Single Negate") INS(ps_sel, (frD), (frA, frC, frB), (rc), (opcd == 4, xo4 == 23), "Paired Single Select") INS(ps_res, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 24), "Paired Single Reciprocal") INS(ps_rsqrte, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 26), "Paired Single Reciprocal Square Root Estimate") INS(ps_msub, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 28), "Paired Single Multiply and Subtract") INS(ps_madd, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 29), "Paired Single Multiply and Add") INS(ps_nmsub, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 30), "Paired Single Negate Multiply and Subtract") INS(ps_nmadd, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 31), "Paired Single Negate Multiply and Add") INS(ps_mr, (frD), (frB), (rc), (opcd == 4, xo1 == 72, !_11_15), "Paired Single Move Register") INS(ps_sum0, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 10), "Paired Single Sum High") INS(ps_sum1, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 11), "Paired Single Sum Low") INS(ps_muls0, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 12), "Paired Single Multiply Scalar High") INS(ps_muls1, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 13), "Paired Single Multiply Scalar Low") INS(ps_madds0, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 14), "Paired Single Multiply and Add Scalar High") INS(ps_madds1, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 15), "Paired Single Multiply and Add Scalar Low") INS(ps_cmpu0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 0, !_9_10, !_31), "Paired Single Compare Unordered High") INS(ps_cmpo0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 32, !_9_10, !_31), "Paired Single Compare Ordered High") INS(ps_cmpu1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 64, !_9_10, !_31), "Paired Single Compare Unordered Low") INS(ps_cmpo1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 96, !_9_10, !_31), "Paired Single Compare Ordered Low") INS(ps_merge00, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 528), "Paired Single Merge High") INS(ps_merge01, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 560), "Paired Single Merge Direct") INS(ps_merge10, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 592), "Paired Single Merge Swapped") INS(ps_merge11, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 624), "Paired Single Merge Low") ================================================ FILE: src/libcpu/espresso/espresso_instruction_fields.inl ================================================ FLD(aa, 30, 30) FLD(bd, 16, 29) FLD(bi, 11, 15) FLD(bo, 6, 10) FLD(crbA, 11, 15) FLD(crbB, 16, 20) FLD(crbD, 6, 10) FLD(crfD, 6, 8) FLD(crfS, 11, 13) FLD(crm, 12, 19) FLD(d, 16, 31) FLD(fm, 7, 14) FLD(frA, 11, 15) FLD(frB, 16, 20) FLD(frC, 21, 25) FLD(frD, 6, 10) FLD(frS, 6, 10) FLD(frc, 31, 31) // Special version of rc for float operations FLD(i, 17, 19) FLD(imm, 16, 19) FLD(kcn, 6, 30) FLD(l, 10, 10) FLD(li, 6, 29) FLD(lk, 31, 31) FLD(mb, 21, 25) FLD(me, 26, 30) FLD(nb, 16, 20) FLD(oe, 21, 21) FLD(opcd, 0, 5) FLD(qd, 20, 31) FLD(qi, 22, 24) FLD(qw, 21, 21) FLD(rA, 11, 15) FLD(rB, 16, 20) FLD(rc, 31, 31) FLD(rD, 6, 10) FLD(rS, 6, 10) FLD(sh, 16, 20) FLD(simm, 16, 31) FLD(sr, 12, 15) FLD(spr, 11, 20) FLD(to, 6, 10) FLD(tbr, 11, 20) FLD(uimm, 16, 31) FLD(w, 16, 16) FLD(xo1, 21, 30) FLD(xo2, 22, 30) FLD(xo3, 25, 30) FLD(xo4, 26, 30) FLD(_6, 6, 6) FLD(_6_9, 6, 9) FLD(_6_10, 6, 10) FLD(_9, 9, 9) FLD(_9_10, 9, 10) FLD(_11, 11, 11) FLD(_11_15, 11, 15) FLD(_14_15, 14, 15) FLD(_15, 15, 15) FLD(_16_20, 16, 20) FLD(_16_29, 16, 29) FLD(_20, 20, 20) FLD(_20_26, 20, 26) FLD(_21, 21, 21) FLD(_21_25, 21, 25) FLD(_30, 30, 30) FLD(_31, 31, 31) // Marker Fields MRKR(PS) MRKR(XERO) MRKR(XERSO) MRKR(XERC) MRKR(CR0) MRKR(CR1) MRKR(FCRISI) MRKR(FCRIDI) MRKR(FCRSNAN) MRKR(FCRZDZ) MRKR(FPRF) MRKR(AOE) MRKR(ARC) MRKR(LR) MRKR(CTR) MRKR(FPSCR) // TO BE REMOVED MRKR(RSRV) ================================================ FILE: src/libcpu/espresso/espresso_instructionid.h ================================================ #pragma once namespace espresso { #define INS(x, ...) x, enum class InstructionID { # include "espresso_instruction_definitions.inl" Invalid, InstructionCount }; #undef INS } // namespace espresso ================================================ FILE: src/libcpu/espresso/espresso_instructionset.cpp ================================================ #include "espresso_instructionset.h" #include "espresso_spr.h" #include #include #include namespace espresso { struct TableEntry { struct FieldMap { InstructionField field; std::vector children; }; void addInstruction(InstructionField field, uint32_t value, InstructionInfo *instrInfo) { auto fieldMap = getFieldMap(field); decaf_check(fieldMap); decaf_check(value < fieldMap->children.size()); fieldMap->children[value].instr = instrInfo; } void addTable(InstructionField field) { if (!getFieldMap(field)) { auto fieldMap = FieldMap {}; auto size = 1 << getInstructionFieldWidth(field); fieldMap.field = field; fieldMap.children.resize(size); fieldMaps.emplace_back(fieldMap); } } TableEntry * getEntry(InstructionField field, uint32_t value) { auto fieldMap = getFieldMap(field); decaf_check(fieldMap); decaf_check(value < fieldMap->children.size()); return &fieldMap->children[value]; } FieldMap * getFieldMap(InstructionField field) { for (auto &fieldMap : fieldMaps) { if (fieldMap.field == field) { return &fieldMap; } } return nullptr; } InstructionInfo *instr = nullptr; std::vector fieldMaps; }; static std::vector sInstructionInfo; static std::vector sAliasData; static TableEntry sInstructionTable; #define FLD(x, y, z, ...) {y, z}, #define MRKR(x, ...) {-1, -1}, static std::pair sFieldBits[] = { { -1, -1 }, #include "espresso_instruction_fields.inl" }; #undef FLD #undef MRKR #define FLD(x, ...) #x, #define MRKR(x, ...) #x, static const char * sFieldNames[] = { #include "espresso_instruction_fields.inl" }; #undef FLD #undef MRKR // Returns true if InstructionField is a marker only field bool isInstructionFieldMarker(InstructionField field) { return sFieldBits[static_cast(field)].first == -1; } // Returns true if instruction is a branch instruction bool isBranchInstruction(InstructionID id) { return id == InstructionID::b || id == InstructionID::bc || id == InstructionID::bcctr || id == InstructionID::bclr; } // Get name of InstructionField const char * getInstructionFieldName(InstructionField field) { return sFieldNames[static_cast(field)]; } // First bit of instruction field uint32_t getInstructionFieldStart(InstructionField field) { return 31 - sFieldBits[static_cast(field)].second; } // Last bit of instruction field uint32_t getInstructionFieldEnd(InstructionField field) { return 31 - sFieldBits[static_cast(field)].first; } // Width of instruction field in bits uint32_t getInstructionFieldWidth(InstructionField field) { auto end = getInstructionFieldEnd(field); auto start = getInstructionFieldStart(field); return end - start + 1; } // Absolute bitmask of instruction field uint32_t getInstructionFieldBitmask(InstructionField field) { auto end = getInstructionFieldEnd(field); auto start = getInstructionFieldStart(field); return make_bitmask(start, end); } uint32_t getInstructionFieldValue(const InstructionField& field, Instruction instr) { if (field == InstructionField::spr) { return static_cast(decodeSPR(instr)); } else { auto mask = getInstructionFieldBitmask(field); auto start = getInstructionFieldStart(field); return (instr & mask) >> start; } } SPR decodeSPR(Instruction instr) { return static_cast(((instr.spr << 5) & 0x3E0) | ((instr.spr >> 5) & 0x1F)); } void encodeSPR(Instruction &instr, SPR spr) { auto sprInt = (uint32_t)spr; instr.spr = ((sprInt << 5) & 0x3E0) | ((sprInt >> 5) & 0x1F); } // Decode Instruction to InstructionInfo InstructionInfo * decodeInstruction(Instruction instr) { auto table = &sInstructionTable; while (table) { for (auto &fieldMap : table->fieldMaps) { auto value = getInstructionFieldValue(fieldMap.field, instr); table = &fieldMap.children[value]; if (table->instr || table->fieldMaps.size()) { break; } } if (table->fieldMaps.size() == 0) { return table->instr; } } return nullptr; } // Encode specified instruction Instruction encodeInstruction(InstructionID id) { auto &data = sInstructionInfo[static_cast(id)]; auto instr = 0u; for (auto &op : data.opcode) { auto field = op.field; auto value = op.value; auto start = getInstructionFieldStart(field); instr |= value << start; } return instr; } // Find InstructionInfo for InstructionID InstructionInfo * findInstructionInfo(InstructionID instrId) { return &sInstructionInfo[static_cast(instrId)]; } // Find any alias which matches instruction InstructionAlias * findInstructionAlias(InstructionInfo *info, Instruction instr) { for (auto &alias : sAliasData) { if (alias.id != info->id) { // Not an alias for this field continue; } auto opMatch = std::all_of(alias.opcode.begin(), alias.opcode.end(), [instr](const auto &op) { auto x = getInstructionFieldValue(op.field, instr); auto y = op.value; if (op.field2 != InstructionField::Invalid) { y = getInstructionFieldValue(op.field2, instr); } return x == y; }); if (opMatch) { return &alias; } } return nullptr; } // Check if instruction is a certain instruction bool isA(InstructionID id, Instruction instr) { const auto &data = sInstructionInfo[static_cast(id)]; return std::all_of(data.opcode.begin(), data.opcode.end(), [instr](const auto &op) { auto field = op.field; auto value = op.value; auto start = getInstructionFieldStart(field); auto mask = getInstructionFieldBitmask(field); return ((instr.value & mask) >> start) == value; }); } // Initialise instructionTable static void initialiseInstructionTable() { for (auto &instr : sInstructionInfo) { TableEntry *table = &sInstructionTable; // Resolve opcodes for (auto i = 0u; i < instr.opcode.size() - 1; ++i) { auto field = instr.opcode[i].field; auto value = instr.opcode[i].value; table->addTable(field); table = table->getEntry(field, value); } // Add the actual instruction entry auto field = instr.opcode.back().field; auto value = instr.opcode.back().value; table->addTable(field); table->addInstruction(field, value, &instr); } } static std::string cleanInsName(const std::string& name) { if (name[name.size() - 1] == '_') { return name.substr(0, name.size() - 1); } return name; } /* * FieldIndex is used to generate the decoding rules. * * For example the add instruction is defined as: * INS(add, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 266), "Add") * * Here both opcd and xo2 will be defined as a FieldIndex, and then during * initialiseInstructionSet they will use FieldIndex::operator== below to generate * an InstructionOpcode which we then use to setup the instruction table decoding. */ struct FieldIndex { FieldIndex(InstructionField _id) : id(_id) { } operator InstructionField() const { return id; } InstructionOpcode operator==(const int &other) const { return InstructionOpcode(id, other); } InstructionOpcode operator==(const FieldIndex &other) const { return InstructionOpcode(id, other.id); } InstructionOpcode operator!() const { return InstructionOpcode(id, 0); } InstructionField id; }; // Create FieldIndex to match the field names we use inside the spec files #define FLD(x, ...) static const FieldIndex x(InstructionField::x); #define MRKR(x, ...) static const FieldIndex x(InstructionField::x); #include "espresso_instruction_fields.inl" #undef FLD #undef MRKR #define PRINTOPS(...) __VA_ARGS__ // Used to populate sInstructionInfo #define INS(name, write, read, flags, opcodes, fullname) \ static const InstructionInfo \ info_ ## name = InstructionInfo { \ InstructionID::name, cleanInsName(#name), fullname, \ { PRINTOPS opcodes }, { PRINTOPS read }, \ { PRINTOPS write }, { PRINTOPS flags } \ }; // Used to populate sInstructionAlias #define INSA(name, op, opcodes) \ static const InstructionAlias \ alias_ ## name = InstructionAlias { \ #name, InstructionID::op, \ { PRINTOPS opcodes } \ }; # include "espresso_instruction_definitions.inl" # include "espresso_instruction_aliases.inl" #undef INS #undef INSA #define INS(name, ...) sInstructionInfo.emplace_back(info_ ## name); #define INSA(name, ...) sAliasData.emplace_back(alias_ ## name); void initialiseInstructionSet() { // Populate sInstructionInfo # include "espresso_instruction_definitions.inl" // Populate sInstructionAlias # include "espresso_instruction_aliases.inl" // Create instruction table initialiseInstructionTable(); }; #undef INS #undef INSA } // namespace espresso ================================================ FILE: src/libcpu/espresso/espresso_instructionset.h ================================================ #pragma once #include #include #include #include "espresso_instruction.h" #include "espresso_instructionid.h" namespace espresso { #define FLD(x, ...) x, #define MRKR(x, ...) x, enum class InstructionField : uint32_t { Invalid, # include "espresso_instruction_fields.inl" FieldCount, }; #undef FLD #undef MRKR struct InstructionOpcode { InstructionOpcode() { } InstructionOpcode(InstructionField field_, uint32_t value_) : field(field_), value(value_) { } InstructionOpcode(InstructionField field_, InstructionField field2_) : field(field_), field2(field2_) { } InstructionField field = InstructionField::Invalid; InstructionField field2 = InstructionField::Invalid; uint32_t value = 0; }; struct InstructionInfo { InstructionID id; std::string name; std::string fullname; std::vector opcode; std::vector read; std::vector write; std::vector flags; }; struct InstructionAlias { std::string name; InstructionID id; std::vector opcode; }; void initialiseInstructionSet(); InstructionInfo * decodeInstruction(Instruction instr); Instruction encodeInstruction(InstructionID id); InstructionInfo * findInstructionInfo(InstructionID instrId); InstructionAlias * findInstructionAlias(InstructionInfo *info, Instruction instr); bool isA(InstructionID id, Instruction instr); template bool isA(Instruction instr) { return isA(id, instr); } bool isInstructionFieldMarker(InstructionField field); bool isBranchInstruction(InstructionID id); const char * getInstructionFieldName(InstructionField field); uint32_t getInstructionFieldStart(InstructionField field); uint32_t getInstructionFieldEnd(InstructionField field); uint32_t getInstructionFieldWidth(InstructionField field); uint32_t getInstructionFieldBitmask(InstructionField field); } // namespace espresso ================================================ FILE: src/libcpu/espresso/espresso_registerformats.h ================================================ #pragma once #include #include namespace espresso { // General Purpose Integer Registers using Register = uint32_t; /** * Condition Register */ union ConditionRegister { uint32_t value; struct { uint32_t cr7 : 4; uint32_t cr6 : 4; uint32_t cr5 : 4; uint32_t cr4 : 4; uint32_t cr3 : 4; uint32_t cr2 : 4; uint32_t cr1 : 4; uint32_t cr0 : 4; }; }; /** * Condition Register Flags * * Value of each cr field cr0...cr7 */ FLAGS_BEG(ConditionRegisterFlag, uint32_t) FLAGS_VALUE(SummaryOverflow, 1 << 0) FLAGS_VALUE(Zero, 1 << 1) FLAGS_VALUE(Positive, 1 << 2) FLAGS_VALUE(Negative, 1 << 3) FLAGS_VALUE(Unordered, SummaryOverflow) FLAGS_VALUE(Equal, Zero) FLAGS_VALUE(GreaterThan, Positive) FLAGS_VALUE(LessThan, Negative) FLAGS_VALUE(FloatingPointOverflowException, SummaryOverflow) FLAGS_VALUE(FloatingPointInvalidException, Zero) FLAGS_VALUE(FloatingPointExceptionEnabled, Positive) FLAGS_VALUE(FloatingPointException, Negative) FLAGS_END(ConditionRegisterFlag) /** * Floating-Point Registers */ union alignas(16) FloatingPointRegister { struct { double value; }; struct { double paired0; // Retains precision of a loaded double value. double paired1; }; struct { uint64_t idw; uint64_t idw_paired1; }; struct { uint32_t iw1; uint32_t iw0; }; }; /** * Floating-Point Status and Control Register */ union FloatingPointStatusAndControlRegister { uint32_t value; struct { uint32_t : 12; uint32_t fpcc : 4; uint32_t : 12; uint32_t cr1 : 4; }; struct { uint32_t rn : 2; // FP Rounding Control uint32_t ni : 1; // FP non-IEEE mode uint32_t xe : 1; // FP Inexact Exception Enable uint32_t ze : 1; // IEEE FP Zero Divide Exception Enable uint32_t ue : 1; // IEEE FP Underflow Exception Enable uint32_t oe : 1; // IEEE FP Overflow Exception Enable uint32_t ve : 1; // FP Invalid Operation Exception Enable uint32_t vxcvi : 1; // FP Invalid Operation Exception for Invalid Integer Convert uint32_t vxsqrt : 1; // FP Invalid Operation Exception for Invalid Square Root uint32_t vxsoft : 1; // FP Invalid Operation Exception for Software Request uint32_t : 1; uint32_t fprf : 5; // FP Result Flags uint32_t fi : 1; // FP Fraction Inexact uint32_t fr : 1; // FP Fraction Rounded uint32_t vxvc : 1; // FP Invalid Operation Exception for Invalid Compare uint32_t vximz : 1; // FP Invalid Operation Exception for Inf*0 uint32_t vxzdz : 1; // FP Invalid Operation Exception for 0/0 uint32_t vxidi : 1; // FP Invalid Operation Exception for Inf/Inf uint32_t vxisi : 1; // FP Invalid Operation Exception for Inf-Inf uint32_t vxsnan : 1; // FP Invalid Operation Exception for SNaN uint32_t xx : 1; // FP Inexact Exception uint32_t zx : 1; // FP Zero Divide Exception uint32_t ux : 1; // FP Underflow Exception uint32_t ox : 1; // FP Overflow Exception uint32_t vx : 1; // FP Invalid Operation Exception Summary uint32_t fex : 1; // FP Enabled Exception Summary uint32_t fx : 1; // FP Exception Summary }; }; /** * Floating-Point Status and Control Register Flags */ FLAGS_BEG(FpscrFlags, uint32_t) FLAGS_VALUE(VXCVI, 1u << 8) FLAGS_VALUE(VXSQRT, 1u << 9) FLAGS_VALUE(VXSOFT, 1u << 10) FLAGS_VALUE(VXVC, 1u << 19) FLAGS_VALUE(VXIMZ, 1u << 20) FLAGS_VALUE(VXZDZ, 1u << 21) FLAGS_VALUE(VXIDI, 1u << 22) FLAGS_VALUE(VXISI, 1u << 23) FLAGS_VALUE(VXSNAN, 1u << 24) FLAGS_VALUE(XX, 1u << 25) FLAGS_VALUE(ZX, 1u << 26) FLAGS_VALUE(UX, 1u << 27) FLAGS_VALUE(OX, 1u << 28) FLAGS_VALUE(VX, 1u << 29) FLAGS_VALUE(FEX, 1u << 30) FLAGS_VALUE(FX, 1u << 31) FLAGS_VALUE(AllVX, VXSNAN | VXISI | VXIDI | VXZDZ | VXIMZ | VXVC | VXSOFT | VXSQRT | VXCVI) FLAGS_VALUE(AllExceptions, AllVX | OX | UX | ZX | XX) ENUM_END(FpscrFlags) /** * Floating-Point Result Flags * * Value of fpscr.fprf */ FLAGS_BEG(FloatingPointResultFlags, uint32_t) FLAGS_VALUE(NaN, 1 << 0) FLAGS_VALUE(Zero, 1 << 1) FLAGS_VALUE(Positive, 1 << 2) FLAGS_VALUE(Negative, 1 << 3) FLAGS_VALUE(ClassDescriptor, 1 << 4) FLAGS_VALUE(Unordered, NaN) FLAGS_VALUE(Equal, Zero) FLAGS_VALUE(GreaterThan, Positive) FLAGS_VALUE(LessThan, Negative) FLAGS_END(FloatingPointResultFlags) /** * Floating-Point Rounding Mode * * Value of fpscr.rn */ ENUM_BEG(FloatingPointRoundMode, uint32_t) ENUM_VALUE(Nearest, 0) ENUM_VALUE(Zero, 1) ENUM_VALUE(Positive, 2) ENUM_VALUE(Negative, 3) ENUM_END(FloatingPointRoundMode) /** * Graphics Quantization Registers */ union GraphicsQuantisationRegister { uint32_t value; struct { uint32_t st_type : 3; uint32_t : 5; uint32_t st_scale : 6; uint32_t : 2; uint32_t ld_type : 3; uint32_t : 5; uint32_t ld_scale : 6; uint32_t : 2; }; }; /** * Graphics Quantization Registers Quantized Data Type * * Value of gqr.st_type / gqr.ld_type */ ENUM_BEG(QuantizedDataType, uint32_t) ENUM_VALUE(Floating, 0) ENUM_VALUE(Unsigned8, 4) ENUM_VALUE(Unsigned16, 5) ENUM_VALUE(Signed8, 6) ENUM_VALUE(Signed16, 7) ENUM_END(QuantizedDataType) /** * Machine State Register */ union MachineStateRegister { uint32_t value; struct { uint32_t le : 1; // Little-endian mode enabled uint32_t ri : 1; // Exception is recoverable uint32_t : 2; uint32_t dr : 1; // Data address translation enabled uint32_t ir : 1; // Instruction address translation enabled uint32_t ip : 1; // Exception prefix uint32_t : 1; uint32_t fe1 : 1; // Floating-point exception mode 1 uint32_t be : 1; // Branch trace enabled uint32_t se : 1; // Single-step trace enabled uint32_t fe0 : 1; // Floating-point exception mode 0 uint32_t me : 1; // Machine check enabled uint32_t fp : 1; // Floating-point available uint32_t pr : 1; // Privelege level (0 = supervisor, 1 = user) uint32_t ee : 1; // External interrupt enabled uint32_t ile : 1; // Exception little-endian mode uint32_t : 1; uint32_t pow : 1; // Power management enabled uint32_t : 13; }; }; /** * Processor Version Register */ union ProcessorVersionRegister { uint32_t value; struct { uint32_t revision : 16; uint32_t version : 16; }; }; /** * Fixed Point Exception Register */ union FixedPointExceptionRegister { uint32_t value; struct { // Byte count for lmwx, stmwx uint32_t byteCount : 7; uint32_t : 22; //! Carry uint32_t ca : 1; //! Overflow uint32_t ov : 1; //! Sticky overflow uint32_t so : 1; }; struct { uint32_t : 28; uint32_t crxr : 4; }; }; } // namespace espresso #include ================================================ FILE: src/libcpu/espresso/espresso_spr.h ================================================ #pragma once #include "espresso_instruction.h" namespace espresso { enum class SPR { XER = 0x1, LR = 0x8, CTR = 0x9, DSISR = 0x12, DAR = 0x13, DEC = 0x16, SDR1 = 0x19, SRR0 = 0x1A, SRR1 = 0x1B, UTBL = 0x10C, UTBU = 0x10D, SPRG0 = 0x110, SPRG1 = 0x111, SPRG2 = 0x112, SPRG3 = 0x113, EAR = 0x11A, TBL = 0x11C, TBU = 0x11D, PVR = 0x11F, IBAT0U = 0x210, IBAT0L = 0x211, IBAT1U = 0x212, IBAT1L = 0x213, IBAT2U = 0x214, IBAT2L = 0x215, IBAT3U = 0x216, IBAT3L = 0x217, DBAT0U = 0x218, DBAT0L = 0x219, DBAT1U = 0x21A, DBAT1L = 0x21B, DBAT2U = 0x21C, DBAT2L = 0x21D, DBAT3U = 0x21E, DBAT3L = 0x21F, IBAT4U = 0x230, IBAT4L = 0x231, IBAT5U = 0x232, IBAT5L = 0x233, IBAT6U = 0x234, IBAT6L = 0x235, IBAT7U = 0x236, IBAT7L = 0x237, DBAT4U = 0x238, DBAT4L = 0x239, DBAT5U = 0x23A, DBAT5L = 0x23B, DBAT6U = 0x23C, DBAT6L = 0x23D, DBAT7U = 0x23E, DBAT7L = 0x23F, UGQR0 = 0x380, UGQR1 = 0x381, UGQR2 = 0x382, UGQR3 = 0x383, UGQR4 = 0x384, UGQR5 = 0x385, UGQR6 = 0x386, UGQR7 = 0x387, UHID2 = 0x388, UWPAR = 0x389, UDMAU = 0x38A, UDMAL = 0x38B, GQR0 = 0x390, GQR1 = 0x391, GQR2 = 0x392, GQR3 = 0x393, GQR4 = 0x394, GQR5 = 0x395, GQR6 = 0x396, GQR7 = 0x397, HID2 = 0x398, WPAR = 0x399, DMA_U = 0x39A, DMA_L = 0x39B, UMMCR0 = 0x3A8, UPMC1 = 0x3A9, UPMC2 = 0x3AA, USIA = 0x3AB, UMMCR1 = 0x3AC, UPMC3 = 0x3AD, UPMC4 = 0x3AE, HID5 = 0x3B0, PCSR = 0x3B2, SCR = 0x3B3, CAR = 0x3B4, BCR = 0x3B5, WPSAR = 0x3B6, MMCR0 = 0x3B8, PMC1 = 0x3B9, PMC2 = 0x3BA, SIA = 0x3BB, MMCR1 = 0x3BC, PMC3 = 0x3BD, PMC4 = 0x3BE, DCATE = 0x3D0, DCATR = 0x3D1, DMATL0 = 0x3D8, DMATU0 = 0x3D9, DMATR0 = 0x3DA, DMATL1 = 0x3DB, DMATU1 = 0x3DC, DMATR1 = 0x3DD, UPIR = 0x3EF, HID0 = 0x3F0, HID1 = 0x3F1, IABR = 0x3F2, HID4 = 0x3F3, TDCL = 0x3F4, DABR = 0x3F5, L2CR = 0x3F9, TDCH = 0x3FA, ICTC = 0x3FB, THRM1 = 0x3FC, THRM2 = 0x3FD, THRM3 = 0x3FE, PIR = 0x3FF, }; SPR decodeSPR(Instruction instr); void encodeSPR(Instruction &instr, SPR spr); } // namespace espresso ================================================ FILE: src/libcpu/functionpointer.h ================================================ #pragma once #include "address.h" namespace cpu { template struct func_pointer_cast_impl; template class FunctionPointer; template class FunctionPointer { public: using function_type = FunctionType; FunctionPointer() = default; FunctionPointer(const FunctionPointer &other) = default; FunctionPointer(FunctionPointer &&other) = default; FunctionPointer &operator=(const FunctionPointer &) = default; FunctionPointer &operator=(FunctionPointer &&) = default; /** * Constructs a FunctionPointer from a nullptr */ FunctionPointer(std::nullptr_t) : mAddress(0) { } AddressType getAddress() const { return mAddress; } explicit operator bool() const { return static_cast(mAddress); } FunctionPointer & operator =(std::nullptr_t) { mAddress = AddressType { 0 }; return *this; } constexpr bool operator ==(std::nullptr_t) const { return !static_cast(mAddress); } constexpr bool operator == (const FunctionPointer &other) const { return mAddress == other.mAddress; } constexpr bool operator != (const FunctionPointer &other) const { return mAddress != other.mAddress; } protected: template friend struct func_pointer_cast_impl; AddressType mAddress; }; template using VirtualFunctionPointer = FunctionPointer; template using PhysicalFunctionPointer = FunctionPointer; template struct func_pointer_cast_impl { using FunctionPointerType = FunctionPointer; static constexpr AddressType cast(FunctionPointerType src) { return src.mAddress; } static constexpr FunctionPointerType cast(AddressType src) { FunctionPointerType dst; dst.mAddress = src; return dst; } }; template struct func_pointer_cast_impl> { using FunctionPointerType = FunctionPointer; static constexpr AddressType cast(FunctionPointerType src) { return src.mAddress; } static constexpr FunctionPointerType cast(AddressType src) { FunctionPointerType dst; dst.mAddress = src; return dst; } }; } // namespace cpu // Custom formatters for fmtlib namespace fmt { inline namespace v8 { template struct formatter; } // Custom formatter defined in cpu_formatters.h template struct formatter, Char, void>; } // namespace fmt ================================================ FILE: src/libcpu/jit_stats.h ================================================ #pragma once #include #include #include #include #include #ifdef PLATFORM_WINDOWS #define WIN32_LEAN_AND_MEAN #include #endif namespace cpu { namespace jit { #ifdef PLATFORM_WINDOWS struct CodeBlockUnwindInfo { static constexpr size_t MaxUnwindInfoSize = 4 + 2 * 30 + 8; std::array data; uint32_t size; RUNTIME_FUNCTION rtlFuncTable; }; #else struct CodeBlockUnwindInfo { }; #endif struct CodeBlockProfileData { std::atomic count; std::atomic time; }; struct CodeBlock { //! Guest address of PPC code. uint32_t address; //! Host address of compiled code. void *code; //! Size of compiled code. uint32_t codeSize; //! Profiling data. CodeBlockProfileData profileData; //! Code block unwind info, only used on Windows. CodeBlockUnwindInfo unwindInfo; }; using CodeBlockIndex = int32_t; static constexpr CodeBlockIndex CodeBlockIndexUncompiled = -1; static constexpr CodeBlockIndex CodeBlockIndexCompiling = -2; static constexpr CodeBlockIndex CodeBlockIndexError = -3; struct JitStats { uint64_t totalTimeInCodeBlocks = 0; uint64_t usedCodeCacheSize = 0; uint64_t usedDataCacheSize = 0; gsl::span compiledBlocks; }; bool sampleStats(JitStats &stats); void resetProfileStats(); void setProfilingMask(unsigned mask); unsigned getProfilingMask(); } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/mem.h ================================================ #pragma once #include #include #include "mmu.h" using ppcaddr_t = uint32_t; namespace mem { // Translate WiiU virtual address to host address template inline Type * translate(ppcaddr_t address) { if (!address) { return nullptr; } else { return reinterpret_cast(cpu::getBaseVirtualAddress() + address); } } // Translate host address to WiiU virtual address inline ppcaddr_t untranslate(const void *ptr) { if (!ptr) { return 0; } auto sptr = reinterpret_cast(ptr); auto sbase = cpu::getBaseVirtualAddress(); decaf_check(sptr >= sbase); decaf_check(sptr <= sbase + 0xFFFFFFFF); return static_cast(sptr - sbase); } // Read Type from virtual address with no endian byte_swap template inline Type readNoSwap(ppcaddr_t address) { return *reinterpret_cast(translate(address)); } // Read Type from virtual address template inline Type read(ppcaddr_t address) { return byte_swap(readNoSwap(address)); } // Write Type to virtual address with no endian byte_swap template inline void writeNoSwap(ppcaddr_t address, Type value) { *reinterpret_cast(translate(address)) = value; } // Write Type to virtual address template inline void write(ppcaddr_t address, Type value) { writeNoSwap(address, byte_swap(value)); } } // namespace mem ================================================ FILE: src/libcpu/memtrack.h ================================================ #pragma once #include "address.h" #include #include "mmu.h" namespace cpu { namespace internal { void initialiseMemtrack(); void registerTrackedRange(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size); void unregisterTrackedRange(VirtualAddress virtualAddress, uint32_t size); void clearTrackedRanges(); } // namespace internal struct MemtrackState { bool operator==(const MemtrackState& other) const { return state == other.state; } bool operator!=(const MemtrackState& other) const { return state != other.state; } uint64_t state; }; MemtrackState getMemoryState(PhysicalAddress physicalAddress, uint32_t size); } // namespace cpu ================================================ FILE: src/libcpu/mmu.h ================================================ #pragma once #include "address.h" #include namespace cpu { namespace internal { extern uintptr_t BaseVirtualAddress; extern uintptr_t BasePhysicalAddress; template inline Value *translate(VirtualAddress address) { return reinterpret_cast(BaseVirtualAddress + address.getAddress()); } template inline Value *translate(PhysicalAddress address) { return reinterpret_cast(BasePhysicalAddress + address.getAddress()); } } // namespace internal enum class MapPermission { ReadOnly, ReadWrite, }; enum class VirtualMemoryType { Invalid, MappedReadOnly, MappedReadWrite, Free, Allocated, }; enum class PhysicalMemoryType { Invalid, MEM0, MEM1, MEM2, SRAM0, SRAM1, UNKRAM, LockedCache, TilingAperture, }; constexpr auto PageSize = uint32_t { 128 * 1024 }; bool initialiseMemory(); bool allocateVirtualAddress(VirtualAddress virtualAddress, uint32_t size); bool freeVirtualAddress(VirtualAddress virtualAddress, uint32_t size); VirtualAddressRange findFreeVirtualAddress(uint32_t size, uint32_t align); VirtualAddressRange findFreeVirtualAddressInRange(VirtualAddressRange range, uint32_t size, uint32_t align); bool mapMemory(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size, MapPermission permission); bool unmapMemory(VirtualAddress virtualAddress, uint32_t size); bool resetVirtualMemory(); VirtualMemoryType queryVirtualAddress(VirtualAddress virtualAddress); bool isValidAddress(VirtualAddress address); bool virtualToPhysicalAddress(VirtualAddress virtualAddress, PhysicalAddress &out); template inline VirtualAddress translate(Type *pointer) { if (!pointer) { return VirtualAddress { 0u }; } else { auto addr = reinterpret_cast(pointer); decaf_check(addr >= internal::BaseVirtualAddress); decaf_check(addr <= internal::BaseVirtualAddress + 0x100000000ull); return VirtualAddress { static_cast(addr - internal::BaseVirtualAddress) }; } } template inline PhysicalAddress translatePhysical(Type *pointer) { if (!pointer) { return PhysicalAddress { 0u }; } else { auto addr = reinterpret_cast(pointer); decaf_check(addr >= internal::BasePhysicalAddress); decaf_check(addr <= internal::BasePhysicalAddress + 0x100000000ull); return PhysicalAddress { static_cast(addr - internal::BasePhysicalAddress) }; } } inline uintptr_t getBaseVirtualAddress() { return internal::BaseVirtualAddress; } inline uintptr_t getBasePhysicalAddress() { return internal::BasePhysicalAddress; } } // namespace cpu ================================================ FILE: src/libcpu/pointer.h ================================================ #pragma once #include "address.h" #include "be2_val.h" #include "mmu.h" #include template struct be2_struct; template class be2_array; namespace cpu { template class Pointer; template class FunctionPointer; template struct pointer_cast_impl; template struct is_big_endian_value : std::false_type { }; template struct is_big_endian_value> : std::true_type { }; template struct is_cpu_pointer : std::false_type { }; template struct is_cpu_pointer> : std::true_type { }; template struct is_cpu_address : std::false_type { }; template struct is_cpu_address> : std::true_type { }; template struct is_cpu_func_pointer : std::false_type { }; template struct is_cpu_func_pointer> : std::true_type { }; template struct pointer_dereference_type; template struct pointer_dereference_type::value>::type> { using type = T; }; /* * 1 byte values do not need to have be2_val<> wrapped around them. */ template struct pointer_dereference_type::value || std::is_enum::value) && sizeof(T) == 1>::type> { using type = T; }; /* * If Type is an arithmetic type, enum type, or cpu::Pointer<> type, then we * must dereference to a be2_val. */ template struct pointer_dereference_type::value || std::is_enum::value || is_cpu_address::value || is_cpu_pointer::value || is_cpu_func_pointer::value) && !std::is_const::value && sizeof(T) != 1>::type> { using type = be2_val; }; /* * const version of above */ template struct pointer_dereference_type::value || std::is_enum::value || is_cpu_address::type>::value || is_cpu_pointer::type>::value || is_cpu_func_pointer::type>::value) && std::is_const::value && sizeof(T) != 1>::type> { using type = const be2_val; }; /* * If Type is an array then we must dereference to be2_array. */ template struct pointer_dereference_type::value>::type> { using type = be2_array::type, std::extent::value>; }; /* * If Type is an class type, or union type, and NOT a cpu::Pointer<> type, then * we must dereference to be2_struct. */ template struct pointer_dereference_type::value || std::is_union::value) && !is_cpu_pointer::value && !is_cpu_address::value && !is_cpu_func_pointer::value && !std::is_array::value>::type> { using type = be2_struct; }; /* * Basically the same as pointer_dereference_type but does not wrap structs in * be2_struct. * * Used for pointer_get_type::type * Pointer::get() */ template struct pointer_get_type; template struct pointer_get_type::value || std::is_union::value) || is_cpu_pointer::value || is_cpu_address::value || is_cpu_func_pointer::value || std::is_array::value>::type> { using type = typename pointer_dereference_type::type; }; template struct pointer_get_type::value || std::is_union::value) && !is_cpu_pointer::value && !is_cpu_address::value && !is_cpu_func_pointer::value && !std::is_array::value>::type> { using type = T; }; template class Pointer { public: using value_type = ValueType; using address_type = AddressType; using dereference_type = typename pointer_dereference_type::type; using get_type = typename pointer_get_type::type; static_assert(!std::is_pointer::value, "cpu::Pointer should point to another cpu::Pointer, not a raw T* pointer."); static_assert(!is_big_endian_value::value, "be2_val should not be used as ValueType for pointer, it is implied implicitly."); static_assert(std::is_class::value || std::is_union::value || std::is_arithmetic::value || std::is_enum::value || std::is_void::value || std::is_array::value, "Invalid ValueType for Pointer"); Pointer() = default; Pointer(const Pointer &) = default; Pointer(Pointer &&) = default; Pointer &operator =(const Pointer &) = default; Pointer &operator =(Pointer &&) = default; // Pointer(nullptr) Pointer(std::nullptr_t) : mAddress(0) { } // Pointer(Pointer) template Pointer(const Pointer::type, address_type> &other, typename std::enable_if::value>::type * = nullptr) : mAddress(other.mAddress) { } // Pointer(Pointer) template Pointer(const Pointer &other, typename std::enable_if::value>::type * = nullptr) : mAddress(other.mAddress) { } // Pointer(be2_val>) template Pointer(const be2_val> &other, typename std::enable_if::value>::type * = nullptr) : mAddress(other.value().mAddress) { } // Pointer = const Pointer & template typename std::enable_if::value, Pointer>::type & operator =(const Pointer::type, address_type> &other) { mAddress = other.mAddress; return *this; } get_type *get() const { return internal::translate(mAddress); } value_type *getRawPointer() const { return internal::translate(mAddress); } explicit operator bool() const { return static_cast(mAddress); } explicit operator Pointer() const { return { *this }; } Pointer & operator =(std::nullptr_t) { mAddress = AddressType { 0u }; return *this; } template typename std::enable_if::value, dereference_type>::type & operator *() const { return *internal::translate(mAddress); } template typename std::enable_if::value, dereference_type>::type * operator ->() const { return internal::translate(mAddress); } template typename std::enable_if::value, dereference_type>::type & operator [](size_t index) const { return internal::translate(mAddress)[index]; } constexpr bool operator == (std::nullptr_t) const { return !static_cast(mAddress); } template constexpr bool operator == (const Pointer &other) const { return mAddress == other.mAddress; } template constexpr bool operator == (const be2_val> &other) const { return mAddress == other.value().mAddress; } constexpr bool operator != (std::nullptr_t) const { return static_cast(mAddress); } template constexpr bool operator != (const Pointer &other) const { return mAddress != other.mAddress; } template constexpr bool operator != (const be2_val> &other) const { return mAddress != other.value().mAddress; } template constexpr bool operator >= (const Pointer &other) const { return mAddress >= other.mAddress; } template constexpr bool operator >= (const be2_val> &other) const { return mAddress >= other.value().mAddress; } template constexpr bool operator <= (const Pointer &other) const { return mAddress <= other.mAddress; } template constexpr bool operator <= (const be2_val> &other) const { return mAddress <= other.value().mAddress; } template constexpr bool operator > (const Pointer &other) const { return mAddress > other.mAddress; } template constexpr bool operator > (const be2_val> &other) const { return mAddress > other.value().mAddress; } template constexpr bool operator < (const Pointer &other) const { return mAddress < other.mAddress; } template constexpr bool operator < (const be2_val> &other) const { return mAddress < other.value().mAddress; } template constexpr typename std::enable_if::value, Pointer &>::type operator ++ () { mAddress += sizeof(value_type); return *this; } template constexpr typename std::enable_if::value, Pointer>::type operator ++ (int) { auto prev = *this; mAddress += sizeof(value_type); return prev; } template constexpr typename std::enable_if::value, Pointer &>::type operator += (ptrdiff_t value) { mAddress += value * sizeof(value_type); return *this; } template constexpr typename std::enable_if::value, Pointer &>::type operator -= (ptrdiff_t value) { mAddress -= value * sizeof(value_type); return *this; } template constexpr typename std::enable_if::value, Pointer>::type operator + (ptrdiff_t value) const { Pointer dst; dst.mAddress = mAddress + (value * sizeof(value_type)); return dst; } template constexpr typename std::enable_if::value, ptrdiff_t>::type operator -(const Pointer &other) const { return (mAddress - other.mAddress) / sizeof(value_type); } template constexpr typename std::enable_if::value, Pointer>::type operator -(ptrdiff_t value) const { Pointer dst; dst.mAddress = mAddress - (value * sizeof(value_type)); return dst; } protected: template friend struct pointer_cast_impl; template friend class Pointer; address_type mAddress; }; template using VirtualPointer = Pointer; template using PhysicalPointer = Pointer; template struct pointer_cast_impl, std::is_pointer, std::negation>> >>::type> { using DstType = std::remove_pointer_t; using SrcType = std::remove_pointer_t; // Pointer to Pointer static constexpr Pointer cast(Pointer src) { Pointer dst; dst.mAddress = src.mAddress; return dst; } }; template struct pointer_cast_impl, std::is_pointer, std::is_const>, std::is_const> >>::type> { using DstType = std::remove_pointer_t; using SrcType = std::remove_pointer_t; // Pointer to Pointer static constexpr Pointer cast(Pointer src) { Pointer dst; dst.mAddress = src.mAddress; return dst; } }; template struct pointer_cast_impl::value>::type> { static_assert(std::is_pointer::value); using DstType = typename std::remove_pointer::type; // AddressType to Pointer static constexpr Pointer cast(AddressType src) { Pointer dst; dst.mAddress = src; return dst; } }; template struct pointer_cast_impl::value>::type> { using SrcType = typename std::remove_pointer::type; // Pointer to AddressType static constexpr AddressType cast(Pointer src) { return src.mAddress; } }; } // namespace cpu // Custom formatters for fmtlib namespace fmt { inline namespace v8 { template struct formatter; } // Custom formatter in cpu_formatters.h template struct formatter, Char, void>; template struct formatter, Char, void>; template struct formatter, Char, void>; } // namespace fmt ================================================ FILE: src/libcpu/src/cpu.cpp ================================================ #include "cpu.h" #include "cpu_alarm.h" #include "cpu_config.h" #include "cpu_host_exception.h" #include "cpu_internal.h" #include "espresso/espresso_instructionset.h" #include "interpreter/interpreter.h" #include "jit/jit.h" #include "jit/binrec/jit_binrec.h" #include "mem.h" #include "mmu.h" #include #include #include #include #include namespace cpu { std::chrono::time_point sStartupTime; static EntrypointHandler sCoreEntryPointHandler; BranchTraceHandler gBranchTraceHandler; static bool sJitEnabled = false; static std::array, 3> sCores { }; static thread_local uint32_t tCurrentCoreId = InvalidCoreId; static thread_local cpu::Core * tCurrentCore = nullptr; void initialise() { auto settings = config(); sJitEnabled = settings->jit.enabled; // Initalise cpu! initialiseMemory(); espresso::initialiseInstructionSet(); interpreter::initialise(); if (sJitEnabled) { auto backend = new jit::BinrecBackend { settings->jit.codeCacheSizeMB * 1024 * 1024, settings->jit.dataCacheSizeMB * 1024 * 1024 }; backend->setOptFlags(settings->jit.optimisationFlags); backend->setVerifyEnabled(settings->jit.verify, settings->jit.verifyAddress); jit::setBackend(backend); } sStartupTime = std::chrono::steady_clock::now(); } void clearInstructionCache() { cpu::jit::clearCache(0, 0xFFFFFFFF); } void invalidateInstructionCache(uint32_t address, uint32_t size) { cpu::jit::clearCache(address, size); } void addJitReadOnlyRange(uint32_t address, uint32_t size) { jit::addReadOnlyRange(address, size); } void coreEntryPoint(Core *core) { tCurrentCoreId = core->id; tCurrentCore = core; sCoreEntryPointHandler(core); } void start() { internal::installHostExceptionHandler(); for (auto i = 0u; i < sCores.size(); ++i) { auto core = jit::initialiseCore(i); if (!core) { core = new Core {}; core->id = i; } sCores[i] = std::unique_ptr { core }; core->thread = std::thread { coreEntryPoint, core }; core->next_alarm = std::chrono::steady_clock::time_point::max(); static const std::string coreNames[] = { "Core #0", "Core #1", "Core #2" }; platform::setThreadName(&core->thread, coreNames[i]); } internal::startAlarmThread(); } void join() { for (auto &core : sCores) { if (core && core->thread.joinable()) { core->thread.join(); core.reset(); } } internal::joinAlarmThread(); } void halt() { for (auto i = 0; i < 3; ++i) { interrupt(i, SRESET_INTERRUPT); } internal::stopAlarmThread(); } Core * getCore(int index) { return sCores[index].get(); } void setCoreEntrypointHandler(EntrypointHandler handler) { sCoreEntryPointHandler = handler; } void setSegfaultHandler(SegfaultHandler handler) { internal::setUserSegfaultHandler(handler); } void setBranchTraceHandler(BranchTraceHandler handler) { gBranchTraceHandler = handler; } std::chrono::steady_clock::time_point tbToTimePoint(uint64_t ticks) { auto cpuTicks = TimerDuration { ticks }; auto nanos = std::chrono::duration_cast(cpuTicks); return sStartupTime + nanos; } uint64_t Core::tb() { auto now = std::chrono::steady_clock::now(); auto ticks = std::chrono::duration_cast(now - sStartupTime); return ticks.count(); } namespace this_core { cpu::Core * state() { return tCurrentCore; } uint32_t id() { return tCurrentCoreId; } void resume() { if (sJitEnabled) { jit::resume(); } else { interpreter::resume(); } } void executeSub() { auto lr = tCurrentCore->lr; tCurrentCore->lr = CALLBACK_ADDR; resume(); tCurrentCore->lr = lr; } void updateRoundingMode() { Core *core = tCurrentCore; static const int modes[4] = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD }; fesetround(modes[core->fpscr.rn]); } } // namespace this_core } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_alarm.cpp ================================================ #include "cpu.h" #include "cpu_alarm.h" #include "cpu_breakpoints.h" #include "cpu_internal.h" #include #include #include #include #include #include struct { std::atomic running { false }; std::mutex mutex; std::condition_variable cv; std::thread thread; } sAlarmData; namespace cpu::internal { static void alarmEntryPoint() { while (sAlarmData.running) { std::unique_lock lock{ sAlarmData.mutex }; auto now = std::chrono::steady_clock::now(); auto next = std::chrono::steady_clock::time_point::max(); bool timedWait = false; for (auto i = 0; i < 3; ++i) { auto core = getCore(i); if (core->next_alarm <= now) { core->next_alarm = std::chrono::steady_clock::time_point::max(); cpu::interrupt(i, ALARM_INTERRUPT); } else if (core->next_alarm < next) { next = core->next_alarm; timedWait = true; } } if (timedWait) { sAlarmData.cv.wait_until(lock, next); } else { sAlarmData.cv.wait(lock); } } } void startAlarmThread() { decaf_check(!sAlarmData.running.load()); sAlarmData.running = true; sAlarmData.thread = std::thread { alarmEntryPoint }; platform::setThreadName(&sAlarmData.thread, "CPU Alarm Thread"); } void joinAlarmThread() { if (sAlarmData.thread.joinable()) { sAlarmData.thread.join(); } } void stopAlarmThread() { sAlarmData.running = false; sAlarmData.cv.notify_all(); } } // namespace cpu::internal namespace cpu::this_core { void setNextAlarm(std::chrono::steady_clock::time_point time) { auto core = this_core::state(); std::unique_lock lock { sAlarmData.mutex }; core->next_alarm = time; sAlarmData.cv.notify_all(); } } // namespace cpu::this_core ================================================ FILE: src/libcpu/src/cpu_alarm.h ================================================ #pragma once namespace cpu::internal { void startAlarmThread(); void joinAlarmThread(); void stopAlarmThread(); } // namespace cpu::internal ================================================ FILE: src/libcpu/src/cpu_breakpoints.cpp ================================================ #include "cpu.h" #include "cpu_breakpoints.h" #include "espresso/espresso_instructionset.h" #include "mem.h" #include #include #include #include #include namespace cpu { static std::shared_ptr sActiveBreakpoints; using ModifyListFn = std::function; static inline void updateBreakpointList(ModifyListFn fn) { auto newList = std::make_shared(); auto currentList = sActiveBreakpoints; do { if (currentList) { *newList = *currentList; } else { newList->clear(); } if (!fn(*newList)) { // If function returns false, do not update breakpoint list. break; } } while (!std::atomic_compare_exchange_strong(&sActiveBreakpoints, ¤tList, newList)); } /** * Add a breakpoint at an address. */ void addBreakpoint(uint32_t address, Breakpoint::Type type) { auto savedCode = mem::read(address); updateBreakpointList([address, type, savedCode](BreakpointList &list) { auto itr = std::find_if(list.begin(), list.end(), [address](auto &bp) { return bp.address == address; }); if (itr != list.end()) { if (itr->type == type) { // Same type of breakpoint already at address, do not modify list. return false; } if (type == Breakpoint::SingleFire && itr->type == Breakpoint::MultiFire) { // Already have a multi fire breakpoint, no need to set single fire. return false; } // Update existing breakpoint type. itr->type = type; return true; } list.push_back({ type, address, savedCode }); return true; }); // Set unconditional trap instruction at address. auto trapInstr = espresso::encodeInstruction(espresso::InstructionID::tw); trapInstr.to = 31; trapInstr.rA = 0; trapInstr.rB = 0; mem::write(address, trapInstr); cpu::invalidateInstructionCache(address, 4); } /** * Remove a breakpoint at an address. */ void removeBreakpoint(uint32_t address) { updateBreakpointList([address](BreakpointList &list) { auto itr = std::find_if(list.begin(), list.end(), [address](auto &bp) { return bp.address == address; }); if (itr == list.end()) { return false; } // Restore saved code. mem::write(address, itr->savedCode); list.erase(itr); return true; }); cpu::invalidateInstructionCache(address, 4); } /** * Returns true if we hit a breakpoint at the address. * * Will remove the breakpoint if it is a SingleFire breakpoint. */ bool testBreakpoint(uint32_t address) { auto list = sActiveBreakpoints; if (!list) { return false; } auto itr = std::find_if(list->begin(), list->end(), [address](auto &bp) { return bp.address == address; }); if (itr == list->end()) { return false; } if (itr->type == Breakpoint::SingleFire) { removeBreakpoint(address); } return true; } /** * Returns true if there are any breakpoints set. */ bool hasBreakpoints() { return sActiveBreakpoints != nullptr; } /** * Returns true if there is a breakpoint at the specified address. */ bool hasBreakpoint(uint32_t address) { auto list = sActiveBreakpoints; if (!list) { return false; } auto itr = std::find_if(list->begin(), list->end(), [address](auto &bp) { return bp.address == address; }); return itr != list->end(); } /** * Get a list of all active breakpoints. */ std::shared_ptr getBreakpoints() { return sActiveBreakpoints; } /** * Get the instruction at an address before we edited it with a tw. * * If there is no breakpoint, reads the memory and returns the current instruction. */ uint32_t getBreakpointSavedCode(uint32_t address) { auto list = sActiveBreakpoints; if (list) { auto itr = std::find_if(list->begin(), list->end(), [address](auto &bp) { return bp.address == address; }); if (itr != list->end()) { return itr->savedCode; } } return mem::read(address); } } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_configstorage.cpp ================================================ #include "cpu_config.h" #include "cpu_configstorage.h" #include namespace cpu { static ConfigStorage sSettings; std::shared_ptr config() { return sSettings.get(); } void setConfig(const Settings &settings) { sSettings.set(std::make_shared(settings)); } void registerConfigChangeListener(ConfigStorage::ChangeListener listener) { sSettings.addListener(listener); } } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_configstorage.h ================================================ #pragma once #include "cpu_config.h" #include namespace cpu { void registerConfigChangeListener(ConfigStorage::ChangeListener listener); } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_host_exception.cpp ================================================ #include "cpu.h" #include #include #include #include #include namespace cpu::internal { static thread_local uint32_t sSegfaultAddr = 0; static thread_local platform::StackTrace *sSegfaultStackTrace = nullptr; static SegfaultHandler sUserSegfaultHandler = nullptr; static void coreSegfaultEntry() { auto core = cpu::this_core::state(); if (sSegfaultAddr == core->nia) { core->srr0 = sSegfaultAddr; } else { core->srr0 = core->nia; core->dar = sSegfaultAddr; core->dsisr = 0u; } if (sUserSegfaultHandler) { sUserSegfaultHandler(core, sSegfaultAddr, sSegfaultStackTrace); decaf_abort("The user segfault handler unexpectedly returned."); } else { decaf_host_fault(fmt::format("Segfault exception, srr0: 0x{:08X}, dar: 0x{:08X}\n", core->srr0, core->dar), sSegfaultStackTrace); } } static void illegalInstructionHandler() { auto core = cpu::this_core::state(); core->srr0 = sSegfaultAddr; decaf_host_fault(fmt::format("Illegal instruction exception, srr0: 0x{:08X}\n", core->srr0), sSegfaultStackTrace); } static platform::ExceptionResumeFunc hostExceptionHandler(platform::Exception *exception) { // Handle illegal instructions! if (exception->type == platform::Exception::InvalidInstruction) { sSegfaultStackTrace = platform::captureStackTrace(); return illegalInstructionHandler; } // Only handle AccessViolation exceptions if (exception->type != platform::Exception::AccessViolation) { return platform::UnhandledException; } // Only handle exceptions from the CPU cores if (this_core::id() >= 0xFF) { return platform::UnhandledException; } // Retreive the exception information auto info = reinterpret_cast(exception); auto address = info->address; // Only handle exceptions within the virtual memory bounds auto memBase = getBaseVirtualAddress(); if (address != 0 && (address < memBase || address >= memBase + 0x100000000)) { return platform::UnhandledException; } sSegfaultAddr = static_cast(address - memBase); sSegfaultStackTrace = platform::captureStackTrace(); return coreSegfaultEntry; } void installHostExceptionHandler() { static bool installed = false; if (!installed) { installed = platform::installExceptionHandler(hostExceptionHandler); } } void setUserSegfaultHandler(SegfaultHandler userHandler) { sUserSegfaultHandler = userHandler; } } // namespace cpu::internal ================================================ FILE: src/libcpu/src/cpu_host_exception.h ================================================ #pragma once #include "cpu.h" namespace cpu::internal { void installHostExceptionHandler(); void setUserSegfaultHandler(SegfaultHandler userHandler); } // namespace cpu::internal ================================================ FILE: src/libcpu/src/cpu_internal.h ================================================ #pragma once #include "cpu.h" #include "cpu_config.h" #include "mem.h" #include #include #include namespace cpu { extern BranchTraceHandler gBranchTraceHandler; Core * getCore(int index); SystemCallHandler getSystemCallHandler(uint32_t id); bool initialiseMemory(); namespace this_core { void updateRoundingMode(); } // namespace this_core } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_interrupts.cpp ================================================ #include "cpu.h" #include "cpu_breakpoints.h" #include "cpu_internal.h" #include #include #include namespace cpu { static void defaultInterruptHandler(Core *core, uint32_t interrupt_flags) { } static InterruptHandler sUserInterruptHandler = &defaultInterruptHandler; static std::mutex sInterruptMutex; static std::condition_variable sInterruptCondition; void interrupt(int coreIndex, uint32_t flags) { std::unique_lock lock { sInterruptMutex }; auto core = getCore(coreIndex); if (core) { core->interrupt.fetch_or(flags); } sInterruptCondition.notify_all(); } void setInterruptHandler(InterruptHandler handler) { sUserInterruptHandler = handler; } namespace this_core { void clearInterrupt(uint32_t flags) { state()->interrupt.fetch_and(~flags); } uint32_t interruptMask() { return state()->interrupt_mask; } uint32_t setInterruptMask(uint32_t mask) { auto core = state(); auto old_mask = core->interrupt_mask; core->interrupt_mask = mask; return old_mask; } void checkInterrupts() { auto core = state(); auto mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS; auto flags = core->interrupt.fetch_and(~mask); if (flags & mask) { sUserInterruptHandler(core, flags); } } void waitForInterrupt() { auto core = this_core::state(); std::unique_lock lock { sInterruptMutex }; while (true) { if (!(core->interrupt_mask & ~NONMASKABLE_INTERRUPTS)) { decaf_abort("WFI thread found all maskable interrupts were disabled"); } auto mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS; auto flags = core->interrupt.fetch_and(~mask); if (flags & mask) { lock.unlock(); sUserInterruptHandler(core, flags); lock.lock(); } else { sInterruptCondition.wait(lock); } } } void waitNextInterrupt(std::chrono::steady_clock::time_point until) { auto core = this_core::state(); std::unique_lock lock { sInterruptMutex }; if (!(core->interrupt_mask & ~NONMASKABLE_INTERRUPTS)) { decaf_abort("WFI thread found all maskable interrupts were disabled"); } auto mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS; auto flags = core->interrupt.fetch_and(~mask); if (!(flags & mask)) { if (until == std::chrono::steady_clock::time_point { }) { sInterruptCondition.wait(lock); } else { sInterruptCondition.wait_until(lock, until); } mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS; flags = core->interrupt.fetch_and(~mask); } lock.unlock(); if (flags & mask) { sUserInterruptHandler(core, flags); } } } // namespace this_core } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_memtrack_posix.cpp ================================================ #include "memtrack.h" #include "mmu.h" #include #include #ifdef PLATFORM_POSIX namespace cpu { namespace internal { void initialiseMemtrack() { } void registerTrackedRange(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size) { } void unregisterTrackedRange(VirtualAddress virtualAddress, uint32_t size) { } void clearTrackedRanges() { } } // namespace internal MemtrackState getMemoryState(PhysicalAddress physicalAddress, uint32_t size) { // POSIX always hashes for the moment. This is due to my inability to actually // test any sort of implementation on a linux system. auto physPtr = reinterpret_cast(cpu::getBasePhysicalAddress() + physicalAddress.getAddress()); auto hashVal = DataHash {}.write(physPtr, size); return MemtrackState { hashVal.value() }; } } // namespace cpu #endif // PLATFORM_POSIX ================================================ FILE: src/libcpu/src/cpu_memtrack_win.cpp ================================================ #include "memtrack.h" #ifdef PLATFORM_WINDOWS #include "cpu_config.h" #include "cpu_internal.h" #include "mmu.h" #include #include #include #include #define WIN32_LEAN_AND_MEAN #include namespace cpu { static constexpr uint64_t PhysTrackSetBit = 0x8000000000000000; static constexpr uint64_t PhysIsMappedBit = 0x4000000000000000; static constexpr uint32_t VirtTrackSetBit = 0x80000000; static constexpr uint32_t VirtIsMappedBit = 0x40000000; struct MappedArea { cpu::VirtualAddress virtAddr; cpu::PhysicalAddress physAddr; uint32_t size; }; static uintptr_t sPhysBaseAddress = 0; static uintptr_t sVirtBaseAddress = 0; static uint64_t sPageSizeBits = 0; static std::atomic *sVirtLookup = nullptr; static std::atomic *sTrackCount = nullptr; static std::vector sVirtMap; namespace internal { LONG writeExceptionHandler(_EXCEPTION_POINTERS *ExceptionInfo) { auto &ExceptionRecord = ExceptionInfo->ExceptionRecord; if (UNLIKELY(ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)) { return EXCEPTION_CONTINUE_SEARCH; } // We do not check to see if sTrackCount is initialized here because we can assume // that it must be initialized if the vectored exeception handler was registered. // We do not verify that the SET bit is set for physical or virtual since another // thread may be racing us. Instead we only check that the region was mapped at // least once (via the IsMapped bits), and if its been mapped once we can assume // that it was meant to be set up. We also allow VirtualProtect to return an old // protection of READ _OR_ READWRITE for the same reasons. auto MemoryAddress = ExceptionRecord->ExceptionInformation[1]; if (MemoryAddress >= sVirtBaseAddress && MemoryAddress < sVirtBaseAddress + 0x100000000) { // This is a virtual address, we need to do a lookup first. auto lookupIdx = (MemoryAddress - sVirtBaseAddress) >> sPageSizeBits; auto oldLookupValue = sVirtLookup[lookupIdx].fetch_and(~VirtTrackSetBit); if (!(oldLookupValue & VirtIsMappedBit)) { // This was an unmapped range to begin with. return EXCEPTION_CONTINUE_SEARCH; } // Figure out which page we belong to. auto trackIdx = oldLookupValue & ~(VirtTrackSetBit | VirtIsMappedBit); // Increment the counter to mark it as having changed sTrackCount[trackIdx].fetch_add(1); } else if (MemoryAddress >= sPhysBaseAddress && MemoryAddress < sPhysBaseAddress + 0x100000000) { // This is a physical address, we can directly convert auto trackIdx = static_cast((MemoryAddress - sPhysBaseAddress) >> sPageSizeBits); // Then we remove the tracking bit auto oldTrackValue = sTrackCount[trackIdx].fetch_and(~PhysTrackSetBit); if (!(oldTrackValue & PhysIsMappedBit)) { // There was no protection on this area. Something else has gone wrong. return EXCEPTION_CONTINUE_SEARCH; } // Increment the counter to mark it as having changed sTrackCount[trackIdx].fetch_add(1); } else { return EXCEPTION_CONTINUE_SEARCH; } // Finally we reprotect the memory to its normal state DWORD oldProtection; VirtualProtect(reinterpret_cast(MemoryAddress), 1, PAGE_READWRITE, &oldProtection); if (oldProtection != PAGE_READONLY && oldProtection != PAGE_READWRITE) { VirtualProtect(reinterpret_cast(MemoryAddress), 1, oldProtection, nullptr); return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_CONTINUE_EXECUTION; } void initialiseMemtrack() { if (!config()->memory.writeTrackEnabled) { return; } SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); auto pageSize = sysInfo.dwPageSize; auto pageSizeBits = 0; auto i = pageSize; while (i >>= 1) pageSizeBits++; sPhysBaseAddress = cpu::getBasePhysicalAddress(); sVirtBaseAddress = cpu::getBaseVirtualAddress(); sPageSizeBits = pageSizeBits; auto numtrackTableEntries = 0x100000000 >> pageSizeBits; // Initialise the lookup table to point everything to 0xFFFFFFFF (invalid) sVirtLookup = new std::atomic[numtrackTableEntries]; memset(sVirtLookup, 0x00, numtrackTableEntries * sizeof(uint32_t)); // Initialise the tracking table to all 0's sTrackCount = new std::atomic[numtrackTableEntries]; memset(sTrackCount, 0x00, numtrackTableEntries * sizeof(uint64_t)); // Install the exception handler AddVectoredExceptionHandler(1, writeExceptionHandler); } void registerTrackedRange(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size) { if (!sTrackCount) { return; } if (size == 0) { return; } // We have to remove any conflicting virtual mappings before we can proceed. Note // that this is technically an incorrect operation, as they may not be overlaying on // eachother perfectly, but its challenging to handle this correctly, and this should // work for the immediate future. // TODO: Correctly unmap regions from the memory tracker. for (auto iter = sVirtMap.begin(); iter != sVirtMap.end(); ++iter) { if (virtualAddress >= iter->virtAddr && virtualAddress < iter->virtAddr + iter->size) { iter = sVirtMap.erase(iter); } } // Add an entry to the mappings list sVirtMap.push_back({ virtualAddress, physicalAddress, size }); // Apply the neccessary changes to the tracking tables auto firstPhysPage = physicalAddress.getAddress() >> sPageSizeBits; auto firstPage = virtualAddress.getAddress() >> sPageSizeBits; auto lastPage = (virtualAddress.getAddress() + (size - 1)) >> sPageSizeBits; for (auto pageIdx = firstPage, physPageIdx = firstPhysPage; pageIdx <= lastPage; ++pageIdx, ++physPageIdx) { auto oldPhysPage = sVirtLookup[pageIdx].exchange(VirtIsMappedBit | physPageIdx); if (oldPhysPage & VirtIsMappedBit) { decaf_abort("write tracker attempted to register an already registered page"); } // In order to avoid needing to go searching for all the pages that we // need to protect, we simply increment the tracking table to indicate // that any of the memory may have changed. The next time the pages are // checked for changes, the correct protections will be applied. sTrackCount[physPageIdx].fetch_add(1); } } void unregisterTrackedRange(VirtualAddress virtualAddress, uint32_t size) { if (!sTrackCount) { return; } if (size == 0) { return; } // Remove the entry from the mappings list for (auto iter = sVirtMap.begin(); iter != sVirtMap.end(); ++iter) { if (iter->virtAddr == virtualAddress && iter->size == size) { sVirtMap.erase(iter); break; } } // Apply the neccessary changes to the tracking tables auto firstPage = virtualAddress.getAddress() >> sPageSizeBits; auto lastPage = firstPage + ((size - 1) >> sPageSizeBits); for (auto pageIdx = firstPage; pageIdx <= lastPage; ++pageIdx) { auto oldPhysPage = sVirtLookup[pageIdx].exchange(0x00000000); if (!(oldPhysPage & VirtIsMappedBit)) { decaf_abort("write tracker attempted to unregister an already unregister page"); } } } void clearTrackedRanges() { if (!sTrackCount) { return; } // Resetting the tracked ranges is as simple as clearing the lookup table // we are using to translate virtual addresses to physical pages. auto numtrackTableEntries = 0x100000000 >> sPageSizeBits; memset(sVirtLookup, 0x00, numtrackTableEntries * sizeof(uint32_t)); } } // namespace internal MemtrackState getMemoryState(PhysicalAddress physicalAddress, uint32_t size) { if (!sTrackCount) { // If the write tracking system is not enabled, we simply hash. auto physPtr = reinterpret_cast(cpu::getBasePhysicalAddress() + physicalAddress.getAddress()); auto hashVal = DataHash {}.write(physPtr, size); return MemtrackState { hashVal.value() }; } if (size == 0) { return MemtrackState { 0 }; } // Calculate the actual return value uint64_t pageIndexTotal = 0; { uintptr_t startAddr = physicalAddress.getAddress(); uintptr_t endAddr = startAddr + (size - 1); auto startPage = startAddr >> sPageSizeBits; auto lastPage = endAddr >> sPageSizeBits; for (auto i = startPage; i <= lastPage; ++i) { auto pageData = sTrackCount[i].load(); auto pageCount = pageData & ~(PhysTrackSetBit); pageIndexTotal += pageCount; } } // Write-protect all these regions for the future for (auto &area : sVirtMap) { if (physicalAddress < area.physAddr || physicalAddress >= area.physAddr + area.size) { continue; } auto virtualAddress = area.virtAddr + (physicalAddress - area.physAddr); uintptr_t startAddr = virtualAddress.getAddress(); uintptr_t endAddr = startAddr + (size - 1); auto startPage = startAddr >> sPageSizeBits; auto lastPage = endAddr >> sPageSizeBits; auto pagePtr = reinterpret_cast(sVirtBaseAddress + (startPage << sPageSizeBits)); auto pageSize = 1 << sPageSizeBits; auto protectCombiner = makeRangeCombiner( [=](void*, uint8_t* pagePtr, uint64_t pageSize) { DWORD oldProtection; VirtualProtect(pagePtr, pageSize, PAGE_READONLY, &oldProtection); if (oldProtection != PAGE_READONLY && oldProtection != PAGE_READWRITE) { decaf_abort("Attempted to write-track a weird page"); } }); for (auto i = startPage; i <= lastPage; ++i) { auto oldTrackValue = sVirtLookup[i].fetch_or(VirtTrackSetBit | VirtIsMappedBit); if (!(oldTrackValue & VirtIsMappedBit)) { decaf_abort("Attempted to write-track unmapped memory"); } if (!(oldTrackValue & VirtTrackSetBit)) { // If we weren't previous tracking this memory, we need to add it. protectCombiner.push(nullptr, pagePtr, pageSize); } pagePtr += pageSize; } protectCombiner.flush(); } return MemtrackState { pageIndexTotal }; } } // namespace cpu #endif // PLATFORM_WINDOWS ================================================ FILE: src/libcpu/src/cpu_mmu.cpp ================================================ #include "mmu.h" #include "memorymap.h" #include "memtrack.h" namespace cpu { static MemoryMap sMemoryMap; bool initialiseMemory() { if (sMemoryMap.reserve()) { internal::initialiseMemtrack(); return true; } return false; } bool allocateVirtualAddress(VirtualAddress virtualAddress, uint32_t size) { return sMemoryMap.allocateVirtualAddress(virtualAddress, size); } bool freeVirtualAddress(VirtualAddress virtualAddress, uint32_t size) { return sMemoryMap.freeVirtualAddress(virtualAddress, size); } VirtualAddressRange findFreeVirtualAddress(uint32_t size, uint32_t align) { return sMemoryMap.findFreeVirtualAddress(size, align); } VirtualAddressRange findFreeVirtualAddressInRange(VirtualAddressRange range, uint32_t size, uint32_t align) { return sMemoryMap.findFreeVirtualAddressInRange(range, size, align); } bool mapMemory(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size, MapPermission permission) { if (sMemoryMap.mapMemory(virtualAddress, physicalAddress, size, permission)) { if (permission == MapPermission::ReadWrite) { internal::registerTrackedRange(virtualAddress, physicalAddress, size); } return true; } return false; } bool unmapMemory(VirtualAddress virtualAddress, uint32_t size) { if (sMemoryMap.unmapMemory(virtualAddress, size)) { internal::unregisterTrackedRange(virtualAddress, size); return true; } return false; } bool resetVirtualMemory() { internal::clearTrackedRanges(); return sMemoryMap.resetVirtualMemory(); } VirtualMemoryType queryVirtualAddress(VirtualAddress virtualAddress) { return sMemoryMap.queryVirtualAddress(virtualAddress); } bool isValidAddress(VirtualAddress address) { PhysicalAddress physical; return sMemoryMap.virtualToPhysicalAddress(address, physical); } bool virtualToPhysicalAddress(VirtualAddress virtualAddress, PhysicalAddress &out) { return sMemoryMap.virtualToPhysicalAddress(virtualAddress, out); } } // namespace cpu ================================================ FILE: src/libcpu/src/cpu_systemcall.cpp ================================================ #include "cpu.h" #include namespace cpu { constexpr auto MaxRegisteredSystemCalls = 0xffff; // This must be `(1< handlers[MaxRegisteredSystemCalls] = { nullptr }; std::atomic_uint32_t validHandlerID = 0; std::atomic_uint32_t illegalHandlerID = 0; } sSystemCall; void setUnknownSystemCallHandler(SystemCallHandler handler) { sSystemCall.unknownHandler = handler; } uint32_t registerSystemCallHandler(SystemCallHandler handler) { auto id = sSystemCall.validHandlerID++; sSystemCall.handlers[id] = handler; return 0x100000 | id; } uint32_t registerIllegalSystemCall() { return 0x800000 | (++sSystemCall.illegalHandlerID); } SystemCallHandler getSystemCallHandler(uint32_t id) { if (LIKELY(id & 0x100000)) { return sSystemCall.handlers[id & MaxRegisteredSystemCalls]; } return sSystemCall.unknownHandler; } } // namespace cpu ================================================ FILE: src/libcpu/src/interpreter/interpreter.cpp ================================================ #include "cpu_breakpoints.h" #include "cpu_internal.h" #include "espresso/espresso_instructionset.h" #include "interpreter.h" #include "interpreter_insreg.h" #include "mem.h" #include "trace.h" #include #include #include namespace cpu { namespace interpreter { static std::vector sInstructionMap; void initialise() { sInstructionMap.resize(static_cast(espresso::InstructionID::InstructionCount), nullptr); // Register instruction handlers registerBranchInstructions(); registerConditionInstructions(); registerFloatInstructions(); registerIntegerInstructions(); registerLoadStoreInstructions(); registerPairedInstructions(); registerSystemInstructions(); } instrfptr_t getInstructionHandler(espresso::InstructionID id) { auto instrId = static_cast(id); if (instrId >= sInstructionMap.size()) { return nullptr; } return sInstructionMap[instrId]; } void registerInstruction(espresso::InstructionID id, instrfptr_t fptr) { sInstructionMap[static_cast(id)] = fptr; } bool hasInstruction(espresso::InstructionID id) { return getInstructionHandler(id) != nullptr; } Core * step_one(Core *core) { // Check if we hit any breakpoints if (testBreakpoint(core->nia)) { core->interrupt.fetch_or(DBGBREAK_INTERRUPT); this_core::checkInterrupts(); } auto cia = core->nia; core->cia = cia; core->nia = cia + 4; auto instr = mem::read(cia); auto data = espresso::decodeInstruction(instr); if (!data) { gLog->error("Could not decode instruction at {:08x} = {:08x}", cia, instr.value); } decaf_check(data); auto trace = traceInstructionStart(instr, data, core); auto fptr = sInstructionMap[static_cast(data->id)]; if (!fptr) { gLog->error("Unimplemented interpreter instruction {}", data->name); } decaf_check(fptr); fptr(core, instr); if (data->id == InstructionID::kc) { // If this is a KC, there is the potential that we are running on a // different core now. Lets make sure that we are using the right one. core = this_core::state(); } decaf_check(core->cia == cia); traceInstructionEnd(trace, instr, data, core); return core; } void resume() { // Before we resume, we need to update our states! this_core::updateRoundingMode(); std::feclearexcept(FE_ALL_EXCEPT); auto core = cpu::this_core::state(); while (core->nia != cpu::CALLBACK_ADDR) { this_core::checkInterrupts(); core = step_one(this_core::state()); } } } // namespace interpreter } // namespace cpu ================================================ FILE: src/libcpu/src/interpreter/interpreter.h ================================================ #pragma once #include "cpu.h" namespace cpu { namespace interpreter { void initialise(); Core * step_one(Core *core); void resume(); } // namespace interpreter } // namespace cpu ================================================ FILE: src/libcpu/src/interpreter/interpreter_branch.cpp ================================================ #include #include "cpu_internal.h" #include "interpreter_insreg.h" static void b(cpu::Core *state, Instruction instr) { uint32_t nia; nia = sign_extend<26>(instr.li << 2); if (!instr.aa) { nia += state->cia; } state->nia = nia; if (instr.lk) { state->lr = state->cia + 4; } if (cpu::gBranchTraceHandler) { cpu::gBranchTraceHandler(state, state->nia); } } // Branch Conditional enum BoBits { CtrValue = 1, NoCheckCtr = 2, CondValue = 3, NoCheckCond = 4 }; enum BcFlags { BcCheckCtr = 1 << 0, BcCheckCond = 1 << 1, BcBranchLR = 1 << 2, BcBranchCTR = 1 << 3 }; template static void bcGeneric(cpu::Core *state, Instruction instr) { auto bo = instr.bo; auto ctr_ok = true; auto cond_ok = true; if constexpr (!!(flags & BcCheckCtr)) { if (!get_bit(bo)) { state->ctr--; auto ctb = static_cast(state->ctr != 0); auto ctv = get_bit(bo); ctr_ok = !!(ctb ^ ctv); } } if constexpr (!!(flags & BcCheckCond)) { if (!get_bit(bo)) { auto crb = get_bit(state->cr.value, 31 - instr.bi); auto crv = get_bit(bo); cond_ok = (crb == crv); } } if (ctr_ok && cond_ok) { uint32_t nia; if constexpr (!!(flags & BcBranchCTR)) { nia = state->ctr & ~0x3; } else if constexpr (!!(flags & BcBranchLR)) { nia = state->lr & ~0x3; } else { nia = sign_extend<16>(instr.bd << 2); if (!instr.aa) { nia += state->cia; } } state->nia = nia; if (instr.lk) { state->lr = state->cia + 4; } } if (cpu::gBranchTraceHandler) { cpu::gBranchTraceHandler(state, state->nia); } } // Branch Conditional static void bc(cpu::Core *state, Instruction instr) { return bcGeneric(state, instr); } // Branch Conditional to CTR static void bcctr(cpu::Core *state, Instruction instr) { return bcGeneric(state, instr); } // Branch Conditional to LR static void bclr(cpu::Core *state, Instruction instr) { return bcGeneric(state, instr); } void cpu::interpreter::registerBranchInstructions() { RegisterInstruction(b); RegisterInstruction(bc); RegisterInstruction(bcctr); RegisterInstruction(bclr); } ================================================ FILE: src/libcpu/src/interpreter/interpreter_condition.cpp ================================================ #include #include #include "interpreter_float.h" #include "interpreter_insreg.h" #include #include using espresso::ConditionRegisterFlag; using espresso::FpscrFlags; // TODO: Move these getCRX functions static std::pair getCRFRange(uint32_t field) { auto me = ((8 - field) * 4) - 1; auto mb = me - 3; return { mb, me }; } uint32_t getCRF(cpu::Core *state, uint32_t field) { auto bits = getCRFRange(field); return (state->cr.value >> bits.first) & 0xf; } void setCRF(cpu::Core *state, uint32_t field, uint32_t value) { auto cr = state->cr.value; auto bits = getCRFRange(field); auto mask = make_bitmask(bits.first, bits.second); cr = (cr & ~mask) | (value << bits.first); state->cr.value = cr; } uint32_t getCRB(cpu::Core *state, uint32_t bit) { return get_bit(state->cr.value, 31 - bit); } void setCRB(cpu::Core *state, uint32_t bit, uint32_t value) { state->cr.value = set_bit_value(state->cr.value, 31 - bit, value); } // Compare enum CmpFlags { CmpImmediate = 1 << 0, // b = imm }; template static void cmpGeneric(cpu::Core *state, Instruction instr) { Type a, b; uint32_t c; a = state->gpr[instr.rA]; if constexpr (flags & CmpImmediate) { if (std::is_signed::value) { b = sign_extend<16>(instr.simm); } else { b = instr.uimm; } } else { b = state->gpr[instr.rB]; } if (a < b) { c = ConditionRegisterFlag::LessThan; } else if (a > b) { c = ConditionRegisterFlag::GreaterThan; } else { c = ConditionRegisterFlag::Equal; } if (state->xer.so) { c |= ConditionRegisterFlag::SummaryOverflow; } setCRF(state, instr.crfD, c); } static void cmp(cpu::Core *state, Instruction instr) { return cmpGeneric(state, instr); } static void cmpi(cpu::Core *state, Instruction instr) { return cmpGeneric(state, instr); } static void cmpl(cpu::Core *state, Instruction instr) { return cmpGeneric(state, instr); } static void cmpli(cpu::Core *state, Instruction instr) { return cmpGeneric(state, instr); } // Floating Compare enum FCmpFlags { FCmpOrdered = 1 << 0, FCmpUnordered = 1 << 1, FCmpPS1 = 1 << 2, }; template static void fcmpGeneric(cpu::Core *state, Instruction instr) { double a, b; uint32_t c; if constexpr (!!(flags & FCmpPS1)) { a = state->fpr[instr.frA].paired1; b = state->fpr[instr.frB].paired1; } else { a = state->fpr[instr.frA].value; b = state->fpr[instr.frB].value; } const uint32_t oldFPSCR = state->fpscr.value; if (is_nan(a) || is_nan(b)) { c = ConditionRegisterFlag::Unordered; const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b); state->fpscr.vxsnan |= vxsnan; if constexpr (flags & FCmpOrdered) { if (!(vxsnan && state->fpscr.ve)) { state->fpscr.vxvc = 1; } } } else if (a < b) { c = ConditionRegisterFlag::LessThan; } else if (a > b) { c = ConditionRegisterFlag::GreaterThan; } else { // a == b c = ConditionRegisterFlag::Equal; } setCRF(state, instr.crfD, c); state->fpscr.fpcc = c; updateFX_FEX_VX(state, oldFPSCR); } static void fcmpo(cpu::Core *state, Instruction instr) { return fcmpGeneric(state, instr); } static void fcmpu(cpu::Core *state, Instruction instr) { return fcmpGeneric(state, instr); } static void ps_cmpo0(cpu::Core *state, Instruction instr) { return fcmpGeneric(state, instr); } static void ps_cmpo1(cpu::Core *state, Instruction instr) { return fcmpGeneric(state, instr); } static void ps_cmpu0(cpu::Core *state, Instruction instr) { return fcmpGeneric(state, instr); } static void ps_cmpu1(cpu::Core *state, Instruction instr) { return fcmpGeneric(state, instr); } // Condition Register AND static void crand(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = a & b; setCRB(state, instr.crbD, d); } // Condition Register AND with Complement static void crandc(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = a & ~b; setCRB(state, instr.crbD, d); } // Condition Register Equivalent static void creqv(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = ~(a ^ b); setCRB(state, instr.crbD, d); } // Condition Register NAND static void crnand(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = ~(a & b); setCRB(state, instr.crbD, d); } // Condition Register NOR static void crnor(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = ~(a | b); setCRB(state, instr.crbD, d); } // Condition Register OR static void cror(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = a | b; setCRB(state, instr.crbD, d); } // Condition Register OR with Complement static void crorc(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = a | ~b; setCRB(state, instr.crbD, d); } // Condition Register XOR static void crxor(cpu::Core *state, Instruction instr) { uint32_t a, b, d; a = getCRB(state, instr.crbA); b = getCRB(state, instr.crbB); d = a ^ b; setCRB(state, instr.crbD, d); } // Move Condition Register Field static void mcrf(cpu::Core *state, Instruction instr) { setCRF(state, instr.crfD, getCRF(state, instr.crfS)); } // Move to Condition Register from FPSCR static void mcrfs(cpu::Core *state, Instruction instr) { const int shiftS = 4 * (7 - instr.crfS); const uint32_t fpscrBits = (state->fpscr.value >> shiftS) & 0xF; setCRF(state, instr.crfD, fpscrBits); // All exception bits copied are cleared; other bits are left alone. // FEX and VX are updated following the normal rules. const uint32_t exceptionBits = FpscrFlags::FX | FpscrFlags::AllExceptions; const uint32_t bitsToClear = exceptionBits & (0xF << shiftS); state->fpscr.value &= ~bitsToClear; updateFEX_VX(state); } // Move to Condition Register from XER static void mcrxr(cpu::Core *state, Instruction instr) { setCRF(state, instr.crfD, state->xer.crxr); state->xer.crxr = 0; } // Move from Condition Register static void mfcr(cpu::Core *state, Instruction instr) { state->gpr[instr.rD] = state->cr.value; } // Move to Condition Register Fields static void mtcrf(cpu::Core *state, Instruction instr) { uint32_t cr, crm, s, mask; s = state->gpr[instr.rS]; cr = state->cr.value; crm = instr.crm; mask = 0; for (auto i = 0u; i < 8; ++i) { if (crm & (1 << i)) { mask |= 0xf << (i * 4); } } cr = (s & mask) | (cr & ~mask); state->cr.value = cr; } void cpu::interpreter::registerConditionInstructions() { RegisterInstruction(cmp); RegisterInstruction(cmpi); RegisterInstruction(cmpl); RegisterInstruction(cmpli); RegisterInstruction(fcmpo); RegisterInstruction(fcmpu); RegisterInstruction(crand); RegisterInstruction(crandc); RegisterInstruction(creqv); RegisterInstruction(crnand); RegisterInstruction(crnor); RegisterInstruction(cror); RegisterInstruction(crorc); RegisterInstruction(crxor); RegisterInstruction(mcrf); RegisterInstruction(mcrfs); RegisterInstruction(mcrxr); RegisterInstruction(mfcr); RegisterInstruction(mtcrf); RegisterInstruction(ps_cmpu0); RegisterInstruction(ps_cmpo0); RegisterInstruction(ps_cmpu1); RegisterInstruction(ps_cmpo1); } ================================================ FILE: src/libcpu/src/interpreter/interpreter_float.cpp ================================================ #include #include #include #include "../cpu_internal.h" #include "interpreter.h" #include "interpreter_float.h" #include "interpreter_insreg.h" #include #include #include using espresso::FpscrFlags; using espresso::FloatingPointResultFlags; using espresso::FloatingPointRoundMode; const uint16_t fres_base[] = { 0x3FFC, 0x3C1C, 0x3875, 0x3504, 0x31C4, 0x2EB1, 0x2BC8, 0x2904, 0x2664, 0x23E5, 0x2184, 0x1F40, 0x1D16, 0x1B04, 0x190A, 0x1725, 0x1554, 0x1396, 0x11EB, 0x104F, 0x0EC4, 0x0D48, 0x0BD7, 0x0A7C, 0x0922, 0x07DF, 0x069C, 0x056F, 0x0442, 0x0328, 0x020E, 0x0106, }; const uint16_t fres_delta[] = { 0x3E1, 0x3A7, 0x371, 0x340, 0x313, 0x2EA, 0x2C4, 0x2A0, 0x27F, 0x261, 0x245, 0x22A, 0x212, 0x1FB, 0x1E5, 0x1D1, 0x1BE, 0x1AC, 0x19B, 0x18B, 0x17C, 0x16E, 0x15B, 0x15B, 0x143, 0x143, 0x12D, 0x12D, 0x11A, 0x11A, 0x108, 0x106, }; const uint16_t frsqrte_base[] = { 0x7FF4, 0x7852, 0x7154, 0x6AE4, 0x64F2, 0x5F6E, 0x5A4C, 0x5580, 0x5102, 0x4CCA, 0x48D0, 0x450E, 0x4182, 0x3E24, 0x3AF2, 0x37E8, 0x34FD, 0x2F97, 0x2AA5, 0x2618, 0x21E4, 0x1DFE, 0x1A5C, 0x16F8, 0x13CA, 0x10CE, 0x0DFE, 0x0B57, 0x08D4, 0x0673, 0x0431, 0x020B, }; const uint16_t frsqrte_delta[] = { 0x7A4, 0x700, 0x670, 0x5F2, 0x584, 0x524, 0x4CC, 0x47E, 0x43A, 0x3FA, 0x3C2, 0x38E, 0x35E, 0x332, 0x30A, 0x2E6, 0x568, 0x4F3, 0x48D, 0x435, 0x3E7, 0x3A2, 0x365, 0x32E, 0x2FC, 0x2D0, 0x2A8, 0x283, 0x261, 0x243, 0x226, 0x20B, }; float ppc_estimate_reciprocal(float v) { auto bits = get_float_bits(v); // Check for an infinite or NaN input. if (bits.exponent == bits.exponent_max) { if (bits.mantissa == 0) { return std::copysign(0.0f, v); } else { std::feraiseexcept(FE_INVALID); return make_quiet(v); } } // Check for a zero or denormal input. int exponent = bits.exponent; uint32_t mantissa = bits.mantissa; if (exponent == 0) { if (mantissa == 0) { std::feraiseexcept(FE_DIVBYZERO); return std::copysign(std::numeric_limits::infinity(), v); } else if (mantissa < 0x200000) { std::feraiseexcept(FE_OVERFLOW | FE_INEXACT); return std::copysign(std::numeric_limits::max(), v); } else if (mantissa < 0x400000) { exponent = -1; mantissa = (mantissa << 2) & 0x7FFFFF; } else { mantissa = (mantissa << 1) & 0x7FFFFF; } } // Calculate the result. The lookup result has 24 bits; the high 23 bits // are copied to the mantissa of the output (except for denormals), and // the lowest bit is copied to FPSCR[FI]. int new_exponent = 253 - exponent; int table_index = mantissa >> 18; int delta_mult = (mantissa >> 8) & 0x3FF; uint32_t lookup_result = ((fres_base[table_index] << 10) - (fres_delta[table_index] * delta_mult)); uint32_t new_mantissa = lookup_result >> 1; bool fpscr_FI = lookup_result & 1; // Denormalize the result if necessary. if (new_exponent <= 0) { fpscr_FI |= new_mantissa & 1; new_mantissa = (new_mantissa >> 1) | 0x400000; if (new_exponent < 0) { fpscr_FI |= new_mantissa & 1; new_mantissa >>= 1; new_exponent = 0; } if (fpscr_FI) { std::feraiseexcept(FE_UNDERFLOW); } } if (fpscr_FI) { std::feraiseexcept(FE_INEXACT); } bits.exponent = new_exponent; bits.mantissa = new_mantissa; return bits.v; } double ppc_estimate_reciprocal_root(double v) { auto bits = get_float_bits(v); // Check for an infinite or NaN input. if (bits.exponent == bits.exponent_max) { if (bits.mantissa == 0) { if (bits.sign) { std::feraiseexcept(FE_INVALID); return make_nan(); } else { return 0.0; } } else { std::feraiseexcept(FE_INVALID); return make_quiet(v); } } // Check for a zero or denormal input. int exponent = bits.exponent; uint64_t mantissa = bits.mantissa; if (exponent == 0) { if (mantissa == 0) { std::feraiseexcept(FE_DIVBYZERO); return std::copysign(std::numeric_limits::infinity(), v); } else { int shift = clz64(mantissa) - 11; mantissa = (mantissa << shift) & UINT64_C(0xFFFFFFFFFFFFF); exponent -= shift - 1; } } // Negative nonzero values are always invalid. If we get this far, we // know the value is not zero or NaN. if (bits.sign) { std::feraiseexcept(FE_INVALID); return make_nan(); } // Calculate the result. int new_exponent = (3068 - exponent) / 2; int table_index = ((mantissa >> 48) & 15) | (exponent & 1 ? 0 : 16); int delta_mult = (mantissa >> 37) & 0x7FF; uint64_t lookup_result = ((frsqrte_base[table_index] << 11) - (frsqrte_delta[table_index] * delta_mult)); uint64_t new_mantissa = lookup_result << 26; bits.exponent = new_exponent; bits.mantissa = new_mantissa; return bits.v; } void updateFEX_VX(cpu::Core *state) { auto &fpscr = state->fpscr; // Invalid Operation Summary fpscr.vx = fpscr.vxsnan | fpscr.vxisi | fpscr.vxidi | fpscr.vxzdz | fpscr.vximz | fpscr.vxvc | fpscr.vxsqrt | fpscr.vxsoft | fpscr.vxcvi; // FP Enabled Exception Summary fpscr.fex = (fpscr.vx & fpscr.ve) | (fpscr.ox & fpscr.oe) | (fpscr.ux & fpscr.ue) | (fpscr.zx & fpscr.ze) | (fpscr.xx & fpscr.xe); } void updateFX_FEX_VX(cpu::Core *state, uint32_t oldValue) { auto &fpscr = state->fpscr; updateFEX_VX(state); // FP Exception Summary const uint32_t newBits = (oldValue ^ fpscr.value) & fpscr.value; if (newBits & FpscrFlags::AllExceptions) { fpscr.fx = 1; } } void updateFPSCR(cpu::Core *state, uint32_t oldValue) { auto except = std::fetestexcept(FE_ALL_EXCEPT); auto round = std::fegetround(); auto &fpscr = state->fpscr; // Underflow fpscr.ux |= !!(except & FE_UNDERFLOW); // Overflow fpscr.ox |= !!(except & FE_OVERFLOW); // Zerodivide fpscr.zx |= !!(except & FE_DIVBYZERO); // Inexact fpscr.fi = !!(except & FE_INEXACT); fpscr.xx |= fpscr.fi; // Fraction Rounded fpscr.fr = !!(round & FE_UPWARD); updateFX_FEX_VX(state, oldValue); std::feclearexcept(FE_ALL_EXCEPT); } template void updateFPRF(cpu::Core *state, Type value) { auto cls = std::fpclassify(value); auto flags = 0u; if (cls == FP_NAN) { flags |= FloatingPointResultFlags::ClassDescriptor; flags |= FloatingPointResultFlags::Unordered; } else if (value != 0) { if (value > 0) { flags |= FloatingPointResultFlags::Positive; } else { flags |= FloatingPointResultFlags::Negative; } if (cls == FP_INFINITE) { flags |= FloatingPointResultFlags::Unordered; } else if (cls == FP_SUBNORMAL) { flags |= FloatingPointResultFlags::ClassDescriptor; } } else { flags |= FloatingPointResultFlags::Equal; if (std::signbit(value)) { flags |= FloatingPointResultFlags::ClassDescriptor; } } state->fpscr.fprf = flags; } // Make sure both float and double versions are available to other sources: template void updateFPRF(cpu::Core *state, float value); template void updateFPRF(cpu::Core *state, double value); void updateFloatConditionRegister(cpu::Core *state) { state->cr.cr1 = state->fpscr.cr1; } // Helper for fmuls/fmadds to round the second (frC) operand appropriately. // May also need to modify the first operand, so both operands are passed // by reference. void roundForMultiply(double *a, double *c) { // The mantissa is truncated from 52 to 24 bits, so bit 27 (counting from // the LSB) is rounded. const uint64_t roundBit = UINT64_C(1) << 27; FloatBitsDouble aBits = get_float_bits(*a); FloatBitsDouble cBits = get_float_bits(*c); // If the second operand has no bits that would be rounded, this whole // function is a no-op, so skip out early. if (!(cBits.uv & ((roundBit << 1) - 1))) { return; } // If the first operand is zero, the result is always zero (even if the // second operand would round to infinity), so avoid generating any // exceptions. if (is_zero(*a)) { return; } // If the first operand is infinity and the second is not zero, the result // is always infinity; get out now so we don't have to worry about it in // normalization. if (is_infinity(*a)) { return; } // If the second operand is a denormal, we normalize it before rounding, // adjusting the exponent of the other operand accordingly. If the // other operand becomes denormal, the product will round to zero in any // case, so we just abort and let the operation proceed normally. if (is_denormal(*c)) { auto cSign = cBits.sign; while (cBits.exponent == 0) { cBits.uv <<= 1; if (aBits.exponent == 0) { return; } aBits.exponent--; } cBits.sign = cSign; } // Perform the rounding. If this causes the value to go to infinity, // we move a power of two to the other operand (if possible) for the // case of an FMA operation in which we need to keep precision for the // intermediate result. Note that this particular rounding operation // ignores FPSCR[RN]. cBits.uv &= -static_cast(roundBit); cBits.uv += cBits.uv & roundBit; if (is_infinity(cBits.v)) { cBits.exponent--; if (aBits.exponent == 0) { auto aSign = aBits.sign; aBits.uv <<= 1; aBits.sign = aSign; } else if (aBits.exponent < aBits.exponent_max - 1) { aBits.exponent++; } else { // The product will overflow anyway, so just leave the first // operand alone and let the host FPU raise exceptions as // appropriate. } } *a = aBits.v; *c = cBits.v; } // Helper for fmadds to properly round to single precision. Assumes // round-to-nearest mode. CLANG_FPU_BUG_WORKAROUND float roundFMAResultToSingle(double result, double a, double b, double c) { if (is_zero(a) || is_zero(b) || is_zero(c)) { return static_cast(result); // Can't lose precision if one addend is zero. } auto resultBits = get_float_bits(result); if (resultBits.exponent < 874 || resultBits.exponent > 1150) { return static_cast(result); // Out of range or inf/NaN. } uint64_t centerValue = 1<<28; uint64_t centerMask = (centerValue << 1) - 1; if (resultBits.exponent < 897) { centerValue <<= 897 - resultBits.exponent; centerMask <<= 897 - resultBits.exponent; } if ((resultBits.mantissa & centerMask) != centerValue) { return static_cast(result); // Not exactly between two single-precision values. } // Repeat the operation in round-toward-zero mode to determine which way // to round the result. const int oldRound = fegetround(); fesetround(FE_TOWARDZERO); feclearexcept(FE_INEXACT); double test = fma(a, c, b); fesetround(oldRound); // We only need to adjust the result if there were dropped bits. // If the truncated result is equal to the original (rounded) result, // it means the infinitely precise result is greater than the rounded // value, so we add 1 ulp (unit in the last place) to force the value // to round up when we convert it to float; likewise, if the truncated // result is different, we subtract 1 ulp to force rounding down. // In both cases, we know the result mantissa is nonzero so it's safe // to just increment or decrement the entire value as an integer. if (fetestexcept(FE_INEXACT)) { if (get_float_bits(test).uv == resultBits.uv) { resultBits.uv++; } else { resultBits.uv--; } } return static_cast(resultBits.v); } // Floating Arithmetic enum FPArithOperator { FPAdd, FPSub, FPMul, FPDiv, }; template static void fpArithGeneric(cpu::Core *state, Instruction instr) { double a, b; Type d; a = state->fpr[instr.frA].value; b = state->fpr[op == FPMul ? instr.frC : instr.frB].value; const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b); bool vxisi, vximz, vxidi, vxzdz, zx; if constexpr (op == FPAdd) { vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) != std::signbit(b); vximz = false; vxidi = false; vxzdz = false; zx = false; } else if constexpr (op == FPSub) { vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) == std::signbit(b); vximz = false; vxidi = false; vxzdz = false; zx = false; } else if constexpr (op == FPMul) { vxisi = false; vximz = (is_infinity(a) && is_zero(b)) || (is_zero(a) && is_infinity(b)); vxidi = false; vxzdz = false; zx = false; } else if constexpr (op == FPDiv) { vxisi = false; vximz = false; vxidi = is_infinity(a) && is_infinity(b); vxzdz = is_zero(a) && is_zero(b); zx = !(vxzdz || vxsnan) && is_zero(b); } else { static_assert("Unexpected flags for fpArithGeneric"); } const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan; state->fpscr.vxisi |= vxisi; state->fpscr.vximz |= vximz; state->fpscr.vxidi |= vxidi; state->fpscr.vxzdz |= vxzdz; if ((vxsnan || vxisi || vximz || vxidi || vxzdz) && state->fpscr.ve) { updateFX_FEX_VX(state, oldFPSCR); } else if (zx && state->fpscr.ze) { state->fpscr.zx = 1; updateFX_FEX_VX(state, oldFPSCR); } else { if (is_nan(a)) { d = static_cast(make_quiet(a)); } else if (is_nan(b)) { d = static_cast(make_quiet(b)); } else if (vxisi || vximz || vxidi || vxzdz) { d = make_nan(); } else { // The Espresso appears to use double precision arithmetic even for // single-precision instructions (for example, 2^128 * 0.5 does not // cause overflow), so we do the same here. if constexpr (op == FPAdd) { d = static_cast(a + b); } else if constexpr (op == FPSub) { d = static_cast(a - b); } else if constexpr (op == FPMul) { // But! The second operand to a single-precision multiply // operation is rounded to 24 bits. if constexpr (std::is_same::value) { roundForMultiply(&a, &b); } d = static_cast(a * b); } else if constexpr (op == FPDiv) { d = static_cast(a / b); } else { static_assert("Unexpected flags for fpArithGeneric"); } } // The PowerPC signals underflow if the result is tiny before rounding; // this differs from x86, which signals underflow only if the result // is tiny after rounding. Sadly, IEEE allows both of these behaviors, // so we can't just say that one is broken, and we have to handle this // case manually. If the rounded result is equal in magnitude to the // minimum normal value for the type, then the unrounded result may // have had a smaller magnitude, so we temporarily set the rounding // mode to round-toward-zero and repeat the operation, discarding the // result but allowing it to set the underflow flag if appropriate. // (In round-toward-zero mode, any value which is tiny before rounding // will also be tiny after rounding.) if (possibleUnderflow(d)) { const int oldRound = fegetround(); fesetround(FE_TOWARDZERO); // Use volatile variables to force the operation to be performed // and prevent the previous result from being reused. volatile double bTemp = b; volatile Type dummy; if constexpr (op == FPAdd) { dummy = static_cast(a + bTemp); } else if constexpr (op == FPSub) { dummy = static_cast(a - bTemp); } else if constexpr (op == FPMul) { // a and b have already been rounded if necessary. dummy = static_cast(a * bTemp); } else if constexpr (op == FPDiv) { dummy = static_cast(a / bTemp); } else { static_assert("Unexpected flags for fpArithGeneric"); } fesetround(oldRound); } if constexpr (std::is_same::value) { double dd = extend_float(d); state->fpr[instr.frD].paired0 = dd; state->fpr[instr.frD].paired1 = dd; updateFPRF(state, d); } else { state->fpr[instr.frD].value = d; updateFPRF(state, d); } updateFPSCR(state, oldFPSCR); } if (instr.rc) { updateFloatConditionRegister(state); } } // Floating Add Double static void fadd(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Add Single static void fadds(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Divide Double static void fdiv(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Divide Single static void fdivs(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Multiply Double static void fmul(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Multiply Single static void fmuls(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Subtract Double static void fsub(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Subtract Single static void fsubs(cpu::Core *state, Instruction instr) { fpArithGeneric(state, instr); } // Floating Reciprocal Estimate Single static void fres(cpu::Core *state, Instruction instr) { double b; float d; b = state->fpr[instr.frB].value; const bool vxsnan = is_signalling_nan(b); const bool zx = is_zero(b); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan; if (vxsnan && state->fpscr.ve) { updateFX_FEX_VX(state, oldFPSCR); } else if (zx && state->fpscr.ze) { state->fpscr.zx = 1; updateFX_FEX_VX(state, oldFPSCR); } else { d = ppc_estimate_reciprocal(static_cast(b)); state->fpr[instr.frD].paired0 = d; state->fpr[instr.frD].paired1 = d; updateFPRF(state, d); state->fpscr.zx |= zx; if (std::fetestexcept(FE_INEXACT)) { // On inexact result, fres sets FPSCR[FI] without also setting // FPSCR[XX]. std::feclearexcept(FE_INEXACT); updateFPSCR(state, oldFPSCR); state->fpscr.fi = 1; } else { updateFPSCR(state, oldFPSCR); } } if (instr.rc) { updateFloatConditionRegister(state); } } // Floating Reciprocal Square Root Estimate static void frsqrte(cpu::Core *state, Instruction instr) { double b, d; b = state->fpr[instr.frB].value; const bool vxsnan = is_signalling_nan(b); const bool vxsqrt = !vxsnan && b < 0.0; const bool zx = is_zero(b); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan; state->fpscr.vxsqrt |= vxsqrt; if ((vxsnan || vxsqrt) && state->fpscr.ve) { updateFX_FEX_VX(state, oldFPSCR); } else if (zx && state->fpscr.ze) { state->fpscr.zx = 1; updateFX_FEX_VX(state, oldFPSCR); } else { d = ppc_estimate_reciprocal_root(b); state->fpr[instr.frD].value = d; updateFPRF(state, d); state->fpscr.zx |= zx; updateFPSCR(state, oldFPSCR); } if (instr.rc) { updateFloatConditionRegister(state); } } static void fsel(cpu::Core *state, Instruction instr) { double a, b, c, d; a = state->fpr[instr.frA].value; b = state->fpr[instr.frB].value; c = state->fpr[instr.frC].value; if (a >= 0.0) { d = c; } else { d = b; } state->fpr[instr.frD].value = d; if (instr.rc) { updateFloatConditionRegister(state); } } // Fused multiply-add instructions enum FMAFlags { FMASubtract = 1 << 0, // Subtract instead of add FMANegate = 1 << 1, // Negate result FMASinglePrec = 1 << 2, // Round result to single precision }; template static void fmaGeneric(cpu::Core *state, Instruction instr) { double a, b, c, d; a = state->fpr[instr.frA].value; b = state->fpr[instr.frB].value; c = state->fpr[instr.frC].value; const double addend = (flags & FMASubtract) ? -b : b; const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b) || is_signalling_nan(c); const bool vximz = (is_infinity(a) && is_zero(c)) || (is_zero(a) && is_infinity(c)); const bool vxisi = (!vximz && !is_nan(a) && !is_nan(c) && (is_infinity(a) || is_infinity(c)) && is_infinity(b) && (std::signbit(a) ^ std::signbit(c)) != std::signbit(addend)); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan; state->fpscr.vxisi |= vxisi; state->fpscr.vximz |= vximz; if ((vxsnan || vxisi || vximz) && state->fpscr.ve) { updateFX_FEX_VX(state, oldFPSCR); } else { if (is_nan(a)) { d = make_quiet(a); } else if (is_nan(b)) { d = make_quiet(b); } else if (is_nan(c)) { d = make_quiet(c); } else if (vxisi || vximz) { d = make_nan(); } else { if constexpr (!!(flags & FMASinglePrec)) { roundForMultiply(&a, &c); } d = std::fma(a, c, addend); bool checkUnderflow; if constexpr (!!(flags & FMASinglePrec)) { checkUnderflow = possibleUnderflow(static_cast(d)); } else { checkUnderflow = possibleUnderflow(d); } if (checkUnderflow) { const int oldRound = fegetround(); fesetround(FE_TOWARDZERO); volatile double addendTemp = addend; if constexpr (!!(flags & FMASinglePrec)) { volatile float dummy; dummy = static_cast(std::fma(a, c, addendTemp)); } else { volatile double dummy; dummy = std::fma(a, c, addendTemp); } fesetround(oldRound); } if constexpr (!!(flags & FMANegate)) { d = -d; } } if constexpr (!!(flags & FMASinglePrec)) { float dFloat; if (state->fpscr.rn == FloatingPointRoundMode::Nearest) { dFloat = roundFMAResultToSingle(d, a, addend, c); } else { dFloat = static_cast(d); } d = extend_float(dFloat); state->fpr[instr.frD].paired0 = d; state->fpr[instr.frD].paired1 = d; updateFPRF(state, dFloat); } else { state->fpr[instr.frD].value = d; updateFPRF(state, d); } updateFPSCR(state, oldFPSCR); } if (instr.rc) { updateFloatConditionRegister(state); } } // Floating Multiply-Add static void fmadd(cpu::Core *state, Instruction instr) { return fmaGeneric<0>(state, instr); } // Floating Multiply-Add Single static void fmadds(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Floating Multiply-Sub static void fmsub(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Floating Multiply-Sub Single static void fmsubs(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Floating Negative Multiply-Add static void fnmadd(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Floating Negative Multiply-Add Single static void fnmadds(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Floating Negative Multiply-Sub static void fnmsub(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Floating Negative Multiply-Sub Single static void fnmsubs(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // fctiw/fctiwz common implementation static void fctiwGeneric(cpu::Core *state, Instruction instr, FloatingPointRoundMode roundMode) { double b; int32_t bi; b = state->fpr[instr.frB].value; const bool vxsnan = is_signalling_nan(b); bool vxcvi, fi; if (is_nan(b)) { vxcvi = true; fi = false; bi = INT_MIN; } else if (b > static_cast(INT_MAX)) { vxcvi = true; fi = false; bi = INT_MAX; } else if (b < static_cast(INT_MIN)) { vxcvi = true; fi = false; bi = INT_MIN; } else { vxcvi = false; switch (roundMode) { case FloatingPointRoundMode::Nearest: // We have to use nearbyint() instead of round() here, because // round() rounds 0.5 away from zero instead of to the nearest // even integer. nearbyint() is dependent on the host's FPU // rounding mode, but since that will reflect FPSCR here, it's // safe to use. bi = static_cast(std::nearbyint(b)); break; case FloatingPointRoundMode::Zero: bi = static_cast(std::trunc(b)); break; case FloatingPointRoundMode::Positive: bi = static_cast(std::ceil(b)); break; case FloatingPointRoundMode::Negative: bi = static_cast(std::floor(b)); break; default: decaf_abort("Unexpected floating point round mode"); } fi = get_float_bits(b).exponent < 1075 && bi != b; } const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan; state->fpscr.vxcvi |= vxcvi; if ((vxsnan || vxcvi) && state->fpscr.ve) { state->fpscr.fr = 0; state->fpscr.fi = 0; updateFX_FEX_VX(state, oldFPSCR); } else { state->fpr[instr.frD].iw1 = bi; state->fpr[instr.frD].iw0 = 0xFFF80000 | (is_negative_zero(b) ? 1 : 0); updateFPSCR(state, oldFPSCR); // We need to set FPSCR[FI] manually since the rounding functions // don't always raise inexact exceptions. if (fi) { state->fpscr.fi = 1; state->fpscr.xx = 1; updateFX_FEX_VX(state, oldFPSCR); } } if (instr.rc) { updateFloatConditionRegister(state); } } static void fctiw(cpu::Core *state, Instruction instr) { return fctiwGeneric(state, instr, static_cast(state->fpscr.rn)); } // Floating Convert to Integer Word with Round toward Zero static void fctiwz(cpu::Core *state, Instruction instr) { return fctiwGeneric(state, instr, FloatingPointRoundMode::Zero); } // Floating Round to Single static void frsp(cpu::Core *state, Instruction instr) { auto b = state->fpr[instr.frB].value; auto vxsnan = is_signalling_nan(b); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan; if (vxsnan && state->fpscr.ve) { updateFX_FEX_VX(state, oldFPSCR); } else { auto d = static_cast(b); state->fpr[instr.frD].paired0 = d; // frD(ps1) is left undefined in the 750CL manual, but the processor // actually copies the result to ps1 like other single-precision // instructions. state->fpr[instr.frD].paired1 = d; updateFPRF(state, d); updateFPSCR(state, oldFPSCR); } if (instr.rc) { updateFloatConditionRegister(state); } } // TODO: do fabs/fnabs/fneg behave like fmr w.r.t. paired singles? // Floating Absolute Value static void fabs(cpu::Core *state, Instruction instr) { uint64_t b, d; b = state->fpr[instr.frB].idw; d = clear_bit(b, 63); state->fpr[instr.frD].idw = d; if (instr.rc) { updateFloatConditionRegister(state); } } // Floating Negative Absolute Value static void fnabs(cpu::Core *state, Instruction instr) { uint64_t b, d; b = state->fpr[instr.frB].idw; d = set_bit(b, 63); state->fpr[instr.frD].idw = d; if (instr.rc) { updateFloatConditionRegister(state); } } // Floating Move Register static void fmr(cpu::Core *state, Instruction instr) { state->fpr[instr.frD].idw = state->fpr[instr.frB].idw; if (instr.rc) { updateFloatConditionRegister(state); } } // Floating Negate static void fneg(cpu::Core *state, Instruction instr) { uint64_t b, d; b = state->fpr[instr.frB].idw; d = flip_bit(b, 63); state->fpr[instr.frD].idw = d; if (instr.rc) { updateFloatConditionRegister(state); } } // Move from FPSCR static void mffs(cpu::Core *state, Instruction instr) { state->fpr[instr.frD].iw1 = state->fpscr.value; if (instr.rc) { updateFloatConditionRegister(state); } } // Move to FPSCR Bit 0 static void mtfsb0(cpu::Core *state, Instruction instr) { state->fpscr.value = clear_bit(state->fpscr.value, 31 - instr.crbD); updateFEX_VX(state); if (instr.crbD >= 30) { cpu::this_core::updateRoundingMode(); } if (instr.rc) { updateFloatConditionRegister(state); } } // Move to FPSCR Bit 1 static void mtfsb1(cpu::Core *state, Instruction instr) { const uint32_t oldValue = state->fpscr.value; state->fpscr.value = set_bit(state->fpscr.value, 31 - instr.crbD); updateFX_FEX_VX(state, oldValue); if (instr.crbD >= 30) { cpu::this_core::updateRoundingMode(); } if (instr.rc) { updateFloatConditionRegister(state); } } // Move to FPSCR Fields static void mtfsf(cpu::Core *state, Instruction instr) { const uint32_t value = state->fpr[instr.frB].iw1; for (int field = 0; field < 8; field++) { // Technically field 0 is at the high end, but as long as the bit // position in the mask and the field we operate on match up, it // doesn't matter which direction we go in. So we use host bit // order for simplicity. if (get_bit(instr.fm, field)) { const uint32_t mask = make_bitmask(4 * field, 4 * field + 3); state->fpscr.value &= ~mask; state->fpscr.value |= value & mask; } } updateFEX_VX(state); if (get_bit(instr.fm, 0)) { cpu::this_core::updateRoundingMode(); } if (instr.rc) { updateFloatConditionRegister(state); } } // Move to FPSCR Field Immediate static void mtfsfi(cpu::Core *state, Instruction instr) { const int shift = 4 * (7 - instr.crfD); state->fpscr.value &= ~(0xF << shift); state->fpscr.value |= instr.imm << shift; updateFEX_VX(state); if (instr.crfD == 7) { cpu::this_core::updateRoundingMode(); } if (instr.rc) { updateFloatConditionRegister(state); } } void cpu::interpreter::registerFloatInstructions() { RegisterInstruction(fadd); RegisterInstruction(fadds); RegisterInstruction(fdiv); RegisterInstruction(fdivs); RegisterInstruction(fmul); RegisterInstruction(fmuls); RegisterInstruction(fsub); RegisterInstruction(fsubs); RegisterInstruction(fres); RegisterInstruction(frsqrte); RegisterInstruction(fsel); RegisterInstruction(fmadd); RegisterInstruction(fmadds); RegisterInstruction(fmsub); RegisterInstruction(fmsubs); RegisterInstruction(fnmadd); RegisterInstruction(fnmadds); RegisterInstruction(fnmsub); RegisterInstruction(fnmsubs); RegisterInstruction(fctiw); RegisterInstruction(fctiwz); RegisterInstruction(frsp); RegisterInstruction(fabs); RegisterInstruction(fnabs); RegisterInstruction(fmr); RegisterInstruction(fneg); RegisterInstruction(mffs); RegisterInstruction(mtfsb0); RegisterInstruction(mtfsb1); RegisterInstruction(mtfsf); RegisterInstruction(mtfsfi); } ================================================ FILE: src/libcpu/src/interpreter/interpreter_float.h ================================================ #pragma once #include #include "../state.h" template static inline bool possibleUnderflow(Type v) { auto bits = get_float_bits(v); return bits.exponent == 1 && bits.mantissa == 0; } float ppc_estimate_reciprocal(float v); double ppc_estimate_reciprocal_root(double v); void updateFEX_VX(cpu::Core *state); void updateFX_FEX_VX(cpu::Core *state, uint32_t oldValue); void updateFPSCR(cpu::Core *state, uint32_t oldValue); template void updateFPRF(cpu::Core *state, Type value); void updateFloatConditionRegister(cpu::Core *state); void roundForMultiply(double *a, double *c); float roundFMAResultToSingle(double result, double a, double b, double c); template Type getFpr(cpu::Core *state, unsigned fr); template<> float getFpr(cpu::Core *state, unsigned fr); template<> double getFpr(cpu::Core *state, unsigned fr); void setFpr(cpu::Core *state, unsigned fr, float value); void setFpr(cpu::Core *state, unsigned fr, double value); ================================================ FILE: src/libcpu/src/interpreter/interpreter_insreg.h ================================================ #pragma once #include "../state.h" #include "../espresso/espresso_instruction.h" #include "../espresso/espresso_instructionid.h" // TODO: Remove me using espresso::Instruction; using espresso::InstructionID; namespace cpu { namespace interpreter { using instrfptr_t = void(*)(Core*, Instruction); bool hasInstruction(espresso::InstructionID instrId); instrfptr_t getInstructionHandler(espresso::InstructionID id); void registerInstruction(espresso::InstructionID id, instrfptr_t fptr); void registerBranchInstructions(); void registerConditionInstructions(); void registerFloatInstructions(); void registerIntegerInstructions(); void registerLoadStoreInstructions(); void registerPairedInstructions(); void registerSystemInstructions(); } // namespace interpreter } // namespace cpu #undef RegisterInstruction #undef RegisterInstructionFn #define RegisterInstruction(x) \ cpu::interpreter::registerInstruction(espresso::InstructionID::x, &x) #define RegisterInstructionFn(x, fn) \ cpu::interpreter::registerInstruction(espresso::InstructionID::x, &fn) ================================================ FILE: src/libcpu/src/interpreter/interpreter_integer.cpp ================================================ #include #include "interpreter_insreg.h" #include using espresso::ConditionRegisterFlag; // Update cr0 with value static void updateConditionRegister(cpu::Core *state, uint32_t value) { auto flags = 0; if (value == 0) { flags |= ConditionRegisterFlag::Zero; } else if (get_bit<31>(value)) { flags |= ConditionRegisterFlag::Negative; } else { flags |= ConditionRegisterFlag::Positive; } if (state->xer.so) { flags |= ConditionRegisterFlag::SummaryOverflow; } state->cr.cr0 = flags; } // Update carry flags static void updateCarry(cpu::Core *state, bool carry) { state->xer.ca = carry; } // Update overflow flags static void updateOverflow(cpu::Core *state, bool overflow) { state->xer.ov = overflow; state->xer.so |= overflow; } // Add enum AddFlags { AddCarry = 1 << 0, // xer[ca] = carry AddExtended = 1 << 1, // d = a + b + xer[ca] AddImmediate = 1 << 2, // d = a + simm AddCheckRecord = 1 << 3, // Check rc and oe then update cr0 and xer AddAlwaysRecord = 1 << 4, // Always update cr0 and xer AddShifted = 1 << 5, // d = a + (b << 16) AddToZero = 1 << 6, // d = a + 0 AddToMinusOne = 1 << 7, // d = a + -1 AddZeroRA = 1 << 8, // a = (rA == 0) ? 0 : gpr[rA] AddSubtract = 1 << 9, // a = ~a and +1 when not carry }; template static void addGeneric(cpu::Core *state, Instruction instr) { uint32_t a, b, d; // Get a value if constexpr (!!(flags & AddZeroRA)) { if (instr.rA == 0) { a = 0; } else { a = state->gpr[instr.rA]; } } else { a = state->gpr[instr.rA]; } if constexpr (!!(flags & AddSubtract)) { a = ~a; } // Get b value if constexpr (!!(flags & AddImmediate)) { b = sign_extend<16>(instr.simm); } else if constexpr (!!(flags & AddToZero)) { b = 0; } else if constexpr (!!(flags & AddToMinusOne)) { b = static_cast(-1); } else { b = state->gpr[instr.rB]; } if constexpr (!!(flags & AddShifted)) { b <<= 16; } // Calculate d d = a + b; // Add xer[ca] if needed if constexpr (!!(flags & AddExtended)) { d += state->xer.ca; } else if constexpr (!!(flags & AddSubtract)) { d += 1; } // Update rD state->gpr[instr.rD] = d; if constexpr (!!(flags & AddCarry)) { auto carry = d < a || (d == a && b > 0); updateCarry(state, carry); } if constexpr (!!(flags & AddAlwaysRecord)) { // Always record only means update CR0, NOT overflow updateConditionRegister(state, d); } else if constexpr (!!(flags & AddCheckRecord)) { if (instr.oe) { auto overflow = !!get_bit<31>((a ^ d) & (b ^ d)); updateOverflow(state, overflow); } if (instr.rc) { updateConditionRegister(state, d); } } } static void add(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addc(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void adde(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addi(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addic(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addicx(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addis(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addme(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } static void addze(cpu::Core *state, Instruction instr) { return addGeneric(state, instr); } // AND enum AndFlags { AndComplement = 1 << 0, // b = ~b AndCheckRecord = 1 << 1, // Check rc then update cr AndImmediate = 1 << 2, // b = uimm AndShifted = 1 << 3, // b = (b << 16) AndAlwaysRecord = 1 << 4, // Always update cr }; template static void andGeneric(cpu::Core *state, Instruction instr) { uint32_t s, a, b; s = state->gpr[instr.rS]; if constexpr (!!(flags & AndImmediate)) { b = instr.uimm; } else { b = state->gpr[instr.rB]; } if constexpr (!!(flags & AndShifted)) { b <<= 16; } if constexpr (!!(flags & AndComplement)) { b = ~b; } a = s & b; state->gpr[instr.rA] = a; if constexpr (!!(flags & AndAlwaysRecord)) { updateConditionRegister(state, a); } else if constexpr (!!(flags & AndCheckRecord)) { if (instr.rc) { updateConditionRegister(state, a); } } } static void and_(cpu::Core *state, Instruction instr) { return andGeneric(state, instr); } static void andc(cpu::Core *state, Instruction instr) { return andGeneric(state, instr); } static void andi(cpu::Core *state, Instruction instr) { return andGeneric(state, instr); } static void andis(cpu::Core *state, Instruction instr) { return andGeneric(state, instr); } // Count Leading Zeroes Word static void cntlzw(cpu::Core *state, Instruction instr) { unsigned long a; uint32_t s; s = state->gpr[instr.rS]; if (!bit_scan_reverse(&a, s)) { a = 32; } else { a = 31 - a; } state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Divide template static void divGeneric(cpu::Core *state, Instruction instr) { Type a, b, d; a = state->gpr[instr.rA]; b = state->gpr[instr.rB]; auto overflow = (b == 0); if constexpr (std::is_signed::value) { if (a == 0x80000000 && b == -1) { overflow = true; } } if (!overflow) { d = a / b; } else { // rD = -1 when rA is negative, 0 when rA is positive d = a < 0 ? -1 : 0; } state->gpr[instr.rD] = d; if (instr.oe) { updateOverflow(state, overflow); } if (instr.rc) { updateConditionRegister(state, d); } } static void divw(cpu::Core *state, Instruction instr) { return divGeneric(state, instr); } static void divwu(cpu::Core *state, Instruction instr) { return divGeneric(state, instr); } // Equivalent static void eqv(cpu::Core *state, Instruction instr) { uint32_t a, s, b; s = state->gpr[instr.rS]; b = state->gpr[instr.rB]; a = ~(s ^ b); state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Extend Sign Byte static void extsb(cpu::Core *state, Instruction instr) { uint32_t a, s; s = state->gpr[instr.rS]; a = sign_extend<8>(s); state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Extend Sign Half Word static void extsh(cpu::Core *state, Instruction instr) { uint32_t a, s; s = state->gpr[instr.rS]; a = sign_extend<16>(s); state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Multiply enum MulFlags { MulLow = 1 << 0, // (uint32_t)d MulHigh = 1 << 1, // d >> 32 MulImmediate = 1 << 2, // b = simm MulCheckOverflow = 1 << 3, // Check oe then update xer MulCheckRecord = 1 << 4, // Check rc then update cr }; // Signed multiply template static void mulSignedGeneric(cpu::Core *state, Instruction instr) { int64_t a, b; int32_t d; a = static_cast(state->gpr[instr.rA]); if constexpr (!!(flags & MulImmediate)) { b = sign_extend<16>(instr.simm); } else { b = static_cast(state->gpr[instr.rB]); } const int64_t product = a * b; if constexpr (!!(flags & MulLow)) { d = static_cast(product); } else if constexpr (!!(flags & MulHigh)) { d = product >> 32; // oe is ignored for mulhw* instructions. } else { static_assert("Unexpected flags for mulSignedGeneric"); } state->gpr[instr.rD] = d; if constexpr (!!(flags & MulCheckOverflow)) { bool overflow; if constexpr (!!(flags & MulLow)) { overflow = (product < INT64_C(-0x80000000) || product > 0x7FFFFFFF); } else { static_assert("Unexpected flags for mulSignedGeneric"); } if (instr.oe) { updateOverflow(state, overflow); } } if constexpr (!!(flags & MulCheckRecord)) { if (instr.rc) { updateConditionRegister(state, d); } } } // Unsigned multiply template static void mulUnsignedGeneric(cpu::Core *state, Instruction instr) { uint64_t a, b; uint32_t d; a = state->gpr[instr.rA]; b = state->gpr[instr.rB]; if constexpr (!!(flags & MulLow)) { d = static_cast(a * b); } else if constexpr (!!(flags & MulHigh)) { d = (a * b) >> 32; } else { static_assert("Unexpected flags for mulUnsignedGeneric"); } state->gpr[instr.rD] = d; if constexpr (!!(flags & MulCheckRecord)) { if (instr.rc) { updateConditionRegister(state, d); } } } static void mulhw(cpu::Core *state, Instruction instr) { return mulSignedGeneric(state, instr); } static void mulhwu(cpu::Core *state, Instruction instr) { return mulUnsignedGeneric(state, instr); } static void mulli(cpu::Core *state, Instruction instr) { return mulSignedGeneric(state, instr); } static void mullw(cpu::Core *state, Instruction instr) { return mulSignedGeneric(state, instr); } // NAND static void nand(cpu::Core *state, Instruction instr) { uint32_t a, s, b; s = state->gpr[instr.rS]; b = state->gpr[instr.rB]; a = ~(s & b); state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Negate static void neg(cpu::Core *state, Instruction instr) { uint32_t a, d; a = state->gpr[instr.rA]; d = (~a) + 1; state->gpr[instr.rD] = d; bool overflow = (a == 0x80000000); if (instr.oe) { updateOverflow(state, overflow); } if (instr.rc) { updateConditionRegister(state, d); } } // NOR static void nor(cpu::Core *state, Instruction instr) { uint32_t a, s, b; s = state->gpr[instr.rS]; b = state->gpr[instr.rB]; a = ~(s | b); state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // OR enum OrFlags { OrComplement = 1 << 0, // b = ~b OrCheckRecord = 1 << 1, // Check rc then update cr OrImmediate = 1 << 2, // b = uimm OrShifted = 1 << 3, // b = (b << 16) OrAlwaysRecord = 1 << 4, // Always update cr }; template static void orGeneric(cpu::Core *state, Instruction instr) { uint32_t s, a, b; s = state->gpr[instr.rS]; if constexpr (!!(flags & OrImmediate)) { b = instr.uimm; } else { b = state->gpr[instr.rB]; } if constexpr (!!(flags & OrShifted)) { b <<= 16; } if constexpr (!!(flags & OrComplement)) { b = ~b; } a = s | b; state->gpr[instr.rA] = a; if constexpr (!!(flags & OrAlwaysRecord)) { updateConditionRegister(state, a); } else if constexpr (!!(flags & OrCheckRecord)) { if (instr.rc) { updateConditionRegister(state, a); } } } static void or_(cpu::Core *state, Instruction instr) { return orGeneric(state, instr); } static void orc(cpu::Core *state, Instruction instr) { return orGeneric(state, instr); } static void ori(cpu::Core *state, Instruction instr) { return orGeneric(state, instr); } static void oris(cpu::Core *state, Instruction instr) { return orGeneric(state, instr); } // Rotate left word enum RlwFlags { RlwImmediate = 1 << 0, // n = sh RlwAnd = 1 << 1, // a = r & m RlwInsert = 1 << 2 // a = (r & m) | (r & ~m) }; template static void rlwGeneric(cpu::Core *state, Instruction instr) { uint32_t s, n, r, m, a; s = state->gpr[instr.rS]; a = state->gpr[instr.rA]; if constexpr (!!(flags & RlwImmediate)) { n = instr.sh; } else { n = state->gpr[instr.rB] & 0x1f; } r = bit_rotate_left(s, n); m = make_ppc_bitmask(instr.mb, instr.me); if constexpr (!!(flags & RlwAnd)) { a = (r & m); } else if constexpr (!!(flags & RlwInsert)) { a = (r & m) | (a & ~m); } state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Rotate Left Word Immediate then Mask Insert static void rlwimi(cpu::Core *state, Instruction instr) { return rlwGeneric(state, instr); } // Rotate Left Word Immediate then AND with Mask static void rlwinm(cpu::Core *state, Instruction instr) { return rlwGeneric(state, instr); } // Rotate Left Word then AND with Mask static void rlwnm(cpu::Core *state, Instruction instr) { return rlwGeneric(state, instr); } // Shift Logical enum ShiftFlags { ShiftLeft = 1 << 0, ShiftRight = 1 << 1, ShiftImmediate = 1 << 2, }; template static void shiftLogical(cpu::Core *state, Instruction instr) { uint32_t n, s, b, a; s = state->gpr[instr.rS]; if constexpr (!!(flags & ShiftImmediate)) { b = instr.sh; } else { b = state->gpr[instr.rB]; } n = b & 0x1f; if (b & 0x20) { a = 0; } else { if constexpr (!!(flags & ShiftLeft)) { a = s << n; } else if constexpr (!!(flags & ShiftRight)) { a = s >> n; } else { static_assert("Unexpected flags for shiftLogical"); } } state->gpr[instr.rA] = a; if (instr.rc) { updateConditionRegister(state, a); } } // Shift Left Word static void slw(cpu::Core *state, Instruction instr) { shiftLogical(state, instr); } // Shift Right Word static void srw(cpu::Core *state, Instruction instr) { shiftLogical(state, instr); } // Shift Arithmetic template static void shiftArithmetic(cpu::Core *state, Instruction instr) { static_assert(flags & ShiftRight, "Shift Arithmetic only supports ShiftRight"); int32_t s, a, n, b; s = state->gpr[instr.rS]; if constexpr (!!(flags & ShiftImmediate)) { b = instr.sh; } else { b = state->gpr[instr.rB]; } bool carry = false; if (b & 0x20) { if (s >= 0) { a = 0; } else { a = -1; carry = true; } } else { n = b & 0x1f; if (n == 0) { a = s; } else { a = s >> n; if ((s < 0) && (s << (32 - n))) { carry = true; } } } state->gpr[instr.rA] = a; updateCarry(state, carry); if (instr.rc) { updateConditionRegister(state, a); } } static void sraw(cpu::Core *state, Instruction instr) { shiftArithmetic(state, instr); } static void srawi(cpu::Core *state, Instruction instr) { shiftArithmetic(state, instr); } // Because sub is rD = ~rA + rB + 1 we can reuse our generic add static void subf(cpu::Core *state, Instruction instr) { addGeneric(state, instr); } static void subfc(cpu::Core *state, Instruction instr) { addGeneric(state, instr); } static void subfe(cpu::Core *state, Instruction instr) { addGeneric(state, instr); } static void subfic(cpu::Core *state, Instruction instr) { addGeneric(state, instr); } static void subfme(cpu::Core *state, Instruction instr) { addGeneric(state, instr); } static void subfze(cpu::Core *state, Instruction instr) { addGeneric(state, instr); } // XOR enum XorFlags { XorCheckRecord = 1 << 1, // Check rc then update cr XorImmediate = 1 << 2, // b = uimm XorShifted = 1 << 3, // b = (b << 16) }; template static void xorGeneric(cpu::Core *state, Instruction instr) { uint32_t s, a, b; s = state->gpr[instr.rS]; if constexpr (!!(flags & XorImmediate)) { b = instr.uimm; } else { b = state->gpr[instr.rB]; } if constexpr (!!(flags & XorShifted)) { b <<= 16; } a = s ^ b; state->gpr[instr.rA] = a; if constexpr (!!(flags & XorCheckRecord)) { if (instr.rc) { updateConditionRegister(state, a); } } } static void xor_(cpu::Core *state, Instruction instr) { return xorGeneric(state, instr); } static void xori(cpu::Core *state, Instruction instr) { return xorGeneric(state, instr); } static void xoris(cpu::Core *state, Instruction instr) { return xorGeneric(state, instr); } void cpu::interpreter::registerIntegerInstructions() { RegisterInstruction(add); RegisterInstruction(addc); RegisterInstruction(adde); RegisterInstruction(addi); RegisterInstruction(addic); RegisterInstruction(addicx); RegisterInstruction(addis); RegisterInstruction(addme); RegisterInstruction(addze); RegisterInstruction(and_); RegisterInstruction(andc); RegisterInstruction(andi); RegisterInstruction(andis); RegisterInstruction(cntlzw); RegisterInstruction(divw); RegisterInstruction(divwu); RegisterInstruction(extsb); RegisterInstruction(extsh); RegisterInstruction(eqv); RegisterInstruction(mulhw); RegisterInstruction(mulhwu); RegisterInstruction(mulli); RegisterInstruction(mullw); RegisterInstruction(nand); RegisterInstruction(neg); RegisterInstruction(nor); RegisterInstruction(or_); RegisterInstruction(orc); RegisterInstruction(ori); RegisterInstruction(oris); RegisterInstruction(rlwimi); RegisterInstruction(rlwinm); RegisterInstruction(rlwnm); RegisterInstruction(srw); RegisterInstruction(slw); RegisterInstruction(sraw); RegisterInstruction(srawi); RegisterInstruction(subf); RegisterInstruction(subf); RegisterInstruction(subfc); RegisterInstruction(subfe); RegisterInstruction(subfic); RegisterInstruction(subfme); RegisterInstruction(subfze); RegisterInstruction(xor_); RegisterInstruction(xori); RegisterInstruction(xoris); } ================================================ FILE: src/libcpu/src/interpreter/interpreter_internal.h ================================================ ================================================ FILE: src/libcpu/src/interpreter/interpreter_loadstore.cpp ================================================ #include "interpreter_float.h" #include "interpreter_insreg.h" #include "mem.h" #include #include #include #include #include #include #include #include using espresso::QuantizedDataType; using espresso::ConditionRegisterFlag; // Load enum LoadFlags { LoadUpdate = 1 << 0, // Save EA in rA LoadIndexed = 1 << 1, // Use rB instead of d LoadSignExtend = 1 << 2, // Sign extend LoadByteReverse = 1 << 3, // Swap bytes LoadReserve = 1 << 4, // lwarx hardware reserve LoadZeroRA = 1 << 5, // Use 0 instead of r0 }; static double convertFloatToDouble(float f) { if (!is_signalling_nan(f)) { return static_cast(f); } else { return extend_float(f); } } static double loadFloatAsDouble(uint32_t ea) { return convertFloatToDouble(mem::read(ea)); } template static void loadFloat(cpu::Core *state, Instruction instr) { uint32_t ea; if constexpr (!!(flags & LoadZeroRA)) { if (instr.rA == 0) { ea = 0; } else { ea = state->gpr[instr.rA]; } } else { ea = state->gpr[instr.rA]; } if constexpr (!!(flags & LoadIndexed)) { ea += state->gpr[instr.rB]; } else { ea += sign_extend<16, int32_t>(instr.d); } const float f = mem::read(ea); state->fpr[instr.rD].paired0 = convertFloatToDouble(f); state->fpr[instr.rD].paired1 = convertFloatToDouble(f); if constexpr (!!(flags & LoadUpdate)) { state->gpr[instr.rA] = ea; } } template static void loadGeneric(cpu::Core *state, Instruction instr) { uint32_t ea; Type d; if constexpr (!!(flags & LoadZeroRA)) { if (instr.rA == 0) { ea = 0; } else { ea = state->gpr[instr.rA]; } } else { ea = state->gpr[instr.rA]; } if constexpr (!!(flags & LoadIndexed)) { ea += state->gpr[instr.rB]; } else { ea += sign_extend<16, int32_t>(instr.d); } Type memd = mem::readNoSwap(ea); if constexpr (!!(flags & LoadByteReverse)) { d = memd; } else { d = byte_swap(memd); } if constexpr (!!(flags & LoadReserve)) { static_assert(!(flags & LoadReserve) || sizeof(Type) == 4, "Reserved reads are only valid on 32-bit values"); state->reserveFlag = true; state->reserveData = *reinterpret_cast(&memd); } if constexpr (std::is_floating_point::value) { state->fpr[instr.rD].value = static_cast(d); // Normally lfd instructions do not modify the second paired-single // slot (ps1) of an FPR, but if the lfd is immediately preceded or // followed by paired-single instructions, frD(ps1) may receive the // high 32 bits of the loaded word or some other value that happens // to be stored in the FP unit. This data hazard is strongly // dependent on pipeline state, and we don't attempt to emulate it. // (Using a double-precision value with paired-single instructions // is documented to result in undefined behavior anyway, so it seems // unlikely this sort of instruction sequence would be found in real // software.) } else { if constexpr (!!(flags & LoadSignExtend)) { state->gpr[instr.rD] = static_cast(sign_extend::value, uint64_t>(static_cast(d))); } else { state->gpr[instr.rD] = static_cast(d); } } if constexpr (!!(flags & LoadUpdate)) { state->gpr[instr.rA] = ea; } } static void lbz(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lbzu(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lbzux(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lbzx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lha(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhau(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhaux(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhax(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhbrx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhz(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhzu(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhzux(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lhzx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lwbrx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lwarx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lwz(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lwzu(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lwzux(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lwzx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lfs(cpu::Core *state, Instruction instr) { return loadFloat(state, instr); } static void lfsu(cpu::Core *state, Instruction instr) { return loadFloat(state, instr); } static void lfsux(cpu::Core *state, Instruction instr) { return loadFloat(state, instr); } static void lfsx(cpu::Core *state, Instruction instr) { return loadFloat(state, instr); } static void lfd(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lfdu(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lfdux(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } static void lfdx(cpu::Core *state, Instruction instr) { return loadGeneric(state, instr); } // Load Multiple Words // Fills registers from rD to r31 with consecutive words from memory static void lmw(cpu::Core *state, Instruction instr) { uint32_t b, ea, r; if (instr.rA) { b = state->gpr[instr.rA]; } else { b = 0; } ea = b + sign_extend<16, int32_t>(instr.d); for (r = instr.rD; r <= 31; ++r, ea += 4) { state->gpr[r] = mem::read(ea); } } // Load String Word (byte-by-byte version of lmw) enum LswFlags { LswIndexed = 1 >> 0, }; template static void lswGeneric(cpu::Core *state, Instruction instr) { uint32_t ea, i, n, r; ea = instr.rA ? state->gpr[instr.rA] : 0; if constexpr (!!(flags & LswIndexed)) { ea += state->gpr[instr.rB]; n = state->xer.byteCount; } else { n = instr.nb ? instr.nb : 32; } r = instr.rD - 1; i = 0; while (n > 0) { if (i == 0) { r = (r + 1) % 32; state->gpr[r] = 0; } state->gpr[r] |= mem::read(ea) << (24 - i); i = (i + 8) % 32; ea = ea + 1; n = n - 1; } } static void lswi(cpu::Core *state, Instruction instr) { lswGeneric(state, instr); } static void lswx(cpu::Core *state, Instruction instr) { lswGeneric(state, instr); } // Store enum StoreFlags { StoreUpdate = 1 << 0, // Save EA in rA StoreIndexed = 1 << 1, // Use rB instead of d StoreByteReverse = 1 << 2, // Swap Bytes StoreConditional = 1 << 3, // lwarx/stwcx Conditional StoreZeroRA = 1 << 4, // Use 0 instead of r0 StoreFloatAsInteger = 1 << 5, // stfiwx }; static void storeDoubleAsFloat(uint32_t ea, double d) { auto dBits = get_float_bits(d); uint32_t fBits; if (dBits.exponent > 896 || dBits.uv << 1 == 0) { // Not truncate_double()! See the PowerPC documentation. fBits = truncate_double_bits(dBits.uv); } else { fBits = static_cast(((1u << 23) | (dBits.mantissa >> 29)) >> (897 - dBits.exponent)); fBits |= dBits.sign << 31; } mem::write(ea, fBits); } template static void storeFloat(cpu::Core *state, Instruction instr) { uint32_t ea; if constexpr (!!(flags & StoreZeroRA)) { if (instr.rA == 0) { ea = 0; } else { ea = state->gpr[instr.rA]; } } else { ea = state->gpr[instr.rA]; } if constexpr (!!(flags & StoreIndexed)) { ea += state->gpr[instr.rB]; } else { ea += sign_extend<16, int32_t>(instr.d); } const double d = state->fpr[instr.rS].value; storeDoubleAsFloat(ea, d); if constexpr (!!(flags & StoreUpdate)) { state->gpr[instr.rA] = ea; } } template struct ReservedWrite { }; template<> struct ReservedWrite { template static inline bool write(cpu::Core *state, uint32_t ea, Type s) { static_assert(sizeof(Type) == 4, "Reserved writes are only valid on 32-bit values"); static_assert(sizeof(std::atomic) == sizeof(Type), "Non-locking reserve relies on zero-overhead atomics"); auto atomicPtr = reinterpret_cast*>(mem::translate(ea)); state->cr.cr0 = state->xer.so ? ConditionRegisterFlag::SummaryOverflow : 0; bool reserveFlag = state->reserveFlag; state->reserveFlag = false; if (!reserveFlag) { // The processor did not have a reservation return false; } auto reserveData = state->reserveData; if (!atomicPtr->compare_exchange_strong(reserveData, s)) { // The data has been modified since it was reserved. return false; } // Store was succesful, set CR0[EQ] state->cr.cr0 |= ConditionRegisterFlag::Equal; return true; } }; template<> struct ReservedWrite { template static inline bool write(cpu::Core *state, uint32_t ea, Type s) { mem::writeNoSwap(ea, s); return true; } }; template static void storeGeneric(cpu::Core *state, Instruction instr) { uint32_t ea; Type s; if constexpr (!!(flags & StoreZeroRA)) { if (instr.rA == 0) { ea = 0; } else { ea = state->gpr[instr.rA]; } } else { ea = state->gpr[instr.rA]; } if constexpr (!!(flags & StoreIndexed)) { ea += state->gpr[instr.rB]; } else { ea += sign_extend<16, int32_t>(instr.d); } if constexpr (!!(flags & StoreFloatAsInteger)) { s = static_cast(state->fpr[instr.rS].iw1); } else if constexpr (std::is_floating_point::value) { s = static_cast(state->fpr[instr.rS].value); } else { s = static_cast(state->gpr[instr.rS]); } if constexpr (!(flags & StoreByteReverse)) { s = byte_swap(s); } if (!ReservedWrite<(flags & StoreConditional) != 0>::write(state, ea, s)) { return; } if constexpr (!!(flags & StoreUpdate)) { state->gpr[instr.rA] = ea; } } static void stb(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stbu(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stbux(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stbx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void sth(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void sthu(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void sthux(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void sthx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stw(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stwu(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stwux(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stwx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void sthbrx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stwbrx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stwcx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stfs(cpu::Core *state, Instruction instr) { storeFloat(state, instr); } static void stfsu(cpu::Core *state, Instruction instr) { storeFloat(state, instr); } static void stfsux(cpu::Core *state, Instruction instr) { storeFloat(state, instr); } static void stfsx(cpu::Core *state, Instruction instr) { storeFloat(state, instr); } static void stfd(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stfdu(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stfdux(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stfdx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } static void stfiwx(cpu::Core *state, Instruction instr) { storeGeneric(state, instr); } // Store Multiple Words // Writes consecutive words to memory from rS to r31 static void stmw(cpu::Core *state, Instruction instr) { uint32_t b, ea, r; if (instr.rA) { b = state->gpr[instr.rA]; } else { b = 0; } ea = b + sign_extend<16, int32_t>(instr.d); for (r = instr.rS; r <= 31; ++r, ea += 4) { mem::write(ea, state->gpr[r]); } } // Store String Word (byte-by-byte version of lmw) enum StswFlags { StswIndexed = 1 >> 0, }; template static void stswGeneric(cpu::Core *state, Instruction instr) { uint32_t ea, i, n, r; ea = instr.rA ? state->gpr[instr.rA] : 0; if constexpr (!!(flags & StswIndexed)) { ea += state->gpr[instr.rB]; n = state->xer.byteCount; } else { n = instr.nb ? instr.nb : 32; } r = instr.rS - 1; i = 0; while (n > 0) { if (i == 0) { r = (r + 1) % 32; } mem::write(ea, (state->gpr[r] >> (24 - i)) & 0xff); i = (i + 8) % 32; ea = ea + 1; n = n - 1; } } static void stswi(cpu::Core *state, Instruction instr) { stswGeneric(state, instr); } static void stswx(cpu::Core *state, Instruction instr) { stswGeneric(state, instr); } static double dequantize(uint32_t ea, QuantizedDataType type, uint32_t scale) { int exp = static_cast(scale); exp -= (exp & 32) << 1; // Sign extend. double result; switch (type) { case QuantizedDataType::Floating: result = loadFloatAsDouble(ea); break; case QuantizedDataType::Unsigned8: result = std::ldexp(static_cast(mem::read(ea)), -exp); break; case QuantizedDataType::Unsigned16: result = std::ldexp(static_cast(mem::read(ea)), -exp); break; case QuantizedDataType::Signed8: result = std::ldexp(static_cast(mem::read(ea)), -exp); break; case QuantizedDataType::Signed16: result = std::ldexp(static_cast(mem::read(ea)), -exp); break; default: decaf_abort(fmt::format("Unknown QuantizedDataType {}", static_cast(type))); } return result; } template inline Type clamp(double value) { double min = static_cast(std::numeric_limits::min()); double max = static_cast(std::numeric_limits::max()); return static_cast(std::max(min, std::min(value, max))); } static void quantize(uint32_t ea, double value, QuantizedDataType type, uint32_t scale) { int exp = static_cast(scale); exp -= (exp & 32) << 1; // Sign extend. switch (type) { case QuantizedDataType::Floating: if (get_float_bits(value).exponent <= 896) { // Make sure to write a zero with the correct sign! mem::write(ea, bit_cast(static_cast(std::signbit(value)) << 31)); } else { storeDoubleAsFloat(ea, value); } break; case QuantizedDataType::Unsigned8: if (is_nan(value)) { mem::write(ea, (uint8_t)(std::signbit(value) ? 0 : 0xFF)); } else { mem::write(ea, clamp(std::ldexp(value, exp))); } break; case QuantizedDataType::Unsigned16: if (is_nan(value)) { mem::write(ea, (uint16_t)(std::signbit(value) ? 0 : 0xFFFF)); } else { mem::write(ea, clamp(std::ldexp(value, exp))); } break; case QuantizedDataType::Signed8: if (is_nan(value)) { mem::write(ea, (int8_t)(std::signbit(value) ? -0x80 : 0x7F)); } else { mem::write(ea, clamp(std::ldexp(value, exp))); } break; case QuantizedDataType::Signed16: if (is_nan(value)) { mem::write(ea, (int16_t)(std::signbit(value) ? -0x8000 : 0x7FFF)); } else { mem::write(ea, clamp(std::ldexp(value, exp))); } break; default: decaf_abort(fmt::format("Unknown QuantizedDataType {}", static_cast(type))); } } // Paired Single Load enum PsqLoadFlags { PsqLoadZeroRA = 1 << 0, PsqLoadUpdate = 1 << 1, PsqLoadIndexed = 1 << 2, }; template static void psqLoad(cpu::Core *state, Instruction instr) { uint32_t ea, ls, c, i, w; QuantizedDataType lt; if constexpr (!!(flags & PsqLoadZeroRA)) { if (instr.rA == 0) { ea = 0; } else { ea = state->gpr[instr.rA]; } } else { ea = state->gpr[instr.rA]; } if constexpr (!!(flags & PsqLoadIndexed)) { ea += state->gpr[instr.rB]; } else { ea += sign_extend<12, int32_t>(instr.qd); } if constexpr (!!(flags & PsqLoadIndexed)) { i = instr.qi; w = instr.qw; } else { i = instr.i; w = instr.w; } c = 4; lt = static_cast(state->gqr[i].ld_type); ls = state->gqr[i].ld_scale; if (lt == QuantizedDataType::Unsigned8 || lt == QuantizedDataType::Signed8) { c = 1; } else if (lt == QuantizedDataType::Unsigned16 || lt == QuantizedDataType::Signed16) { c = 2; } if (w == 0) { state->fpr[instr.frD].paired0 = dequantize(ea, lt, ls); state->fpr[instr.frD].paired1 = dequantize(ea + c, lt, ls); } else { state->fpr[instr.frD].paired0 = dequantize(ea, lt, ls); state->fpr[instr.frD].paired1 = 1.0; } if constexpr (!!(flags & PsqLoadUpdate)) { state->gpr[instr.rA] = ea; } } static void psq_l(cpu::Core *state, Instruction instr) { psqLoad(state, instr); } static void psq_lu(cpu::Core *state, Instruction instr) { psqLoad(state, instr); } static void psq_lx(cpu::Core *state, Instruction instr) { psqLoad(state, instr); } static void psq_lux(cpu::Core *state, Instruction instr) { psqLoad(state, instr); } // Paired Single Store enum PsqStoreFlags { PsqStoreZeroRA = 1 << 0, PsqStoreUpdate = 1 << 1, PsqStoreIndexed = 1 << 2, }; template static void psqStore(cpu::Core *state, Instruction instr) { uint32_t ea, sts, c, i, w; QuantizedDataType stt; if constexpr (flags & PsqStoreZeroRA) { if (instr.rA == 0) { ea = 0; } else { ea = state->gpr[instr.rA]; } } else { ea = state->gpr[instr.rA]; } if constexpr (!!(flags & PsqStoreIndexed)) { ea += state->gpr[instr.rB]; } else { ea += sign_extend<12, int32_t>(instr.qd); } if constexpr (!!(flags & PsqStoreIndexed)) { i = instr.qi; w = instr.qw; } else { i = instr.i; w = instr.w; } c = 4; stt = static_cast(state->gqr[i].st_type); sts = state->gqr[i].st_scale; if (stt == QuantizedDataType::Unsigned8 || stt == QuantizedDataType::Signed8) { c = 1; } else if (stt == QuantizedDataType::Unsigned16 || stt == QuantizedDataType::Signed16) { c = 2; } if (w == 0) { quantize(ea, state->fpr[instr.frS].paired0, stt, sts); quantize(ea + c, state->fpr[instr.frS].paired1, stt, sts); } else { quantize(ea, state->fpr[instr.frS].paired0, stt, sts); } if constexpr (!!(flags & PsqStoreUpdate)) { state->gpr[instr.rA] = ea; } } static void psq_st(cpu::Core *state, Instruction instr) { psqStore(state, instr); } static void psq_stu(cpu::Core *state, Instruction instr) { psqStore(state, instr); } static void psq_stx(cpu::Core *state, Instruction instr) { psqStore(state, instr); } static void psq_stux(cpu::Core *state, Instruction instr) { psqStore(state, instr); } void cpu::interpreter::registerLoadStoreInstructions() { RegisterInstruction(lbz); RegisterInstruction(lbzu); RegisterInstruction(lbzx); RegisterInstruction(lbzux); RegisterInstruction(lha); RegisterInstruction(lhau); RegisterInstruction(lhax); RegisterInstruction(lhaux); RegisterInstruction(lhz); RegisterInstruction(lhzu); RegisterInstruction(lhzx); RegisterInstruction(lhzux); RegisterInstruction(lwz); RegisterInstruction(lwzu); RegisterInstruction(lwzx); RegisterInstruction(lwzux); RegisterInstruction(lhbrx); RegisterInstruction(lwbrx); RegisterInstruction(lwarx); RegisterInstruction(lmw); RegisterInstruction(lswi); RegisterInstruction(lswx); RegisterInstruction(stb); RegisterInstruction(stbu); RegisterInstruction(stbx); RegisterInstruction(stbux); RegisterInstruction(sth); RegisterInstruction(sthu); RegisterInstruction(sthx); RegisterInstruction(sthux); RegisterInstruction(stw); RegisterInstruction(stwu); RegisterInstruction(stwx); RegisterInstruction(stwux); RegisterInstruction(sthbrx); RegisterInstruction(stwbrx); RegisterInstruction(stmw); RegisterInstruction(stswi); RegisterInstruction(stswx); RegisterInstruction(stwcx); RegisterInstruction(lfs); RegisterInstruction(lfsu); RegisterInstruction(lfsx); RegisterInstruction(lfsux); RegisterInstruction(lfd); RegisterInstruction(lfdu); RegisterInstruction(lfdx); RegisterInstruction(lfdux); RegisterInstruction(stfs); RegisterInstruction(stfsu); RegisterInstruction(stfsx); RegisterInstruction(stfsux); RegisterInstruction(stfd); RegisterInstruction(stfdu); RegisterInstruction(stfdx); RegisterInstruction(stfdux); RegisterInstruction(stfiwx); RegisterInstruction(psq_l); RegisterInstruction(psq_lu); RegisterInstruction(psq_lx); RegisterInstruction(psq_lux); RegisterInstruction(psq_st); RegisterInstruction(psq_stu); RegisterInstruction(psq_stx); RegisterInstruction(psq_stux); } ================================================ FILE: src/libcpu/src/interpreter/interpreter_pairedsingle.cpp ================================================ #include #include #include "interpreter_insreg.h" #include "interpreter_float.h" #include #include // Register move / sign bit manipulation enum MoveMode { MoveDirect, MoveNegate, MoveAbsolute, MoveNegAbsolute, }; template static void moveGeneric(cpu::Core *state, Instruction instr) { uint32_t b0, b1, d0, d1; const bool ps0_nan = is_signalling_nan(state->fpr[instr.frB].paired0); const bool ps1_nan = is_signalling_nan(state->fpr[instr.frB].paired1); if (ps0_nan) { b0 = truncate_double_bits(state->fpr[instr.frB].idw); } else { // We have to round ps0 if it has excess precision, so we can't just // chop off the trailing bits. b0 = bit_cast(static_cast(state->fpr[instr.frB].paired0)); } // ps1 is always truncated, whether NaN or not. b1 = bit_cast(truncate_double(state->fpr[instr.frB].paired1)); if constexpr (mode == MoveDirect) { d0 = b0; d1 = b1; } else if constexpr (mode == MoveNegate) { d0 = b0 ^ 0x80000000; d1 = b1 ^ 0x80000000; } else if constexpr (mode == MoveAbsolute) { d0 = b0 & ~0x80000000; d1 = b1 & ~0x80000000; } else if constexpr (mode == MoveNegAbsolute) { d0 = b0 | 0x80000000; d1 = b1 | 0x80000000; } else { static_assert("Unexpected mode for moveGeneric"); } if (!ps0_nan) { state->fpr[instr.frD].paired0 = static_cast(bit_cast(d0)); } else { state->fpr[instr.frD].idw = extend_float_nan_bits(d0); } if (!ps1_nan) { state->fpr[instr.frD].paired1 = static_cast(bit_cast(d1)); } else { state->fpr[instr.frD].idw_paired1 = extend_float_nan_bits(d1); } if (instr.rc) { updateFloatConditionRegister(state); } } // Move Register static void ps_mr(cpu::Core *state, Instruction instr) { return moveGeneric(state, instr); } // Negate static void ps_neg(cpu::Core *state, Instruction instr) { return moveGeneric(state, instr); } // Absolute static void ps_abs(cpu::Core *state, Instruction instr) { return moveGeneric(state, instr); } // Negative Absolute static void ps_nabs(cpu::Core *state, Instruction instr) { return moveGeneric(state, instr); } // Paired-single arithmetic enum PSArithOperator { PSAdd, PSSub, PSMul, PSDiv, }; // Returns whether a result value was written (i.e., not aborted by an // exception). template static bool psArithSingle(cpu::Core *state, Instruction instr, float *result) { double a, b; if constexpr (slotA == 0) { a = state->fpr[instr.frA].paired0; } else { a = state->fpr[instr.frA].paired1; } if constexpr (slotB == 0) { b = state->fpr[op == PSMul ? instr.frC : instr.frB].paired0; } else { b = state->fpr[op == PSMul ? instr.frC : instr.frB].paired1; } const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b); bool vxisi, vximz, vxidi, vxzdz, zx; if constexpr (op == PSAdd) { vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) != std::signbit(b); vximz = false; vxidi = false; vxzdz = false; zx = false; } else if constexpr (op == PSSub) { vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) == std::signbit(b); vximz = false; vxidi = false; vxzdz = false; zx = false; } else if constexpr (op == PSMul) { vxisi = false; vximz = (is_infinity(a) && is_zero(b)) || (is_zero(a) && is_infinity(b)); vxidi = false; vxzdz = false; zx = false; } else if constexpr (op == PSDiv) { vxisi = false; vximz = false; vxidi = is_infinity(a) && is_infinity(b); vxzdz = is_zero(a) && is_zero(b); zx = !(vxzdz || vxsnan) && is_zero(b); } else { static_assert("Unexpected flags for psArithSingle"); } state->fpscr.vxsnan |= vxsnan; state->fpscr.vxisi |= vxisi; state->fpscr.vximz |= vximz; state->fpscr.vxidi |= vxidi; state->fpscr.vxzdz |= vxzdz; state->fpscr.zx |= zx; const bool vxEnabled = (vxsnan || vxisi || vximz || vxidi || vxzdz) && state->fpscr.ve; const bool zxEnabled = zx && state->fpscr.ze; if (vxEnabled || zxEnabled) { return false; } float d; if (is_nan(a)) { d = make_quiet(truncate_double(a)); } else if (is_nan(b)) { d = make_quiet(truncate_double(b)); } else if (vxisi || vximz || vxidi || vxzdz) { d = make_nan(); } else { if constexpr (op == PSAdd) { d = static_cast(a + b); } else if constexpr (op == PSSub) { d = static_cast(a - b); } else if constexpr (op == PSMul) { if constexpr (slotB == 0) { roundForMultiply(&a, &b); // Not necessary for slot 1. } d = static_cast(a * b); } else if constexpr (op == PSDiv) { d = static_cast(a / b); } else { static_assert("Unexpected flags for psArithSingle"); } if (possibleUnderflow(d)) { const int oldRound = fegetround(); fesetround(FE_TOWARDZERO); volatile double bTemp = b; volatile float dummy; if constexpr (op == PSAdd) { dummy = static_cast(a + bTemp); } else if constexpr (op == PSSub) { dummy = static_cast(a - bTemp); } else if constexpr (op == PSMul) { dummy = static_cast(a * bTemp); } else if constexpr (op == PSDiv) { dummy = static_cast(a / bTemp); } else { static_assert("Unexpected flags for psArithSingle"); } fesetround(oldRound); } } *result = d; return true; } template static void psArithGeneric(cpu::Core *state, Instruction instr) { const uint32_t oldFPSCR = state->fpscr.value; float d0, d1; const bool wrote0 = psArithSingle(state, instr, &d0); const bool wrote1 = psArithSingle(state, instr, &d1); if (wrote0 && wrote1) { state->fpr[instr.frD].paired0 = extend_float(d0); state->fpr[instr.frD].paired1 = extend_float(d1); } if (wrote0) { updateFPRF(state, d0); } updateFPSCR(state, oldFPSCR); if (instr.rc) { updateFloatConditionRegister(state); } } // Add static void ps_add(cpu::Core *state, Instruction instr) { return psArithGeneric(state, instr); } // Subtract static void ps_sub(cpu::Core *state, Instruction instr) { return psArithGeneric(state, instr); } // Multiply static void ps_mul(cpu::Core *state, Instruction instr) { return psArithGeneric(state, instr); } static void ps_muls0(cpu::Core *state, Instruction instr) { return psArithGeneric(state, instr); } static void ps_muls1(cpu::Core *state, Instruction instr) { return psArithGeneric(state, instr); } // Divide static void ps_div(cpu::Core *state, Instruction instr) { return psArithGeneric(state, instr); } template static void psSumGeneric(cpu::Core *state, Instruction instr) CLANG_FPU_BUG_WORKAROUND { const uint32_t oldFPSCR = state->fpscr.value; float d; if (psArithSingle(state, instr, &d)) { updateFPRF(state, d); if constexpr (slot == 0) { state->fpr[instr.frD].paired0 = extend_float(d); state->fpr[instr.frD].idw_paired1 = state->fpr[instr.frC].idw_paired1; } else { float ps0; if (is_nan(state->fpr[instr.frC].paired0)) { ps0 = truncate_double(state->fpr[instr.frC].paired0); } else { const auto inexact = !!std::fetestexcept(FE_INEXACT); const auto overflow = !!std::fetestexcept(FE_OVERFLOW); ps0 = static_cast(state->fpr[instr.frC].paired0); if (!inexact) { std::feclearexcept(FE_INEXACT); } if (!overflow) { std::feclearexcept(FE_OVERFLOW); } } state->fpr[instr.frD].paired0 = extend_float(ps0); state->fpr[instr.frD].paired1 = extend_float(d); } } updateFPSCR(state, oldFPSCR); if (instr.rc) { updateFloatConditionRegister(state); } } // Sum High static void ps_sum0(cpu::Core *state, Instruction instr) { return psSumGeneric<0>(state, instr); } // Sum Low static void ps_sum1(cpu::Core *state, Instruction instr) { return psSumGeneric<1>(state, instr); } // Fused multiply-add instructions enum FMAFlags { FMASubtract = 1 << 0, // Subtract instead of add FMANegate = 1 << 1, // Negate result }; // Returns whether a result value was written (i.e., not aborted by an // exception). template static bool fmaSingle(cpu::Core *state, Instruction instr, float *result) { double a, b, c; if constexpr (slotAB == 0) { a = state->fpr[instr.frA].paired0; b = state->fpr[instr.frB].paired0; } else { a = state->fpr[instr.frA].paired1; b = state->fpr[instr.frB].paired1; } if constexpr (slotC == 0) { c = state->fpr[instr.frC].paired0; } else { c = state->fpr[instr.frC].paired1; } const double addend = (flags & FMASubtract) ? -b : b; const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b) || is_signalling_nan(c); const bool vximz = (is_infinity(a) && is_zero(c)) || (is_zero(a) && is_infinity(c)); const bool vxisi = (!vximz && !is_nan(a) && !is_nan(c) && (is_infinity(a) || is_infinity(c)) && is_infinity(b) && (std::signbit(a) ^ std::signbit(c)) != std::signbit(addend)); state->fpscr.vxsnan |= vxsnan; state->fpscr.vxisi |= vxisi; state->fpscr.vximz |= vximz; if ((vxsnan || vxisi || vximz) && state->fpscr.ve) { return false; } float d; if (is_nan(a)) { d = make_quiet(truncate_double(a)); } else if (is_nan(b)) { d = make_quiet(truncate_double(b)); } else if (is_nan(c)) { d = make_quiet(truncate_double(c)); } else if (vxisi || vximz) { d = make_nan(); } else { if constexpr (slotC == 0) { roundForMultiply(&a, &c); // Not necessary for slot 1. } double d64 = std::fma(a, c, addend); if (state->fpscr.rn == espresso::FloatingPointRoundMode::Nearest) { d = roundFMAResultToSingle(d64, a, addend, c); } else { d = static_cast(d64); } if (possibleUnderflow(d)) { const int oldRound = fegetround(); fesetround(FE_TOWARDZERO); volatile double addendTemp = addend; volatile float dummy; dummy = (float)std::fma(a, c, addendTemp); fesetround(oldRound); } if constexpr (!!(flags & FMANegate)) { d = -d; } } *result = d; return true; } template static void fmaGeneric(cpu::Core *state, Instruction instr) { const uint32_t oldFPSCR = state->fpscr.value; float d0, d1; const bool wrote0 = fmaSingle(state, instr, &d0); const bool wrote1 = fmaSingle(state, instr, &d1); if (wrote0 && wrote1) { state->fpr[instr.frD].paired0 = extend_float(d0); state->fpr[instr.frD].paired1 = extend_float(d1); } if (wrote0) { updateFPRF(state, d0); } updateFPSCR(state, oldFPSCR); if (instr.rc) { updateFloatConditionRegister(state); } } static void ps_madd(cpu::Core *state, Instruction instr) { return fmaGeneric<0, 0, 1>(state, instr); } static void ps_madds0(cpu::Core *state, Instruction instr) { return fmaGeneric<0, 0, 0>(state, instr); } static void ps_madds1(cpu::Core *state, Instruction instr) { return fmaGeneric<0, 1, 1>(state, instr); } static void ps_msub(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } static void ps_nmadd(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } static void ps_nmsub(cpu::Core *state, Instruction instr) { return fmaGeneric(state, instr); } // Merge registers enum MergeFlags { MergeValue0 = 1 << 0, MergeValue1 = 1 << 1 }; template static void mergeGeneric(cpu::Core *state, Instruction instr) { float d0, d1; if constexpr (!!(flags & MergeValue0)) { if (!is_signalling_nan(state->fpr[instr.frA].paired1)) { d0 = static_cast(state->fpr[instr.frA].paired1); } else { d0 = truncate_double(state->fpr[instr.frA].paired1); } } else { if (!is_signalling_nan(state->fpr[instr.frA].paired0)) { d0 = static_cast(state->fpr[instr.frA].paired0); } else { d0 = truncate_double(state->fpr[instr.frA].paired0); } } // When inserting a double-precision value into slot 1, the value is // truncated rather than rounded. double d1_double; if constexpr (!!(flags & MergeValue1)) { d1_double = state->fpr[instr.frB].paired1; } else { d1_double = state->fpr[instr.frB].paired0; } auto d1_bits = get_float_bits(d1_double); if (d1_bits.exponent >= 1151 && d1_bits.exponent < 2047) { d1 = std::numeric_limits::max(); } else { d1 = truncate_double(d1_double); } state->fpr[instr.frD].paired0 = extend_float(d0); state->fpr[instr.frD].paired1 = extend_float(d1); // Don't leak any exceptions (inexact, overflow etc.) to later instructions. std::feclearexcept(FE_ALL_EXCEPT); if (instr.rc) { updateFloatConditionRegister(state); } } static void ps_merge00(cpu::Core *state, Instruction instr) { return mergeGeneric(state, instr); } static void ps_merge01(cpu::Core *state, Instruction instr) { return mergeGeneric(state, instr); } static void ps_merge11(cpu::Core *state, Instruction instr) { return mergeGeneric(state, instr); } static void ps_merge10(cpu::Core *state, Instruction instr) { return mergeGeneric(state, instr); } // Reciprocal static void ps_res(cpu::Core *state, Instruction instr) { const double b0 = state->fpr[instr.frB].paired0; const double b1 = state->fpr[instr.frB].paired1; const bool vxsnan0 = is_signalling_nan(b0); const bool vxsnan1 = is_signalling_nan(b1); const bool zx0 = is_zero(b0); const bool zx1 = is_zero(b1); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan0 || vxsnan1; state->fpscr.zx |= zx0 || zx1; float d0 = 0.0, d1 = 0.0; auto write = true; if ((vxsnan0 && state->fpscr.ve) || (zx0 && state->fpscr.ze)) { write = false; } else { d0 = ppc_estimate_reciprocal(truncate_double(b0)); updateFPRF(state, d0); } if ((vxsnan1 && state->fpscr.ve) || (zx1 && state->fpscr.ze)) { write = false; } else { d1 = ppc_estimate_reciprocal(truncate_double(b1)); } if (write) { state->fpr[instr.frD].paired0 = extend_float(d0); state->fpr[instr.frD].paired1 = extend_float(d1); } if (std::fetestexcept(FE_INEXACT)) { // On inexact result, ps_res sets FPSCR[FI] without also setting // FPSCR[XX] (like fres). std::feclearexcept(FE_INEXACT); updateFPSCR(state, oldFPSCR); state->fpscr.fi = 1; } else { updateFPSCR(state, oldFPSCR); } if (instr.rc) { updateFloatConditionRegister(state); } } // Reciprocal Square Root static void ps_rsqrte(cpu::Core *state, Instruction instr) { const double b0 = state->fpr[instr.frB].paired0; const double b1 = state->fpr[instr.frB].paired1; const bool vxsnan0 = is_signalling_nan(b0); const bool vxsnan1 = is_signalling_nan(b1); const bool vxsqrt0 = !vxsnan0 && std::signbit(b0) && !is_zero(b0); const bool vxsqrt1 = !vxsnan1 && std::signbit(b1) && !is_zero(b1); const bool zx0 = is_zero(b0); const bool zx1 = is_zero(b1); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan0 || vxsnan1; state->fpscr.vxsqrt |= vxsqrt0 || vxsqrt1; state->fpscr.zx |= zx0 || zx1; double d0 = 0.0, d1 = 0.0; bool write = true; if (((vxsnan0 || vxsqrt0) && state->fpscr.ve) || (zx0 && state->fpscr.ze)) { write = false; } else { d0 = ppc_estimate_reciprocal_root(b0); updateFPRF(state, d0); } if (((vxsnan1 || vxsqrt1) && state->fpscr.ve) || (zx1 && state->fpscr.ze)) { write = false; } else { d1 = ppc_estimate_reciprocal_root(b1); } if (write) { // ps_rsqrte behaves strangely when the result's magnitude is out of // range: ps0 keeps its double-precision exponent, while ps1 appears // to get an arbitrary value from the floating-point circuitry. The // details of how ps1's exponent is affected are unknown, but the // logic below works for double-precision inputs 0x7FE...FFF (maximum // normal) and 0x000...001 (minimum denormal). auto bits0 = get_float_bits(d0); bits0.mantissa &= UINT64_C(0xFFFFFE0000000); state->fpr[instr.frD].paired0 = bits0.v; auto bits1 = get_float_bits(d1); if (bits1.exponent == 0) { // Leave as zero (reciprocal square root can never be a denormal). } else if (bits1.exponent < 1151) { int8_t exponent8 = (bits1.exponent - 1023) & 0xFF; bits1.exponent = 1023 + exponent8; } else if (bits1.exponent < 2047) { bits1.exponent = 1022; } bits1.mantissa &= UINT64_C(0xFFFFFE0000000); state->fpr[instr.frD].paired1 = bits1.v; } updateFPSCR(state, oldFPSCR); if (instr.rc) { updateFloatConditionRegister(state); } } // Select static void ps_sel(cpu::Core *state, Instruction instr) { auto a0 = state->fpr[instr.frA].paired0; auto a1 = state->fpr[instr.frA].paired1; auto b0 = state->fpr[instr.frB].paired0; auto b1 = state->fpr[instr.frB].paired1; auto c0 = state->fpr[instr.frC].paired0; auto c1 = state->fpr[instr.frC].paired1; auto d1 = (a1 >= 0) ? c1 : b1; auto d0 = (a0 >= 0) ? c0 : b0; state->fpr[instr.frD].paired0 = d0; state->fpr[instr.frD].paired1 = d1; if (instr.rc) { updateFloatConditionRegister(state); } } void cpu::interpreter::registerPairedInstructions() { RegisterInstruction(ps_add); RegisterInstruction(ps_div); RegisterInstruction(ps_mul); RegisterInstruction(ps_sub); RegisterInstruction(ps_abs); RegisterInstruction(ps_nabs); RegisterInstruction(ps_neg); RegisterInstruction(ps_sel); RegisterInstruction(ps_res); RegisterInstruction(ps_rsqrte); RegisterInstruction(ps_msub); RegisterInstruction(ps_madd); RegisterInstruction(ps_nmsub); RegisterInstruction(ps_nmadd); RegisterInstruction(ps_mr); RegisterInstruction(ps_sum0); RegisterInstruction(ps_sum1); RegisterInstruction(ps_muls0); RegisterInstruction(ps_muls1); RegisterInstruction(ps_madds0); RegisterInstruction(ps_madds1); RegisterInstruction(ps_merge00); RegisterInstruction(ps_merge01); RegisterInstruction(ps_merge10); RegisterInstruction(ps_merge11); } ================================================ FILE: src/libcpu/src/interpreter/interpreter_system.cpp ================================================ #include "cpu.h" #include "cpu_breakpoints.h" #include "cpu_internal.h" #include "espresso/espresso_spr.h" #include "espresso/espresso_instructionset.h" #include "interpreter_insreg.h" #include "mem.h" #include #include #include #include #include using espresso::SPR; /* // System Linkage INS(rfi, (), (), (), (opcd == 19, xo1 == 50), "") // Trap INS(tw, (), (to, ra, rb), (), (opcd == 31, xo1 == 4), "") INS(twi, (), (to, ra, simm), (), (opcd == 3), "") // Lookaside Buffer Management INS(tlbie, (), (rb), (), (opcd == 31, xo1 == 306), "") INS(tlbsync, (), (), (), (opcd == 31, xo1 == 566), "") // External Control INS(eciwx, (rd), (ra, rb), (), (opcd == 31, xo1 == 310), "") INS(ecowx, (rd), (ra, rb), (), (opcd == 31, xo1 == 438), "") */ // Cache Management static void icbi(cpu::Core *state, Instruction instr) { } // Data Cache Block Flush static void dcbf(cpu::Core *state, Instruction instr) { } // Data Cache Block Invalidate static void dcbi(cpu::Core *state, Instruction instr) { } // Data Cache Block Store static void dcbst(cpu::Core *state, Instruction instr) { } // Data Cache Block Touch static void dcbt(cpu::Core *state, Instruction instr) { } // Data Cache Block Touch for Store static void dcbtst(cpu::Core *state, Instruction instr) { } // Data Cache Block Zero static void dcbz(cpu::Core *state, Instruction instr) { uint32_t addr; if (instr.rA == 0) { addr = 0; } else { addr = state->gpr[instr.rA]; } addr += state->gpr[instr.rB]; addr = align_down(addr, 32); memset(mem::translate(addr), 0, 32); } // Data Cache Block Zero Locked static void dcbz_l(cpu::Core *state, Instruction instr) { dcbz(state, instr); } // Enforce In-Order Execution of I/O static void eieio(cpu::Core *state, Instruction instr) { } // Synchronise static void sync(cpu::Core *state, Instruction instr) { } // Instruction Synchronise static void isync(cpu::Core *state, Instruction instr) { } // Move from Special Purpose Register static void mfspr(cpu::Core *state, Instruction instr) { auto spr = decodeSPR(instr); auto value = 0u; switch (spr) { case SPR::XER: value = state->xer.value; break; case SPR::LR: value = state->lr; break; case SPR::CTR: value = state->ctr; break; case SPR::UGQR0: value = state->gqr[0].value; break; case SPR::UGQR1: value = state->gqr[1].value; break; case SPR::UGQR2: value = state->gqr[2].value; break; case SPR::UGQR3: value = state->gqr[3].value; break; case SPR::UGQR4: value = state->gqr[4].value; break; case SPR::UGQR5: value = state->gqr[5].value; break; case SPR::UGQR6: value = state->gqr[6].value; break; case SPR::UGQR7: value = state->gqr[7].value; break; case SPR::UPIR: value = cpu::this_core::id(); break; default: gLog->error("Invalid mfspr SPR {}", static_cast(spr)); state->interrupt.fetch_or(cpu::PROGRAM_INTERRUPT); cpu::this_core::checkInterrupts(); } state->gpr[instr.rD] = value; } // Move to Special Purpose Register static void mtspr(cpu::Core *state, Instruction instr) { auto spr = decodeSPR(instr); auto value = state->gpr[instr.rS]; switch (spr) { case SPR::XER: state->xer.value = value; break; case SPR::LR: state->lr = value; break; case SPR::CTR: state->ctr = value; break; case SPR::UGQR0: state->gqr[0].value = value; break; case SPR::UGQR1: state->gqr[1].value = value; break; case SPR::UGQR2: state->gqr[2].value = value; break; case SPR::UGQR3: state->gqr[3].value = value; break; case SPR::UGQR4: state->gqr[4].value = value; break; case SPR::UGQR5: state->gqr[5].value = value; break; case SPR::UGQR6: state->gqr[6].value = value; break; case SPR::UGQR7: state->gqr[7].value = value; break; default: gLog->error("Invalid mtspr SPR {}", static_cast(spr)); state->interrupt.fetch_or(cpu::PROGRAM_INTERRUPT); cpu::this_core::checkInterrupts(); } } // Move from Time Base Register static void mftb(cpu::Core *state, Instruction instr) { auto tbr = decodeSPR(instr); auto value = 0u; switch (tbr) { case SPR::UTBL: value = static_cast(state->tb() & 0xFFFFFFFF); break; case SPR::UTBU: value = static_cast(state->tb() >> 32); break; default: decaf_abort(fmt::format("Invalid mftb TBR {}", static_cast(tbr))); } state->gpr[instr.rD] = value; } // Move from Machine State Register static void mfmsr(cpu::Core *state, Instruction instr) { state->gpr[instr.rD] = state->msr.value; } // Move to Machine State Register static void mtmsr(cpu::Core *state, Instruction instr) { state->msr.value = state->gpr[instr.rS]; } // Move from Segment Register static void mfsr(cpu::Core *state, Instruction instr) { state->gpr[instr.rD] = state->sr[instr.sr]; } // Move from Segment Register Indirect static void mfsrin(cpu::Core *state, Instruction instr) { auto sr = state->gpr[instr.rB] & 0xf; state->gpr[instr.rD] = state->sr[sr]; } // Move to Segment Register static void mtsr(cpu::Core *state, Instruction instr) { state->sr[instr.sr] = state->gpr[instr.rS]; } // Move to Segment Register Indirect static void mtsrin(cpu::Core *state, Instruction instr) { auto sr = state->gpr[instr.rB] & 0xf; state->sr[sr] = state->gpr[instr.rS]; } // Kernel call static void kc(cpu::Core *state, Instruction instr) { auto kcId = instr.kcn; auto handler = cpu::getSystemCallHandler(kcId); state->systemCallStackHead = state->gpr[1]; state = handler(state, kcId); } // Trap Word static void tw(cpu::Core *state, Instruction instr) { if (!cpu::hasBreakpoint(state->cia)) { decaf_abort(fmt::format("Game raised a trap exception at 0x{:08X}.", state->nia)); } // By this point we have already handled the breakpoint thanks to checkInterrupts, // so here we just need to execute the breakpoint's saved instruction! auto savedInstr = cpu::getBreakpointSavedCode(state->cia); auto data = espresso::decodeInstruction(savedInstr); decaf_check(data); auto handler = cpu::interpreter::getInstructionHandler(data->id); decaf_check(handler); handler(state, savedInstr); } void cpu::interpreter::registerSystemInstructions() { RegisterInstruction(dcbf); RegisterInstruction(dcbi); RegisterInstruction(dcbst); RegisterInstruction(dcbt); RegisterInstruction(dcbtst); RegisterInstruction(dcbz); RegisterInstruction(dcbz_l); RegisterInstruction(eieio); RegisterInstruction(icbi); RegisterInstruction(isync); RegisterInstruction(sync); RegisterInstruction(mfspr); RegisterInstruction(mtspr); RegisterInstruction(mftb); RegisterInstruction(mfmsr); RegisterInstruction(mtmsr); RegisterInstruction(mfsr); RegisterInstruction(mfsrin); RegisterInstruction(mtsr); RegisterInstruction(mtsrin); RegisterInstruction(kc); RegisterInstruction(tw); } ================================================ FILE: src/libcpu/src/jit/binrec/jit_binrec.cpp ================================================ #include "cpu.h" #include "cpu_breakpoints.h" #include "cpu_internal.h" #include "espresso/espresso_instructionset.h" #include "jit_binrec.h" #include "interpreter/interpreter.h" #include "mem.h" #include "mmu.h" #include #include #include #include #include #include #define offsetof2(s, m) ((size_t)&reinterpret_cast((((s*)0)->m))) namespace cpu { namespace jit { static void *brChainLookup(BinrecCore *core, ppcaddr_t address); static uint64_t brTimeBaseHandler(BinrecCore *core); static BinrecCore *brSyscallHandler(BinrecCore *core, espresso::Instruction instr); static BinrecCore *brTrapHandler(BinrecCore *core); static void brLog(void *, binrec::LogLevel level, const char *message); BinrecBackend::BinrecBackend(size_t codeCacheSize, size_t dataCacheSize) { mCodeCache.initialise(codeCacheSize, dataCacheSize); mHandles.fill(nullptr); } BinrecBackend::~BinrecBackend() { mCodeCache.free(); } Core * BinrecBackend::initialiseCore(uint32_t id) { // See binrec.h. static const uint16_t fresTable[64] = { 0x3FFC,0x3E1, 0x3C1C,0x3A7, 0x3875,0x371, 0x3504,0x340, 0x31C4,0x313, 0x2EB1,0x2EA, 0x2BC8,0x2C4, 0x2904,0x2A0, 0x2664,0x27F, 0x23E5,0x261, 0x2184,0x245, 0x1F40,0x22A, 0x1D16,0x212, 0x1B04,0x1FB, 0x190A,0x1E5, 0x1725,0x1D1, 0x1554,0x1BE, 0x1396,0x1AC, 0x11EB,0x19B, 0x104F,0x18B, 0x0EC4,0x17C, 0x0D48,0x16E, 0x0BD7,0x15B, 0x0A7C,0x15B, 0x0922,0x143, 0x07DF,0x143, 0x069C,0x12D, 0x056F,0x12D, 0x0442,0x11A, 0x0328,0x11A, 0x020E,0x108, 0x0106,0x106 }; static const uint16_t frsqrteTable[64] = { 0x7FF4,0x7A4, 0x7852,0x700, 0x7154,0x670, 0x6AE4,0x5F2, 0x64F2,0x584, 0x5F6E,0x524, 0x5A4C,0x4CC, 0x5580,0x47E, 0x5102,0x43A, 0x4CCA,0x3FA, 0x48D0,0x3C2, 0x450E,0x38E, 0x4182,0x35E, 0x3E24,0x332, 0x3AF2,0x30A, 0x37E8,0x2E6, 0x34FD,0x568, 0x2F97,0x4F3, 0x2AA5,0x48D, 0x2618,0x435, 0x21E4,0x3E7, 0x1DFE,0x3A2, 0x1A5C,0x365, 0x16F8,0x32E, 0x13CA,0x2FC, 0x10CE,0x2D0, 0x0DFE,0x2A8, 0x0B57,0x283, 0x08D4,0x261, 0x0673,0x243, 0x0431,0x226, 0x020B,0x20B }; auto core = new BinrecCore {}; core->id = id; core->backend = this; core->chainLookup = brChainLookup; core->mftbHandler = brTimeBaseHandler; core->scHandler = brSyscallHandler; core->trapHandler = brTrapHandler; core->fresTable = fresTable; core->frsqrteTable = frsqrteTable; #ifdef DECAF_JIT_ALLOW_PROFILING core->calledHLE = false; #endif return core; } void BinrecBackend::addReadOnlyRange(uint32_t address, uint32_t size) { mReadOnlyRanges.emplace_back(address, size); } void BinrecBackend::clearCache(uint32_t address, uint32_t size) { if (address == 0 && size == 0xFFFFFFFF) { mCodeCache.clear(); mTotalProfileTime = 0; } else { mCodeCache.invalidate(address, size); } } BinrecHandle * BinrecBackend::createBinrecHandle() { binrec::Setup setup; std::memset(&setup, 0, sizeof(setup)); setup.guest = binrec::Arch::BINREC_ARCH_PPC_7XX; #ifdef PLATFORM_WINDOWS setup.host = binrec::Arch::BINREC_ARCH_X86_64_WINDOWS_SEH; #else setup.host = binrec::native_arch(); #endif setup.host_features = binrec::native_features(); setup.guest_memory_base = reinterpret_cast(getBaseVirtualAddress()); setup.state_offsets_ppc.gpr = offsetof2(BinrecCore, gpr); setup.state_offsets_ppc.fpr = offsetof2(BinrecCore, fpr); setup.state_offsets_ppc.gqr = offsetof2(BinrecCore, gqr); setup.state_offsets_ppc.lr = offsetof2(BinrecCore, lr); setup.state_offsets_ppc.ctr = offsetof2(BinrecCore, ctr); setup.state_offsets_ppc.cr = offsetof2(BinrecCore, cr); setup.state_offsets_ppc.xer = offsetof2(BinrecCore, xer); setup.state_offsets_ppc.fpscr = offsetof2(BinrecCore, fpscr); setup.state_offsets_ppc.pvr = offsetof2(BinrecCore, pvr); setup.state_offsets_ppc.pir = offsetof2(BinrecCore, id); setup.state_offsets_ppc.reserve_flag = offsetof2(BinrecCore, reserveFlag); setup.state_offsets_ppc.reserve_state = offsetof2(BinrecCore, reserveData); setup.state_offsets_ppc.nia = offsetof2(BinrecCore, nia); setup.state_offsets_ppc.timebase_handler = offsetof2(BinrecCore, mftbHandler); setup.state_offsets_ppc.sc_handler = offsetof2(BinrecCore, scHandler); setup.state_offsets_ppc.trap_handler = offsetof2(BinrecCore, trapHandler); setup.state_offsets_ppc.fres_lut = offsetof2(BinrecCore, fresTable); setup.state_offsets_ppc.frsqrte_lut = offsetof2(BinrecCore, frsqrteTable); setup.state_offset_chain_lookup = offsetof2(BinrecCore, chainLookup); setup.state_offset_branch_exit_flag = offsetof2(BinrecCore, interrupt); setup.log = brLog; auto handle = new BinrecHandle {}; if (!handle->initialize(setup)) { delete handle; return nullptr; } handle->set_optimization_flags(mOptFlags.common, mOptFlags.guest, mOptFlags.host); handle->enable_branch_exit_test(true); handle->enable_chaining(mOptFlags.useChaining); if (mVerifyEnabled && mVerifyAddress == 0) { handle->set_pre_insn_callback(brVerifyPreHandler); handle->set_post_insn_callback(brVerifyPostHandler); } for (const auto &range : mReadOnlyRanges) { handle->add_readonly_region(range.first, range.second); } return handle; } CodeBlock * BinrecBackend::checkForCodeBlockTrampoline(uint32_t address) { // If the first instruction is an unconditional branch (such as for a // function wrapping another one), just call the target's code directly. // core->nia will eventually be set to a proper value, so we don't need // to worry that it will be out of sync here. auto instr = mem::read(address); auto data = espresso::decodeInstruction(instr); if (data && data->id == espresso::InstructionID::b && !instr.lk) { // Watch out for cycles when looking up the target! auto target = address; for (int tries = 10; tries > 0 && data && data->id == espresso::InstructionID::b && !instr.lk; --tries) { auto branchAddress = target; target = sign_extend<26>(instr.li << 2); if (!instr.aa) { target += branchAddress; } instr = mem::read(target); data = espresso::decodeInstruction(instr); } if (target != address) { auto block = mCodeCache.getBlockByAddress(target); if (block) { // Mark this address to point to target block mCodeCache.setBlockIndex(address, mCodeCache.getIndex(block)); return block; } } } return nullptr; } CodeBlock * BinrecBackend::getCodeBlock(BinrecCore *core, uint32_t address) { auto indexPtr = mCodeCache.getIndexPointer(address); auto blockIndex = indexPtr->load(); // If block is uncompiled, let's try mark it as compiling! if (UNLIKELY(blockIndex == CodeBlockIndexUncompiled)) { if (!indexPtr->compare_exchange_strong(blockIndex, CodeBlockIndexCompiling)) { // Another thread has started compiling, wait for it to finish. while (blockIndex == CodeBlockIndexCompiling) { using namespace std::chrono_literals; std::this_thread::sleep_for(10us); blockIndex = indexPtr->load(); } } } // Check if the block has been compiled if (LIKELY(blockIndex >= 0)) { auto block = mCodeCache.getBlockByIndex(blockIndex); return block; } // Do not try to recompile again if it failed before if (UNLIKELY(blockIndex == CodeBlockIndexError)) { return nullptr; } // Do not compile if there is a breakpoint at address. if (UNLIKELY(hasBreakpoint(address))) { return nullptr; } // Check for possible branch trampoline if (auto block = checkForCodeBlockTrampoline(address)) { return block; } auto handle = mHandles[core->id]; if (!handle) { handle = createBinrecHandle(); mHandles[core->id] = handle; } if (mVerifyEnabled && mVerifyAddress != 0) { if (address == mVerifyAddress) { handle->set_pre_insn_callback(brVerifyPreHandler); handle->set_post_insn_callback(brVerifyPostHandler); } else { handle->set_pre_insn_callback(nullptr); handle->set_post_insn_callback(nullptr); } } // In extreme cases (such as dense floating-point code with no // optimizations enabled), translation could fail due to internal // libbinrec limits, so try repeatedly with smaller code ranges if // the first translation attempt fails. auto limit = 4096u; auto size = long { 0 }; void *buffer = nullptr; while (!handle->translate(core, address, address + limit - 1, &buffer, &size)) { limit /= 2; if (limit < 256) { gLog->warn("Failed to translate code at 0x{:X}", address); indexPtr->store(CodeBlockIndexError); return nullptr; } } #ifdef PLATFORM_WINDOWS // First 8 bytes of buffer is offset to start of code auto codeOffset = *reinterpret_cast(buffer); auto unwindInfo = reinterpret_cast(reinterpret_cast(buffer) + 8); auto unwindSize = codeOffset - 8; auto code = reinterpret_cast(reinterpret_cast(buffer) + codeOffset); auto codeSize = size - codeOffset; #else auto code = buffer; auto codeSize = size; void *unwindInfo = nullptr; auto unwindSize = size_t { 0 }; #endif auto block = mCodeCache.registerCodeBlock(address, code, codeSize, unwindInfo, unwindSize); decaf_check(block); free(buffer); // Clear any floating-point exceptions raised by the translation so // the translated code doesn't pick them up. std::feclearexcept(FE_ALL_EXCEPT); return block; } inline CodeBlock * BinrecBackend::getCodeBlockFast(BinrecCore *core, uint32_t address) { auto indexPtr = mCodeCache.getConstIndexPointer(address); if (LIKELY(indexPtr)) { auto blockIndex = indexPtr->load(); if (LIKELY(blockIndex >= 0)) { auto block = mCodeCache.getBlockByIndex(blockIndex); return block; } } // Block is not yet compiled, so take the slow path. return getCodeBlock(core, address); } static inline uint64_t rdtsc() { #ifdef _MSC_VER return __rdtsc(); #else uint64_t tsc; __asm__ volatile("rdtsc; shl $32,%%rdx; or %%rdx,%%rax" : "=a" (tsc) : : "rdx"); return tsc; #endif } void BinrecBackend::resumeExecution() { auto memBase = cpu::getBaseVirtualAddress(); // Prepare FPU state for guest code execution. this_core::updateRoundingMode(); std::feclearexcept(FE_ALL_EXCEPT); auto core = reinterpret_cast(this_core::state()); decaf_check(core->nia != CALLBACK_ADDR); if (mVerifyEnabled) { // Use a separate routine for verify mode so we don't have to check // the current mode on every iteration through the loop. Note that // we don't attempt to profile while verifying. return resumeVerifyExecution(); } do { if (UNLIKELY(core->interrupt.load())) { this_core::checkInterrupts(); // We might have been rescheduled onto a different core. core = reinterpret_cast(this_core::state()); } const ppcaddr_t address = core->nia; auto block = getCodeBlockFast(core, address); // To keep overhead in the non-profiling case as low as possible, we // only check for zeroness of the profiling mask here, which is just // a memory-immediate compare and a non-taken branch on x86. If the // mask is nonzero, we'll check again for the specific core bit on // the profiling side of the test. #ifdef DECAF_JIT_ALLOW_PROFILING if (LIKELY(!mProfilingMask)) { #else if (1) { #endif if (LIKELY(block)) { auto entry = reinterpret_cast(block->code); core = entry(core, memBase); } else { // Step over the current instruction, in case it's confusing // the translator. TODO: Consider blacklisting the address to // avoid trying to translate it every time we encounter it. interpreter::step_one(core); // If we just returned from a system call, we might have been // rescheduled onto a different core. core = reinterpret_cast(this_core::state()); } } else { // mProfilingMask != 0 const uint64_t start = rdtsc(); if (block) { auto entry = reinterpret_cast(block->code); core = entry(core, memBase); } else { interpreter::step_one(core); core = reinterpret_cast(this_core::state()); } // Don't count profiling data for HLE calls since those have // nothing to do with JIT performance (and might also have // caused us to switch cores, so the RDTSC difference wouldn't // make any sense). if (UNLIKELY(core->calledHLE)) { core->calledHLE = false; } else if (block && mProfilingMask & (1 << core->id)) { const uint64_t time = rdtsc() - start; mTotalProfileTime += time; block->profileData.time += time; block->profileData.count++; } } } while (core->nia != CALLBACK_ADDR); } /** * Get a sample of JIT stats. */ bool BinrecBackend::sampleStats(JitStats &stats) { stats.totalTimeInCodeBlocks = mTotalProfileTime; stats.compiledBlocks = mCodeCache.getCompiledCodeBlocks(); stats.usedCodeCacheSize = mCodeCache.getCodeCacheSize(); stats.usedDataCacheSize = mCodeCache.getDataCacheSize(); return true; } /** * Reset JIT profiling stats. */ void BinrecBackend::resetProfileStats() { // Clear block stats auto blocks = mCodeCache.getCompiledCodeBlocks(); for (auto &block : blocks) { block.profileData.count = 0; block.profileData.time = 0; } // Clear generic stats mTotalProfileTime = 0; } /** * Set which cores to profile. */ void BinrecBackend::setProfilingMask(unsigned mask) { mProfilingMask = mask; } /** * Get which cores are being profiled. */ unsigned BinrecBackend::getProfilingMask() { return mProfilingMask; } /** * Callback from libbinrec to look up translated blocks for function chaining. */ void * brChainLookup(BinrecCore *core, ppcaddr_t address) { auto block = core->backend->getCodeBlock(core, address); if (!block) { return nullptr; } return block->code; } /** * Callback from libbinrec to handle time base reads. */ uint64_t brTimeBaseHandler(BinrecCore *core) { return core->tb(); } /** * Callback from libbinrec to handle system calls. */ BinrecCore * brSyscallHandler(BinrecCore *core, espresso::Instruction instr) { core->systemCallStackHead = core->gpr[1]; auto handler = cpu::getSystemCallHandler(instr.kcn); auto newCore = handler(core, instr.kcn); // We might have been rescheduled on a new core. core = reinterpret_cast(newCore); // If the next instruction is a blr, execute it ourselves rather than // spending the overhead of calling into JIT for just that instruction. auto next_instr = mem::read(core->nia); if (next_instr == 0x4E800020) { core->nia = core->lr; } #ifdef DECAF_JIT_ALLOW_PROFILING core->calledHLE = true; // Suppress profiling for this call. #endif return core; } /** * Callback from libbinrec to handle PPC trap exceptions. */ BinrecCore * brTrapHandler(BinrecCore *core) { if (!cpu::hasBreakpoint(core->nia)) { decaf_abort(fmt::format("Game raised a trap exception at 0x{:08X}.", core->nia)); } // If we have a breakpoint, we will fall back to interpreter to handle it. return core; } /** * Callback from libbinrec to handle log output. */ void brLog(void *, binrec::LogLevel level, const char *message) { switch (level) { case BINREC_LOGLEVEL_ERROR: gLog->error("[libbinrec] {}", message); break; case BINREC_LOGLEVEL_WARNING: gLog->warn("[libbinrec] {}", message); break; case BINREC_LOGLEVEL_INFO: // Nothing really important here, so output as debug instead gLog->debug("[libbinrec] {}", message); break; } } } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/binrec/jit_binrec.h ================================================ #pragma once #include "state.h" #include "mem.h" #include "espresso/espresso_instruction.h" #include "jit/jit_codecache.h" #include "jit/jit_backend.h" #include #include #include namespace cpu { namespace jit { struct BinrecOptimisationFlags { bool useChaining = false; unsigned int common = 0; unsigned int guest = 0; unsigned int host = 0; }; class BinrecBackend; struct VerifyBuffer; struct BinrecCore : public Core { BinrecBackend *backend; // JIT callback functions void *(*chainLookup)(BinrecCore *, ppcaddr_t); bool (*branchCallback)(BinrecCore *, ppcaddr_t); uint64_t (*mftbHandler)(BinrecCore *); BinrecCore *(*scHandler)(BinrecCore *, espresso::Instruction); BinrecCore *(*trapHandler)(BinrecCore *); // Lookup tables for fres/frsqrte instructions const uint16_t *fresTable; const uint16_t *frsqrteTable; // JIT verification buffer (local to jit::resume()) VerifyBuffer *verifyBuffer; // HLE call flag (for JIT profiler) bool calledHLE; //! Trap Handler hit a breakpoint. bool hitBreakpoint; }; using BinrecHandle = binrec::Handle; using BinrecEntry = BinrecCore * (*)(BinrecCore *core, uintptr_t membase); class BinrecBackend : public JitBackend { public: BinrecBackend(size_t codeCacheSize, size_t dataCacheSize); ~BinrecBackend() override; Core * initialiseCore(uint32_t id) override; void clearCache(uint32_t address, uint32_t size) override; void resumeExecution() override; void addReadOnlyRange(uint32_t address, uint32_t size) override; bool sampleStats(JitStats &stats) override; void resetProfileStats() override; void setProfilingMask(unsigned mask) override; unsigned getProfilingMask() override; public: void setOptFlags(const std::vector &optList); void setVerifyEnabled(bool enabled, uint32_t address = 0); CodeBlock * getCodeBlock(BinrecCore *core, uint32_t address); protected: BinrecHandle *createBinrecHandle(); inline CodeBlock * getCodeBlockFast(BinrecCore *core, uint32_t address); CodeBlock * checkForCodeBlockTrampoline(uint32_t address); void resumeVerifyExecution(); void verifyInit(Core *core, VerifyBuffer *verifyBuf); void verifyPre(Core *core, VerifyBuffer *verifyBuf, uint32_t cia, uint32_t instr); void verifyPost(Core *core, VerifyBuffer *verifyBuf, uint32_t cia, uint32_t instr); static void brVerifyPreHandler(BinrecCore *core, uint32_t address); static void brVerifyPostHandler(BinrecCore *core, uint32_t address); private: CodeCache mCodeCache; std::array mHandles; BinrecOptimisationFlags mOptFlags; std::vector> mReadOnlyRanges; std::atomic mTotalProfileTime { 0 }; uint32_t mProfilingMask = 0; bool mVerifyEnabled = false; uint32_t mVerifyAddress = 0; }; } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/binrec/jit_binrec_opt.cpp ================================================ #include "jit_binrec.h" #include #include #include #include namespace cpu { namespace jit { struct OptFlagInfo { enum { OPTFLAG_COMMON, OPTFLAG_GUEST, OPTFLAG_HOST, OPTFLAG_CHAIN } type; unsigned int value; }; static const std::map sOptFlags = { {"BASIC", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::BASIC}}, {"DECONDITION", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::DECONDITION}}, {"DEEP_DATA_FLOW", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::DEEP_DATA_FLOW}}, {"DSE", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::DSE}}, {"DSE_FP", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::DSE_FP}}, {"FOLD_CONSTANTS", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::FOLD_CONSTANTS}}, {"FOLD_FP_CONSTANTS", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::FOLD_FP_CONSTANTS}}, {"NATIVE_IEEE_NAN", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::NATIVE_IEEE_NAN}}, {"NATIVE_IEEE_UNDERFLOW", {OptFlagInfo::OPTFLAG_COMMON, binrec::Optimize::NATIVE_IEEE_UNDERFLOW}}, {"PPC_ASSUME_NO_SNAN", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::ASSUME_NO_SNAN}}, {"PPC_CONSTANT_GQRS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::CONSTANT_GQRS}}, {"PPC_DETECT_FCFI_EMUL", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::DETECT_FCFI_EMUL}}, {"PPC_FAST_FCTIW", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::FAST_FCTIW}}, {"PPC_FAST_FMADDS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::FAST_FMADDS}}, {"PPC_FAST_FMULS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::FAST_FMULS}}, {"PPC_FAST_STFS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::FAST_STFS}}, {"PPC_FNMADD_ZERO_SIGN", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::FNMADD_ZERO_SIGN}}, {"PPC_FORWARD_LOADS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::FORWARD_LOADS}}, {"PPC_IGNORE_FPSCR_VXFOO", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::IGNORE_FPSCR_VXFOO}}, {"PPC_NATIVE_RECIPROCAL", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::NATIVE_RECIPROCAL}}, {"PPC_NO_FPSCR_STATE", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::NO_FPSCR_STATE}}, {"PPC_PAIRED_LWARX_STWCX", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::PAIRED_LWARX_STWCX}}, {"PPC_PS_STORE_DENORMALS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::PS_STORE_DENORMALS}}, {"PPC_SC_BLR", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::SC_BLR}}, {"PPC_SINGLE_PREC_INPUTS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::SINGLE_PREC_INPUTS}}, {"PPC_TRIM_CR_STORES", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::TRIM_CR_STORES}}, {"PPC_USE_SPLIT_FIELDS", {OptFlagInfo::OPTFLAG_GUEST, binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS}}, {"X86_ADDRESS_OPERANDS", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::ADDRESS_OPERANDS}}, {"X86_BRANCH_ALIGNMENT", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::BRANCH_ALIGNMENT}}, {"X86_CONDITION_CODES", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::CONDITION_CODES}}, {"X86_FIXED_REGS", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::FIXED_REGS}}, {"X86_FORWARD_CONDITIONS", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::FORWARD_CONDITIONS}}, {"X86_MERGE_REGS", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::MERGE_REGS}}, {"X86_STORE_IMMEDIATE", {OptFlagInfo::OPTFLAG_HOST, binrec::Optimize::HostX86::STORE_IMMEDIATE}}, // Maps to sUseChaining instead of a flag value {"CHAIN", {OptFlagInfo::OPTFLAG_CHAIN}}, }; void BinrecBackend::setOptFlags(const std::vector &optList) { mOptFlags.common = 0; mOptFlags.guest = 0; mOptFlags.host = 0; mOptFlags.useChaining = false; for (const auto &i : optList) { auto flag = sOptFlags.find(i); if (flag == sOptFlags.end()) { gLog->warn("Unknown optimization flag: {}", i); continue; } switch (flag->second.type) { case OptFlagInfo::OPTFLAG_CHAIN: mOptFlags.useChaining = true; break; case OptFlagInfo::OPTFLAG_COMMON: mOptFlags.common |= flag->second.value; break; case OptFlagInfo::OPTFLAG_GUEST: mOptFlags.guest |= flag->second.value; break; case OptFlagInfo::OPTFLAG_HOST: mOptFlags.host |= flag->second.value; break; } } } void BinrecBackend::setVerifyEnabled(bool enabled, uint32_t address) { mVerifyEnabled = enabled; mVerifyAddress = address; } } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/binrec/jit_binrec_verify.cpp ================================================ #include "cpu.h" #include "cpu_internal.h" #include "state.h" #include "espresso/espresso_disassembler.h" #include "espresso/espresso_instructionset.h" #include "interpreter/interpreter.h" #include "interpreter/interpreter_float.h" #include "interpreter/interpreter_insreg.h" #include "jit_binrec.h" #include "mem.h" #include "mmu.h" #include #include #include #include #include #include #include // Define this to ignore differences in generated QNaN sign bits (PowerPC // 0x7FF8...0 vs Intel 0xFFF8...0) when any of the NATIVE_IEEE_NAN, // PPC_IGNORE_FPSCR_VXFOO, or PPC_NO_FPSCR_STATE optimizations are // enabled. If this is defined and a difference in generated QNaN sign // bit is found, the JIT-generated value will be copied to the verify // block, which may mask JIT bugs! #define FIXUP_OPTIMIZED_QNAN namespace cpu::jit { struct VerifyBuffer { //! Copy of core state before JIT execution Core coreCopy; //! True if current instruction touches memory bool isMemoryInstr; //! Address accessed by instruction (if any) uint32_t memoryAddress; //! Number of bytes accessed by instruction uint32_t memorySize; //! Copy of memory before JIT execution uint8_t preJitBuffer[128]; //! Copy of memory as written by JIT code uint8_t postJitBuffer[128]; }; void BinrecBackend::resumeVerifyExecution() { auto core = reinterpret_cast(this_core::state()); if (!core->verifyBuffer) { core->verifyBuffer = new VerifyBuffer(); } do { if (core->interrupt.load()) { this_core::checkInterrupts(); core = reinterpret_cast(this_core::state()); } const ppcaddr_t address = core->nia; auto codeBlock = core->backend->getCodeBlock(core, core->nia); if (codeBlock) { if (!mVerifyAddress || address == mVerifyAddress) { verifyInit(core, core->verifyBuffer); } auto entry = reinterpret_cast(codeBlock->code); entry(core, getBaseVirtualAddress()); } else { interpreter::step_one(core); } core = reinterpret_cast(this_core::state()); } while (core->nia != CALLBACK_ADDR); } /** * Callback from libbinrec to for block pre execution verify callback. */ void BinrecBackend::brVerifyPreHandler(BinrecCore *core, uint32_t address) { auto instr = mem::read(address); core->backend->verifyPre(core, core->verifyBuffer, address, instr); } /** * Callback from libbinrec to for block post execution verify callback. */ void BinrecBackend::brVerifyPostHandler(BinrecCore *core, uint32_t address) { auto instr = mem::read(address); core->backend->verifyPost(core, core->verifyBuffer, address, instr); } // Ensure load/store verification is not broken by other threads static std::mutex memoryLock; // Return whether the given instruction accesses memory. We include HLE // calls (the kc pseudoinstruction) in this set since HLE code could (and // generally will) touch guest memory. static bool isMemoryInstruction(uint32_t instr) { auto data = espresso::decodeInstruction(instr); return data->id == espresso::InstructionID::lbz || data->id == espresso::InstructionID::lbzu || data->id == espresso::InstructionID::lbzx || data->id == espresso::InstructionID::lbzux || data->id == espresso::InstructionID::lhz || data->id == espresso::InstructionID::lhzu || data->id == espresso::InstructionID::lhzx || data->id == espresso::InstructionID::lhzux || data->id == espresso::InstructionID::lhbrx || data->id == espresso::InstructionID::lha || data->id == espresso::InstructionID::lhau || data->id == espresso::InstructionID::lhax || data->id == espresso::InstructionID::lhaux || data->id == espresso::InstructionID::lwz || data->id == espresso::InstructionID::lwzu || data->id == espresso::InstructionID::lwzx || data->id == espresso::InstructionID::lwzux || data->id == espresso::InstructionID::lwbrx || data->id == espresso::InstructionID::lwarx || data->id == espresso::InstructionID::lfs || data->id == espresso::InstructionID::lfsu || data->id == espresso::InstructionID::lfsx || data->id == espresso::InstructionID::lfsux || data->id == espresso::InstructionID::lfd || data->id == espresso::InstructionID::lfdu || data->id == espresso::InstructionID::lfdx || data->id == espresso::InstructionID::lfdux || data->id == espresso::InstructionID::lmw || data->id == espresso::InstructionID::lswi || data->id == espresso::InstructionID::lswx || data->id == espresso::InstructionID::psq_l || data->id == espresso::InstructionID::psq_lu || data->id == espresso::InstructionID::psq_lx || data->id == espresso::InstructionID::psq_lux || data->id == espresso::InstructionID::stb || data->id == espresso::InstructionID::stbu || data->id == espresso::InstructionID::stbx || data->id == espresso::InstructionID::stbux || data->id == espresso::InstructionID::sth || data->id == espresso::InstructionID::sthu || data->id == espresso::InstructionID::sthx || data->id == espresso::InstructionID::sthux || data->id == espresso::InstructionID::sthbrx || data->id == espresso::InstructionID::stw || data->id == espresso::InstructionID::stwu || data->id == espresso::InstructionID::stwx || data->id == espresso::InstructionID::stwux || data->id == espresso::InstructionID::stwbrx || data->id == espresso::InstructionID::stwcx || data->id == espresso::InstructionID::stfs || data->id == espresso::InstructionID::stfsu || data->id == espresso::InstructionID::stfsx || data->id == espresso::InstructionID::stfsux || data->id == espresso::InstructionID::stfiwx || data->id == espresso::InstructionID::stfd || data->id == espresso::InstructionID::stfdu || data->id == espresso::InstructionID::stfdx || data->id == espresso::InstructionID::stfdux || data->id == espresso::InstructionID::stmw || data->id == espresso::InstructionID::stswi || data->id == espresso::InstructionID::stswx || data->id == espresso::InstructionID::dcbz || data->id == espresso::InstructionID::dcbz_l || data->id == espresso::InstructionID::psq_st || data->id == espresso::InstructionID::psq_stu || data->id == espresso::InstructionID::psq_stx || data->id == espresso::InstructionID::psq_stux || data->id == espresso::InstructionID::kc ; } static void lookupMemoryTarget(Core *core, VerifyBuffer *verifyBuf, espresso::Instruction instr) { auto coreCopy = &verifyBuf->coreCopy; auto data = espresso::decodeInstruction(instr); if (!data) { // Instruction word was invalid verifyBuf->memorySize = 0; verifyBuf->memoryAddress = 0; return; } // Calculate size and address separately to reduce code duplication switch (data->id) { case espresso::InstructionID::stb: case espresso::InstructionID::stbu: case espresso::InstructionID::stbx: case espresso::InstructionID::stbux: verifyBuf->memorySize = 1; break; case espresso::InstructionID::sth: case espresso::InstructionID::sthu: case espresso::InstructionID::sthx: case espresso::InstructionID::sthux: case espresso::InstructionID::sthbrx: verifyBuf->memorySize = 2; break; case espresso::InstructionID::stw: case espresso::InstructionID::stwu: case espresso::InstructionID::stwx: case espresso::InstructionID::stwux: case espresso::InstructionID::stwbrx: case espresso::InstructionID::stwcx: case espresso::InstructionID::stfs: case espresso::InstructionID::stfsu: case espresso::InstructionID::stfsx: case espresso::InstructionID::stfsux: case espresso::InstructionID::stfiwx: verifyBuf->memorySize = 4; break; case espresso::InstructionID::stfd: case espresso::InstructionID::stfdu: case espresso::InstructionID::stfdx: case espresso::InstructionID::stfdux: verifyBuf->memorySize = 8; break; case espresso::InstructionID::stmw: verifyBuf->memorySize = 4 * (32 - instr.rS); break; case espresso::InstructionID::stswi: verifyBuf->memorySize = instr.nb; break; case espresso::InstructionID::stswx: verifyBuf->memorySize = coreCopy->xer.byteCount; break; case espresso::InstructionID::dcbz: case espresso::InstructionID::dcbz_l: verifyBuf->memorySize = 32; break; case espresso::InstructionID::psq_stx: case espresso::InstructionID::psq_stux: { auto i = instr.qi; auto w = instr.qw; auto numStores = (w == 1) ? 1 : 2; auto stt = static_cast(core->gqr[i].st_type); if (stt == espresso::QuantizedDataType::Unsigned8 || stt == espresso::QuantizedDataType::Signed8) { verifyBuf->memorySize = 1 * numStores; } else if (stt == espresso::QuantizedDataType::Unsigned16 || stt == espresso::QuantizedDataType::Signed16) { verifyBuf->memorySize = 2 * numStores; } else { verifyBuf->memorySize = 4 * numStores; } break; } case espresso::InstructionID::psq_st: case espresso::InstructionID::psq_stu: { auto i = instr.i; auto w = instr.w; auto numStores = (w == 1) ? 1 : 2; auto stt = static_cast(core->gqr[i].st_type); if (stt == espresso::QuantizedDataType::Unsigned8 || stt == espresso::QuantizedDataType::Signed8) { verifyBuf->memorySize = 1 * numStores; } else if (stt == espresso::QuantizedDataType::Unsigned16 || stt == espresso::QuantizedDataType::Signed16) { verifyBuf->memorySize = 2 * numStores; } else { verifyBuf->memorySize = 4 * numStores; } break; } default: verifyBuf->memorySize = 0; verifyBuf->memoryAddress = 0; return; } switch (data->id) { case espresso::InstructionID::stb: case espresso::InstructionID::stbu: case espresso::InstructionID::sth: case espresso::InstructionID::sthu: case espresso::InstructionID::stw: case espresso::InstructionID::stwu: case espresso::InstructionID::stmw: case espresso::InstructionID::stfs: case espresso::InstructionID::stfsu: case espresso::InstructionID::stfd: case espresso::InstructionID::stfdu: if (instr.rA == 0) { verifyBuf->memoryAddress = 0; } else { verifyBuf->memoryAddress = coreCopy->gpr[instr.rA]; } verifyBuf->memoryAddress += sign_extend<16, int32_t>(instr.d); break; case espresso::InstructionID::psq_st: case espresso::InstructionID::psq_stu: if (instr.rA == 0) { verifyBuf->memoryAddress = 0; } else { verifyBuf->memoryAddress = coreCopy->gpr[instr.rA]; } verifyBuf->memoryAddress += sign_extend<12, int32_t>(instr.qd); break; case espresso::InstructionID::stbx: case espresso::InstructionID::stbux: case espresso::InstructionID::sthx: case espresso::InstructionID::sthux: case espresso::InstructionID::sthbrx: case espresso::InstructionID::stwx: case espresso::InstructionID::stwux: case espresso::InstructionID::stwbrx: case espresso::InstructionID::stwcx: case espresso::InstructionID::stswx: case espresso::InstructionID::stfsx: case espresso::InstructionID::stfsux: case espresso::InstructionID::stfiwx: case espresso::InstructionID::stfdx: case espresso::InstructionID::stfdux: case espresso::InstructionID::psq_stx: case espresso::InstructionID::psq_stux: if (instr.rA == 0) { verifyBuf->memoryAddress = 0; } else { verifyBuf->memoryAddress = coreCopy->gpr[instr.rA]; } verifyBuf->memoryAddress += coreCopy->gpr[instr.rB]; break; case espresso::InstructionID::stswi: if (instr.rA == 0) { verifyBuf->memoryAddress = 0; } else { verifyBuf->memoryAddress = coreCopy->gpr[instr.rA]; } break; case espresso::InstructionID::dcbz: case espresso::InstructionID::dcbz_l: if (instr.rA == 0) { verifyBuf->memoryAddress = 0; } else { verifyBuf->memoryAddress = coreCopy->gpr[instr.rA]; } verifyBuf->memoryAddress += coreCopy->gpr[instr.rB]; verifyBuf->memoryAddress = align_down(verifyBuf->memoryAddress, 32); break; default: decaf_abort("Missing memoryAddress calculation"); } } // Helper for verifyPost() so we don't have to pay the disassembly cost // if the instruction worked as expected. static std::string disassemble(uint32_t instr, uint32_t address) { espresso::Disassembly disassembly; espresso::disassemble(static_cast(instr), disassembly, address); return espresso::disassemblyToText(disassembly); } static bool shouldVerify(const espresso::InstructionInfo *data) { return data != nullptr && data->id != espresso::InstructionID::kc && data->id != espresso::InstructionID::lwarx && data->id != espresso::InstructionID::mftb && data->id != espresso::InstructionID::stwcx; } void BinrecBackend::verifyInit(Core *core, VerifyBuffer *verifyBuf) { // We copy the core state once when entering the JIT block, then call // the interpreter repeatedly on this copy. This lets the interpreter // behave correctly even if the JIT callbacks don't fully update the // state due to optimizations (and also avoids the cost of a copy on // every instruction). memcpy(static_cast(&verifyBuf->coreCopy), static_cast(core), sizeof(CoreRegs)); // Regenerate FEX and VX because libbinrec doesn't store them in the // state block. updateFEX_VX(&verifyBuf->coreCopy); } void BinrecBackend::verifyPre(Core *core, VerifyBuffer *verifyBuf, uint32_t cia, uint32_t instr) { if (!shouldVerify(espresso::decodeInstruction(instr))) { return; } // If entering a load/store instruction, lock out other cores so we can // safely verify the instruction's behavior. verifyBuf->isMemoryInstr = isMemoryInstruction(instr); if (verifyBuf->isMemoryInstr) { memoryLock.lock(); } // Save the initial contents of any memory touched by the instruction. lookupMemoryTarget(core, verifyBuf, static_cast(instr)); if (verifyBuf->memorySize > 0) { decaf_check(verifyBuf->memorySize <= sizeof(verifyBuf->preJitBuffer)); memcpy(verifyBuf->preJitBuffer, mem::translate(verifyBuf->memoryAddress), verifyBuf->memorySize); } } void BinrecBackend::verifyPost(Core *core, VerifyBuffer *verifyBuf, uint32_t cia, uint32_t instr) { auto data = espresso::decodeInstruction(instr); auto instrId = data ? data->id : static_cast(-1); if (!shouldVerify(data)) { // We can't repeat the instruction without causing side effects, so // assume it worked and reinitialize the verify buffer from the // current core state, taking into account any optimizations that // may leave the active state block not up to date. auto savedCR = verifyBuf->coreCopy.cr; auto savedFPSCR = verifyBuf->coreCopy.fpscr; verifyInit(core, verifyBuf); if (mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS) { verifyBuf->coreCopy.cr.value = savedCR.value; // stwcx. will properly update cr0.eq even if USE_SPLIT_FIELDS, // so copy that bit across. if (instrId == espresso::InstructionID::stwcx) { verifyBuf->coreCopy.cr.value &= ~(1<<29); verifyBuf->coreCopy.cr.value |= core->cr.value & (1<<29); } } if (mOptFlags.guest & binrec::Optimize::GuestPPC::NO_FPSCR_STATE) { verifyBuf->coreCopy.fpscr.value &= 0xFF; verifyBuf->coreCopy.fpscr.value |= savedFPSCR.value & 0xFFFFFF00; } else if (mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS) { verifyBuf->coreCopy.fpscr.fr = savedFPSCR.fr; verifyBuf->coreCopy.fpscr.fi = savedFPSCR.fi; verifyBuf->coreCopy.fpscr.fprf = savedFPSCR.fprf; } else if (mOptFlags.common & binrec::Optimize::FOLD_FP_CONSTANTS) { verifyBuf->coreCopy.fpscr.fr = savedFPSCR.fr; verifyBuf->coreCopy.fpscr.fi = savedFPSCR.fi; } return; } auto coreCopy = &verifyBuf->coreCopy; if (verifyBuf->memorySize > 0) { // Save the data written by JIT code... memcpy(verifyBuf->postJitBuffer, mem::translate(verifyBuf->memoryAddress), verifyBuf->memorySize); // ... and restore the original data for the interpreter. memcpy(mem::translate(verifyBuf->memoryAddress), verifyBuf->preJitBuffer, verifyBuf->memorySize); } // Execute the instruction using the interpreter implementation on the // saved copy of the core state. if (data) { auto fptr = interpreter::getInstructionHandler(instrId); decaf_assert(fptr, fmt::format("Unimplemented interpreter instruction {}", data->name)); coreCopy->cia = cia; coreCopy->nia = cia + 4; fptr(coreCopy, instr); } uint8_t expectedMemory[128]; if (verifyBuf->memorySize > 0) { // Save the expected data (as written by the interpreter). memcpy(expectedMemory, mem::translate(verifyBuf->memoryAddress), verifyBuf->memorySize); } // If this was a load/store instruction, let other cores proceed again. if (verifyBuf->isMemoryInstr) { memoryLock.unlock(); } // Check all registers and any touched memory for discrepancies. decaf_assert(core->nia == coreCopy->nia, fmt::format("Wrong NIA at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", cia, disassemble(instr, cia), core->nia, coreCopy->nia)); for (auto i = 0; i < 32; ++i) { decaf_assert(core->gpr[i] == coreCopy->gpr[i], fmt::format("Wrong value in GPR {} at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", i, cia, disassemble(instr, cia), core->gpr[i], coreCopy->gpr[i])); } for (auto i = 0; i < 32; ++i) { #ifdef FIXUP_OPTIMIZED_QNAN if ((mOptFlags.common & binrec::Optimize::NATIVE_IEEE_NAN) || (mOptFlags.guest & (binrec::Optimize::GuestPPC::IGNORE_FPSCR_VXFOO | binrec::Optimize::GuestPPC::NO_FPSCR_STATE))) { if (core->fpr[i].idw == UINT64_C(0xFFF8000000000000) && coreCopy->fpr[i].idw == UINT64_C(0x7FF8000000000000)) { coreCopy->fpr[i].idw = core->fpr[i].idw; } } #endif decaf_assert(core->fpr[i].idw == coreCopy->fpr[i].idw, fmt::format("Wrong value in FPR {} at 0x{:X}: {}\n Found: 0x{:08X}_{:08X} ({:g})\n Expected: 0x{:08X}_{:08X} ({:g})", i, cia, disassemble(instr, cia), static_cast(core->fpr[i].idw >> 32), static_cast(core->fpr[i].idw), core->fpr[i].value, static_cast(coreCopy->fpr[i].idw >> 32), static_cast(coreCopy->fpr[i].idw), coreCopy->fpr[i].value)); } for (auto i = 0; i < 32; ++i) { #ifdef FIXUP_OPTIMIZED_QNAN if ((mOptFlags.common & binrec::Optimize::NATIVE_IEEE_NAN) || (mOptFlags.guest & (binrec::Optimize::GuestPPC::IGNORE_FPSCR_VXFOO | binrec::Optimize::GuestPPC::NO_FPSCR_STATE))) { if (core->fpr[i].idw_paired1 == UINT64_C(0xFFF8000000000000) && coreCopy->fpr[i].idw_paired1 == UINT64_C(0x7FF8000000000000)) { coreCopy->fpr[i].idw_paired1 = core->fpr[i].idw_paired1; } } #endif decaf_assert(core->fpr[i].idw_paired1 == coreCopy->fpr[i].idw_paired1, fmt::format("Wrong value in PS1 {} at 0x{:X}: {}\n Found: 0x{:08X}_{:08X} ({:g})\n Expected: 0x{:08X}_{:08X} ({:g})", i, cia, disassemble(instr, cia), static_cast(core->fpr[i].idw_paired1 >> 32), static_cast(core->fpr[i].idw_paired1), core->fpr[i].paired1, static_cast(coreCopy->fpr[i].idw_paired1 >> 32), static_cast(coreCopy->fpr[i].idw_paired1), coreCopy->fpr[i].paired1)); } for (auto i = 0; i < 8; ++i) { decaf_assert(core->gqr[i].value == coreCopy->gqr[i].value, fmt::format("Wrong value in GQR {} at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", i, cia, disassemble(instr, cia), core->gqr[i].value, coreCopy->gqr[i].value)); } decaf_assert(core->lr == coreCopy->lr, fmt::format("Wrong value in LR at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", cia, disassemble(instr, cia), core->lr, coreCopy->lr)); decaf_assert(core->ctr == coreCopy->ctr, fmt::format("Wrong value in CTR at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", cia, disassemble(instr, cia), core->ctr, coreCopy->ctr)); // Skip CR check if split fields are enabled, because the value in CR // may not be up to date. if (!(mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS)) { decaf_assert(core->cr.value == coreCopy->cr.value, fmt::format("Wrong value in CR at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", cia, disassemble(instr, cia), core->cr.value, coreCopy->cr.value)); } decaf_assert(core->xer.value == coreCopy->xer.value, fmt::format("Wrong value in XER at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", cia, disassemble(instr, cia), core->xer.value, coreCopy->xer.value)); // Skip FPRF check for fctiw[z] and mffs, which leave it undefined (and // we don't attempt to mimic whatever the hardware actually does for // them). For these instructions, we copy the JIT state into the // local copy so as not to trigger spurious failures on subsequent // instructions. if (instrId == espresso::InstructionID::fctiw || instrId == espresso::InstructionID::fctiwz || instrId == espresso::InstructionID::mffs) { coreCopy->fpscr.value &= ~0x0001F000; coreCopy->fpscr.value |= core->fpscr.value & 0x0001F000; } // Regenerate FEX and VX because libbinrec doesn't store them in the // state block. updateFEX_VX(core); // Ignore parts of FPSCR which may not be up to date based on enabled // optimizations. uint32_t fpscrMask; if (mOptFlags.guest & binrec::Optimize::GuestPPC::NO_FPSCR_STATE) { fpscrMask = 0xFF; } else if (mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS) { fpscrMask = ~0x0007F000u; } else if (mOptFlags.common & binrec::Optimize::FOLD_FP_CONSTANTS) { fpscrMask = ~0x00060000u; } else { fpscrMask = ~0u; } decaf_assert((core->fpscr.value & fpscrMask) == (coreCopy->fpscr.value & fpscrMask), fmt::format("Wrong value in FPSCR at 0x{:X}: {}\n Found: 0x{:08X}\n Expected: 0x{:08X}", cia, disassemble(instr, cia), core->fpscr.value, coreCopy->fpscr.value)); for (auto i = 0u; i < verifyBuf->memorySize; ++i) { auto found = verifyBuf->postJitBuffer[i]; auto expected = expectedMemory[i]; if (found != expected) { // Try and make the output reasonably useful std::string addressStr = fmt::format("0x{:X}", verifyBuf->memoryAddress); std::string foundStr, expectedStr; if (instrId == espresso::InstructionID::stswi || instrId == espresso::InstructionID::stswx) { addressStr += fmt::format("+0x{:X}", i); foundStr = fmt::format("0x{:02X}", found); expectedStr = fmt::format("0x{:02X}", expected); } else if (instrId == espresso::InstructionID::stmw) { auto offset = align_down(i, 4); addressStr += fmt::format("+0x{:X}", offset); foundStr = fmt::format("0x{:08X}", byte_swap(*reinterpret_cast(&verifyBuf->postJitBuffer[offset]))); expectedStr = fmt::format("0x{:08X}", byte_swap(*reinterpret_cast(&expectedMemory[offset]))); } else if (verifyBuf->memorySize == 8) { foundStr = fmt::format("0x{:08X}_{:08X}", byte_swap(*reinterpret_cast(verifyBuf->postJitBuffer)), byte_swap(*reinterpret_cast(&verifyBuf->postJitBuffer[4]))); expectedStr = fmt::format("0x{:08X}_{:08X}", byte_swap(*reinterpret_cast(expectedMemory)), byte_swap(*reinterpret_cast(&expectedMemory[4]))); } else if (verifyBuf->memorySize == 4) { foundStr = fmt::format("0x{:08X}", byte_swap(*reinterpret_cast(verifyBuf->postJitBuffer))); expectedStr = fmt::format("0x{:08X}", byte_swap(*reinterpret_cast(expectedMemory))); } else if (verifyBuf->memorySize == 2) { foundStr = fmt::format("0x{:04X}", byte_swap(*reinterpret_cast(verifyBuf->postJitBuffer))); expectedStr = fmt::format("0x{:04X}", byte_swap(*reinterpret_cast(expectedMemory))); } else { foundStr = fmt::format("0x{:02X}", found); expectedStr = fmt::format("0x{:02X}", expected); } decaf_abort(fmt::format("Wrong data written to {} at 0x{:X}: {}\n Found: {}\n Expected: {}", addressStr, cia, disassemble(instr, cia), foundStr, expectedStr)); } } } } // namespace cpu::jit ================================================ FILE: src/libcpu/src/jit/jit.cpp ================================================ #include "jit.h" #include "jit_backend.h" namespace cpu { namespace jit { static JitBackend * sBackend = nullptr; /** * Set the JIT backend to use. */ void setBackend(JitBackend *backend) { sBackend = backend; } /** * Return the current active JIT backend. */ JitBackend * getBackend() { return sBackend; } /** * Initialize JIT-related fields in a Core instance. */ Core * initialiseCore(uint32_t id) { if (sBackend) { return sBackend->initialiseCore(id); } else { return nullptr; } } /** * Clear the JIT cache for the given address range. * * This function must not be called while any JIT code is being executed. * There is no guarentee that only the selected address range will be cleared. */ void clearCache(uint32_t address, uint32_t size) { if (sBackend) { sBackend->clearCache(address, size); } } /** * Mark the given range of addresses as read-only for JIT optimization. */ void addReadOnlyRange(uint32_t address, uint32_t size) { if (sBackend) { sBackend->addReadOnlyRange(address, size); } } /** * Begin executing guest code on the current core. */ void resume() { sBackend->resumeExecution(); } } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/jit.h ================================================ #pragma once #include "jit_backend.h" namespace cpu { namespace jit { JitBackend * getBackend(); void setBackend(JitBackend *backend); Core * initialiseCore(uint32_t id); void clearCache(uint32_t address, uint32_t size); void addReadOnlyRange(uint32_t address, uint32_t size); void resume(); } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/jit_backend.h ================================================ #pragma once #include "jit_stats.h" #include "state.h" #include namespace cpu { namespace jit { class JitBackend { public: virtual ~JitBackend() = default; //! Initialise core specific state. virtual Core * initialiseCore(uint32_t id) = 0; //! Clear any cached code for specified memory range. virtual void clearCache(uint32_t address, uint32_t size) = 0; //! Resume execution on current core. virtual void resumeExecution() = 0; //! Mark a region of memory as read only. virtual void addReadOnlyRange(uint32_t address, uint32_t size) = 0; //! Sample JIT stats. virtual bool sampleStats(JitStats &stats) = 0; //! Reset JIT profiling stats. virtual void resetProfileStats() = 0; //! Set which cores to profile virtual void setProfilingMask(unsigned mask) = 0; //! Get which cores are being profiled virtual unsigned getProfilingMask() = 0; private: }; } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/jit_codecache.cpp ================================================ #include "jit_codecache.h" #include "jit_stats.h" #include #include #include #include #include #include #include #include #include #include namespace cpu { namespace jit { CodeCache::~CodeCache() { free(); } /** * Initialise the code cache. */ bool CodeCache::initialise(size_t codeSize, size_t dataSize) { mReserveAddress = 0; mReserveSize = codeSize + dataSize; for (auto n = 2; n < 32; ++n) { auto base = 0x100000000 * n; if (platform::reserveMemory(base, mReserveSize)) { mReserveAddress = base; break; } } decaf_assert(mReserveAddress, "Failed to map memory for JIT"); mCodeAllocator.flags = platform::ProtectFlags::ReadWriteExecute; mCodeAllocator.baseAddress = mReserveAddress; mCodeAllocator.reserved = codeSize; mCodeAllocator.growthSize = 4 * 1024 * 1024; mCodeAllocator.committed = 0; mCodeAllocator.allocated = 0; mDataAllocator.flags = platform::ProtectFlags::ReadWrite; mDataAllocator.baseAddress = mReserveAddress + codeSize; mDataAllocator.reserved = dataSize; mDataAllocator.growthSize = 1 * 1024 * 1024; mDataAllocator.committed = 0; mDataAllocator.allocated = 0; mFastIndex = new std::atomic *>[Level1Size]; std::memset(mFastIndex, 0, sizeof(mFastIndex[0]) * Level1Size); return true; } /** * Clear the code cache. * * This will also unregister any unwind info with Windows. */ void CodeCache::clear() { #ifdef PLATFORM_WINDOWS // Delete any registered function tables for (auto offset = 0u; offset < mDataAllocator.allocated; offset += sizeof(CodeBlock)) { auto blockAddress = mDataAllocator.baseAddress + offset; auto block = reinterpret_cast(blockAddress); RtlDeleteFunctionTable(&block->unwindInfo.rtlFuncTable); } #endif // Reset the allocators, don't bother uncommitting their memory. mDataAllocator.allocated = 0; mCodeAllocator.allocated = 0; // Clear fast index, don't bother unallocating memory. if (mFastIndex) { for (auto i = 0u; i < Level1Size; ++i) { auto level2 = mFastIndex[i].load(); for (auto j = 0u; level2 && j < Level2Size; ++j) { level2[j].store(CodeBlockIndexUncompiled); } mFastIndex[i].store(nullptr); } std::memset(mFastIndex, 0, sizeof(mFastIndex[0]) * Level1Size); } } /** * Invalidate a region of code. * * Because it's super complicated to do properly let's just be a leaky fuck, * for now our "invalidation" is really just forgetting that we compiled a block. */ void CodeCache::invalidate(uint32_t base, uint32_t size) { // Find any block containing this address and invalidate them! auto blocks = getCompiledCodeBlocks(); for (auto &block : blocks) { auto start = block.address; auto end = start + 4096; // FIXME: Just assume 4096 limit for now.. if (base + size < start) { continue; } if (base >= end) { continue; } getIndexPointer(block.address)->store(CodeBlockIndexUncompiled); } } /** * Free the memory we are using for our JIT. */ void CodeCache::free() { clear(); if (mFastIndex) { for (auto i = 0u; i < Level1Size; ++i) { auto level2 = mFastIndex[i].load(); if (level2) { delete[] level2; } } delete[] mFastIndex; mFastIndex = nullptr; } if (mReserveAddress) { platform::freeMemory(mReserveAddress, mReserveSize); mReserveAddress = 0; mReserveSize = 0; } } /** * Returns the amount of data allocated in the code cache. */ size_t CodeCache::getCodeCacheSize() { return mCodeAllocator.allocated; } /** * Returns the amount of data allocated in the data cache. */ size_t CodeCache::getDataCacheSize() { return mDataAllocator.allocated; } /** * Returns a list of all compiled code blocks. */ gsl::span CodeCache::getCompiledCodeBlocks() { auto count = mDataAllocator.allocated.load() / sizeof(CodeBlock); auto first = reinterpret_cast(mDataAllocator.baseAddress); return gsl::make_span(first, count); } /** * Find a compiled code block from it's address. */ CodeBlock * CodeCache::getBlockByAddress(uint32_t address) { auto index = getIndexPointer(address)->load(); if (index < 0) { return nullptr; } else { return getBlockByIndex(index); } } /** * Find a compiled code block's CodeBlockIndex. */ CodeBlockIndex CodeCache::getIndex(uint32_t address) { return getIndexPointer(address)->load(); } /** * Get a compiled code block's CodeBlockIndex. */ CodeBlockIndex CodeCache::getIndex(CodeBlock *block) { auto blockAddress = reinterpret_cast(block); auto index = (blockAddress - mDataAllocator.baseAddress) / sizeof(CodeBlock); return static_cast(index); } /** * Get a pointer to the CodeBlockIndex for the address. * * This is used for registering the CodeBlock whilst compiling. */ std::atomic * CodeCache::getIndexPointer(uint32_t address) { decaf_check((address & 0x3) == 0); auto index1 = (address & 0xFFFF0000) >> 16; auto index2 = (address & 0x0000FFFC) >> 2; auto level2 = mFastIndex[index1].load(); if (UNLIKELY(!level2)) { auto newLevel2 = new std::atomic[Level2Size]; std::memset(newLevel2, CodeBlockIndexUncompiled, sizeof(newLevel2[0]) * Level2Size); if (mFastIndex[index1].compare_exchange_strong(level2, newLevel2)) { level2 = newLevel2; } else { // compare_exchange updates level1 if we were preempted delete[] newLevel2; } } return &level2[index2]; } /** * Set a CodeBlockIndex for an address, useful for mirroring duplicate functions. */ void CodeCache::setBlockIndex(uint32_t address, CodeBlockIndex index) { decaf_check(index >= 0); getIndexPointer(address)->store(index); } /** * Register a block of code in the CodeCache. * * This will allocate memory for the code and data, and update the code block index. */ CodeBlock * CodeCache::registerCodeBlock(uint32_t address, void *code, size_t size, void *unwindInfo, size_t unwindSize) { auto dataAddress = allocate(mDataAllocator, sizeof(CodeBlock), 1); auto codeAddress = allocate(mCodeAllocator, size, 16); // Setup me block auto block = reinterpret_cast(dataAddress); block->address = address; block->code = reinterpret_cast(codeAddress); block->codeSize = static_cast(size); std::memcpy(block->code, code, size); // Initialise profiling data block->profileData.count = 0; block->profileData.time = 0; #ifdef PLATFORM_WINDOWS // Register unwind info decaf_check(unwindSize <= CodeBlockUnwindInfo::MaxUnwindInfoSize); block->unwindInfo.size = static_cast(unwindSize); std::memcpy(block->unwindInfo.data.data(), unwindInfo, unwindSize); auto unwindAddress = reinterpret_cast(block->unwindInfo.data.data()); block->unwindInfo.rtlFuncTable.BeginAddress = static_cast(codeAddress - mReserveAddress); block->unwindInfo.rtlFuncTable.EndAddress = static_cast(block->unwindInfo.rtlFuncTable.BeginAddress + size); block->unwindInfo.rtlFuncTable.UnwindData = static_cast(unwindAddress - mReserveAddress); RtlAddFunctionTable(&block->unwindInfo.rtlFuncTable, 1, mReserveAddress); #endif auto index = getIndex(block); auto indexPtr = getIndexPointer(address); indexPtr->store(index); return block; } /** * Allocate memory from the specified CodeCache::FrameAllocator. */ uintptr_t CodeCache::allocate(FrameAllocator &allocator, size_t size, size_t alignment) { auto alignedSize = align_up(size + (alignment - 1), alignment); auto offset = allocator.allocated.fetch_add(alignedSize); auto alignedOffset = align_up(offset, alignment); // Check if we have gone past end of committed memory. if (alignedOffset + alignedSize > allocator.committed.load()) { std::lock_guard lock { allocator.mutex }; auto committed = allocator.committed.load(); while (alignedOffset + alignedSize > committed) { if (!platform::commitMemory(allocator.baseAddress + committed, allocator.growthSize, allocator.flags)) { decaf_abort("Failed to commit memory for JIT"); } committed += allocator.growthSize; } allocator.committed.store(committed); } return allocator.baseAddress + alignedOffset; } } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/jit_codecache.h ================================================ #pragma once #include "jit_stats.h" #include #include #include #include #include #include namespace cpu { namespace jit { /** * Code Cache Responsibilities: * * 1. Map guest address to host address. * 2. Allocate executable host memory. * 3. Allocate and populate unwind information. */ class CodeCache { struct FrameAllocator { // Memory flags platform::ProtectFlags flags; //! Base address uintptr_t baseAddress; //! Amount of data allocated so far. std::atomic allocated; //! Amount of memory committed std::atomic committed; //! Amount of memory reserved size_t reserved; size_t growthSize; std::mutex mutex; }; // Fast Index level sizes static constexpr size_t Level1Size = 0x10000; static constexpr size_t Level2Size = 0x4000; public: ~CodeCache(); bool initialise(size_t codeSize, size_t dataSize); void clear(); void invalidate(uint32_t address, uint32_t size); void free(); size_t getCodeCacheSize(); size_t getDataCacheSize(); gsl::span getCompiledCodeBlocks(); CodeBlock * getBlockByAddress(uint32_t address); /** * Find a compiled code block from its CodeBlockIndex. */ CodeBlock * getBlockByIndex(CodeBlockIndex index) { auto blockAddress = mDataAllocator.baseAddress + index * sizeof(CodeBlock); return reinterpret_cast(blockAddress); } CodeBlockIndex getIndex(uint32_t address); CodeBlockIndex getIndex(CodeBlock *block); std::atomic * getIndexPointer(uint32_t address); /** * Get a const pointer to the CodeBlockIndex for the address, or null if * no block is registered for the address. * * This is used for quickly looking up a code block for execution. */ const std::atomic * getConstIndexPointer(uint32_t address) { auto index1 = (address & 0xFFFF0000) >> 16; auto index2 = (address & 0x0000FFFC) >> 2; auto level2 = mFastIndex[index1].load(); if (UNLIKELY(!level2)) { return nullptr; } return &level2[index2]; } void setBlockIndex(uint32_t address, CodeBlockIndex index); CodeBlock * registerCodeBlock(uint32_t address, void *code, size_t size, void *unwindInfo, size_t unwindSize); private: uintptr_t allocate(FrameAllocator &allocator, size_t size, size_t alignment); private: size_t mReserveAddress = 0; size_t mReserveSize = 0; FrameAllocator mCodeAllocator; FrameAllocator mDataAllocator; std::atomic *> *mFastIndex = nullptr; }; } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/jit/jit_stats.cpp ================================================ #include "jit.h" #include "jit_stats.h" namespace cpu { namespace jit { bool sampleStats(JitStats &stats) { auto backend = getBackend(); if (backend) { return backend->sampleStats(stats); } else { return false; } } void resetProfileStats() { auto backend = getBackend(); if (backend) { backend->resetProfileStats(); } } void setProfilingMask(unsigned mask) { auto backend = getBackend(); if (backend) { backend->setProfilingMask(mask); } } unsigned getProfilingMask() { auto backend = getBackend(); if (backend) { return backend->getProfilingMask(); } else { return 0; } } } // namespace jit } // namespace cpu ================================================ FILE: src/libcpu/src/memorymap.cpp ================================================ #include "memorymap.h" #include #include #include #include #include namespace cpu { namespace internal { uintptr_t BaseVirtualAddress = 0; uintptr_t BasePhysicalAddress = 0; } // namespace internal static constexpr PhysicalAddress MEM1BaseAddress = PhysicalAddress { 0 }; static constexpr PhysicalAddress MEM1EndAddress = PhysicalAddress { 0x01FFFFFF }; static constexpr size_t MEM1Size = (MEM1EndAddress - MEM1BaseAddress) + 1; static constexpr PhysicalAddress MEM0BaseAddress = PhysicalAddress { 0x08000000 }; static constexpr PhysicalAddress MEM0EndAddress = PhysicalAddress { 0x082DFFFF }; static constexpr size_t MEM0Size = (MEM0EndAddress - MEM0BaseAddress) + 1; static constexpr PhysicalAddress MEM2BaseAddress = PhysicalAddress { 0x10000000 }; static constexpr PhysicalAddress MEM2EndAddress = PhysicalAddress { 0x8FFFFFFF }; static constexpr size_t MEM2Size = (MEM2EndAddress - MEM2BaseAddress) + 1; static constexpr PhysicalAddress UNKRAMBaseAddress = PhysicalAddress { 0xFFC00000 }; static constexpr PhysicalAddress UNKRAMEndAddress = PhysicalAddress { 0xFFE7FFFF }; static constexpr size_t UNKRAMSize = (UNKRAMEndAddress - UNKRAMBaseAddress) + 1; static constexpr PhysicalAddress SRAM1BaseAddress = PhysicalAddress { 0xFFF00000 }; static constexpr PhysicalAddress SRAM1EndAddress = PhysicalAddress { 0xFFF07FFF }; static constexpr size_t SRAM1Size = (SRAM1EndAddress - SRAM1BaseAddress) + 1; static constexpr PhysicalAddress SRAM0BaseAddress = PhysicalAddress { 0xFFFF0000 }; static constexpr PhysicalAddress SRAM0EndAddress = PhysicalAddress { 0xFFFFFFFF }; static constexpr size_t SRAM0Size = (SRAM0EndAddress - SRAM0BaseAddress) + 1; // HACK: Doesn't exist at this physical address on hardware. _DECAF ONLY_ // HACK: Set to page size (128kb) even though it's actually 16kb per core, // due to memory map restrictions. static constexpr PhysicalAddress LCBaseAddress = PhysicalAddress { 0x02000000 }; static constexpr PhysicalAddress LCEndAddress = PhysicalAddress { 0x0201FFFF }; static constexpr size_t LCSize = (LCEndAddress - LCBaseAddress) + 1; // Tiling Aperture dedicated memory. // HACK: Doesn't exist at this physical address on hardware. _DECAF ONLY_ // On hardware the memory controller maps apertures using fancy logic, // we can't do that so we need a dedicated memory region for it. static constexpr size_t TASize = 256 * 1024 * 1024; static constexpr PhysicalAddress TABaseAddress = PhysicalAddress { 0xD0000000 }; static constexpr PhysicalAddress TAEndAddress = TABaseAddress + TASize - 1; MemoryMap::~MemoryMap() { free(); } bool MemoryMap::reserve() { decaf_check(platform::getSystemPageSize() <= cpu::PageSize); // Reserve physical address space mPhysicalBase = reserveBaseAddress(); if (!mPhysicalBase) { gLog->error("Unable to reserve base address for physical memory"); free(); return false; } // Reserve virtual address space mVirtualBase = reserveBaseAddress(); if (!mVirtualBase) { gLog->error("Unable to reserve base address for virtual memory"); free(); return false; } internal::BaseVirtualAddress = mVirtualBase; internal::BasePhysicalAddress = mPhysicalBase; mReservedMemory.push_back({ VirtualAddress { 0 }, VirtualAddress { 0xFFFFFFFF } }); // Commit MEM0 mMem0 = platform::createMemoryMappedFile(MEM0Size); if (mMem0 == platform::InvalidMapFileHandle) { gLog->error("Unable to create MEM1 mapping"); free(); return false; } // Commit MEM1 mMem1 = platform::createMemoryMappedFile(MEM1Size); if (mMem1 == platform::InvalidMapFileHandle) { gLog->error("Unable to create MEM1 mapping"); free(); return false; } // Commit MEM2 mMem2 = platform::createMemoryMappedFile(MEM2Size); if (mMem2 == platform::InvalidMapFileHandle) { gLog->error("Unable to create MEM2 mapping"); free(); return false; } // Commit unknown kernel RAM mUnkRam = platform::createMemoryMappedFile(UNKRAMSize); if (mUnkRam == platform::InvalidMapFileHandle) { gLog->error("Unable to create UNKRAM mapping"); free(); return false; } // Commit SRAM0 mSram0 = platform::createMemoryMappedFile(SRAM0Size); if (mSram0 == platform::InvalidMapFileHandle) { gLog->error("Unable to create SRAM0 mapping"); free(); return false; } // Commit SRAM1 mSram1 = platform::createMemoryMappedFile(SRAM1Size); if (mSram1 == platform::InvalidMapFileHandle) { gLog->error("Unable to create SRAM1 mapping"); free(); return false; } // Commit LC mLockedCache = platform::createMemoryMappedFile(LCSize); if (mLockedCache == platform::InvalidMapFileHandle) { gLog->error("Unable to create Locked Cache mapping"); free(); return false; } // Commit Tiling Apertures mTilingAperture = platform::createMemoryMappedFile(TASize); if (mTilingAperture == platform::InvalidMapFileHandle) { gLog->error("Unable to create Tiling Aperture mapping"); free(); return false; } // Release our reserved memory so we can map it if (!platform::freeMemory(mPhysicalBase, 0x100000000ull)) { gLog->error("Unable to release physical address space"); free(); return false; } // Map physical address space auto ptrMem0 = getPhysicalPointer(MEM0BaseAddress); auto viewMem0 = platform::mapViewOfFile(mMem0, platform::ProtectFlags::ReadWrite, 0, MEM0Size, ptrMem0); if (viewMem0 != ptrMem0) { gLog->error("Unable to map MEM0 to physical address space"); free(); return false; } auto ptrMem1 = getPhysicalPointer(MEM1BaseAddress); auto viewMem1 = platform::mapViewOfFile(mMem1, platform::ProtectFlags::ReadWrite, 0, MEM1Size, ptrMem1); if (viewMem1 != ptrMem1) { gLog->error("Unable to map MEM1 to physical address space"); free(); return false; } auto ptrMem2 = getPhysicalPointer(MEM2BaseAddress); auto viewMem2 = platform::mapViewOfFile(mMem2, platform::ProtectFlags::ReadWrite, 0, MEM2Size, ptrMem2); if (viewMem2 != ptrMem2) { gLog->error("Unable to map MEM2 to physical address space"); free(); return false; } auto ptrUnkRam = getPhysicalPointer(UNKRAMBaseAddress); auto viewUnkRam = platform::mapViewOfFile(mUnkRam, platform::ProtectFlags::ReadWrite, 0, UNKRAMSize, ptrUnkRam); if (viewUnkRam != ptrUnkRam) { gLog->error("Unable to map UNKRAM to physical address space"); free(); return false; } auto ptrSram0 = getPhysicalPointer(SRAM0BaseAddress); auto viewSram0 = platform::mapViewOfFile(mSram0, platform::ProtectFlags::ReadWrite, 0, SRAM0Size, ptrSram0); if (viewSram0 != ptrSram0) { gLog->error("Unable to map SRAM0 to physical address space"); free(); return false; } auto ptrSram1 = getPhysicalPointer(SRAM1BaseAddress); auto viewSram1 = platform::mapViewOfFile(mSram1, platform::ProtectFlags::ReadWrite, 0, SRAM1Size, ptrSram1); if (viewSram1 != ptrSram1) { gLog->error("Unable to map SRAM1 to physical address space"); free(); return false; } auto ptrLC = getPhysicalPointer(LCBaseAddress); auto viewLC = platform::mapViewOfFile(mLockedCache, platform::ProtectFlags::ReadWrite, 0, LCSize, ptrLC); if (viewLC != ptrLC) { gLog->error("Unable to map Locked Cache to physical address space"); free(); return false; } auto ptrTA = getPhysicalPointer(TABaseAddress); auto viewTA = platform::mapViewOfFile(mTilingAperture, platform::ProtectFlags::ReadWrite, 0, TASize, ptrTA); if (viewTA != ptrTA) { gLog->error("Unable to map Tiling Aperture to physical address space"); free(); return false; } return true; } void MemoryMap::free() { // Unmap all views while (mMappedMemory.size()) { auto &mapping = mMappedMemory[0]; unmapMemory(mapping.virtualAddress, mapping.size); } mReservedMemory.clear(); // Close file mappings if (mMem0 != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(MEM0BaseAddress), MEM0Size); platform::closeMemoryMappedFile(mMem0); mMem0 = platform::InvalidMapFileHandle; } if (mMem1 != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(MEM1BaseAddress), MEM1Size); platform::closeMemoryMappedFile(mMem1); mMem1 = platform::InvalidMapFileHandle; } if (mMem2 != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(MEM2BaseAddress), MEM2Size); platform::closeMemoryMappedFile(mMem2); mMem2 = platform::InvalidMapFileHandle; } if (mUnkRam != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(UNKRAMBaseAddress), UNKRAMSize); platform::closeMemoryMappedFile(mUnkRam); mUnkRam = platform::InvalidMapFileHandle; } if (mSram0 != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(SRAM0BaseAddress), SRAM0Size); platform::closeMemoryMappedFile(mSram0); mSram0 = platform::InvalidMapFileHandle; } if (mSram1 != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(SRAM1BaseAddress), SRAM1Size); platform::closeMemoryMappedFile(mSram1); mSram1 = platform::InvalidMapFileHandle; } if (mLockedCache != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(LCBaseAddress), LCSize); platform::closeMemoryMappedFile(mLockedCache); mLockedCache = platform::InvalidMapFileHandle; } if (mTilingAperture != platform::InvalidMapFileHandle) { platform::unmapViewOfFile(getPhysicalPointer(TABaseAddress), TASize); platform::closeMemoryMappedFile(mTilingAperture); mTilingAperture = platform::InvalidMapFileHandle; } // Release virtual memory if (mVirtualBase) { platform::freeMemory(mVirtualBase, 0x100000000ull); mVirtualBase = 0; } // Release physical memory if (mPhysicalBase) { platform::freeMemory(mPhysicalBase, 0x100000000ull); mPhysicalBase = 0; } } bool MemoryMap::isVirtualAddressFree(VirtualAddress start, uint32_t size) { auto end = start + (size - 1); for (auto &reservation : mReservedMemory) { if (reservation.start <= start && reservation.end >= end) { return true; } } return false; } VirtualMemoryType MemoryMap::queryVirtualAddress(VirtualAddress virtualAddress) { for (auto &mapped : mMappedMemory) { auto mapStart = mapped.virtualAddress; auto mapEnd = mapped.virtualAddress + (mapped.size - 1); if (virtualAddress >= mapStart && virtualAddress <= mapEnd) { if (mapped.permission == MapPermission::ReadOnly) { return VirtualMemoryType::MappedReadOnly; } else { return VirtualMemoryType::MappedReadWrite; } } } if (isVirtualAddressFree(virtualAddress, 1)) { return VirtualMemoryType::Free; } return VirtualMemoryType::Allocated; } PhysicalMemoryType MemoryMap::queryPhysicalAddress(PhysicalAddress physicalAddress) { if (physicalAddress >= MEM0BaseAddress && physicalAddress <= MEM0EndAddress) { return PhysicalMemoryType::MEM0; } else if (physicalAddress >= MEM1BaseAddress && physicalAddress <= MEM1EndAddress) { return PhysicalMemoryType::MEM1; } else if (physicalAddress >= MEM2BaseAddress && physicalAddress <= MEM2EndAddress) { return PhysicalMemoryType::MEM2; } else if (physicalAddress >= UNKRAMBaseAddress && physicalAddress <= UNKRAMEndAddress) { return PhysicalMemoryType::UNKRAM; } else if (physicalAddress >= SRAM0BaseAddress && physicalAddress <= SRAM0EndAddress) { return PhysicalMemoryType::SRAM0; } else if (physicalAddress >= SRAM1BaseAddress && physicalAddress <= SRAM1EndAddress) { return PhysicalMemoryType::SRAM1; } else if (physicalAddress >= LCBaseAddress && physicalAddress <= LCEndAddress) { return PhysicalMemoryType::LockedCache; } else if (physicalAddress >= TABaseAddress && physicalAddress <= TAEndAddress) { return PhysicalMemoryType::TilingAperture; } return PhysicalMemoryType::Invalid; } bool MemoryMap::virtualToPhysicalAddress(VirtualAddress virtualAddress, PhysicalAddress &out) { for (auto &mapping : mMappedMemory) { if (mapping.virtualAddress <= virtualAddress && mapping.virtualAddress + mapping.size > virtualAddress) { out = mapping.physicalAddress + (virtualAddress - mapping.virtualAddress); return true; } } return false; } bool MemoryMap::allocateVirtualAddress(VirtualAddress start, uint32_t size) { if (size == 0) { return false; } start = align_up(start, cpu::PageSize); size = align_up(size, cpu::PageSize); auto end = start + (size - 1); if (!isVirtualAddressFree(start, size)) { // Must be free to be able to allocate. return false; } for (auto itr = mReservedMemory.begin(); itr != mReservedMemory.end(); ++itr) { auto &reservation = *itr; if (start >= reservation.start && end <= reservation.end) { releaseReservation(reservation); if (start == reservation.start && end == reservation.end) { // Consumed whole reservation mReservedMemory.erase(itr); } else if (start == reservation.start) { // Consumed from start of reservation reservation.start += size; acquireReservation(reservation); } else if (end == reservation.end) { // Consumed from end of reservation reservation.end -= size; acquireReservation(reservation); } else { // Consumed in middle of reservation auto newReservation = VirtualReservation {}; newReservation.start = start + size; newReservation.end = reservation.end; acquireReservation(newReservation); reservation.end = start; reservation.end -= 1; acquireReservation(reservation); mReservedMemory.insert(itr + 1, newReservation); } return true; } } return false; } bool MemoryMap::freeVirtualAddress(VirtualAddress start, uint32_t size) { start = align_up(start, cpu::PageSize); size = align_up(size, cpu::PageSize); if (isVirtualAddressFree(start, size)) { // Check if it's already free return true; } // Check if we can combine with a previous reservation for (auto itr = mReservedMemory.begin(); itr != mReservedMemory.end(); ++itr) { auto &reservation = *itr; auto mergeItr = mReservedMemory.end(); if (reservation.start == start + size) { // Expand reservation backward releaseReservation(reservation); reservation.start -= size; if (itr != mReservedMemory.begin()) { // Check if we can merge with the previous reservation auto prev = itr - 1; if (prev->end + 1 == reservation.start) { releaseReservation(*prev); reservation.start = prev->start; mergeItr = prev; } } acquireReservation(reservation); if (mergeItr != mReservedMemory.end()) { mReservedMemory.erase(mergeItr); } return true; } else if (reservation.end + 1 == start) { // Expand reservation forward releaseReservation(reservation); reservation.end += size; auto next = itr + 1; if (next != mReservedMemory.end()) { // Check if we can merge with the next reservation if (next->start == reservation.end + 1) { releaseReservation(*next); reservation.end = next->end; mergeItr = next; } } acquireReservation(reservation); if (mergeItr != mReservedMemory.end()) { mReservedMemory.erase(mergeItr); } return true; } } // We cannot combine with a previous reservation so we must make a new one! auto reservation = VirtualReservation {}; reservation.start = start; reservation.end = start + (size - 1); acquireReservation(reservation); mReservedMemory.insert(std::upper_bound(mReservedMemory.begin(), mReservedMemory.end(), reservation, [](const auto &m1, const auto &m2) { return m1.start < m2.start; }), reservation); return true; } VirtualAddressRange MemoryMap::findFreeVirtualAddress(uint32_t size, uint32_t align) { size = align_up(size, cpu::PageSize); for (auto &reservation : mReservedMemory) { auto alignedStart = align_up(align_up(reservation.start, align), cpu::PageSize); auto alignedEnd = alignedStart + (size - 1); if (reservation.start <= alignedStart && reservation.end >= alignedEnd) { return { alignedStart, size }; } } return { VirtualAddress { 0u }, 0u }; } VirtualAddressRange MemoryMap::findFreeVirtualAddressInRange(VirtualAddressRange range, uint32_t size, uint32_t align) { auto rangeStart = range.start; auto rangeEnd = range.start + (range.size - 1); size = align_up(size, cpu::PageSize); for (auto &reservation : mReservedMemory) { if (rangeEnd < reservation.start || rangeStart > reservation.end) { // Not in range continue; } auto start = std::max(reservation.start, rangeStart); auto end = std::min(reservation.end, rangeEnd); auto alignedStart = align_up(align_up(start, align), cpu::PageSize); auto alignedEnd = alignedStart + (size - 1); // Ensure address is in range if (start <= alignedStart && end >= alignedEnd) { return { alignedStart, size }; } } return { VirtualAddress { 0u }, 0u }; } bool MemoryMap::mapMemory(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size, MapPermission permission) { auto physicalMemoryType = queryPhysicalAddress(physicalAddress); if (physicalMemoryType == PhysicalMemoryType::Invalid) { gLog->error("Attempted to map invalid physical address 0x{:08X}", physicalAddress.getAddress()); return false; } if (queryVirtualAddress(virtualAddress) != VirtualMemoryType::Allocated) { gLog->error("Attempted to map physical address 0x{:08X} to an invalid virtual address 0x{:08X}", virtualAddress.getAddress(), physicalAddress.getAddress()); return false; } // Map virtual address to physical memory void *view = nullptr; auto virtualPtr = getVirtualPointer(virtualAddress); auto protectFlags = platform::ProtectFlags { }; if (permission == MapPermission::ReadOnly) { protectFlags = platform::ProtectFlags::ReadOnly; } else if (permission == MapPermission::ReadWrite) { protectFlags = platform::ProtectFlags::ReadWrite; } else { gLog->error("Invalid permission {} passed to mapMemory", static_cast(permission)); return false; } if (physicalMemoryType == PhysicalMemoryType::MEM0) { view = platform::mapViewOfFile(mMem0, protectFlags, physicalAddress - MEM0BaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::MEM1) { view = platform::mapViewOfFile(mMem1, protectFlags, physicalAddress - MEM1BaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::MEM2) { view = platform::mapViewOfFile(mMem2, protectFlags, physicalAddress - MEM2BaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::UNKRAM) { view = platform::mapViewOfFile(mUnkRam, protectFlags, physicalAddress - UNKRAMBaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::SRAM0) { view = platform::mapViewOfFile(mSram0, protectFlags, physicalAddress - SRAM0BaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::SRAM1) { view = platform::mapViewOfFile(mSram1, protectFlags, physicalAddress - SRAM1BaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::LockedCache) { view = platform::mapViewOfFile(mLockedCache, protectFlags, physicalAddress - LCBaseAddress, size, virtualPtr); } else if (physicalMemoryType == PhysicalMemoryType::TilingAperture) { view = platform::mapViewOfFile(mTilingAperture, protectFlags, physicalAddress - TABaseAddress, size, virtualPtr); } else { gLog->error("Invalid physicalMemoryType {} for mapMemory", static_cast(physicalMemoryType)); return false; } // Add to the memory map auto virtualMemoryMap = VirtualMemoryMap {}; virtualMemoryMap.virtualAddress = virtualAddress; virtualMemoryMap.physicalAddress = physicalAddress; virtualMemoryMap.size = size; virtualMemoryMap.permission = permission; mMappedMemory.insert(std::upper_bound(mMappedMemory.begin(), mMappedMemory.end(), virtualMemoryMap, [](const auto &m1, const auto &m2) { return m1.virtualAddress < m2.virtualAddress; }), virtualMemoryMap); if (view != virtualPtr) { gLog->error("Unable to map virtual address 0x{:08X} to physical address 0x{:08X}", virtualAddress.getAddress(), physicalAddress.getAddress()); unmapMemory(virtualAddress, size); return false; } return true; } bool MemoryMap::unmapMemory(VirtualAddress virtualAddress, uint32_t size) { std::vector remaps; auto start = align_up(virtualAddress, cpu::PageSize); auto end = start + (align_up(size, cpu::PageSize) - 1); for (auto itr = mMappedMemory.begin(); itr != mMappedMemory.end(); ) { auto mapStart = itr->virtualAddress; auto mapSize = itr->size; auto mapEnd = itr->virtualAddress + (itr->size - 1); auto remap = VirtualMemoryMap { }; if (mapStart < start && mapEnd > start) { // End of map goes into unmap range remap.physicalAddress = itr->physicalAddress; remap.virtualAddress = itr->virtualAddress; remap.size = static_cast(start - mapStart); remap.permission = itr->permission; remaps.push_back(remap); } else if (mapStart < end && mapEnd > end) { // Start of map is in unmap range auto offset = end - mapStart; remap.physicalAddress = itr->physicalAddress + offset; remap.virtualAddress = itr->virtualAddress + offset; remap.size = static_cast(remap.size - offset); remap.permission = itr->permission; remaps.push_back(remap); } else if (mapStart > end || mapEnd < start) { // Not in unmap range ++itr; continue; } itr = mMappedMemory.erase(itr); if (!platform::unmapViewOfFile(getVirtualPointer(mapStart), mapSize)) { gLog->error("Unexpected error whilst unmapping virtual address 0x{:08X}", virtualAddress.getAddress()); } } for (auto &remap : remaps) { if (!mapMemory(remap.virtualAddress, remap.physicalAddress, remap.size, remap.permission)) { gLog->error("Unexpected error whilst remapping virtual address 0x{:08X}", remap.virtualAddress.getAddress()); } } return true; } bool MemoryMap::resetVirtualMemory() { // First unmap all memory for (auto &mapping : mMappedMemory) { if (!platform::unmapViewOfFile(getVirtualPointer(mapping.virtualAddress), mapping.size)) { gLog->error("Unexpected error whilst unmapping virtual address 0x{:08X}", mapping.virtualAddress.getAddress()); } } mMappedMemory.clear(); if (mReservedMemory.size() == 1) { // If there is only 1 reservation then we should be good to go. decaf_check(mReservedMemory[0].start == VirtualAddress { 0 }); decaf_check(mReservedMemory[0].end == VirtualAddress { 0xFFFFFFFF }); return true; } // Cleanup reservations for (auto &reservation : mReservedMemory) { if (!releaseReservation(reservation)) { gLog->error("Unexpected error whilst releasing virtual address 0x{:08X}", reservation.start.getAddress()); } } mReservedMemory.clear(); // Reserve whole range acquireReservation({ VirtualAddress { 0 }, VirtualAddress { 0xFFFFFFFF } }); mReservedMemory.push_back({ VirtualAddress { 0 }, VirtualAddress { 0xFFFFFFFF } }); return true; } bool MemoryMap::acquireReservation(VirtualReservation reservation) { return platform::reserveMemory(mVirtualBase + reservation.start.getAddress(), (reservation.end - reservation.start) + 1); } bool MemoryMap::releaseReservation(VirtualReservation reservation) { return platform::freeMemory(mVirtualBase + reservation.start.getAddress(), (reservation.end - reservation.start) + 1); } uintptr_t MemoryMap::reserveBaseAddress() { for (auto n = 32; n < 64; n++) { auto baseAddress = 1ull << n; if (platform::reserveMemory(baseAddress, 0x100000000ull)) { return static_cast(baseAddress); } } return 0; } void * MemoryMap::getPhysicalPointer(PhysicalAddress physicalAddress) { return reinterpret_cast(mPhysicalBase + physicalAddress.getAddress()); } void * MemoryMap::getVirtualPointer(VirtualAddress virtualAddress) { return reinterpret_cast(mVirtualBase + virtualAddress.getAddress()); } } // namespace cpu ================================================ FILE: src/libcpu/src/memorymap.h ================================================ #pragma once #include "address.h" #include "mmu.h" #include "pointer.h" #include #include #include namespace cpu { class MemoryMap { struct VirtualReservation { //! Inclusive start address. VirtualAddress start; //! Inclusive end address. VirtualAddress end; }; struct VirtualMemoryMap { VirtualAddress virtualAddress; PhysicalAddress physicalAddress; uint32_t size; MapPermission permission; }; public: MemoryMap() = default; ~MemoryMap(); bool reserve(); void free(); bool allocateVirtualAddress(VirtualAddress start, uint32_t size); bool freeVirtualAddress(VirtualAddress start, uint32_t size); VirtualAddressRange findFreeVirtualAddress(uint32_t size, uint32_t align); VirtualAddressRange findFreeVirtualAddressInRange(VirtualAddressRange range, uint32_t size, uint32_t align); bool virtualToPhysicalAddress(VirtualAddress virtualAddress, PhysicalAddress &out); bool mapMemory(VirtualAddress virtualAddress, PhysicalAddress physicalAddress, uint32_t size, MapPermission permission); bool unmapMemory(VirtualAddress virtualAddress, uint32_t size); bool resetVirtualMemory(); PhysicalMemoryType queryPhysicalAddress(PhysicalAddress physicalAddress); VirtualMemoryType queryVirtualAddress(VirtualAddress virtualAddress); private: uintptr_t reserveBaseAddress(); bool isVirtualAddressFree(VirtualAddress start, uint32_t size); bool acquireReservation(VirtualReservation reservation); bool releaseReservation(VirtualReservation reservation); void *getPhysicalPointer(PhysicalAddress physicalAddress); void *getVirtualPointer(VirtualAddress virtualAddress); private: platform::MapFileHandle mMem0 = platform::InvalidMapFileHandle; platform::MapFileHandle mMem1 = platform::InvalidMapFileHandle; platform::MapFileHandle mMem2 = platform::InvalidMapFileHandle; platform::MapFileHandle mUnkRam = platform::InvalidMapFileHandle; platform::MapFileHandle mSram0 = platform::InvalidMapFileHandle; platform::MapFileHandle mSram1 = platform::InvalidMapFileHandle; platform::MapFileHandle mLockedCache = platform::InvalidMapFileHandle; platform::MapFileHandle mTilingAperture = platform::InvalidMapFileHandle; uintptr_t mVirtualBase = 0; uintptr_t mPhysicalBase = 0; std::vector mMappedMemory; std::vector mReservedMemory; }; } // namespace cpu ================================================ FILE: src/libcpu/src/statedbg.h ================================================ #pragma once #include #include #include #include #include #include "state.h" template static std::string toHexString(T i) { std::ostringstream oss; oss << "0x" << std::hex << i; return oss.str(); } inline bool dbgStateCmp(cpu::Core* state, cpu::Core* estate, std::vector& errors) { if (memcmp(state, estate, sizeof(cpu::Core)) == 0) { return true; } #define CHECKONE(n, m) if (state->n != estate->n) errors.push_back(std::string(m) + " (got:" + toHexString(state->n) + " expected:" + toHexString(estate->n) + ")") #define CHECKONEI(n, m, i) CHECKONE(n, std::string(m) + "[" + std::to_string(i) + "]") CHECKONE(cia, "CIA"); CHECKONE(nia, "NIA"); for (auto i = 0; i < 32; ++i) { CHECKONEI(gpr[i], "GPR", i); } for (auto i = 0; i < 32; ++i) { CHECKONEI(fpr[i].idw, "FPR", i); } CHECKONE(cr.value, "CR"); CHECKONE(xer.value, "XER"); CHECKONE(lr, "CTR"); CHECKONE(ctr, "CTR"); CHECKONE(fpscr.value, "FPSCR"); CHECKONE(pvr.value, "PVR"); CHECKONE(msr.value, "MSR"); for (auto i = 0; i < 16; ++i) { CHECKONEI(sr[i], "SR", i); } for (auto i = 0; i < 8; ++i) { CHECKONEI(gqr[i].value, "GQR", i); } #undef CHECKONEI #undef CHECKONE return false; } ================================================ FILE: src/libcpu/src/trace.cpp ================================================ #include "trace.h" #include "state.h" #include "statedbg.h" #include "cpu.h" #include "espresso/espresso_disassembler.h" #include "espresso/espresso_instructionset.h" #include "espresso/espresso_spr.h" #include #include #include #include #include #include using espresso::Instruction; using espresso::InstructionID; using espresso::InstructionInfo; using espresso::InstructionField; using espresso::SPR; //#define TRACE_ENABLED //#define TRACE_SC_ENABLED //#define TRACE_VERIFICATION struct Tracer { size_t index; size_t numTraces; std::vector traces; cpu::CoreRegs prevState; }; static inline void debugPrint(std::string out) { gLog->debug(out); out.push_back('\n'); platform::debugLog(out); } namespace cpu { cpu::Tracer * allocTracer(size_t size) { // TODO: TRACE_ENABLED should actually be handled by kernel #ifdef TRACE_ENABLED auto tracer = new Tracer(); tracer->index = 0; tracer->numTraces = 0; tracer->traces.resize(size); return tracer; #else return nullptr; #endif } void freeTracer(cpu::Tracer *tracer) { if (tracer) { delete tracer; } } namespace this_core { void setTracer(cpu::Tracer *tracer) { state()->tracer = tracer; } } // namespace this_core } // namespace cpu std::string getStateFieldName(TraceFieldType type) { if (type == StateField::Invalid) { return "Invalid"; } if (type >= StateField::GPR0 && type <= StateField::GPR31) { return fmt::format("r{:02}", type - StateField::GPR); } else if (type >= StateField::FPR0 && type <= StateField::FPR31) { return fmt::format("f{:02}", type - StateField::FPR); } else if (type >= StateField::GQR0 && type <= StateField::GQR7) { return fmt::format("q{:02}", type - StateField::FPR); } else if (type == StateField::CR) { return "CR"; } else if (type == StateField::XER) { return "XER"; } else if (type == StateField::LR) { return "LR"; } else if (type == StateField::FPSCR) { return "FPSCR"; } else if (type == StateField::CTR) { return "CTR"; } else { decaf_abort(fmt::format("Invalid TraceFieldType {}", static_cast(type))); } } static void printFieldValue(fmt::memory_buffer &out, Instruction instr, TraceFieldType type, const TraceFieldValue& value) { if (type == StateField::Invalid) { return; } if (type >= StateField::GPR0 && type <= StateField::GPR31) { fmt::format_to(std::back_inserter(out), " r{:02} = {:08x}\n", type - StateField::GPR, value.u32v0); } else if (type == StateField::CR) { auto valX = [&](int i) { return (value.u32v0 >> ((i) * 4)) & 0xF; }; fmt::format_to(std::back_inserter(out), " CR = {:04b} {:04b} {:04b} {:04b} {:04b} {:04b} {:04b} {:04b}\n", valX(7), valX(6), valX(5), valX(4), valX(3), valX(2), valX(1), valX(0)); } else if (type == StateField::XER) { fmt::format_to(std::back_inserter(out), " XER = {:08x}\n", value.u32v0); } else if (type == StateField::LR) { fmt::format_to(std::back_inserter(out), " LR = {:08x}\n", value.u32v0); } else if (type == StateField::CTR) { fmt::format_to(std::back_inserter(out), " CTR = {:08x}\n", value.u32v0); } else { decaf_abort(fmt::format("Invalid TraceFieldType {}", static_cast(type))); } } static void printInstruction(fmt::memory_buffer &out, const Trace& trace, int index) { auto dis = espresso::Disassembly { }; espresso::disassemble(trace.instr, dis, trace.cia); auto disassemblyText = espresso::disassemblyToText(dis); auto addend = std::string { }; /* if (dis.instruction->id == InstructionID::kc) { auto scall = gSystem.getSyscallData(trace.instr.li); addend = " [" + std::string(scall->name) + "]"; } */ for (auto &write : trace.writes) { printFieldValue(out, trace.instr, write.type, write.value); } fmt::format_to(std::back_inserter(out), " [{}] {:08x} {}{}\n", index, trace.cia, disassemblyText.c_str(), addend.c_str()); for (auto &read : trace.reads) { printFieldValue(out, trace.instr, read.type, read.value); } } const Trace& getTrace(Tracer *tracer, int index) { auto tracerSize = tracer->numTraces; decaf_check(index >= 0); decaf_check(index < tracerSize); auto realIndex = (int)tracer->index - 1 - index; while (realIndex < 0) { realIndex += (int)tracerSize; } while (realIndex >= tracerSize) { realIndex -= (int)tracerSize; } return tracer->traces[realIndex]; } size_t getTracerNumTraces(Tracer *tracer) { return tracer->numTraces; } void traceInit(cpu::Core *state, size_t size) { #ifdef TRACE_ENABLED state->tracer = new Tracer(); state->tracer->index = 0; state->tracer->numTraces = 0; state->tracer->traces.resize(size); #else state->tracer = nullptr; #endif } static uint32_t getFieldStateField(Instruction instr, InstructionField field) { switch (field) { case InstructionField::rA: return StateField::GPR + instr.rA; case InstructionField::rB: return StateField::GPR + instr.rB; case InstructionField::rS: return StateField::GPR + instr.rS; case InstructionField::rD: return StateField::GPR + instr.rD; case InstructionField::frA: return StateField::FPR + instr.frA; case InstructionField::frB: return StateField::FPR + instr.frB; case InstructionField::frC: return StateField::FPR + instr.frC; case InstructionField::frD: return StateField::FPR + instr.frD; case InstructionField::frS: return StateField::FPR + instr.frS; case InstructionField::spr: switch (decodeSPR(instr)) { case SPR::CTR: return StateField::CTR; case SPR::LR: return StateField::LR; case SPR::XER: return StateField::XER; case SPR::GQR0: return StateField::GQR + 0; case SPR::GQR1: return StateField::GQR + 1; case SPR::GQR2: return StateField::GQR + 2; case SPR::GQR3: return StateField::GQR + 3; case SPR::GQR4: return StateField::GQR + 4; case SPR::GQR5: return StateField::GQR + 5; case SPR::GQR6: return StateField::GQR + 6; case SPR::GQR7: return StateField::GQR + 7; default: break; } break; case InstructionField::bo: if ((instr.bo & 4) == 0) { return StateField::CTR; } break; case InstructionField::bi: return StateField::CR; case InstructionField::crbA: case InstructionField::crbB: case InstructionField::crbD: case InstructionField::crfD: case InstructionField::crfS: case InstructionField::crm: return StateField::CR; case InstructionField::oe: if (instr.oe) { return StateField::XER; } break; case InstructionField::rc: if (instr.rc) { return StateField::CR; } break; case InstructionField::lk: return StateField::LR; case InstructionField::XERO: return StateField::XER; case InstructionField::CTR: return StateField::CTR; case InstructionField::LR: return StateField::LR; case InstructionField::FPSCR: return StateField::FPSCR; case InstructionField::RSRV: // TODO: Handle this? default: break; } return StateField::Invalid; } void saveStateField(const cpu::CoreRegs *state, TraceFieldType type, TraceFieldValue &field) { field.u64v0 = 0; field.u64v1 = 0; if (type == StateField::Invalid) { return; } if (type >= StateField::GPR0 && type <= StateField::GPR31) { field.u32v0 = state->gpr[type - StateField::GPR]; } else if (type >= StateField::FPR0 && type <= StateField::FPR31) { field.u64v0 = state->fpr[type - StateField::FPR].idw; } else if (type >= StateField::GQR0 && type <= StateField::GQR7) { field.u32v0 = state->gqr[type - StateField::GQR].value; } else if (type == StateField::CR) { field.u32v0 = state->cr.value; } else if (type == StateField::XER) { field.u32v0 = state->xer.value; } else if (type == StateField::LR) { field.u32v0 = state->lr; } else if (type == StateField::CTR) { field.u32v0 = state->ctr; } else if (type == StateField::FPSCR) { field.u32v0 = state->fpscr.value; } else { decaf_abort(fmt::format("Invalid TraceFieldType {}", static_cast(type))); } } void restoreStateField(cpu::CoreRegs *state, TraceFieldType type, const TraceFieldValue &field) { if (type == StateField::Invalid) { return; } if (type >= StateField::GPR0 && type <= StateField::GPR31) { state->gpr[type - StateField::GPR] = field.u32v0; } else if (type >= StateField::FPR0 && type <= StateField::FPR31) { state->fpr[type - StateField::FPR].idw = field.u64v0; } else if (type >= StateField::GQR0 && type <= StateField::GQR7) { state->gqr[type - StateField::GQR].value = field.u32v0; } else if (type == StateField::CR) { state->cr.value = field.u32v0; } else if (type == StateField::XER) { state->xer.value = field.u32v0; } else if (type == StateField::LR) { state->lr = field.u32v0; } else if (type == StateField::CTR) { state->ctr = field.u32v0; } else if (type == StateField::FPSCR) { state->fpscr.value = field.u32v0; } else { decaf_abort(fmt::format("Invalid TraceFieldType {}", static_cast(type))); } } template static void pushUniqueField(std::vector &fields, uint32_t fieldId) { if (fieldId == StateField::Invalid) { return; } for (auto &i : fields) { if (i.type == fieldId) { return; } } fields.push_back({ fieldId }); } Trace * traceInstructionStart(Instruction instr, InstructionInfo *data, cpu::Core *state) { if (!state->tracer) { return nullptr; } auto tracer = state->tracer; auto &trace = tracer->traces[tracer->index]; auto tracerSize = tracer->traces.size(); if (tracer->numTraces < tracerSize) { tracer->numTraces++; } tracer->index = (tracer->index + 1) % tracerSize; // Setup Trace trace.instr = instr; trace.cia = state->cia; trace.reads.clear(); trace.writes.clear(); // Automatically determine register changes for (auto i = 0u; i < data->read.size(); ++i) { auto stateField = getFieldStateField(instr, data->read[i]); pushUniqueField(trace.reads, stateField); } for (auto i = 0u; i < data->write.size(); ++i) { auto stateField = getFieldStateField(instr, data->write[i]); pushUniqueField(trace.writes, stateField); } for (auto i = 0u; i < data->flags.size(); ++i) { auto stateField = getFieldStateField(instr, data->flags[i]); pushUniqueField(trace.writes, stateField); } // Some special cases. if (data->id == InstructionID::lmw) { for (uint32_t i = StateField::GPR + instr.rD; i <= StateField::GPR31; ++i) { pushUniqueField(trace.writes, i); } } else if (data->id == InstructionID::stmw) { for (uint32_t i = StateField::GPR + instr.rS; i <= StateField::GPR31; ++i) { pushUniqueField(trace.reads, i); } } else if (data->id == InstructionID::stswi) { // TODO: Implement Me } else if (data->id == InstructionID::stswx) { // TODO: Implement Me } #ifdef TRACE_VERIFICATION if (data->id == InstructionID::stswi) { //assert(0); } else if (data->id == InstructionID::stswx) { //assert(0); } #endif // Save all state for (auto &i : trace.reads) { saveStateField(state, i.type, i.value); } for (auto &i : trace.writes) { saveStateField(state, i.type, i.prevalue); } // TODO: This is a bit of a hack... We should probably not do this... tracer->prevState = *state; return &trace; } void traceInstructionEnd(Trace *trace, Instruction instr, InstructionInfo *data, cpu::Core *state) { if (!trace) { return; } auto tracer = state->tracer; // Special hack for KC for now if (data->id == InstructionID::kc) { trace->writes.clear(); for (int i = 0; i < StateField::Max; ++i) { TraceFieldValue curVal, prevVal; saveStateField(&tracer->prevState, i, prevVal); saveStateField(state, i, curVal); if (curVal.value != prevVal.value) { pushUniqueField(trace->writes, i); } } for (auto &i : trace->writes) { saveStateField(&tracer->prevState, i.type, i.prevalue); } } for (auto &i : trace->writes) { saveStateField(state, i.type, i.value); } #ifdef TRACE_VERIFICATION if (tracer->numTraces > 0) { auto errors = std::vector {}; auto checkState = *state; checkState.nia = tracer->prevState.nia; for (auto &i : trace->writes) { restoreStateField(&checkState, i.type, i.prevalue); } if (!dbgStateCmp(&checkState, &tracer->prevState, errors)) { gLog->error("Trace Compliance errors w/ {}", data->name); for (auto &err : errors) { gLog->error(err); } DebugBreak(); } } #endif } void tracePrint(cpu::Core *state, int start, int count) { auto tracer = state->tracer; if (!tracer) { debugPrint("Tracing is disabled"); return; } auto tracerSize = static_cast(getTracerNumTraces(tracer)); if (count == 0) { count = tracerSize - start; } auto end = start + count; decaf_check(start >= 0); decaf_check(end <= tracerSize); fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Trace - Print {} to {}\n", start, end); for (auto i = start; i < end; ++i) { auto &trace = getTrace(tracer, i); printInstruction(out, trace, i); } debugPrint(to_string(out)); } int traceReg(cpu::Core *state, int start, int regIdx) { auto tracer = state->tracer; auto tracerSize = static_cast(getTracerNumTraces(tracer)); bool found = false; fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Trace - Search {} to {} for write r{}\n", start, tracerSize, regIdx); decaf_check(start >= 0); decaf_check(start < tracerSize); for (auto i = start; i < tracerSize; ++i) { auto &trace = getTrace(tracer, i); bool wasMatchedWrite = false; for (auto &j : trace.writes) { if (j.type == StateField::GPR + static_cast(regIdx)) { wasMatchedWrite = true; break; } } if (wasMatchedWrite) { printInstruction(out, trace, i); found = true; break; } } if (!found) { fmt::format_to(std::back_inserter(out), " Nothing Found"); } debugPrint(to_string(out)); return -1; } static cpu::Core *gRegTraceState = nullptr; static int gRegTraceIndex = 0; static int gRegTraceNextReg = -1; void traceRegStart(cpu::Core *state, int start, int regIdx) { gRegTraceState = state; gRegTraceIndex = start; gRegTraceNextReg = -1; traceRegNext(regIdx); } void traceRegNext(int regIdx) { if (!gRegTraceState || gRegTraceIndex < 0) { debugPrint("Need to use traceRegStart first."); return; } auto foundIndex = traceReg(gRegTraceState, gRegTraceIndex, regIdx); if (foundIndex == -1) { gRegTraceIndex = -1; return; } auto tracer = gRegTraceState->tracer; auto &trace = getTrace(tracer, foundIndex); if (trace.reads.size() == 1) { if (trace.reads.front().type >= StateField::GPR0 && trace.reads.front().type <= StateField::GPR31) { gRegTraceNextReg = trace.reads.front().type - StateField::GPR; debugPrint(fmt::format("Suggested next register is r{}", gRegTraceNextReg)); } else { // Not a GPR read gRegTraceNextReg = -1; } } else { // More than one read field gRegTraceNextReg = -1; } gRegTraceIndex = foundIndex + 1; } void traceRegAround() { if (!gRegTraceState || gRegTraceIndex < 0) { debugPrint("Need to use traceRegStart first."); return; } int start = gRegTraceIndex - 5; int end = gRegTraceIndex + 4; auto tracer = gRegTraceState->tracer; auto tracerSize = static_cast(getTracerNumTraces(tracer)); if (start < 0) { start = 0; } if (end > tracerSize) { end = tracerSize; } tracePrint(gRegTraceState, start, end - start); } void traceRegContinue() { if (gRegTraceNextReg == -1) { debugPrint("No stored suggested next register."); return; } traceRegNext(gRegTraceNextReg); } std::list gSyscallTrace; std::mutex gSyscallTraceLock; void tracePrintSyscall(int count) { std::unique_lock lock(gSyscallTraceLock); if (count <= 0 || count >= gSyscallTrace.size()) { count = (int)gSyscallTrace.size(); } fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Trace - Last {} syscalls\n", count); int j = 0; for (auto i = gSyscallTrace.begin(); i != gSyscallTrace.end() && j < count; ++i, ++j) { fmt::format_to(std::back_inserter(out), " {}\n", *i); } debugPrint(to_string(out)); } void traceLogSyscall(const std::string &info) { #ifdef TRACE_SC_ENABLED std::unique_lock lock(gSyscallTraceLock); gSyscallTrace.push_front(info); while (gSyscallTrace.size() > 2048) { gSyscallTrace.pop_back(); } #endif } ================================================ FILE: src/libcpu/src/utils.h ================================================ #pragma once #include #include "state.h" // TODO: Move these getCRX functions uint32_t getCRF(cpu::Core *state, uint32_t field); void setCRF(cpu::Core *state, uint32_t field, uint32_t value); uint32_t getCRB(cpu::Core *state, uint32_t bit); void setCRB(cpu::Core *state, uint32_t bit, uint32_t value); ================================================ FILE: src/libcpu/state.h ================================================ #pragma once #include "espresso/espresso_registerformats.h" #include #include #include struct Tracer; namespace cpu { static const uint32_t coreClockSpeed = 1243125000; static const uint32_t busClockSpeed = 248625000; static const uint32_t timerClockSpeed = busClockSpeed / 4; using TimerDuration = std::chrono::duration>; struct CoreRegs { //! Current execution address uint32_t cia; //! Next execution address uint32_t nia; //! Integer Registers espresso::Register gpr[32]; //! Floating-point Registers espresso::FloatingPointRegister fpr[32]; //! Condition Register espresso::ConditionRegister cr; //! XER Carry/Overflow register espresso::FixedPointExceptionRegister xer; //! Link Register uint32_t lr; //! Count Register uint32_t ctr; //! Floating-Point Status and Control Register espresso::FloatingPointStatusAndControlRegister fpscr; //! Processor Version Register espresso::ProcessorVersionRegister pvr; //! Machine State Register espresso::MachineStateRegister msr; //! Segment Registers uint32_t sr[16]; //! Graphics Quantization Registers espresso::GraphicsQuantisationRegister gqr[8]; //! Data Address Register uint32_t dar; //! DSI Status Register uint32_t dsisr; //! Machine Status Save and Restore Register 0 uint32_t srr0; }; struct Core : CoreRegs { // Core ID uint32_t id; // Value of gpr[1] at time of system call uint32_t systemCallStackHead; uint32_t interrupt_mask { 0xFFFFFFFF }; std::atomic interrupt { 0 }; bool reserveFlag { false }; uint32_t reserveData; std::thread thread; std::chrono::steady_clock::time_point next_alarm; // Tracer used to record executed instructions Tracer *tracer; // Get current core time uint64_t tb(); }; } // namespace cpu ================================================ FILE: src/libcpu/trace.h ================================================ #pragma once #include #include #include #include "espresso/espresso_instruction.h" #include "espresso/espresso_instructionset.h" namespace cpu { struct Core; struct CoreRegs; } // namespace cpu struct Tracer; // TODO: Probably should rename this to something reasonable namespace StateField { enum Field : uint8_t { Invalid, GPR, GPR0 = GPR, GPR31 = GPR + 31, FPR, FPR0 = FPR, FPR31 = FPR + 31, GQR, GQR0 = GQR, GQR7 = GQR + 7, CR, XER, LR, CTR, FPSCR, Max, }; } typedef uint32_t TraceFieldType; struct TraceFieldValue { struct ValueType { bool operator==(const ValueType& rhs) { return data[0] == rhs.data[0] && data[1] == rhs.data[1]; } bool operator!=(const ValueType& rhs) { return data[0] != rhs.data[0] || data[1] != rhs.data[1]; } uint64_t data[2]; }; union { struct { uint32_t u32v0; uint32_t u32v1; uint32_t u32v2; uint32_t u32v3; }; struct { uint64_t u64v0; uint64_t u64v1; }; struct { float f32v0; float f32v1; float f32v2; float f32v3; }; struct { double f64v0; double f64v1; }; struct { uint32_t mem_size; uint32_t mem_offset; }; struct { uint64_t value0; uint64_t value1; }; ValueType value; }; }; static_assert(sizeof(TraceFieldValue) == sizeof(TraceFieldValue::value), "TraceFieldValue::value size must match total structure size"); std::string getStateFieldName(TraceFieldType type); void saveStateField(const cpu::CoreRegs *state, TraceFieldType type, TraceFieldValue &field); void restoreStateField(cpu::CoreRegs *state, TraceFieldType type, const TraceFieldValue &field); struct Trace { struct _R { TraceFieldType type; TraceFieldValue value; }; struct _W { TraceFieldType type; TraceFieldValue value; TraceFieldValue prevalue; }; espresso::Instruction instr; uint32_t cia; std::vector<_R> reads; std::vector<_W> writes; }; const Trace & getTrace(Tracer *tracer, int index); size_t getTracerNumTraces(Tracer *tracer); void traceInit(cpu::Core *state, size_t size); Trace * traceInstructionStart(espresso::Instruction instr, espresso::InstructionInfo *data, cpu::Core *state); void traceInstructionEnd(Trace *trace, espresso::Instruction instr, espresso::InstructionInfo *data, cpu::Core *state); void tracePrint(cpu::Core *state, int start, int count); int traceReg(cpu::Core *state, int start, int regIdx); void traceRegStart(cpu::Core *state, int start, int regIdx); void traceRegNext(int regIdx); void traceRegContinue(); void tracePrintSyscall(int count); void traceLogSyscall(const std::string& info); ================================================ FILE: src/libdecaf/CMakeLists.txt ================================================ project(libdecaf) include_directories(".") include_directories("src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_library(libdecaf STATIC ${SOURCE_FILES} ${HEADER_FILES}) GroupSources("Source Files" src) target_compile_definitions(libdecaf PRIVATE DECAF_INSTALL_RESOURCESDIR="${DECAF_INSTALL_RESOURCESDIR}") target_link_libraries(libdecaf addrlib common libcpu libgfd libgpu) target_link_libraries(libdecaf cnl imgui pugixml ${CARES_LIBRARY} ${CURL_LIBRARY} ${OPENSSL_LIBRARY} ${LIBUV_LIBRARY} ${ZLIB_LIBRARY} ${FFMPEG_LIBRARY}) if(MSVC) target_link_libraries(libdecaf Crypt32 ws2_32 Psapi IPHLPAPI userenv) target_compile_options(libdecaf PUBLIC /wd4251) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(libdecaf dl) endif() if(DECAF_PCH) target_precompile_headers(libdecaf PRIVATE ) AutoGroupPCHFiles() endif() ================================================ FILE: src/libdecaf/decaf.h ================================================ #pragma once #include "decaf_eventlistener.h" #include "decaf_graphics.h" #include "decaf_input.h" #include #include #include namespace decaf { std::string makeConfigPath(const std::string &filename); bool createConfigDirectory(); std::string getResourcePath(const std::string &filename); bool initialise(const std::string &gamePath); void start(); bool stopping(); int waitForExit(); void shutdown(); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_config.h ================================================ #pragma once #include #include #include #include namespace decaf { struct DebuggerSettings { bool enabled = true; bool break_on_entry = false; bool break_on_exit = true; bool gdb_stub = false; unsigned gdb_stub_port = 2159; }; struct Gx2Settings { bool dump_textures = false; bool dump_shaders = false; bool dump_shader_binaries_only = false; }; struct LogSettings { bool async = false; bool to_file = false; bool to_stdout = false; std::string level = "debug"; std::vector> levels = { { "ios_fs", "warn" }, }; std::string directory = "."; bool branch_trace = false; bool hle_trace = false; bool hle_trace_res = false; std::vector hle_trace_filters = { "+.*", "-coreinit.rpl::__ghsLock", "-coreinit.rpl::__ghsUnlock", "-coreinit.rpl::__gh_errno_ptr", "-coreinit.rpl::__gh_set_errno", "-coreinit.rpl::__gh_get_errno", "-coreinit.rpl::__get_eh_globals", "-coreinit.rpl::OSGetTime", "-coreinit.rpl::OSGetSystemTime", }; }; struct SoundSettings { bool dump_sounds = false; }; enum class SystemRegion { Japan = 0x01, USA = 0x02, Europe = 0x04, Unknown8 = 0x08, China = 0x10, Korea = 0x20, Taiwan = 0x40, }; struct SystemSettings { SystemRegion region = SystemRegion::Europe; std::string content_path = {}; std::string hfio_path = ""; std::string mlc_path = "mlc"; std::string otp_path = "otp.bin"; std::string sdcard_path = "sdcard"; std::string resources_path = "resources"; std::string slc_path = "slc"; std::vector title_directories = {}; bool time_scale_enabled = false; double time_scale = 1.0; std::vector lle_modules; bool dump_hle_rpl = false; // TODO: Move this to a debug api command? }; struct Settings { DebuggerSettings debugger; Gx2Settings gx2; LogSettings log; SoundSettings sound; SystemSettings system; }; std::shared_ptr config(); void setConfig(const Settings &settings); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_content.h ================================================ #pragma once #include namespace decaf { enum class TitleType { Application = 0, Demo = 2, Data = 0xB, DLC = 0xC, Update = 0xE, }; using TitleID = uint64_t; constexpr inline bool isSystemTitle(TitleID id) { return !!((id >> 32) & 0x10); } constexpr inline TitleType getTitleTypeFromID(TitleID id) { return static_cast((id >> 32) & 0x0f); } } // namespace decaf ================================================ FILE: src/libdecaf/decaf_debug_api.h ================================================ #pragma once #include #include #include #include #include #include #include #include namespace decaf::debug { using VirtualAddress = uint32_t; using PhysicalAddress = uint32_t; using CafeThreadHandle = VirtualAddress; using PauseCallback = std::function; struct AnalyseDatabase { struct Function { VirtualAddress start; VirtualAddress end; std::string name; }; struct Instruction { // Addresses of instructions which jump to this one std::vector sourceBranches; // User-left comments std::string comments; }; struct Lookup { //! Information about the function at this address. const Function *function = nullptr; //! Information about the instruction at this address. const Instruction *instruction = nullptr; }; std::vector functions; std::unordered_map instructions; }; struct CafeMemorySegment { //! Name of the memory segment. std::string name; //! Virtual Address of the start of the memory segment. VirtualAddress address; //! Size of the memory segment. uint32_t size; //! Alignment of the memory segment. uint32_t align; //! Segment has read permissions. bool read; //! Segment has write permissions. bool write; //! Segment has execute permissions. bool execute; }; struct CafeModuleInfo { //! Start address of text section. VirtualAddress textAddr = 0; //! Size of text. uint32_t textSize = 0; //! Start address of data section. VirtualAddress dataAddr = 0; //! Size of data. uint32_t dataSize = 0; }; struct CafeThread { enum ThreadAffinity { None = 0, Core0 = 1 << 0, Core1 = 1 << 1, Core2 = 1 << 2, Any = Core0 | Core1 | Core2, }; enum ThreadState { //! Thread is inactive. Inactive = 0, //! Thread is ready to execute. Ready = 1 << 0, //! Thread is currently executing. Running = 1 << 1, //! Thread is blocked waiting for something e.g. a mutex. Waiting = 1 << 2, //! Thread is about to be terminated. Moribund = 1 << 3, }; //! Virtual Address of the OSThread structure. CafeThreadHandle handle = 0; //! Assigned thread id. int id = -1; //! Name of the thread. std::string name; //! Current execution state. ThreadState state = ThreadState::Inactive; //! Thread core affinity. ThreadAffinity affinity = ThreadAffinity::None; //! Base thread priority. int basePriority = 0; //! Active thread priority. int priority = 0; //! The core this thread is currently running on, -1 if not running on a core. int coreId = -1; //! The amount of time this thread has been running. std::chrono::nanoseconds executionTime; //! The starting address of the stack. VirtualAddress stackStart = 0; //! The end address of the stack. VirtualAddress stackEnd = 0; //! Current execution address VirtualAddress cia = 0; //! Next execution address VirtualAddress nia = 0; //! Integer Registers std::array gpr; //! Floating-point Registers std::array fpr; //! Floating-point Registers - Paired Single 1 std::array ps1; //! Condition Register uint32_t cr; //! XER Carry/Overflow register uint32_t xer; //! Link Register uint32_t lr; //! Count Register uint32_t ctr; //! Machine State Register uint32_t msr; }; struct CpuBreakpoint { enum Type { SingleFire, MultiFire, }; //! Breakpoint type. Type type; //! Address of breakpoint. uint32_t address; //! Code at address before we inserted a TW instruction. uint32_t savedCode; }; struct CpuContext { //! Current execution address uint32_t cia; //! Next execution address uint32_t nia; //! Integer Registers std::array gpr; //! Floating-point Registers std::array fpr; //! Floating-point Registers - Paired Single 1 std::array ps1; //! Condition Register uint32_t cr; //! XER Carry/Overflow register uint32_t xer; //! Link Register uint32_t lr; //! Count Register uint32_t ctr; //! Floating-Point Status and Control Register uint32_t fpscr; //! Processor Version Register uint32_t pvr; //! Machine State Register uint32_t msr; //! Segment Registers std::array sr; //! Graphics Quantization Registers std::array gqr; //! Data Address Register uint32_t dar; //! DSI Status Register uint32_t dsisr; //! Machine Status Save and Restore Register 0 uint32_t srr0; }; struct CafeVoice { enum State { Stopped = 0, Playing = 1, }; enum Format { ADPCM = 0, LPCM16 = 0x0A, LPCM8 = 0x19, }; enum VoiceType { Default = 0, Streaming = 1, }; //! The index of this voice. int index = -1; //! Current play state of the voice. State state = State::Stopped; //! Encoding format of the voice data. Format format; //! Voice type. VoiceType type; //! Address of voice data. VirtualAddress data; //! Current offset into voice data. int currentOffset; //! Loop offset of voice data. int loopOffset; //! End offset of voice data. int endOffset; //! Looping enabled. bool loopingEnabled; }; enum class Pm4CaptureState { Disabled, WaitStartNextFrame, WaitEndNextFrame, Enabled, }; //! Check if the debug API is ready to be used, this returns true once a game //! .rpx has been loaded bool ready(); // Code analysis void analyseLoadedModules(AnalyseDatabase &db); void analyseCode(AnalyseDatabase &db, VirtualAddress start, VirtualAddress end); const AnalyseDatabase::Function *analyseLookupFunction(const AnalyseDatabase &db, VirtualAddress address); AnalyseDatabase::Lookup analyseLookupAddress(const AnalyseDatabase &db, VirtualAddress address); uint32_t analyseScanFunctionEnd(VirtualAddress start); void analyseToggleAsFunction(AnalyseDatabase &db, VirtualAddress address); // CafeOS bool findClosestSymbol(VirtualAddress addr, uint32_t *outSymbolDistance, char *symbolNameBuffer, uint32_t symbolNameBufferLength, char *moduleNameBuffer, uint32_t moduleNameBufferLength); bool getLoadedModuleInfo(CafeModuleInfo &info); bool sampleCafeMemorySegments(std::vector &segments); bool sampleCafeRunningThread(int coreId, CafeThread &info); bool sampleCafeThreads(std::vector &threads); bool sampleCafeVoices(std::vector &voiceInfos); // pm4 capture Pm4CaptureState pm4CaptureState(); bool pm4CaptureNextFrame(); bool pm4CaptureBegin(); bool pm4CaptureEnd(); // Controller void setPauseCallback(PauseCallback callback); bool pause(); bool resume(); bool isPaused(); int getPauseInitiatorCoreId(); const CpuContext *getPausedContext(int core); bool stepInto(int core); bool stepOver(int core); bool hasBreakpoint(VirtualAddress address); bool addBreakpoint(VirtualAddress address); bool removeBreakpoint(VirtualAddress address); // CPU void sampleCpuBreakpoints(std::vector &breakpoints); // Memory bool isValidVirtualAddress(VirtualAddress address); size_t getMemoryPageSize(); size_t readMemory(VirtualAddress address, void *dst, size_t size); size_t writeMemory(VirtualAddress address, const void *src, size_t size); } // namespace decaf::debug ================================================ FILE: src/libdecaf/decaf_erreula.h ================================================ #pragma once #include namespace decaf { class ErrEulaDriver { public: void buttonClicked(); void button1Clicked(); void button2Clicked(); virtual void onOpenErrorCode(int32_t errorCode) { }; virtual void onOpenErrorMessage(std::u16string message, std::u16string button1 = {}, std::u16string button2 = {}) { }; virtual void onClose() { }; }; void setErrEulaDriver(ErrEulaDriver *driver); ErrEulaDriver *errEulaDriver(); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_eventlistener.h ================================================ #pragma once #include "decaf_game.h" namespace decaf { enum class EventType { GameLoaded, }; class EventListener; void addEventListener(EventListener *listener); void removeEventListener(EventListener *listener); class EventListener { public: virtual ~EventListener() { removeEventListener(this); } virtual void onGameLoaded(const GameInfo &info) { }; }; } // namespace decaf ================================================ FILE: src/libdecaf/decaf_graphics.h ================================================ #pragma once #include #include #include namespace decaf { void setGraphicsDriver(gpu::GraphicsDriver *driver); gpu::GraphicsDriver * getGraphicsDriver(); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_input.h ================================================ #pragma once #include #include #include namespace decaf { namespace input { enum class Category { Invalid, VPAD, // DRC WPAD, // Wii Remote + extensions, Pro Controller, etc WBC, // Balance board }; namespace vpad { struct ButtonStatus { uint32_t sync : 1; uint32_t home : 1; uint32_t minus : 1; uint32_t plus : 1; uint32_t r : 1; uint32_t l : 1; uint32_t zr : 1; uint32_t zl : 1; uint32_t down : 1; uint32_t up : 1; uint32_t right : 1; uint32_t left : 1; uint32_t y : 1; uint32_t x : 1; uint32_t b : 1; uint32_t a : 1; uint32_t stickR : 1; uint32_t stickL : 1; }; struct AccelerometerStatus { float accelX; float accelY; float accelZ; float magnitute; float variation; float verticalX; float verticalY; }; struct GyroStatus { // TODO: Gyro status }; struct MagnetometerStatus { float x; float y; float z; }; struct TouchStatus { //! True if screen is currently being touched bool down; //! The normalised x-coordinate of a touched point, between 0.0 and 1.0. float x; //! The normalised y-coordinate of a touched point, between 0.0 and 1.0. float y; }; struct Status { //! Whether the controller is currently connected or not. bool connected; //! Indicates what buttons are held down. ButtonStatus buttons; //! Position of left analog stick float leftStickX; float leftStickY; //! Position of right analog stick float rightStickX; float rightStickY; //! Status of accelorometer AccelerometerStatus accelorometer; //! Status of gyro GyroStatus gyro; //! Current touch position on DRC TouchStatus touch; //! Status of DRC magnetometer MagnetometerStatus magnetometer; //! Current volume set by the slide control uint8_t slideVolume; //! Battery level of controller uint8_t battery; //! Status of DRC microphone uint8_t micStatus; }; } // namespace vpad namespace wpad { static const size_t MaxChannels = 4; enum class BaseControllerType { Disconnected, Wiimote, Classic, Pro, }; struct WiimoteButtonStatus { uint32_t up : 1; uint32_t down : 1; uint32_t right : 1; uint32_t left : 1; uint32_t a : 1; uint32_t b : 1; uint32_t button1 : 1; uint32_t button2 : 1; uint32_t minus : 1; uint32_t home : 1; uint32_t plus : 1; }; //! Wiimote struct WiimoteStatus { WiimoteButtonStatus buttons; }; struct ClassicButtonStatus { uint32_t up : 1; uint32_t down : 1; uint32_t right : 1; uint32_t left : 1; uint32_t a : 1; uint32_t b : 1; uint32_t x : 1; uint32_t y : 1; uint32_t r : 1; uint32_t l : 1; uint32_t zr : 1; uint32_t zl : 1; uint32_t plus : 1; uint32_t home : 1; uint32_t minus : 1; }; //! Wii Classic Controller struct ClassicStatus { ClassicButtonStatus buttons; float leftStickX; float leftStickY; float rightStickX; float rightStickY; float triggerL; float triggerR; }; struct ProButtonStatus { uint32_t up : 1; uint32_t down : 1; uint32_t right : 1; uint32_t left : 1; uint32_t a : 1; uint32_t b : 1; uint32_t x : 1; uint32_t y : 1; uint32_t r : 1; uint32_t l : 1; uint32_t zr : 1; uint32_t zl : 1; uint32_t plus : 1; uint32_t home : 1; uint32_t minus : 1; uint32_t leftStick : 1; uint32_t rightStick : 1; }; //! Wii U Pro Controller struct ProStatus { ProButtonStatus buttons; float leftStickX; float leftStickY; float rightStickX; float rightStickY; }; struct NunchukButtonStatus { uint32_t z : 1; uint32_t c : 1; }; //! Wii Nunchuk extension struct NunchukStatus { bool connected; NunchukButtonStatus buttons; float accelX; float accelY; float accelZ; float stickX; float stickY; }; //! Wii Motion Plus extension struct MotionPlusStatus { bool connected; float pitch; float yaw; float roll; }; struct Status { //! Base type of this controller, optionally a nunchuk or motion plus device //! can be connected on top of this base controller type. BaseControllerType type; union { WiimoteStatus wiimote; ProStatus pro; ClassicStatus classic; }; MotionPlusStatus motionPlus; NunchukStatus nunchuk; }; } // namespace wpad } // namespace input class InputDriver { public: virtual void sampleVpadController(int channel, input::vpad::Status &status) = 0; virtual void sampleWpadController(int channel, input::wpad::Status &status) = 0; }; void setInputDriver(InputDriver *driver); InputDriver *getInputDriver(); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_log.h ================================================ #pragma once #include #include #include #include namespace decaf { void initialiseLogging(std::string_view filename); std::shared_ptr makeLogger(std::string name, std::vector userSinks = {}); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_nullinputdriver.h ================================================ #pragma once #include "decaf_input.h" namespace decaf { class NullInputDriver : public InputDriver { public: virtual void sampleVpadController(int channel, input::vpad::Status &status) override; virtual void sampleWpadController(int channel, input::wpad::Status &status) override; }; } // namespace decaf ================================================ FILE: src/libdecaf/decaf_pm4replay.h ================================================ #pragma once #include #include #include namespace decaf::pm4 { #pragma pack(push, 1) static const std::array CaptureMagic = { 'D', 'P', 'M', '4' }; struct CapturePacket { enum Type : uint32_t { Invalid, CommandBuffer, MemoryLoad, RegisterSnapshot, SetBuffer, }; Type type; uint32_t size; uint64_t timestamp; }; struct CaptureMemoryLoad { enum MemoryType : uint32_t { Unknown, CpuFlush, SurfaceSync, ShadowState, CommandBuffer, AttributeBuffer, UniformBuffer, IndexBuffer, Surface, FetchShader, VertexShader, PixelShader, GeometryShader, }; MemoryType type; phys_addr address; }; struct CaptureSetBuffer { enum Type : uint32_t { Invalid, TvBuffer, DrcBuffer, }; Type type; phys_addr address; uint32_t size; uint32_t renderMode; uint32_t surfaceFormat; uint32_t bufferingMode; uint32_t width; uint32_t height; }; #pragma pack(pop) } // namespace decaf::pm4 ================================================ FILE: src/libdecaf/decaf_softwarekeyboard.h ================================================ #pragma once #include #include namespace decaf { class SoftwareKeyboardDriver { public: void accept(); void reject(); void setInputString(std::u16string_view text); virtual void onOpen(std::u16string defaultText) { }; virtual void onClose() { }; virtual void onInputStringChanged(std::u16string text) { }; }; void setSoftwareKeyboardDriver(SoftwareKeyboardDriver *driver); SoftwareKeyboardDriver *softwareKeyboardDriver(); } // namespace decaf ================================================ FILE: src/libdecaf/decaf_sound.h ================================================ #pragma once #include namespace decaf { class SoundDriver { public: virtual ~SoundDriver() { } virtual bool start(unsigned outputRate, unsigned numChannels) = 0; // Sample data has channels interleaved. The implementation may reuse // the sample buffer as a temporary buffer (of length // samples * numChannels * numSamples). // TODO: DRC/controller output not yet supported. virtual void output(int16_t *samples, unsigned numSamples) = 0; virtual void stop() = 0; }; void setSoundDriver(SoundDriver *driver); SoundDriver *getSoundDriver(); } // namespace decaf ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface.h ================================================ #pragma once #include #include #include #include #include #include namespace cafe { /** * Type used to indicate function takes variable arguments. */ struct var_args { // Set to the index of the first gpr register to use. uint32_t gpr; // Set to the index of the first fpr register to use. uint32_t fpr; }; namespace detail { /** * The register a type is stored in. * * We must distinguish between gpr for 32 and 64 bit values because a 64 bit * value has register index alignment in the PPC calling convention. */ enum class RegisterType { Gpr32, Gpr64, Fpr, Void, VarArgs, }; /** * A type T is stored in an FPR register if it is: * - A floating point type */ template using is_fpr_type = is_true::value>; /** * Check if a type looks like a common/bitfield.h defined bitfield. */ template struct void_t { typedef void type; }; template struct is_bitfield_type : std::false_type { }; template struct is_bitfield_type::type> : std::true_type { }; /** * A type T is stored in a single GPR register if it is: * - sizeof(T) <= 4 or T is a bool (sizeof bool is not defined) * - Not a floating point * - Not a var_args type * - If it is a cpu function pointer * - If it is a cpu pointer * - If a uint32_t can be constructed from T. */ template using is_gpr32_type = is_true< (sizeof(T) <= 4 || std::is_same::value) && !std::is_floating_point::value && !std::is_pointer::value && !std::is_same::value && (std::is_constructible::type, uint32_t>::value || cpu::is_cpu_pointer::value || cpu::is_cpu_func_pointer::value || is_bitfield_type::value)>; /** * A type T is stored in two aligned GPR registers if it is: * - sizeof(T) == 8 * - Not a floating point * - If a uint64_t can be constructed from T */ template using is_gpr64_type = is_true< sizeof(T) == 8 && !std::is_floating_point::value && !std::is_pointer::value && std::is_constructible::type, uint64_t>::value && !std::is_same::value>; template using is_var_args_type = is_true< std::is_same::value>; // Gets the register type for a type T. template struct register_type; template struct register_type::value>::type> { static constexpr auto value = RegisterType::Void; static constexpr auto return_index = 0; }; template struct register_type::value>::type> { static constexpr auto value = RegisterType::Fpr; static constexpr auto return_index = 1; }; template struct register_type::value>::type> { static constexpr auto value = RegisterType::Gpr32; static constexpr auto return_index = 3; }; template struct register_type::value>::type> { static constexpr auto value = RegisterType::Gpr64; static constexpr auto return_index = 3; }; template struct register_type::value>::type> { static constexpr auto value = RegisterType::VarArgs; static constexpr auto return_index = 0; }; // Prepends a type T to a tuple template struct tuple_prepend; template struct tuple_prepend> { using type = std::tuple; }; template struct tuple_prepend { using type = std::tuple; }; // Calculate the index for a given RegisterType template struct register_index; template struct register_index { static constexpr auto value = GprIndex; static constexpr auto gpr_next = value + 1; static constexpr auto fpr_next = FprIndex; }; template struct register_index { static constexpr auto value = ((GprIndex % 2) == 0) ? (GprIndex + 1) : GprIndex; static constexpr auto gpr_next = value + 2; static constexpr auto fpr_next = FprIndex; }; template struct register_index { static constexpr auto value = FprIndex; static constexpr auto gpr_next = GprIndex; static constexpr auto fpr_next = value + 1; }; template struct register_index { static constexpr auto value = GprIndex | (FprIndex << 8); static constexpr auto gpr_next = -1; static constexpr auto fpr_next = -1; }; // An empty type to store function parameter info template struct param_info_t { using type = ValueType; static constexpr auto reg_index = Index; static constexpr auto reg_type = Type; }; // Calculates a std::tuple type for a list of types template struct get_param_infos_impl; template struct get_param_infos_impl { using head_register_type = register_type>; using head_arg_index = register_index; using type = typename tuple_prepend< param_info_t, typename get_param_infos_impl::type >::type; }; template struct get_param_infos_impl { using type = std::tuple<>; }; // Creates a runtime data structure of parameter info struct RuntimeParamInfo { // Required default constructor for empty make_runtime_param_info constexpr RuntimeParamInfo() : reg_type(RegisterType::Gpr32), reg_index(0), is_signed(false), is_pointer(false), is_string(false) { } constexpr RuntimeParamInfo(RegisterType type, int index, bool is_signed, bool is_pointer, bool is_string) : reg_type(type), reg_index(index), is_signed(is_signed), is_pointer(is_pointer), is_string(is_string) { } RegisterType reg_type; int reg_index; bool is_signed; bool is_pointer; bool is_string; }; template static constexpr std::array make_runtime_param_info(std::tuple) { return { RuntimeParamInfo { Ts::reg_type, Ts::reg_index, std::is_signed::type>::value, cpu::is_cpu_pointer::value || cpu::is_cpu_func_pointer::value, std::is_same>::value }... }; } template static constexpr std::array make_runtime_param_info(std::tuple<>) { return {}; } // Stores information about a function template struct function_traits; template struct function_traits { using return_type = register_type>; using return_info = param_info_t; using param_info = typename get_param_infos_impl<3, 1, ArgTypes...>::type; static constexpr auto is_member_function = false; static constexpr auto num_args = sizeof...(ArgTypes); static constexpr auto has_return_value = !std::is_void::value; static constexpr std::array runtime_param_info = make_runtime_param_info(param_info {}); }; template struct function_traits { using return_type = register_type>; using return_info = param_info_t; using param_info = typename get_param_infos_impl<4, 1, ArgTypes...>::type; using object_info = param_info_t, RegisterType::Gpr32, 3>; static constexpr auto is_member_function = true; static constexpr auto num_args = sizeof...(ArgTypes); static constexpr auto has_return_value = !std::is_void::value; static constexpr std::array runtime_param_info = make_runtime_param_info(param_info{}); }; template struct function_traits : function_traits { static constexpr auto is_const_member_function = true; }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; template struct function_traits : function_traits { }; } // namespace detail } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface_invoke_guest.h ================================================ #pragma once #include "cafe_ppc_interface_params.h" #include namespace cafe { namespace detail { template inline decltype(auto) invoke_guest_impl(cpu::Core *core, cpu::VirtualAddress address, FunctionTraitsType &&, std::index_sequence, const ArgTypes &... args) { if constexpr (FunctionTraitsType::num_args > 0) { // Write arguments to registers auto param_info = typename FunctionTraitsType::param_info { }; (writeParam(core, std::get(param_info), args), ...); } // Write stack frame back chain auto frame = virt_cast(virt_addr{ core->gpr[1] - 8 }); frame[0] = core->systemCallStackHead; frame[1] = core->lr; core->gpr[1] -= 8; // Save state auto cia = core->cia; auto nia = core->nia; // Set state to our function to call core->cia = 0; core->nia = address.getAddress(); // Start executing! cpu::this_core::executeSub(); // Grab the most recent core state as it may have changed. core = cpu::this_core::state(); // Restore state core->cia = cia; core->nia = nia; // Restore stack core->gpr[1] += 8; // Return the result if constexpr (FunctionTraitsType::has_return_value) { return readParam(core, typename FunctionTraitsType::return_info { }); } } } // namespace detail // Invoke a guest function from a host context template inline decltype(auto) invoke(cpu::Core *core, cpu::FunctionPointer fn, const ArgTypes &... args) { using func_traits = detail::function_traits; return detail::invoke_guest_impl(core, fn.getAddress(), func_traits { }, std::make_index_sequence {}, args...); } // Invoke a be2_val guest function from a host context template inline decltype(auto) invoke(cpu::Core *core, be2_val> fn, const ArgTypes &... args) { using func_traits = detail::function_traits; return detail::invoke_guest_impl(core, fn.getAddress(), func_traits { }, std::make_index_sequence {}, args...); } } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface_invoke_host.h ================================================ #pragma once #include "cafe_ppc_interface_params.h" #include namespace cafe { namespace detail { // Because of the way the host invocations work, some of our functions // are no-return but they run through here anyways, causing the compiler // to become confused. We disable those warnings for this section. #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4702) #endif template inline cpu::Core * invoke_host_impl(cpu::Core *core, FunctionTraitsType &&, std::index_sequence) { auto param_info = typename FunctionTraitsType::param_info { }; if constexpr (FunctionTraitsType::has_return_value) { auto return_info = typename FunctionTraitsType::return_info { }; if constexpr (FunctionTraitsType::is_member_function) { auto obj = readParam(core, typename FunctionTraitsType::object_info { }); auto result = (obj.getRawPointer()->*HostFunc)(readParam(core, std::get(param_info))...); core = cpu::this_core::state(); writeParam(core, return_info, result); } else { auto result = HostFunc(readParam(core, std::get(param_info))...); core = cpu::this_core::state(); writeParam(core, return_info, result); } return core; } else { if constexpr (FunctionTraitsType::is_member_function) { auto obj = readParam(core, typename FunctionTraitsType::object_info { }); (obj.getRawPointer()->*HostFunc)(readParam(core, std::get(param_info))...); } else { HostFunc(readParam(core, std::get(param_info))...); } // We must refresh our Core* as it may have changed during the kernel call return cpu::this_core::state(); } } #ifdef _MSC_VER # pragma warning(pop) #endif } // namespace detail // Invoke a host function from a guest context template [[nodiscard]] inline cpu::Core * invoke(cpu::Core *core) { using func_traits = detail::function_traits; return detail::invoke_host_impl(core, func_traits { }, std::make_index_sequence {}); } } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface_params.h ================================================ #pragma once #include "cafe_ppc_interface.h" #include #include #include namespace cafe::detail { template inline uint32_t readGpr(cpu::Core* core) { if constexpr (regIndex <= 10) { return core->gpr[regIndex]; } else { // Args come after the backchain from the caller (8 bytes). auto addr = core->gpr[1] + 8 + 4 * static_cast(regIndex - 11); return *virt_cast(virt_addr { addr }); } } template inline ArgType readParam(cpu::Core* core, param_info_t) { using ValueType = std::remove_cv_t; if constexpr (regType == RegisterType::Gpr32) { auto value = readGpr(core); if constexpr (is_virt_ptr::value) { return virt_cast(static_cast(value)); } else if constexpr (is_phys_ptr::value) { return phys_cast(static_cast(value)); } else if constexpr (is_virt_func_ptr::value) { return virt_func_cast(static_cast(value)); } else if constexpr (is_bitfield_type::value) { return ArgType::get(value); } else if constexpr (std::is_same::value) { return !!value; } else { return static_cast(value); } } else if constexpr (regType == RegisterType::Gpr64) { auto hi = static_cast(readGpr(core)) << 32; auto lo = static_cast(readGpr(core)); return static_cast(hi | lo); } else if constexpr (regType == RegisterType::Fpr) { return static_cast(core->fpr[regIndex].paired0); } else if constexpr (regType == RegisterType::VarArgs) { return var_args { ((regIndex - 3) & 0xFF), (regIndex >> 8) }; } } template inline void writeParam(cpu::Core *core, param_info_t, const std::remove_cv_t &value) { using ValueType = std::remove_cv_t; static_assert(regType != RegisterType::VarArgs, "writeParam not supported with VarArgs"); if constexpr (regType == RegisterType::Gpr32) { if constexpr (is_virt_ptr::value) { core->gpr[regIndex] = static_cast(virt_cast(value)); } else if constexpr (is_phys_ptr::value) { core->gpr[regIndex] = static_cast(phys_cast(value)); } else if constexpr (is_virt_func_ptr::value) { core->gpr[regIndex] = static_cast(virt_func_cast(value)); } else if constexpr (is_bitfield_type::value) { core->gpr[regIndex] = static_cast(value.value); } else if constexpr (std::is_same::value) { core->gpr[regIndex] = static_cast(value ? 1 : 0); } else { core->gpr[regIndex] = static_cast(value); } } else if constexpr (regType == RegisterType::Gpr64) { core->gpr[regIndex] = static_cast((value >> 32) & 0xFFFFFFFF); core->gpr[regIndex + 1] = static_cast(value & 0xFFFFFFFF); } else if constexpr (regType == RegisterType::Fpr) { core->fpr[regIndex].paired0 = static_cast(value); } } template inline void writeParam(cpu::Core *core, param_info_t p, const be2_val &value) { writeParam(core, p, value.value()); } } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface_trace_host.cpp ================================================ #include "cafe_ppc_interface_trace_host.h" #include #include #include #include #include namespace cafe::detail { inline uint32_t readGpr(cpu::Core *core, int regIndex) { if (regIndex <= 10) { return core->gpr[regIndex]; } else { // Args come after the backchain from the caller (8 bytes). auto addr = core->gpr[1] + 8 + 4 * static_cast(regIndex - 11); return *virt_cast(virt_addr { addr }); } } void invoke_trace_host_impl(cpu::Core *core, const char *name, bool is_member_function, const RuntimeParamInfo *params, size_t numParams) { fmt::memory_buffer message; fmt::format_to(std::back_inserter(message), "{}(", name); if (is_member_function) { fmt::format_to(std::back_inserter(message), "this = {}, ", static_cast(readGpr(core, 3))); } for (auto i = 0u; i < numParams; ++i) { auto &p = params[i]; if (i > 0) { fmt::format_to(std::back_inserter(message), ", "); } switch (p.reg_type) { case RegisterType::Gpr32: if (p.is_string) { auto value = readGpr(core, p.reg_index); if (value) { fmt::format_to(std::back_inserter(message), "\"{}\"", virt_cast(static_cast(value)).get()); } else { fmt::format_to(std::back_inserter(message), "{}", static_cast(value)); } } else if (p.is_pointer) { fmt::format_to(std::back_inserter(message), "{}", static_cast(readGpr(core, p.reg_index))); } else if (p.is_signed) { fmt::format_to(std::back_inserter(message), "{}", static_cast(readGpr(core, p.reg_index))); } else { fmt::format_to(std::back_inserter(message), "{}", readGpr(core, p.reg_index)); } break; case RegisterType::Gpr64: { auto hi = static_cast(readGpr(core, p.reg_index)) << 32; auto lo = static_cast(readGpr(core, p.reg_index + 1)); if (p.is_signed) { fmt::format_to(std::back_inserter(message), "{}", static_cast(hi | lo)); } else { fmt::format_to(std::back_inserter(message), "{}", static_cast(hi | lo)); } break; } case RegisterType::Fpr: fmt::format_to(std::back_inserter(message), "{}", core->fpr[p.reg_index].paired0); break; case RegisterType::VarArgs: fmt::format_to(std::back_inserter(message), "..."); break; case RegisterType::Void: break; } } fmt::format_to(std::back_inserter(message), ") from 0x{:08X}", core->lr); gLog->debug(std::string_view { message.data(), message.size() }); } } // namespace cafe::detail ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface_trace_host.h ================================================ #pragma once #include "cafe_ppc_interface.h" #include namespace cafe { namespace detail { void invoke_trace_host_impl(cpu::Core *core, const char *name, bool is_member_function, const RuntimeParamInfo *params, size_t numParams); } // namespace detail //! Trace log a host function call from a guest context template void invoke_trace(cpu::Core *core, const char *name) { using func_traits = detail::function_traits; invoke_trace_host_impl(core, name, func_traits::is_member_function, func_traits::runtime_param_info.data(), func_traits::runtime_param_info.size()); } } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_ppc_interface_varargs.h ================================================ #pragma once #include "cafe_ppc_interface.h" #include #include namespace cafe { struct va_list { static constexpr auto NumSavedRegs = 8u; class iterator { public: iterator(va_list *list, unsigned gpr, unsigned fpr) : mList(list), mGpr(gpr), mFpr(fpr) { } template typename std::enable_if< sizeof(Type) == 4 && !std::is_floating_point::value && !std::is_pointer::value && !cpu::is_cpu_pointer::value , Type>::type next() { return bit_cast(nextGpr32()); } template typename std::enable_if< sizeof(Type) == 8 && !std::is_floating_point::value && !std::is_pointer::value && !cpu::is_cpu_pointer::value , Type>::type next() { return bit_cast(nextGpr64()); } template typename std::enable_if< cpu::is_cpu_pointer::value , Type>::type next() { return virt_cast(virt_addr { nextGpr32() }); } template typename std::enable_if< std::is_floating_point::value , Type>::type next() { return static_cast(nextFpr()); } protected: uint32_t nextGpr32() { auto value = uint32_t { 0 }; auto index = mGpr; if (index < NumSavedRegs) { value = mList->reg_save_area[index]; } else { value = mList->overflow_arg_area[index - NumSavedRegs]; } mGpr++; return value; } uint64_t nextGpr64() { // Align gpr to 64 bit if (mGpr % 2) { mGpr++; } auto value = static_cast(nextGpr32()) << 32; value |= nextGpr32(); return value; } double nextFpr() { auto value = double { 0.0 }; auto fpr_save_area = virt_cast(virt_addrof(mList->reg_save_area[NumSavedRegs])); if (mFpr < NumSavedRegs) { value = fpr_save_area[mFpr]; } else { decaf_abort("How the fuck do we handle va_list with FPR overflow"); } mFpr++; return value; } private: va_list *mList; unsigned mGpr; unsigned mFpr; }; iterator begin() { return iterator(this, firstGpr, firstFpr); } //! Index of first GPR be2_val firstGpr; //! Index of first FPR be2_val firstFpr; PADDING(2); //! Pointer to register values r10+ be2_virt_ptr overflow_arg_area; //! Pointer to register values r3...r10 followed by f1...f8 (if saved) be2_virt_ptr reg_save_area; }; CHECK_OFFSET(va_list, 0x00, firstGpr); CHECK_OFFSET(va_list, 0x01, firstFpr); CHECK_OFFSET(va_list, 0x04, overflow_arg_area); CHECK_OFFSET(va_list, 0x08, reg_save_area); CHECK_SIZE(va_list, 0x0C); /** * Structure to help us allocate a va_list on the stack */ struct stack_va_list { //! Actual va_list structure be2_struct list; //! Padding to 8 byte align PADDING(0x4); //! Save area for r3...r10 be2_array gpr_save_area; //! Save area for f1...f8 be2_array fpr_save_area; }; CHECK_OFFSET(stack_va_list, 0x00, list); CHECK_OFFSET(stack_va_list, 0x10, gpr_save_area); CHECK_OFFSET(stack_va_list, 0x30, fpr_save_area); CHECK_SIZE(stack_va_list, 0x70); static inline virt_ptr make_va_list(const var_args &va) { auto core = cpu::this_core::state(); auto overflow = virt_addr { core->gpr[1] + 8 + 8 }; // +8 for kcstub() adjustment // Allocate space on stack, 8 bytes for weird PPC ABI storage core->gpr[1] -= 8 + sizeof(stack_va_list); // Setup the va_list structure auto stack_list = virt_cast(virt_addr { core->gpr[1] + 8 }); stack_list->list.firstGpr = static_cast(va.gpr); stack_list->list.firstFpr = static_cast(va.fpr); stack_list->list.reg_save_area = virt_addrof(stack_list->gpr_save_area[0]); stack_list->list.overflow_arg_area = virt_cast(overflow); // Save r3...r10 for (auto i = 3; i <= 10; ++i) { stack_list->gpr_save_area[i - 3] = core->gpr[i]; } // Optionally save f1...f8 auto saveFloat = !!(core->cr.value & (1 << (31 - 6))); if (saveFloat) { for (auto i = 1; i <= 8; ++i) { stack_list->fpr_save_area[i - 1] = core->fpr[i].value; } } return virt_addrof(stack_list->list); } static inline void free_va_list(virt_ptr list) { auto core = cpu::this_core::state(); core->gpr[1] += 8 + sizeof(stack_va_list); } } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_stackobject.h ================================================ #pragma once #include #include #include #include #include #include #include namespace cafe { // #define DECAF_CHECK_STACK_CAFE_OBJECTS template class StackObject : public virt_ptr { static constexpr auto StackAlignment = align_up(Alignment, 4u); static constexpr auto StackSize = align_up(static_cast(sizeof(Type) * NumElements), StackAlignment); virt_addr mRestoreStackAddress; public: StackObject() { auto core = cpu::this_core::state(); // Adjust stack mRestoreStackAddress = virt_addr { core->gpr[1] }; auto address = align_down(mRestoreStackAddress - StackSize, StackAlignment); core->gpr[1] = static_cast(address); // Initialise object virt_ptr::mAddress = address; std::uninitialized_default_construct_n(virt_ptr::get(), NumElements); } StackObject(const Type &value) : StackObject() { std::memcpy(virt_ptr::get(), &value, sizeof(Type)); } ~StackObject() { auto core = cpu::this_core::state(); // Destroy object std::destroy_n(virt_ptr::get(), NumElements); // Restore stack core->gpr[1] = static_cast(mRestoreStackAddress); #ifdef DECAF_CHECK_STACK_CAFE_OBJECTS decaf_check(virt_ptr::mAddress == oldStackTop); #endif } // Disable copy StackObject(const StackObject &) = delete; StackObject &operator=(const StackObject&) = delete; // Disable move StackObject(StackObject &&) noexcept = delete; StackObject &operator=(StackObject&&) noexcept = delete; }; template class StackArray : public StackObject { public: StackArray() { } StackArray(const Type (&values)[NumElements]) { std::memcpy(virt_ptr::get(), &values, sizeof(Type) * NumElements); } // Disable copy StackArray(const StackArray &) = delete; StackArray &operator=(const StackArray &) = delete; // Disable move StackArray(StackArray &&) noexcept = delete; StackArray &operator=(StackArray &&) noexcept = delete; constexpr uint32_t size() const { return NumElements; } constexpr auto &operator[](std::size_t index) { return virt_ptr::get()[index]; } constexpr const auto &operator[](std::size_t index) const { return virt_ptr::get()[index]; } }; class StackString : public virt_ptr { public: StackString(std::string_view hostString) : mSize(align_up(static_cast(hostString.size()) + 1, 4)) { auto core = cpu::this_core::state(); // Adjust stack auto oldStackTop = virt_addr { core->gpr[1] }; auto newStackTop = oldStackTop - mSize; core->gpr[1] = newStackTop.getAddress(); // Initialise string virt_ptr::mAddress = virt_addr { newStackTop }; std::memcpy(virt_ptr::get(), hostString.data(), hostString.size()); virt_ptr::get()[hostString.size()] = char { 0 }; } ~StackString() { if (mSize) { auto core = cpu::this_core::state(); // Adjust stack auto oldStackTop = virt_addr { core->gpr[1] }; auto newStackTop = oldStackTop + mSize; core->gpr[1] = newStackTop.getAddress(); #ifdef DECAF_CHECK_STACK_CAFE_OBJECTS decaf_check(virt_ptr::mAddress == oldStackTop); #endif } } // Disable copy StackString(const StackString &) = delete; StackString &operator=(const StackString &) = delete; StackString(StackString &&from) : mSize(from.mSize) { from.mSize = 0u; virt_ptr::mAddress = from.mAddress; } StackString &operator =(StackString &&from) { mSize = from.mSize; virt_ptr::mAddress = from.mAddress; from.mSize = 0u; return *this; } private: uint32_t mSize; }; inline StackString make_stack_string(std::string_view str) { return { str }; } } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/cafe_tinyheap.cpp ================================================ #include "cafe_tinyheap.h" #include #include #include #include #include namespace cafe { template struct TrackingBlockBase { template using be2_pointer_type = be2_val>; //! Pointer to the data heap for this block be2_pointer_type data; //! Size is negative when unallocated, positive when allocated. be2_val size; //! Index of next block be2_val prevBlockIdx; //! Index of previous block be2_val nextBlockIdx; }; CHECK_OFFSET(TrackingBlockBase, 0x00, data); CHECK_OFFSET(TrackingBlockBase, 0x04, size); CHECK_OFFSET(TrackingBlockBase, 0x08, prevBlockIdx); CHECK_OFFSET(TrackingBlockBase, 0x0C, nextBlockIdx); CHECK_SIZE(TrackingBlockBase, 0x10); using TrackingBlockVirtual = TrackingBlockBase; using TrackingBlockPhysical = TrackingBlockBase; using TrackingBlock = TrackingBlockVirtual; static virt_ptr getTrackingBlocks(virt_ptr heap) { return virt_cast(virt_cast(heap) + sizeof(TinyHeapVirtual)); } static phys_ptr getTrackingBlocks(phys_ptr heap) { return phys_cast(phys_cast(heap) + sizeof(TinyHeapPhysical)); } // TODO: Make this pointer_cast in cpu headers? template static auto pointer_cast(virt_ptr src) { return virt_cast(src); } template static auto pointer_cast(be2_virt_ptr src) { return virt_cast(src); } template static auto pointer_cast(phys_ptr src) { return phys_cast(src); } template static auto pointer_cast(be2_phys_ptr src) { return phys_cast(src); } template static virt_addr pointer_to_address(virt_ptr src) { return virt_cast(src); } template static virt_addr pointer_to_address(be2_virt_ptr src) { return virt_cast(src); } template static phys_addr pointer_to_address(phys_ptr src) { return phys_cast(src); } template static phys_addr pointer_to_address(be2_phys_ptr src) { return phys_cast(src); } template static auto pointer_addrof(SrcType &src) { if constexpr (std::is_same::value) { return virt_addrof(src); } else { return phys_addrof(src); } } template static void dumpHeap(HeapPointer heap) { auto idx = heap->firstBlockIdx; auto blocks = getTrackingBlocks(heap); gLog->debug("Heap at {} - {}", heap->dataHeapStart, heap->dataHeapEnd); while (idx != -1) { auto addr = pointer_to_address(blocks[idx].data); auto size = blocks[idx].size; if (size < 0) { size = -size; } gLog->debug("block {} - {} {}", addr, addr + size, blocks[idx].size < 0 ? "free" : "alloc"); idx = blocks[idx].nextBlockIdx; } } template static int32_t findBlockIdxContaining(cpu::Pointer, AddressType> heap, cpu::Pointer ptr) { if (!heap || !ptr || ptr < heap->dataHeapStart || ptr >= heap->dataHeapEnd) { return -1; } auto trackingBlocks = getTrackingBlocks(heap); auto distFromStart = pointer_to_address(ptr) - pointer_to_address(heap->dataHeapStart); auto distFromEnd = pointer_to_address(heap->dataHeapEnd) - pointer_to_address(ptr); if (distFromStart < distFromEnd) { // Search forwards from start auto idx = heap->firstBlockIdx; while (idx >= 0) { auto &block = trackingBlocks[idx]; auto blockStart = pointer_cast(block.data); auto blockEnd = blockStart + (block.size >= 0 ? +block.size : -block.size); if (ptr >= blockStart && ptr < blockEnd) { return idx; } idx = block.nextBlockIdx; } } else { // Search backwards from end auto idx = heap->lastBlockIdx; while (idx >= 0) { auto &block = trackingBlocks[idx]; auto blockStart = pointer_cast(block.data); auto blockEnd = blockStart + (block.size >= 0 ? +block.size : -block.size); if (ptr >= blockStart && ptr < blockEnd) { return idx; } idx = block.prevBlockIdx; } } return -1; } template TinyHeapError TinyHeap_Setup(cpu::Pointer, AddressType> heap, int32_t trackingHeapSize, cpu::Pointer dataHeap, int32_t dataHeapSize) { if (trackingHeapSize < 64 || dataHeapSize <= 0) { return TinyHeapError::SetupFailed; } auto numTrackingBlocks = (trackingHeapSize - 0x30) / 16; if (numTrackingBlocks <= 0) { return TinyHeapError::SetupFailed; } auto trackingBlocks = getTrackingBlocks(heap); std::memset(trackingBlocks.get(), 0, numTrackingBlocks * sizeof(TrackingBlock)); for (auto i = 1; i < numTrackingBlocks; ++i) { trackingBlocks[i].prevBlockIdx = i - 1; trackingBlocks[i].nextBlockIdx = i + 1; } trackingBlocks[1].prevBlockIdx = -1; trackingBlocks[numTrackingBlocks - 1].nextBlockIdx = -1; trackingBlocks[0].data = dataHeap; trackingBlocks[0].size = -dataHeapSize; trackingBlocks[0].prevBlockIdx = -1; trackingBlocks[0].nextBlockIdx = -1; heap->dataHeapStart = dataHeap; heap->dataHeapEnd = pointer_cast(dataHeap) + dataHeapSize; heap->firstBlockIdx = 0; heap->lastBlockIdx = 0; heap->nextFreeBlockIdx = 1; return TinyHeapError::OK; } template static TinyHeapError allocInBlock(cpu::Pointer, AddressType> heap, int32_t blockIdx, int32_t beforeOffset, int32_t holeBeforeIdx, int32_t holeAfterIdx, int32_t size) { auto trackingBlocks = getTrackingBlocks(heap); auto &block = trackingBlocks[blockIdx]; // Check if we need to create a hole before the allocation if (beforeOffset != 0) { auto &beforeBlock = trackingBlocks[holeBeforeIdx]; beforeBlock.data = block.data; beforeBlock.size = -beforeOffset; beforeBlock.prevBlockIdx = block.prevBlockIdx; beforeBlock.nextBlockIdx = blockIdx; if (beforeBlock.prevBlockIdx >= 0) { trackingBlocks[beforeBlock.prevBlockIdx].nextBlockIdx = holeBeforeIdx; } else { heap->firstBlockIdx = holeBeforeIdx; } block.data = pointer_cast(block.data) + beforeOffset; block.size += beforeOffset; // += because block.size is negative at this point block.prevBlockIdx = holeBeforeIdx; } // Mark the block as allocated by flipping sign of size block.size = -block.size; // Check if we need to create a hole after the allocation if (block.size != size) { auto &afterBlock = trackingBlocks[holeAfterIdx]; afterBlock.data = pointer_cast(block.data) + size; afterBlock.size = -(block.size - size); afterBlock.prevBlockIdx = blockIdx; afterBlock.nextBlockIdx = block.nextBlockIdx; if (afterBlock.nextBlockIdx >= 0) { trackingBlocks[afterBlock.nextBlockIdx].prevBlockIdx = holeAfterIdx; } else { heap->lastBlockIdx = holeAfterIdx; } block.size = size; block.nextBlockIdx = holeAfterIdx; } return TinyHeapError::OK; } template static void freeBlock(cpu::Pointer, AddressType> heap, int32_t blockIdx) { auto trackingBlocks = getTrackingBlocks(heap); auto block = pointer_addrof(trackingBlocks[blockIdx]); // Mark the block as unallocated block->size = -block->size; if (block->prevBlockIdx >= 0) { auto prevBlockIdx = block->prevBlockIdx; auto prevBlock = pointer_addrof(trackingBlocks[prevBlockIdx]); if (prevBlock->size < 0) { // Merge block into prevBlock! prevBlock->size += block->size; prevBlock->nextBlockIdx = block->nextBlockIdx; if (prevBlock->nextBlockIdx >= 0) { trackingBlocks[prevBlock->nextBlockIdx].prevBlockIdx = prevBlockIdx; } else { heap->lastBlockIdx = prevBlockIdx; } // Insert block at start of free list block->data = nullptr; block->size = 0; block->prevBlockIdx = -1; block->nextBlockIdx = heap->nextFreeBlockIdx; if (heap->nextFreeBlockIdx >= 0) { trackingBlocks[heap->nextFreeBlockIdx].prevBlockIdx = blockIdx; } heap->nextFreeBlockIdx = blockIdx; // Set block to prevBlock so we can maybe merge with nextBlock block = prevBlock; blockIdx = prevBlockIdx; } } if (block->nextBlockIdx >= 0) { auto nextBlockIdx = block->nextBlockIdx; auto nextBlock = pointer_addrof(trackingBlocks[nextBlockIdx]); if (nextBlock->size < 0) { // Merge nextBlock into block! block->size += nextBlock->size; block->nextBlockIdx = nextBlock->nextBlockIdx; if (block->nextBlockIdx >= 0) { trackingBlocks[block->nextBlockIdx].prevBlockIdx = blockIdx; } else { heap->lastBlockIdx = blockIdx; } // Insert nextBlock at start of free list nextBlock->data = nullptr; nextBlock->size = 0; nextBlock->prevBlockIdx = -1; nextBlock->nextBlockIdx = heap->nextFreeBlockIdx; if (heap->nextFreeBlockIdx >= 0) { trackingBlocks[heap->nextFreeBlockIdx].prevBlockIdx = nextBlockIdx; } heap->nextFreeBlockIdx = nextBlockIdx; } } } TinyHeapError TinyHeap_Setup(virt_ptr heap, int32_t trackingHeapSize, virt_ptr dataHeap, int32_t dataHeapSize) { return TinyHeap_Setup(heap, trackingHeapSize, dataHeap, dataHeapSize); } TinyHeapError TinyHeap_Setup(phys_ptr heap, int32_t trackingHeapSize, phys_ptr dataHeap, int32_t dataHeapSize) { return TinyHeap_Setup(heap, trackingHeapSize, dataHeap, dataHeapSize); } template TinyHeapError TinyHeap_Alloc(cpu::Pointer, AddressType> heap, int32_t size, int32_t align, cpu::Pointer *outPtr) { auto fromFront = true; if (!heap) { return TinyHeapError::InvalidHeap; } *outPtr = nullptr; if (size <= 0) { return TinyHeapError::OK; } auto trackingBlocks = getTrackingBlocks(heap); auto blockIdx = -1; if (align < 0) { align = -align; fromFront = false; } if (fromFront) { // Search forwards from first auto idx = heap->firstBlockIdx; while (idx >= 0) { auto &block = trackingBlocks[idx]; if (block.size < 0) { auto blockStart = pointer_to_address(block.data); auto blockEnd = blockStart - block.size; auto alignedStart = align_up(blockStart, align); if (alignedStart + size <= blockEnd) { blockIdx = idx; break; } } idx = block.nextBlockIdx; } } else { // Search backwards from last auto idx = heap->lastBlockIdx; while (idx >= 0) { auto &block = trackingBlocks[idx]; if (block.size < 0) { auto blockStart = pointer_to_address(block.data); auto blockEnd = blockStart - block.size; auto alignedStart = align_up(blockStart, align); if (alignedStart + size <= blockEnd) { blockIdx = idx; break; } } idx = block.prevBlockIdx; } } if (blockIdx < 0) { // No blocks to fit this allocation in return TinyHeapError::AllocFailed; } auto &block = trackingBlocks[blockIdx]; auto blockStart = pointer_to_address(block.data); auto blockEnd = blockStart - block.size; auto alignedStart = fromFront ? align_up(blockStart, align) : align_down(blockEnd - size, align); auto beforeOffset = static_cast(alignedStart - blockStart); auto afterOffset = blockEnd - (alignedStart + size); // Check if we need to insert a block before allocated block auto holeBeforeIdx = -1; if (beforeOffset != 0) { holeBeforeIdx = heap->nextFreeBlockIdx; if (holeBeforeIdx < 0) { // No free block to use to punch a hole return TinyHeapError::AllocFailed; } heap->nextFreeBlockIdx = trackingBlocks[holeBeforeIdx].nextBlockIdx; } // Check if we need to insert a block after allocated block auto holeAfterIdx = -1; if (afterOffset != 0) { holeAfterIdx = heap->nextFreeBlockIdx; if (holeAfterIdx < 0) { // No free block to use to punch a hole if (holeBeforeIdx >= 0) { // Restore holeBeforeIdx heap->nextFreeBlockIdx = holeBeforeIdx; } return TinyHeapError::AllocFailed; } heap->nextFreeBlockIdx = trackingBlocks[holeAfterIdx].nextBlockIdx; } auto error = allocInBlock(heap, blockIdx, beforeOffset, holeBeforeIdx, holeAfterIdx, size); *outPtr = block.data; return error; } TinyHeapError TinyHeap_Alloc(virt_ptr heap, int32_t size, int32_t align, virt_ptr *outPtr) { return TinyHeap_Alloc(heap, size, align, outPtr); } TinyHeapError TinyHeap_Alloc(phys_ptr heap, int32_t size, int32_t align, phys_ptr *outPtr) { return TinyHeap_Alloc(heap, size, align, outPtr); } template TinyHeapError TinyHeap_AllocAt(cpu::Pointer, AddressType> heap, cpu::Pointer ptr, int32_t size) { auto blockIdx = findBlockIdxContaining(heap, ptr); if (blockIdx < 0) { // Address not in heap return TinyHeapError::InvalidHeap; } auto trackingBlocks = getTrackingBlocks(heap); auto &block = trackingBlocks[blockIdx]; if (block.size > 0) { // Already allocated return TinyHeapError::AllocAtFailed; } auto beforeOffset = static_cast(pointer_cast(ptr) - pointer_cast(block.data)); auto afterOffset = (pointer_cast(ptr) + -block.size) - (pointer_cast(block.data) + size); if (afterOffset < 0) { // Not enough space return TinyHeapError::AllocAtFailed; } // Check if we need to insert a block before allocated block auto holeBeforeIdx = -1; if (beforeOffset != 0) { holeBeforeIdx = heap->nextFreeBlockIdx; if (holeBeforeIdx < 0) { // No free block to use to punch a hole return TinyHeapError::AllocAtFailed; } heap->nextFreeBlockIdx = trackingBlocks[holeBeforeIdx].nextBlockIdx; } // Check if we need to insert a block after allocated block auto holeAfterIdx = -1; if (afterOffset != 0) { holeAfterIdx = heap->nextFreeBlockIdx; if (holeAfterIdx < 0) { // No free block to use to punch a hole if (holeBeforeIdx >= 0) { // Restore holeBeforeIdx heap->nextFreeBlockIdx = holeBeforeIdx; } return TinyHeapError::AllocAtFailed; } heap->nextFreeBlockIdx = trackingBlocks[holeAfterIdx].nextBlockIdx; } return allocInBlock(heap, blockIdx, beforeOffset, holeBeforeIdx, holeAfterIdx, size); } TinyHeapError TinyHeap_AllocAt(virt_ptr heap, virt_ptr ptr, int32_t size) { return TinyHeap_AllocAt(heap, ptr, size); } TinyHeapError TinyHeap_AllocAt(phys_ptr heap, phys_ptr ptr, int32_t size) { return TinyHeap_AllocAt(heap, ptr, size); } template static void TinyHeap_Free(cpu::Pointer, AddressType> heap, cpu::Pointer ptr) { auto blockIdx = findBlockIdxContaining(heap, ptr); if (blockIdx < 0) { // Address not in heap return; } auto trackingBlocks = getTrackingBlocks(heap); auto &block = trackingBlocks[blockIdx]; if (block.data != ptr) { // Can only free whole blocks return; } if (block.size < 0) { // Already free return; } freeBlock(heap, blockIdx); } void TinyHeap_Free(virt_ptr heap, virt_ptr ptr) { TinyHeap_Free(heap, ptr); } void TinyHeap_Free(phys_ptr heap, phys_ptr ptr) { TinyHeap_Free(heap, ptr); } template static int32_t TinyHeap_GetLargestFree(cpu::Pointer, AddressType> heap) { if (!heap) { return 0; } if (heap->firstBlockIdx == -1) { return 0; } auto trackingBlocks = getTrackingBlocks(heap); auto idx = heap->firstBlockIdx; auto largestFree = 0; while (idx >= 0) { auto &block = trackingBlocks[idx]; if (block.size < 0) { largestFree = std::max(largestFree, -block.size); } idx = block.nextBlockIdx; } return largestFree; } int32_t TinyHeap_GetLargestFree(virt_ptr heap) { return TinyHeap_GetLargestFree(heap); } int32_t TinyHeap_GetLargestFree(phys_ptr heap) { return TinyHeap_GetLargestFree(heap); } template static cpu::Pointer TinyHeap_Enum(cpu::Pointer, AddressType> heap, cpu::Pointer prevBlockPtr, cpu::Pointer *outPtr, uint32_t *outSize) { if (!heap) { return nullptr; } auto blocks = getTrackingBlocks(heap); auto block = blocks + heap->firstBlockIdx; if (prevBlockPtr) { // Iterate through list to make sure prevBlockPtr is actually in it. auto prevBlock = pointer_cast(prevBlockPtr); while (block != prevBlock) { if (block->nextBlockIdx == -1) { goto error; } block = blocks + block->nextBlockIdx; } // Now we're at prevBlock, let's go to the next block if (block->nextBlockIdx == -1) { goto error; } block = blocks + block->nextBlockIdx; } if (block) { // Find the next allocated block while (block->size <= 0) { if (block->nextBlockIdx == -1) { goto error; } block = blocks + block->nextBlockIdx; } if (outPtr) { *outPtr = block->data; } if (outSize) { *outSize = static_cast(block->size); } return block; } error: if (outPtr) { *outPtr = nullptr; } if (outSize) { *outSize = 0; } return nullptr; } virt_ptr TinyHeap_Enum(virt_ptr heap, virt_ptr prevBlockPtr, virt_ptr *outPtr, uint32_t *outSize) { return TinyHeap_Enum(heap, prevBlockPtr, outPtr, outSize); } phys_ptr TinyHeap_Enum(phys_ptr> heap, phys_ptr prevBlockPtr, phys_ptr *outPtr, uint32_t *outSize) { return TinyHeap_Enum(heap, prevBlockPtr, outPtr, outSize); } template static cpu::Pointer TinyHeap_EnumFree(cpu::Pointer, AddressType> heap, cpu::Pointer prevBlockPtr, cpu::Pointer *outPtr, uint32_t *outSize) { if (!heap) { return nullptr; } auto blocks = getTrackingBlocks(heap); auto block = blocks + heap->firstBlockIdx; if (prevBlockPtr) { // Iterate through list to make sure prevBlockPtr is actually in it. auto prevBlock = pointer_cast *>(prevBlockPtr); while (block != prevBlock) { if (block->nextBlockIdx == -1) { goto error; } block = blocks + block->nextBlockIdx; } // Now we're at prevBlock, let's go to the next block if (block->nextBlockIdx == -1) { goto error; } block = blocks + block->nextBlockIdx; } if (block) { // Find the next free block while (block->size >= 0) { if (block->nextBlockIdx == -1) { goto error; } block = blocks + block->nextBlockIdx; } if (outPtr) { *outPtr = block->data; } if (outSize) { *outSize = static_cast(-block->size); } return block; } error: if (outPtr) { *outPtr = nullptr; } if (outSize) { *outSize = 0; } return nullptr; } virt_ptr TinyHeap_EnumFree(virt_ptr heap, virt_ptr prevBlockPtr, virt_ptr *outPtr, uint32_t *outSize) { return TinyHeap_EnumFree(heap, prevBlockPtr, outPtr, outSize); } phys_ptr TinyHeap_EnumFree(phys_ptr> heap, phys_ptr prevBlockPtr, phys_ptr *outPtr, uint32_t *outSize) { return TinyHeap_EnumFree(heap, prevBlockPtr, outPtr, outSize); } } // namespace cafe::tinyheap ================================================ FILE: src/libdecaf/src/cafe/cafe_tinyheap.h ================================================ #pragma once #include namespace cafe { constexpr auto TinyHeapHeaderSize = 0x30; constexpr auto TinyHeapBlockSize = 16; enum class TinyHeapError { OK = 0, SetupFailed = -520001, InvalidHeap = -520002, AllocAtFailed = -520003, AllocFailed = -520004, }; template struct TinyHeapBase { template using be2_pointer_type = be2_val>; //! Pointer to the start of the data heap be2_pointer_type dataHeapStart; //! Pointer to the end of the data heap be2_pointer_type dataHeapEnd; //! Index of first tracking block be2_val firstBlockIdx; //! Index of last tracking block be2_val lastBlockIdx; //! Index of first unused tracking block be2_val nextFreeBlockIdx; }; CHECK_OFFSET(TinyHeapBase, 0x00, dataHeapStart); CHECK_OFFSET(TinyHeapBase, 0x04, dataHeapEnd); CHECK_OFFSET(TinyHeapBase, 0x08, firstBlockIdx); CHECK_OFFSET(TinyHeapBase, 0x0C, lastBlockIdx); CHECK_OFFSET(TinyHeapBase, 0x10, nextFreeBlockIdx); CHECK_SIZE(TinyHeapBase, 0x14); using TinyHeapVirtual = TinyHeapBase; using TinyHeapPhysical = TinyHeapBase; using TinyHeap = TinyHeapVirtual; // TinyHeap virtual TinyHeapError TinyHeap_Setup(virt_ptr heap, int32_t trackingHeapSize, virt_ptr dataHeap, int32_t dataHeapSize); TinyHeapError TinyHeap_Alloc(virt_ptr heap, int32_t size, int32_t align, virt_ptr *outPtr); TinyHeapError TinyHeap_AllocAt(virt_ptr heap, virt_ptr ptr, int32_t size); void TinyHeap_Free(virt_ptr heap, virt_ptr ptr); int32_t TinyHeap_GetLargestFree(virt_ptr heap); virt_ptr TinyHeap_Enum(virt_ptr heap, virt_ptr prevBlockPtr, virt_ptr *outPtr, uint32_t *outSize); virt_ptr TinyHeap_EnumFree(virt_ptr heap, virt_ptr prevBlockPtr, virt_ptr *outPtr, uint32_t *outSize); // TinyHeap physical TinyHeapError TinyHeap_Setup(phys_ptr heap, int32_t trackingHeapSize, phys_ptr dataHeap, int32_t dataHeapSize); TinyHeapError TinyHeap_Alloc(phys_ptr heap, int32_t size, int32_t align, phys_ptr *outPtr); TinyHeapError TinyHeap_AllocAt(phys_ptr heap, phys_ptr ptr, int32_t size); void TinyHeap_Free(phys_ptr heap, phys_ptr ptr); int32_t TinyHeap_GetLargestFree(phys_ptr heap); phys_ptr TinyHeap_Enum(phys_ptr heap, phys_ptr prevBlockPtr, phys_ptr *outPtr, uint32_t *outSize); phys_ptr TinyHeap_EnumFree(phys_ptr heap, phys_ptr prevBlockPtr, phys_ptr *outPtr, uint32_t *outSize); } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel.cpp ================================================ #include "cafe_kernel.h" #include "cafe_kernel_context.h" #include "cafe_kernel_exception.h" #include "cafe_kernel_heap.h" #include "cafe_kernel_ipckdriver.h" #include "cafe_kernel_ipc.h" #include "cafe_kernel_lock.h" #include "cafe_kernel_loader.h" #include "cafe_kernel_mcp.h" #include "cafe_kernel_mmu.h" #include "cafe_kernel_process.h" #include "cafe_kernel_shareddata.h" #include "cafe_kernel_userdrivers.h" #include "cafe/libraries/cafe_hle.h" #include "debug_api/debug_api_controller.h" #include "decaf_config.h" #include "decaf_configstorage.h" #include "decaf_events.h" #include "decaf_game.h" #include "ios/mcp/ios_mcp_mcp_types.h" #include #include #include #include #include #include namespace cafe::kernel { struct StaticKernelData { struct CoreData { // Used for cpu branch trace handler be2_val symbolDistance; be2_array symbolNameBuffer; be2_array moduleNameBuffer; }; be2_array coreData; be2_struct prepareTitleInfo; }; static virt_ptr sKernelData = nullptr; static internal::AddressSpace sKernelAddressSpace; static std::array, 3> sSubCoreEntryContexts = { }; static std::string sExecutableName; static std::atomic sStopping { false }; static std::atomic sBranchTraceEnabled { false }; static std::atomic sBranchTraceHandlerSet { false }; static void mainCoreEntryPoint(cpu::Core *core) { internal::setActiveAddressSpace(&sKernelAddressSpace); internal::initialiseWorkAreaHeap(); internal::initialiseCoreContext(core); internal::initialiseExceptionContext(core); internal::initialiseExceptionHandlers(); // Set all cores to kernel process for (auto i = 0; i < 3; ++i) { internal::initialiseCoreProcess(i, RamPartitionId::Kernel, UniqueProcessId::Kernel, KernelProcessId::Invalid); } internal::initialiseProcessData(); internal::ipckDriverInit(); internal::ipckDriverOpen(); internal::initialiseIpc(); // TODO: This is normally called by root.rpx loadShared(); internal::registerRootUserDrivers(); // Prepare title auto titleInfo = virt_addrof(sKernelData->prepareTitleInfo); if (auto error = internal::mcpPrepareTitle(ios::mcp::DefaultTitleId, titleInfo); error || titleInfo->argstr[0] == '\0') { // Not a full title - fill out some default values! titleInfo->version = 1u; titleInfo->cmdFlags = 0u; titleInfo->avail_size = 0u; titleInfo->codegen_size = 0u; titleInfo->codegen_core = 1u; titleInfo->max_size = 0x40000000u; titleInfo->max_codesize = 0x0E000000u; titleInfo->default_stack0_size = 0u; titleInfo->default_stack1_size = 0u; titleInfo->default_stack2_size = 0u; titleInfo->exception_stack0_size = 0x1000u; titleInfo->exception_stack1_size = 0x1000u; titleInfo->exception_stack2_size = 0x1000u; string_copy(virt_addrof(titleInfo->argstr).get(), titleInfo->argstr.size(), sExecutableName.data(), sExecutableName.size()); } else { gLog->info("Loaded title {:016X}, argstr \"{}\"", titleInfo->titleId, virt_addrof(titleInfo->argstr).get()); } auto rpx = std::string_view { virt_addrof(titleInfo->argstr).get() }; if (rpx.empty()) { gLog->error("Could not find game executable to load."); return; } // Perform the initial load internal::loadGameProcess(rpx, titleInfo); // Notify front end that game is loaded auto gameInfo = decaf::GameInfo { }; gameInfo.titleId = titleInfo->titleId; if (auto pos = rpx.find_first_of(' '); pos != std::string_view::npos) { gameInfo.executable = rpx.substr(0, pos); } else { gameInfo.executable = rpx; } decaf::event::onGameLoaded(gameInfo); // Start the game internal::finishInitAndPreload(); } static void subCoreEntryPoint(cpu::Core *core) { internal::setActiveAddressSpace(&sKernelAddressSpace); internal::initialiseCoreContext(core); internal::initialiseExceptionContext(core); internal::ipckDriverInit(); internal::ipckDriverOpen(); while (!sStopping.load()) { internal::kernelLockAcquire(); auto entryContext = sSubCoreEntryContexts[core->id]; internal::kernelLockRelease(); if (entryContext) { // Set the core's current process to the main application internal::setCoreToProcessId(RamPartitionId::MainApplication, KernelProcessId::Kernel); internal::initialiseCoreProcess(core->id, RamPartitionId::MainApplication, UniqueProcessId::Game, KernelProcessId::Kernel); switchContext(entryContext); break; } cpu::this_core::waitNextInterrupt(); } } void setSubCoreEntryContext(int coreId, virt_ptr context) { internal::kernelLockAcquire(); sSubCoreEntryContexts[coreId] = context; internal::kernelLockRelease(); cpu::interrupt(coreId, cpu::GENERIC_INTERRUPT); } static void cpuEntrypoint(cpu::Core *core) { if (core->id == 1) { mainCoreEntryPoint(core); } else { subCoreEntryPoint(core); } internal::idleCoreLoop(core); } static void cpuBranchTraceHandler(cpu::Core *core, uint32_t target) { if (sBranchTraceEnabled) { auto &data = sKernelData->coreData[core->id]; auto symbolFound = internal::findClosestSymbol(virt_addr { target }, virt_addrof(data.symbolDistance), virt_addrof(data.symbolNameBuffer), data.symbolNameBuffer.size(), virt_addrof(data.moduleNameBuffer), data.moduleNameBuffer.size()); if (symbolFound && data.moduleNameBuffer[0] && data.symbolNameBuffer[0]) { gLog->trace("CPU branched to: 0x{:08X} {}|{}+0x{:X}", target, virt_addrof(data.moduleNameBuffer).get(), virt_addrof(data.symbolNameBuffer).get(), data.symbolDistance); } else { gLog->trace("CPU branched to: 0x{:08X}", target); } } } static cpu::Core * cpuUnknownSystemCallHandler(cpu::Core *core, uint32_t id) { return cafe::hle::Library::handleUnknownSystemCall(core, id); } void start() { // Register config change handler static std::once_flag sRegisteredConfigChangeListener; std::call_once(sRegisteredConfigChangeListener, []() { decaf::registerConfigChangeListener( [](const decaf::Settings &settings) { if (settings.log.branch_trace && !sBranchTraceHandlerSet) { cpu::setBranchTraceHandler(&cpuBranchTraceHandler); sBranchTraceHandlerSet = true; } sBranchTraceEnabled = settings.log.branch_trace; }); }); // Initialise CafeOS HLE hle::initialiseLibraries(); // Initialise memory internal::initialiseAddressSpace(&sKernelAddressSpace, RamPartitionId::Kernel, phys_addr { 0x72000000 }, 0x0E000000, phys_addr { 0x20000000 }, 0x52000000, 0, 0, phys_addr { 0 }, 0, phys_addr { 0 }, 0, 0, false); internal::loadAddressSpace(&sKernelAddressSpace); internal::initialiseStaticDataHeap(); // Initialise static data sKernelData = internal::allocStaticData(); internal::initialiseStaticContextData(); internal::initialiseStaticExceptionData(); internal::initialiseStaticIpckDriverData(); internal::initialiseStaticIpcData(); internal::initialiseStaticUserDriversData(); // Setup cpu cpu::setCoreEntrypointHandler(&cpuEntrypoint); sBranchTraceEnabled = decaf::config()->log.branch_trace; if (sBranchTraceEnabled) { cpu::setBranchTraceHandler(&cpuBranchTraceHandler); sBranchTraceHandlerSet = true; } cpu::setUnknownSystemCallHandler(&cpuUnknownSystemCallHandler); // Start the cpu cpu::start(); } bool stopping() { return sStopping; } void join() { cpu::join(); } void stop() { if (!sStopping) { sStopping = true; cpu::halt(); } } void setExecutableFilename(const std::string& name) { sExecutableName = name; } namespace internal { void idleCoreLoop(cpu::Core *core) { // Set up the default expected state for the nia/cia of idle threads. // This must be kept in sync with reschedule which sets them to this // for debugging purposes. core->nia = 0xFFFFFFFF; core->cia = 0xFFFFFFFF; while (!sStopping.load()) { cpu::this_core::waitForInterrupt(); } gLog->info("Core {} exit", core->id); } void exit() { // Cafe kernel is about to exit - IOS threads should also stop. auto error = IOS_Ioctl(RamPartitionId::Kernel, RamPartitionId::Invalid, getPpcAppHandle(), ios::mcp::PPCAppCommand::PowerOff, nullptr, 0, nullptr, 0); if (error != ios::Error::OK) { gLog->warn("/dev/ppc_app power off ioctl failed with error: {}", error); } // Set the running flag to false so idle loops exit. sStopping = true; // Tell the CPU to stop. if (decaf::config()->debugger.break_on_exit) { decaf::debug::handleDebugBreakInterrupt(); } cpu::halt(); // Switch to idle context to prevent further execution. switchContext(nullptr); } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel.h ================================================ #pragma once #include #include namespace cpu { struct Core; } namespace cafe::kernel { struct Context; void start(); void join(); void stop(); bool stopping(); void setExecutableFilename(const std::string &name); void setSubCoreEntryContext(int coreId, virt_ptr context); namespace internal { void idleCoreLoop(cpu::Core *core); void exit(); } // internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_context.cpp ================================================ #include "cafe_kernel_context.h" #include "cafe_kernel_heap.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/kernel/cafe_kernel.h" #include "cafe/libraries/cafe_hle.h" #include #include #include #include namespace cafe::kernel { struct HostContext { platform::Fiber *fiber = nullptr; virt_ptr context = nullptr; cpu::Tracer *tracer = nullptr; }; static std::array, 3> sCurrentContext; static std::array, 3> sDeadContext; static std::array, 3> sIdleContext; constexpr auto CoreThreadStackSize = 0x100u; struct StaticContextData { be2_array coreThreadContext; be2_array coreThreadStackBuffer; }; static virt_ptr sContextData; static void checkDeadContext(); using ContextEntryPoint = virt_func_ptr; void copyContextFromCpu(virt_ptr context) { auto state = cpu::this_core::state(); for (auto i = 0; i < 32; ++i) { context->gpr[i] = state->gpr[i]; } for (auto i = 0; i < 32; ++i) { context->fpr[i] = state->fpr[i].value; context->psf[i] = state->fpr[i].paired1; } for (auto i = 0; i < 8; ++i) { context->gqr[i] = state->gqr[i].value; } context->cr = state->cr.value; context->lr = state->lr; context->ctr = state->ctr; context->xer = state->xer.value; context->fpscr = state->fpscr.value; context->srr0 = state->srr0; context->dar = state->dar; context->dsisr = state->dsisr; } void copyContextToCpu(virt_ptr context) { auto state = cpu::this_core::state(); for (auto i = 0; i < 32; ++i) { state->gpr[i] = context->gpr[i]; } for (auto i = 0; i < 32; ++i) { state->fpr[i].value = context->fpr[i]; state->fpr[i].paired1 = context->psf[i]; } for (auto i = 0; i < 8; ++i) { state->gqr[i].value = context->gqr[i]; } state->cr.value = context->cr; state->lr = context->lr; state->ctr = context->ctr; state->xer.value = context->xer; state->fpscr.value = context->fpscr; state->srr0 = context->srr0; state->dar = context->dar; state->dsisr = context->dsisr; } void sleepCurrentContext() { // Grab the current core and context information auto core = cpu::this_core::state(); auto context = sCurrentContext[core->id]; decaf_check(context); // Save all our registers to the context copyContextFromCpu(context); context->nia = core->nia; context->cia = core->cia; // Some things to help us when debugging... core->nia = 0xFFFFFFFF; core->cia = 0xFFFFFFFF; cpu::this_core::setTracer(nullptr); } void wakeCurrentContext() { // Clean up any dead fibers checkDeadContext(); // Grab the current core and context information auto core = cpu::this_core::state(); auto context = sCurrentContext[core->id]; decaf_check(context); // Restore our context from the OSContext copyContextToCpu(context); core->nia = context->nia; core->cia = context->cia; // Some things to help us when debugging... cpu::this_core::setTracer(context->hostContext->tracer); } static void fiberEntryPoint(void *) { // Load up the context wakeCurrentContext(); // Invoke the PPC thread entry point, note we do not pass any arguments // because whoever created the thread would have already put the arguments // into the guest registers. auto core = cpu::this_core::state(); auto exitPoint = virt_func_cast(static_cast(core->lr)); auto entryPoint = virt_func_cast(static_cast(core->nia)); invoke(core, entryPoint); invoke(core, exitPoint); decaf_check("Control flow returned to fiber entry point"); } static void freeHostContext(HostContext *hostContext) { cpu::freeTracer(hostContext->tracer); platform::destroyFiber(hostContext->fiber); delete hostContext; } // This must be called under the same scheduler lock // that added the thread to tDeadThread, we simply use // the thread_local to pass it between fibers. static void checkDeadContext() { auto coreId = cpu::this_core::id(); auto deadContext = sDeadContext[coreId]; if (deadContext) { sDeadContext[coreId] = nullptr; // Something broken if we are accidentally cleaning // up currently active context... decaf_check(deadContext != sCurrentContext[coreId]); // Something is broken if we have no fiber decaf_check(deadContext->hostContext); // Destroy the fiber freeHostContext(deadContext->hostContext); deadContext->hostContext = nullptr; } } void exitThreadNoLock() { auto coreId = cpu::this_core::id(); // Make sure exitThread is not called multiple times decaf_check(!sDeadContext[coreId]); // Mark this fiber to be cleaned up sDeadContext[coreId] = sCurrentContext[coreId]; } static platform::Fiber * getContextFiber(virt_ptr context) { if (!context->hostContext) { context->hostContext = new HostContext(); context->hostContext->tracer = cpu::allocTracer(1024 * 10 * 10); context->hostContext->fiber = platform::createFiber(fiberEntryPoint, nullptr); context->hostContext->context = context; } return context->hostContext->fiber; } void resetFaultedContextFiber(virt_ptr context, platform::FiberEntryPoint entry, void *param) { auto oldFiber = context->hostContext->fiber; setContextFiberEntry(context, entry, param); platform::swapToFiber(oldFiber, context->hostContext->fiber); } void setContextFiberEntry(virt_ptr context, platform::FiberEntryPoint entry, void *param) { if (!context->hostContext) { context->hostContext = new HostContext(); context->hostContext->tracer = cpu::allocTracer(1024 * 10 * 10); context->hostContext->context = context; } context->hostContext->fiber = platform::createFiber(entry, param); } virt_ptr getCurrentContext() { return sCurrentContext[cpu::this_core::id()]; } void switchContext(virt_ptr next) { // Don't do anything if we are switching to the same context. auto coreId = cpu::this_core::id(); auto current = sCurrentContext[coreId]; if (current == next) { return; } if (!next) { next = sIdleContext[coreId]; } // Perform savage operations before the switch sleepCurrentContext(); // Switch to the new fiber, note that coreId is no longer valid // after this point, as this context may have been switched to // a new core. sCurrentContext[coreId] = next; platform::swapToFiber(getContextFiber(current), getContextFiber(next)); // Perform restoral operations after the switch wakeCurrentContext(); } /** * This should only be run from coreinit entry point, it will hijack the idle * context for core 1 and adopt it into coreinit's default thread 1. */ void hijackCurrentHostContext(virt_ptr context) { auto coreId = cpu::this_core::id(); auto current = sCurrentContext[coreId]; decaf_check(current == sIdleContext[1]); context->hostContext = current->hostContext; sCurrentContext[coreId] = context; // Reset the current core to an idle context current->hostContext = nullptr; setContextFiberEntry( current, [](void *core) { wakeCurrentContext(); internal::idleCoreLoop(reinterpret_cast(core)); }, cpu::this_core::state()); } namespace internal { void initialiseCoreContext(cpu::Core *core) { // Allocate the root context auto context = virt_addrof(sContextData->coreThreadContext[core->id]); std::memset(context.get(), 0, sizeof(Context)); auto stack = virt_addrof(sContextData->coreThreadStackBuffer[core->id * CoreThreadStackSize]); context->gpr[1] = virt_cast(stack).getAddress() + CoreThreadStackSize - 8; context->attr |= 1 << core->id; // Setup host context for the root fiber context->hostContext = new HostContext(); context->hostContext->tracer = cpu::allocTracer(1024 * 10 * 10); context->hostContext->fiber = platform::getThreadFiber(); context->hostContext->context = context; // Save some needed information about the fiber run states. sIdleContext[core->id] = context; sCurrentContext[core->id] = context; sDeadContext[core->id] = nullptr; // Copy our core context to cpu core copyContextToCpu(context); // Set the core nia/cia to something debuggable core->nia = 0xFFFFFFF0u | core->id; core->cia = 0xFFFFFFF0u | core->id; } void initialiseStaticContextData() { sContextData = allocStaticData(); } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_context.h ================================================ #pragma once #include #include #include namespace cafe::kernel { #ifndef DECAF_KERNEL_LLE struct HostContext; #endif struct Context { static constexpr uint64_t Tag = 0x4F53436F6E747874ull; //! Should always be set to the value OSContext::Tag. be2_val tag; be2_array gpr; be2_val cr; be2_val lr; be2_val ctr; be2_val xer; be2_val srr0; be2_val srr1; //These are only set during an exception be2_val dsisr; be2_val dar; be2_val exceptionType; UNKNOWN(0x8); be2_val fpscr; be2_array fpr; be2_val spinLockCount; be2_val state; be2_array gqr; be2_val pir; be2_array psf; be2_array coretime; be2_val starttime; be2_val error; be2_val attr; #ifdef DECAF_KERNEL_LLE be2_val pmc1; be2_val pmc2; be2_val pmc3; be2_val pmc4; #else HostContext *hostContext; be2_val nia; be2_val cia; #endif be2_val mmcr0; be2_val mmcr1; }; CHECK_OFFSET(Context, 0x00, tag); CHECK_OFFSET(Context, 0x08, gpr); CHECK_OFFSET(Context, 0x88, cr); CHECK_OFFSET(Context, 0x8c, lr); CHECK_OFFSET(Context, 0x90, ctr); CHECK_OFFSET(Context, 0x94, xer); CHECK_OFFSET(Context, 0x98, srr0); CHECK_OFFSET(Context, 0x9c, srr1); CHECK_OFFSET(Context, 0xA0, dsisr); CHECK_OFFSET(Context, 0xA4, dar); CHECK_OFFSET(Context, 0xA8, exceptionType); CHECK_OFFSET(Context, 0xb4, fpscr); CHECK_OFFSET(Context, 0xb8, fpr); CHECK_OFFSET(Context, 0x1b8, spinLockCount); CHECK_OFFSET(Context, 0x1ba, state); CHECK_OFFSET(Context, 0x1bc, gqr); CHECK_OFFSET(Context, 0x1DC, pir); CHECK_OFFSET(Context, 0x1e0, psf); CHECK_OFFSET(Context, 0x2e0, coretime); CHECK_OFFSET(Context, 0x2f8, starttime); CHECK_OFFSET(Context, 0x300, error); #ifdef DECAF_KERNEL_LLE CHECK_OFFSET(Context, 0x308, pmc1); CHECK_OFFSET(Context, 0x30c, pmc2); CHECK_OFFSET(Context, 0x310, pmc3); CHECK_OFFSET(Context, 0x314, pmc4); #endif CHECK_OFFSET(Context, 0x318, mmcr0); CHECK_OFFSET(Context, 0x31c, mmcr1); CHECK_SIZE(Context, 0x320); void copyContextToCpu(virt_ptr context); void copyContextFromCpu(virt_ptr context); void exitThreadNoLock(); void resetFaultedContextFiber(virt_ptr context, platform::FiberEntryPoint entry, void *param); void setContextFiberEntry(virt_ptr context, platform::FiberEntryPoint entry, void *param); virt_ptr getCurrentContext(); void switchContext(virt_ptr next); void hijackCurrentHostContext(virt_ptr context); void sleepCurrentContext(); void wakeCurrentContext(); namespace internal { void initialiseCoreContext(cpu::Core *core); void initialiseStaticContextData(); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_exception.cpp ================================================ #include "cafe_kernel_context.h" #include "cafe_kernel_exception.h" #include "cafe_kernel_interrupts.h" #include "cafe_kernel_heap.h" #include "cafe_kernel_ipckdriver.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "decaf_config.h" #include "debug_api/debug_api_controller.h" #include "cafe/libraries/coreinit/coreinit_alarm.h" #include "cafe/libraries/coreinit/coreinit_interrupts.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/gx2/gx2_event.h" #include #include #include #include #include #include #include #include #include #include namespace cafe::kernel { constexpr auto ExceptionThreadStackSize = 0x100u; struct StaticExceptionData { be2_array exceptionThreadContext; be2_array exceptionStackBuffer; }; static virt_ptr sExceptionData; static std::array sUserExceptionHandlers; static std::array sKernelExceptionHandlers; static std::array sExceptionStackTraces; bool setUserModeExceptionHandler(ExceptionType type, ExceptionHandlerFn handler) { if (sUserExceptionHandlers[type]) { return false; } sUserExceptionHandlers[type] = handler; return true; } namespace internal { static void defaultExceptionHandler(ExceptionType type, virt_ptr interruptedContext); inline void dispatchException(ExceptionType type, virt_ptr interruptedContext) { if (sKernelExceptionHandlers[type]) { sKernelExceptionHandlers[type](type, interruptedContext); } else if (sUserExceptionHandlers[type]) { sUserExceptionHandlers[type](type, interruptedContext); } else { defaultExceptionHandler(type, interruptedContext); } } static void exceptionContextFiberEntry(void *) { auto coreId = cpu::this_core::id(); wakeCurrentContext(); while (true) { auto context = getCurrentContext(); auto interruptedContext = virt_cast(virt_addr { context->gpr[3].value() }); auto flags = context->gpr[4]; if (flags & cpu::ALARM_INTERRUPT) { dispatchException(ExceptionType::Decrementer, interruptedContext); } if (flags & cpu::GPU7_INTERRUPT) { dispatchExternalInterrupt(InterruptType::Gpu7, interruptedContext); } if (flags & cpu::IPC_INTERRUPT) { dispatchExternalInterrupt(static_cast(InterruptType::IpcPpc0 + coreId), interruptedContext); } // Return to interrupted context switchContext(interruptedContext); } } static void handleCpuInterrupt(cpu::Core *core, uint32_t flags) { auto interruptedContext = getCurrentContext(); if (flags & cpu::SRESET_INTERRUPT) { dispatchException(ExceptionType::SystemReset, interruptedContext); } if (flags & cpu::DBGBREAK_INTERRUPT) { dispatchException(ExceptionType::Breakpoint, interruptedContext); } if (flags & cpu::PROGRAM_INTERRUPT) { dispatchException(ExceptionType::Program, interruptedContext); } // Disable interrupts auto originalInterruptMask = cpu::this_core::setInterruptMask(cpu::SRESET_INTERRUPT | cpu::DBGBREAK_INTERRUPT); // Switch to the exception context fiber auto exceptionContext = virt_addrof(sExceptionData->exceptionThreadContext[core->id]); exceptionContext->gpr[3] = static_cast(virt_cast(interruptedContext)); exceptionContext->gpr[4] = flags; switchContext(exceptionContext); // Restore interrupts cpu::this_core::setInterruptMask(originalInterruptMask); // Always dispatch an ICI so userland coreinit can reschedule dispatchException(ExceptionType::ICI, interruptedContext); } struct UnhandledExceptionData { ExceptionType type; virt_ptr context; int coreId; platform::StackTrace *stackTrace = nullptr; }; static void unhandledExceptionFiberEntryPoint(void *param) { auto exceptionData = reinterpret_cast(param); auto context = exceptionData->context; // We may have been in the middle of a kernel function... if (coreinit::internal::isSchedulerLocked()) { coreinit::internal::unlockScheduler(); } // Log the core state fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Unhandled exception {}\n", exceptionData->type); fmt::format_to(std::back_inserter(out), "Warning: Register values may not be reliable when using JIT.\n"); switch (exceptionData->type) { case ExceptionType::DSI: fmt::format_to(std::back_inserter(out), "Core{} Instruction at 0x{:08X} (value from SRR0) attempted to access invalid address 0x{:08X} (value from DAR)\n", exceptionData->coreId, context->srr0, context->dar); break; case ExceptionType::ISI: fmt::format_to(std::back_inserter(out), "Core{} Attempted to fetch instruction from invalid address 0x{:08X} (value from SRR0)\n", exceptionData->coreId, context->srr0); break; case ExceptionType::Alignment: fmt::format_to(std::back_inserter(out), "Core{} Instruction at 0x{:08X} (value from SRR0) attempted to access unaligned address 0x{:08X} (value from DAR)\n", exceptionData->coreId, context->srr0, context->dar); break; case ExceptionType::Program: fmt::format_to(std::back_inserter(out), "Core{} Program exception: Possible illegal instruction/operation at or around 0x{:08X} (value from SRR0)\n", exceptionData->coreId, context->srr0); break; default: break; } fmt::format_to(std::back_inserter(out), "nia: 0x{:08x}\n", context->nia); fmt::format_to(std::back_inserter(out), "lr: 0x{:08x}\n", context->lr); fmt::format_to(std::back_inserter(out), "cr: 0x{:08x}\n", context->cr); fmt::format_to(std::back_inserter(out), "ctr: 0x{:08x}\n", context->ctr); fmt::format_to(std::back_inserter(out), "xer: 0x{:08x}\n", context->xer); for (auto i = 0u; i < 32; ++i) { fmt::format_to(std::back_inserter(out), "gpr[{}]: 0x{:08x}\n", i, context->gpr[i]); } gLog->critical(std::string_view { out.data(), out.size() }); // If the decaf debugger is enabled, we will catch this exception there if (decaf::config()->debugger.enabled) { // Move back an instruction so we can re-execute the failed instruction // and so that the debugger shows the right stop point. cpu::this_core::state()->nia -= 4; coreinit::internal::pauseCoreTime(true); decaf::debug::handleDebugBreakInterrupt(); coreinit::internal::pauseCoreTime(false); // This will shut down the thread and reschedule. This is required // since returning from the segfault handler is an error. coreinit::OSExitThread(0); } else { // If there is no debugger then let's crash decaf lul! decaf_host_fault(fmt::format("Unhandled exception {}, srr0: 0x{:08X} nia: 0x{:08X}\n", exceptionData->type, context->srr0, context->nia), exceptionData->stackTrace); } } static void defaultExceptionHandler(ExceptionType type, virt_ptr interruptedContext) { auto exceptionData = new UnhandledExceptionData { }; exceptionData->type = type; exceptionData->coreId = cpu::this_core::id(); exceptionData->context = interruptedContext; exceptionData->stackTrace = sExceptionStackTraces[exceptionData->coreId]; resetFaultedContextFiber(getCurrentContext(), unhandledExceptionFiberEntryPoint, exceptionData); } static void handleCpuSegfault(cpu::Core *core, uint32_t address, platform::StackTrace *stackTrace) { auto interruptedContext = getCurrentContext(); copyContextFromCpu(interruptedContext); sExceptionStackTraces[core->id] = stackTrace; if (address == core->nia) { dispatchException(ExceptionType::ISI, interruptedContext); } else { dispatchException(ExceptionType::DSI, interruptedContext); } } static void handleDebugBreakException(ExceptionType type, virt_ptr interruptedContext) { if (decaf::config()->debugger.enabled) { coreinit::internal::pauseCoreTime(true); decaf::debug::handleDebugBreakInterrupt(); coreinit::internal::pauseCoreTime(false); } } static void handleIciException(ExceptionType type, virt_ptr interruptedContext) { // Call user ICI handler if set, else just ignore if (sUserExceptionHandlers[type]) { sUserExceptionHandlers[type](type, interruptedContext); } } static void handleSystemResetException(ExceptionType type, virt_ptr interruptedContext) { platform::exitThread(0); } void initialiseExceptionContext(cpu::Core *core) { auto context = virt_addrof(sExceptionData->exceptionThreadContext[core->id]); std::memset(context.get(), 0, sizeof(Context)); auto stack = virt_addrof(sExceptionData->exceptionStackBuffer[core->id * ExceptionThreadStackSize]); context->gpr[1] = virt_cast(stack).getAddress() + ExceptionThreadStackSize - 8; context->attr |= 1 << core->id; setContextFiberEntry(context, exceptionContextFiberEntry, nullptr); } void initialiseExceptionHandlers() { setKernelExceptionHandler(ExceptionType::SystemReset, handleSystemResetException); setKernelExceptionHandler(ExceptionType::Breakpoint, handleDebugBreakException); setKernelExceptionHandler(ExceptionType::ICI, handleIciException); // TODO: Move this to kernel timers setKernelExceptionHandler(ExceptionType::Decrementer, [](ExceptionType type, virt_ptr interruptedContext) { coreinit::internal::disableScheduler(); coreinit::internal::handleAlarmInterrupt(interruptedContext); coreinit::internal::enableScheduler(); }); cpu::setInterruptHandler(&handleCpuInterrupt); cpu::setSegfaultHandler(&handleCpuSegfault); } void initialiseStaticExceptionData() { sExceptionData = allocStaticData(); } void setKernelExceptionHandler(ExceptionType type, ExceptionHandlerFn handler) { sKernelExceptionHandlers[type] = handler; } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_exception.h ================================================ #pragma once #include "cafe_kernel_context.h" #include #include namespace cafe::kernel { #include ENUM_BEG(ExceptionType, uint32_t) ENUM_VALUE(SystemReset, 0) ENUM_VALUE(MachineCheck, 1) ENUM_VALUE(DSI, 2) ENUM_VALUE(ISI, 3) ENUM_VALUE(ExternalInterrupt, 4) ENUM_VALUE(Alignment, 5) ENUM_VALUE(Program, 6) ENUM_VALUE(FloatingPoint, 7) ENUM_VALUE(Decrementer, 8) ENUM_VALUE(SystemCall, 9) ENUM_VALUE(Trace, 10) ENUM_VALUE(PerformanceMonitor, 11) ENUM_VALUE(Breakpoint, 12) ENUM_VALUE(SystemInterrupt, 13) ENUM_VALUE(ICI, 14) ENUM_VALUE(Max, 15) ENUM_END(ExceptionType) #include using ExceptionHandlerFn = void(*)(ExceptionType type, virt_ptr interruptedContext); bool setUserModeExceptionHandler(ExceptionType type, ExceptionHandlerFn handler); namespace internal { void initialiseExceptionContext(cpu::Core *core); void initialiseExceptionHandlers(); void initialiseStaticExceptionData(); void setKernelExceptionHandler(ExceptionType type, ExceptionHandlerFn handler); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_heap.cpp ================================================ #include "cafe_kernel_heap.h" #include "cafe_kernel_mmu.h" #include "cafe/cafe_tinyheap.h" #include #include namespace cafe::kernel::internal { static FrameAllocator sStaticDataHeap; static virt_ptr sWorkAreaHeap; static constexpr auto WorkAreaHeapTrackingBlockCount = 0x80; void initialiseStaticDataHeap() { auto staticDataMapping = getVirtualMemoryMap(VirtualMemoryRegion::Kernel_0xFFE00000); sStaticDataHeap = FrameAllocator { virt_cast(staticDataMapping.vaddr).get(), staticDataMapping.size, }; } bool initialiseWorkAreaHeap() { auto trackingSize = WorkAreaHeapTrackingBlockCount * TinyHeapBlockSize + TinyHeapHeaderSize; auto workAreaMapping = getVirtualMemoryMap(VirtualMemoryRegion::KernelWorkAreaHeap); sWorkAreaHeap = virt_cast(workAreaMapping.vaddr); auto error = TinyHeap_Setup(sWorkAreaHeap, trackingSize, virt_cast(workAreaMapping.vaddr + trackingSize), workAreaMapping.size - trackingSize); return error == TinyHeapError::OK; } virt_ptr allocStaticData(size_t size, size_t alignment) { return virt_cast(cpu::translate(sStaticDataHeap.allocate(size, alignment))); } virt_ptr allocFromWorkArea(int32_t size, int32_t alignment) { virt_ptr ptr; if (TinyHeap_Alloc(sWorkAreaHeap, size, alignment, &ptr) != TinyHeapError::OK) { return nullptr; } return ptr; } void freeToWorkArea(virt_ptr ptr) { TinyHeap_Free(sWorkAreaHeap, ptr); } } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_heap.h ================================================ #pragma once #include namespace cafe::kernel::internal { void initialiseStaticDataHeap(); bool initialiseWorkAreaHeap(); virt_ptr allocStaticData(size_t size, size_t alignment = 4u); virt_ptr allocFromWorkArea(int32_t size, int32_t alignment = 4); void freeToWorkArea(virt_ptr ptr); template inline virt_ptr allocStaticData() { return virt_cast(allocStaticData(sizeof(Type), alignof(Type))); } } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_info.cpp ================================================ #include "cafe_kernel_info.h" #include "cafe_kernel_process.h" #include "cafe/loader/cafe_loader_init.h" #include "cafe/loader/cafe_loader_loaded_rpl.h" namespace cafe::kernel { void getType0Info(virt_ptr info, uint32_t size) { info->upid = internal::getCurrentUniqueProcessId(); info->rampid = internal::getCurrentRamPartitionId(); info->appFlags = ProcessFlags::get(0) .debugLevel(DebugLevel::Verbose) .disableSharedLibraries(false) .isFirstProcess(true); auto startInfo = internal::getCurrentRamPartitionStartInfo(); info->dataAreaStart = startInfo->dataAreaStart; info->dataAreaEnd = startInfo->dataAreaEnd; info->sdaBase = startInfo->sdaBase; info->sda2Base = startInfo->sda2Base; info->systemHeapSize = startInfo->systemHeapSize; auto partitionData = internal::getCurrentRamPartitionData(); auto &core0 = partitionData->perCoreStartInfo[0]; auto &core1 = partitionData->perCoreStartInfo[1]; auto &core2 = partitionData->perCoreStartInfo[2]; info->stackBase0 = core0.stackBase; info->stackBase1 = core1.stackBase; info->stackBase2 = core2.stackBase; info->stackEnd0 = core0.stackEnd; info->stackEnd1 = core1.stackEnd; info->stackEnd2 = core2.stackEnd; info->exceptionStackBase0 = core0.exceptionStackBase; info->exceptionStackBase1 = core1.exceptionStackBase; info->exceptionStackBase2 = core2.exceptionStackBase; info->exceptionStackEnd0 = core0.exceptionStackEnd; info->exceptionStackEnd1 = core1.exceptionStackEnd; info->exceptionStackEnd2 = core2.exceptionStackEnd; info->lockedCacheBase0 = virt_addr { 0xFFC00000 }; info->lockedCacheBase1 = virt_addr { 0xFFC40000 }; info->lockedCacheBase2 = virt_addr { 0xFFC80000 }; info->physDataAreaStart = partitionData->ramPartitionAllocation.dataStart; info->physDataAreaEnd = partitionData->ramPartitionAllocation.availStart; info->physAvailStart = partitionData->ramPartitionAllocation.availStart; info->physAvailEnd = partitionData->ramPartitionAllocation.codeGenStart; info->physCodeGenStart = partitionData->ramPartitionAllocation.codeGenStart; info->physCodeGenEnd = partitionData->ramPartitionAllocation.codeStart; info->titleId = partitionData->titleInfo.titleId; if (startInfo->coreinit) { auto coreinit = startInfo->coreinit; info->coreinit.loaderHandle = coreinit->moduleNameBuffer; info->coreinit.textAddr = coreinit->textAddr; info->coreinit.textOffset = coreinit->textOffset; info->coreinit.textSize = coreinit->textSize; info->coreinit.dataAddr = coreinit->dataAddr; info->coreinit.dataOffset = coreinit->dataOffset; info->coreinit.dataSize = coreinit->dataSize; info->coreinit.loadAddr = coreinit->loadAddr; info->coreinit.loadOffset = coreinit->loadOffset; info->coreinit.loadSize = coreinit->loadSize; } } void getType6Info(virt_ptr info, uint32_t size) { std::memset(info.get(), 0, sizeof(Info6)); // TODO: This comes from ios/mcp GetLaunchParameters info->osTitleId = 0x000500101000400Aull; info->unk0x08 = 0u; } void getArgStr(virt_ptr buffer, uint32_t size) { auto partitionData = internal::getCurrentRamPartitionData(); auto length = partitionData->argstr.size(); if (length >= size) { length = size - 1; } std::memcpy(buffer.get(), partitionData->argstr.data(), length); buffer[length] = char { 0 }; } void getInfo(InfoType type, virt_ptr buffer, uint32_t size) { switch (type) { case InfoType::Type0: getType0Info(virt_cast(buffer), size); break; case InfoType::Type6: getType6Info(virt_cast(buffer), size); break; case InfoType::ArgStr: getArgStr(virt_cast(buffer), size); break; default: decaf_abort(fmt::format("Unexpected kernel info type {}", static_cast(type))); } } SystemMode getSystemMode() { return SystemMode::Production; } } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_info.h ================================================ #pragma once #include "cafe_kernel_process.h" #include namespace cafe::kernel { enum class InfoType { Type0 = 0, Type6 = 6, ArgStr = 3, }; enum class SystemMode : int32_t { Production = 1, Debug = 2, }; struct Info0 { struct CoreinitInfo { be2_virt_ptr loaderHandle; be2_val textAddr; be2_val textOffset; be2_val textSize; be2_val dataAddr; be2_val dataOffset; be2_val dataSize; be2_val loadAddr; be2_val loadOffset; be2_val loadSize; }; be2_val upid; be2_val rampid; be2_val appFlags; be2_val dataAreaStart; be2_val dataAreaEnd; be2_val physDataAreaStart; be2_val physDataAreaEnd; be2_val physAvailStart; be2_val physAvailEnd; be2_val physCodeGenStart; be2_val physCodeGenEnd; be2_val sdaBase; be2_val sda2Base; be2_val systemHeapSize; be2_val stackEnd0; be2_val stackEnd1; be2_val stackEnd2; be2_val stackBase0; be2_val stackBase1; be2_val stackBase2; be2_val exceptionStackEnd0; be2_val exceptionStackEnd1; be2_val exceptionStackEnd2; be2_val exceptionStackBase0; be2_val exceptionStackBase1; be2_val exceptionStackBase2; be2_val lockedCacheBase0; be2_val lockedCacheBase1; be2_val lockedCacheBase2; be2_struct coreinit; be2_val unk0x9C; be2_val titleId; }; CHECK_OFFSET(Info0, 0x00, upid); CHECK_OFFSET(Info0, 0x04, rampid); CHECK_OFFSET(Info0, 0x08, appFlags); CHECK_OFFSET(Info0, 0x0C, dataAreaStart); CHECK_OFFSET(Info0, 0x10, dataAreaEnd); CHECK_OFFSET(Info0, 0x14, physDataAreaStart); CHECK_OFFSET(Info0, 0x18, physDataAreaEnd); CHECK_OFFSET(Info0, 0x1C, physAvailStart); CHECK_OFFSET(Info0, 0x20, physAvailEnd); CHECK_OFFSET(Info0, 0x24, physCodeGenStart); CHECK_OFFSET(Info0, 0x28, physCodeGenEnd); CHECK_OFFSET(Info0, 0x2C, sdaBase); CHECK_OFFSET(Info0, 0x30, sda2Base); CHECK_OFFSET(Info0, 0x34, systemHeapSize); CHECK_OFFSET(Info0, 0x38, stackEnd0); CHECK_OFFSET(Info0, 0x3C, stackEnd1); CHECK_OFFSET(Info0, 0x40, stackEnd2); CHECK_OFFSET(Info0, 0x44, stackBase0); CHECK_OFFSET(Info0, 0x48, stackBase1); CHECK_OFFSET(Info0, 0x4C, stackBase2); CHECK_OFFSET(Info0, 0x50, exceptionStackEnd0); CHECK_OFFSET(Info0, 0x54, exceptionStackEnd1); CHECK_OFFSET(Info0, 0x58, exceptionStackEnd2); CHECK_OFFSET(Info0, 0x5C, exceptionStackBase0); CHECK_OFFSET(Info0, 0x60, exceptionStackBase1); CHECK_OFFSET(Info0, 0x64, exceptionStackBase2); CHECK_OFFSET(Info0, 0x68, lockedCacheBase0); CHECK_OFFSET(Info0, 0x6C, lockedCacheBase1); CHECK_OFFSET(Info0, 0x70, lockedCacheBase2); CHECK_OFFSET(Info0, 0x74, coreinit); CHECK_OFFSET(Info0, 0x9C, unk0x9C); CHECK_OFFSET(Info0, 0xA0, titleId); CHECK_SIZE(Info0, 0xA8); struct Info6 { be2_val osTitleId; be2_val unk0x08; PADDING(0x108 - 0xC); }; CHECK_OFFSET(Info6, 0x00, osTitleId); CHECK_OFFSET(Info6, 0x08, unk0x08); CHECK_SIZE(Info6, 0x108); void getType0Info(virt_ptr info, uint32_t size); void getType6Info(virt_ptr info, uint32_t size); void getArgStr(virt_ptr buffer, uint32_t size); void getInfo(InfoType type, virt_ptr buffer, uint32_t size); SystemMode getSystemMode(); } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_interrupts.cpp ================================================ #include "cafe_kernel_context.h" #include "cafe_kernel_interrupts.h" #include "cafe_kernel_process.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" namespace cafe::kernel { struct InterruptData { std::array kernelHandlers; std::array userHandlers; std::array, InterruptType::Max> userHandlerData; }; static std::array sPerCoreInterruptData; UserInterruptHandlerFn setUserModeInterruptHandler(InterruptType type, UserInterruptHandlerFn handler, virt_ptr userData) { auto &data = sPerCoreInterruptData[cpu::this_core::id()]; if (type >= InterruptType::Max) { // Invalid interrupt type return nullptr; } auto previous = data.userHandlers[type]; data.userHandlers[type] = handler; data.userHandlerData[type] = userData; return previous; } void clearAndEnableInterrupt(InterruptType type) { } void disableInterrupt(InterruptType type) { } namespace internal { void dispatchExternalInterrupt(InterruptType type, virt_ptr interruptedContext) { auto &data = sPerCoreInterruptData[cpu::this_core::id()]; if (auto kernelHandler = data.kernelHandlers[type]) { kernelHandler(type, interruptedContext); } else if (auto userHandler = data.userHandlers[type]) { userHandler(type, interruptedContext, data.userHandlerData[type]); } } void setKernelInterruptHandler(InterruptType type, KernelInterruptHandlerFn handler) { sPerCoreInterruptData[cpu::this_core::id()].kernelHandlers[type] = handler; } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_interrupts.h ================================================ #pragma once #include "cafe_kernel_context.h" #include "cafe_kernel_processid.h" namespace cafe::kernel { #include ENUM_BEG(InterruptType, uint32_t) ENUM_VALUE(Error, 0) ENUM_VALUE(Dsp, 1) ENUM_VALUE(Gpu7, 2) ENUM_VALUE(GpiPpc, 3) ENUM_VALUE(PrimaryI2C, 4) ENUM_VALUE(DspAi, 5) ENUM_VALUE(DspAi2, 6) ENUM_VALUE(DspAcc, 7) ENUM_VALUE(DspDsp, 8) ENUM_VALUE(IpcPpc0, 9) ENUM_VALUE(IpcPpc1, 10) ENUM_VALUE(IpcPpc2, 11) ENUM_VALUE(Ahb, 12) ENUM_VALUE(Max, 13) ENUM_END(InterruptType) #include using UserInterruptHandlerFn = void(*)(InterruptType type, virt_ptr interruptedContext, virt_ptr userData); UserInterruptHandlerFn setUserModeInterruptHandler(InterruptType type, UserInterruptHandlerFn callback, virt_ptr userData); void clearAndEnableInterrupt(InterruptType type); void disableInterrupt(InterruptType type); namespace internal { using KernelInterruptHandlerFn = void(*)(InterruptType type, virt_ptr interruptedContext); void dispatchExternalInterrupt(InterruptType type, virt_ptr interruptContext); void setKernelInterruptHandler(InterruptType type, KernelInterruptHandlerFn handler); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_ipc.cpp ================================================ #include "cafe_kernel.h" #include "cafe_kernel_ipc.h" #include "cafe_kernel_ipckdriver.h" #include "cafe_kernel_heap.h" #include "cafe_kernel_lock.h" #include "cafe_kernel_mmu.h" #include "cafe/cafe_stackobject.h" #include "cafe/cafe_tinyheap.h" #include "ios/ios_ipc.h" #include #include #include namespace cafe::kernel::internal { constexpr auto IpcBufferSize = 0x4000u; constexpr auto IpcBufferAlign = 0x40u; struct StaticIpcData { be2_virt_ptr ipcHeap; be2_array ipcHeapBuffer; be2_val mcpHandle; be2_val ppcAppHandle; be2_val cblHandle; }; struct SynchronousCallback { std::atomic replyReceived = false; ios::Error error = ios::Error::InvalidArg; virt_ptr buffer = nullptr; }; static virt_ptr sIpcData; static std::mutex sIpcHeapMutex; static void ipcInitialiseHeap() { sIpcData->ipcHeap = virt_cast(virt_addrof(sIpcData->ipcHeapBuffer)); TinyHeap_Setup(sIpcData->ipcHeap, 0x430, virt_addrof(sIpcData->ipcHeapBuffer) + 0x430, sIpcData->ipcHeapBuffer.size() - 0x430); } virt_ptr ipcAllocBuffer(uint32_t size, int32_t *outError) { auto lock = std::unique_lock { sIpcHeapMutex }; auto allocPtr = virt_ptr { nullptr }; auto error = TinyHeap_Alloc(sIpcData->ipcHeap, align_up(size, IpcBufferAlign), IpcBufferAlign, &allocPtr); if (outError) { *outError = static_cast(error); } return allocPtr; } void ipcFreeBuffer(virt_ptr buffer) { auto lock = std::unique_lock { sIpcHeapMutex }; TinyHeap_Free(sIpcData->ipcHeap, buffer); } static void synchronousCallback(ios::Error error, virt_ptr context) { auto synchronousReply = virt_cast(context); synchronousReply->error = error; synchronousReply->replyReceived = true; if (synchronousReply->buffer) { ipcFreeBuffer(synchronousReply->buffer); } } static ios::Error waitSynchronousReply(virt_ptr synchronousReply, std::chrono::microseconds timeout, uint32_t unk) { auto waitUntil = std::chrono::steady_clock::now() + timeout; auto error = ios::Error::Timeout; while (!synchronousReply->replyReceived) { cpu::this_core::waitNextInterrupt(waitUntil); if (std::chrono::steady_clock::now() >= waitUntil) { break; } } if (synchronousReply->replyReceived) { error = synchronousReply->error; synchronousReply->replyReceived = false; synchronousReply->buffer = nullptr; } return error; } ios::Error IOS_OpenAsync(RamPartitionId clientProcessId, virt_ptr device, ios::OpenMode mode, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData) { virt_ptr requestBlock; auto driver = ipckDriverGetInstance(); auto error = ipckDriverAllocateRequestBlock(clientProcessId, RamPartitionId::Invalid, driver, &requestBlock, 0, ios::Command::Open, asyncCallback, asyncCallbackData); if (error < ios::Error::OK) { return error; } requestBlock->request->request.args.open.name = effectiveToPhysical(device); requestBlock->request->request.args.open.nameLen = static_cast(strlen(device.get())); requestBlock->request->request.args.open.mode = mode; requestBlock->request->request.args.open.caps = 0ull; error = ipckDriverSubmitRequest(driver, requestBlock); if (error < ios::Error::OK) { ipckDriverFreeRequestBlock(driver, requestBlock); } return error; } ios::Error IOS_Open(RamPartitionId clientProcessId, virt_ptr device, ios::OpenMode mode) { auto synchronousReply = StackObject { }; auto error = IOS_OpenAsync(clientProcessId, device, mode, &synchronousCallback, synchronousReply); if (error < ios::Error::OK) { return error; } return waitSynchronousReply(synchronousReply, std::chrono::milliseconds { 35 }, 6); } ios::Error IOS_CloseAsync(RamPartitionId clientProcessId, ios::Handle handle, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData, uint32_t unkArg0) { auto requestBlock = virt_ptr { }; auto driver = ipckDriverGetInstance(); auto error = ipckDriverAllocateRequestBlock(clientProcessId, RamPartitionId::Invalid, driver, &requestBlock, handle, ios::Command::Open, asyncCallback, asyncCallbackData); if (error < ios::Error::OK) { return error; } requestBlock->request->request.args.close.unkArg0 = unkArg0; error = ipckDriverSubmitRequest(driver, requestBlock); if (error < ios::Error::OK) { ipckDriverFreeRequestBlock(driver, requestBlock); } return error; } ios::Error IOS_Close(RamPartitionId clientProcessId, ios::Handle handle, uint32_t unkArg0) { auto synchronousReply = StackObject { }; auto error = IOS_CloseAsync(clientProcessId, handle, &synchronousCallback, synchronousReply, unkArg0); if (error < ios::Error::OK) { return error; } return waitSynchronousReply(synchronousReply, std::chrono::milliseconds { 35 }, 6); } ios::Error IOS_IoctlAsync(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, ios::Handle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData) { auto requestBlock = virt_ptr { }; auto driver = ipckDriverGetInstance(); auto error = ipckDriverAllocateRequestBlock(clientProcessId, loaderProcessId, driver, &requestBlock, handle, ios::Command::Ioctl, asyncCallback, asyncCallbackData); if (error < ios::Error::OK) { return error; } auto &ioctl = requestBlock->request->request.args.ioctl; ioctl.request = request; ioctl.inputLength = inLen; ioctl.outputLength = outLen; if (inBuf) { ioctl.inputBuffer = effectiveToPhysical(inBuf); } else { ioctl.inputBuffer = nullptr; } if (outBuf) { ioctl.outputBuffer = effectiveToPhysical(outBuf); } else { ioctl.outputBuffer = nullptr; } error = ipckDriverSubmitRequest(driver, requestBlock); if (error < ios::Error::OK) { ipckDriverFreeRequestBlock(driver, requestBlock); } return error; } ios::Error IOS_Ioctl(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, ios::Handle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen) { auto synchronousReply = StackObject { }; auto error = IOS_IoctlAsync(clientProcessId, loaderProcessId, handle, request, inBuf, inLen, outBuf, outLen, &synchronousCallback, synchronousReply); if (error < ios::Error::OK) { return error; } return waitSynchronousReply(synchronousReply, std::chrono::seconds { 35 }, 6); } void initialiseIpc() { ipcInitialiseHeap(); auto nameBuffer = virt_cast(ipcAllocBuffer(0x20)); string_copy(nameBuffer.get(), "/dev/mcp", 0x20); sIpcData->mcpHandle = IOS_Open(RamPartitionId::Kernel, nameBuffer, ios::OpenMode::None); string_copy(nameBuffer.get(), "/dev/ppc_app", 0x20); sIpcData->ppcAppHandle = IOS_Open(RamPartitionId::Kernel, nameBuffer, ios::OpenMode::None); string_copy(nameBuffer.get(), "/dev/mcp", 0x20); sIpcData->cblHandle = IOS_Open(RamPartitionId::Kernel, nameBuffer, ios::OpenMode::None); ipcFreeBuffer(nameBuffer); } void initialiseStaticIpcData() { sIpcData = allocStaticData(); } } // namespace cafe::kernel::internal namespace cafe::kernel { ios::Handle getMcpHandle() { return internal::sIpcData->mcpHandle; } ios::Handle getPpcAppHandle() { return internal::sIpcData->ppcAppHandle; } ios::Handle getCblHandle() { return internal::sIpcData->cblHandle; } } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_ipc.h ================================================ #pragma once #include "cafe_kernel_ipckdriver.h" #include "cafe_kernel_processid.h" #include "ios/ios_ipc.h" namespace cafe::kernel { ios::Handle getMcpHandle(); ios::Handle getPpcAppHandle(); ios::Handle getCblHandle(); namespace internal { ios::Error IOS_OpenAsync(RamPartitionId clientProcessId, virt_ptr device, ios::OpenMode mode, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData); ios::Error IOS_Open(RamPartitionId clientProcessId, virt_ptr device, ios::OpenMode mode); ios::Error IOS_CloseAsync(RamPartitionId clientProcessId, ios::Handle handle, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData, uint32_t unkArg0); ios::Error IOS_Close(RamPartitionId clientProcessId, ios::Handle handle, uint32_t unkArg0); ios::Error IOS_IoctlAsync(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, ios::Handle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData); ios::Error IOS_Ioctl(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, ios::Handle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen); virt_ptr ipcAllocBuffer(uint32_t size, int32_t *outError = nullptr); void ipcFreeBuffer(virt_ptr buffer); void initialiseIpc(); void initialiseStaticIpcData(); } // namespace internal } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_ipckdriver.cpp ================================================ #include "cafe_kernel_ipckdriver.h" #include "cafe_kernel_interrupts.h" #include "cafe_kernel_heap.h" #include "cafe_kernel_process.h" #include "cafe/libraries/coreinit/coreinit_ipcdriver.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "ios/kernel/ios_kernel_ipc_thread.h" #include #include #include #include namespace cafe::kernel { struct StaticIpckDriverData { be2_array drivers; be2_array requestBuffer; }; static virt_ptr sIpckDriverData = nullptr; static std::mutex sIpcMutex; static std::vector> sPendingResponses[3]; namespace internal { ios::Error submitUserRequest(virt_ptr driver, virt_ptr request); ios::Error submitLoaderRequest(virt_ptr driver, virt_ptr request); } // namespace internal /** * Open the current core's IPCKDriver for the current userland process. */ ios::Error ipckDriverUserOpen(uint32_t numReplies, virt_ptr replyQueue, IPCKUserInterruptHandlerFn handler) { auto driver = internal::ipckDriverGetInstance(); if (!driver) { return ios::Error::FailInternal; } auto pidx = static_cast(internal::getCurrentRamPartitionId()); if (driver->perProcessReplyQueue[pidx]) { // Already open! return ios::Error::Busy; } if (!replyQueue || numReplies != IPCKDriverReplyQueue::Size) { return ios::Error::Invalid; } driver->perProcessNumUserRequests[pidx] = 0u; driver->perProcessReplyQueue[pidx] = replyQueue; driver->perProcessCallbacks[pidx] = handler; std::memset(replyQueue.get(), 0, sizeof(IPCKDriverReplyQueue)); return ios::Error::OK; } /** * Close the current core's IPCKDriver for the current userland process. */ ios::Error ipckDriverUserClose() { auto driver = internal::ipckDriverGetInstance(); if (!driver) { return ios::Error::OK; } // TODO: Cleanup any pending requests for the current process return ios::Error::OK; } /** * Submit a user IPC request. */ ios::Error ipckDriverUserSubmitRequest(virt_ptr request) { auto driver = internal::ipckDriverGetInstance(); if (!driver) { return ios::Error::OK; } return internal::submitUserRequest(driver, request); } /** * Open the current core's IPCKDriver for the loader process. */ ios::Error ipckDriverLoaderOpen() { auto driver = internal::ipckDriverGetInstance(); if (!driver) { return ios::Error::FailInternal; } /* TODO: Enable this check when we have proper multi process auto kernelProcessId = getKernelProcessId(); if (kernelProcessId != KernelProcessId::Loader) { return ios::Error::Invalid; } */ auto pidx = static_cast(internal::getCurrentRamPartitionId()); driver->perProcessNumLoaderRequests[pidx] = 0u; IPCKDriver_FIFOInit(virt_addrof(driver->perProcessLoaderReply[pidx])); return ios::Error::OK; } /** * Close the current core's IPCKDriver for the loader process. */ ios::Error ipckDriverLoaderClose() { auto driver = internal::ipckDriverGetInstance(); if (!driver) { return ios::Error::OK; } // TODO: Cleanup any pending loader requests return ios::Error::OK; } /** * Submit a loader IPC request. */ ios::Error ipckDriverLoaderSubmitRequest(virt_ptr request) { auto driver = internal::ipckDriverGetInstance(); if (!driver) { return ios::Error::FailInternal; } return internal::submitLoaderRequest(driver, request); } /** * Poll for completion of any loader IPC request. */ virt_ptr ipckDriverLoaderPollCompletion() { auto driver = internal::ipckDriverGetInstance(); auto block = virt_ptr { nullptr }; auto pidx = static_cast(internal::getCurrentRamPartitionId()); auto error = IPCKDriver_FIFOPop(virt_addrof(driver->perProcessLoaderReply[pidx]), &block); if (error < ios::Error::OK) { return nullptr; } // Copy reply to our user request structure std::memcpy(block->userRequest.get(), block->request.get(), 0x48u); driver->perProcessNumLoaderRequests[pidx]--; auto request = block->userRequest; ipckDriverFreeRequestBlock(driver, block); return request; } /** * Submit an IPC reply from IOS. */ void ipckDriverIosSubmitReply(phys_ptr reply) { auto coreId = reply->cpuId - ios::CpuId::PPC0; sIpcMutex.lock(); sPendingResponses[coreId].push_back(reply); sIpcMutex.unlock(); cpu::interrupt(coreId, cpu::IPC_INTERRUPT); } namespace internal { virt_ptr ipckDriverGetInstance() { return virt_addrof(sIpckDriverData->drivers[cpu::this_core::id()]); } ios::Error ipckDriverAllocateRequestBlock(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, virt_ptr driver, virt_ptr *outRequestBlock, ios::Handle handle, ios::Command command, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackData) { auto error = IPCKDriver_FIFOPop(virt_addrof(driver->freeFifo), outRequestBlock); if (error < ios::Error::OK) { if (error == ios::Error::QEmpty) { return ios::Error::QFull; } return error; } auto requestBlock = *outRequestBlock; requestBlock->flags = requestBlock->flags.value() .unk_0x0C00(0) .replyState(IPCKDriverRequestBlock::WaitReply) .requestState(IPCKDriverRequestBlock::Allocated) .clientProcessId(clientProcessId) .loaderProcessId(loaderProcessId); requestBlock->asyncCallback->func = asyncCallback; requestBlock->asyncCallbackData = asyncCallbackData; requestBlock->userRequest = nullptr; auto request = requestBlock->request; std::memset(virt_addrof(request->request.args).get(), 0, sizeof(request->request.args)); request->request.clientPid = static_cast(clientProcessId); request->request.handle = handle; request->request.command = command; request->request.flags = 0u; request->request.reply = ios::Error::OK; request->request.cpuId = static_cast(cpu::this_core::id() + 1); if (clientProcessId != RamPartitionId::Kernel) { request->request.titleId = getCurrentTitleId(); } request->prevHandle = handle; request->prevCommand = command; return ios::Error::OK; } void ipckDriverFreeRequestBlock(virt_ptr driver, virt_ptr requestBlock) { requestBlock->flags = requestBlock->flags.value() .requestState(IPCKDriverRequestBlock::Unallocated) .replyState(IPCKDriverRequestBlock::WaitReply) .unk_0x0C00(0); requestBlock->asyncCallback->func = nullptr; requestBlock->asyncCallbackData = nullptr; requestBlock->userRequest = nullptr; IPCKDriver_FIFOPush(virt_addrof(driver->freeFifo), requestBlock); } ios::Error ipckDriverSubmitRequest(virt_ptr driver, virt_ptr requestBlock) { auto error = IPCKDriver_FIFOPush(virt_addrof(driver->outboundFIFO), requestBlock); if (error < ios::Error::OK) { return error; } if (driver->state != IPCKDriverState::Open) { return ios::Error::NotReady; } error = IPCKDriver_FIFOPop(virt_addrof(driver->outboundFIFO), &requestBlock); if (error < ios::Error::OK) { return error; } ios::kernel::submitIpcRequest( effectiveToPhysical(virt_addrof(requestBlock->request->request))); return ios::Error::OK; } static ios::Error allocateUserRequestBlock(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, virt_ptr driver, virt_ptr *outRequestBlock, virt_ptr userRequest) { auto error = ipckDriverAllocateRequestBlock(clientProcessId, loaderProcessId, driver, outRequestBlock, 0, ios::Command::Invalid, nullptr, nullptr); if (error >= ios::Error::OK) { auto requestBlock = *outRequestBlock; requestBlock->userRequest = userRequest; requestBlock->flags = requestBlock->flags.value() .clientProcessId(clientProcessId); } return error; } static ios::Error processLoaderOrUserRequest(virt_ptr driver, virt_ptr requestBlock, bool isLoader) { auto request = requestBlock->request; request->prevHandle = request->request.handle; request->prevCommand = request->request.command; request->request.flags = 0u; request->request.reply = ios::Error::OK; request->request.cpuId = static_cast(cpu::this_core::id() + 1); if (!isLoader) { request->request.clientPid = static_cast(getCurrentRamPartitionId()); request->request.titleId = getCurrentTitleId(); } switch (request->request.command) { case ios::Command::Open: request->request.args.open.name = phys_cast(effectiveToPhysical(request->buffer1)); break; case ios::Command::Read: request->request.args.read.data = effectiveToPhysical(request->buffer1); break; case ios::Command::Write: request->request.args.write.data = effectiveToPhysical(request->buffer1); break; case ios::Command::Ioctl: if (request->buffer1) { request->request.args.ioctl.inputBuffer = phys_cast(effectiveToPhysical(request->buffer1)); } else { request->request.args.ioctl.inputBuffer = nullptr; } if (request->buffer2) { request->request.args.ioctl.outputBuffer = phys_cast(effectiveToPhysical(request->buffer2)); } else { request->request.args.ioctl.outputBuffer = nullptr; } break; case ios::Command::Ioctlv: { auto &ioctlv = request->request.args.ioctlv; if (request->buffer1) { ioctlv.vecs = phys_cast(effectiveToPhysical(request->buffer1)); } else { ioctlv.vecs = nullptr; } for (auto i = 0u; i < ioctlv.numVecIn + ioctlv.numVecOut; ++i) { if (!ioctlv.vecs[i].vaddr) { continue; } ioctlv.vecs[i].paddr = effectiveToPhysical(ioctlv.vecs[i].vaddr); } break; } default: break; } return ios::Error::OK; } static ios::Error submitUserOrLoaderRequest(virt_ptr driver, virt_ptr userRequest, bool isLoader) { auto error = ios::Error::OK; auto rampid = getCurrentRamPartitionId(); auto pidx = static_cast(rampid); if (!driver) { error = ios::Error::Invalid; } else { if (driver->perProcessNumUserRequests[pidx] + driver->perProcessNumLoaderRequests[pidx] >= IPCKRequestsPerProcess) { error = ios::Error::QFull; } else { virt_ptr requestBlock; error = allocateUserRequestBlock(isLoader ? RamPartitionId::Kernel : rampid, isLoader ? rampid : RamPartitionId::Invalid, driver, &requestBlock, userRequest); if (error >= ios::Error::OK) { std::memcpy(requestBlock->request.get(), userRequest.get(), 0x48u); error = processLoaderOrUserRequest(driver, requestBlock, isLoader); if (error >= ios::Error::OK) { error = ipckDriverSubmitRequest(driver, requestBlock); } if (isLoader) { driver->perProcessNumLoaderRequests[pidx]++; } else { driver->perProcessNumUserRequests[pidx]++; } if (error < ios::Error::OK) { ipckDriverFreeRequestBlock(driver, requestBlock); } } } } driver->perProcessLastError[pidx] = error; return ios::Error::OK; } ios::Error submitUserRequest(virt_ptr driver, virt_ptr userRequest) { return submitUserOrLoaderRequest(driver, userRequest, false); } ios::Error submitLoaderRequest(virt_ptr driver, virt_ptr userRequest) { return submitUserOrLoaderRequest(driver, userRequest, true); } static ios::Error defensiveProcessIncomingMessagePointer(virt_ptr driver, virt_ptr request, virt_ptr *outRequestBlock) { auto index = request - driver->requestsBuffer; if (index >= IPCKRequestsPerCore || index < 0) { return ios::Error::Invalid; } if (driver->requestBlocks[index].request != request) { return ios::Error::Invalid; } auto requestBlock = virt_addrof(driver->requestBlocks[index]); auto flags = requestBlock->flags.value(); if (flags.requestState() == IPCKDriverRequestBlock::Unallocated) { return ios::Error::Invalid; } requestBlock->flags = flags.replyState(IPCKDriverRequestBlock::ReceivedReply); *outRequestBlock = requestBlock; return ios::Error::OK; } static void processReply(virt_ptr driver, phys_ptr reply) { if (driver->state < IPCKDriverState::Open) { return; } auto requestBlock = virt_ptr { nullptr }; auto request = virt_cast(physicalToEffectiveCached(phys_cast(reply))); auto error = defensiveProcessIncomingMessagePointer(driver, request, &requestBlock); if (error < ios::Error::OK) { return; } if (requestBlock->asyncCallback->func) { requestBlock->asyncCallback->func(reply->reply, requestBlock->asyncCallbackData); ipckDriverFreeRequestBlock(driver, requestBlock); } else { auto isLoader = false; auto flags = requestBlock->flags.value(); auto processId = flags.clientProcessId(); if (flags.loaderProcessId() != RamPartitionId::Invalid && flags.loaderProcessId() != RamPartitionId::Kernel) { processId = flags.loaderProcessId(); isLoader = true; } auto pidx = static_cast(processId); if (isLoader) { IPCKDriver_FIFOPush(virt_addrof(driver->perProcessLoaderReply[pidx]), requestBlock); } else { IPCKDriver_FIFOPush(virt_addrof(driver->perProcessUserReply[pidx]), requestBlock); } } } static void dispatchUserRepliesCallback(virt_ptr driver, virt_ptr interruptedContext, uint32_t pidx) { // Fill the process reply queue auto requestBlock = virt_ptr { nullptr }; auto replyQueue = driver->perProcessReplyQueue[pidx]; while (replyQueue->numReplies < replyQueue->replies.size()) { auto error = IPCKDriver_FIFOPop(virt_addrof(driver->perProcessUserReply[pidx]), &requestBlock); if (error < ios::Error::OK) { break; } std::memcpy(requestBlock->userRequest.get(), requestBlock->request.get(), 0x48u); replyQueue->replies[replyQueue->numReplies] = requestBlock->userRequest; replyQueue->numReplies++; driver->perProcessNumUserRequests[pidx]--; ipckDriverFreeRequestBlock(driver, requestBlock); } for (auto i = replyQueue->numReplies; i < replyQueue->replies.size(); ++i) { replyQueue->replies[i] = nullptr; } // Call the user callback if (driver->perProcessCallbacks[pidx]) { driver->perProcessCallbacks[pidx](driver->interruptType, interruptedContext); } } static void ipckDriverHandleInterrupt(InterruptType type, virt_ptr interruptedContext) { auto driver = ipckDriverGetInstance(); auto responses = std::vector> { }; // Get the pending replies sIpcMutex.lock(); sPendingResponses[driver->coreId].swap(responses); sIpcMutex.unlock(); // Process replies into process queue for (auto response : responses) { processReply(driver, response); } // Dispatch any pending user replies for the current process auto pidx = static_cast(getCurrentRamPartitionId()); if (driver->perProcessUserReply[pidx].count) { dispatchUserRepliesCallback(driver, interruptedContext, pidx); } } static ios::Error initialiseRegisters(virt_ptr driver) { switch (driver->coreId) { case 0: driver->registers.ppcMsg = virt_cast(virt_addr { 0x0D800400 }); driver->registers.ppcCtrl = virt_cast(virt_addr { 0x0D800404 }); driver->registers.armMsg = virt_cast(virt_addr { 0x0D800408 }); driver->registers.ahbLt = virt_cast(virt_addr { 0x0D800444 }); driver->registers.unkMaybeFlags = 0x40000000u; break; case 1: driver->registers.ppcMsg = virt_cast(virt_addr { 0x0D800410 }); driver->registers.ppcCtrl = virt_cast(virt_addr { 0x0D800414 }); driver->registers.armMsg = virt_cast(virt_addr { 0x0D800418 }); driver->registers.ahbLt = virt_cast(virt_addr { 0x0D800454 }); driver->registers.unkMaybeFlags = 0x10000000u; break; case 2: driver->registers.ppcMsg = virt_cast(virt_addr { 0x0D800420 }); driver->registers.ppcCtrl = virt_cast(virt_addr { 0x0D800424 }); driver->registers.armMsg = virt_cast(virt_addr { 0x0D800428 }); driver->registers.ahbLt = virt_cast(virt_addr { 0x0D800464 }); driver->registers.unkMaybeFlags = 0x04000000u; break; } return ios::Error::OK; } ios::Error ipckDriverInit() { auto driver = ipckDriverGetInstance(); std::memset(driver.get(), 0, sizeof(IPCKDriver)); driver->coreId = cpu::this_core::id(); switch (driver->coreId) { case 0: driver->interruptType = InterruptType::IpcPpc0; driver->requestsBuffer = virt_addrof(sIpckDriverData->requestBuffer); break; case 1: driver->interruptType = InterruptType::IpcPpc1; driver->requestsBuffer = virt_addrof(sIpckDriverData->requestBuffer) + IPCKRequestsPerCore; break; case 2: driver->interruptType = InterruptType::IpcPpc2; driver->requestsBuffer = virt_addrof(sIpckDriverData->requestBuffer) + IPCKRequestsPerCore * 2; break; } auto error = initialiseRegisters(driver); if (error < ios::Error::OK) { return error; } std::memset(driver->requestsBuffer.get(), 0, sizeof(IPCKDriverRequest) * IPCKRequestsPerCore); driver->state = IPCKDriverState::Initialised; // TODO: Register proc action callback to cleanup IPCKDriver for process return ios::Error::OK; } static ios::Error initialiseResourceBuffers(virt_ptr driver) { for (auto i = 0u; i < IPCKRequestsPerCore; ++i) { // Allocate memory to hold our host function pointer, this is because the // host function pointer will be 8 bytes on 64bit systems but we only // have space for 4 bytes in the structure. auto hostCallbackPtr = allocStaticData(); hostCallbackPtr->func = nullptr; driver->requestBlocks[i].asyncCallback = hostCallbackPtr; driver->requestBlocks[i].asyncCallbackData = nullptr; driver->requestBlocks[i].userRequest = nullptr; driver->requestBlocks[i].request = virt_addrof(driver->requestsBuffer[i]); } return ios::Error::OK; } ios::Error ipckDriverOpen() { auto driver = ipckDriverGetInstance(); if (driver->state != IPCKDriverState::Initialised && driver->state != IPCKDriverState::Unknown1) { return ios::Error::NotReady; } auto error = initialiseResourceBuffers(driver); if (error < ios::Error::OK) { return error; } IPCKDriver_FIFOInit(virt_addrof(driver->freeFifo)); IPCKDriver_FIFOInit(virt_addrof(driver->outboundFIFO)); for (auto i = 0u; i < NumRamPartitions; ++i) { IPCKDriver_FIFOInit(virt_addrof(driver->perProcessUserReply[i])); IPCKDriver_FIFOInit(virt_addrof(driver->perProcessLoaderReply[i])); } for (auto i = 0u; i < IPCKRequestsPerCore; ++i) { IPCKDriver_FIFOPush(virt_addrof(driver->freeFifo), virt_addrof(driver->requestBlocks[i])); } driver->unk0x04++; setKernelInterruptHandler(driver->interruptType, ipckDriverHandleInterrupt); driver->state = IPCKDriverState::Open; return ios::Error::OK; } void initialiseStaticIpckDriverData() { sIpckDriverData = allocStaticData(); } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_ipckdriver.h ================================================ #pragma once #include "cafe_kernel_interrupts.h" #include "cafe_kernel_ipckdriverfifo.h" #include "cafe_kernel_processid.h" #include "ios/ios_error.h" #include "ios/ios_ipc.h" #include #include #include namespace cafe::kernel { #pragma pack(push, 1) using IpcRequest = ios::IpcRequest; using IpcCommand = ios::Command; struct IPCKDriverRequest { //! Actual IPC request be2_struct request; //! Allegedly the previous IPC command be2_val prevCommand; //! Allegedly the previous IPC handle be2_val prevHandle; //! Buffer argument 1 be2_virt_ptr buffer1; //! Buffer argument 2 be2_virt_ptr buffer2; //! Buffer to copy device name to for IOS_Open be2_array nameBuffer; UNKNOWN(0x80 - 0x68); }; CHECK_OFFSET(IPCKDriverRequest, 0x00, request); CHECK_OFFSET(IPCKDriverRequest, 0x38, prevCommand); CHECK_OFFSET(IPCKDriverRequest, 0x3C, prevHandle); CHECK_OFFSET(IPCKDriverRequest, 0x40, buffer1); CHECK_OFFSET(IPCKDriverRequest, 0x44, buffer2); CHECK_OFFSET(IPCKDriverRequest, 0x48, nameBuffer); CHECK_SIZE(IPCKDriverRequest, 0x80); struct IPCKDriverReplyQueue { static constexpr auto Size = 0x30u; be2_val numReplies; be2_array, Size> replies; }; CHECK_OFFSET(IPCKDriverReplyQueue, 0x00, numReplies); CHECK_OFFSET(IPCKDriverReplyQueue, 0x04, replies); CHECK_SIZE(IPCKDriverReplyQueue, 0xC4); using IPCKUserInterruptHandlerFn = void(*)(InterruptType type, virt_ptr interruptedContext); #pragma pack(pop) ios::Error ipckDriverUserOpen(uint32_t numReplies, virt_ptr replyQueue, IPCKUserInterruptHandlerFn handler); ios::Error ipckDriverUserClose(); ios::Error ipckDriverUserSubmitRequest(virt_ptr request); ios::Error ipckDriverLoaderOpen(); ios::Error ipckDriverLoaderClose(); ios::Error ipckDriverLoaderSubmitRequest(virt_ptr request); virt_ptr ipckDriverLoaderPollCompletion(); void ipckDriverIosSubmitReply(phys_ptr reply); namespace internal { #pragma pack(push, 1) constexpr auto IPCKRequestsPerCore = 0xB0u; constexpr auto IPCKRequestsPerProcess = 0x30u; enum class IPCKDriverState : uint32_t { Invalid = 0, Unknown1 = 1, Initialised = 2, Open = 3, Submitting = 4, }; struct IPCKDriverRegisters { be2_virt_ptr ppcMsg; be2_virt_ptr ppcCtrl; be2_virt_ptr armMsg; be2_virt_ptr ahbLt; be2_val unkMaybeFlags; }; // This is a pointer to a host function pointer - we must allocate guest // memory to store the host function pointer in. using IPCKDriverHostAsyncCallbackFn = void(*)(ios::Error, virt_ptr); struct IPCKDriverHostAsyncCallback { IPCKDriverHostAsyncCallbackFn func; }; struct IPCKDriverRequestBlock { enum RequestState { Unallocated = 0, Allocated = 1, }; enum ReplyState { WaitReply = 0, ReceivedReply = 1, }; BITFIELD_BEG(Flags, uint32_t) BITFIELD_ENTRY(10, 2, uint8_t, unk_0x0C00) BITFIELD_ENTRY(12, 2, ReplyState, replyState) BITFIELD_ENTRY(14, 2, RequestState, requestState) BITFIELD_ENTRY(16, 8, RamPartitionId, loaderProcessId) BITFIELD_ENTRY(24, 8, RamPartitionId, clientProcessId) BITFIELD_END be2_val flags; //! Kernel request callback be2_virt_ptr asyncCallback; //! Data passed to kernel callback be2_virt_ptr asyncCallbackData; //! User memory for a request be2_virt_ptr userRequest; //! Kernel memory for request, assigned from driver's requestsBuffer be2_virt_ptr request; }; CHECK_OFFSET(IPCKDriverRequestBlock, 0x00, flags); CHECK_OFFSET(IPCKDriverRequestBlock, 0x04, asyncCallback); CHECK_OFFSET(IPCKDriverRequestBlock, 0x08, asyncCallbackData); CHECK_OFFSET(IPCKDriverRequestBlock, 0x0C, userRequest); CHECK_OFFSET(IPCKDriverRequestBlock, 0x10, request); CHECK_SIZE(IPCKDriverRequestBlock, 0x14); struct IPCKDriver { be2_val state; be2_val unk0x04; be2_val coreId; be2_val interruptType; be2_virt_ptr requestsBuffer; UNKNOWN(0x4); be2_array, NumRamPartitions> perProcessReplyQueue; #ifdef DECAF_KERNEL_LLE be2_array, NumRamPartitions> perProcessCallbacks; be2_array, NumRamPartitions> perProcessCallbackStacks; be2_array, NumRamPartitions> perProcessCallbackContexts; #else std::array perProcessCallbacks; PADDING(0x98 - (0x38 + sizeof(IPCKUserInterruptHandlerFn) * NumRamPartitions)); #endif be2_array perProcessLastError; UNKNOWN(0x4); be2_struct registers; UNKNOWN(0x8); be2_array perProcessNumUserRequests; be2_array perProcessNumLoaderRequests; be2_struct> freeFifo; be2_struct> outboundFIFO; be2_array, NumRamPartitions> perProcessUserReply; be2_array, NumRamPartitions> perProcessLoaderReply; be2_array requestBlocks; }; CHECK_OFFSET(IPCKDriver, 0x0, state); CHECK_OFFSET(IPCKDriver, 0x4, unk0x04); CHECK_OFFSET(IPCKDriver, 0x8, coreId); CHECK_OFFSET(IPCKDriver, 0xC, interruptType); CHECK_OFFSET(IPCKDriver, 0x10, requestsBuffer); CHECK_OFFSET(IPCKDriver, 0x18, perProcessReplyQueue); #ifdef DECAF_KERNEL_LLE CHECK_OFFSET(IPCKDriver, 0x38, perProcessCallbacks); CHECK_OFFSET(IPCKDriver, 0x58, perProcessCallbackStacks); CHECK_OFFSET(IPCKDriver, 0x78, perProcessCallbackContexts); #endif CHECK_OFFSET(IPCKDriver, 0x98, perProcessLastError); CHECK_OFFSET(IPCKDriver, 0xBC, registers); CHECK_OFFSET(IPCKDriver, 0xD8, perProcessNumUserRequests); CHECK_OFFSET(IPCKDriver, 0xF8, perProcessNumLoaderRequests); CHECK_OFFSET(IPCKDriver, 0x118, freeFifo); CHECK_OFFSET(IPCKDriver, 0x3E8, outboundFIFO); CHECK_OFFSET(IPCKDriver, 0x6B8, perProcessUserReply); CHECK_OFFSET(IPCKDriver, 0xD38, perProcessLoaderReply); CHECK_OFFSET(IPCKDriver, 0x13B8, requestBlocks); CHECK_SIZE(IPCKDriver, 0x2178); #pragma pack(pop) virt_ptr ipckDriverGetInstance(); ios::Error ipckDriverAllocateRequestBlock(RamPartitionId clientProcessId, RamPartitionId loaderProcessId, virt_ptr driver, virt_ptr *outRequestBlock, ios::Handle handle, ios::Command command, IPCKDriverHostAsyncCallbackFn asyncCallback, virt_ptr asyncContext); void ipckDriverFreeRequestBlock(virt_ptr driver, virt_ptr requestBlock); ios::Error ipckDriverSubmitRequest(virt_ptr driver, virt_ptr requestBlock); ios::Error ipckDriverInit(); ios::Error ipckDriverOpen(); void initialiseStaticIpckDriverData(); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_ipckdriverfifo.h ================================================ #pragma once #include "ios/ios_error.h" #include namespace cafe::kernel::internal { struct IPCKDriverRequestBlock; /** * FIFO queue for IPCKDriverRequestBlocks. * * Functions similar to a ring buffer. */ template struct IPCKDriverFIFO { //! The current item index to push to be2_val pushIndex; //! The current item index to pop from be2_val popIndex; //! The number of items in the queue be2_val count; //! Tracks the highest amount of items there has been in the queue be2_val maxCount; //! Items in the queue be2_array, Size> requestBlocks; }; CHECK_OFFSET(IPCKDriverFIFO<1>, 0x00, pushIndex); CHECK_OFFSET(IPCKDriverFIFO<1>, 0x04, popIndex); CHECK_OFFSET(IPCKDriverFIFO<1>, 0x08, count); CHECK_OFFSET(IPCKDriverFIFO<1>, 0x0C, maxCount); CHECK_OFFSET(IPCKDriverFIFO<1>, 0x10, requestBlocks); CHECK_SIZE(IPCKDriverFIFO<1>, 0x14); /** * Initialises an IPCKDriverFIFO structure. */ template static void IPCKDriver_FIFOInit(virt_ptr> fifo) { fifo->pushIndex = 0; fifo->popIndex = -1; fifo->count = 0; fifo->requestBlocks.fill(nullptr); } /** * Push a request into an IPCKDriverFIFO structure. * * \retval ios::Error::OK * Success. * * \retval ios::Error::QFull * There was no free space in the queue to push the request. */ template static ios::Error IPCKDriver_FIFOPush(virt_ptr> fifo, virt_ptr requestBlock) { if (fifo->pushIndex == fifo->popIndex) { return ios::Error::QFull; } fifo->requestBlocks[fifo->pushIndex] = requestBlock; if (fifo->popIndex == -1) { fifo->popIndex = fifo->pushIndex; } fifo->count += 1; fifo->pushIndex = static_cast((fifo->pushIndex + 1) % Size); if (fifo->count > fifo->maxCount) { fifo->maxCount = fifo->count; } return ios::Error::OK; } /** * Pop a request from an IPCKDriverFIFO structure. * * \retval ios::Error::OK * Success. * * \retval ios::Error::QEmpty * There was no requests to pop from the queue. */ template static ios::Error IPCKDriver_FIFOPop(virt_ptr> fifo, virt_ptr *outRequestBlock) { if (fifo->popIndex == -1) { return ios::Error::QEmpty; } auto requestBlock = fifo->requestBlocks[fifo->popIndex]; fifo->count -= 1; if (fifo->count == 0) { fifo->popIndex = -1; } else { fifo->popIndex = static_cast((fifo->popIndex + 1) % Size); } *outRequestBlock = requestBlock; return ios::Error::OK; } } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_loader.cpp ================================================ #include "cafe_kernel_loader.h" #include "cafe_kernel_mmu.h" #include "cafe_kernel_process.h" #include "cafe/loader/cafe_loader_entry.h" #include "cafe/loader/cafe_loader_globals.h" #include "cafe/loader/cafe_loader_loaded_rpl.h" #include #include #include namespace cafe::kernel { namespace internal { static int32_t loaderEntry(); } // namespace internal int32_t loaderLink(loader::LOADER_Handle handle, virt_ptr minFileInfo, virt_ptr linkInfo, uint32_t linkInfoSize) { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Link; loaderIpc->entryParams.dispatch.handle = handle; loaderIpc->entryParams.dispatch.minFileInfo = minFileInfo; loaderIpc->entryParams.dispatch.linkInfo = linkInfo; loaderIpc->entryParams.dispatch.linkInfoSize = linkInfoSize; return internal::loaderEntry(); } int32_t loaderPrep(virt_ptr minFileInfo) { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Prep; loaderIpc->entryParams.dispatch.minFileInfo = minFileInfo; return internal::loaderEntry(); } int32_t loaderPurge(loader::LOADER_Handle handle) { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Purge; loaderIpc->entryParams.dispatch.handle = handle; return internal::loaderEntry(); } int32_t loaderSetup(loader::LOADER_Handle handle, virt_ptr minFileInfo) { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Setup; loaderIpc->entryParams.dispatch.handle = handle; loaderIpc->entryParams.dispatch.minFileInfo = minFileInfo; return internal::loaderEntry(); } int32_t loaderQuery(loader::LOADER_Handle handle, virt_ptr outMinFileInfo) { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Query; loaderIpc->entryParams.dispatch.handle = handle; loaderIpc->entryParams.dispatch.minFileInfo = outMinFileInfo; return internal::loaderEntry(); } int32_t loaderUserGainControl() { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::UserGainControl; return internal::loaderEntry(); } int32_t findClosestSymbol(virt_addr addr, virt_ptr outSymbolDistance, virt_ptr symbolNameBuffer, uint32_t symbolNameBufferLength, virt_ptr moduleNameBuffer, uint32_t moduleNameBufferLength) { // Verify parameters if (outSymbolDistance && (virt_cast(outSymbolDistance) < virt_addr { 0x10000000 } || virt_cast(outSymbolDistance) >= virt_addr { 0xC0000000 } || virt_cast(outSymbolDistance) & 3 || !validateAddressRange(virt_cast(outSymbolDistance), 4))) { return 0xBAD20008; } if (symbolNameBufferLength) { if (virt_cast(symbolNameBuffer) < virt_addr { 0x10000000 } || virt_cast(symbolNameBuffer) >= virt_addr { 0xC0000000 } || !validateAddressRange(virt_cast(symbolNameBuffer), symbolNameBufferLength)) { return 0xBAD2000A; } } else { symbolNameBuffer = nullptr; } if (moduleNameBufferLength) { if (virt_cast(moduleNameBuffer) < virt_addr { 0x10000000 } || virt_cast(moduleNameBuffer) >= virt_addr { 0xC0000000 } || !validateAddressRange(virt_cast(moduleNameBuffer), moduleNameBufferLength)) { return 0xBAD2000A; } } else { moduleNameBuffer = nullptr; } return internal::findClosestSymbol(addr, outSymbolDistance, symbolNameBuffer, symbolNameBufferLength, moduleNameBuffer, moduleNameBufferLength); } namespace internal { static std::pair, virt_addr> getLoaderContext() { auto contextStorage = loader::getContextStorage(); auto context = virt_ptr { nullptr }; auto stackTop = virt_addr { 0 }; switch (cpu::this_core::id()) { case 0: context = virt_addrof(contextStorage->context0); stackTop = virt_cast(virt_addrof(contextStorage->stack0) + contextStorage->stack0.size() - 16); break; case 1: context = virt_addrof(contextStorage->context1); stackTop = virt_cast(virt_addrof(contextStorage->stack1) + contextStorage->stack1.size() - 16); break; case 2: context = virt_addrof(contextStorage->context2); stackTop = virt_cast(virt_addrof(contextStorage->stack2) + contextStorage->stack2.size() - 16); break; default: decaf_abort(fmt::format("Unexpected core id {}", cpu::this_core::id())); } return { context, stackTop }; } static int32_t loaderEntry() { auto loaderIpc = loader::getKernelIpcStorage(); auto [context, stackTop] = getLoaderContext(); loaderIpc->entryParams.context = context; loaderIpc->entryParams.procConfig = 0; loaderIpc->entryParams.procContext = getCurrentContext(); loaderIpc->entryParams.interruptsAllowed = TRUE; loaderIpc->entryParams.procId = getCurrentUniqueProcessId(); // In a real kernel we'd switch to the PPC context // But our loader is HLE only atm so we just call the loader directly return loader::LoaderStart(TRUE, virt_addrof(loaderIpc->entryParams)); } static void KiRPLLoaderSetup(ProcessFlags processFlags, UniqueProcessId callerProcessId, UniqueProcessId targetProcessId) { auto loaderIpc = loader::getKernelIpcStorage(); auto [context, stackTop] = getLoaderContext(); if (targetProcessId == UniqueProcessId::Root) { processFlags = ProcessFlags::get(0). isFirstProcess(true); } else { // Clear all flags except debugLevel processFlags = ProcessFlags::get(0). debugLevel(processFlags.debugLevel()); if (processFlags.unkBit12() || processFlags.isFirstProcess()) { processFlags = processFlags .disableSharedLibraries(true); } // TODO: Fix this when we implement multi-process // Normally this would only be set by Root process, but in decaf until we // implement processes then we will always be first process processFlags = processFlags. isFirstProcess(true); } loaderIpc->unk0x00 = 0u; loaderIpc->processFlags = processFlags; // In a real kernel we'd switch to the PPC context // context->srr0 = loader entry point // context->srr1 = 0x4000 context->gpr[1] = static_cast(stackTop); context->gpr[3] = 0u; context->gpr[4] = static_cast(virt_cast(virt_addrof(loaderIpc->entryParams))); // But our loader is HLE only atm so we just call the loader directly loader::LoaderStart(FALSE, virt_addrof(loaderIpc->entryParams)); } void KiRPLStartup(UniqueProcessId callerProcessId, UniqueProcessId targetProcessId, ProcessFlags processFlags, uint32_t numCodeAreaHeapBlocks, uint32_t maxCodeSize, uint32_t maxDataSize, uint32_t titleLoc) { auto loaderIpc = loader::getKernelIpcStorage(); loaderIpc->maxDataSize = maxDataSize; loaderIpc->callerProcessId = callerProcessId; loaderIpc->maxCodeSize = maxCodeSize; loaderIpc->procTitleLoc = titleLoc; loaderIpc->targetProcessId = targetProcessId; loaderIpc->numCodeAreaHeapBlocks = numCodeAreaHeapBlocks; loaderIpc->rpxModule = nullptr; loaderIpc->loadedModuleList = nullptr; loaderIpc->unk0x28 = 0u; loaderIpc->entryParams.procContext = nullptr; loaderIpc->entryParams.procId = UniqueProcessId::Invalid; loaderIpc->entryParams.procConfig = -1; loaderIpc->entryParams.context = nullptr; loaderIpc->entryParams.interruptsAllowed = FALSE; std::memset(virt_addrof(loaderIpc->entryParams.dispatch).get(), 0, sizeof(loader::LOADER_EntryDispatch)); KiRPLLoaderSetup(processFlags, callerProcessId, targetProcessId); } int32_t findClosestSymbol(virt_addr addr, uint32_t *outSymbolDistance, char *symbolNameBuffer, uint32_t symbolNameBufferLength, char *moduleNameBuffer, uint32_t moduleNameBufferLength) { auto partitionData = getCurrentRamPartitionData(); if (!partitionData) { return 0xBAD20002; } if (outSymbolDistance) { *outSymbolDistance = static_cast(addr); } if (symbolNameBuffer) { symbolNameBuffer[0] = char { 0 }; } if (moduleNameBuffer) { moduleNameBuffer[0] = char { 0 }; } // Find the module and section containing the given address auto containingModule = virt_ptr { nullptr }; auto containingSectionIndex = 0u; for (auto rpl = partitionData->loadedModuleList; rpl; rpl = rpl->nextLoadedRpl) { for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) { auto sectionAddress = rpl->sectionAddressBuffer[i]; if (!sectionAddress) { continue; } auto sectionHeader = virt_cast( virt_cast(rpl->sectionHeaderBuffer) + rpl->elfHeader.shentsize * i); if (addr >= sectionAddress && addr < sectionAddress + sectionHeader->size) { containingModule = rpl; containingSectionIndex = i; break; } } } // Search the module's symbol table for the nearest symbol auto nearestSymbolName = virt_ptr { nullptr }; auto nearestSymbolValue = virt_addr { 0 }; if (containingModule) { auto nearestSymbolDistance = uint32_t { 0xFFFFFFFFu }; for (auto i = 0u; i < containingModule->elfHeader.shnum; ++i) { auto sectionAddress = containingModule->sectionAddressBuffer[i]; auto sectionHeader = virt_cast( virt_cast(containingModule->sectionHeaderBuffer) + containingModule->elfHeader.shentsize * i); if (sectionHeader->type == loader::rpl::SHT_SYMTAB) { auto strTab = containingModule->sectionAddressBuffer[sectionHeader->link]; auto symTabEntSize = sectionHeader->entsize ? static_cast(sectionHeader->entsize) : sizeof(loader::rpl::Symbol); auto numSymbols = sectionHeader->size / symTabEntSize; for (auto j = 0u; j < numSymbols; ++j) { auto symbol = virt_cast(sectionAddress + j * symTabEntSize); auto symbolAddr = virt_addr { static_cast(symbol->value) }; if (symbol->shndx != containingSectionIndex || symbolAddr > addr) { continue; } // Ignore symbols beginning with $ or . auto symbolName = virt_cast(strTab + symbol->name); if (symbolName[0] == '$' || symbolName[0] == '.') { continue; } if (addr - symbolAddr < nearestSymbolDistance) { nearestSymbolName = symbolName; nearestSymbolDistance = static_cast(addr - symbolAddr); nearestSymbolValue = symbolAddr; if (nearestSymbolDistance == 0) { break; } } } if (nearestSymbolDistance == 0) { break; } } } } // Set the output if (moduleNameBuffer) { if (containingModule) { string_copy(moduleNameBuffer, containingModule->moduleNameBuffer.get(), moduleNameBufferLength); } else { moduleNameBuffer[0] = char { 0 }; } } if (symbolNameBuffer) { if (nearestSymbolName && nearestSymbolName[0]) { string_copy(symbolNameBuffer, nearestSymbolName.get(), symbolNameBufferLength); } else { string_copy(symbolNameBuffer, "", symbolNameBufferLength); } } if (outSymbolDistance) { *outSymbolDistance = static_cast(addr - nearestSymbolValue); } return 0; } int32_t findClosestSymbol(virt_addr addr, virt_ptr outSymbolDistance, virt_ptr symbolNameBuffer, uint32_t symbolNameBufferLength, virt_ptr moduleNameBuffer, uint32_t moduleNameBufferLength) { auto symbolDistance = uint32_t { 0 }; auto result = findClosestSymbol(addr, &symbolDistance, symbolNameBuffer.get(), symbolNameBufferLength, moduleNameBuffer.get(), moduleNameBufferLength); *outSymbolDistance = symbolDistance; return result; } } // namespace internal } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_loader.h ================================================ #include "cafe_kernel_process.h" #include "cafe/loader/cafe_loader_minfileinfo.h" #include namespace cafe::kernel { int32_t loaderLink(loader::LOADER_Handle handle, virt_ptr minFileInfo, virt_ptr linkInfo, uint32_t linkInfoSize); int32_t loaderPrep(virt_ptr minFileInfo); int32_t loaderPurge(loader::LOADER_Handle handle); int32_t loaderSetup(loader::LOADER_Handle handle, virt_ptr minFileInfo); int32_t loaderQuery(loader::LOADER_Handle handle, virt_ptr outMinFileInfo); int32_t loaderUserGainControl(); int32_t findClosestSymbol(virt_addr addr, virt_ptr outSymbolDistance, virt_ptr symbolNameBuffer, uint32_t symbolNameBufferLength, virt_ptr moduleNameBuffer, uint32_t moduleNameBufferLength); namespace internal { void KiRPLStartup(UniqueProcessId callerProcessId, UniqueProcessId targetProcessId, ProcessFlags processFlags, uint32_t numCodeAreaHeapBlocks, uint32_t maxCodeSize, uint32_t maxDataSize, uint32_t titleLoc); int32_t findClosestSymbol(virt_addr addr, virt_ptr outSymbolDistance, virt_ptr symbolNameBuffer, uint32_t symbolNameBufferLength, virt_ptr moduleNameBuffer, uint32_t moduleNameBufferLength); int32_t findClosestSymbol(virt_addr addr, uint32_t *outSymbolDistance, char *symbolNameBuffer, uint32_t symbolNameBufferLength, char *moduleNameBuffer, uint32_t moduleNameBufferLength); } // namespace internal } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_lock.cpp ================================================ #include "cafe_kernel_lock.h" #include #include namespace cafe::kernel::internal { static SpinLock sKernelLock { 0 }; bool spinLockAcquire(SpinLock &spinLock, uint32_t value) { auto expected = 0u; if (!value) { return false; } while (!spinLock.value.compare_exchange_weak(expected, value, std::memory_order_acquire)) { expected = 0; } return true; } bool spinLockRelease(SpinLock &spinLock, uint32_t expected) { auto value = spinLock.value.exchange(0, std::memory_order_release); return (value == expected); } void kernelLockAcquire() { spinLockAcquire(sKernelLock, cpu::this_core::id() + 1); } void kernelLockRelease() { spinLockRelease(sKernelLock, cpu::this_core::id() + 1); } } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_lock.h ================================================ #pragma once #include #include namespace cafe::kernel::internal { struct SpinLock { std::atomic value; }; bool spinLockAcquire(SpinLock &spinLock, uint32_t value); bool spinLockRelease(SpinLock &spinLock, uint32_t expected); void kernelLockAcquire(); void kernelLockRelease(); } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_mcp.cpp ================================================ #include "cafe_kernel_ipc.h" #include "cafe_kernel_mcp.h" #include "cafe_kernel_processid.h" #include "ios/mcp/ios_mcp_enum.h" #include "ios/mcp/ios_mcp_mcp_types.h" #include "ios/mcp/ios_mcp_mcp_request.h" #include "ios/mcp/ios_mcp_mcp_response.h" namespace cafe::kernel::internal { using namespace ios::mcp; ios::Error mcpGetFileLength(std::string_view path, uint32_t *outSize, MCPFileType fileType, uint32_t a4) { auto buffer = ipcAllocBuffer(sizeof(MCPRequestGetFileLength)); // Prepare request auto request = virt_cast(buffer); request->fileType = fileType; request->unk0x18 = a4; request->name = path; // Send ioctl auto error = IOS_Ioctl(RamPartitionId::Kernel, RamPartitionId::Invalid, getMcpHandle(), MCPCommand::GetFileLength, buffer, sizeof(MCPRequestGetFileLength), nullptr, 0u); if (error >= ios::Error::OK) { *outSize = static_cast(error); } ipcFreeBuffer(buffer); return error; } ios::Error mcpLoadFile(std::string_view path, virt_ptr dataBuffer, uint32_t size, uint32_t pos, MCPFileType fileType, UniqueProcessId cafeProcessId) { auto buffer = ipcAllocBuffer(sizeof(MCPRequestLoadFile)); // Prepare request auto request = virt_cast(buffer); request->fileType = fileType; request->name = path; request->pos = pos; request->cafeProcessId = static_cast(cafeProcessId); // Send ioctl auto error = IOS_Ioctl(RamPartitionId::Kernel, RamPartitionId::Invalid, getMcpHandle(), MCPCommand::LoadFile, buffer, sizeof(MCPRequestLoadFile), dataBuffer, size); ipcFreeBuffer(buffer); return error; } ios::Error mcpPrepareTitle(MCPTitleId titleId, virt_ptr outTitleInfo) { auto buffer = ipcAllocBuffer(sizeof(MCPRequestPrepareTitle)); // Prepare request auto request = virt_cast(buffer); request->titleId = titleId; // Send ioctl auto error = IOS_Ioctl(RamPartitionId::Kernel, RamPartitionId::Invalid, getMcpHandle(), MCPCommand::PrepareTitle0x52, buffer, sizeof(MCPRequestPrepareTitle), buffer, sizeof(MCPResponsePrepareTitle)); // Handle response if (error >= ios::Error::OK) { auto response = virt_cast(buffer); std::memcpy(outTitleInfo.get(), virt_addrof(response->titleInfo).get(), sizeof(MCPPPrepareTitleInfo)); } ipcFreeBuffer(buffer); return error; } ios::Error mcpSwitchTitle(RamPartitionId rampid, phys_addr dataStart, phys_addr codeGenStart, phys_addr codeEnd) { auto buffer = ipcAllocBuffer(sizeof(MCPRequestSwitchTitle)); // Prepare request auto request = virt_cast(buffer); request->cafeProcessId = static_cast(rampid); request->dataStart = dataStart; request->codeGenStart = codeGenStart; request->codeEnd = codeEnd; // Send ioctl auto error = IOS_Ioctl(RamPartitionId::Kernel, RamPartitionId::Invalid, getMcpHandle(), MCPCommand::SwitchTitle, buffer, sizeof(MCPRequestSwitchTitle), nullptr, 0u); ipcFreeBuffer(buffer); return error; } } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_mcp.h ================================================ #pragma once #include "cafe_kernel_processid.h" #include "ios/ios_error.h" #include "ios/mcp/ios_mcp_mcp_types.h" #include #include namespace cafe::kernel::internal { ios::Error mcpGetFileLength(std::string_view path, uint32_t *outSize, ios::mcp::MCPFileType fileType, uint32_t a4); ios::Error mcpLoadFile(std::string_view path, virt_ptr buffer, uint32_t size, uint32_t pos, ios::mcp::MCPFileType fileType, UniqueProcessId cafeProcessId); ios::Error mcpPrepareTitle(ios::mcp::MCPTitleId titleId, virt_ptr outTitleInfo); ios::Error mcpSwitchTitle(RamPartitionId rampid, phys_addr dataStart, phys_addr codeGenStart, phys_addr physEnd); } // namespace cafe::kernel::internal ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_mmu.cpp ================================================ #include "cafe_kernel_mmu.h" #include "cafe_kernel_process.h" #include #include #include #include #include #include #include namespace cafe::kernel { constexpr auto MaxCodeGenSize = 0x2000000u; constexpr auto MaxTotalCodeSize = 0xE800000u; constexpr auto OverlayArenaVirtualStart = virt_addr { 0xA0000000 }; constexpr auto OverlayArenaVirtualEnd = virt_addr { 0xBC000000 }; constexpr auto OverlayArenaPhysicalStart = phys_addr { 0x34000000 }; constexpr auto OverlayArenaPhysicalEnd = phys_addr { 0x50000000 }; constexpr auto OverlayArenaSize = static_cast(OverlayArenaPhysicalEnd - OverlayArenaPhysicalStart); static std::array sCoreActiveAddressSpace = { nullptr, nullptr, nullptr }; namespace internal { static bool loadMapping(MemoryMap &mapping); static bool unloadMapping(MemoryMap &mapping); } enum MemoryMapFlags { MapUnknown1 = 1 << 1, MapUnknown2 = 1 << 2, MapUnknown4 = 1 << 4, MapUnknown6 = 1 << 6, MapCodeGen = 1 << 9, MapUnknown10 = 1 << 10, MapUnknown11 = 1 << 11, MapMainApplication = 1 << 13, MapUncached = 1 << 30, }; // This must be kept in order with enum class VirtualMemoryRegion MemoryMap sMemoryMap[] = { // CafeOS { virt_addr { 0x01000000 }, 0x800000, phys_addr { 0x32000000 }, 0x2CE08002 }, // CodeGen area { virt_addr { 0x01800000 }, 0x20000, phys_addr { 0 }, 0x28101200 }, // App Code { virt_addr { 0x02000000 }, 0, phys_addr { 0 }, 0x2CF09400 }, // App Data { virt_addr { 0x10000000 }, 0, phys_addr { 0 }, 0x28305800 }, // Overlay { virt_addr { 0xA0000000 }, 0x40000000, phys_addr { 0 }, 0x00002000 }, // Foreground bucket { virt_addr { 0xE0000000 }, 0x4000000, phys_addr { 0x14000000 }, 0x28204004 }, // Tiling Apertures { virt_addr { 0xE8000000 }, 0x2000000, phys_addr { 0xD0000000 }, 0x78200004 }, // Loader globals which are also read/write by kernel { virt_addr { 0xEFE00000 }, 0x80000, phys_addr { 0x1B900000 }, 0x28109010 }, // MEM1 { virt_addr { 0xF4000000 }, 0x2000000, phys_addr { 0 }, 0x28204004 }, // Loader bounce buffer { virt_addr { 0xF6000000 }, 0x800000, phys_addr { 0x1B000000 }, 0x3CA08002 }, // Shared data { virt_addr { 0xF8000000 }, 0x3000000, phys_addr { 0x18000000 }, 0x2CA08002 }, // Unknown { virt_addr { 0xFB000000 }, 0x800000, phys_addr { 0x1C800000 }, 0x28200002 }, // Registers { virt_addr { 0xFC000000 }, 0xC0000, phys_addr { 0xC000000 }, 0x70100022 }, { virt_addr { 0xFC0C0000 }, 0x120000, phys_addr { 0xC0C0000 }, 0x70100022 }, { virt_addr { 0xFC1E0000 }, 0x20000, phys_addr { 0xC1E0000 }, 0x78100024 }, { virt_addr { 0xFC200000 }, 0x80000, phys_addr { 0xC200000 }, 0x78100024 }, { virt_addr { 0xFC280000 }, 0x20000, phys_addr { 0xC280000 }, 0x78100024 }, // Write gather memory, 0x20000 per core { virt_addr { 0xFC2A0000 }, 0x20000, phys_addr { 0xC2A0000 }, 0x78100023 }, // Registers { virt_addr { 0xFC300000 }, 0x20000, phys_addr { 0xC300000 }, 0x78100024 }, { virt_addr { 0xFC320000 }, 0xE0000, phys_addr { 0xC320000 }, 0x70100022 }, { virt_addr { 0xFD000000 }, 0x400000, phys_addr { 0xD000000 }, 0x70100022 }, // Unknown { virt_addr { 0xFE000000 }, 0x800000, phys_addr { 0x1C000000 }, 0x20200002 }, // Kernel "work area" heap { virt_addr { 0xFF200000 }, 0x80000, phys_addr { 0x1B800000 }, 0x20100040 }, // Unknown { virt_addr { 0xFF280000 }, 0x80000, phys_addr { 0x1B880000 }, 0x20100040 }, // Locked Cache { virt_addr { 0xFFC00000 }, 0x20000, phys_addr { 0xFFC00000 }, 0x08100004 }, { virt_addr { 0xFFC40000 }, 0x20000, phys_addr { 0xFFC40000 }, 0x08100004 }, { virt_addr { 0xFFC80000 }, 0x20000, phys_addr { 0xFFC80000 }, 0x0810000C }, // Unknown { virt_addr { 0xFFCE0000 }, 0x20000, phys_addr { 0 }, 0x50100004 }, // Kernel data { virt_addr { 0xFFE00000 }, 0x20000, phys_addr { 0xFFE00000 }, 0x20100040 }, { virt_addr { 0xFFE40000 }, 0x20000, phys_addr { 0xFFE40000 }, 0x20100040 }, { virt_addr { 0xFFE80000 }, 0x60000, phys_addr { 0xFFE80000 }, 0x20100040 }, // Unknown { virt_addr { 0xFFF60000 }, 0x20000, phys_addr { 0xFFE20000 }, 0x20100080 }, { virt_addr { 0xFFF80000 }, 0x20000, phys_addr { 0xFFE60000 }, 0x2C100040 }, { virt_addr { 0xFFFA0000 }, 0x20000, phys_addr { 0xFFE60000 }, 0x20100080 }, { virt_addr { 0xFFFC0000 }, 0x20000, phys_addr { 0x1BFE0000 }, 0x24100002 }, { virt_addr { 0xFFFE0000 }, 0x20000, phys_addr { 0x1BF80000 }, 0x28100102 }, }; constexpr auto sMemoryMapSize = sizeof(sMemoryMap) / sizeof(sMemoryMap[0]); static_assert(static_cast(VirtualMemoryRegion::Max) == sMemoryMapSize); MemoryMap & getVirtualMemoryMap(VirtualMemoryRegion region) { return sMemoryMap[static_cast(region)]; } std::pair getForegroundBucket() { // TODO: Ensure process is in foreground, else return { 0, 0 } return { virt_addr { 0xE0000000 }, 0x4000000 }; } std::pair enableOverlayArena() { auto partitionData = internal::getCurrentRamPartitionData(); // TODO: In a multi process environment we will have to exit the overlay // process. if (!partitionData->addressSpace.overlayArenaEnabled) { auto mapping = MemoryMap { }; mapping.vaddr = OverlayArenaVirtualStart; mapping.paddr = OverlayArenaPhysicalStart; mapping.size = OverlayArenaSize; internal::loadMapping(mapping); partitionData->ramPartitionAllocation.overlayStart = OverlayArenaPhysicalStart; partitionData->ramPartitionAllocation.overlayEnd = OverlayArenaPhysicalEnd; partitionData->addressSpace.overlayArenaEnabled = true; } return { OverlayArenaVirtualStart, OverlayArenaSize }; } void disableOverlayArena() { auto partitionData = internal::getCurrentRamPartitionData(); if (partitionData->addressSpace.overlayArenaEnabled) { auto mapping = MemoryMap { }; mapping.vaddr = OverlayArenaVirtualStart; mapping.paddr = OverlayArenaPhysicalStart; mapping.size = OverlayArenaSize; internal::unloadMapping(mapping); partitionData->ramPartitionAllocation.overlayStart = phys_addr { 0 }; partitionData->ramPartitionAllocation.overlayEnd = phys_addr { 0 }; partitionData->addressSpace.overlayArenaEnabled = false; } } std::pair getAvailablePhysicalAddressRange() { auto partitionData = internal::getCurrentRamPartitionData(); return { partitionData->addressSpace.availStart, partitionData->addressSpace.availSize }; } std::pair getDataPhysicalAddressRange() { auto partitionData = internal::getCurrentRamPartitionData(); return { partitionData->addressSpace.dataStart, partitionData->addressSpace.dataSize }; } std::pair getVirtualMapAddressRange() { auto partitionData = internal::getCurrentRamPartitionData(); return { partitionData->addressSpace.virtualMapStart, partitionData->addressSpace.virtualMapSize }; } std::pair getCodeGenVirtualRange() { auto partitionData = internal::getCurrentRamPartitionData(); return { partitionData->addressSpace.codeGenView.mappings[0].vaddr, partitionData->addressSpace.codeGenView.mappings[0].size }; } static bool checkInVirtualMapRange(internal::AddressSpace *addressSpace, virt_addr addr, uint32_t size) { return addr >= addressSpace->virtualMapStart && (addr + size) < (addressSpace->virtualMapStart + addressSpace->virtualMapSize); } virt_addr allocateVirtualAddress(virt_addr addr, uint32_t size, uint32_t align) { auto partitionData = internal::getCurrentRamPartitionData(); if (addr && !checkInVirtualMapRange(&partitionData->addressSpace, addr, size)) { return virt_addr { 0 }; } if (!align) { align = PageSize; } if (align & (align - 1)) { return virt_addr { 0 }; } if (align < PageSize) { return virt_addr { 0 }; } if (!addr) { auto range = cpu::findFreeVirtualAddressInRange( { partitionData->addressSpace.virtualMapStart, partitionData->addressSpace.virtualMapSize }, size, align); addr = range.start; size = range.size; } if (!cpu::allocateVirtualAddress(addr, size)) { return virt_addr { 0 }; } return addr; } bool freeVirtualAddress(virt_addr addr, uint32_t size) { auto partitionData = internal::getCurrentRamPartitionData(); if (!checkInVirtualMapRange(&partitionData->addressSpace, addr, size)) { return false; } return cpu::freeVirtualAddress(addr, size); } QueryMemoryResult queryVirtualAddress(cpu::VirtualAddress addr) { auto partitionData = internal::getCurrentRamPartitionData(); if (!checkInVirtualMapRange(&partitionData->addressSpace, addr, 0)) { return QueryMemoryResult::Invalid; } switch (cpu::queryVirtualAddress(addr)) { case cpu::VirtualMemoryType::MappedReadOnly: return QueryMemoryResult::MappedReadOnly; case cpu::VirtualMemoryType::MappedReadWrite: return QueryMemoryResult::MappedReadWrite; case cpu::VirtualMemoryType::Free: return QueryMemoryResult::Free; case cpu::VirtualMemoryType::Allocated: return QueryMemoryResult::Allocated; default: return QueryMemoryResult::Invalid; } } bool mapMemory(virt_addr virtAddr, phys_addr physAddr, uint32_t size, MapMemoryPermission permission) { auto partitionData = internal::getCurrentRamPartitionData(); if (!checkInVirtualMapRange(&partitionData->addressSpace, virtAddr, size)) { return false; } auto cpuPermission = cpu::MapPermission { }; if (permission == MapMemoryPermission::ReadOnly) { cpuPermission = cpu::MapPermission::ReadOnly; } else if (permission == MapMemoryPermission::ReadWrite) { cpuPermission = cpu::MapPermission::ReadWrite; } else { gLog->error("Unexpected mapMemory permission: {}", static_cast(permission)); return false; } return cpu::mapMemory(virtAddr, physAddr, size, cpuPermission); } bool unmapMemory(cpu::VirtualAddress addr, uint32_t size) { auto partitionData = internal::getCurrentRamPartitionData(); if (!checkInVirtualMapRange(&partitionData->addressSpace, addr, size)) { return false; } return cpu::unmapMemory(addr, size); } bool validateAddressRange(virt_addr address, uint32_t size) { auto addressSpace = internal::getActiveAddressSpace(); auto view = addressSpace->perCoreView[cpu::this_core::id()]; for (auto i = 0u; i < view->numMappings; ++i) { if (address >= view->mappings[i].vaddr && (address + size) <= (view->mappings[i].vaddr + view->mappings[i].size)) { return true; } } return false; } phys_addr effectiveToPhysical(virt_addr address) { auto addressSpace = internal::getActiveAddressSpace(); auto view = addressSpace->perCoreView[cpu::this_core::id()]; if (addressSpace->overlayArenaEnabled && address >= OverlayArenaVirtualStart && address < OverlayArenaVirtualEnd) { return OverlayArenaPhysicalStart + (address - OverlayArenaVirtualStart); } // Lookup in our mappings for (auto i = 0u; i < view->numMappings; ++i) { if (address < view->mappings[i].vaddr || address >= view->mappings[i].vaddr + view->mappings[i].size) { continue; } return view->mappings[i].paddr + static_cast(address - view->mappings[i].vaddr); } // Lookup in user mappings if (checkInVirtualMapRange(addressSpace, address, 0)) { auto result = phys_addr { 0 }; if (cpu::virtualToPhysicalAddress(address, result)) { return result; } } return phys_addr { 0 }; } virt_addr physicalToEffectiveCached(phys_addr address) { auto addressSpace = internal::getActiveAddressSpace(); auto view = addressSpace->perCoreView[cpu::this_core::id()]; // Lookup in our mappings only for memory regions NOT marked as uncached for (auto i = 0u; i < view->numMappings; ++i) { if ((view->mappings[i].flags & MapUncached) || address < view->mappings[i].paddr || address >= view->mappings[i].paddr + view->mappings[i].size) { continue; } return view->mappings[i].vaddr + static_cast(address - view->mappings[i].paddr); } return virt_addr { 0 }; } virt_addr physicalToEffectiveUncached(phys_addr address) { auto addressSpace = internal::getActiveAddressSpace(); auto view = addressSpace->perCoreView[cpu::this_core::id()]; // Lookup in our mappings only for memory regions marked as uncached for (auto i = 0u; i < view->numMappings; ++i) { if (!(view->mappings[i].flags & MapUncached) || address < view->mappings[i].paddr || address >= view->mappings[i].paddr + view->mappings[i].size) { continue; } return view->mappings[i].vaddr + static_cast(address - view->mappings[i].paddr); } return virt_addr { 0 }; } namespace internal { /** * KiInitAddressSpace */ static void setAddressSpaceView(AddressSpaceView *view, MemoryMap *mappings, uint32_t numMappings, uint32_t flags) { view->numMappings = 0u; for (auto i = 0u; i < numMappings; ++i) { auto &mapping = mappings[i]; if ((mapping.flags >> 16) & flags) { view->mappings[view->numMappings++] = mapping; } } } AddressSpace * getActiveAddressSpace() { return sCoreActiveAddressSpace[cpu::this_core::id()]; } void setActiveAddressSpace(AddressSpace *addressSpace) { sCoreActiveAddressSpace[cpu::this_core::id()] = addressSpace; } bool initialiseAddressSpace(AddressSpace *addressSpace, RamPartitionId rampid, phys_addr codeStart, uint32_t codeSize, phys_addr dataStart, uint32_t dataSize, uint32_t a7, uint32_t a8, phys_addr availStart, uint32_t availSize, phys_addr codeGenStart, uint32_t codeGenSize, uint32_t codeGenCore, bool overlayArenaEnabled) { auto baseFlags = 0u; if (codeGenSize > MaxCodeGenSize) { return false; } if (codeGenSize + codeSize > MaxTotalCodeSize) { return false; } if (rampid == RamPartitionId::MainApplication) { auto overlayArenaSize = 0u; if (overlayArenaEnabled) { overlayArenaSize = 0x20000000u; } addressSpace->virtualMapStart = virt_addr { 0xA0000000 } + overlayArenaSize; addressSpace->virtualMapSize = 0x40000000u - overlayArenaSize; addressSpace->virtualPageMap.fill(0x7FFF); addressSpace->dataStart = dataStart; addressSpace->dataSize = dataSize; addressSpace->availStart = availStart; addressSpace->availSize = availSize; baseFlags |= MapMainApplication; } sMemoryMap[2].vaddr = virt_addr { 0x10000000u } - codeSize; sMemoryMap[2].paddr = codeStart; sMemoryMap[2].size = codeSize; sMemoryMap[3].paddr = dataStart; sMemoryMap[3].size = dataSize; if (codeGenSize) { auto &codeGenMap = sMemoryMap[1]; codeGenMap.size = codeGenSize; codeGenMap.paddr = codeGenStart; codeGenMap.flags |= (MapUnknown10 << 16); setAddressSpaceView(&addressSpace->codeGenView, &codeGenMap, 1, MapCodeGen | MapUnknown10); } else { // Disable codegen mapping auto &codeGenMap = sMemoryMap[1]; codeGenMap.flags = 0; } setAddressSpaceView(&addressSpace->viewKernel, sMemoryMap, sMemoryMapSize, baseFlags | MapUnknown1 | MapUnknown6 | MapUnknown10 | MapUnknown11 | MapUnknown2); setAddressSpaceView(&addressSpace->viewUnknownKernelProcess1, sMemoryMap, sMemoryMapSize, baseFlags | MapUnknown1 | MapUnknown6 | MapUnknown10 | MapUnknown11); setAddressSpaceView(&addressSpace->viewLoader, sMemoryMap, sMemoryMapSize, baseFlags | MapUnknown1 | MapUnknown6 | MapUnknown10 | MapUnknown11 | MapUnknown4); // TODO: For now we hardcode to viewLoader because we do not change views addressSpace->perCoreView.fill(&addressSpace->viewLoader); return true; } bool loadMapping(MemoryMap &mapping) { if (mapping.paddr >= phys_addr { 0x0C000000 } && mapping.paddr <= phys_addr { 0x0D000000 }) { // XXX: Physical register access is disabled for now. return true; } if (mapping.paddr == phys_addr { 0xFFE80000 }) { // XXX: Unknown memory return true; } if (!cpu::allocateVirtualAddress(mapping.vaddr, mapping.size)) { gLog->error("Unexpected failure allocating virtual address {} - {}", mapping.vaddr, mapping.vaddr + mapping.size); return false; } if (!cpu::mapMemory(mapping.vaddr, mapping.paddr, mapping.size, cpu::MapPermission::ReadWrite)) { gLog->error("Unexpected failure mapping virtual address {} to physical address {}", mapping.vaddr, mapping.paddr); return false; } return true; } bool unloadMapping(MemoryMap &mapping) { if (mapping.paddr >= phys_addr { 0x0C000000 } && mapping.paddr <= phys_addr { 0x0D000000 }) { // XXX: Physical register access is disabled for now. return true; } if (mapping.paddr == phys_addr { 0xFFE80000 }) { // XXX: Unknown memory return true; } if (!cpu::unmapMemory(mapping.vaddr, mapping.size)) { gLog->error("Unexpected failure unmapping virtual address {} from physical address {}", mapping.vaddr, mapping.paddr); return false; } if (!cpu::freeVirtualAddress(mapping.vaddr, mapping.size)) { gLog->error("Unexpected failure freeing virtual address {} - {}", mapping.vaddr, mapping.vaddr + mapping.size); return false; } return true; } bool loadAddressSpace(AddressSpace *addressSpace) { if (!cpu::resetVirtualMemory()) { decaf_abort("Unexpected failure resetting virtual memory"); } auto coreId = cpu::this_core::id(); if (coreId == cpu::InvalidCoreId) { // We have to load the kernel address space before we are running on a cpu // core, so in that case load the address space for core 1 coreId = 1; } auto view = addressSpace->perCoreView[coreId]; for (auto i = 0u; i < view->numMappings; ++i) { auto &mapping = view->mappings[i]; if (!mapping.vaddr || !mapping.size) { continue; } // FIXME: Because we do not fully emulate the mappings yet we need to // check if paddr != 0, except paddr == 0 is valid for MEM1 @ 0xF4000000 if (!mapping.paddr && mapping.vaddr != virt_addr { 0xF4000000 }) { continue; } loadMapping(mapping); } return true; } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_mmu.h ================================================ #pragma once #include "cafe_kernel_processid.h" #include #include #include #include namespace cafe::kernel { static constexpr auto PageSize = 128 * 1024u; struct MemoryMap { virt_addr vaddr; uint32_t size; phys_addr paddr; uint32_t flags; }; // This must be kept in order with cafe_kernel_mmu.cpp:sMemoryMap enum class VirtualMemoryRegion { CafeOS, CodeGenArea, AppCode, AppData, Overlay, ForegroundBucket, TilingApertures, LoaderGlobals, MEM1, LoaderBounceBuffer, SharedData, Unknown_0xFB000000, Registers_0xFC000000, Registers_0xFC0C0000, Registers_0xFC1E0000, Registers_0xFC200000, Registers_0xFC280000, WriteGatherMemory_0xFC2A0000, Registers_0xFC300000, Registers_0xFC320000, Registers_0xFD000000, Unknown_0xFE000000, KernelWorkAreaHeap, Unknown_0xFF280000, LockedCacheCore0, LockedCacheCore1, LockedCacheCore2, Unknown_0xFFCE0000, Kernel_0xFFE00000, Kernel_0xFFE40000, Kernel_0xFFE80000, Unknown_0xFFF60000, Unknown_0xFFF80000, Unknown_0xFFFA0000, Unknown_0xFFFC0000, Unknown_0xFFFE0000, Max, }; enum class MapMemoryPermission { ReadOnly = 1, ReadWrite = 2, }; enum class QueryMemoryResult { Invalid = 0, MappedReadOnly = 1, MappedReadWrite = 2, Free = 3, Allocated = 4, }; MemoryMap & getVirtualMemoryMap(VirtualMemoryRegion region); std::pair getForegroundBucket(); std::pair enableOverlayArena(); void disableOverlayArena(); std::pair getAvailablePhysicalAddressRange(); std::pair getDataPhysicalAddressRange(); std::pair getVirtualMapAddressRange(); std::pair getCodeGenVirtualRange(); virt_addr allocateVirtualAddress(virt_addr addr, uint32_t size, uint32_t align); bool freeVirtualAddress(virt_addr addr, uint32_t size); QueryMemoryResult queryVirtualAddress(cpu::VirtualAddress addr); bool mapMemory(virt_addr virtAddr, phys_addr physAddr, uint32_t size, MapMemoryPermission permission); bool unmapMemory(cpu::VirtualAddress addr, uint32_t size); bool validateAddressRange(virt_addr address, uint32_t size); phys_addr effectiveToPhysical(virt_addr address); template inline phys_ptr effectiveToPhysical(virt_ptr ptr) { return phys_cast(effectiveToPhysical(virt_cast(ptr))); } template inline phys_ptr effectiveToPhysical(be2_virt_ptr ptr) { return phys_cast(effectiveToPhysical(virt_cast(ptr))); } virt_addr physicalToEffectiveCached(phys_addr address); virt_addr physicalToEffectiveUncached(phys_addr address); namespace internal { struct AddressSpaceView { std::array mappings; uint32_t numMappings; }; struct AddressSpace { bool overlayArenaEnabled; virt_addr virtualMapStart; uint32_t virtualMapSize; phys_addr dataStart; uint32_t dataSize; phys_addr availStart; uint32_t availSize; std::array virtualPageMap; std::array perCoreView; AddressSpaceView codeGenView; AddressSpaceView viewKernel; AddressSpaceView viewUnknownKernelProcess1; AddressSpaceView viewLoader; }; AddressSpace * getActiveAddressSpace(); void setActiveAddressSpace(AddressSpace *addressSpace); bool initialiseAddressSpace(AddressSpace *map, RamPartitionId rampid, phys_addr codeStart, uint32_t codeSize, phys_addr dataStart, uint32_t dataSize, uint32_t a7, uint32_t a8, phys_addr availStart, uint32_t availSize, phys_addr codeGenStart, uint32_t codeGenSize, uint32_t codeGenCore, bool overlayArenaEnabled); bool loadAddressSpace(AddressSpace *info); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_process.cpp ================================================ #include "cafe_kernel.h" #include "cafe_kernel_loader.h" #include "cafe_kernel_mmu.h" #include "cafe_kernel_mcp.h" #include "cafe_kernel_process.h" #include "ios/mcp/ios_mcp_mcp_types.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/libraries/cafe_hle.h" #include "cafe/loader/cafe_loader_globals.h" #include "cafe/loader/cafe_loader_loaded_rpl.h" #include #include #include namespace cafe::kernel::internal { constexpr auto MinCodeSize = 0x20000u; constexpr auto MaxCodeSize = 0xE000000u; constexpr auto MinDataSize = 0x700000u; constexpr auto UnkReserveSize = 0x60000u; struct CoreProcessData { RamPartitionId rampid; UniqueProcessId upid; KernelProcessId pid; }; static std::array sCoreProcessData; static std::array sRamPartitionData; struct RamPartitionConfig { RamPartitionId id; phys_addr base; uint32_t size; }; static constexpr RamPartitionConfig DevRamPartitionConfig[] = { { RamPartitionId::Kernel, phys_addr { 0 }, 0 }, { RamPartitionId::OverlayMenu, phys_addr { 0x28000000 }, 0x8000000 }, { RamPartitionId::Root, phys_addr { 0x30000000 }, 0x2000000 }, { RamPartitionId::ErrorDisplay, phys_addr { 0x33000000 }, 0x1000000 }, { RamPartitionId::OverlayApp, phys_addr { 0x34000000 }, 0x1C000000 }, { RamPartitionId::MainApplication, phys_addr { 0x50000000 }, 0x80000000 }, }; static constexpr RamPartitionConfig RetailRamPartitionConfig[] = { { RamPartitionId::Kernel, phys_addr { 0 }, 0 }, { RamPartitionId::OverlayMenu, phys_addr { 0x28000000 }, 0x8000000 }, { RamPartitionId::Root, phys_addr { 0x30000000 }, 0x2000000 }, { RamPartitionId::ErrorDisplay, phys_addr { 0x33000000 }, 0x1000000 }, { RamPartitionId::OverlayApp, phys_addr { 0x34000000 }, 0x1C000000 }, { RamPartitionId::MainApplication, phys_addr { 0x50000000 }, 0x40000000 }, }; static const RamPartitionConfig * sRamPartitionConfig = RetailRamPartitionConfig; static void allocateRamPartition(RamPartitionId rampid, uint32_t max_size, uint32_t avail_size, uint32_t codegen_size, uint32_t max_codesize, uint32_t codegen_core, RamPartitionAllocation *info) { const RamPartitionConfig *config = nullptr; for (auto i = 0u; i < NumRamPartitions; ++i) { if (sRamPartitionConfig[i].id == rampid) { config = &sRamPartitionConfig[i]; break; } } decaf_check(config); max_codesize = std::max(max_codesize, MinCodeSize); max_codesize = std::min(max_codesize, MaxCodeSize); max_codesize = align_up(max_codesize, PageSize); avail_size = align_up(avail_size, PageSize); codegen_size = align_up(codegen_size, PageSize); decaf_check(max_codesize < max_size); decaf_check(max_codesize <= MaxCodeSize); info->dataStart = config->base; info->codeEnd = config->base + config->size; info->codeStart = align_down(info->codeEnd - max_codesize, PageSize); info->codeGenStart = align_down(info->codeStart - codegen_size, PageSize); info->availStart = align_down(info->codeGenStart - UnkReserveSize - avail_size, PageSize); info->codegen_core = codegen_core; info->overlayStart = phys_addr { 0 }; info->overlayEnd = phys_addr { 0 }; decaf_check(info->availStart - info->dataStart >= MinDataSize); } RamPartitionData * getCurrentRamPartitionData() { return getRamPartitionData(getCurrentRamPartitionId()); } RamPartitionId getCurrentRamPartitionId() { auto core = cpu::this_core::id(); if (core == cpu::InvalidCoreId) { core = 1; } return sCoreProcessData[core].rampid; } KernelProcessId getCurrentKernelProcessId() { auto core = cpu::this_core::id(); if (core == cpu::InvalidCoreId) { core = 1; } return getCurrentRamPartitionData()->coreKernelProcessId[core]; } UniqueProcessId getCurrentUniqueProcessId() { return getCurrentRamPartitionData()->uniqueProcessId; } ios::mcp::MCPTitleId getCurrentTitleId() { return getCurrentRamPartitionData()->titleInfo.titleId; } RamPartitionData * getRamPartitionData(RamPartitionId id) { return &sRamPartitionData[static_cast(id)]; } void setCoreToProcessId(RamPartitionId ramPartitionId, KernelProcessId kernelProcessId) { auto coreId = cpu::this_core::id(); auto partitionData = getRamPartitionData(ramPartitionId); // Check if we need to set a new ram ramPartitionId if (sCoreProcessData[coreId].rampid != ramPartitionId) { if (coreId == 1) { // TODO: When we have per-core address space then do not check coreId loadAddressSpace(&partitionData->addressSpace); } setActiveAddressSpace(&partitionData->addressSpace); sCoreProcessData[coreId].rampid = ramPartitionId; } partitionData->coreKernelProcessId[coreId] = kernelProcessId; } static void initialisePerCoreStartInfo(ProcessPerCoreStartInfo *core0, ProcessPerCoreStartInfo *core1, ProcessPerCoreStartInfo *core2) { auto partitionData = getCurrentRamPartitionData(); auto stackSize0 = std::max(0x4000u, partitionData->startInfo.stackSize); auto stackSize1 = std::max(0x4000u, partitionData->startInfo.stackSize); auto stackSize2 = std::max(0x4000u, partitionData->startInfo.stackSize); auto exceptionStackSize0 = 0x1000u; auto exceptionStackSize1 = 0x1000u; auto exceptionStackSize2 = 0x1000u; core0->entryPoint = partitionData->startInfo.entryPoint; core1->entryPoint = partitionData->startInfo.entryPoint; core2->entryPoint = partitionData->startInfo.entryPoint; core0->sdaBase = partitionData->startInfo.sdaBase; core1->sdaBase = partitionData->startInfo.sdaBase; core2->sdaBase = partitionData->startInfo.sdaBase; core0->sda2Base = partitionData->startInfo.sda2Base; core1->sda2Base = partitionData->startInfo.sda2Base; core2->sda2Base = partitionData->startInfo.sda2Base; core0->unk0x1C = partitionData->startInfo.unk0x10; core1->unk0x1C = partitionData->startInfo.unk0x10; core2->unk0x1C = partitionData->startInfo.unk0x10; core0->stackEnd = align_up(partitionData->startInfo.dataAreaStart, 8); core1->stackEnd = align_up(core0->stackEnd + stackSize0, 8); core2->stackEnd = align_up(core1->stackEnd + stackSize1, 8); core0->stackBase = align_down(core0->stackEnd + stackSize0, 8) - 8; core1->stackBase = align_down(core1->stackEnd + stackSize1, 8) - 8; core2->stackBase = align_down(core2->stackEnd + stackSize2, 8) - 8; core0->exceptionStackEnd = align_up(core2->stackEnd + stackSize2, 8); core1->exceptionStackEnd = align_up(core0->exceptionStackEnd + exceptionStackSize0, 8); core2->exceptionStackEnd = align_up(core1->exceptionStackEnd + exceptionStackSize1, 8); core0->exceptionStackBase = align_down(core0->exceptionStackEnd + exceptionStackSize0, 8) - 8; core1->exceptionStackBase = align_down(core1->exceptionStackEnd + exceptionStackSize1, 8) - 8; core2->exceptionStackBase = align_down(core2->exceptionStackEnd + exceptionStackSize2, 8) - 8; auto stacksEnd = align_up(core2->exceptionStackEnd + exceptionStackSize2, 8); decaf_check(stacksEnd > partitionData->startInfo.dataAreaStart && stacksEnd <= partitionData->startInfo.dataAreaEnd); partitionData->startInfo.dataAreaStart = stacksEnd; } loader::RPL_STARTINFO * getCurrentRamPartitionStartInfo() { return &getCurrentRamPartitionData()->startInfo; } void loadGameProcess(std::string_view rpx, virt_ptr titleInfo) { auto rampid = RamPartitionId::MainApplication; auto partitionData = getRamPartitionData(rampid); partitionData->argstr = virt_addrof(titleInfo->argstr).get(); partitionData->uniqueProcessId = UniqueProcessId::Game; partitionData->titleId = titleInfo->titleId; std::memcpy(&partitionData->titleInfo, titleInfo.get(), sizeof(ios::mcp::MCPPPrepareTitleInfo)); // Initialise RAM partition allocateRamPartition(rampid, titleInfo->max_size, titleInfo->avail_size, titleInfo->codegen_size, titleInfo->max_codesize, titleInfo->codegen_core, &partitionData->ramPartitionAllocation); partitionData->overlayArenaEnabled = false; if (rampid == RamPartitionId::MainApplication) { partitionData->overlayArenaEnabled = !!titleInfo->overlay_arena; } initialiseAddressSpace( &partitionData->addressSpace, rampid, partitionData->ramPartitionAllocation.codeStart, static_cast(partitionData->ramPartitionAllocation.codeEnd - partitionData->ramPartitionAllocation.codeStart), partitionData->ramPartitionAllocation.dataStart, static_cast(partitionData->ramPartitionAllocation.availStart - partitionData->ramPartitionAllocation.dataStart), 0, 0, partitionData->ramPartitionAllocation.availStart, static_cast(partitionData->ramPartitionAllocation.codeGenStart - partitionData->ramPartitionAllocation.availStart), partitionData->ramPartitionAllocation.codeGenStart, static_cast(partitionData->ramPartitionAllocation.codeStart - partitionData->ramPartitionAllocation.codeGenStart), partitionData->ramPartitionAllocation.codegen_core, partitionData->overlayArenaEnabled); // Switch to the kernel process setCoreToProcessId(rampid, KernelProcessId::Loader); initialiseCoreProcess(cpu::this_core::id(), rampid, UniqueProcessId::Game, KernelProcessId::Loader); // Switch to the IOS MCP title mcpSwitchTitle(rampid, partitionData->ramPartitionAllocation.dataStart, partitionData->ramPartitionAllocation.codeGenStart, partitionData->ramPartitionAllocation.codeEnd); // Run the loader auto num_codearea_heap_blocks = titleInfo->num_codearea_heap_blocks; if (!num_codearea_heap_blocks) { num_codearea_heap_blocks = 256u; } auto num_workarea_heap_blocks = titleInfo->num_workarea_heap_blocks; if (!num_workarea_heap_blocks) { num_workarea_heap_blocks = 512u; } cafe::loader::setLoadRpxName(rpx); KiRPLStartup( cafe::kernel::UniqueProcessId::Kernel, cafe::kernel::UniqueProcessId::Game, cafe::kernel::ProcessFlags::get(0).debugLevel(cafe::kernel::DebugLevel::Verbose), num_codearea_heap_blocks + num_workarea_heap_blocks, static_cast(partitionData->ramPartitionAllocation.codeEnd - partitionData->ramPartitionAllocation.codeStart), static_cast(partitionData->ramPartitionAllocation.availStart - partitionData->ramPartitionAllocation.dataStart), 0); // Notify jit of read only sections in the RPX if (cpu::config()->jit.rodataReadOnly) { auto loadedRpx = cafe::loader::getGlobalStorage()->loadedRpx; auto shStrSection = virt_ptr { nullptr }; if (auto shstrndx = loadedRpx->elfHeader.shstrndx) { shStrSection = virt_cast(loadedRpx->sectionAddressBuffer[shstrndx]); } for (auto i = 0u; i < loadedRpx->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast( virt_cast(loadedRpx->sectionHeaderBuffer) + (i * loadedRpx->elfHeader.shentsize)); auto sectionAddress = loadedRpx->sectionAddressBuffer[i]; if (!sectionAddress || sectionHeader->type != loader::rpl::SHT_PROGBITS) { continue; } if (shStrSection && sectionHeader->name) { auto name = shStrSection + sectionHeader->name; if (strcmp(name.get(), ".rodata") == 0) { cpu::addJitReadOnlyRange(sectionAddress.getAddress(), sectionHeader->size); continue; } } if (!(sectionHeader->flags & loader::rpl::SHF_WRITE)) { // TODO: Fix me // When we have a small section, e.g. .syscall section with // sectionHeader->size == 8, we seem to break binrec //cpu::addJitReadOnlyRange(sectionAddress.getAddress(), // sectionHeader->size); } } } // Run the HLE relocation for coreinit. auto &startInfo = cafe::loader::getKernelIpcStorage()->startInfo; auto coreinitRpl = startInfo.coreinit; cafe::hle::relocateLibrary( std::string_view { coreinitRpl->moduleNameBuffer.get(), coreinitRpl->moduleNameLen }, virt_cast(coreinitRpl->textBuffer), virt_cast(coreinitRpl->dataBuffer) ); } /** * KiProcess_FinishInitAndPreload */ void finishInitAndPreload() { auto partitionData = getCurrentRamPartitionData(); auto loaderIpcStorage = cafe::loader::getKernelIpcStorage(); partitionData->startInfo = loaderIpcStorage->startInfo; partitionData->loadedRpx = loaderIpcStorage->rpxModule; partitionData->loadedModuleList = loaderIpcStorage->loadedModuleList; initialisePerCoreStartInfo(&partitionData->perCoreStartInfo[0], &partitionData->perCoreStartInfo[1], &partitionData->perCoreStartInfo[2]); // KiProcess_Launch using EntryPointFn = virt_func_ptr; auto entryPoint = partitionData->perCoreStartInfo[cpu::this_core::id()].entryPoint; cafe::invoke(cpu::this_core::state(), virt_func_cast(entryPoint)); } void initialiseCoreProcess(int coreId, RamPartitionId rampid, UniqueProcessId upid, KernelProcessId pid) { auto &data = sCoreProcessData[coreId]; data.rampid = rampid; data.upid = upid; data.pid = pid; } void initialiseProcessData() { for (auto i = 0u; i < sRamPartitionData.size(); ++i) { auto &data = sRamPartitionData[i]; // data.state = 0 // data.field_0 = -1 data.ramPartitionId = static_cast(i); data.coreKernelProcessId[0] = KernelProcessId::Invalid; data.coreKernelProcessId[1] = KernelProcessId::Invalid; data.coreKernelProcessId[2] = KernelProcessId::Invalid; } } } // namespace cafe::kernel::internal namespace cafe::kernel { void exitProcess(int code) { auto partitionData = internal::getCurrentRamPartitionData(); partitionData->exitCode = code; internal::exit(); } int getProcessExitCode(RamPartitionId rampid) { return internal::getRamPartitionData(rampid)->exitCode; } } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_process.h ================================================ #pragma once #include "cafe/kernel/cafe_kernel_mmu.h" #include "cafe/kernel/cafe_kernel_processid.h" #include "cafe/loader/cafe_loader_init.h" #include "ios/mcp/ios_mcp_mcp_types.h" #include #include #include namespace cafe::loader { struct RPL_STARTINFO; }; namespace cafe::kernel { void exitProcess(int code); int getProcessExitCode(RamPartitionId rampid); namespace internal { struct ProcessPerCoreStartInfo { UNKNOWN(0x8); be2_val stackBase; be2_val stackEnd; be2_val sda2Base; be2_val sdaBase; UNKNOWN(0x4); be2_val unk0x1C; be2_val entryPoint; be2_val exceptionStackBase; be2_val exceptionStackEnd; }; CHECK_OFFSET(ProcessPerCoreStartInfo, 0x08, stackBase); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x0C, stackEnd); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x10, sda2Base); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x14, sdaBase); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x1C, unk0x1C); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x20, entryPoint); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x24, exceptionStackBase); CHECK_OFFSET(ProcessPerCoreStartInfo, 0x28, exceptionStackEnd); CHECK_SIZE(ProcessPerCoreStartInfo, 0x2C); struct RamPartitionAllocation { phys_addr dataStart; phys_addr availStart; phys_addr codeGenStart; phys_addr codeStart; phys_addr codeEnd; uint32_t codegen_core; phys_addr overlayStart; phys_addr overlayEnd; }; struct RamPartitionData { UniqueProcessId uniqueProcessId; RamPartitionId ramPartitionId; ios::mcp::MCPTitleId titleId; std::array coreKernelProcessId; loader::RPL_STARTINFO startInfo; internal::AddressSpace addressSpace; RamPartitionAllocation ramPartitionAllocation; std::string argstr; virt_ptr loadedRpx; virt_ptr loadedModuleList; ios::mcp::MCPPPrepareTitleInfo titleInfo; bool overlayArenaEnabled; std::array perCoreStartInfo; int exitCode; }; // CHECK_OFFSET(RamPartitionData, 0x04, rampId); // CHECK_OFFSET(RamPartitionData, 0x08, titleId); // CHECK_OFFSET(RamPartitionData, 0x10, sdkVersion); // CHECK_OFFSET(RamPartitionData, 0x14, titleVersion); // CHECK_OFFSET(RamPartitionData, 0x18, state); // CHECK_OFFSET(RamPartitionData, 0x1C, perCoreUniqueProcessId); // CHECK_OFFSET(RamPartitionData, 0x28, startInfo); // CHECK_OFFSET(RamPartitionData, 0x50, addressSpace); // CHECK_OFFSET(RamPartitionData, 0xE58, cmdFlags); // // CHECK_OFFSET(RamPartitionData, 0xE80, ramPartitionAllocation); // CHECK_OFFSET(RamPartitionData, 0xEA0, numCodeAreaHeapBlocks); // CHECK_OFFSET(RamPartitionData, 0xEA4, argstr); // CHECK_OFFSET(RamPartitionData, 0xEA8, loadedRpx); // CHECK_OFFSET(RamPartitionData, 0xEAC, loadedModuleList); // // CHECK_OFFSET(RamPartitionData, 0xECC, titleInfo); // // CHECK_OFFSET(RamPartitionData, 0x1118, userExceptionHandlersCore0); // CHECK_OFFSET(RamPartitionData, 0x11CC, userExceptionHandlersCore1); // CHECK_OFFSET(RamPartitionData, 0x1180, userExceptionHandlersCore2); // // CHECK_OFFSET(RamPartitionData, 0x1344, titleLoc); // CHECK_OFFSET(RamPartitionData, 0x1348, overlay_arena); // // CHECK_OFFSET(RamPartitionData, 0x1698, perCoreStartInfo); // CHECK_SIZE(RamPartitionData, 0x17a0); RamPartitionData * getCurrentRamPartitionData(); RamPartitionId getCurrentRamPartitionId(); KernelProcessId getCurrentKernelProcessId(); UniqueProcessId getCurrentUniqueProcessId(); ios::mcp::MCPTitleId getCurrentTitleId(); loader::RPL_STARTINFO * getCurrentRamPartitionStartInfo(); RamPartitionData * getRamPartitionData(RamPartitionId id); void setCoreToProcessId(RamPartitionId ramPartitionId, KernelProcessId kernelProcessId); void loadGameProcess(std::string_view rpx, virt_ptr titleInfo); void finishInitAndPreload(); void initialiseCoreProcess(int coreId, RamPartitionId rampid, UniqueProcessId upid, KernelProcessId pid); void initialiseProcessData(); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_processid.cpp ================================================ #include "cafe_kernel_processid.h" namespace cafe::kernel { RamPartitionId getRamPartitionIdFromUniqueProcessId(UniqueProcessId id) { switch (id) { case UniqueProcessId::Kernel: return RamPartitionId::Kernel; case UniqueProcessId::Root: return RamPartitionId::Root; case UniqueProcessId::HomeMenu: return RamPartitionId::MainApplication; case UniqueProcessId::TV: return RamPartitionId::OverlayApp; case UniqueProcessId::EManual: return RamPartitionId::OverlayApp; case UniqueProcessId::OverlayMenu: return RamPartitionId::OverlayMenu; case UniqueProcessId::ErrorDisplay: return RamPartitionId::ErrorDisplay; case UniqueProcessId::MiniMiiverse: return RamPartitionId::OverlayApp; case UniqueProcessId::InternetBrowser: return RamPartitionId::OverlayApp; case UniqueProcessId::Miiverse: return RamPartitionId::OverlayApp; case UniqueProcessId::EShop: return RamPartitionId::OverlayApp; case UniqueProcessId::FLV: return RamPartitionId::OverlayApp; case UniqueProcessId::DownloadManager: return RamPartitionId::OverlayApp; case UniqueProcessId::Game: return RamPartitionId::MainApplication; default: decaf_abort(fmt::format("Unknown UniqueProcessId {}", static_cast(id))); } } } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_processid.h ================================================ #pragma once #include #include namespace cafe::kernel { enum class UniqueProcessId : int32_t { Invalid = -1, Kernel = 0, Root = 1, HomeMenu = 2, TV = 3, EManual = 4, OverlayMenu = 5, ErrorDisplay = 6, MiniMiiverse = 7, InternetBrowser = 8, Miiverse = 9, EShop = 10, FLV = 11, DownloadManager = 12, Game = 15, }; constexpr auto NumRamPartitions = 8; enum class RamPartitionId : int32_t { Invalid = -1, Kernel = 0, Root = 1, Loader = 2, OverlayApp = 4, OverlayMenu = 5, ErrorDisplay = 6, MainApplication = 7, }; enum class KernelProcessId : int32_t { Invalid = -1, Kernel = 0, Loader = 2, }; enum class DebugLevel : uint32_t { Error = 0, Warn = 1, Info = 2, Notice = 3, Verbose = 7, }; BITFIELD_BEG(ProcessFlags, uint32_t) BITFIELD_ENTRY(0, 1, bool, isFirstProcess); BITFIELD_ENTRY(1, 1, bool, disableSharedLibraries); BITFIELD_ENTRY(9, 3, DebugLevel, debugLevel); BITFIELD_ENTRY(12, 1, bool, unkBit12); BITFIELD_ENTRY(27, 1, bool, isDebugMode); BITFIELD_ENTRY(29, 1, bool, isColdBoot); BITFIELD_END using TitleId = uint64_t; RamPartitionId getRamPartitionIdFromUniqueProcessId(UniqueProcessId id); } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_shareddata.cpp ================================================ #include "cafe_kernel_mcp.h" #include "cafe_kernel_shareddata.h" #include "decaf_config.h" #include "decaf.h" #include #include #include namespace cafe::kernel { static SharedArea sFontChinese; static SharedArea sFontKorean; static SharedArea sFontStandard; static SharedArea sFontTaiwanese; static uint32_t loadSharedData(const char *filename, SharedArea &area, virt_addr addr) { auto fileSize = uint32_t { 0 }; auto error = internal::mcpGetFileLength(filename, &fileSize, ios::mcp::MCPFileType::SharedDataContent, 0); if (error < ios::Error::OK) { return 0; } error = internal::mcpLoadFile(filename, virt_cast(addr), fileSize, 0, ios::mcp::MCPFileType::SharedDataContent, UniqueProcessId::Kernel); if (error < ios::Error::OK) { return 0; } area.address = addr; area.size = fileSize; return fileSize; } static uint32_t loadResourcesFile(const char *filename, SharedArea &area, virt_addr addr) { auto file = std::ifstream { decaf::getResourcePath(std::string("fonts/") + filename), std::ifstream::in | std::ifstream::binary }; if (!file.is_open()) { area.size = 0u; area.address = virt_addr { 0 }; return 0; } file.seekg(0, std::ifstream::end); area.size = static_cast(file.tellg()); area.address = addr; file.seekg(0, std::ifstream::beg); file.read(virt_cast(area.address).get(), area.size); file.close(); return area.size; } void loadShared() { auto addr = virt_addr { 0xF8000000 }; // FontChinese auto size = loadSharedData("CafeCn.ttf", sFontChinese, addr); if (!size) { size = loadResourcesFile("CafeCn.ttf", sFontChinese, addr); } addr = align_up(addr + size, 0x10); // FontKorean size = loadSharedData("CafeKr.ttf", sFontKorean, addr); if (!size) { size = loadResourcesFile("CafeKr.ttf", sFontKorean, addr); } addr = align_up(addr + size, 0x10); // FontStandard size = loadSharedData("CafeStd.ttf", sFontStandard, addr); if (!size) { size = loadResourcesFile("CafeStd.ttf", sFontStandard, addr); } addr = align_up(addr + size, 0x10); // FontTaiwanese size = loadSharedData("CafeTw.ttf", sFontTaiwanese, addr); if (!size) { size = loadResourcesFile("CafeTw.ttf", sFontTaiwanese, addr); } addr = align_up(addr + size, 0x10); } SharedArea getSharedArea(SharedAreaId id) { auto area = SharedArea { }; switch (id) { case SharedAreaId::FontChinese: area = sFontChinese; break; case SharedAreaId::FontKorean: area = sFontKorean; break; case SharedAreaId::FontStandard: area = sFontStandard; break; case SharedAreaId::FontTaiwanese: area = sFontTaiwanese; break; default: area.address = virt_addr { 0 }; area.size = 0u; } return area; } } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_shareddata.h ================================================ #pragma once #include namespace cafe::kernel { enum class SharedAreaId : uint32_t { FontChinese = 0xFFCAFE01u, FontKorean = 0xFFCAFE02u, FontStandard = 0xFFCAFE03u, FontTaiwanese = 0xFFCAFE04u, }; struct SharedArea { virt_addr address; uint32_t size; }; void loadShared(); SharedArea getSharedArea(SharedAreaId id); } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_userdrivers.cpp ================================================ #include "cafe_kernel_userdrivers.h" #include "cafe_kernel_lock.h" #include "cafe_kernel_heap.h" #include "cafe_kernel_process.h" #include "cafe/cafe_stackobject.h" #include #include namespace cafe::kernel { struct UserDriver { be2_array name; be2_val ownerUpid; be2_val unk0x44; be2_virt_ptr next; }; struct StaticUserDriversData { be2_virt_ptr registeredDrivers; }; static virt_ptr sUserDriversData = nullptr; static internal::SpinLock sDriverLock { 0 }; int32_t registerUserDriver(virt_ptr name, uint32_t nameLen, virt_ptr currentUpid, virt_ptr ownerUpid) { // Get a lowercase copy of name StackArray nameCopy; string_copy(nameCopy.get(), nameCopy.size(), name.get(), std::max(nameLen, 63u)); nameCopy[63] = 0; for (auto i = 0u; i < nameCopy.size() && nameCopy[i]; ++i) { nameCopy[i] = static_cast(std::tolower(static_cast(nameCopy[i]))); } auto upid = internal::getCurrentUniqueProcessId(); if (currentUpid) { *currentUpid = upid; } internal::spinLockAcquire(sDriverLock, cpu::this_core::id() + 1); // Check if this driver has already been registered for (auto itr = sUserDriversData->registeredDrivers; itr; itr = itr->next) { if (std::strncmp(virt_addrof(itr->name).get(), nameCopy.get(), 64) == 0) { if (ownerUpid) { *ownerUpid = itr->ownerUpid; internal::spinLockRelease(sDriverLock, cpu::this_core::id() + 1); return 0; } } } // Add driver to list auto driver = virt_cast(internal::allocFromWorkArea(sizeof(UserDriver))); std::memcpy(virt_addrof(driver->name).get(), nameCopy.get(), driver->name.size()); driver->ownerUpid = upid; driver->next = sUserDriversData->registeredDrivers; sUserDriversData->registeredDrivers = driver; internal::spinLockRelease(sDriverLock, cpu::this_core::id() + 1); if (ownerUpid) { *ownerUpid = upid; } return 0; } int32_t deregisterUserDriver(virt_ptr name, uint32_t nameLen) { // Get a lowercase copy of name StackArray nameCopy; string_copy(nameCopy.get(), nameCopy.size(), name.get(), std::max(nameLen, 63u)); nameCopy[63] = 0; for (auto i = 0u; i < nameCopy.size() && nameCopy[i]; ++i) { nameCopy[i] = static_cast(std::tolower(static_cast(nameCopy[i]))); } // Remove driver with same name & upid from list auto upid = internal::getCurrentUniqueProcessId(); auto prev = virt_ptr { nullptr }; internal::spinLockAcquire(sDriverLock, cpu::this_core::id() + 1); for (auto itr = sUserDriversData->registeredDrivers; itr; itr = itr->next) { if (std::strncmp(virt_addrof(itr->name).get(), nameCopy.get(), 64) == 0) { if (itr->ownerUpid == upid) { if (prev) { prev->next = itr->next; } else { sUserDriversData->registeredDrivers = itr->next; } internal::freeToWorkArea(itr); } break; } prev = itr; } internal::spinLockRelease(sDriverLock, cpu::this_core::id() + 1); return 0; } namespace internal { void initialiseStaticUserDriversData() { sUserDriversData = allocStaticData(); } /** * These drivers are normally registered by root.rpx as part of the boot * process. We must register them here so when they are subsequently reigstered * by an application they will see the kernel driver already has been * associated with the root process and behave appropriately. * * Essentially this is a hack because we do not emulate the root.rpx process. * It's times like this where I regret not writing an LLE emulator ... :) */ void registerRootUserDrivers() { auto registerRootDriver = [](const char *name) { auto driver = virt_cast(internal::allocFromWorkArea(sizeof(UserDriver))); driver->name = name; driver->ownerUpid = UniqueProcessId::Root; driver->next = sUserDriversData->registeredDrivers; sUserDriversData->registeredDrivers = driver; }; // Registered via call from root.rpx to PADInit registerRootDriver("pad"); // Registered via call from root.rpx to WPADInit registerRootDriver("wpad"); // Registered via call from root.rpx to VPADInit registerRootDriver("vpad"); } } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/kernel/cafe_kernel_userdrivers.h ================================================ #pragma once #include "cafe_kernel_processid.h" #include namespace cafe::kernel { int32_t registerUserDriver(virt_ptr name, uint32_t nameLen, virt_ptr currentUpid, virt_ptr ownerUpid); int32_t deregisterUserDriver(virt_ptr name, uint32_t nameLen); namespace internal { void initialiseStaticUserDriversData(); void registerRootUserDrivers(); } // namespace internal } // namespace cafe::kernel ================================================ FILE: src/libdecaf/src/cafe/libraries/avm/avm.cpp ================================================ #include "avm.h" namespace cafe::avm { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::avm ================================================ FILE: src/libdecaf/src/cafe/libraries/avm/avm.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::avm { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::avm, "avm.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::avm ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle.cpp ================================================ #include "cafe_hle.h" #include "avm/avm.h" #include "camera/camera.h" #include "coreinit/coreinit.h" #include "dc/dc.h" #include "dmae/dmae.h" #include "drmapp/drmapp.h" #include "erreula/erreula.h" #include "gx2/gx2.h" #include "h264/h264.h" #include "lzma920/lzma920.h" #include "mic/mic.h" #include "nfc/nfc.h" #include "nio_prof/nio_prof.h" #include "nlibcurl/nlibcurl.h" #include "nlibnss2/nlibnss2.h" #include "nlibnss/nlibnss.h" #include "nn_acp/nn_acp.h" #include "nn_ac/nn_ac.h" #include "nn_act/nn_act.h" #include "nn_aoc/nn_aoc.h" #include "nn_boss/nn_boss.h" #include "nn_ccr/nn_ccr.h" #include "nn_cmpt/nn_cmpt.h" #include "nn_dlp/nn_dlp.h" #include "nn_ec/nn_ec.h" #include "nn_fp/nn_fp.h" #include "nn_hai/nn_hai.h" #include "nn_hpad/nn_hpad.h" #include "nn_idbe/nn_idbe.h" #include "nn_ndm/nn_ndm.h" #include "nn_nets2/nn_nets2.h" #include "nn_nfp/nn_nfp.h" #include "nn_nim/nn_nim.h" #include "nn_olv/nn_olv.h" #include "nn_pdm/nn_pdm.h" #include "nn_save/nn_save.h" #include "nn_sl/nn_sl.h" #include "nn_spm/nn_spm.h" #include "nn_temp/nn_temp.h" #include "nn_uds/nn_uds.h" #include "nn_vctl/nn_vctl.h" #include "nsysccr/nsysccr.h" #include "nsyshid/nsyshid.h" #include "nsyskbd/nsyskbd.h" #include "nsysnet/nsysnet.h" #include "nsysuhs/nsysuhs.h" #include "nsysuvd/nsysuvd.h" #include "ntag/ntag.h" #include "padscore/padscore.h" #include "proc_ui/proc_ui.h" #include "sndcore2/sndcore2.h" #include "snd_core/snd_core.h" #include "snduser2/snduser2.h" #include "snd_user/snd_user.h" #include "swkbd/swkbd.h" #include "sysapp/sysapp.h" #include "tcl/tcl.h" #include "tve/tve.h" #include "uac/uac.h" #include "uac_rpl/uac_rpl.h" #include "usb_mic/usb_mic.h" #include "uvc/uvc.h" #include "uvd/uvd.h" #include "vpadbase/vpadbase.h" #include "vpad/vpad.h" #include "zlib125/zlib125.h" #include "decaf_config.h" #include "decaf_configstorage.h" #include #include #include #include namespace cafe::hle { volatile bool FunctionTraceEnabled = false; static std::array(LibraryId::Max)> sLibraries; static void registerLibrary(Library *library) { sLibraries[static_cast(library->id())] = library; library->generate(); } void initialiseLibraries() { sLibraries.fill(nullptr); registerLibrary(new avm::Library { }); registerLibrary(new camera::Library { }); registerLibrary(new coreinit::Library { }); registerLibrary(new dc::Library { }); registerLibrary(new dmae::Library { }); registerLibrary(new drmapp::Library { }); registerLibrary(new nn_erreula::Library { }); registerLibrary(new gx2::Library { }); registerLibrary(new h264::Library { }); registerLibrary(new lzma920::Library { }); registerLibrary(new mic::Library { }); registerLibrary(new nfc::Library { }); registerLibrary(new nio_prof::Library { }); registerLibrary(new nlibcurl::Library { }); registerLibrary(new nlibnss2::Library { }); registerLibrary(new nlibnss::Library { }); registerLibrary(new nn_acp::Library { }); registerLibrary(new nn_ac::Library { }); registerLibrary(new nn_act::Library { }); registerLibrary(new nn_aoc::Library { }); registerLibrary(new nn_boss::Library { }); registerLibrary(new nn_ccr::Library { }); registerLibrary(new nn_cmpt::Library { }); registerLibrary(new nn_dlp::Library { }); registerLibrary(new nn_ec::Library { }); registerLibrary(new nn_fp::Library { }); registerLibrary(new nn_hai::Library { }); registerLibrary(new nn_hpad::Library { }); registerLibrary(new nn_idbe::Library { }); registerLibrary(new nn_ndm::Library { }); registerLibrary(new nn_nets2::Library { }); registerLibrary(new nn_nfp::Library { }); registerLibrary(new nn_nim::Library { }); registerLibrary(new nn_olv::Library { }); registerLibrary(new nn_pdm::Library { }); registerLibrary(new nn_save::Library { }); registerLibrary(new nn_sl::Library { }); registerLibrary(new nn_spm::Library { }); registerLibrary(new nn_temp::Library { }); registerLibrary(new nn_uds::Library { }); registerLibrary(new nn_vctl::Library { }); registerLibrary(new nsysccr::Library { }); registerLibrary(new nsyshid::Library { }); registerLibrary(new nsyskbd::Library { }); registerLibrary(new nsysnet::Library { }); registerLibrary(new nsysuhs::Library { }); registerLibrary(new nsysuvd::Library { }); registerLibrary(new ntag::Library { }); registerLibrary(new padscore::Library { }); registerLibrary(new proc_ui::Library { }); registerLibrary(new sndcore2::Library { }); registerLibrary(new snd_core::Library { }); registerLibrary(new snduser2::Library { }); registerLibrary(new snd_user::Library { }); registerLibrary(new swkbd::Library { }); registerLibrary(new sysapp::Library { }); registerLibrary(new tcl::Library { }); registerLibrary(new tve::Library { }); registerLibrary(new uac::Library { }); registerLibrary(new uac_rpl::Library { }); registerLibrary(new usb_mic::Library { }); registerLibrary(new uvc::Library { }); registerLibrary(new uvd::Library { }); registerLibrary(new vpadbase::Library { }); registerLibrary(new vpad::Library { }); registerLibrary(new zlib125::Library { }); // Register config change handler static std::once_flag sRegisteredConfigChangeListener; std::call_once(sRegisteredConfigChangeListener, []() { decaf::registerConfigChangeListener( [](const decaf::Settings &settings) { setTraceEnabled(settings.log.hle_trace); applyTraceFilters(settings.log.hle_trace_filters); }); }); // Apply trace config setTraceEnabled(decaf::config()->log.hle_trace); applyTraceFilters(decaf::config()->log.hle_trace_filters); } Library * getLibrary(LibraryId id) { return sLibraries[static_cast(id)]; } Library * getLibrary(std::string_view name) { for (auto library : sLibraries) { if (library && library->name() == name) { return library; } } return nullptr; } void relocateLibrary(std::string_view name, virt_addr textBaseAddress, virt_addr dataBaseAddress) { auto libraryName = std::string { name } + ".rpl"; auto library = getLibrary(libraryName); if (library) { library->relocate(textBaseAddress, dataBaseAddress); } } void applyTraceFilters(const std::vector &filterStrings) { struct TraceFilter2 { bool enable; std::regex re; }; std::vector filters; filters.reserve(filterStrings.size()); for (auto &filterString : filterStrings) { auto &filter = filters.emplace_back(); // First character is + enable or - disable if (filterString[0] == '+') { filter.enable = true; } else if (filterString[0] == '-') { filter.enable = false; } else { gLog->error(fmt::format( "Invalid trace filter {}, expected to begin with + or -", filterString)); } try { filter.re = filterString.c_str() + 1; } catch (std::regex_error e) { gLog->error(fmt::format( "Invalid trace filter {}, regex error {}", filterString, e.what())); } } auto buffer = std::string { }; buffer.reserve(512); for (auto library : sLibraries) { auto libraryNameSize = library->name().size(); buffer.insert(0, library->name()); const auto &symbolMap = library->getSymbolMap(); for (auto &[symbolName, symbol] : symbolMap) { if (symbol->type != LibrarySymbol::Function) { continue; } auto funcSymbol = static_cast(symbol.get()); // Match regex against libraryName::functionName buffer.resize(libraryNameSize); buffer += "::"; buffer += funcSymbol->name; for (auto &filter : filters) { if (std::regex_match(buffer, filter.re)) { funcSymbol->traceEnabled = filter.enable; } } } } } void setTraceEnabled(bool enabled) { FunctionTraceEnabled = enabled; } } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle.h ================================================ #pragma once #include "cafe_hle_library.h" #include #include #include #include namespace cafe::hle { void initialiseLibraries(); Library * getLibrary(LibraryId id); Library * getLibrary(std::string_view name); void relocateLibrary(std::string_view name, virt_addr textBaseAddress, virt_addr dataBaseAddress); void applyTraceFilters(const std::vector &filters); void setTraceEnabled(bool enabled); } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library.cpp ================================================ #include "cafe_hle.h" #include "cafe_hle_library.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "cafe/loader/cafe_loader_rpl.h" #include "decaf_config.h" #include #include #include #include #include #include #include #include namespace rpl = cafe::loader::rpl; namespace cafe::hle { static std::unordered_map sUnimplementedSystemCalls; static virt_ptr sUnimplementedFunctionStubMemory = nullptr; static uint32_t sUnimplementedFunctionStubPos = 0u; static uint32_t sUnimplementedFunctionStubSize = 0u; virt_addr registerUnimplementedSymbol(std::string_view module, std::string_view name) { auto libraryName = std::string { module } + ".rpl"; auto library = getLibrary(libraryName); if (!library) { return virt_addr { 0u }; } // Check if we already created an unimplemented function stub. if (auto unimpl = library->findUnimplementedFunctionExport(name)) { return unimpl->value; } // Ensure we have sufficient stub memory if (sUnimplementedFunctionStubPos + 8 >= sUnimplementedFunctionStubSize) { gLog->error("Out of stub memory for unimplemented function {}::{}", module, name); return virt_addr { 0u }; } // Create a new unimplemented function auto unimpl = new UnimplementedLibraryFunction { }; unimpl->library = library; unimpl->syscallID = cpu::registerIllegalSystemCall(); unimpl->name = name; unimpl->value = virt_cast(sUnimplementedFunctionStubMemory) + sUnimplementedFunctionStubPos; // Generate a kc, blr stub auto stub = virt_cast(unimpl->value); auto kc = espresso::encodeInstruction(espresso::InstructionID::kc); kc.kcn = unimpl->syscallID; stub[0] = kc.value; auto bclr = espresso::encodeInstruction(espresso::InstructionID::bclr); bclr.bo = 0b10100; stub[1] = bclr.value; sUnimplementedFunctionStubPos += 8; // Add to the list library->addUnimplementedFunctionExport(unimpl); sUnimplementedSystemCalls[unimpl->syscallID] = unimpl; gLog->debug("Unimplemented function import {}::{} added at {}", module, name, unimpl->value); return unimpl->value; } void setUnimplementedFunctionStubMemory(virt_ptr base, uint32_t size) { sUnimplementedFunctionStubPos = 0; sUnimplementedFunctionStubSize = size; sUnimplementedFunctionStubMemory = base; } cpu::Core * Library::handleUnknownSystemCall(cpu::Core *state, uint32_t id) { // Because we register explicit handlers for all of the valid kernel // calls, if we get an unknown kernel call it must either be one of // the unimplemented functions we registered, or something has gone // awry and we can't continue anyways... auto unimplIter = sUnimplementedSystemCalls.find(id); if (unimplIter != sUnimplementedSystemCalls.end()) { auto &unimpl = unimplIter->second; gLog->warn("Unimplemented function call {}::{} from 0x{:08X}", unimpl->library ? unimpl->library->name().c_str() : "", unimpl->name, state->lr); // Set r3 to some nonsense value to try and catch errors from // unimplemented functions sooner. state->gpr[3] = 0xC5C5C5C5u; return state; } decaf_abort(fmt::format("Unexpected kernel call {} from 0x{:08X}", id, state->lr)); } void Library::registerSystemCalls() { for (auto const &[name, symbol] : mSymbolMap) { if (symbol->type == LibrarySymbol::Function) { auto funcSymbol = static_cast(symbol.get()); auto newKcId = cpu::registerSystemCallHandler(funcSymbol->invokeHandler); funcSymbol->syscallID = newKcId; } } } void Library::generate() { registerSymbols(); if (!mTypeInfo.empty()) { RegisterFunctionInternalName("__pure_virtual_called", cafe::ghs::pure_virtual_called); RegisterFunctionInternalName("__dt__Q2_3std9type_infoFv", cafe::ghs::std_typeinfo_Destructor); } if (mEntryPointSymbolName.empty()) { RegisterGenericEntryPoint(); } registerSystemCalls(); generateRpl(); } void Library::relocate(virt_addr textBaseAddress, virt_addr dataBaseAddress) { for (auto const &[name, symbol] : mSymbolMap) { if (symbol->type == LibrarySymbol::Function) { auto funcSymbol = static_cast(symbol.get()); funcSymbol->address = textBaseAddress + funcSymbol->offset; if (funcSymbol->hostPtr) { *(funcSymbol->hostPtr) = virt_cast(funcSymbol->address); } } else if (symbol->type == LibrarySymbol::Data) { auto dataSymbol = static_cast(symbol.get()); dataSymbol->address = dataBaseAddress + dataSymbol->offset; if (dataSymbol->hostPointer) { *(dataSymbol->hostPointer) = virt_cast(dataSymbol->address); } // TODO: When we relocate for a process switch we should not call this // constructor - must differentiate between relocate for load and // relocate for process switch. if (dataSymbol->constructor) { (*dataSymbol->constructor)(virt_cast(dataSymbol->address).get()); } } } for (auto &type : mTypeInfo) { if (type.hostVirtualTablePtr) { *type.hostVirtualTablePtr = virt_cast(dataBaseAddress + type.virtualTableOffset); } if (type.hostTypeDescriptorPtr) { *type.hostTypeDescriptorPtr = virt_cast(dataBaseAddress + type.typeDescriptorOffset); } } } static uint32_t addSectionString(std::vector &data, std::string_view name, int align = 1) { auto pos = data.size(); // Insert string with null terminator data.insert(data.end(), name.begin(), name.end()); data.push_back(0); // Pad to 4 byte alignment while ((data.size() % align) != 0) { data.push_back(0); } return static_cast(pos); } static void generateTypeDescriptors(Library *library, std::vector &types, const uint32_t dataBaseAddr, std::vector &data, std::vector &relocations) { auto stdTypeInfo = LibraryTypeInfo { }; auto addRelocation = [&relocations](const uint32_t offset, const uint32_t symbol, const int32_t symbolAddend) { auto rela = rpl::Rela { }; rela.info = rpl::R_PPC_ADDR32 | (symbol << 8); rela.addend = symbolAddend; rela.offset = offset; relocations.insert(relocations.end(), reinterpret_cast(&rela), reinterpret_cast(&rela) + sizeof(rpl::Rela)); }; // Add a relocation against symbol 1 ($TEXT) auto addTextRelocation = [&addRelocation, dataBaseAddr](const uint32_t offset, const uint32_t relocationAddress) { addRelocation(offset + dataBaseAddr, 1, relocationAddress); }; // Add a relocation against symbol 2 ($DATA) auto addDataRelocation = [&addRelocation, dataBaseAddr](const uint32_t offset, const uint32_t relocationAddress) { addRelocation(offset + dataBaseAddr, 2, relocationAddress); }; auto addTypeDescriptor = [&](LibraryTypeInfo &typeInfo) { typeInfo.nameOffset = addSectionString(data, typeInfo.name, 4); LibrarySymbol *typeIdSymbol = nullptr; if (typeInfo.typeIdSymbol) { typeIdSymbol = library->findSymbol(typeInfo.typeIdSymbol); } if (typeIdSymbol) { // Use the given type id symbol typeInfo.typeIdOffset = typeIdSymbol->offset; } else { // Allocate some memory so the address acts as a unique type id typeInfo.typeIdOffset = static_cast(data.size()); data.resize(data.size() + 4); } if (!typeInfo.baseTypes.empty()) { // Reserve space for base types typeInfo.baseTypeOffset = static_cast(data.size()); data.resize(data.size() + sizeof(ghs::BaseTypeDescriptor) * typeInfo.baseTypes.size()); // Last base type flags = 0x1600 *reinterpret_cast *>(&data[data.size() - 4]) = 0x1600u; } // Insert type descriptor, all the values are filled via relocations typeInfo.typeDescriptorOffset = static_cast(data.size()); data.resize(data.size() + sizeof(ghs::TypeDescriptor)); // Create virtual table if (!typeInfo.virtualTable.empty()) { typeInfo.virtualTableOffset = static_cast(data.size()); { // First entry points to the type descriptor auto entryOffset = static_cast(data.size()); data.resize(data.size() + sizeof(ghs::VirtualTable)); addDataRelocation(entryOffset + 4, typeInfo.typeDescriptorOffset); } for (auto entry : typeInfo.virtualTable) { auto entryOffset = static_cast(data.size()); auto symbol = library->findSymbol(entry); decaf_assert(symbol, fmt::format("Could not find vtable function {}", entry)); data.resize(data.size() + sizeof(ghs::VirtualTable)); addTextRelocation(entryOffset + 4, symbol->offset); } } // Add type descriptor relocations, must be done after setting // virtualTableOffset incase typeInfo == stdTypeInfo addDataRelocation(typeInfo.typeDescriptorOffset + 0x00, stdTypeInfo.virtualTableOffset); addDataRelocation(typeInfo.typeDescriptorOffset + 0x04, typeInfo.nameOffset); addDataRelocation(typeInfo.typeDescriptorOffset + 0x08, typeInfo.typeIdOffset); if (!typeInfo.baseTypes.empty()) { addDataRelocation(typeInfo.typeDescriptorOffset + 0x0C, typeInfo.baseTypeOffset); } }; // Generate type descriptors stdTypeInfo.name = "std::type_info"; stdTypeInfo.virtualTable.push_back("__dt__Q2_3std9type_infoFv"); addTypeDescriptor(stdTypeInfo); for (auto &type : types) { addTypeDescriptor(type); } /* * Add relocations for base types. * * We do this here instead of in addTypeDescriptor so we do not restrict * ourselves to defining base types before the types that inherit them. */ auto findTypeInfo = [&types](const char *name) -> LibraryTypeInfo * { for (auto &type : types) { if (strcmp(type.name, name) == 0) { return &type; } } return nullptr; }; for (auto &type : types) { auto baseTypeOffset = type.baseTypeOffset; for (auto baseType : type.baseTypes) { auto baseTypeInfo = findTypeInfo(baseType); decaf_assert(baseTypeInfo, fmt::format("Could not find base type {}", baseType)); addDataRelocation(baseTypeOffset, baseTypeInfo->typeDescriptorOffset); baseTypeOffset += 8; } } } constexpr auto LibraryFunctionStubSize = 8u; constexpr auto CodeBaseAddress = 0x02000000u; constexpr auto DataBaseAddress = 0x10000000u; constexpr auto LoadBaseAddress = 0xC0000000u; struct Section { rpl::SectionHeader header; std::vector data; }; void Library::generateRpl() { // Build up our symbol information auto funcSymbols = std::vector { }; auto dataSymbols = std::vector { }; auto numDataExports = 0u; auto numCodeExports = 0u; auto dataSymbolsSize = 0u; auto textSymbolSize = 0u; for (auto const &[name, symbol] : mSymbolMap) { if (symbol->type == LibrarySymbol::Function) { auto funcSymbol = static_cast(symbol.get()); textSymbolSize += LibraryFunctionStubSize; if (symbol->exported) { numCodeExports++; } funcSymbols.push_back(funcSymbol); } else if (symbol->type == LibrarySymbol::Data) { auto dataSymbol = static_cast(symbol.get()); dataSymbolsSize = align_up(dataSymbolsSize, dataSymbol->align); dataSymbolsSize += dataSymbol->size; if (symbol->exported) { numDataExports++; } dataSymbols.push_back(dataSymbol); } } // Calculate required number of sections auto numSections = 1u; auto textSectionIndex = 0u; auto fexportSectionIndex = 0u; auto fexportRelaSectionIndex = 0u; auto dataSectionIndex = 0u; auto dataRelaSectionIndex = 0u; auto dexportSectionIndex = 0u; auto dexportRelaSectionIndex = 0u; auto firstImportSectionIndex = 0u; if (textSymbolSize) { textSectionIndex = numSections++; } if (numCodeExports) { fexportSectionIndex = numSections++; fexportRelaSectionIndex = numSections++; } if (dataSymbolsSize || !mTypeInfo.empty()) { dataSectionIndex = numSections++; } if (!mTypeInfo.empty()) { dataRelaSectionIndex = numSections++; } if (numDataExports) { dexportSectionIndex = numSections++; dexportRelaSectionIndex = numSections++; } if (mLibraryDependencies.size()) { firstImportSectionIndex = numSections; numSections += static_cast(mLibraryDependencies.size()); } auto symTabSectionIndex = numSections++; auto strTabSectionIndex = numSections++; auto shStrTabSectionIndex = numSections++; auto crcSectionIndex = numSections++; auto fileInfoSectionIndex = numSections++; auto sections = std::vector
{ }; sections.resize(numSections); auto textSection = sections.begin() + textSectionIndex; auto fexportSection = sections.begin() + fexportSectionIndex; auto fexportRelaSection = sections.begin() + fexportRelaSectionIndex; auto dataSection = sections.begin() + dataSectionIndex; auto dataRelaSection = sections.begin() + dataRelaSectionIndex; auto dexportSection = sections.begin() + dexportSectionIndex; auto dexportRelaSection = sections.begin() + dexportRelaSectionIndex; auto firstImportSection = sections.begin() + firstImportSectionIndex; auto symTabSection = sections.begin() + symTabSectionIndex; auto strTabSection = sections.begin() + strTabSectionIndex; auto shStrTabSection = sections.begin() + shStrTabSectionIndex; auto crcSection = sections.begin() + crcSectionIndex; auto fileInfoSection = sections.begin() + fileInfoSectionIndex; auto loadAddr = LoadBaseAddress; // Add empty string to string sections shStrTabSection->data.push_back(0); strTabSection->data.push_back(0); // Generate .text if (textSectionIndex) { textSection->header.name = addSectionString(shStrTabSection->data, ".text"); textSection->header.type = rpl::SHT_PROGBITS; textSection->header.flags = rpl::SHF_EXECINSTR | rpl::SHF_ALLOC; textSection->header.addralign = 32u; textSection->header.addr = CodeBaseAddress; textSection->header.offset = 0u; textSection->header.size = 0u; textSection->header.link = 0u; textSection->header.info = 0u; textSection->header.entsize = 0u; } // Generate .fexports if (fexportSectionIndex) { fexportSection->header.name = addSectionString(shStrTabSection->data, ".fexports"); fexportSection->header.type = rpl::SHT_RPL_EXPORTS; fexportSection->header.flags = rpl::SHF_EXECINSTR | rpl::SHF_ALLOC; fexportSection->header.addralign = 4u; fexportSection->header.offset = 0u; fexportSection->header.addr = align_up(loadAddr, fexportSection->header.addralign); fexportSection->header.size = 8u + static_cast(sizeof(rpl::Export) * numCodeExports); fexportSection->header.link = 0u; fexportSection->header.info = 0u; fexportSection->header.entsize = 0u; fexportSection->data.resize(fexportSection->header.size); auto exports = rpl::Exports { }; exports.count = numCodeExports; exports.signature = 0u; std::memcpy(fexportSection->data.data(), &exports, sizeof(rpl::Exports)); } // Generate .rela.fexports if (fexportRelaSectionIndex) { fexportRelaSection->header.name = addSectionString(shStrTabSection->data, ".rela.fexports"); fexportRelaSection->header.type = rpl::SHT_RELA; fexportRelaSection->header.flags = 0u; fexportRelaSection->header.addralign = 4u; fexportRelaSection->header.addr = 0u; fexportRelaSection->header.offset = 0u; fexportRelaSection->header.size = static_cast(sizeof(rpl::Rela) * numCodeExports); fexportRelaSection->header.link = symTabSectionIndex; fexportRelaSection->header.info = fexportSectionIndex; fexportRelaSection->header.entsize = static_cast(sizeof(rpl::Rela)); fexportRelaSection->data.resize(fexportRelaSection->header.size); } if (textSymbolSize) { auto exportIdx = 0u; auto textOffset = static_cast(textSection->data.size()); textSection->data.resize(textSection->data.size() + textSymbolSize); for (auto &symbol : funcSymbols) { // Write syscall thunk auto kc = espresso::encodeInstruction(espresso::InstructionID::kc); kc.kcn = symbol->syscallID; auto bclr = espresso::encodeInstruction(espresso::InstructionID::bclr); bclr.bo = 20; bclr.bi = 0; auto thunk = reinterpret_cast *>(textSection->data.data() + textOffset); *(thunk + 0) = kc.value; *(thunk + 1) = bclr.value; if (symbol->exported) { auto exportOffset = 8u + (sizeof(rpl::Export) * exportIdx); auto relaOffset = sizeof(rpl::Rela) * exportIdx; // Write to .fexport auto fexport = rpl::Export { }; fexport.name = addSectionString(fexportSection->data, symbol->name); fexport.value = textSection->header.addr + textOffset; std::memcpy(fexportSection->data.data() + exportOffset, &fexport, sizeof(rpl::Export)); // Write to .rela.fexport auto rela = rpl::Rela { }; rela.info = rpl::R_PPC_ADDR32 | (symbol->index << 8); rela.addend = 0; rela.offset = static_cast(fexportSection->header.addr + exportOffset); std::memcpy(fexportRelaSection->data.data() + relaOffset, &rela, sizeof(rpl::Rela)); ++exportIdx; } symbol->offset = textOffset; textOffset += LibraryFunctionStubSize; } if (fexportSectionIndex) { // Update loadAddr loadAddr = fexportSection->header.addr + static_cast(fexportSection->data.size()); } } // Generate .data if (dataSectionIndex) { dataSection->header.name = addSectionString(shStrTabSection->data, ".data"); dataSection->header.type = rpl::SHT_PROGBITS; dataSection->header.flags = rpl::SHF_WRITE | rpl::SHF_ALLOC; dataSection->header.addralign = 32u; dataSection->header.addr = DataBaseAddress; dataSection->header.offset = 0u; dataSection->header.size = 0u; dataSection->header.link = 0u; dataSection->header.info = 0u; dataSection->header.entsize = 0u; } // Generate .rela.data if (dataRelaSectionIndex) { dataRelaSection->header.name = addSectionString(shStrTabSection->data, ".rela.data"); dataRelaSection->header.type = rpl::SHT_RELA; dataRelaSection->header.flags = 0u; dataRelaSection->header.addralign = 4u; dataRelaSection->header.addr = 0u; dataRelaSection->header.offset = 0u; dataRelaSection->header.size = 0u; dataRelaSection->header.link = symTabSectionIndex; dataRelaSection->header.info = dataSectionIndex; dataRelaSection->header.entsize = static_cast(sizeof(rpl::Rela)); } // Generate .dexports if (dexportSectionIndex) { dexportSection->header.name = addSectionString(shStrTabSection->data, ".dexports"); dexportSection->header.type = rpl::SHT_RPL_EXPORTS; dexportSection->header.flags = rpl::SHF_ALLOC; dexportSection->header.addralign = 4u; dexportSection->header.offset = 0u; dexportSection->header.addr = align_up(loadAddr, dexportSection->header.addralign); dexportSection->header.size = 8u + static_cast(sizeof(rpl::Export) * numDataExports); dexportSection->header.link = 0u; dexportSection->header.info = 0u; dexportSection->header.entsize = 0u; dexportSection->data.resize(dexportSection->header.size); auto exports = rpl::Exports { }; exports.count = numDataExports; exports.signature = 0u; std::memcpy(dexportSection->data.data(), &exports, sizeof(rpl::Exports)); } // Generate .rela.dexports if (dexportRelaSectionIndex) { dexportRelaSection->header.name = addSectionString(shStrTabSection->data, ".rela.dexports"); dexportRelaSection->header.type = rpl::SHT_RELA; dexportRelaSection->header.flags = 0u; dexportRelaSection->header.addralign = 4u; dexportRelaSection->header.addr = 0u; dexportRelaSection->header.offset = 0u; dexportRelaSection->header.size = static_cast(sizeof(rpl::Rela) * numDataExports); dexportRelaSection->header.link = symTabSectionIndex; dexportRelaSection->header.info = dexportSectionIndex; dexportRelaSection->header.entsize = static_cast(sizeof(rpl::Rela)); dexportRelaSection->data.resize(dexportRelaSection->header.size); } // Write symbols and exports if (dataSymbolsSize) { auto exportIdx = 0; auto dataOffset = static_cast(dataSection->data.size()); dataSection->data.resize(dataSection->data.size() + dataSymbolsSize); for (auto &symbol : dataSymbols) { dataOffset = align_up(dataOffset, symbol->align); if (symbol->exported) { auto exportOffset = 8 + (sizeof(rpl::Export) * exportIdx); auto relaOffset = sizeof(rpl::Rela) * exportIdx; // Write to .dexport auto dexport = rpl::Export { }; dexport.name = addSectionString(dexportSection->data, symbol->name); dexport.value = dataSection->header.addr + dataOffset; std::memcpy(dexportSection->data.data() + exportOffset, &dexport, sizeof(rpl::Export)); // Write to .rela.dexport auto rela = rpl::Rela { }; rela.info = rpl::R_PPC_ADDR32 | (symbol->index << 8); rela.addend = 0; rela.offset = static_cast(dexportSection->header.addr + exportOffset); std::memcpy(dexportRelaSection->data.data() + relaOffset, &rela, sizeof(rpl::Rela)); ++exportIdx; } symbol->offset = dataOffset; dataOffset += symbol->size; } decaf_check(dataOffset == dataSymbolsSize); if (dexportSectionIndex) { // Update loadAddr loadAddr = dexportSection->header.addr + static_cast(dexportSection->data.size()); } } // Generate .fimport_{} if (firstImportSectionIndex) { for (auto i = 0u; i < mLibraryDependencies.size(); ++i) { auto importSection = firstImportSection + i; importSection->header.name = addSectionString(shStrTabSection->data, fmt::format(".fimport_{}", mLibraryDependencies[i])); importSection->header.type = rpl::SHT_RPL_IMPORTS; importSection->header.flags = rpl::SHF_ALLOC | rpl::SHF_EXECINSTR; importSection->header.addralign = 4u; importSection->header.offset = 0u; importSection->header.addr = align_up(loadAddr, importSection->header.addralign); importSection->header.size = 8u; importSection->header.link = 0u; importSection->header.info = 0u; importSection->header.entsize = 0u; importSection->data.resize(importSection->header.size); auto imports = rpl::Imports { }; imports.count = 0u; imports.signature = 0u; std::memcpy(importSection->data.data(), &imports, sizeof(rpl::Imports)); loadAddr = importSection->header.addr + static_cast(importSection->data.size()); } } if (!mTypeInfo.empty()) { generateTypeDescriptors(this, mTypeInfo, dataSection->header.addr, dataSection->data, dataRelaSection->data); } // Generate symtab auto symbolCount = funcSymbols.size() + dataSymbols.size() + cafe::hle::Library::BaseSymbolIndex; symTabSection->data.resize(sizeof(rpl::Symbol) * symbolCount); auto getSymbol = [&symTabSection](uint32_t index) { return reinterpret_cast(symTabSection->data.data() + (index * sizeof(rpl::Symbol))); }; // Generate NULL symbol { auto nullSymbol = getSymbol(0); std::memset(nullSymbol, 0, sizeof(rpl::Symbol)); } // Generate $TEXT symbol { auto textSymbol = getSymbol(1); textSymbol->name = addSectionString(strTabSection->data, "$TEXT"); textSymbol->value = CodeBaseAddress; textSymbol->size = 0u; textSymbol->info = static_cast(rpl::STT_SECTION | (rpl::STB_LOCAL << 4)); textSymbol->shndx = static_cast(textSectionIndex); textSymbol->other = uint8_t { 0 }; } // Generate $DATA symbol { auto dataSymbol = getSymbol(2); dataSymbol->name = addSectionString(strTabSection->data, "$DATA"); dataSymbol->value = DataBaseAddress; dataSymbol->size = 0u; dataSymbol->info = static_cast(rpl::STT_SECTION | (rpl::STB_LOCAL << 4)); dataSymbol->shndx = static_cast(dataSectionIndex); dataSymbol->other = uint8_t { 0 }; } for (auto &dataSymbol : dataSymbols) { auto symbol = getSymbol(dataSymbol->index); auto binding = dataSymbol->exported ? rpl::STB_GLOBAL : rpl::STB_LOCAL; symbol->name = addSectionString(strTabSection->data, dataSymbol->name); symbol->value = dataSymbol->offset + dataSection->header.addr; symbol->size = dataSymbol->size; symbol->info = static_cast(rpl::STT_OBJECT | (binding << 4)); symbol->shndx = static_cast(dataSectionIndex); symbol->other = uint8_t { 0 }; } for (auto &funcSymbol : funcSymbols) { auto symbol = getSymbol(funcSymbol->index); auto binding = funcSymbol->exported ? rpl::STB_GLOBAL : rpl::STB_LOCAL; symbol->name = addSectionString(strTabSection->data, funcSymbol->name); symbol->value = funcSymbol->offset + textSection->header.addr; symbol->size = LibraryFunctionStubSize; symbol->info = static_cast(rpl::STT_FUNC | (binding << 4)); symbol->shndx = static_cast(textSectionIndex); symbol->other = uint8_t { 0 }; } symTabSection->header.name = addSectionString(shStrTabSection->data, ".symtab"); symTabSection->header.type = rpl::SHT_SYMTAB; symTabSection->header.flags = rpl::SHF_ALLOC; symTabSection->header.addralign = 4u; symTabSection->header.addr = align_up(loadAddr, symTabSection->header.addralign); symTabSection->header.offset = 0u; // Set later symTabSection->header.size = static_cast(symTabSection->data.size()); symTabSection->header.link = strTabSectionIndex; symTabSection->header.info = 1u; symTabSection->header.entsize = static_cast(sizeof(rpl::Symbol)); loadAddr = symTabSection->header.addr + symTabSection->header.size; // Generate strtab strTabSection->header.name = addSectionString(shStrTabSection->data, ".strtab"); strTabSection->header.type = rpl::SHT_STRTAB; strTabSection->header.flags = rpl::SHF_ALLOC; strTabSection->header.addralign = 4u; strTabSection->header.addr = align_up(loadAddr, strTabSection->header.addralign); strTabSection->header.offset = 0u; // Set later strTabSection->header.size = static_cast(strTabSection->data.size()); strTabSection->header.link = 0u; strTabSection->header.info = 0u; strTabSection->header.entsize = 0u; loadAddr = strTabSection->header.addr + strTabSection->header.size; // Generate shstrtab shStrTabSection->header.name = addSectionString(shStrTabSection->data, ".shstrtab"); shStrTabSection->header.type = rpl::SHT_STRTAB; shStrTabSection->header.flags = rpl::SHF_ALLOC; shStrTabSection->header.addralign = 4u; shStrTabSection->header.addr = align_up(loadAddr, shStrTabSection->header.addralign); shStrTabSection->header.offset = 0u; // Set later shStrTabSection->header.size = static_cast(shStrTabSection->data.size()); shStrTabSection->header.link = 0u; shStrTabSection->header.info = 0u; shStrTabSection->header.entsize = 0u; loadAddr = shStrTabSection->header.addr + shStrTabSection->header.size; // Generate SHT_RPL_FILEINFO fileInfoSection->header.name = 0u; fileInfoSection->header.type = rpl::SHT_RPL_FILEINFO; fileInfoSection->header.flags = 0u; fileInfoSection->header.addralign = 4u; fileInfoSection->header.addr = 0u; fileInfoSection->header.offset = 0u; // Set later fileInfoSection->header.size = static_cast(sizeof(rpl::RPLFileInfo_v4_2)); fileInfoSection->header.link = 0u; fileInfoSection->header.info = 0u; fileInfoSection->header.entsize = 0u; fileInfoSection->data.resize(fileInfoSection->header.size); auto infoFileName = addSectionString(fileInfoSection->data, mName); auto info = reinterpret_cast(fileInfoSection->data.data()); info->version = 0xCAFE0402u; info->textSize = 0u; info->textAlign = 32u; info->dataSize = 0u; info->dataAlign = 4096u; info->loadSize = 0u; info->loadAlign = 4u; info->tempSize = 0u; info->trampAdjust = 0u; info->trampAddition = 0u; info->sdaBase = 0u; info->sda2Base = 0u; info->stackSize = 0x10000u; info->filename = infoFileName; info->heapSize = 0x8000u; info->flags = 0u; info->minVersion = 0x5078u; info->compressionLevel = -1; info->fileInfoPad = 0u; info->cafeSdkVersion = 0x5335u; info->cafeSdkRevision = 0x10D4Bu; info->tlsAlignShift = uint16_t { 0u }; info->tlsModuleIndex = int16_t { 0 }; info->runtimeFileInfoSize = 0u; info->tagOffset = 0u; for (auto §ion : sections) { if (section.data.size()) { section.header.size = static_cast(section.data.size()); } auto size = section.header.size; if (section.header.addr >= CodeBaseAddress && section.header.addr < DataBaseAddress) { auto val = section.header.addr + section.header.size - CodeBaseAddress; if (val > info->textSize) { info->textSize = val; } } else if (section.header.addr >= DataBaseAddress && section.header.addr < LoadBaseAddress) { auto val = section.header.addr + section.header.size - DataBaseAddress; if (val > info->dataSize) { info->dataSize = val; } } else if (section.header.addr >= LoadBaseAddress) { auto val = section.header.addr + section.header.size - LoadBaseAddress; if (val > info->loadSize) { info->loadSize = val; } } else if (section.header.addr == 0 && section.header.type != rpl::SHT_RPL_CRCS && section.header.type != rpl::SHT_RPL_FILEINFO) { info->tempSize += (size + 128); } } info->textSize = align_up(info->textSize, info->textAlign); info->dataSize = align_up(info->dataSize, info->dataAlign); info->loadSize = align_up(info->loadSize, info->loadAlign); // Generate SHT_RPL_CRCS crcSection->header.name = 0u; crcSection->header.type = rpl::SHT_RPL_CRCS; crcSection->header.flags = 0u; crcSection->header.addralign = 4u; crcSection->header.addr = 0u; crcSection->header.offset = 0u; // Set later crcSection->header.size = static_cast(4 * sections.size()); crcSection->header.link = 0u; crcSection->header.info = 0u; crcSection->header.entsize = 4u; crcSection->data.resize(crcSection->header.size); for (auto i = 0u; i < sections.size(); ++i) { auto crc = uint32_t { 0u }; auto §ion = sections[i]; if (section.data.size() && i != (sections.size() - 2)) { crc = crc32(0, Z_NULL, 0); crc = crc32(crc, reinterpret_cast(section.data.data()), static_cast(section.data.size())); } *reinterpret_cast *>(crcSection->data.data() + i * 4) = crc; } // Generate file header rpl::Header fileHeader; fileHeader.magic[0] = uint8_t { 0x7F }; fileHeader.magic[1] = uint8_t { 'E' }; fileHeader.magic[2] = uint8_t { 'L' }; fileHeader.magic[3] = uint8_t { 'F' }; fileHeader.fileClass = rpl::ELFCLASS32; fileHeader.encoding = rpl::ELFDATA2MSB; fileHeader.elfVersion = rpl::EV_CURRENT; fileHeader.abi = rpl::EABI_CAFE; fileHeader.abiVersion = rpl::EABI_VERSION_CAFE; fileHeader.type = uint16_t { 0xFE01 }; fileHeader.machine = rpl::EM_PPC; fileHeader.version = 1u; fileHeader.phoff = 0u; fileHeader.shoff = 0x40u; fileHeader.flags = 0u; fileHeader.ehsize = static_cast(sizeof(rpl::Header)); fileHeader.phentsize = uint16_t { 0 }; fileHeader.phnum = uint16_t { 0 }; fileHeader.shentsize = static_cast(sizeof(rpl::SectionHeader)); fileHeader.shnum = static_cast(sections.size()); fileHeader.shstrndx = static_cast(shStrTabSectionIndex); // Find and set the entry point decaf_check(!mEntryPointSymbolName.empty()); auto entryPointItr = mSymbolMap.find(mEntryPointSymbolName); decaf_check(entryPointItr != mSymbolMap.end()); auto entryPointAddr = textSection->header.addr + entryPointItr->second->offset; fileHeader.entry = entryPointAddr; // Calculate file offsets auto offset = static_cast(fileHeader.shoff); offset += align_up(static_cast( sizeof(rpl::SectionHeader) * sections.size()), 64); crcSection->header.offset = offset; offset += crcSection->header.size; fileInfoSection->header.offset = offset; offset += fileInfoSection->header.size; // Data sections for (auto §ion : sections) { if (section.header.type == rpl::SHT_PROGBITS && !(section.header.flags & rpl::SHF_EXECINSTR)) { section.header.offset = offset; offset += section.header.size; } } // Export sections for (auto §ion : sections) { if (section.header.type == rpl::SHT_RPL_EXPORTS) { section.header.offset = offset; offset += section.header.size; } } // Import sections for (auto §ion : sections) { if (section.header.type == rpl::SHT_RPL_IMPORTS) { section.header.offset = offset; offset += section.header.size; } } // symtab & strtab for (auto §ion : sections) { if (section.header.type == rpl::SHT_SYMTAB || section.header.type == rpl::SHT_STRTAB) { section.header.offset = offset; offset += section.header.size; } } // Code sections for (auto §ion : sections) { if (section.header.type == rpl::SHT_PROGBITS && (section.header.flags & rpl::SHF_EXECINSTR)) { section.header.offset = offset; offset += section.header.size; } } // Relocation sections for (auto §ion : sections) { if (section.header.type == rpl::SHT_RELA) { section.header.offset = offset; offset += section.header.size; } } // Write out the generated RPL mGeneratedRpl.resize(offset, 0); std::memcpy(mGeneratedRpl.data() + 0, &fileHeader, sizeof(rpl::Header)); offset = fileHeader.shoff; for (auto §ion : sections) { std::memcpy(mGeneratedRpl.data() + offset, §ion.header, sizeof(rpl::SectionHeader)); offset += fileHeader.shentsize; } for (auto §ion : sections) { if (section.header.offset && section.data.size()) { std::memcpy(mGeneratedRpl.data() + section.header.offset, section.data.data(), section.data.size()); } } // TODO: Move this to a debug api command? if (decaf::config()->system.dump_hle_rpl) { std::ofstream out { mName, std::fstream::binary }; out.write(reinterpret_cast(mGeneratedRpl.data()), mGeneratedRpl.size()); } } } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library.h ================================================ #pragma once #include "cafe_hle_library_symbol.h" #include "cafe_hle_library_typeinfo.h" #include #include #include #include namespace cafe::hle { struct UnimplementedLibraryFunction { class Library* library = nullptr; std::string name; uint32_t syscallID = 0xFFFFFFFFu; virt_addr value; }; enum class LibraryId { avm, camera, coreinit, dc, dmae, drmapp, erreula, gx2, h264, lzma920, mic, nfc, nio_prof, nlibcurl, nlibnss2, nlibnss, nn_acp, nn_ac, nn_act, nn_aoc, nn_boss, nn_ccr, nn_cmpt, nn_dlp, nn_ec, nn_fp, nn_hai, nn_hpad, nn_idbe, nn_ndm, nn_nets2, nn_nfp, nn_nim, nn_olv, nn_pdm, nn_save, nn_sl, nn_spm, nn_temp, nn_uds, nn_vctl, nsysccr, nsyshid, nsyskbd, nsysnet, nsysuhs, nsysuvd, ntag, padscore, proc_ui, sndcore2, snd_core, snduser2, snd_user, swkbd, sysapp, tcl, tve, uac, uac_rpl, usb_mic, uvc, uvd, vpadbase, vpad, zlib125, Max, }; class Library { public: // We have 3 default suymbols: NULL, .text, .data static constexpr auto BaseSymbolIndex = uint32_t { 3 }; static cpu::Core * handleUnknownSystemCall(cpu::Core *state, uint32_t id); public: Library(LibraryId id, std::string name) : mID(id), mName(std::move(name)) { } virtual ~Library() = default; LibraryId id() const { return mID; } const std::string & name() const { return mName; } virt_addr findSymbolAddress(std::string_view name) const { auto itr = mSymbolMap.find(name); if (itr == mSymbolMap.end()) { return virt_addr { 0 }; } return itr->second->address; } LibrarySymbol * findSymbol(std::string_view name) const { auto itr = mSymbolMap.find(name); if (itr == mSymbolMap.end()) { return nullptr; } return itr->second.get(); } LibrarySymbol * findSymbol(virt_addr addr) const { for (auto &[name, symbol] : mSymbolMap) { if (symbol->address == addr) { return symbol.get(); } } return nullptr; } const std::vector & getGeneratedRpl() const { return mGeneratedRpl; } void generate(); void relocate(virt_addr textBaseAddress, virt_addr dataBaseAddress); void addUnimplementedFunctionExport(UnimplementedLibraryFunction *unimpl) { mUnimplementedFunctionExports.push_back(unimpl); } UnimplementedLibraryFunction * findUnimplementedFunctionExport(std::string_view name) { for (auto unimpl : mUnimplementedFunctionExports) { if (unimpl->name == name) { return unimpl; } } return nullptr; } const auto & getSymbolMap() const { return mSymbolMap; } void registerLibraryDependency(const char *name) { mLibraryDependencies.push_back(name); } void registerSymbol(const std::string &name, std::unique_ptr symbol) { decaf_check(mSymbolMap.find(name) == mSymbolMap.end()); symbol->index = BaseSymbolIndex + static_cast(mSymbolMap.size()); symbol->name = name; mSymbolMap.emplace(name, std::move(symbol)); } void registerTypeInfo(LibraryTypeInfo &&typeInfo) { mTypeInfo.emplace_back(std::move(typeInfo)); } void setEntryPointSymbolName(const std::string& name) { mEntryPointSymbolName = name; } protected: virtual void registerSymbols() = 0; void registerSystemCalls(); void generateRpl(); private: LibraryId mID; std::string mName; std::vector mLibraryDependencies; std::map, std::less<>> mSymbolMap; std::vector mTypeInfo; std::vector mGeneratedRpl; std::vector mUnimplementedFunctionExports; std::string mEntryPointSymbolName; }; virt_addr registerUnimplementedSymbol(std::string_view module, std::string_view name); void setUnimplementedFunctionStubMemory(virt_ptr base, uint32_t size); } // namespace cafe::hle #include "cafe_hle_library_register.h" ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library_data.h ================================================ #pragma once #include "cafe_hle_library_symbol.h" #include namespace cafe::hle { struct LibraryData : LibrarySymbol { LibraryData() : LibrarySymbol(LibrarySymbol::Data) { } virtual ~LibraryData() { } //! Pointer to the host pointer to guest memory which we should update virt_ptr *hostPointer = nullptr; //! Host constructor to call on allocated memory void (*constructor)(void *) = nullptr; //! Size of this data symbol uint32_t size = 0; //! Align of this data symbol uint32_t align = 0; }; } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library_function.h ================================================ #pragma once #include "cafe_hle_library_symbol.h" #include "cafe/cafe_ppc_interface_invoke_host.h" #include "cafe/cafe_ppc_interface_trace_host.h" #include namespace cafe::hle { extern volatile bool FunctionTraceEnabled; using InvokeHandler = cpu::Core * (*)(cpu::Core * core, uint32_t id); struct LibraryFunction : public LibrarySymbol { LibraryFunction(InvokeHandler _invokeHandler, bool& _traceEnabledRef) : LibrarySymbol(LibrarySymbol::Function), invokeHandler(_invokeHandler), traceEnabled(_traceEnabledRef) { } virtual ~LibraryFunction() { } //! The actual handler for this function InvokeHandler invokeHandler; //! Reference to the underlying invoke handler trace wrapper's trace enabled // value, specifying whether trace logging is enabled for this function or not. bool &traceEnabled; //! ID number of syscall. uint32_t syscallID = 0xFFFFFFFFu; //! Pointer to host function pointer, only set for internal functions. virt_ptr *hostPtr = nullptr; }; namespace internal { template struct TracingWrapper { static inline cpu::Core *wrapped(cpu::Core *core, uint32_t kcId) { if (FunctionTraceEnabled && traceEnabled) { invoke_trace(core, traceName.c_str()); } return invoke(core); } static inline std::string traceName = "_missingName"; static inline bool traceEnabled = false; }; template inline std::unique_ptr makeLibraryFunction(const std::string &name) { TracingWrapper::traceName = name; auto libraryFunction = new LibraryFunction( TracingWrapper::wrapped, TracingWrapper::traceEnabled); return std::unique_ptr { libraryFunction }; } } // namespace internal } // cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library_register.h ================================================ #pragma once #include "cafe_hle_library.h" #include "cafe_hle_library_function.h" #include "cafe_hle_library_data.h" #include "coreinit/coreinit_enum.h" namespace cafe::coreinit { using OSDynLoad_ModuleHandle = uint32_t; namespace internal { OSDynLoad_Error relocateHleLibrary(OSDynLoad_ModuleHandle moduleHandle); } // namespace internal } // namespace cafe::coreinit namespace cafe::hle { typedef int32_t(&RplEntryFunctionType)(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason); template static int32_t cafe_rpl_crt(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { coreinit::internal::relocateHleLibrary(moduleHandle); return Fn(moduleHandle, reason); } template static int32_t cafe_generic_rpl_crt(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { coreinit::internal::relocateHleLibrary(moduleHandle); return 0; } template static void registerNoCrtEntryPoint(hle::Library *library, const std::string &name) { auto symbol = internal::makeLibraryFunction(name); symbol->exported = true; library->registerSymbol(name, std::move(symbol)); library->setEntryPointSymbolName(name); } template static void registerEntryPoint(hle::Library *library, const std::string &name) { auto symbol = internal::makeLibraryFunction(name); symbol->exported = true; library->registerSymbol(name, std::move(symbol)); static const std::string crtEntryName = "__rpl_crt"; registerNoCrtEntryPoint>(library, crtEntryName); } template static void registerFunctionInternal(hle::Library *library, const char *name, virt_func_ptr::type>& hostPtr) { auto symbol = internal::makeLibraryFunction(name); symbol->exported = false; symbol->hostPtr = reinterpret_cast*>(&hostPtr); library->registerSymbol(name, std::move(symbol)); } template static void registerFunctionInternal(hle::Library *library, const char *name) { auto symbol = internal::makeLibraryFunction(name); symbol->exported = false; library->registerSymbol(name, std::move(symbol)); } template static void registerFunctionExport(hle::Library *library, const char *name) { auto symbol = internal::makeLibraryFunction(name); symbol->exported = true; library->registerSymbol(name, std::move(symbol)); } template static void registerDataInternal(hle::Library *library, const char *name, virt_ptr &data) { auto symbol = std::make_unique(); symbol->exported = false; symbol->hostPointer = reinterpret_cast*>(&data); symbol->constructor = [](void* ptr) { new (ptr) DataType(); }; symbol->size = sizeof(DataType); symbol->align = alignof(DataType); library->registerSymbol(name, std::move(symbol)); } template static void registerDataExport(hle::Library *library, const char *name, virt_ptr &data) { auto symbol = std::make_unique(); symbol->exported = true; symbol->hostPointer = reinterpret_cast*>(&data); symbol->constructor = [](void* ptr) { new (ptr) DataType(); }; symbol->size = sizeof(DataType); symbol->align = alignof(DataType); library->registerSymbol(name, std::move(symbol)); } namespace detail { template class has_ghs_virtual_table { typedef char yes_type; typedef long no_type; template static yes_type test(decltype(&U::VirtualTable)); template static no_type test(...); public: static constexpr bool value = sizeof(test(0)) == sizeof(yes_type); }; } // namespace detail template static void registerTypeInfo(hle::Library *library, const char *typeName, std::vector &&virtualTable, std::vector &&baseTypes, const char *typeIdSymbol = nullptr) { auto typeInfo = LibraryTypeInfo { }; typeInfo.name = typeName; typeInfo.typeIdSymbol = typeIdSymbol; typeInfo.hostTypeDescriptorPtr = &ObjectType::TypeDescriptor; typeInfo.virtualTable = std::move(virtualTable); typeInfo.baseTypes = std::move(baseTypes); if constexpr (detail::has_ghs_virtual_table::value) { typeInfo.hostVirtualTablePtr = &ObjectType::VirtualTable; } library->registerTypeInfo(std::move(typeInfo)); } #define fnptr_decltype(Func) \ std::conditional::value, std::add_pointer::type, decltype(Func)>::type #define RegisterEntryPoint(fn) \ cafe::hle::registerEntryPoint(this, #fn) #define RegisterNoCrtEntryPoint(fn) \ cafe::hle::registerNoCrtEntryPoint(this, #fn) #define RegisterGenericEntryPoint() \ RegisterNoCrtEntryPoint(cafe_generic_rpl_crt<0>) #define RegisterFunctionExport(fn) \ cafe::hle::registerFunctionExport(this, #fn) #define RegisterFunctionExportName(name, fn) \ cafe::hle::registerFunctionExport(this, name) #define RegisterDataExport(data) \ cafe::hle::registerDataExport(this, #data, data) #define RegisterDataExportName(name, data) \ cafe::hle::registerDataExport(this, name, data) #define RegisterFunctionInternal(fn, ptr) \ cafe::hle::registerFunctionInternal(this, "__internal__" # fn, ptr) #define RegisterFunctionInternalName(name, fn) \ cafe::hle::registerFunctionInternal(this, name) #define RegisterDataInternal(data) \ cafe::hle::registerDataInternal(this, "__internal__" # data, data) #define RegisterTypeInfo(type, name, ...) \ cafe::hle::registerTypeInfo(this, name, __VA_ARGS__); } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library_symbol.h ================================================ #pragma once #include #include namespace cafe::hle { struct LibrarySymbol { static constexpr auto InvalidOffset = 0xCD000000; enum Type { Undefined, Function, Data, }; LibrarySymbol(Type type) : type(type) { } virtual ~LibrarySymbol() { } //! Symbol type Type type = Undefined; //! Symbol index in library uint32_t index = 0; //! Symbol name std::string name; //! Whether the symbol is exported or not bool exported = false; //! Offset in .text or .data section uint32_t offset = InvalidOffset; //! Virtual address of this symbol //! TODO: Change stuff to use offset when we go multi-process! virt_addr address = virt_addr { 0 }; }; } // namespace cafe ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_library_typeinfo.h ================================================ #pragma once #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include #include namespace cafe::hle { struct LibraryTypeInfo { const char *name = nullptr; std::vector virtualTable; std::vector baseTypes; virt_ptr *hostVirtualTablePtr = nullptr; virt_ptr *hostTypeDescriptorPtr = nullptr; uint32_t nameOffset = 0u; uint32_t baseTypeOffset = 0u; uint32_t typeDescriptorOffset = 0u; uint32_t virtualTableOffset = 0u; uint32_t typeIdOffset = 0u; const char *typeIdSymbol = nullptr; }; } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_stub.cpp ================================================ #include "cafe_hle_stub.h" #include namespace cafe::hle { void warnStubInvoked(const char *name) { gLog->warn("Application invoked stubbed function `{}`", name); } } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/cafe_hle_stub.h ================================================ #pragma once #ifdef _MSC_VER #define PRETTY_FUNCTION_NAME __FUNCSIG__ #else #define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__ #endif #define decaf_warn_stub() \ { \ static bool warned = false; \ if (!warned) { \ cafe::hle::warnStubInvoked(PRETTY_FUNCTION_NAME); \ warned = true; \ } \ } namespace cafe::hle { void warnStubInvoked(const char *name); } // namespace cafe::hle ================================================ FILE: src/libdecaf/src/cafe/libraries/camera/camera.cpp ================================================ #include "camera.h" namespace cafe::camera { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerCamSymbols(); } } // namespace cafe::camera ================================================ FILE: src/libdecaf/src/cafe/libraries/camera/camera.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::camera { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::camera, "camera.rpl") { } protected: virtual void registerSymbols() override; private: void registerCamSymbols(); }; } // namespace cafe::camera ================================================ FILE: src/libdecaf/src/cafe/libraries/camera/camera_cam.cpp ================================================ #include "camera.h" #include "camera_cam.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::camera { CAMHandle CAMInit(uint32_t id, virt_ptr info, virt_ptr outError) { decaf_warn_stub(); *outError = CAMError::OK; return id; } void CAMExit(CAMHandle handle) { decaf_warn_stub(); } int32_t CAMOpen(CAMHandle handle) { decaf_warn_stub(); return CAMError::OK; } int32_t CAMClose(CAMHandle handle) { decaf_warn_stub(); return CAMError::OK; } int32_t CAMGetMemReq(virt_ptr info) { decaf_warn_stub(); if (!info) { return -1; } auto bufferSize = info->width * info->height * 10; auto extraSize = 2 * 0x14 + 0x5400; return bufferSize + extraSize; } void Library::registerCamSymbols() { RegisterFunctionExport(CAMInit); RegisterFunctionExport(CAMExit); RegisterFunctionExport(CAMOpen); RegisterFunctionExport(CAMClose); RegisterFunctionExport(CAMGetMemReq); } } // namespace cafe::camera ================================================ FILE: src/libdecaf/src/cafe/libraries/camera/camera_cam.h ================================================ #pragma once #include "camera_enum.h" #include namespace cafe::camera { #pragma pack(push, 1) using CAMHandle = int32_t; struct CAMMemoryInfo { be2_val unk0; be2_val width; be2_val height; }; CHECK_OFFSET(CAMMemoryInfo, 0x00, unk0); CHECK_OFFSET(CAMMemoryInfo, 0x04, width); CHECK_OFFSET(CAMMemoryInfo, 0x08, height); CHECK_SIZE(CAMMemoryInfo, 0x0C); struct CAMInitInfo { CAMMemoryInfo memInfo; be2_virt_ptr workMemory; be2_val workMemorySize; // 0x14 = some function pointer UNKNOWN(0x20); }; CHECK_OFFSET(CAMInitInfo, 0x00, memInfo); CHECK_OFFSET(CAMInitInfo, 0x0C, workMemory); CHECK_OFFSET(CAMInitInfo, 0x10, workMemorySize); CHECK_SIZE(CAMInitInfo, 0x34); #pragma pack(pop) CAMHandle CAMInit(uint32_t id, virt_ptr info, virt_ptr outError); void CAMExit(CAMHandle handle); int32_t CAMOpen(CAMHandle handle); int32_t CAMClose(CAMHandle handle); int32_t CAMGetMemReq(virt_ptr info); } // namespace cafe::camera ================================================ FILE: src/libdecaf/src/cafe/libraries/camera/camera_enum.h ================================================ #ifndef CAFE_CAMERA_ENUM_H #define CAFE_CAMERA_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(camera) ENUM_BEG(CAMError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(GenericError, -1) ENUM_VALUE(AlreadyInitialised, -12) ENUM_END(CAMError) ENUM_NAMESPACE_EXIT(camera) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_CAMERA_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit.cpp ================================================ #include "coreinit.h" #include "coreinit_alarm.h" #include "coreinit_appio.h" #include "coreinit_bsp.h" #include "coreinit_device.h" #include "coreinit_driver.h" #include "coreinit_dynload.h" #include "coreinit_exception.h" #include "coreinit_ghs.h" #include "coreinit_im.h" #include "coreinit_interrupts.h" #include "coreinit_ipcdriver.h" #include "coreinit_lockedcache.h" #include "coreinit_mcp.h" #include "coreinit_memallocator.h" #include "coreinit_memory.h" #include "coreinit_memheap.h" #include "coreinit_scheduler.h" #include "coreinit_systeminfo.h" #include "coreinit_systemmessagequeue.h" #include "coreinit_thread.h" #include "coreinit_time.h" #include #include "cafe/libraries/cafe_hle.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" namespace cafe::coreinit { static void coreinit_entry(/* no args for coreinit entry point */) { auto coreId = cpu::this_core::id(); // Always initialise time then system info first, other things depend on it. internal::initialiseTime(); internal::initialiseSystemInfo(); internal::initialiseIci(); internal::initialiseSystemMessageQueue(); internal::initialiseExceptionHandlers(); internal::initialiseScheduler(); internal::initialiseThreads(); internal::initialiseAlarmThread(); internal::initialiseLockedCache(coreId); internal::initialiseMemory(); internal::initialiseMemHeap(); internal::initialiseAllocatorStaticData(); IPCDriverInit(); IPCDriverOpen(); internal::initialiseAppIoThreads(); internal::initialiseDeviceTable(); bspInitializeShimInterface(); internal::initialiseMcp(); auto entryPoint = internal::initialiseDynLoad(); // registerMemDriver // registerCacheDriver // registerIpcDriver // registerInputDriver internal::initialiseIm(); // Actually called from registerInputDriver // registerTestDriver // registerAcpLoadDriver // registerButtonDriver // registerClipboardDriver // driver on init internal::driverOnInit(); auto entryFunc = virt_func_cast argv)>(entryPoint); auto result = cafe::invoke(cpu::this_core::state(), entryFunc, uint32_t { 0u }, virt_ptr { nullptr }); ghs_exit(result); } void Library::registerSymbols() { RegisterNoCrtEntryPoint(coreinit_entry); registerAlarmSymbols(); registerAppIoSymbols(); registerAtomicSymbols(); registerAtomic64Symbols(); registerBspSymbols(); registerCacheSymbols(); registerClipboardSymbols(); registerCodeGenSymbols(); registerContextSymbols(); registerCoreSymbols(); registerCoroutineSymbols(); registerCosReportSymbols(); registerDeviceSymbols(); registerDriverSymbols(); registerDynLoadSymbols(); registerEventSymbols(); registerExceptionSymbols(); registerFastMutexSymbols(); registerFiberSymbols(); registerFsSymbols(); registerFsClientSymbols(); registerFsCmdSymbols(); registerFsCmdBlockSymbols(); registerFsDriverSymbols(); registerFsStateMachineSymbols(); registerFsaSymbols(); registerFsaCmdSymbols(); registerFsaShimSymbols(); registerGhsSymbols(); registerHandleSymbols(); registerImSymbols(); registerInterruptSymbols(); registerIosSymbols(); registerIpcBufPoolSymbols(); registerIpcDriverSymbols(); registerLockedCacheSymbols(); registerMcpSymbols(); registerMemAllocatorSymbols(); registerMemBlockHeapSymbols(); registerMemDefaultHeapSymbols(); registerMemExpHeapSymbols(); registerMemFrmHeapSymbols(); registerMemHeapSymbols(); registerMemListSymbols(); registerMemorySymbols(); registerMemUnitHeapSymbols(); registerMessageQueueSymbols(); registerMutexSymbols(); registerOsReportSymbols(); registerOverlayArenaSymbols(); registerRendezvousSymbols(); registerSchedulerSymbols(); registerScreenSymbols(); registerSemaphoreSymbols(); registerSnprintfSymbols(); registerSpinLockSymbols(); registerSystemHeapSymbols(); registerSystemInfoSymbols(); registerSystemMessageQueueSymbols(); registerTaskQueueSymbols(); registerThreadSymbols(); registerTimeSymbols(); registerUserConfigSymbols(); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::coreinit { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::coreinit, "coreinit.rpl") { } protected: virtual void registerSymbols() override; private: void registerAlarmSymbols(); void registerAppIoSymbols(); void registerAtomicSymbols(); void registerAtomic64Symbols(); void registerBspSymbols(); void registerCacheSymbols(); void registerClipboardSymbols(); void registerCodeGenSymbols(); void registerContextSymbols(); void registerCoreSymbols(); void registerCoroutineSymbols(); void registerCosReportSymbols(); void registerDeviceSymbols(); void registerDriverSymbols(); void registerDynLoadSymbols(); void registerEventSymbols(); void registerExceptionSymbols(); void registerFastMutexSymbols(); void registerFiberSymbols(); void registerFsSymbols(); void registerFsClientSymbols(); void registerFsCmdSymbols(); void registerFsCmdBlockSymbols(); void registerFsDriverSymbols(); void registerFsStateMachineSymbols(); void registerFsaSymbols(); void registerFsaCmdSymbols(); void registerFsaShimSymbols(); void registerGhsSymbols(); void registerHandleSymbols(); void registerImSymbols(); void registerInterruptSymbols(); void registerIosSymbols(); void registerIpcBufPoolSymbols(); void registerIpcDriverSymbols(); void registerLockedCacheSymbols(); void registerMcpSymbols(); void registerMemAllocatorSymbols(); void registerMemBlockHeapSymbols(); void registerMemDefaultHeapSymbols(); void registerMemExpHeapSymbols(); void registerMemFrmHeapSymbols(); void registerMemHeapSymbols(); void registerMemListSymbols(); void registerMemorySymbols(); void registerMemUnitHeapSymbols(); void registerMessageQueueSymbols(); void registerMutexSymbols(); void registerOsReportSymbols(); void registerOverlayArenaSymbols(); void registerRendezvousSymbols(); void registerSchedulerSymbols(); void registerScreenSymbols(); void registerSemaphoreSymbols(); void registerSnprintfSymbols(); void registerSpinLockSymbols(); void registerSystemHeapSymbols(); void registerSystemInfoSymbols(); void registerSystemMessageQueueSymbols(); void registerTaskQueueSymbols(); void registerThreadSymbols(); void registerTimeSymbols(); void registerUserConfigSymbols(); }; } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_alarm.cpp ================================================ #include "coreinit.h" #include "coreinit_alarm.h" #include "coreinit_core.h" #include "coreinit_spinlock.h" #include "coreinit_interrupts.h" #include "coreinit_scheduler.h" #include "coreinit_thread.h" #include "coreinit_memheap.h" #include "coreinit_memory.h" #include "coreinit_time.h" #include "coreinit_internal_queue.h" #include "coreinit_internal_idlock.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include #include #include #include namespace cafe::coreinit { constexpr auto AlarmThreadStackSize = 0x8000u; struct StaticAlarmData { internal::IdLock lock; struct PerCoreAlarmData { be2_struct thread; be2_array threadName; be2_array threadStack; be2_struct alarmQueue; be2_struct callbackAlarmQueue; be2_struct callbackThreadQueue; }; be2_array perCoreData; }; static virt_ptr sAlarmData = nullptr; static OSThreadEntryPointFn sAlarmCallbackThreadEntry; namespace internal { using AlarmQueue = Queue; void updateCpuAlarmNoALock(); } // namespace internal /** * Internal alarm cancel. * * Reset the alarm state to cancelled. * Wakes up all threads waiting on the alarm. * Removes the alarm from any queue it is in. */ static BOOL cancelAlarmNoAlarmLock(virt_ptr alarm) { // We are technically supposed to try and remove the alarm // from the callback queue as well. if (alarm->state != OSAlarmState::Set) { return FALSE; } alarm->state = OSAlarmState::Idle; alarm->nextFire = 0; alarm->period = 0; if (alarm->alarmQueue) { internal::AlarmQueue::erase(alarm->alarmQueue, alarm); alarm->alarmQueue = nullptr; } return TRUE; } /** * Cancel an alarm. */ BOOL OSCancelAlarm(virt_ptr alarm) { // First cancel the alarm whilst holding alarm lock internal::acquireIdLock(sAlarmData->lock, alarm); auto result = cancelAlarmNoAlarmLock(alarm); internal::releaseIdLock(sAlarmData->lock, alarm); if (!result) { return FALSE; } // Now wakeup any waiting threads internal::lockScheduler(); for (auto thread = alarm->threadQueue.head; thread; thread = thread->link.next) { thread->alarmCancelled = TRUE; } internal::wakeupThreadNoLock(virt_addrof(alarm->threadQueue)); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); return TRUE; } /** * Cancel all alarms which have a matching tag. */ void OSCancelAlarms(uint32_t group) { internal::lockScheduler(); internal::acquireIdLock(sAlarmData->lock, static_cast(-2)); for (auto &perCoreData : sAlarmData->perCoreData) { for (auto alarm = perCoreData.alarmQueue.head; alarm; ) { auto next = alarm->link.next; if (alarm->group == group) { if (cancelAlarmNoAlarmLock(alarm)) { for (auto thread = alarm->threadQueue.head; thread; thread = thread->link.next) { thread->alarmCancelled = TRUE; } internal::wakeupThreadNoLock(virt_addrof(alarm->threadQueue)); } } alarm = next; } } internal::releaseIdLock(sAlarmData->lock, static_cast(-2)); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); } /** * Initialise an alarm structure. */ void OSCreateAlarm(virt_ptr alarm) { OSCreateAlarmEx(alarm, nullptr); } /** * Initialise an alarm structure. */ void OSCreateAlarmEx(virt_ptr alarm, virt_ptr name) { // Holding the alarm here is neccessary since its valid to call // Create on an already active alarm, as long as its not set. internal::acquireIdLock(sAlarmData->lock, alarm); memset(alarm, 0, sizeof(OSAlarm)); alarm->tag = OSAlarm::Tag; alarm->name = name; OSInitThreadQueueEx(virt_addrof(alarm->threadQueue), alarm); internal::releaseIdLock(sAlarmData->lock, alarm); } /** * Return the user data stored in the alarm using OSSetAlarmUserData */ virt_ptr OSGetAlarmUserData(virt_ptr alarm) { return alarm->userData; } /** * Initialise an alarm queue structure */ void OSInitAlarmQueue(virt_ptr queue) { OSInitAlarmQueueEx(queue, nullptr); } /** * Initialise an alarm queue structure with a name */ void OSInitAlarmQueueEx(virt_ptr queue, virt_ptr name) { memset(queue, 0, sizeof(OSAlarmQueue)); queue->tag = OSAlarmQueue::Tag; queue->name = name; } /** * Set a one shot alarm to perform a callback after an amount of time. */ BOOL OSSetAlarm(virt_ptr alarm, OSTime time, AlarmCallbackFn callback) { return OSSetPeriodicAlarm(alarm, OSGetTime() + time, 0, callback); } /** * Set a repeated alarm to execute a callback every interval from start. * * \param alarm * The alarm to set. * * \param start * The absolute time the alarm should first be triggered. * * \param interval * The interval between triggers after the first trigger. * * \param callback * The alarm callback to call when the alarm is triggered. * * \return * Returns TRUE if the alarm was succesfully set, FALSE otherwise. */ BOOL OSSetPeriodicAlarm(virt_ptr alarm, OSTime start, OSTime interval, AlarmCallbackFn callback) { internal::acquireIdLock(sAlarmData->lock, alarm); if (!start) { start = OSGetTime() + interval; } // Set alarm alarm->nextFire = start; alarm->callback = callback; alarm->period = interval; alarm->context = nullptr; alarm->state = OSAlarmState::Set; // Erase from old alarm queue if (alarm->alarmQueue) { internal::AlarmQueue::erase(alarm->alarmQueue, alarm); alarm->alarmQueue = nullptr; } // Add to this core's alarm queue auto queue = virt_addrof(sAlarmData->perCoreData[OSGetCoreId()].alarmQueue); alarm->alarmQueue = queue; internal::AlarmQueue::append(queue, alarm); // Set the interrupt timer in processor // TODO: Store the last set CPU alarm time, and simply check this // alarm against that time to make finding the soonest alarm cheaper. internal::updateCpuAlarmNoALock(); internal::releaseIdLock(sAlarmData->lock, alarm); return TRUE; } /** * Set an alarm tag which is used in OSCancelAlarms for bulk cancellation. */ void OSSetAlarmTag(virt_ptr alarm, uint32_t group) { internal::acquireIdLock(sAlarmData->lock, alarm); alarm->group = group; internal::releaseIdLock(sAlarmData->lock, alarm); } /** * Set alarm user data which is returned by OSGetAlarmUserData. */ void OSSetAlarmUserData(virt_ptr alarm, virt_ptr data) { internal::acquireIdLock(sAlarmData->lock, alarm); alarm->userData = data; internal::releaseIdLock(sAlarmData->lock, alarm); } /** * Sleep the current thread until the alarm has been triggered or cancelled. * * \return * Returns TRUE if alarm expired, FALSE if alarm cancelled */ BOOL OSWaitAlarm(virt_ptr alarm) { internal::lockScheduler(); internal::acquireIdLock(sAlarmData->lock, alarm); decaf_check(alarm); decaf_check(alarm->tag == OSAlarm::Tag); if (alarm->state != OSAlarmState::Set) { internal::releaseIdLock(sAlarmData->lock, alarm); internal::unlockScheduler(); return FALSE; } OSGetCurrentThread()->alarmCancelled = false; internal::sleepThreadNoLock(virt_addrof(alarm->threadQueue)); internal::releaseIdLock(sAlarmData->lock, alarm); internal::rescheduleSelfNoLock(); auto cancelled = OSGetCurrentThread()->alarmCancelled; internal::unlockScheduler(); if (cancelled) { return FALSE; } else { return TRUE; } } static void insertAlarm(virt_ptr alarm, AlarmCallbackFn callback) { decaf_check(alarm); decaf_check(alarm->state == OSAlarmState::Idle || alarm->state == OSAlarmState::Invalid); decaf_check(alarm->link.prev == nullptr && alarm->link.next == nullptr); decaf_check(!OSIsInterruptEnabled()); decaf_check(internal::isLockHeldBySomeone(sAlarmData->lock)); OSGetSystemTime(); } int OSGetAlarmFromQueue(virt_ptr queue, virt_ptr> outAlarm, virt_ptr> outCallbackContext, uint32_t r6) { OSDisableInterrupts(); while (true) { internal::acquireIdLock(sAlarmData->lock, queue); if (queue->head) { auto alarm = queue->head; queue->head = alarm->link.next; if (!queue->head) { queue->tail = nullptr; } else { queue->head->link.prev = nullptr; } alarm->link.prev = nullptr; alarm->link.next = nullptr; alarm->state = OSAlarmState::Invalid; if (alarm->period) { insertAlarm(alarm, alarm->callback); } } internal::releaseIdLock(sAlarmData->lock, queue); } } static uint32_t alarmCallbackThreadEntry(uint32_t coreId, virt_ptr arg2) { auto queue = virt_addrof(sAlarmData->perCoreData[coreId].alarmQueue); auto cbQueue = virt_addrof(sAlarmData->perCoreData[coreId].callbackAlarmQueue); auto threadQueue = virt_addrof(sAlarmData->perCoreData[coreId].callbackThreadQueue); while (true) { internal::lockScheduler(); internal::acquireIdLock(sAlarmData->lock, arg2); virt_ptr alarm = internal::AlarmQueue::popFront(cbQueue); if (alarm == nullptr) { // No alarms currently pending for callback internal::sleepThreadNoLock(threadQueue); internal::releaseIdLock(sAlarmData->lock, arg2); internal::rescheduleSelfNoLock(); internal::unlockScheduler(); continue; } if (alarm->period) { alarm->nextFire = alarm->nextFire + alarm->period; alarm->state = OSAlarmState::Set; internal::AlarmQueue::append(queue, alarm); alarm->alarmQueue = queue; internal::updateCpuAlarmNoALock(); } internal::releaseIdLock(sAlarmData->lock, arg2); internal::unlockScheduler(); if (alarm->callback) { cafe::invoke(cpu::this_core::state(), alarm->callback, alarm, alarm->context); } } return 0; } namespace internal { BOOL setAlarmInternal(virt_ptr alarm, OSTime time, AlarmCallbackFn callback, virt_ptr userData) { alarm->group = 0xFFFFFFFF; alarm->userData = userData; return OSSetAlarm(alarm, time, callback); } bool cancelAlarm(virt_ptr alarm) { internal::acquireIdLock(sAlarmData->lock, alarm); auto result = cancelAlarmNoAlarmLock(alarm); internal::releaseIdLock(sAlarmData->lock, alarm); return result == TRUE; } void updateCpuAlarmNoALock() { auto &queue = sAlarmData->perCoreData[cpu::this_core::id()].alarmQueue; auto next = std::chrono::steady_clock::time_point::max(); for (virt_ptr alarm = queue.head; alarm; ) { auto nextAlarm = alarm->link.next; // Update next if its not past yet if (alarm->state == OSAlarmState::Set && alarm->nextFire) { auto nextFire = cpu::tbToTimePoint(alarm->nextFire - internal::getBaseTime()); if (nextFire < next) { next = nextFire; } } alarm = nextAlarm; } cpu::this_core::setNextAlarm(next); } void handleAlarmInterrupt(virt_ptr context) { auto &coreAlarmData = sAlarmData->perCoreData[cpu::this_core::id()]; auto queue = virt_addrof(coreAlarmData.alarmQueue); auto cbQueue = virt_addrof(coreAlarmData.callbackAlarmQueue); auto cbThreadQueue = virt_addrof(coreAlarmData.callbackThreadQueue); auto now = OSGetTime(); internal::lockScheduler(); acquireIdLockWithCoreId(sAlarmData->lock); for (virt_ptr alarm = queue->head; alarm; ) { auto nextAlarm = alarm->link.next; // Expire it if its past its nextFire time if (alarm->nextFire <= now) { decaf_check(alarm->state == OSAlarmState::Set); internal::AlarmQueue::erase(queue, alarm); alarm->alarmQueue = nullptr; alarm->state = OSAlarmState::Expired; alarm->context = context; if (alarm->threadQueue.head) { wakeupThreadNoLock(virt_addrof(alarm->threadQueue)); rescheduleOtherCoreNoLock(); } if (alarm->group == 0xFFFFFFFF) { // System-internal alarm if (alarm->callback) { auto originalMask = cpu::this_core::setInterruptMask(0); cafe::invoke(cpu::this_core::state(), alarm->callback, alarm, context); cpu::this_core::setInterruptMask(originalMask); } } else { internal::AlarmQueue::append(cbQueue, alarm); alarm->alarmQueue = cbQueue; wakeupThreadNoLock(cbThreadQueue); } } alarm = nextAlarm; } internal::updateCpuAlarmNoALock(); releaseIdLockWithCoreId(sAlarmData->lock); internal::unlockScheduler(); } void initialiseAlarmThread() { auto coreId = cpu::this_core::id(); auto &coreData = sAlarmData->perCoreData[coreId]; // Iniitalise data coreData.threadName = fmt::format("Alarm Thread {}", coreId); OSInitAlarmQueue(virt_addrof(coreData.alarmQueue)); OSInitAlarmQueue(virt_addrof(coreData.callbackAlarmQueue)); OSInitThreadQueue(virt_addrof(coreData.callbackThreadQueue)); // Start alarm thread auto thread = virt_addrof(coreData.thread); auto stack = virt_addrof(coreData.threadStack); coreinit__OSCreateThreadType(thread, sAlarmCallbackThreadEntry, coreId, nullptr, virt_cast(stack + coreData.threadStack.size()), coreData.threadStack.size(), 1, static_cast(1 << coreId), OSThreadType::AppIo); OSSetThreadName(thread, virt_addrof(coreData.threadName)); OSResumeThread(thread); } } // namespace internal void Library::registerAlarmSymbols() { RegisterFunctionExport(OSCancelAlarm); RegisterFunctionExport(OSCancelAlarms); RegisterFunctionExport(OSCreateAlarm); RegisterFunctionExport(OSCreateAlarmEx); RegisterFunctionExport(OSGetAlarmUserData); RegisterFunctionExport(OSInitAlarmQueue); RegisterFunctionExport(OSInitAlarmQueueEx); RegisterFunctionExport(OSSetAlarm); RegisterFunctionExport(OSSetPeriodicAlarm); RegisterFunctionExport(OSSetAlarmTag); RegisterFunctionExport(OSSetAlarmUserData); RegisterFunctionExport(OSWaitAlarm); RegisterDataInternal(sAlarmData); RegisterFunctionInternal(alarmCallbackThreadEntry, sAlarmCallbackThreadEntry); } } // namespace coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_alarm.h ================================================ #pragma once #include "coreinit_context.h" #include "coreinit_enum.h" #include "coreinit_thread.h" #include "coreinit_time.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_alarms Alarms * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct OSAlarm; using AlarmCallbackFn = virt_func_ptr alarm, virt_ptr context)>; struct OSAlarmQueue { static constexpr uint32_t Tag = 0x614C6D51; //! Should always be set to the value OSAlarmQueue::Tag. be2_val tag; //! Name set from OSInitAlarmQueueEx. be2_virt_ptr name; UNKNOWN(4); //! List of threads waiting on this alarm queue. be2_struct threadQueue; //! First alarm in the queue. be2_virt_ptr head; //! Last alarm in the queue. be2_virt_ptr tail; }; CHECK_OFFSET(OSAlarmQueue, 0x00, tag); CHECK_OFFSET(OSAlarmQueue, 0x04, name); CHECK_OFFSET(OSAlarmQueue, 0x0c, threadQueue); CHECK_OFFSET(OSAlarmQueue, 0x1c, head); CHECK_OFFSET(OSAlarmQueue, 0x20, tail); CHECK_SIZE(OSAlarmQueue, 0x24); struct OSAlarmLink { //! Previous alarm in the queue. be2_virt_ptr prev; //! Next alarm in the queue. be2_virt_ptr next; }; CHECK_OFFSET(OSAlarmLink, 0x00, prev); CHECK_OFFSET(OSAlarmLink, 0x04, next); CHECK_SIZE(OSAlarmLink, 0x08); struct OSAlarm { static constexpr uint32_t Tag = 0x614C724D; //! Should always be set to the value OSAlarm::Tag. be2_val tag; //! Name set from OSCreateAlarmEx. be2_virt_ptr name; UNKNOWN(4); //! The callback to execute once the alarm is triggered. be2_val callback; //! Used with OSCancelAlarms for bulk cancellation of alarms. be2_val group; UNKNOWN(4); //! The time when the alarm will next be triggered. be2_val nextFire; //! Link used for when this OSAlarm object is inside an OSAlarmQueue be2_struct link; //! The period between alarm triggers, this is only set for periodic alarms. be2_val period; //! The time the alarm was started. be2_val tbrStart; //! User data set with OSSetAlarmUserData and retrieved with OSGetAlarmUserData. be2_virt_ptr userData; //! The current state of the alarm. be2_val state; //! Queue of threads currently waiting for the alarm to trigger with OSWaitAlarm. be2_struct threadQueue; //! The queue that this alarm is currently in. be2_virt_ptr alarmQueue; //! The context the alarm was triggered on. be2_virt_ptr context; }; CHECK_OFFSET(OSAlarm, 0x00, tag); CHECK_OFFSET(OSAlarm, 0x04, name); CHECK_OFFSET(OSAlarm, 0x0c, callback); CHECK_OFFSET(OSAlarm, 0x10, group); CHECK_OFFSET(OSAlarm, 0x18, nextFire); CHECK_OFFSET(OSAlarm, 0x20, link); CHECK_OFFSET(OSAlarm, 0x28, period); CHECK_OFFSET(OSAlarm, 0x30, tbrStart); CHECK_OFFSET(OSAlarm, 0x38, userData); CHECK_OFFSET(OSAlarm, 0x3c, state); CHECK_OFFSET(OSAlarm, 0x40, threadQueue); CHECK_OFFSET(OSAlarm, 0x50, alarmQueue); CHECK_OFFSET(OSAlarm, 0x54, context); CHECK_SIZE(OSAlarm, 0x58); #pragma pack(pop) BOOL OSCancelAlarm(virt_ptr alarm); void OSCancelAlarms(uint32_t alarmTag); void OSCreateAlarm(virt_ptr alarm); void OSCreateAlarmEx(virt_ptr alarm, virt_ptr name); virt_ptr OSGetAlarmUserData(virt_ptr alarm); void OSInitAlarmQueue(virt_ptr queue); void OSInitAlarmQueueEx(virt_ptr queue, virt_ptr name); BOOL OSSetAlarm(virt_ptr alarm, OSTime time, AlarmCallbackFn callback); BOOL OSSetPeriodicAlarm(virt_ptr alarm, OSTime start, OSTime interval, AlarmCallbackFn callback); void OSSetAlarmTag(virt_ptr alarm, uint32_t alarmTag); void OSSetAlarmUserData(virt_ptr alarm, virt_ptr data); BOOL OSWaitAlarm(virt_ptr alarm); namespace internal { BOOL setAlarmInternal(virt_ptr alarm, OSTime time, AlarmCallbackFn callback, virt_ptr userData); bool cancelAlarm(virt_ptr alarm); void handleAlarmInterrupt(virt_ptr context); void initialiseAlarmThread(); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_appio.cpp ================================================ #include "coreinit.h" #include "coreinit_appio.h" #include "coreinit_core.h" #include "coreinit_fsa.h" #include "coreinit_fs_cmdblock.h" #include "coreinit_thread.h" #include "coreinit_messagequeue.h" #include "cafe/cafe_stackobject.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include #include #include namespace cafe::coreinit { constexpr auto MessagesPerCore = 256u; constexpr auto AppIoThreadStackSize = 0x2000u; struct StaticAppIoData { struct PerCoreData { be2_array threadName; be2_struct thread; be2_struct queue; be2_array messages; be2_array stack; }; be2_array perCoreData; }; static virt_ptr sAppIoData = nullptr; static OSThreadEntryPointFn sAppIoThreadEntry; virt_ptr OSGetDefaultAppIOQueue() { return virt_addrof(sAppIoData->perCoreData[OSGetCoreId()].queue); } static uint32_t appIoThreadEntry(uint32_t coreId, virt_ptr arg2) { auto msg = StackObject { }; auto coreData = virt_addrof(sAppIoData->perCoreData[coreId]); auto queue = virt_addrof(coreData->queue); OSInitMessageQueue(queue, virt_addrof(coreData->messages), coreData->messages.size()); while (true) { OSReceiveMessage(queue, msg, OSMessageFlags::Blocking); auto funcType = static_cast(msg->args[2].value()); switch (funcType) { case OSFunctionType::FsaCmdAsync: { auto result = FSAGetAsyncResult(msg); if (result->userCallback) { cafe::invoke(cpu::this_core::state(), result->userCallback, result->error, result->command, result->request, result->response, result->userContext); } break; } case OSFunctionType::FsCmdAsync: { auto result = FSGetAsyncResult(msg); if (result->asyncData.userCallback) { cafe::invoke(cpu::this_core::state(), result->asyncData.userCallback, result->client, result->block, result->status, result->asyncData.userContext); } break; } case OSFunctionType::FsCmdHandler: { internal::fsCmdBlockHandleResult(virt_cast(msg->message)); break; } default: decaf_abort(fmt::format("Unimplemented OSFunctionType {}", funcType)); } } } namespace internal { void initialiseAppIoThreads() { for (auto i = 0u; i < sAppIoData->perCoreData.size(); ++i) { auto &coreData = sAppIoData->perCoreData[i]; auto thread = virt_addrof(coreData.thread); auto stack = virt_addrof(coreData.stack); coreData.threadName = fmt::format("I/O Thread {}", i); coreinit__OSCreateThreadType(thread, sAppIoThreadEntry, i, nullptr, virt_cast(stack + coreData.stack.size()), coreData.stack.size(), 16, static_cast(1 << i), OSThreadType::AppIo); OSSetThreadName(thread, virt_addrof(coreData.threadName)); OSResumeThread(thread); } } } // namespace internal void Library::registerAppIoSymbols() { RegisterFunctionExport(OSGetDefaultAppIOQueue); RegisterDataInternal(sAppIoData); RegisterFunctionInternal(appIoThreadEntry, sAppIoThreadEntry); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_appio.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_appio App IO * \ingroup coreinit * @{ */ struct OSMessageQueue; virt_ptr OSGetDefaultAppIOQueue(); namespace internal { void initialiseAppIoThreads(); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic.cpp ================================================ #include "coreinit.h" #include "coreinit_atomic.h" #include #include namespace cafe::coreinit { BOOL OSCompareAndSwapAtomic(virt_ptr> atomic, uint32_t compare, uint32_t value) { return atomic->compare_exchange_strong(compare, value) ? TRUE : FALSE; } BOOL OSCompareAndSwapAtomicEx(virt_ptr> atomic, uint32_t compare, uint32_t value, virt_ptr old) { auto result = atomic->compare_exchange_strong(compare, value); *old = compare; return result ? TRUE : FALSE; } uint32_t OSSwapAtomic(virt_ptr> atomic, uint32_t value) { return atomic->exchange(value); } int32_t OSAddAtomic(virt_ptr> atomic, int32_t value) { return atomic->fetch_add(value); } uint32_t OSAndAtomic(virt_ptr> atomic, uint32_t value) { return atomic->fetch_and(value); } uint32_t OSOrAtomic(virt_ptr> atomic, uint32_t value) { return atomic->fetch_or(value); } uint32_t OSXorAtomic(virt_ptr> atomic, uint32_t value) { return atomic->fetch_xor(value); } BOOL OSTestAndClearAtomic(virt_ptr> atomic, uint32_t bit) { auto previous = OSAndAtomic(atomic, ~(1 << bit)); if (previous & (1 << bit)) { return TRUE; } else { return FALSE; } } BOOL OSTestAndSetAtomic(virt_ptr> atomic, uint32_t bit) { auto previous = OSOrAtomic(atomic, 1 << bit); if (previous & (1 << bit)) { return TRUE; } else { return FALSE; } } void Library::registerAtomicSymbols() { RegisterFunctionExport(OSCompareAndSwapAtomic); RegisterFunctionExport(OSCompareAndSwapAtomicEx); RegisterFunctionExport(OSSwapAtomic); RegisterFunctionExport(OSAddAtomic); RegisterFunctionExport(OSAndAtomic); RegisterFunctionExport(OSOrAtomic); RegisterFunctionExport(OSXorAtomic); RegisterFunctionExport(OSTestAndClearAtomic); RegisterFunctionExport(OSTestAndSetAtomic); } } // namespace coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic.h ================================================ #pragma once #include #include namespace cafe::coreinit { /** * \defgroup coreinit_atomic Atomic * \ingroup coreinit * @{ */ BOOL OSCompareAndSwapAtomic(virt_ptr> atomic, uint32_t compare, uint32_t value); BOOL OSCompareAndSwapAtomicEx(virt_ptr> atomic, uint32_t compare, uint32_t value, virt_ptr old); uint32_t OSSwapAtomic(virt_ptr> atomic, uint32_t value); int32_t OSAddAtomic(virt_ptr> atomic, int32_t value); uint32_t OSAndAtomic(virt_ptr> atomic, uint32_t value); uint32_t OSOrAtomic(virt_ptr> atomic, uint32_t value); uint32_t OSXorAtomic(virt_ptr> atomic, uint32_t value); BOOL OSTestAndClearAtomic(virt_ptr> atomic, uint32_t bit); BOOL OSTestAndSetAtomic(virt_ptr> atomic, uint32_t bit); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic64.cpp ================================================ #include "coreinit.h" #include "coreinit_atomic64.h" #include "coreinit_internal_idlock.h" #include namespace cafe::coreinit { static internal::IdLock sAtomic64Lock; uint64_t OSGetAtomic64(virt_ptr ptr) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto value = *ptr; internal::releaseIdLockWithCoreId(sAtomic64Lock); return value; } uint64_t OSSetAtomic64(virt_ptr ptr, uint64_t value) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto old = *ptr; *ptr = value; internal::releaseIdLockWithCoreId(sAtomic64Lock); return old; } BOOL OSCompareAndSwapAtomic64(virt_ptr ptr, uint64_t compare, uint64_t value) { BOOL result = FALSE; internal::acquireIdLockWithCoreId(sAtomic64Lock); if (*ptr == compare) { *ptr = value; result = TRUE; } internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } BOOL OSCompareAndSwapAtomicEx64(virt_ptr ptr, uint64_t compare, uint64_t value, virt_ptr old) { BOOL result = FALSE; internal::acquireIdLockWithCoreId(sAtomic64Lock); *old = *ptr; if (*ptr == compare) { *ptr = value; result = TRUE; } internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } uint64_t OSSwapAtomic64(virt_ptr ptr, uint64_t value) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto old = *ptr; *ptr = value; internal::releaseIdLockWithCoreId(sAtomic64Lock); return old; } int64_t OSAddAtomic64(virt_ptr ptr, int64_t value) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto result = (*ptr += value); internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } uint64_t OSAndAtomic64(virt_ptr ptr, uint64_t value) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto result = (*ptr &= value); internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } uint64_t OSOrAtomic64(virt_ptr ptr, uint64_t value) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto result = (*ptr |= value); internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } uint64_t OSXorAtomic64(virt_ptr ptr, uint64_t value) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto result = (*ptr ^= value); internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } BOOL OSTestAndClearAtomic64(virt_ptr ptr, uint32_t bit) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto result = get_bit(*ptr, bit) ? TRUE : FALSE; *ptr = clear_bit(*ptr, bit); internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } BOOL OSTestAndSetAtomic64(virt_ptr ptr, uint32_t bit) { internal::acquireIdLockWithCoreId(sAtomic64Lock); auto result = get_bit(*ptr, bit) ? TRUE : FALSE; *ptr = set_bit(*ptr, bit); internal::releaseIdLockWithCoreId(sAtomic64Lock); return result; } void Library::registerAtomic64Symbols() { RegisterFunctionExport(OSGetAtomic64); RegisterFunctionExport(OSSetAtomic64); RegisterFunctionExport(OSCompareAndSwapAtomic64); RegisterFunctionExport(OSCompareAndSwapAtomicEx64); RegisterFunctionExport(OSSwapAtomic64); RegisterFunctionExport(OSAddAtomic64); RegisterFunctionExport(OSAndAtomic64); RegisterFunctionExport(OSOrAtomic64); RegisterFunctionExport(OSXorAtomic64); RegisterFunctionExport(OSTestAndClearAtomic64); RegisterFunctionExport(OSTestAndSetAtomic64); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic64.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_atomic64 Atomic64 * \ingroup coreinit * @{ */ uint64_t OSGetAtomic64(virt_ptr ptr); uint64_t OSSetAtomic64(virt_ptr ptr, uint64_t value); BOOL OSCompareAndSwapAtomic64(virt_ptr ptr, uint64_t compare, uint64_t value); BOOL OSCompareAndSwapAtomicEx64(virt_ptr ptr, uint64_t compare, uint64_t value, virt_ptr old); uint64_t OSSwapAtomic64(virt_ptr ptr, uint64_t value); int64_t OSAddAtomic64(virt_ptr ptr, int64_t value); uint64_t OSAndAtomic64(virt_ptr ptr, uint64_t value); uint64_t OSOrAtomic64(virt_ptr ptr, uint64_t value); uint64_t OSXorAtomic64(virt_ptr ptr, uint64_t value); BOOL OSTestAndClearAtomic64(virt_ptr ptr, uint32_t bit); BOOL OSTestAndSetAtomic64(virt_ptr ptr, uint32_t bit); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_bsp.cpp ================================================ #include "coreinit.h" #include "coreinit_bsp.h" #include "coreinit_ios.h" #include "cafe/cafe_stackobject.h" #include "ios/bsp/ios_bsp_bsp_request.h" #include "ios/bsp/ios_bsp_bsp_response.h" namespace cafe::coreinit { using ios::bsp::BSPCommand; using ios::bsp::BSPRequest; using ios::bsp::BSPResponse; using ios::bsp::BSPResponseGetHardwareVersion; struct BSPIpcBuffer { be2_struct request; UNKNOWN(0x80 - 0x48); be2_struct response; }; struct StaticBspData { be2_val bspHandle; }; static virt_ptr sBspData = nullptr; namespace internal { static BSPError prepareIpcBuffer(std::size_t responseSize, virt_ptr buffer); } BSPError bspInitializeShimInterface() { auto result = IOS_Open(make_stack_string("/dev/bsp"), ios::OpenMode::None); if (IOS_FAILED(result)) { return BSPError::IosError; } sBspData->bspHandle = static_cast(result); return BSPError::OK; } BSPError bspGetHardwareVersion(virt_ptr version) { auto buffer = StackObject { }; auto error = internal::prepareIpcBuffer(sizeof(BSPResponseGetHardwareVersion), buffer); if (error) { return error; } auto result = IOS_Ioctl(sBspData->bspHandle, BSPCommand::GetHardwareVersion, virt_addrof(buffer->request), sizeof(BSPRequest), virt_addrof(buffer->response.getHardwareVersion), sizeof(BSPResponseGetHardwareVersion)); if (IOS_FAILED(result)) { return BSPError::IosError; } *version = buffer->response.getHardwareVersion.hardwareVersion; return BSPError::OK; } namespace internal { static BSPError prepareIpcBuffer(std::size_t responseSize, virt_ptr buffer) { if (responseSize > sizeof(ios::bsp::BSPResponse)) { return BSPError::ResponseTooLarge; } std::memset(virt_addrof(buffer->request).get(), 0, sizeof(BSPRequest)); return BSPError::OK; } BSPError bspShutdownShimInterface() { auto result = IOS_Close(sBspData->bspHandle); if (IOS_FAILED(result)) { return BSPError::IosError; } sBspData->bspHandle = IOSError::Invalid; return BSPError::OK; } } // namespace internal void Library::registerBspSymbols() { RegisterFunctionExport(bspInitializeShimInterface); RegisterFunctionExport(bspGetHardwareVersion); RegisterDataInternal(sBspData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_bsp.h ================================================ #pragma once #include "coreinit_enum.h" #include "ios/bsp/ios_bsp_enum.h" #include namespace cafe::coreinit { using BSPHardwareVersion = ios::bsp::HardwareVersion; BSPError bspInitializeShimInterface(); BSPError bspGetHardwareVersion(virt_ptr version); namespace internal { BSPError bspShutdownShimInterface(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_cache.cpp ================================================ #include "coreinit.h" #include "coreinit_cache.h" #include "coreinit_memory.h" #include "cafe/libraries/gx2/gx2_internal_flush.h" #include #include namespace cafe::coreinit { /** * Equivalent to dcbi instruction. */ void DCInvalidateRange(virt_addr address, uint32_t size) { // Also signal the GPU to update the memory range. gx2::internal::notifyGpuFlush(OSEffectiveToPhysical(address), size); } /** * Equivalent to dcbf, sync, eieio. */ void DCFlushRange(virt_addr address, uint32_t size) { // Also signal the memory store to the GPU. gx2::internal::notifyCpuFlush(OSEffectiveToPhysical(address), size); } /** * Equivalent to dcbst, sync, eieio. */ void DCStoreRange(virt_addr address, uint32_t size) { // Also signal the memory store to the GPU. gx2::internal::notifyCpuFlush(OSEffectiveToPhysical(address), size); } /** * Equivalent to dcbf. */ void DCFlushRangeNoSync(virt_addr address, uint32_t size) { // TODO: DCFlushRangeNoSync } /** * Equivalent to dcbst. */ void DCStoreRangeNoSync(virt_addr address, uint32_t size) { // TODO: DCStoreRangeNoSync } /** * Equivalent to dcbz instruction. */ void DCZeroRange(virt_addr address, uint32_t size) { auto alignedAddr = align_down(address, 32); auto alignedSize = align_up(size, 32); std::memset(virt_cast(alignedAddr).get(), 0, alignedSize); } /** * Equivalent to dcbt instruction. */ void DCTouchRange(virt_addr address, uint32_t size) { // TODO: DCTouchRange } /** * Equivalent to a sync instruction. */ void OSCoherencyBarrier() { } /** * Equivalent to an eieio instruction. */ void OSEnforceInorderIO() { } /** * Checks if a range of memory is "DC Valid"? * * Fails when the range is between 0xE8000000 or 0xEC000000 */ BOOL OSIsAddressRangeDCValid(virt_addr address, uint32_t size) { auto beg = address; auto end = beg + size - 1; if (beg > virt_addr { 0xEC000000 } && end > virt_addr { 0xEC000000 }) { return TRUE; } if (beg < virt_addr { 0xE8000000 } && end < virt_addr { 0xE8000000 }) { return TRUE; } return FALSE; } /** * Equivalent to a eieio, sync. */ void OSMemoryBarrier() { std::atomic_thread_fence(std::memory_order_seq_cst); } void Library::registerCacheSymbols() { RegisterFunctionExport(DCInvalidateRange); RegisterFunctionExport(DCFlushRange); RegisterFunctionExport(DCStoreRange); RegisterFunctionExport(DCFlushRangeNoSync); RegisterFunctionExport(DCStoreRangeNoSync); RegisterFunctionExport(DCZeroRange); RegisterFunctionExport(DCTouchRange); RegisterFunctionExport(OSCoherencyBarrier); RegisterFunctionExport(OSEnforceInorderIO); RegisterFunctionExport(OSIsAddressRangeDCValid); RegisterFunctionExport(OSMemoryBarrier); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_cache.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_cache Cache * \ingroup coreinit * @{ */ void DCInvalidateRange(virt_addr address, uint32_t size); void DCFlushRange(virt_addr address, uint32_t size); void DCStoreRange(virt_addr address, uint32_t size); void DCFlushRangeNoSync(virt_addr address, uint32_t size); void DCStoreRangeNoSync(virt_addr address, uint32_t size); void DCZeroRange(virt_addr address, uint32_t size); void DCTouchRange(virt_addr address, uint32_t size); void OSCoherencyBarrier(); void OSEnforceInorderIO(); BOOL OSIsAddressRangeDCValid(virt_addr address, uint32_t size); void OSMemoryBarrier(); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_clipboard.cpp ================================================ #include "coreinit.h" #include "coreinit_clipboard.h" #include "coreinit_driver.h" #include "coreinit_memory.h" #include "cafe/cafe_stackobject.h" #include namespace cafe::coreinit { constexpr auto MaxClipboardSize = 0x800u; struct ClipboardSaveData { be2_val size; UNKNOWN(0xC); be2_array buffer; }; static virt_ptr sClipboardSaveData = nullptr; BOOL OSCopyFromClipboard(virt_ptr buffer, virt_ptr size) { if (!size) { return FALSE; } if (buffer && *size == 0) { return FALSE; } if (!OSGetForegroundBucket(nullptr, nullptr)) { // Can only use clipboard when application is in foreground. return FALSE; } if (OSDriver_CopyFromSaveArea(10, sClipboardSaveData, sizeof(ClipboardSaveData)) != OSDriver_Error::OK) { return FALSE; } if (buffer && sClipboardSaveData->size) { std::memcpy(buffer.get(), virt_addrof(sClipboardSaveData->buffer).get(), std::min(sClipboardSaveData->size, *size)); } *size = sClipboardSaveData->size; return TRUE; } BOOL OSCopyToClipboard(virt_ptr buffer, uint32_t size) { if (!OSGetForegroundBucket(nullptr, nullptr)) { // Can only use clipboard when application is in foreground. return FALSE; } if (size > MaxClipboardSize) { return FALSE; } if (buffer) { sClipboardSaveData->size = size; std::memcpy(virt_addrof(sClipboardSaveData->buffer).get(), buffer.get(), size); } else { if (size != 0) { return FALSE; } sClipboardSaveData->size = 0u; } if (OSDriver_CopyToSaveArea(10, sClipboardSaveData, sizeof(ClipboardSaveData)) != OSDriver_Error::OK) { return FALSE; } return TRUE; } void Library::registerClipboardSymbols() { RegisterFunctionExport(OSCopyFromClipboard); RegisterFunctionExport(OSCopyToClipboard); RegisterDataInternal(sClipboardSaveData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_clipboard.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_clipboard Clipboard * \ingroup coreinit * @{ */ BOOL OSCopyFromClipboard(virt_ptr buffer, virt_ptr size); BOOL OSCopyToClipboard(virt_ptr buffer, uint32_t size); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_codegen.cpp ================================================ #include "coreinit.h" #include "coreinit_codegen.h" #include "cafe/kernel/cafe_kernel_mmu.h" namespace cafe::coreinit { void OSGetCodegenVirtAddrRange(virt_ptr outAddress, virt_ptr outSize) { auto range = kernel::getCodeGenVirtualRange(); if (outAddress) { *outAddress = range.first; } if (outSize) { *outSize = range.second; } } void Library::registerCodeGenSymbols() { RegisterFunctionExport(OSGetCodegenVirtAddrRange); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_codegen.h ================================================ #pragma once #include namespace cafe::coreinit { void OSGetCodegenVirtAddrRange(virt_ptr outAddress, virt_ptr outSize); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_context.cpp ================================================ #include "coreinit.h" #include "coreinit_context.h" #include "coreinit_core.h" #include "coreinit_systeminfo.h" #include "cafe/kernel/cafe_kernel_context.h" namespace cafe::coreinit { struct StaticContextData { be2_array, 3> userContext; }; static virt_ptr sContextData = nullptr; void OSInitContext(virt_ptr context, virt_addr nia, virt_addr stackBase) { context->tag = OSContext::Tag; context->gpr.fill(0u); context->gpr[1] = stackBase; context->gpr[2] = internal::getSda2Base(); context->gpr[13] = internal::getSdaBase(); context->cr = 0u; context->lr = 0u; context->ctr = 0u; context->xer = 0u; context->nia = nia; context->cia = 0xFFFFFFFFu; context->gqr.fill(0u); context->spinLockCount = uint16_t { 0 }; context->hostContext = nullptr; context->mmcr0 = 0u; context->mmcr1 = 0u; context->state = uint16_t { 0u }; context->coretime.fill(0); context->starttime = 0ll; context->srr0 = 0u; context->srr1 = 0u; context->dsisr = 0u; context->dar = 0u; context->exceptionType = 0u; } void OSSetCurrentContext(virt_ptr context) { auto coreId = OSGetCoreId(); auto prevContext = virt_ptr { nullptr }; if (sContextData->userContext[coreId] && (sContextData->userContext[coreId]->state & 8)) { prevContext = sContextData->userContext[coreId]; kernel::copyContextFromCpu(prevContext); } sContextData->userContext[coreId] = context; kernel::copyContextToCpu(context); } void OSSetCurrentFPUContext(uint32_t unk) { } void OSSetCurrentUserContext(virt_ptr context) { sContextData->userContext[OSGetCoreId()] = context; } void Library::registerContextSymbols() { RegisterFunctionExport(OSInitContext); RegisterFunctionExport(OSSetCurrentContext); RegisterFunctionExport(OSSetCurrentFPUContext); RegisterFunctionExportName("__OSSetCurrentUserContext", OSSetCurrentUserContext); RegisterDataInternal(sContextData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_context.h ================================================ #pragma once #include "cafe/kernel/cafe_kernel_context.h" namespace cafe::coreinit { using OSContext = cafe::kernel::Context; CHECK_SIZE(OSContext, 0x320); void OSInitContext(virt_ptr context, virt_addr nia, virt_addr stackBase); void OSSetCurrentContext(virt_ptr context); void OSSetCurrentFPUContext(uint32_t unk); void OSSetCurrentUserContext(virt_ptr context); } // cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_core.cpp ================================================ #include "coreinit.h" #include "coreinit_core.h" #include namespace cafe::coreinit { /** * Returns the ID of the core currently executing this thread. */ uint32_t OSGetCoreId() { return cpu::this_core::id(); } /** * Returns true if the current core is the main core. */ BOOL OSIsMainCore() { return OSGetCoreId() == OSGetMainCoreId(); } void Library::registerCoreSymbols() { RegisterFunctionExport(OSGetCoreCount); RegisterFunctionExport(OSGetCoreId); RegisterFunctionExport(OSGetMainCoreId); RegisterFunctionExport(OSIsMainCore); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_core.h ================================================ #pragma once #include #include namespace cafe::coreinit { /** * \defgroup coreinit_core Core Identification * \ingroup coreinit * @{ */ static constexpr auto MainCoreId = 1u; static constexpr auto CoreCount = 3u; constexpr uint32_t OSGetCoreCount() { return CoreCount; } constexpr uint32_t OSGetMainCoreId() { return MainCoreId; } uint32_t OSGetCoreId(); BOOL OSIsMainCore(); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_coroutine.cpp ================================================ #include "coreinit.h" #include "coreinit_coroutine.h" #include "coreinit_thread.h" #include "cafe/kernel/cafe_kernel_context.h" namespace cafe::coreinit { void OSInitCoroutine(virt_ptr context, uint32_t entry, uint32_t stack) { context->lr = entry; context->gpr1 = stack; } uint32_t OSLoadCoroutine(virt_ptr coroutine, uint32_t returnValue) { auto thread = OSGetCurrentThread(); auto context = virt_addrof(thread->context); // Switch to new coroutine state context->lr = coroutine->lr; context->cr = coroutine->cr; context->gqr[1] = coroutine->gqr1; context->gpr[1] = coroutine->gpr1; context->gpr[2] = coroutine->gpr2; context->gpr[3] = returnValue; context->gpr[13] = coroutine->gpr13_31[0]; for (auto i = 14; i < 32; i++) { context->gpr[i] = coroutine->gpr13_31[i - 13]; context->fpr[i] = coroutine->fpr14_31[i - 14]; context->psf[i] = static_cast(coroutine->ps14_31[i - 14][1]); } // Copy context to CPU registers cafe::kernel::copyContextToCpu(context); return returnValue; } uint32_t OSSaveCoroutine(virt_ptr coroutine) { // Copy CPU registers to context auto thread = OSGetCurrentThread(); auto context = virt_addrof(thread->context); cafe::kernel::copyContextFromCpu(context); // Update the coroutine with context registers coroutine->lr = context->lr; coroutine->cr = context->cr; coroutine->gqr1 = context->gqr[1]; coroutine->gpr1 = context->gpr[1]; coroutine->gpr2 = context->gpr[2]; coroutine->gpr13_31[0] = context->gpr[13]; for (auto i = 14; i < 32; i++) { coroutine->gpr13_31[i - 13] = context->gpr[i]; coroutine->fpr14_31[i - 14] = context->fpr[i]; coroutine->ps14_31[i - 14][0] = static_cast(context->fpr[i]); coroutine->ps14_31[i - 14][1] = static_cast(context->psf[i]); } return 0; } void OSSwitchCoroutine(virt_ptr from, virt_ptr to) { if (OSSaveCoroutine(from) == 0) { OSLoadCoroutine(to, 1); } } void Library::registerCoroutineSymbols() { RegisterFunctionExport(OSInitCoroutine); RegisterFunctionExport(OSLoadCoroutine); RegisterFunctionExport(OSSaveCoroutine); RegisterFunctionExport(OSSwitchCoroutine); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_coroutine.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_coroutine Coroutines * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct OSCoroutine { be2_val lr; be2_val cr; be2_val gqr1; be2_val gpr1; be2_val gpr2; be2_val gpr13_31[19]; be2_val fpr14_31[18]; be2_val ps14_31[18][2]; }; CHECK_OFFSET(OSCoroutine, 0x000, lr); CHECK_OFFSET(OSCoroutine, 0x004, cr); CHECK_OFFSET(OSCoroutine, 0x008, gqr1); CHECK_OFFSET(OSCoroutine, 0x00C, gpr1); CHECK_OFFSET(OSCoroutine, 0x010, gpr2); CHECK_OFFSET(OSCoroutine, 0x014, gpr13_31); CHECK_OFFSET(OSCoroutine, 0x060, fpr14_31); CHECK_OFFSET(OSCoroutine, 0x0F0, ps14_31); CHECK_SIZE(OSCoroutine, 0x180); #pragma pack(pop) void OSInitCoroutine(virt_ptr context, uint32_t entry, uint32_t stack); uint32_t OSLoadCoroutine(virt_ptr coroutine, uint32_t returnValue); uint32_t OSSaveCoroutine(virt_ptr coroutine); void OSSwitchCoroutine(virt_ptr from, virt_ptr to); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_cosreport.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_snprintf.h" #include "coreinit_systeminfo.h" #include "cafe/cafe_stackobject.h" #include "cafe/cafe_ppc_interface_varargs.h" #include #include #include #include #include namespace cafe::coreinit { static void handleReport(COSReportModule module, COSReportLevel level, virt_ptr msg) { // TODO: This actually goes down to a kernel syscall switch (level) { case COSReportLevel::Error: gLog->error("COSVReport {}: {}", module, msg.get()); break; case COSReportLevel::Warn: gLog->warn("COSVReport {}: {}", module, msg.get()); break; case COSReportLevel::Info: gLog->info("COSVReport {}: {}", module, msg.get()); break; case COSReportLevel::Verbose: gLog->debug("COSVReport {}: {}", module, msg.get()); break; } } void COSVReport(COSReportModule module, COSReportLevel level, virt_ptr fmt, virt_ptr vaList) { auto buffer = StackArray { }; internal::formatStringV(buffer, buffer.size(), fmt, vaList); handleReport(module, level, buffer); } void COSError(COSReportModule module, virt_ptr fmt, var_args args) { auto vaList = make_va_list(args); COSVReport(module, COSReportLevel::Error, fmt, vaList); free_va_list(vaList); } void COSInfo(COSReportModule module, virt_ptr fmt, var_args args) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Info) { auto vaList = make_va_list(args); COSVReport(module, COSReportLevel::Info, fmt, vaList); free_va_list(vaList); } } void COSVerbose(COSReportModule module, virt_ptr fmt, var_args args) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Verbose) { auto vaList = make_va_list(args); COSVReport(module, COSReportLevel::Verbose, fmt, vaList); free_va_list(vaList); } } void COSWarn(COSReportModule module, virt_ptr fmt, var_args args) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Warn) { auto vaList = make_va_list(args); COSVReport(module, COSReportLevel::Warn, fmt, vaList); free_va_list(vaList); } } namespace internal { void COSVReport(COSReportModule module, COSReportLevel level, const std::string_view &msg) { auto buffer = StackArray { }; string_copy(buffer.get(), msg.data(), buffer.size()); buffer[127] = char { 0 }; handleReport(module, level, buffer); } } // namespace internal void Library::registerCosReportSymbols() { RegisterFunctionExport(COSError); RegisterFunctionExport(COSInfo); RegisterFunctionExport(COSVerbose); RegisterFunctionExport(COSVReport); RegisterFunctionExport(COSWarn); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_cosreport.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_systeminfo.h" #include "cafe/cafe_ppc_interface_varargs.h" #include #include #include namespace cafe::coreinit { void COSVReport(COSReportModule module, COSReportLevel level, virt_ptr fmt, virt_ptr vaList); void COSError(COSReportModule module, virt_ptr fmt, var_args va); void COSInfo(COSReportModule module, virt_ptr fmt, var_args va); void COSVerbose(COSReportModule module, virt_ptr fmt, var_args va); void COSWarn(COSReportModule module, virt_ptr fmt, var_args va); namespace internal { void COSVReport(COSReportModule module, COSReportLevel level, const std::string_view &msg); inline void COSError(COSReportModule module, const std::string_view &msg) { COSVReport(module, COSReportLevel::Error, msg); } inline void COSWarn(COSReportModule module, const std::string_view &msg) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Warn) { COSVReport(module, COSReportLevel::Warn, msg); } } inline void COSInfo(COSReportModule module, const std::string_view &msg) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Info) { COSVReport(module, COSReportLevel::Info, msg); } } inline void COSVerbose(COSReportModule module, const std::string_view &msg) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Verbose) { COSVReport(module, COSReportLevel::Verbose, msg); } } } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_device.cpp ================================================ #include "coreinit.h" #include "coreinit_device.h" #include "coreinit_memory.h" namespace cafe::coreinit { constexpr auto NumDevices = 16u; struct StaticDeviceData { be2_array deviceTable; }; static virt_ptr sDeviceData = nullptr; uint16_t OSReadRegister16(OSDeviceID device, uint32_t id) { auto deviceBaseAddr = sDeviceData->deviceTable[device]; if (!deviceBaseAddr) { return 0; } return *virt_cast(deviceBaseAddr + id * 4); } uint32_t OSReadRegister32Ex(OSDeviceID device, uint32_t id) { auto deviceBaseAddr = sDeviceData->deviceTable[device]; if (!deviceBaseAddr) { return 0; } return *virt_cast(deviceBaseAddr + id * 4); } void OSWriteRegister16(OSDeviceID device, uint32_t id, uint16_t value) { auto deviceBaseAddr = sDeviceData->deviceTable[device]; if (deviceBaseAddr) { *virt_cast(deviceBaseAddr + id * 4) = value; } } void OSWriteRegister32Ex(OSDeviceID device, uint32_t id, uint32_t value) { auto deviceBaseAddr = sDeviceData->deviceTable[device]; if (deviceBaseAddr) { *virt_cast(deviceBaseAddr + id * 4) = value; } } namespace internal { void initialiseDeviceTable() { sDeviceData->deviceTable.fill(virt_addr { 0 }); // Registers are disabled for now. #if 0 sDeviceData->deviceTable[OSDeviceID::VI] = OSPhysicalToEffectiveUncached(phys_addr { 0x0C1E0000 }); sDeviceData->deviceTable[OSDeviceID::DSP] = OSPhysicalToEffectiveUncached(phys_addr { 0x0C280000 }); sDeviceData->deviceTable[OSDeviceID::GFXSP] = OSPhysicalToEffectiveUncached(phys_addr { 0x0C200000 }); sDeviceData->deviceTable[OSDeviceID::LATTE_REGS] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D000000 }); sDeviceData->deviceTable[OSDeviceID::LEGACY_SI] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006400 }); sDeviceData->deviceTable[OSDeviceID::LEGACY_AI_I2S3] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006C00 }); sDeviceData->deviceTable[OSDeviceID::LEGACY_AI_I2S5] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006E00 }); sDeviceData->deviceTable[OSDeviceID::LEGACY_EXI] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006800 }); #endif } } // namespace internal void Library::registerDeviceSymbols() { RegisterFunctionExport(OSReadRegister16); RegisterFunctionExport(OSWriteRegister16); RegisterFunctionExportName("__OSReadRegister32Ex", OSReadRegister32Ex); RegisterFunctionExportName("__OSWriteRegister32Ex", OSWriteRegister32Ex); RegisterDataInternal(sDeviceData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_device.h ================================================ #pragma once #include "coreinit_enum.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_device Device * \ingroup coreinit * @{ */ uint16_t OSReadRegister16(OSDeviceID device, uint32_t id); void OSWriteRegister16(OSDeviceID device, uint32_t id, uint16_t value); uint32_t OSReadRegister32Ex(OSDeviceID device, uint32_t id); void OSWriteRegister32Ex(OSDeviceID device, uint32_t id, uint32_t value); namespace internal { void initialiseDeviceTable(); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_driver.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_driver.h" #include "coreinit_dynload.h" #include "coreinit_log.h" #include "coreinit_memory.h" #include "coreinit_osreport.h" #include "coreinit_spinlock.h" #include "coreinit_systemheap.h" #include "coreinit_systeminfo.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/kernel/cafe_kernel_userdrivers.h" #include "cafe/libraries/cafe_hle_stub.h" #include #include #include #include namespace cafe::coreinit { struct StaticDriverData { be2_val minUnk0x10; be2_val maxUnk0x10; be2_struct lock; be2_val didInit; be2_val numRegistered; be2_virt_ptr registeredDrivers; be2_virt_ptr actionCallbackDriver; }; static virt_ptr sDriverData = nullptr; /** * Register a driver. * * \param moduleHandle * The module handle to associate this driver with. * * \param unk1 * Unknown, maybe something like a priority? * * \param driverInterface * Implementation of the driver interface functions. * * \param userDriverId * A user provided unique id to identify this driver. * * \param[out] outCurrentUpid * The unique process id of the current process. * * \param[out] outOwnerUpid * The unique process id of the process which first registered the driver with * the kernel. * * \param[out] outDidInit * Set to 1 after internal:driverInitialise has been called, 0 before. * * \return * Returns OSDriver_Error::OK on success, an error code otherwise. */ OSDriver_Error OSDriver_Register(OSDynLoad_ModuleHandle moduleHandle, uint32_t unk1, // Something like a priority virt_ptr driverInterface, OSDriver_UserDriverId userDriverId, virt_ptr outRegisteredUpid, virt_ptr outOwnerUpid, virt_ptr outDidInit) { auto callerModule = StackObject { }; auto error = OSDriver_Error::OK; auto dynloadError = OSDynLoad_Error::OK; if (outRegisteredUpid) { *outRegisteredUpid = kernel::UniqueProcessId::Kernel; } if (outOwnerUpid) { *outOwnerUpid = kernel::UniqueProcessId::Kernel; } if (outDidInit) { *outDidInit = sDriverData->didInit; } if (!driverInterface || !driverInterface->getName) { return OSDriver_Error::InvalidArgument; } // Get the drivers name through the driverInterface auto name = cafe::invoke(cpu::this_core::state(), driverInterface->getName, userDriverId); if (!name) { return OSDriver_Error::InvalidArgument; } auto nameLen = static_cast(strlen(name.get())); if (nameLen == 0 || nameLen >= 64) { return OSDriver_Error::InvalidArgument; } // Note on real coreinit.rpl it will try acquire callerModule from LR // regardless of whether moduleHandle is set, however that makes it // difficult for our HLE functions to call this function. if (!moduleHandle) { // Get the module handle from the caller of this function dynloadError = OSDynLoad_AcquireContainingModule( virt_cast(virt_addr { cpu::this_core::state()->lr }), OSDynLoad_SectionType::CodeOnly, callerModule); if (dynloadError != OSDynLoad_Error::OK) { internal::OSPanic("OSDrivers.c", 288, "***OSDriver_Register could not find caller module.\n"); } moduleHandle = *callerModule; } // Allocate memory for the driver structure auto driver = virt_cast(OSAllocFromSystem(sizeof(OSDriver), 4)); if (internal::isAppDebugLevelNotice()) { internal::COSInfo( COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:DRIVER_REG,ALLOC,=\"{}\",-{}", driver, sizeof(OSDriver))); } if (!driver) { if (internal::isAppDebugLevelNotice()) { internal::dumpSystemHeap(); } OSDynLoad_Release(*callerModule); return OSDriver_Error::OutOfSysMemory; } memset(driver, 0, sizeof(OSDriver)); // Get module handle for driverInterface.getName dynloadError = OSDynLoad_AcquireContainingModule( virt_cast(driverInterface->getName.getAddress()), OSDynLoad_SectionType::CodeOnly, virt_addrof(driver->interfaceModuleHandles[0])); if (dynloadError != OSDynLoad_Error::OK) { internal::COSWarn( COSReportModule::Unknown1, fmt::format( "*** OSDriver_Register - failed to acquire containing module for driver \"{}\" Name() @ {}", name, driverInterface->getName)); error = static_cast(dynloadError); goto error; } // Get module handle for driverInterface.onInit if (driverInterface->onInit) { dynloadError = OSDynLoad_AcquireContainingModule( virt_cast(driverInterface->onInit.getAddress()), OSDynLoad_SectionType::CodeOnly, virt_addrof(driver->interfaceModuleHandles[1])); if (dynloadError != OSDynLoad_Error::OK) { internal::COSWarn( COSReportModule::Unknown1, fmt::format( "*** OSDriver_Register - failed to acquire containing module for driver \"{}\" AutoInit() @ {}", name, driverInterface->onInit)); error = static_cast(dynloadError); goto error; } } // Get module handle for driverInterface.onAcquiredForeground if (driverInterface->onAcquiredForeground) { dynloadError = OSDynLoad_AcquireContainingModule( virt_cast(driverInterface->onAcquiredForeground.getAddress()), OSDynLoad_SectionType::CodeOnly, virt_addrof(driver->interfaceModuleHandles[2])); if (dynloadError != OSDynLoad_Error::OK) { internal::COSWarn( COSReportModule::Unknown1, fmt::format( "*** OSDriver_Register - failed to acquire containing module for driver \"{}\" OnAcquiredForeground() @ {}", name, driverInterface->onAcquiredForeground)); error = static_cast(dynloadError); goto error; } } // Get module handle for driverInterface.onReleasedForeground if (driverInterface->onReleasedForeground) { dynloadError = OSDynLoad_AcquireContainingModule( virt_cast(driverInterface->onReleasedForeground.getAddress()), OSDynLoad_SectionType::CodeOnly, virt_addrof(driver->interfaceModuleHandles[3])); if (dynloadError != OSDynLoad_Error::OK) { internal::COSWarn( COSReportModule::Unknown1, fmt::format( "*** OSDriver_Register - failed to acquire containing module for driver \"{}\" OnReleasedForeground() @ {}", name, driverInterface->onReleasedForeground)); error = static_cast(dynloadError); goto error; } } // Get module handle for driverInterface.onDone if (driverInterface->onDone) { dynloadError = OSDynLoad_AcquireContainingModule( virt_cast(driverInterface->onDone.getAddress()), OSDynLoad_SectionType::CodeOnly, virt_addrof(driver->interfaceModuleHandles[4])); if (dynloadError != OSDynLoad_Error::OK) { internal::COSWarn( COSReportModule::Unknown1, fmt::format( "*** OSDriver_Register - failed to acquire containing module for driver \"{}\" AutoDone() @ {}", name, driverInterface->onDone)); error = static_cast(dynloadError); goto error; } } OSUninterruptibleSpinLock_Acquire(virt_addrof(sDriverData->lock)); // Check if a driver has already been registered with the same name for (auto other = sDriverData->registeredDrivers; other; other = other->next) { auto otherName = cafe::invoke(cpu::this_core::state(), other->interfaceFunctions.getName, other->userDriverId); if (iequals(otherName.get(), name.get())) { error = OSDriver_Error::AlreadyRegistered; OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock)); goto error; } } dynloadError = static_cast( kernel::registerUserDriver(name, nameLen, virt_addrof(driver->registeredUpid), virt_addrof(driver->ownerUpid))); OSLogPrintf(0, 1, 0, "%s: Reg=%s, ms=%d", "OSDriver_Register", name, 0); if (dynloadError == OSDynLoad_Error::OK) { // Initialise driver data if (sDriverData->didInit) { driver->unk0x08 = 1u; driver->inForeground = OSGetForegroundBucket(nullptr, nullptr); } driver->coreID = cpu::this_core::id(); driver->moduleHandle = moduleHandle; driver->interfaceFunctions = *driverInterface; driver->userDriverId = userDriverId; driver->unk0x10 = unk1; if (driver->unk0x10 < sDriverData->minUnk0x10) { sDriverData->minUnk0x10 = driver->unk0x10; } if (driver->unk0x10 > sDriverData->maxUnk0x10) { sDriverData->maxUnk0x10 = driver->unk0x10; } // Insert to front of list driver->next = sDriverData->registeredDrivers; sDriverData->registeredDrivers = driver; if (outRegisteredUpid) { *outRegisteredUpid = driver->registeredUpid; } if (outOwnerUpid) { *outOwnerUpid = driver->ownerUpid; } sDriverData->numRegistered++; } OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock)); // Deduplicate dynload allocated handles for (auto i = 0u; i < driver->interfaceModuleHandles.size(); ++i) { if (driver->interfaceModuleHandles[i] == moduleHandle) { OSDynLoad_Release(driver->interfaceModuleHandles[i]); driver->interfaceModuleHandles[i] = 0u; } } OSDynLoad_Release(*callerModule); return OSDriver_Error::OK; error: for (auto i = 0u; i < driver->interfaceModuleHandles.size(); ++i) { if (driver->interfaceModuleHandles[i]) { OSDynLoad_Release(driver->interfaceModuleHandles[i]); } } OSDynLoad_Release(*callerModule); if (internal::isAppDebugLevelNotice()) { internal::COSInfo( COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:DRIVER_REG, FREE, =\"{}\",{}", driver, sizeof(OSDriver))); } OSFreeToSystem(driver); return error; } /** * Deregister a driver. * * \param moduleHandle * Module handle the driver was registered under. * * \param userDriverId * The user's driver id for the driver to deregister. * * \return * Returns OSDriver_Error::OK on success, an error code otherwise. */ OSDriver_Error OSDriver_Deregister(OSDynLoad_ModuleHandle moduleHandle, OSDriver_UserDriverId userDriverId) { if (!sDriverData->numRegistered) { return OSDriver_Error::DriverNotFound; } OSUninterruptibleSpinLock_Acquire(virt_addrof(sDriverData->lock)); // Find the driver in the registered driver linked list auto prev = virt_ptr { nullptr }; auto driver = sDriverData->registeredDrivers; while (driver) { if (driver->moduleHandle == moduleHandle && driver->userDriverId == userDriverId) { break; } prev = driver; driver = driver->next; } if (!driver) { OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock)); return OSDriver_Error::DriverNotFound; } // Check if we are trying to deregister from "inside action callback" if (sDriverData->actionCallbackDriver == driver) { internal::COSWarn(COSReportModule::Unknown1, "***OSDriver_Deregister() of self from inside action callback!\n"); sDriverData->actionCallbackDriver = nullptr; } // Remove the driver from the linked list if (prev) { prev->next = driver->next; } else { sDriverData->registeredDrivers = driver->next; } // Grab the drivers name for kernel deregister auto name = cafe::invoke(cpu::this_core::state(), driver->interfaceFunctions.getName, userDriverId); OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock)); auto startTicks = OSGetTick(); // Deregister driver with the kernel if (driver->registeredUpid == driver->ownerUpid) { kernel::deregisterUserDriver(name, static_cast(strlen(name.get()))); } // Free the dynload handles for (auto handle : driver->interfaceModuleHandles) { if (handle) { OSDynLoad_Release(handle); } } if (internal::isAppDebugLevelNotice()) { internal::COSInfo( COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:DRIVER_REG, FREE, =\"{}\",{}\n", driver, sizeof(OSDriver))); } // Free the driver OSFreeToSystem(driver); OSLogPrintf(0, 1, 0, "%s: Reg=%s, ms=%d", "OSDriver_Deregister", name, internal::ticksToMs(OSGetTick() - startTicks)); return OSDriver_Error::OK; } OSDriver_Error OSDriver_CopyFromSaveArea(OSDriver_UserDriverId driverId, virt_ptr data, uint32_t size) { decaf_warn_stub(); std::memset(data.get(), 0, size); return OSDriver_Error::OK; } OSDriver_Error OSDriver_CopyToSaveArea(OSDriver_UserDriverId driverId, virt_ptr data, uint32_t size) { decaf_warn_stub(); return OSDriver_Error::OK; } namespace internal { void driverOnInit() { sDriverData->didInit = TRUE; if (!sDriverData->numRegistered) { return; } // TODO: Driver init should be called from driver action thread for (auto driver = sDriverData->registeredDrivers; driver; driver = driver->next) { if (driver->interfaceFunctions.onInit) { cafe::invoke(cpu::this_core::state(), driver->interfaceFunctions.onInit, driver->userDriverId); } } } void driverOnDone() { // TODO: OSDriver OnDone } } // namespace internal void Library::registerDriverSymbols() { RegisterFunctionExport(OSDriver_Register); RegisterFunctionExport(OSDriver_Deregister); RegisterFunctionExport(OSDriver_CopyFromSaveArea); RegisterFunctionExport(OSDriver_CopyToSaveArea); RegisterDataInternal(sDriverData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_driver.h ================================================ #pragma once #include "coreinit_dynload.h" #include "coreinit_enum.h" #include "cafe/kernel/cafe_kernel_processid.h" #include namespace cafe::coreinit { #pragma pack(push, 1) using OSDriver_UserDriverId = uint32_t; using OSDriver_GetNameFn = virt_func_ptr(OSDriver_UserDriverId)>; using OSDriver_OnInitFn = virt_func_ptr; using OSDriver_OnAcquiredForegroundFn = virt_func_ptr; using OSDriver_OnReleasedForegroundFn = virt_func_ptr; using OSDriver_OnDoneFn = virt_func_ptr; struct OSDriverInterface { //! Return the driver name be2_val getName; //! Called to initialise the driver. be2_val onInit; //! Called when application is brought to foreground. be2_val onAcquiredForeground; //! Called when application is sent to background. be2_val onReleasedForeground; //! Called when driver is done. be2_val onDone; }; struct OSDriver { //! Module handle of current RPL. be2_val moduleHandle; //! First argument passed to all driver interface functions. be2_val userDriverId; //! Unknown, set to 1 in OSDriver_Register. be2_val unk0x08; //! Whether OSDriver_Register was called when process is in foreground. be2_val inForeground; //! Unknown, value set from r4 of OSDriver_Register. be2_val unk0x10; //!Core on which OSDriver_Register was called. be2_val coreID; //! Interface function pointers. be2_struct interfaceFunctions; //! Module handles for each interface function. be2_array interfaceModuleHandles; //! The current upid be2_val registeredUpid; //! The current owner of this driver reigstered with the kernel be2_val ownerUpid; //! Pointer to next OSDriver in linked list. be2_virt_ptr next; }; CHECK_OFFSET(OSDriver, 0x00, moduleHandle); CHECK_OFFSET(OSDriver, 0x04, userDriverId); CHECK_OFFSET(OSDriver, 0x08, unk0x08); CHECK_OFFSET(OSDriver, 0x0C, inForeground); CHECK_OFFSET(OSDriver, 0x10, unk0x10); CHECK_OFFSET(OSDriver, 0x14, coreID); CHECK_OFFSET(OSDriver, 0x18, interfaceFunctions); CHECK_OFFSET(OSDriver, 0x2C, interfaceModuleHandles); CHECK_OFFSET(OSDriver, 0x40, registeredUpid); CHECK_OFFSET(OSDriver, 0x44, ownerUpid); CHECK_OFFSET(OSDriver, 0x48, next); CHECK_SIZE(OSDriver, 0x4C); #pragma pack(pop) OSDriver_Error OSDriver_Register(OSDynLoad_ModuleHandle moduleHandle, uint32_t unk1, virt_ptr driverInterface, OSDriver_UserDriverId userDriverId, virt_ptr outRegisteredUpid, virt_ptr outOwnerUpid, virt_ptr outDidOSDriverInit); OSDriver_Error OSDriver_Deregister(OSDynLoad_ModuleHandle moduleHandle, OSDriver_UserDriverId userDriverId); OSDriver_Error OSDriver_CopyFromSaveArea(OSDriver_UserDriverId driverId, virt_ptr data, uint32_t size); OSDriver_Error OSDriver_CopyToSaveArea(OSDriver_UserDriverId driverId, virt_ptr data, uint32_t size); namespace internal { void driverOnInit(); void driverOnDone(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_dynload.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_dynload.h" #include "coreinit_ghs.h" #include "coreinit_handle.h" #include "coreinit_mutex.h" #include "coreinit_memheap.h" #include "coreinit_osreport.h" #include "coreinit_systemheap.h" #include "coreinit_systeminfo.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle.h" #include "cafe/loader/cafe_loader_rpl.h" #include "cafe/kernel/cafe_kernel_info.h" #include "cafe/kernel/cafe_kernel_loader.h" #include "cafe/kernel/cafe_kernel_mmu.h" #include "debug_api/debug_api_controller.h" #include #include #include #include namespace cafe::coreinit { using OSDynLoad_RplEntryFn = virt_func_ptr< int32_t (OSDynLoad_ModuleHandle moduleHandle, OSDynLoad_EntryReason reason)>; using OSDynLoad_InitDefaultHeapFn = virt_func_ptr< void(virt_ptr mem1, virt_ptr foreground, virt_ptr mem2)>; constexpr auto MaxTlsModuleIndex = uint32_t { 0x7fff }; struct RPL_DATA { be2_val handle; be2_virt_ptr loaderHandle; be2_virt_ptr moduleName; be2_val moduleNameLen; be2_val sectionInfoCount; be2_virt_ptr sectionInfo; be2_virt_ptr> importModules; be2_val importModuleCount; be2_val userFileInfoSize; be2_virt_ptr userFileInfo; be2_virt_ptr notifyData; be2_val entryPoint; be2_val dataSectionSize; be2_virt_ptr dataSection; be2_val loadSectionSize; be2_virt_ptr loadSection; be2_val dynLoadFreeFn; be2_val codeExports; be2_val numCodeExports; be2_val dataExports; be2_val numDataExports; be2_virt_ptr next; UNKNOWN(0x94 - 0x58); }; struct StaticDynLoadData { be2_array loaderLockName; be2_val allocFn; be2_val freeFn; be2_val tlsAllocLocked; be2_val tlsHeader; be2_val tlsModuleIndex; be2_val tlsAllocFn; be2_val tlsFreeFn; be2_virt_ptr notifyCallbacks; be2_struct handleTable; be2_array coreinitModuleName; be2_virt_ptr rplDataList; be2_virt_ptr rpxData; be2_virt_ptr rpxName; be2_val rpxNameLength; be2_val inModuleEntryPoint; be2_virt_ptr linkingRplList; be2_val preInitMem1Heap; be2_val preInitMem2Heap; be2_val preInitForegroundHeap; be2_val numAllocations; be2_val totalAllocatedBytes; be2_val numModules; be2_virt_ptr> modules; struct FatalErrorInfo { be2_val msgType; be2_val error; be2_val loaderError; be2_val line; be2_array funcName; }; be2_struct fatalError; }; static virt_ptr sDynLoadData = nullptr; static virt_ptr sDynLoad_LoaderLock = nullptr; namespace internal { static OSDynLoad_Error doImports(virt_ptr rplData); static void release(OSDynLoad_ModuleHandle moduleHandle, virt_ptr> unloadedModuleList); /** * Perform an allocation for the OSDYNLOAD_HEAP heap. */ static OSDynLoad_Error dynLoadHeapAlloc(const char *name, OSDynLoad_AllocFn allocFn, uint32_t size, int32_t align, virt_ptr> outPtr) { auto error = cafe::invoke(cpu::this_core::state(), allocFn, size, align, outPtr); if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "OSDYNLOAD_HEAP:{},ALLOC,=\"{}\",-{}", name, *outPtr, size)); } return error; } /** * Perform a free for the OSDYNLOAD_HEAP heap. */ static void dynLoadHeapFree(const char *name, OSDynLoad_FreeFn freeFn, virt_ptr ptr, uint32_t size) { if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "OSDYNLOAD_HEAP:{},FREE,=\"{}\",-{}", name, ptr, size)); } cafe::invoke(cpu::this_core::state(), freeFn, ptr); } /** * Perform an allocation for the RPL_SYSHEAP heap. */ static virt_ptr rplSysHeapAlloc(const char *name, uint32_t size, int32_t align) { auto ptr = OSAllocFromSystem(size, align); if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:{},ALLOC,=\"{}\",-{}", name, ptr, size)); } return ptr; } /** * Perform a free for the RPL_SYSHEAP heap. */ static void rplSysHeapFree(const char *name, virt_ptr ptr, uint32_t size) { if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:{},FREE,=\"{}\",-{}", name, ptr, size)); } OSFreeToSystem(ptr); } /** * Set dynload fatal error info. */ static void setFatalErrorInfo(uint32_t fatalMsgType, uint32_t fatalErr, uint32_t loaderError, uint32_t fatalLine, virt_ptr fatalFunction) { if (sDynLoadData->fatalError.error) { return; } sDynLoadData->fatalError.msgType = fatalMsgType; sDynLoadData->fatalError.error = fatalErr; sDynLoadData->fatalError.loaderError = loaderError; sDynLoadData->fatalError.line = fatalLine; sDynLoadData->fatalError.funcName = fatalFunction.get(); } /** * Set dynload fatal error info with some sort of deviceLocation magic. */ static void setFatalErrorInfo2(uint32_t deviceLocation, uint32_t loaderError, bool a3, uint32_t fatalLine, std::string_view fatalFunction) { if (sDynLoadData->fatalError.error) { return; } switch (deviceLocation) { case 0: { if (a3) { sDynLoadData->fatalError.msgType = 2u; sDynLoadData->fatalError.error = 1603203u; } else { sDynLoadData->fatalError.msgType = 1u; sDynLoadData->fatalError.error = 1603200u; } sDynLoadData->fatalError.loaderError = loaderError; sDynLoadData->fatalError.line = fatalLine; sDynLoadData->fatalError.funcName = fatalFunction; break; } case 1: { if (a3) { sDynLoadData->fatalError.msgType = 8u; sDynLoadData->fatalError.error = 1603204u; } else { sDynLoadData->fatalError.msgType = 1u; sDynLoadData->fatalError.error = 1603201u; } sDynLoadData->fatalError.loaderError = loaderError; sDynLoadData->fatalError.line = fatalLine; sDynLoadData->fatalError.funcName = fatalFunction; break; } case 2: { if (a3) { sDynLoadData->fatalError.msgType = 5u; sDynLoadData->fatalError.error = 1604205u; } else { sDynLoadData->fatalError.msgType = 1u; sDynLoadData->fatalError.error = 1602202u; } sDynLoadData->fatalError.loaderError = loaderError; sDynLoadData->fatalError.line = fatalLine; sDynLoadData->fatalError.funcName = fatalFunction; break; } default: OSPanic("OSDynLoad_DynInit.c", 104, "***Unknown device location error."); } } /** * Clear the dynload fatal error info. */ static void resetFatalErrorInfo() { std::memset(virt_addrof(sDynLoadData->fatalError).get(), 0, sizeof(sDynLoadData->fatalError)); } /** * Report a fatal dynload error to the kernel. */ static void reportFatalError() { auto error = StackObject { }; if (!sDynLoadData->fatalError.error) { OSPanic("OSDynLoad_DynInit.c", 122, "__OSDynLoad_InitFromCoreInit() - gFatalInfo unexpectantly empty!!!."); } error->errorCode = sDynLoadData->fatalError.error; error->internalErrorCode = sDynLoadData->fatalError.loaderError; error->messageType = sDynLoadData->fatalError.msgType; error->processId = static_cast(OSGetUPID()); OSSendFatalError(error, virt_addrof(sDynLoadData->fatalError.funcName), sDynLoadData->fatalError.line); } static OSDynLoad_Error internalAcquire(virt_ptr name, virt_ptr> outRplData, virt_ptr outNumEntryModules, virt_ptr>> outEntryModules, bool doLoad); /** * Run all the entry points in the given entry points array. */ static int32_t runEntryPoints(uint32_t numEntryModules, virt_ptr> entryModules) { auto error = 0; for (auto i = 0u; i < numEntryModules; ++i) { auto entry = virt_func_cast(entryModules[i]->entryPoint); error = cafe::invoke(cpu::this_core::state(), entry, entryModules[i]->handle, OSDynLoad_EntryReason::Loaded); if (error) { COSError(COSReportModule::Unknown2, fmt::format( "*** ERROR: Module \"{}\" returned error code {} ({} from its entrypoint on load.", entryModules[i]->moduleName, error, error)); setFatalErrorInfo2(entryModules[i]->userFileInfo->titleLocation, error, false, 44, "__OSDynLoad_RunEntryPoints"); } entryModules[i]->entryPoint = virt_addr { 0 }; } if (entryModules) { rplSysHeapFree("RPL_ENTRYPOINTS_ARRAY", entryModules, 4 * numEntryModules); } return error; } /** * Load modulePath. */ static OSDynLoad_Error internalAcquire2(virt_ptr modulePath, virt_ptr outModuleHandle, BOOL isCheckLoaded) { auto rplData = StackObject> { }; auto numEntryModules = StackObject { }; auto entryModules = StackObject>> { }; *rplData = nullptr; *numEntryModules = 0u; *entryModules = nullptr; if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:{} START,{}", isCheckLoaded ? "CHECK_LOADED" : "ACQUIRE", modulePath)); } if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "SYSTEM_HEAP:{} START,{}", isCheckLoaded ? "CHECK_LOADED" : "ACQUIRE", modulePath)); } if (!outModuleHandle) { return OSDynLoad_Error::InvalidAcquirePtr; } if (!modulePath) { return OSDynLoad_Error::InvalidModuleNamePtr; } if (!modulePath[0]) { return OSDynLoad_Error::InvalidModuleName; } resetFatalErrorInfo(); if (auto error = internalAcquire(modulePath, rplData, numEntryModules, entryModules, isCheckLoaded)) { if (*entryModules) { rplSysHeapFree("ENTRYPOINTS_ARRAY", entryModules, isCheckLoaded); } if (!isCheckLoaded) { COSError(COSReportModule::Unknown2, fmt::format( "Error: Could not load acquired RPL \"{}\".", modulePath)); } *outModuleHandle = 0u; return error; } *outModuleHandle = (*rplData)->handle; if (!isCheckLoaded) { if (runEntryPoints(*numEntryModules, *entryModules) != OSDynLoad_Error::OK) { if (*outModuleHandle) { OSDynLoad_Release(*outModuleHandle); } *outModuleHandle = 0u; return OSDynLoad_Error::RunEntryPointError; } } resetFatalErrorInfo(); if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:{} END,{}", isCheckLoaded ? "CHECK_LOADED" : "ACQUIRE", modulePath)); } if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "SYSTEM_HEAP:{} END,{}", isCheckLoaded ? "CHECK_LOADED" : "ACQUIRE", modulePath)); } return OSDynLoad_Error::OK; } /** * Count the number of notify callbacks registered. */ static uint32_t getNotifyCallbackCount() { auto count = 0u; for (auto itr = sDynLoadData->notifyCallbacks; itr; itr = itr->next) { ++count; } return count; } /** * Unload the given module. */ static void unloadModule(virt_ptr rplData, virt_ptr> unloadedModuleList) { // Run the RPL entry point with Unloaded if (rplData->entryPoint) { auto entry = virt_func_cast(rplData->entryPoint); cafe::invoke(cpu::this_core::state(), entry, rplData->handle, OSDynLoad_EntryReason::Unloaded); } if (rplData->notifyData) { // TODO: Check for alarms, threads, interrupt callbacks, ipc callbacks // which reference this module } // Release all imported modules for (auto i = 0u; i < rplData->importModuleCount; ++i) { if (rplData->importModules[i] && rplData->importModules[i] != virt_cast(virt_addr { 0xFFFFFFFFu })) { release(rplData->importModules[i]->handle, unloadedModuleList); rplData->importModules[i] = nullptr; } } // Remove from the loaded module list auto prev = virt_ptr { nullptr }; for (auto itr = sDynLoadData->rplDataList; itr; itr = itr->next) { if (itr == rplData) { break; } prev = itr; } if (prev) { prev->next = rplData->next; } else { sDynLoadData->rplDataList = rplData->next; } // Insert into unloaded module list rplData->next = *unloadedModuleList; *unloadedModuleList = rplData; } /** * Free allocated memory for a loaded module. */ static void internalPurge(virt_ptr rpl) { if (!rpl->handle) { internal::OSPanic("OSDynLoad_Purge.c", 23, "OSDynLoad_InternalPurge - nonzero handle on purge."); } if (rpl->loaderHandle) { kernel::loaderPurge(rpl->loaderHandle); rpl->loaderHandle = nullptr; } rpl->entryPoint = virt_addr { 0 }; rpl->codeExports = virt_addr { 0 }; rpl->dataExports = virt_addr { 0 }; rpl->numDataExports = virt_addr { 0 }; rpl->numCodeExports = virt_addr { 0 }; // Free dataSection if (rpl->dataSection && rpl->dynLoadFreeFn) { dynLoadHeapFree("RPL_DATA_AREA", rpl->dynLoadFreeFn, rpl->dataSection, rpl->dataSectionSize); sDynLoadData->totalAllocatedBytes -= rpl->dataSectionSize; sDynLoadData->numAllocations--; } rpl->dataSectionSize = 0u; // Free loadSection if (rpl->loadSection && rpl->dynLoadFreeFn) { dynLoadHeapFree("RPL_DATA_AREA", rpl->dynLoadFreeFn, rpl->loadSection, rpl->loadSectionSize); sDynLoadData->totalAllocatedBytes -= rpl->loadSectionSize; sDynLoadData->numAllocations--; } rpl->loadSectionSize = 0u; // Free notifyData if (rpl->notifyData) { if (rpl->notifyData->name) { auto pathStringLength = 0u; if (rpl->userFileInfo) { pathStringLength = rpl->userFileInfo->pathStringLength; } rplSysHeapFree("RPL_NOTIFY_NAME", rpl->notifyData->name, pathStringLength); } rplSysHeapFree("RPL_NOTIFY", rpl->notifyData, sizeof(OSDynLoad_NotifyData)); } rpl->notifyData = nullptr; // Free file info if (rpl->userFileInfo && rpl->dynLoadFreeFn) { dynLoadHeapFree("RPL_FILE_INFO", rpl->dynLoadFreeFn, rpl->userFileInfo, rpl->userFileInfoSize); sDynLoadData->totalAllocatedBytes -= rpl->userFileInfoSize; sDynLoadData->numAllocations--; } rpl->userFileInfo = nullptr; rpl->userFileInfoSize = 0u; // Free section info if (rpl->sectionInfo) { rplSysHeapFree("RPL_SEC_INFO", rpl->sectionInfo, sizeof(loader::LOADER_SectionInfo) * rpl->sectionInfoCount); } rpl->sectionInfo = nullptr; rpl->sectionInfoCount = 0u; // Free imported modules array if (rpl->importModules) { rplSysHeapFree("RPL_SEC_INFO", rpl->importModules, 4 * rpl->importModuleCount); } rpl->importModules = nullptr; rpl->importModuleCount = 0u; rpl->dynLoadFreeFn = nullptr; // Free module name if (rpl->moduleName) { rplSysHeapFree("RPL_NAME", rpl->moduleName, align_up(rpl->moduleNameLen, 4)); } rpl->moduleName = nullptr; rpl->moduleNameLen = 0u; // And finally.. free the RPL data itself. std::memset(rpl.get(), 0, sizeof(RPL_DATA)); rplSysHeapFree("RPL_DATA", rpl, sizeof(RPL_DATA)); } /** * Release a loaded module. */ static void release(OSDynLoad_ModuleHandle moduleHandle, virt_ptr> unloadedModuleList) { auto userData1 = StackObject> { }; auto refCount = StackObject { }; auto rootUnloadedModuleList = StackObject> { }; if (moduleHandle == OSDynLoad_CurrentModuleHandle || !sDynLoadData->rplDataList) { return; } // First we translate and add ref to get the userData1 if (auto error = OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable), moduleHandle, userData1, nullptr)) { COSError(COSReportModule::Unknown2, fmt::format( "*** OSDynLoad_Release: RPL Module handle {} was not valid@0. (err=0x{:08X})", moduleHandle, error)); return; } auto rplData = virt_cast(userData1); // Release our just acquired reference if (auto error = OSHandle_Release(virt_addrof(sDynLoadData->handleTable), moduleHandle, refCount)) { COSError(COSReportModule::Unknown2, fmt::format( "*** OSDynLoad_Release: RPL Module handle {} was not valid@1. (err=0x{:08X})", moduleHandle, error)); return; } if (refCount == 0) { COSError(COSReportModule::Unknown2, fmt::format( "*** OSDynLoad_Release: reference count error on OSDynLoad_ModuleHandle. ({})", *refCount)); return; } // Now do the actual release if (auto error = OSHandle_Release(virt_addrof(sDynLoadData->handleTable), moduleHandle, refCount)) { COSError(COSReportModule::Unknown2, fmt::format( "*** OSDynLoad_Release: RPL Module handle {} was not valid@2. (err=0x{:08X})", moduleHandle, error)); return; } if (refCount) { // Nothing to do, ref count still >0 return; } OSLockMutex(sDynLoad_LoaderLock); auto isFirstUnloadModule = false; if (!unloadedModuleList) { unloadedModuleList = rootUnloadedModuleList; *rootUnloadedModuleList = nullptr; isFirstUnloadModule = true; } auto notifyCallbackCount = 0u; auto notifyCallbackArray = virt_ptr { nullptr }; if (isFirstUnloadModule) { notifyCallbackCount = getNotifyCallbackCount(); if (notifyCallbackCount) { auto allocSize = static_cast( notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback)); notifyCallbackArray = virt_cast( rplSysHeapAlloc("RPL_NOTIFY_ARRAY", allocSize, 4)); if (notifyCallbackArray) { auto notifyCallback = sDynLoadData->notifyCallbacks; for (auto i = 0u; notifyCallback; ++i) { std::memcpy(virt_addrof(notifyCallbackArray[i]).get(), notifyCallback.get(), sizeof(OSDynLoad_NotifyCallback)); notifyCallback = notifyCallback->next; } } } } unloadModule(rplData, unloadedModuleList); if (isFirstUnloadModule) { // Call notify callbacks for all the unloaded modules and free their // notify data for (auto rpl = *unloadedModuleList; rpl; rpl = rpl->next) { if (notifyCallbackArray) { for (auto i = 0u; i < notifyCallbackCount; ++i) { cafe::invoke(cpu::this_core::state(), notifyCallbackArray[i].notifyFn, rpl->handle, notifyCallbackArray->userArg1, OSDynLoad_NotifyEvent::Unloaded, rpl->notifyData); } } if (rpl->notifyData->name) { auto pathStringLength = 0u; if (rpl->userFileInfo) { pathStringLength = rpl->userFileInfo->pathStringLength; } rplSysHeapFree("RPL_NOTIFY", rpl->notifyData->name, pathStringLength); } rplSysHeapFree("RPL_NOTIFY", rpl->notifyData, sizeof(OSDynLoad_NotifyData)); rpl->notifyData = nullptr; } if (notifyCallbackArray) { rplSysHeapFree("RPL_NOTIFY_ARRAY", notifyCallbackArray, notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback)); } for (auto rpl = *unloadedModuleList; rpl; rpl = rpl->next) { internalPurge(rpl); } } OSUnlockMutex(sDynLoad_LoaderLock); } /** * Binary search for an export. */ static virt_ptr binarySearchExport(virt_ptr exports, uint32_t numExports, virt_ptr name) { auto exportNames = virt_cast(exports) - 8; auto left = 0u; auto right = numExports; while (true) { auto index = left + (right - left) / 2; auto exportName = exportNames.get() + (exports[index].name & 0x7FFFFFFF); auto cmpValue = strcmp(name.get(), exportName); if (cmpValue == 0) { return exports + index; } else if (cmpValue < 0) { right = index; } else { left = index + 1; } if (left >= right) { return nullptr; } } } /** * Find export sections in an RPL. */ static void findExports(virt_ptr rplData) { for (auto i = 0u; i < rplData->sectionInfoCount; ++i) { auto §ionInfo = rplData->sectionInfo[i]; if (sectionInfo.type == loader::rpl::SHT_RPL_EXPORTS) { if (sectionInfo.flags & loader::rpl::SHF_EXECINSTR) { rplData->codeExports = sectionInfo.address + 8; rplData->numCodeExports = *virt_cast(sectionInfo.address); } else { rplData->dataExports = sectionInfo.address + 8; rplData->numDataExports = *virt_cast(sectionInfo.address); } } } } /** * Find TLS sections in an RPL. */ static bool findTlsSection(virt_ptr rplData) { // Find TLS section if (rplData->userFileInfo->fileInfoFlags & loader::rpl::RPL_HAS_TLS) { auto tlsAddressStart = virt_addr { 0xFFFFFFFFu }; auto tlsAddressEnd = virt_addr { 0 }; for (auto i = 0u; i < rplData->sectionInfoCount; ++i) { auto §ionInfo = rplData->sectionInfo[i]; if (sectionInfo.flags & loader::rpl::SHF_TLS) { if (sectionInfo.address < tlsAddressStart) { tlsAddressStart = sectionInfo.address; } if (sectionInfo.address + sectionInfo.size > tlsAddressEnd) { tlsAddressEnd = sectionInfo.address + sectionInfo.size; } } } if (tlsAddressStart == virt_addr { 0xFFFFFFFFu } || tlsAddressEnd == virt_addr { 0 }) { return false; } rplData->userFileInfo->tlsAddressStart = tlsAddressStart; rplData->userFileInfo->tlsSectionSize = static_cast(tlsAddressEnd - tlsAddressStart); } return true; } /** * sSetupPerm */ static int32_t setupPerm(virt_ptr rplData, bool updateTlsModuleIndex) { auto minFileInfo = StackObject { }; std::memset(minFileInfo.get(), 0, sizeof(loader::LOADER_MinFileInfo)); minFileInfo->size = static_cast(sizeof(loader::LOADER_MinFileInfo)); minFileInfo->version = 4u; minFileInfo->outPathStringSize = virt_addrof(minFileInfo->pathStringSize); minFileInfo->outNumberOfSections = virt_addrof(rplData->sectionInfoCount); minFileInfo->outSizeOfFileInfo = virt_addrof(rplData->userFileInfoSize); if (updateTlsModuleIndex) { minFileInfo->inoutNextTlsModuleNumber = virt_addrof(sDynLoadData->tlsModuleIndex); } // Query to get the required buffer sizes auto error = kernel::loaderQuery(rplData->loaderHandle, minFileInfo); if (error) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - could not query loader about module (err=0x{:08X}).", error)); if (minFileInfo->fatalErr) { setFatalErrorInfo( minFileInfo->fatalMsgType, minFileInfo->fatalErr, minFileInfo->error, minFileInfo->fatalLine, virt_addrof(minFileInfo->fatalFunction)); reportFatalError(); } else { setFatalErrorInfo2(minFileInfo->fileLocation, error, 1, 162, "sSetupPerm"); reportFatalError(); } } // Allocate section info rplData->sectionInfo = virt_cast( rplSysHeapAlloc("RPL_SEC_INFO", sizeof(loader::LOADER_SectionInfo) * rplData->sectionInfoCount, -4)); if (!rplData->sectionInfo) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - could not allocate memory to hold section info (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::OutOfSysMemory, 0, 179, "sSetupPerm"); reportFatalError(); } minFileInfo->outSectionInfo = rplData->sectionInfo; // Allocate file info rplData->userFileInfo = virt_cast( rplSysHeapAlloc("RPL_FILE_INFO", rplData->userFileInfoSize, 4)); if (!rplData->userFileInfo) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - could not allocate memory to hold file info (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::OutOfSysMemory, 0, 196, "sSetupPerm"); reportFatalError(); } minFileInfo->outFileInfo = rplData->userFileInfo; std::memset(rplData->userFileInfo.get(), 0, sizeof(loader::LOADER_UserFileInfo)); // Allocate path string if (minFileInfo->pathStringSize) { minFileInfo->pathStringBuffer = virt_cast( rplSysHeapAlloc("RPL_NOTIFY_NAME", minFileInfo->pathStringSize, 4)); if (!minFileInfo->pathStringBuffer) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - could not allocate memory to hold path string (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::OutOfSysMemory, 0, 218, "sSetupPerm"); reportFatalError(); } std::memset(minFileInfo->pathStringBuffer.get(), 0, minFileInfo->pathStringSize); } // Query to get the data error = kernel::loaderQuery(rplData->loaderHandle, minFileInfo); if (error) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - could not get file information (err=0x{:08X}).", error)); setFatalErrorInfo( minFileInfo->fatalMsgType, minFileInfo->fatalErr, minFileInfo->error, minFileInfo->fatalLine, virt_addrof(minFileInfo->fatalFunction)); reportFatalError(); } findExports(rplData); if (!findTlsSection(rplData)) { COSError(COSReportModule::Unknown2, "*** Can not find section for TLS data."); setFatalErrorInfo2(rplData->userFileInfo->titleLocation, OSDynLoad_Error::TLSSectionNotFound, 1, 277, "sSetupPerm"); reportFatalError(); } return 0; } /** * Call the initialise default heap function for an RPL. * * For .rpx this is __preinit_user, for coreinit it is CoreInitDefaultHeap. */ static OSDynLoad_Error initialiseDefaultHeap(virt_ptr rplData, virt_ptr functionName) { auto funcAddr = StackObject { }; auto error = OSDynLoad_FindExport(rplData->handle, FALSE, functionName, funcAddr); if (error != OSDynLoad_Error::OK) { return error; } cafe::invoke(cpu::this_core::state(), virt_func_cast(*funcAddr), virt_addrof(sDynLoadData->preInitMem1Heap), virt_addrof(sDynLoadData->preInitForegroundHeap), virt_addrof(sDynLoadData->preInitMem2Heap)); auto allocFn = StackObject { }; auto freeFn = StackObject { }; error = OSDynLoad_GetAllocator(allocFn, freeFn); if (error != OSDynLoad_Error::OK || !(*allocFn) || !(*freeFn)) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InternalInitDefaultHeap() - {} did not set OSDynLoad " "allocator (err=0x{:08X}).", functionName, error)); setFatalErrorInfo2(rplData->userFileInfo->titleLocation, error, 0, 363, "__OSDynLoad_InternalInitDefaultHeap"); reportFatalError(); } error = OSDynLoad_GetTLSAllocator(allocFn, freeFn); if (error != OSDynLoad_Error::OK || !(*allocFn) || !(*freeFn)) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InternalInitDefaultHeap() - {} did not set OSDynLoad " "TLS allocator (err=0x{:08X}).", functionName, error)); setFatalErrorInfo2(rplData->userFileInfo->titleLocation, error, 0, 363, "__OSDynLoad_InternalInitDefaultHeap"); reportFatalError(); } return OSDynLoad_Error::OK; } /** * Resolve a module name. * * Removes any directories or file extension. */ static std::pair, uint32_t> resolveModuleName(virt_ptr name) { auto str = std::string_view { name.get() }; auto pos = str.find_last_of("\\/"); if (pos != std::string_view::npos) { str = str.substr(pos); name += pos; } pos = str.find_first_of('.'); if (pos != std::string_view::npos) { str = str.substr(0, pos); } return { name, static_cast(str.size()) }; } /** * __OSDynLoad_InitCommon */ static OSDynLoad_Error initialiseCommon() { sDynLoadData->loaderLockName = "{ LoaderLock }"; OSInitMutexEx(sDynLoad_LoaderLock, virt_addrof(sDynLoadData->loaderLockName)); OSHandle_InitTable(virt_addrof(sDynLoadData->handleTable)); // Parse argstr into rpx name std::tie(sDynLoadData->rpxName, sDynLoadData->rpxNameLength) = resolveModuleName(getArgStr()); if (sDynLoadData->rpxNameLength > 63u) { sDynLoadData->rpxNameLength = 63u; } // TODO: OSDynLoad_AddNotifyCallback(MasterAgent_LoadNotify, 0) return OSDynLoad_Error::OK; } /** * __BuildKernelNotify */ static OSDynLoad_Error buildKernelNotify(virt_ptr linkList, virt_ptr *outLinkInfo, virt_ptr rpl) { auto numModules = 0u; *outLinkInfo = nullptr; for (auto module = linkList; module; module = module->next) { numModules++; } auto linkInfoSize = static_cast(sizeof(loader::LOADER_LinkModule) * numModules + 8); auto linkInfo = virt_cast( rplSysHeapAlloc("RPL_MODULES_LINK_ARRAY", linkInfoSize, -4)); if (!linkInfo) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2(rpl->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, 0, 101, "__BuildKernelNotify"); return OSDynLoad_Error::OutOfSysMemory; } std::memset(linkInfo.get(), 0, linkInfoSize); linkInfo->numModules = numModules; linkInfo->size = linkInfoSize; auto moduleIndex = 0u; for (auto module = linkList; module; module = module->next) { linkInfo->modules[moduleIndex].loaderHandle = module->loaderHandle; linkInfo->modules[moduleIndex].entryPoint = virt_addr { 0 }; moduleIndex++; } *outLinkInfo = linkInfo; return OSDynLoad_Error::OK; } /** * __OSDynLoad_ExecuteDynamicLink */ static OSDynLoad_Error executeDynamicLink(virt_ptr rpx, virt_ptr outNumEntryModules, virt_ptr>> outEntryModules, virt_ptr rpl) { auto minFileInfo = StackObject { }; auto linkInfo = virt_ptr { nullptr }; auto error = OSDynLoad_Error::OK; *outNumEntryModules = 0u; *outEntryModules = nullptr; std::memset(minFileInfo.get(), 0, sizeof(loader::LOADER_MinFileInfo)); minFileInfo->size = static_cast(sizeof(loader::LOADER_MinFileInfo)); minFileInfo->version = 4u; if (auto notifyError = buildKernelNotify(sDynLoadData->linkingRplList, &linkInfo, rpl)) { return notifyError; } // Call the loader to link the rpx error = static_cast(kernel::loaderLink(nullptr, minFileInfo, linkInfo, linkInfo->size)); if (error) { COSError(COSReportModule::Unknown2, "*** Kernel refused to link RPLs."); if (minFileInfo->fatalErr) { setFatalErrorInfo( minFileInfo->fatalMsgType, minFileInfo->fatalErr, minFileInfo->error, minFileInfo->fatalLine, virt_addrof(minFileInfo->fatalFunction)); } error = OSDynLoad_Error::LoaderError; } else { for (auto i = 0u; i < linkInfo->numModules; ++i) { auto &linkModule = linkInfo->modules[i]; auto rplData = virt_ptr { nullptr }; for (rplData = sDynLoadData->linkingRplList; rplData; rplData = rplData->next) { if (rplData->loaderHandle == linkModule.loaderHandle) { break; } } if (!rplData) { OSPanic("OSDynLoad_Acquire.c", 110, "*** Error in dynamic linking. linked module not found."); error = OSDynLoad_Error::ModuleNotFound; } else { // Allocate and fill out the notify data structure rplData->entryPoint = linkModule.entryPoint; rplData->notifyData = virt_cast( rplSysHeapAlloc("RPL_NOTIFY", sizeof(OSDynLoad_NotifyData), 4)); if (!rplData->notifyData) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2(rpl->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, 0, 174, "__OSDynLoad_ExecuteDynamicLink"); error = OSDynLoad_Error::OutOfSysMemory; continue; } auto notifyData = rplData->notifyData; std::memset(notifyData.get(), 0, sizeof(OSDynLoad_NotifyData)); notifyData->name = rplData->userFileInfo->pathString; rplData->userFileInfo->pathString = nullptr; notifyData->textAddr = linkModule.textAddr; notifyData->textSize = linkModule.textSize; notifyData->textOffset = linkModule.textOffset; notifyData->dataAddr = linkModule.dataAddr; notifyData->dataSize = linkModule.dataSize; notifyData->dataOffset = linkModule.dataOffset; notifyData->readAddr = linkModule.dataAddr; notifyData->readSize = linkModule.dataSize; notifyData->readOffset = linkModule.dataOffset; if (!isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "{}: TEXT {}:{} DATA {}:{} LOAD {}:{}", rplData->moduleName, linkModule.textAddr, linkModule.textAddr + linkModule.textSize, linkModule.dataAddr, linkModule.dataAddr + linkModule.dataSize, linkModule.loadAddr, linkModule.loadAddr + linkModule.loadSize)); } else { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},TEXT,start,=\"{}\"", rplData->moduleName, linkModule.textAddr)); COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},TEXT,end,=\"{}\"", rplData->moduleName, linkModule.textAddr + linkModule.textSize)); COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},DATA,start,=\"{}\"", rplData->moduleName, linkModule.dataAddr)); COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},DATA,end,=\"{}\"", rplData->moduleName, linkModule.dataAddr + linkModule.dataSize)); COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},LOAD,start,=\"{}\"", rplData->moduleName, linkModule.loadAddr)); COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},LOAD,end,=\"{}\"", rplData->moduleName, linkModule.loadAddr + linkModule.loadSize)); } } } } if (!error) { // Allocate and fill up the entry modules array auto entryModules = virt_ptr> { nullptr }; sDynLoadData->modules = virt_cast *>( rplSysHeapAlloc("RPL_MODULES_ARRAY", 4 * linkInfo->numModules, -4)); if (!sDynLoadData->modules) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } sDynLoadData->modules = nullptr; sDynLoadData->numModules = 0u; } else { entryModules = virt_cast *>( rplSysHeapAlloc("RPL_ENTRYPOINTS_ARRAY", 4 * linkInfo->numModules, -4)); if (!entryModules) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } rplSysHeapFree("RPL_MODULES_ARRAY", sDynLoadData->modules, 4 * linkInfo->numModules); sDynLoadData->modules = nullptr; sDynLoadData->numModules = 0u; } } *outEntryModules = entryModules; auto itr = sDynLoadData->linkingRplList; auto prev = sDynLoadData->linkingRplList; sDynLoadData->numModules = 0u; while (true) { if (sDynLoadData->modules) { OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), itr->handle); sDynLoadData->modules[sDynLoadData->numModules] = itr; if (itr != rpx) { entryModules[(*outNumEntryModules)++] = itr; } } itr = itr->next; sDynLoadData->numModules++; if (!itr) { break; } prev = itr; } if (sDynLoadData->rplDataList && (sDynLoadData->rplDataList->userFileInfo->fileInfoFlags & loader::rpl::RPL_IS_RPX)) { prev->next = sDynLoadData->rplDataList->next; sDynLoadData->rplDataList->next = sDynLoadData->linkingRplList; } else { prev->next = sDynLoadData->rplDataList; sDynLoadData->rplDataList = sDynLoadData->linkingRplList; } sDynLoadData->linkingRplList = nullptr; } rplSysHeapFree("RPL_MODULES_LINK_ARRAY", linkInfo, 0); return error; } /** * Prepare an RPL for loading. */ static OSDynLoad_Error prepareLoad(virt_ptr *ptrRplData, OSDynLoad_AllocFn dynLoadAlloc, virt_ptr *ptrMinFileInfo) { auto allocPtr = StackObject> { }; auto rplData = *ptrRplData; auto minFileInfo = *ptrMinFileInfo; // Allocate section info rplData->sectionInfo = virt_cast( rplSysHeapAlloc("RPL_SEC_INFO", rplData->sectionInfoCount * sizeof(loader::LOADER_SectionInfo), -4)); if (!rplData->sectionInfo) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::OutOfSysMemory, 0, 620, "sPrepareLoad"); return OSDynLoad_Error::OutOfSysMemory; } minFileInfo->outSectionInfo = rplData->sectionInfo; // Allocate file info if (auto error = dynLoadHeapAlloc("RPL_FILE_INFO", dynLoadAlloc, rplData->userFileInfoSize, 4, allocPtr)) { setFatalErrorInfo2(minFileInfo->fileLocation, error, 0, 637, "sPrepareLoad"); return error; } rplData->userFileInfo = virt_cast(*allocPtr); if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},FILE,start,=\"{}\"", rplData->moduleName, rplData->userFileInfo)); COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_LAYOUT:{},FILE,end,=\"{}\"", rplData->moduleName, virt_cast(rplData->userFileInfo) + rplData->userFileInfoSize)); } minFileInfo->outFileInfo = rplData->userFileInfo; sDynLoadData->numAllocations++; sDynLoadData->totalAllocatedBytes += rplData->userFileInfoSize; std::memset(rplData->userFileInfo.get(), 0, rplData->userFileInfoSize); // Allocate path string buffer if (minFileInfo->pathStringSize) { minFileInfo->pathStringBuffer = virt_cast( rplSysHeapAlloc("RPL_NOTIFY_NAME", minFileInfo->pathStringSize, 4)); if (!minFileInfo->pathStringBuffer) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::OutOfSysMemory, 0, 666, "sPrepareLoad"); return OSDynLoad_Error::OutOfSysMemory; } std::memset(minFileInfo->pathStringBuffer.get(), 0, minFileInfo->pathStringSize); } if (minFileInfo->fileInfoFlags & loader::rpl::RPL_FLAG_4) { // Query the loader for file info if (auto error = kernel::loaderQuery(rplData->loaderHandle, minFileInfo)) { if (minFileInfo->fatalErr) { setFatalErrorInfo( minFileInfo->fatalMsgType, error, minFileInfo->error, minFileInfo->fatalLine, virt_addrof(minFileInfo->fatalFunction)); } return OSDynLoad_Error::InvalidRPL; } } else { // Allocate data section if (minFileInfo->dataSize) { if (auto error = dynLoadHeapAlloc("RPL_DATA_AREA", dynLoadAlloc, minFileInfo->dataSize, minFileInfo->dataAlign, virt_addrof(rplData->dataSection))) { setFatalErrorInfo2(minFileInfo->fileLocation, error, false, 705, "sPrepareLoad"); return error; } if (!kernel::validateAddressRange(virt_cast(rplData->dataSection), minFileInfo->dataSize) || !align_check(virt_cast(rplData->dataSection), minFileInfo->dataAlign)) { setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::InvalidAllocatedPtr, true, 701, "sPrepareLoad"); return OSDynLoad_Error::InvalidAllocatedPtr; } rplData->dataSectionSize = minFileInfo->dataSize; minFileInfo->dataBuffer = rplData->dataSection; sDynLoadData->numAllocations++; sDynLoadData->totalAllocatedBytes += minFileInfo->dataSize; } // Allocate load section if (minFileInfo->loadSize) { if (auto error = dynLoadHeapAlloc("RPL_LOAD_INFO_AREA", dynLoadAlloc, minFileInfo->loadSize, minFileInfo->loadAlign, virt_addrof(rplData->loadSection))) { setFatalErrorInfo2(minFileInfo->fileLocation, error, false, 748, "sPrepareLoad"); return error; } if (!kernel::validateAddressRange(virt_cast(rplData->loadSection), minFileInfo->loadSize) || !align_check(virt_cast(rplData->loadSection), minFileInfo->loadAlign)) { setFatalErrorInfo2(minFileInfo->fileLocation, OSDynLoad_Error::InvalidAllocatedPtr, true, 752, "sPrepareLoad"); return OSDynLoad_Error::InvalidAllocatedPtr; } rplData->loadSectionSize = minFileInfo->loadSize; minFileInfo->loadBuffer = rplData->loadSection; sDynLoadData->numAllocations++; sDynLoadData->totalAllocatedBytes += minFileInfo->loadSize; } // Run the loader setup if (auto error = kernel::loaderSetup(rplData->loaderHandle, minFileInfo)) { if (minFileInfo->fatalErr) { setFatalErrorInfo( minFileInfo->fatalMsgType, error, minFileInfo->error, minFileInfo->fatalLine, virt_addrof(minFileInfo->fatalFunction)); } return OSDynLoad_Error::LoaderError; } } rplSysHeapFree("RPL_TEMP_DATA", minFileInfo, sizeof(loader::LOADER_MinFileInfo)); *ptrMinFileInfo = nullptr; findExports(rplData); if (!findTlsSection(rplData)) { COSError(COSReportModule::Unknown2, "*** Can not find section for TLS data."); setFatalErrorInfo2(rplData->userFileInfo->titleLocation, OSDynLoad_Error::TLSSectionNotFound, 0, 831, "sPrepareLoad"); return OSDynLoad_Error::TLSSectionNotFound; } rplData->next = sDynLoadData->linkingRplList; sDynLoadData->linkingRplList = rplData; *ptrRplData = nullptr; return doImports(rplData); } /** * Release all importing modules. */ static void releaseImports() { auto inLinkingList = [](virt_ptr rpl) { for (auto itr = sDynLoadData->linkingRplList; itr; itr = itr->next) { if (rpl == itr) { return true; } } return false; }; for (auto module = sDynLoadData->linkingRplList; module; module = module->next) { for (auto i = 0u; i < module->importModuleCount; ++i) { auto importModule = module->importModules[i]; if (!importModule) { continue; } if (inLinkingList(importModule)) { OSHandle_Release(virt_addrof(sDynLoadData->handleTable), importModule->handle, nullptr); } else { OSDynLoad_Release(importModule->handle); } } } } /** * Acquire a module. */ static OSDynLoad_Error internalAcquire(virt_ptr name, virt_ptr> outRplData, virt_ptr outNumEntryModules, virt_ptr>> outEntryModules, bool doLoad) { auto error = OSDynLoad_Error::OK; // Resolve module name auto[moduleName, moduleNameLength] = resolveModuleName(name); if (!moduleNameLength) { setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, OSDynLoad_Error::EmptyModuleName, 0, 949, "__OSDynLoad_InternalAcquire"); return OSDynLoad_Error::EmptyModuleName; } // Allocate rpl name auto rplNameLength = align_up(moduleNameLength + 1, 4); auto rplName = virt_cast( rplSysHeapAlloc("RPL_NAME", rplNameLength, 4)); if (!rplName) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, 0, 964, "__OSDynLoad_InternalAcquire"); return OSDynLoad_Error::OutOfSysMemory; } for (auto i = 0u; i < moduleNameLength; ++i) { rplName[i] = static_cast(tolower(moduleName[i])); } rplName[moduleNameLength] = char { 0 }; auto rplNameCleanup = gsl::finally([&]() { if (rplName) { rplSysHeapFree("RPL_NAME", rplName, rplNameLength); } }); OSLockMutex(sDynLoad_LoaderLock); auto unlock = gsl::finally([&]() { OSUnlockMutex(sDynLoad_LoaderLock); }); // We cannot call acquire whilst inside a module entry point if (sDynLoadData->inModuleEntryPoint) { error = OSDynLoad_Error::InModuleEntryPoint; setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, error, true, 995, "__OSDynLoad_InternalAcquire"); return error; } // Check if we already loaded this module for (auto rpl = sDynLoadData->rplDataList; rpl; rpl = rpl->next) { if (strcmp(rpl->moduleName.get(), rplName.get()) == 0) { OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), rpl->handle); *outRplData = rpl; return OSDynLoad_Error::OK; } } // Check if we are already loading this module for (auto rpl = sDynLoadData->linkingRplList; rpl; rpl = rpl->next) { if (strcmp(rpl->moduleName.get(), rplName.get()) == 0) { OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), rpl->handle); *outRplData = rpl; return OSDynLoad_Error::OK; } } // Verify we're not loading coreinit, that'd be a massive fuckup if (strcmp(rplName.get(), "coreinit") == 0) { COSError(COSReportModule::Unknown2, "*********"); COSError(COSReportModule::Unknown2, fmt::format( "Error: Trying to load \"{}\".", rplName)); COSError(COSReportModule::Unknown2, "*********"); OSPanic("OSDynLoad_Acquire.c", 1035, "core library conflict."); } // Get the currently set allocator functions auto dynLoadAlloc = StackObject { }; auto dynLoadFree = StackObject { }; error = OSDynLoad_GetAllocator(dynLoadAlloc, dynLoadFree); if (error || !*dynLoadAlloc || !*dynLoadFree) { if (!error) { error = OSDynLoad_Error::InvalidAllocatorPtr; } setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, error, false, 1047, "__OSDynLoad_InternalAcquire"); return error; } // Allocate the notify callback array if needed auto firstLinkingRpl = !sDynLoadData->linkingRplList; auto notifyCallbackCount = 0u; auto notifyCallbackArray = virt_ptr { nullptr }; auto notifyCallbackArrayCleanup = gsl::finally([&]() { if (notifyCallbackArray) { rplSysHeapFree("RPL_NOTIFY_ARRAY", notifyCallbackArray, notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback)); } }); if (firstLinkingRpl) { notifyCallbackCount = getNotifyCallbackCount(); auto allocSize = static_cast(notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback)); notifyCallbackArray = virt_cast( rplSysHeapAlloc("RPL_NOTIFY_ARRAY", allocSize, 4)); if (!notifyCallbackArray) { setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, false, 1077, "__OSDynLoad_InternalAcquire"); return OSDynLoad_Error::OutOfSysMemory; } auto notifyCallback = sDynLoadData->notifyCallbacks; for (auto i = 0u; notifyCallback; ++i) { std::memcpy(virt_addrof(notifyCallbackArray[i]).get(), notifyCallback.get(), sizeof(OSDynLoad_NotifyCallback)); notifyCallback = notifyCallback->next; } } // Allocate rpl data auto rplData = virt_cast( rplSysHeapAlloc("RPL_DATA", sizeof(RPL_DATA), 4)); if (!rplData) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, false, 1105, "__OSDynLoad_InternalAcquire"); return OSDynLoad_Error::OutOfSysMemory; } auto rplDataCleanup = gsl::finally([&]() { if (rplData) { if (rplData->handle) { auto handleRefCount = StackObject { }; while (!OSHandle_Release(virt_addrof(sDynLoadData->handleTable), rplData->handle, handleRefCount) && *handleRefCount); internalPurge(rplData); } rplSysHeapFree("RPL_DATA", rplData, sizeof(RPL_DATA)); } }); std::memset(rplData.get(), 0, sizeof(RPL_DATA)); // Allocate min file info auto minFileInfo = virt_cast( rplSysHeapAlloc("RPL_TEMP_DATA", sizeof(loader::LOADER_MinFileInfo), 4)); if (!minFileInfo) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2( sDynLoadData->rpxData->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, false, 1131, "__OSDynLoad_InternalAcquire"); return OSDynLoad_Error::OutOfSysMemory; } auto minFileInfoCleanup = gsl::finally([&]() { if (minFileInfo) { rplSysHeapFree("RPL_TEMP_DATA", minFileInfo, sizeof(loader::LOADER_MinFileInfo)); } }); // Do loader prep auto kernelHandle = StackObject { }; std::memset(minFileInfo.get(), 0, sizeof(loader::LOADER_MinFileInfo)); minFileInfo->version = 4u; minFileInfo->size = static_cast(sizeof(loader::LOADER_MinFileInfo)); minFileInfo->outKernelHandle = kernelHandle; minFileInfo->moduleNameBufferLen = moduleNameLength; minFileInfo->moduleNameBuffer = rplName; minFileInfo->inoutNextTlsModuleNumber = virt_addrof(sDynLoadData->tlsModuleIndex); minFileInfo->outPathStringSize = virt_addrof(minFileInfo->pathStringSize); minFileInfo->outNumberOfSections = virt_addrof(rplData->sectionInfoCount); minFileInfo->outSizeOfFileInfo = virt_addrof(rplData->userFileInfoSize); if (auto prepError = kernel::loaderPrep(minFileInfo)) { if (minFileInfo->fatalErr) { setFatalErrorInfo( minFileInfo->fatalMsgType, minFileInfo->fatalErr, minFileInfo->error, minFileInfo->fatalLine, virt_addrof(minFileInfo->fatalFunction)); } return static_cast(prepError); } auto kernelHandleCleanup = gsl::finally([&]() { if (*kernelHandle) { kernel::loaderPurge(*kernelHandle); } }); // Assign tls header if (sDynLoadData->tlsModuleIndex > sDynLoadData->tlsHeader) { if (sDynLoadData->tlsModuleIndex > MaxTlsModuleIndex) { COSError(COSReportModule::Unknown2, fmt::format( "*** Too many RPLs have tls data; maximum 32k, needed {}", sDynLoadData->tlsModuleIndex)); setFatalErrorInfo2( minFileInfo->fileLocation, OSDynLoad_Error::TLSTooManyModules, false, 1178, "__OSDynLoad_InternalAcquire"); return OSDynLoad_Error::TLSTooManyModules; } sDynLoadData->tlsHeader = std::min(sDynLoadData->tlsModuleIndex + 8, MaxTlsModuleIndex); } // Allocate a DynLoad handle for the new RPL if (auto allocError = OSHandle_Alloc(virt_addrof(sDynLoadData->handleTable), rplData, nullptr, virt_addrof(rplData->handle))) { setFatalErrorInfo2(minFileInfo->fileLocation, allocError, false, 1195, "__OSDynLoad_InternalAcquire"); return static_cast(allocError); } // Prepare load *outRplData = rplData; rplData->dynLoadFreeFn = *dynLoadFree; rplData->loaderHandle = *kernelHandle; rplData->moduleName = rplName; rplData->moduleNameLen = moduleNameLength; *kernelHandle = nullptr; rplName = nullptr; error = prepareLoad(&rplData, *dynLoadAlloc, &minFileInfo); if (firstLinkingRpl) { if (error) { // If prepareLoad returned an error then we need to clean up rplData // in the case that prepareLoad did not clean it up itself if (!rplData && *outRplData) { auto handleRefCount = StackObject { }; while (!OSHandle_Release(virt_addrof(sDynLoadData->handleTable), (*outRplData)->handle, handleRefCount) && *handleRefCount); } } else { error = executeDynamicLink(nullptr, outNumEntryModules, outEntryModules, *outRplData); } releaseImports(); // Release any remaining RPLs still in the linking list while (sDynLoadData->linkingRplList) { auto rpl = sDynLoadData->linkingRplList; sDynLoadData->linkingRplList = rpl->next; rpl->next = nullptr; if (rpl->handle) { auto handleRefCount = StackObject { }; while(!OSHandle_Release(virt_addrof(sDynLoadData->handleTable), rpl->handle, handleRefCount)); } } } return error; } /** * __OSDynLoad_DoImports */ static OSDynLoad_Error doImports(virt_ptr rplData) { // Get the section header string table auto shstrndx = rplData->userFileInfo->shstrndx; if (!rplData->userFileInfo->shstrndx) { COSError(COSReportModule::Unknown2, fmt::format( "Error: Could not get section string table index for \"{}\".", rplData->moduleName)); setFatalErrorInfo2(rplData->userFileInfo->titleLocation, OSDynLoad_Error::InvalidShStrNdx, true, 391, "__OSDynLoad_DoImports"); return OSDynLoad_Error::InvalidShStrNdx; } auto shStrSection = virt_cast(rplData->sectionInfo[shstrndx].address); if (!shStrSection) { COSError(COSReportModule::Unknown2, fmt::format( "Error: Could not get section string table for \"{}\".", rplData->moduleName)); setFatalErrorInfo2(rplData->userFileInfo->titleLocation, OSDynLoad_Error::InvalidShStrSection, true, 383, "__OSDynLoad_DoImports"); return OSDynLoad_Error::InvalidShStrSection; } // Count the number of imported modules auto numImportModules = 0u; for (auto i = 0u; i < rplData->sectionInfoCount; ++i) { if (rplData->sectionInfo[i].address && rplData->sectionInfo[i].name && (rplData->sectionInfo[i].flags & loader::rpl::SHF_ALLOC) && rplData->sectionInfo[i].type == loader::rpl::SHT_RPL_IMPORTS) { numImportModules++; } } if (!numImportModules) { return OSDynLoad_Error::OK; } // Allocate imported modules array rplData->importModuleCount = numImportModules; rplData->importModules = virt_cast *>( rplSysHeapAlloc("RPL_SEC_INFO", sizeof(virt_ptr) * numImportModules, 4)); if (!rplData->importModules) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } setFatalErrorInfo2(rplData->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, false, 423, "__OSDynLoad_DoImports"); return OSDynLoad_Error::OutOfSysMemory; } // Acquire all imported modules auto importModuleIdx = 0u; for (auto i = 0u; i < rplData->sectionInfoCount; ++i) { if (rplData->sectionInfo[i].address && rplData->sectionInfo[i].name && (rplData->sectionInfo[i].flags & loader::rpl::SHF_ALLOC) && rplData->sectionInfo[i].type == loader::rpl::SHT_RPL_IMPORTS) { auto name = shStrSection + rplData->sectionInfo[i].name; if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:IMPORT START,{}", name)); COSInfo(COSReportModule::Unknown2, fmt::format( "SYSTEM_HEAP:IMPORT START,{}", name)); } auto error = internalAcquire(name + 9, virt_addrof(rplData->importModules[importModuleIdx]), 0, 0, 0); if (isAppDebugLevelNotice()) { COSInfo(COSReportModule::Unknown2, fmt::format( "RPL_SYSHEAP:IMPORT END,{}", name)); COSInfo(COSReportModule::Unknown2, fmt::format( "SYSTEM_HEAP:IMPORT END,{}", name)); } if (error) { COSError(COSReportModule::Unknown2, fmt::format( "Error: Could not load imported RPL \"{}\".", name)); return error; } auto importModule = rplData->importModules[importModuleIdx]; if (!importModule->entryPoint) { // Ensure rplData comes after importModule in the linking list auto itr = virt_ptr { nullptr }; auto prev = virt_ptr { nullptr }; for (itr = sDynLoadData->linkingRplList; itr != rplData; itr = itr->next) { prev = itr; } while ((itr = itr->next)) { if (itr == importModule) { if (prev) { prev->next = rplData->next; } else { sDynLoadData->linkingRplList = rplData->next; } rplData->next = importModule->next; importModule->next = rplData; break; } } } ++importModuleIdx; } } rplSysHeapFree("RPL_SEC_INFO", rplData->sectionInfo, rplData->sectionInfoCount * sizeof(loader::LOADER_SectionInfo)); rplData->sectionInfo = nullptr; return OSDynLoad_Error::OK; } } // namespace internal /** * Registers a callback to be called when an RPL is loaded or unloaded. */ OSDynLoad_Error OSDynLoad_AddNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn, virt_ptr userArg1) { if (!notifyFn) { return OSDynLoad_Error::InvalidAllocatorPtr; } auto notifyCallback = virt_cast( OSAllocFromSystem(sizeof(OSDynLoad_NotifyCallback), 4)); if (!notifyCallback) { return OSDynLoad_Error::OutOfSysMemory; } OSLockMutex(sDynLoad_LoaderLock); notifyCallback->notifyFn = notifyFn; notifyCallback->userArg1 = userArg1; notifyCallback->next = sDynLoadData->notifyCallbacks; sDynLoadData->notifyCallbacks = notifyCallback; OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } /** * Deletes a callback previously registered by OSDynLoad_AddNotifyCallback. */ void OSDynLoad_DelNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn, virt_ptr userArg1) { if (!notifyFn) { return; } OSLockMutex(sDynLoad_LoaderLock); // Find the callback auto prevCallback = virt_ptr { nullptr }; auto callback = sDynLoadData->notifyCallbacks; while (callback) { if (callback->notifyFn == notifyFn && callback->userArg1 == userArg1) { break; } prevCallback = callback; callback = callback->next; } if (callback) { // Erase it from linked list if (prevCallback) { prevCallback->next = callback->next; } else { sDynLoadData->notifyCallbacks = callback->next; } } OSUnlockMutex(sDynLoad_LoaderLock); // Free the callback if (callback) { OSFreeToSystem(callback); } } /** * Retrieve the allocator functions set by OSDynLoad_SetAllocator. */ OSDynLoad_Error OSDynLoad_GetAllocator(virt_ptr outAllocFn, virt_ptr outFreeFn) { OSLockMutex(sDynLoad_LoaderLock); if (outAllocFn) { *outAllocFn = sDynLoadData->allocFn; } if (outFreeFn) { *outFreeFn = sDynLoadData->freeFn; } OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } /** * Set the allocator which controls allocation of RPL segments. */ OSDynLoad_Error OSDynLoad_SetAllocator(OSDynLoad_AllocFn allocFn, OSDynLoad_FreeFn freeFn) { if (!allocFn || !freeFn) { return OSDynLoad_Error::InvalidAllocatorPtr; } OSLockMutex(sDynLoad_LoaderLock); sDynLoadData->allocFn = allocFn; sDynLoadData->freeFn = freeFn; OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } /** * Retrieve the allocator functions set by OSDynLoad_SetTLSAllocator. */ OSDynLoad_Error OSDynLoad_GetTLSAllocator(virt_ptr outAllocFn, virt_ptr outFreeFn) { OSLockMutex(sDynLoad_LoaderLock); if (outAllocFn) { *outAllocFn = sDynLoadData->tlsAllocFn; } if (outFreeFn) { *outFreeFn = sDynLoadData->tlsFreeFn; } OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } /** * Set the allocator which controls allocation of TLS memory. */ OSDynLoad_Error OSDynLoad_SetTLSAllocator(OSDynLoad_AllocFn allocFn, OSDynLoad_FreeFn freeFn) { if (!allocFn || !freeFn) { return OSDynLoad_Error::InvalidAllocatorPtr; } if (sDynLoadData->tlsAllocLocked) { return OSDynLoad_Error::TLSAllocatorLocked; } OSLockMutex(sDynLoad_LoaderLock); sDynLoadData->tlsAllocFn = allocFn; sDynLoadData->tlsFreeFn = freeFn; OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } /** * Acquire a module. * * If a module is already loaded this will increase it's ref count. * If a module is not yet loaded it will be loaded. */ OSDynLoad_Error OSDynLoad_Acquire(virt_ptr modulePath, virt_ptr outModuleHandle) { return internal::internalAcquire2(modulePath, outModuleHandle, FALSE); } /** * Release a module. * * Decreases ref count of module and deallocates it if ref count hits 0. */ void OSDynLoad_Release(OSDynLoad_ModuleHandle moduleHandle) { internal::release(moduleHandle, nullptr); } /** * Acquire the module which contains ptr. */ OSDynLoad_Error OSDynLoad_AcquireContainingModule(virt_ptr ptr, OSDynLoad_SectionType sectionType, virt_ptr outHandle) { if (!outHandle) { return OSDynLoad_Error::InvalidAcquirePtr; } if (!ptr) { return OSDynLoad_Error::InvalidContainPtr; } OSLockMutex(sDynLoad_LoaderLock); auto addr = virt_cast(ptr); for (auto itr = sDynLoadData->rplDataList; itr; itr = itr->next) { if (!itr->notifyData) { continue; } if (sectionType == OSDynLoad_SectionType::Any || sectionType == OSDynLoad_SectionType::DataOnly) { if (addr >= itr->notifyData->dataAddr && addr < itr->notifyData->dataAddr + itr->notifyData->dataSize) { if (itr == sDynLoadData->rpxData) { *outHandle = OSDynLoad_CurrentModuleHandle; } else { OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), itr->handle); *outHandle = itr->handle; } OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } } if (sectionType == OSDynLoad_SectionType::Any || sectionType == OSDynLoad_SectionType::CodeOnly) { if (addr >= itr->notifyData->textAddr && addr < itr->notifyData->textAddr + itr->notifyData->textSize) { if (itr == sDynLoadData->rpxData) { *outHandle = OSDynLoad_CurrentModuleHandle; } else { OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), itr->handle); *outHandle = itr->handle; } OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::OK; } } } OSUnlockMutex(sDynLoad_LoaderLock); return OSDynLoad_Error::ContainModuleNotFound; } /** * Find an export from a library handle. */ OSDynLoad_Error OSDynLoad_FindExport(OSDynLoad_ModuleHandle moduleHandle, BOOL isData, virt_ptr name, virt_ptr outAddr) { virt_ptr rplData = nullptr; auto exportData = virt_ptr { nullptr }; auto error = OSDynLoad_Error::OK; if (moduleHandle == OSDynLoad_CurrentModuleHandle) { rplData = sDynLoadData->rpxData; } else { auto handleData = StackObject> { }; if (OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable), moduleHandle, handleData, nullptr)) { return OSDynLoad_Error::InvalidHandle; } rplData = virt_cast(*handleData); } OSLockMutex(sDynLoad_LoaderLock); if (isData) { if (!rplData->dataExports) { error = OSDynLoad_Error::ModuleHasNoDataExports; } else { exportData = internal::binarySearchExport( virt_cast(rplData->dataExports), rplData->numDataExports, name); } } else { if (!rplData->codeExports) { error = OSDynLoad_Error::ModuleHasNoCodeExports; } else { exportData = internal::binarySearchExport( virt_cast(rplData->codeExports), rplData->numCodeExports, name); } } if (error == OSDynLoad_Error::OK) { if (!exportData) { error = OSDynLoad_Error::ExportNotFound; } else if (exportData->value & 0x80000000) { error = OSDynLoad_Error::TLSFindExportInvalid; } else { *outAddr = exportData->value; } } OSUnlockMutex(sDynLoad_LoaderLock); if (moduleHandle != OSDynLoad_CurrentModuleHandle) { OSHandle_Release(virt_addrof(sDynLoadData->handleTable), moduleHandle, nullptr); } return error; } /** * Find an tag from a library handle. */ OSDynLoad_Error OSDynLoad_FindTag(OSDynLoad_ModuleHandle moduleHandle, virt_ptr tag, virt_ptr buffer, virt_ptr inoutBufferSize) { return OSDynLoad_Error::InvalidParam; } /** * Get statistics about the loader memory heap. */ OSDynLoad_Error OSDynLoad_GetLoaderHeapStatistics(virt_ptr stats) { return OSDynLoad_Error::InvalidParam; } /** * Get the module name for a module handle. */ OSDynLoad_Error OSDynLoad_GetModuleName(OSDynLoad_ModuleHandle moduleHandle, virt_ptr buffer, virt_ptr inoutBufferSize) { auto error = OSDynLoad_Error::OK; if (!moduleHandle) { return OSDynLoad_Error::InvalidHandle; } if (!inoutBufferSize || !kernel::validateAddressRange(virt_cast(inoutBufferSize), 4)) { return OSDynLoad_Error::InvalidParam; } if (moduleHandle == OSDynLoad_CurrentModuleHandle) { auto bufferLength = static_cast( strlen(sDynLoadData->rpxName.get()) + 1); if (bufferLength > *inoutBufferSize) { *inoutBufferSize = bufferLength; error = OSDynLoad_Error::BufferTooSmall; } else { std::memcpy(buffer.get(), sDynLoadData->rpxName.get(), bufferLength - 1); buffer[bufferLength - 1] = char { 0 }; } } else { auto handleUserData1 = StackObject> { }; if (OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable), moduleHandle, handleUserData1, nullptr) != OSHandleError::OK) { return OSDynLoad_Error::InvalidHandle; } OSLockMutex(sDynLoad_LoaderLock); auto rplData = virt_cast(*handleUserData1); auto bufferLength = rplData->moduleNameLen + 1; if (bufferLength > *inoutBufferSize) { *inoutBufferSize = bufferLength; error = OSDynLoad_Error::BufferTooSmall; } else { std::memcpy(buffer.get(), rplData->moduleName.get(), bufferLength - 1); buffer[bufferLength - 1] = char { 0 }; } OSUnlockMutex(sDynLoad_LoaderLock); OSHandle_Release(virt_addrof(sDynLoadData->handleTable), moduleHandle, nullptr); } return error; } /** * Return the number of loaded RPLs. * * Will always return 0 on non-debug builds of CafeOS. */ uint32_t OSDynLoad_GetNumberOfRPLs() { return 0; } /** * Get the info for count RPLs beginning at first. */ uint32_t OSDynLoad_GetRPLInfo(uint32_t first, uint32_t count, virt_ptr outRplInfos) { if (!count) { return 1; } return 0; } /** * Check if a module is loaded and return the handle of a dynamic library. */ OSDynLoad_Error OSDynLoad_IsModuleLoaded(virt_ptr name, virt_ptr outHandle) { return internal::internalAcquire2(name, outHandle, TRUE); } /** * Find a symbol name for the given address. * * \return * Returns the address of the nearest symbol. */ virt_addr OSGetSymbolName(virt_addr address, virt_ptr buffer, uint32_t bufferSize) { auto symbolDistance = StackObject { }; auto symbolNameBuffer = StackArray { }; auto moduleNameBuffer = StackArray { }; *buffer = char { 0 }; if (bufferSize < 16) { return address; } symbolNameBuffer[0] = char { 0 }; moduleNameBuffer[0] = char { 0 }; auto error = kernel::findClosestSymbol(address, symbolDistance, symbolNameBuffer, symbolNameBuffer.size(), moduleNameBuffer, moduleNameBuffer.size()); if (error || (!symbolNameBuffer[0] && !moduleNameBuffer[0])) { string_copy(buffer.get(), "unknown", bufferSize); buffer[bufferSize - 1] = char { 0 }; return address; } auto symbolAddress = address - *symbolDistance; auto moduleNameLength = strlen(moduleNameBuffer.get()); auto symbolNameLength = strlen(symbolNameBuffer.get()); if (moduleNameLength) { string_copy(buffer.get(), moduleNameBuffer.get(), bufferSize); if (moduleNameLength + 1 >= bufferSize) { buffer[bufferSize - 1] = char { 0 }; return symbolAddress; } if (symbolNameLength) { buffer[moduleNameLength] = char { '|' }; moduleNameLength++; } } if (symbolNameLength) { string_copy(buffer.get() + moduleNameLength, symbolNameBuffer.get(), bufferSize - moduleNameLength); } return symbolAddress; } /** * __tls_get_addr * Gets the TLS data for tls_index. */ virt_ptr tls_get_addr(virt_ptr index) { if (!sDynLoadData->tlsHeader) { internal::COSError(COSReportModule::Unknown2, "*** __OSDynLoad_gTLSHeader not initialized."); internal::OSPanic("OSDynLoad_Acquire.c", 299, "__OSDynLoad_gTLSHeader not initialized."); } if (index->moduleIndex < 0) { internal::COSError(COSReportModule::Unknown2, "*** __OSDynLoad_gTLSHeader not initialized."); internal::OSPanic("OSDynLoad_Acquire.c", 304, "__OSDynLoad_gTLSHeader not initialized."); } if (sDynLoadData->tlsHeader <= index->moduleIndex) { internal::COSError(COSReportModule::Unknown2, "*** __OSDynLoad_gTLSHeader not initialized."); internal::OSPanic("OSDynLoad_Acquire.c", 309, "__OSDynLoad_gTLSHeader not initialized."); } auto thread = OSGetCurrentThread(); if (thread->tlsSectionCount <= index->moduleIndex) { auto allocPtr = StackObject> { }; // Allocate new TLS section data if (auto error = cafe::invoke(cpu::this_core::state(), sDynLoadData->tlsAllocFn, static_cast(sizeof(OSTLSSection) * sDynLoadData->tlsHeader), 4, allocPtr)) { internal::COSError(COSReportModule::Unknown2, "*** Couldn't allocate internal TLS framework blocks."); internal::OSPanic("OSDynLoad_Acquire.c", 317, "out of memory."); } std::memset(allocPtr->get(), 0, sizeof(OSTLSSection) * sDynLoadData->tlsHeader); // Copy and free old TLS section data if (thread->tlsSectionCount) { std::memcpy(allocPtr->get(), thread->tlsSections.get(), sizeof(OSTLSSection) * sDynLoadData->tlsHeader); cafe::invoke(cpu::this_core::state(), sDynLoadData->tlsFreeFn, thread->tlsSections); } thread->tlsSections = virt_cast(*allocPtr); thread->tlsSectionCount = sDynLoadData->tlsHeader; } if (!thread->tlsSections[index->moduleIndex].data) { // Find the module for this tls index auto module = virt_ptr { nullptr }; for (module = sDynLoadData->rplDataList; module; module = module->next) { if (module->userFileInfo->tlsModuleIndex == static_cast(index->moduleIndex)) { break; } } if (!module || !(module->userFileInfo->fileInfoFlags & loader::rpl::RPL_HAS_TLS) || !module->userFileInfo->tlsAddressStart || !module->userFileInfo->tlsSectionSize) { internal::COSError(COSReportModule::Unknown2, "*** Can not find module for TLS data."); internal::OSPanic("OSDynLoad_Acquire.c", 343, "Can not find module for TLS data."); } // Allocate tls image for this module auto allocPtr = StackObject> { }; if (auto error = cafe::invoke(cpu::this_core::state(), sDynLoadData->tlsAllocFn, module->userFileInfo->tlsSectionSize, 1u << module->userFileInfo->tlsAlignShift, allocPtr)) { internal::COSError(COSReportModule::Unknown2, "*** Couldn't allocate thread TLS image."); internal::OSPanic("OSDynLoad_Acquire.c", 352, "out of memory."); } std::memcpy(allocPtr->get(), virt_cast(module->userFileInfo->tlsAddressStart).get(), module->userFileInfo->tlsSectionSize); thread->tlsSections[index->moduleIndex].data = *allocPtr; } return virt_cast( virt_cast(thread->tlsSections[index->moduleIndex].data) + index->offset); } namespace internal { void dynLoadTlsFree(virt_ptr thread) { if (thread->tlsSectionCount) { for (auto i = 0u; i < thread->tlsSectionCount; ++i) { cafe::invoke(cpu::this_core::state(), sDynLoadData->tlsFreeFn, thread->tlsSections[i].data); } cafe::invoke(cpu::this_core::state(), sDynLoadData->tlsFreeFn, virt_cast(thread->tlsSections)); } } static void initCoreinitNotifyData(virt_ptr rpl) { auto info0 = StackObject { }; kernel::getInfo(kernel::InfoType::Type0, info0, sizeof(kernel::Info0)); rpl->notifyData = virt_cast( rplSysHeapAlloc("RPL_NOTIFY", sizeof(OSDynLoad_NotifyData), 4)); if (!rpl) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format("sInitNotifyHdr() - mem alloc failed (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(rpl->userFileInfo->titleLocation, OSDynLoad_Error::OutOfSysMemory, 0, 326, "sInitNotifyHdr"); reportFatalError(); } std::memset(rpl->notifyData.get(), 0, sizeof(OSDynLoad_NotifyData)); // Steal the path string! rpl->notifyData->name = rpl->userFileInfo->pathString; rpl->userFileInfo->pathString = nullptr; // Update section info rpl->notifyData->textAddr = info0->coreinit.textAddr; rpl->notifyData->textOffset = info0->coreinit.textOffset; rpl->notifyData->textSize = info0->coreinit.textSize; rpl->notifyData->dataAddr = info0->coreinit.dataAddr; rpl->notifyData->dataOffset = info0->coreinit.dataOffset; rpl->notifyData->dataSize = info0->coreinit.dataSize; rpl->notifyData->readAddr = info0->coreinit.dataAddr; rpl->notifyData->readOffset = info0->coreinit.dataOffset; rpl->notifyData->readSize = info0->coreinit.dataSize; // TODO: MasterAgent_LoadNotify } /** * __OSDynLoad_InitFromCoreInit */ virt_addr initialiseDynLoad() { resetFatalErrorInfo(); if (auto error = initialiseCommon()) { COSError(COSReportModule::Unknown2, fmt::format( "OSDynLoad_InitCommon() failed with err 0x{:08X}\n", error)); reportFatalError(); } // Allocate rpl data for coreinit auto coreinitRplData = virt_cast( rplSysHeapAlloc("RPL_DATA", sizeof(RPL_DATA), 4)); if (!coreinitRplData) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - mem alloc failed (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(0, OSDynLoad_Error::OutOfSysMemory, false, 440, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } std::memset(coreinitRplData.get(), 0, sizeof(RPL_DATA)); // Allocate handle for coreinit if (auto handleError = OSHandle_Alloc(virt_addrof(sDynLoadData->handleTable), coreinitRplData, nullptr, virt_addrof(coreinitRplData->handle))) { decaf_abort(fmt::format("Unexpected OSHandle_Alloc error = {}", handleError)); COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - handle alloc failed (err=0x{:08X}.", handleError)); setFatalErrorInfo2(0, handleError, 0, 452, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } // Call setupPerm for coreinit sDynLoadData->coreinitModuleName = "coreinit"; coreinitRplData->moduleName = virt_addrof(sDynLoadData->coreinitModuleName); coreinitRplData->moduleNameLen = 8u; coreinitRplData->loaderHandle = getCoreinitLoaderHandle(); coreinitRplData->next = sDynLoadData->rplDataList; sDynLoadData->rplDataList = coreinitRplData; setupPerm(coreinitRplData, false); rplSysHeapFree("RPL_SEC_INFO", coreinitRplData->sectionInfo, coreinitRplData->sectionInfoCount * sizeof(loader::LOADER_SectionInfo)); coreinitRplData->sectionInfo = nullptr; sDynLoadData->tlsAllocLocked = FALSE; initialiseDefaultHeap(coreinitRplData, make_stack_string("CoreInitDefaultHeap")); // Allocate rpx data auto rpxData = virt_cast( rplSysHeapAlloc("RPL_DATA", sizeof(RPL_DATA), 4)); if (!rpxData) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - mem alloc failed (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(0, OSDynLoad_Error::OutOfSysMemory, false, 493, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } std::memset(rpxData.get(), 0, sizeof(RPL_DATA)); sDynLoadData->rpxData = rpxData; // Allocate module name rpxData->handle = 0xFFFFFFFFu; rpxData->moduleName = virt_cast( rplSysHeapAlloc("RPL_NAME", sDynLoadData->rpxNameLength + 1, 4)); rpxData->moduleNameLen = sDynLoadData->rpxNameLength; if (!rpxData->moduleName) { if (isAppDebugLevelNotice()) { dumpSystemHeap(); } COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - mem alloc failed (err=0x{:08X}).", OSDynLoad_Error::OutOfSysMemory)); setFatalErrorInfo2(0, OSDynLoad_Error::OutOfSysMemory, false, 514, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } std::memcpy(rpxData->moduleName.get(), sDynLoadData->rpxName.get(), rpxData->moduleNameLen); rpxData->moduleName[rpxData->moduleNameLen] = char { 0 }; // Call setupPerm for rpx setupPerm(rpxData, true); if (sDynLoadData->tlsModuleIndex) { sDynLoadData->tlsHeader = sDynLoadData->tlsModuleIndex + 8; } sDynLoadData->linkingRplList = rpxData; rpxData->entryPoint = virt_addr { 0u }; if (auto error = doImports(rpxData)) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - main program load failed (err=0x{:08X}).", error)); setFatalErrorInfo2(rpxData->userFileInfo->titleLocation, error, true, 540, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } auto numEntryModules = StackObject { }; auto entryModules = StackObject>> { }; if (auto error = executeDynamicLink(rpxData, numEntryModules, entryModules, rpxData)) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - dynamic link of main program failed (err=0x{:08X}).", error)); setFatalErrorInfo2(rpxData->userFileInfo->titleLocation, error, true, 549, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } if (coreinitRplData->userFileInfo->pathString) { initCoreinitNotifyData(coreinitRplData); } kernel::loaderUserGainControl(); // Notify debugger of entry points auto preinitAddr = StackObject { }; if (auto error = OSDynLoad_FindExport(rpxData->handle, FALSE, make_stack_string("__preinit_user"), preinitAddr) != OSDynLoad_Error::OK) { *preinitAddr = virt_addr { 0 }; } decaf::debug::notifyEntry(static_cast(*preinitAddr), static_cast(rpxData->entryPoint)); // Run the preinit entry point initialiseDefaultHeap(rpxData, make_stack_string("__preinit_user")); sDynLoadData->tlsAllocLocked = TRUE; if (sDynLoadData->preInitMem1Heap) { MEMSetBaseHeapHandle(MEMBaseHeapType::MEM1, sDynLoadData->preInitMem1Heap); } if (sDynLoadData->preInitForegroundHeap) { MEMSetBaseHeapHandle(MEMBaseHeapType::FG, sDynLoadData->preInitForegroundHeap); } if (sDynLoadData->preInitMem2Heap) { MEMSetBaseHeapHandle(MEMBaseHeapType::MEM2, sDynLoadData->preInitMem2Heap); } initialiseGhs(); if (auto error = runEntryPoints(*numEntryModules, *entryModules)) { COSError(COSReportModule::Unknown2, fmt::format( "__OSDynLoad_InitFromCoreInit() - initialization functions of main program failed (err=0x{:08X}).", error)); reportFatalError(); } if (sDynLoadData->modules) { rplSysHeapFree("RPL_MODULES_ARRAY", sDynLoadData->modules, sDynLoadData->numModules); } sDynLoadData->modules = nullptr; sDynLoadData->numModules = 0u; if (!rpxData->entryPoint) { COSError(COSReportModule::Unknown2, "__OSDynLoad_InitFromCoreInit() - main program is NULL."); setFatalErrorInfo2(rpxData->userFileInfo->titleLocation, OSDynLoad_Error::InvalidRpxEntryPoint, true, 643, "__OSDynLoad_InitFromCoreInit"); reportFatalError(); } return rpxData->entryPoint; } /** * Relocate HLE variables to the loaded addresses for a given module. */ OSDynLoad_Error relocateHleLibrary(OSDynLoad_ModuleHandle moduleHandle) { auto handleData = StackObject> { }; if (OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable), moduleHandle, handleData, nullptr)) { return OSDynLoad_Error::InvalidHandle; } auto rplData = virt_cast(*handleData); cafe::hle::relocateLibrary( std::string_view { rplData->moduleName.get(), rplData->moduleNameLen }, rplData->notifyData->textAddr, rplData->notifyData->dataAddr ); return OSDynLoad_Error::OK; } } // namespace internal void Library::registerDynLoadSymbols() { RegisterFunctionExport(OSDynLoad_AddNotifyCallback); RegisterFunctionExport(OSDynLoad_DelNotifyCallback); RegisterFunctionExportName("OSDynLoad_AddNofifyCallback", // covfefe OSDynLoad_AddNotifyCallback); RegisterFunctionExport(OSDynLoad_GetAllocator); RegisterFunctionExport(OSDynLoad_SetAllocator); RegisterFunctionExport(OSDynLoad_GetTLSAllocator); RegisterFunctionExport(OSDynLoad_SetTLSAllocator); RegisterFunctionExport(OSDynLoad_Acquire); RegisterFunctionExport(OSDynLoad_AcquireContainingModule); RegisterFunctionExport(OSDynLoad_FindExport); RegisterFunctionExport(OSDynLoad_FindTag); RegisterFunctionExport(OSDynLoad_GetLoaderHeapStatistics); RegisterFunctionExport(OSDynLoad_GetModuleName); RegisterFunctionExport(OSDynLoad_GetNumberOfRPLs); RegisterFunctionExport(OSDynLoad_GetRPLInfo); RegisterFunctionExport(OSDynLoad_IsModuleLoaded); RegisterFunctionExport(OSDynLoad_Release); RegisterDataExportName("OSDynLoad_gLoaderLock", sDynLoad_LoaderLock); RegisterFunctionExport(OSGetSymbolName); RegisterFunctionExportName("__tls_get_addr", tls_get_addr); RegisterDataInternal(sDynLoadData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_dynload.h ================================================ #pragma once #include "coreinit_enum.h" #include namespace cafe::coreinit { struct OSThread; struct OSDynLoad_NotifyData; using OSDynLoad_ModuleHandle = uint32_t; constexpr OSDynLoad_ModuleHandle OSDynLoad_CurrentModuleHandle = OSDynLoad_ModuleHandle { 0xFFFFFFFF }; using OSDynLoad_AllocFn = virt_func_ptr< OSDynLoad_Error(int32_t size, int32_t align, virt_ptr> out)>; using OSDynLoad_FreeFn = virt_func_ptr< void(virt_ptr ptr)>; using OSDynLoad_NotifyCallbackFn = virt_func_ptr< void(OSDynLoad_ModuleHandle handle, virt_ptr userArg1, OSDynLoad_NotifyEvent event, virt_ptr)>; struct OSDynLoad_LoaderHeapStatistics; struct OSDynLoad_NotifyData { be2_virt_ptr name; be2_val textAddr; be2_val textOffset; be2_val textSize; be2_val dataAddr; be2_val dataOffset; be2_val dataSize; be2_val readAddr; be2_val readOffset; be2_val readSize; }; CHECK_OFFSET(OSDynLoad_NotifyData, 0x00, name); CHECK_OFFSET(OSDynLoad_NotifyData, 0x04, textAddr); CHECK_OFFSET(OSDynLoad_NotifyData, 0x08, textOffset); CHECK_OFFSET(OSDynLoad_NotifyData, 0x0C, textSize); CHECK_OFFSET(OSDynLoad_NotifyData, 0x10, dataAddr); CHECK_OFFSET(OSDynLoad_NotifyData, 0x14, dataOffset); CHECK_OFFSET(OSDynLoad_NotifyData, 0x18, dataSize); CHECK_OFFSET(OSDynLoad_NotifyData, 0x1C, readAddr); CHECK_OFFSET(OSDynLoad_NotifyData, 0x20, readOffset); CHECK_OFFSET(OSDynLoad_NotifyData, 0x24, readSize); CHECK_SIZE(OSDynLoad_NotifyData, 0x28); struct OSDynLoad_NotifyCallback { UNKNOWN(0x4); be2_val notifyFn; be2_virt_ptr userArg1; be2_virt_ptr next; }; CHECK_SIZE(OSDynLoad_NotifyCallback, 0x10); struct tls_index { be2_val moduleIndex; be2_val offset; }; CHECK_OFFSET(tls_index, 0x00, moduleIndex); CHECK_OFFSET(tls_index, 0x04, offset); CHECK_SIZE(tls_index, 0x08); OSDynLoad_Error OSDynLoad_AddNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn, virt_ptr userArg1); void OSDynLoad_DelNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn, virt_ptr userArg1); OSDynLoad_Error OSDynLoad_GetAllocator(virt_ptr outAllocFn, virt_ptr outFreeFn); OSDynLoad_Error OSDynLoad_SetAllocator(OSDynLoad_AllocFn allocFn, OSDynLoad_FreeFn freeFn); OSDynLoad_Error OSDynLoad_GetTLSAllocator(virt_ptr outAllocFn, virt_ptr outFreeFn); OSDynLoad_Error OSDynLoad_SetTLSAllocator(OSDynLoad_AllocFn allocFn, OSDynLoad_FreeFn freeFn); OSDynLoad_Error OSDynLoad_Acquire(virt_ptr modulePath, virt_ptr outModuleHandle); OSDynLoad_Error OSDynLoad_AcquireContainingModule(virt_ptr ptr, OSDynLoad_SectionType sectionType, virt_ptr outHandle); OSDynLoad_Error OSDynLoad_FindExport(OSDynLoad_ModuleHandle moduleHandle, BOOL isData, virt_ptr name, virt_ptr outAddr); OSDynLoad_Error OSDynLoad_FindTag(OSDynLoad_ModuleHandle moduleHandle, virt_ptr tag, virt_ptr buffer, virt_ptr inoutBufferSize); OSDynLoad_Error OSDynLoad_GetLoaderHeapStatistics(virt_ptr stats); OSDynLoad_Error OSDynLoad_GetModuleName(OSDynLoad_ModuleHandle moduleHandle, virt_ptr buffer, virt_ptr inoutBufferSize); uint32_t OSDynLoad_GetNumberOfRPLs(); uint32_t OSDynLoad_GetRPLInfo(uint32_t first, uint32_t count, virt_ptr outRplInfos); OSDynLoad_Error OSDynLoad_IsModuleLoaded(virt_ptr name, virt_ptr outHandle); void OSDynLoad_Release(OSDynLoad_ModuleHandle moduleHandle); virt_addr OSGetSymbolName(virt_addr address, virt_ptr buffer, uint32_t bufferSize); virt_ptr tls_get_addr(virt_ptr index); namespace internal { void dynLoadTlsFree(virt_ptr thread); virt_addr initialiseDynLoad(); OSDynLoad_Error relocateHleLibrary(OSDynLoad_ModuleHandle moduleHandle); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_enum.h ================================================ #ifndef COREINIT_ENUM_H #define COREINIT_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(coreinit) ENUM_BEG(BSPError, uint32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(IosError, 0x40) ENUM_VALUE(ResponseTooLarge, 0x80) ENUM_END(BSPError) ENUM_BEG(COSReportLevel, uint32_t) ENUM_VALUE(Error, 0) ENUM_VALUE(Warn, 1) ENUM_VALUE(Info, 2) ENUM_VALUE(Verbose, 3) ENUM_END(COSReportLevel) ENUM_BEG(COSReportModule, uint32_t) ENUM_VALUE(Unknown0, 0) ENUM_VALUE(Unknown1, 1) ENUM_VALUE(Unknown2, 2) ENUM_VALUE(Unknown5, 5) ENUM_END(COSReportModule) ENUM_BEG(IPCDriverStatus, uint32_t) ENUM_VALUE(Closed, 1) ENUM_VALUE(Initialised, 2) ENUM_VALUE(Open, 3) ENUM_END(IPCDriverStatus) ENUM_BEG(OSAlarmState, uint32_t) ENUM_VALUE(Idle, 0) ENUM_VALUE(Set, 1) ENUM_VALUE(Expired, 2) ENUM_VALUE(Invalid, 0x79646CFF) ENUM_END(OSAlarmState) ENUM_BEG(OSDeviceID, uint32_t) ENUM_VALUE(VI, 0) ENUM_VALUE(DSP, 1) ENUM_VALUE(GFXSP, 2) ENUM_VALUE(SI, 6) ENUM_VALUE(LATTE_REGS, 11) ENUM_VALUE(LEGACY_SI, 12) ENUM_VALUE(LEGACY_AI_I2S3, 13) ENUM_VALUE(LEGACY_AI_I2S5, 14) ENUM_VALUE(LEGACY_EXI, 15) ENUM_END(OSDeviceID) ENUM_BEG(OSDriver_Error, uint32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(DriverNotFound, 0xBAD70002) ENUM_VALUE(InvalidArgument, 0xBAD70003) ENUM_VALUE(OutOfSysMemory, 0xBAD70004) ENUM_VALUE(AlreadyRegistered, 0xBAD70005) ENUM_END(OSDriver_Error) ENUM_BEG(OSDynLoad_Error, uint32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(OutOfMemory, 0xBAD10002) ENUM_VALUE(InvalidHandle, 0xBAD10003) ENUM_VALUE(InvalidRPL, 0xBAD1000C) ENUM_VALUE(InvalidNotifyPtr, 0xBAD1000E) ENUM_VALUE(InvalidModuleNamePtr, 0xBAD1000F) ENUM_VALUE(InvalidModuleName, 0xBAD10010) ENUM_VALUE(InvalidAcquirePtr, 0xBAD10011) ENUM_VALUE(EmptyModuleName, 0xBAD10012) ENUM_VALUE(InvalidAllocatedPtr, 0xBAD10014) ENUM_VALUE(InModuleEntryPoint, 0xBAD10016) ENUM_VALUE(InvalidAllocatorPtr, 0xBAD10017) ENUM_VALUE(ModuleHasNoDataExports, 0xBAD1001B) ENUM_VALUE(ModuleHasNoCodeExports, 0xBAD1001C) ENUM_VALUE(ExportNotFound, 0xBAD1001D) ENUM_VALUE(InvalidParam, 0xBAD1001E) ENUM_VALUE(BufferTooSmall, 0xBAD1001F) ENUM_VALUE(LoaderError, 0xBAD10021) ENUM_VALUE(ModuleNotFound, 0xBAD10023) ENUM_VALUE(InvalidContainPtr, 0xBAD10027) ENUM_VALUE(ContainModuleNotFound, 0xBAD10028) ENUM_VALUE(RunEntryPointError, 0xBAD10029) ENUM_VALUE(OutOfSysMemory, 0xBAD1002F) ENUM_VALUE(TLSAllocatorLocked, 0xBAD10031) ENUM_VALUE(TLSTooManyModules, 0xBAD10032) ENUM_VALUE(TLSFindExportInvalid, 0xBAD10033) ENUM_VALUE(InvalidShStrNdx, 0xBAD1003A) ENUM_VALUE(InvalidShStrSection, 0xBAD1003B) ENUM_VALUE(TLSSectionNotFound, 0xBAD1003D) ENUM_VALUE(InvalidRpxEntryPoint, 0xBAD1003E) ENUM_END(OSDynLoad_Error) ENUM_BEG(OSDynLoad_EntryReason, uint32_t) ENUM_VALUE(Loaded, 1) ENUM_VALUE(Unloaded, 2) ENUM_END(OSDynLoad_EntryReason) ENUM_BEG(OSDynLoad_NotifyEvent, uint32_t) ENUM_VALUE(Unloaded, 0) ENUM_VALUE(Loaded, 1) ENUM_END(OSDynLoad_NotifyEvent) ENUM_BEG(OSDynLoad_SectionType, uint32_t) ENUM_VALUE(Any, 0) ENUM_VALUE(CodeOnly, 1) ENUM_VALUE(DataOnly, 2) ENUM_END(OSDynLoad_SectionType) ENUM_BEG(OSEventMode, uint32_t) //! A manual event will only reset when OSResetEvent is called. ENUM_VALUE(ManualReset, 0) //! An auto event will reset everytime a thread is woken. ENUM_VALUE(AutoReset, 1) ENUM_END(OSEventMode) ENUM_BEG(OSExceptionMode, uint32_t) ENUM_VALUE(System, 0) ENUM_VALUE(Thread, 1) ENUM_VALUE(Global, 2) ENUM_VALUE(ThreadAllCores, 3) ENUM_VALUE(GlobalAllCores, 4) ENUM_END(OSExceptionMode) ENUM_BEG(OSExceptionType, uint32_t) ENUM_VALUE(SystemReset, 0) ENUM_VALUE(MachineCheck, 1) ENUM_VALUE(DSI, 2) ENUM_VALUE(ISI, 3) ENUM_VALUE(ExternalInterrupt, 4) ENUM_VALUE(Alignment, 5) ENUM_VALUE(Program, 6) ENUM_VALUE(FloatingPoint, 7) ENUM_VALUE(Decrementer, 8) ENUM_VALUE(SystemCall, 9) ENUM_VALUE(Trace, 10) ENUM_VALUE(PerformanceMonitor, 11) ENUM_VALUE(Breakpoint, 12) ENUM_VALUE(SystemInterrupt, 13) ENUM_VALUE(ICI, 14) ENUM_VALUE(Max, 15) ENUM_END(OSExceptionType) ENUM_BEG(OSFunctionType, uint32_t) ENUM_VALUE(HioOpen, 1) ENUM_VALUE(HioReadAsync, 2) ENUM_VALUE(HioWriteAsync, 3) ENUM_VALUE(FsaCmdAsync, 4) ENUM_VALUE(FsaPrCmdAsync, 5) ENUM_VALUE(FsaPrCmdAsyncNoAlloc, 6) ENUM_VALUE(FsaAttachEvent, 7) ENUM_VALUE(FsCmdAsync, 8) ENUM_VALUE(FsCmdHandler, 9) ENUM_VALUE(FsAttachEvent, 10) ENUM_VALUE(FsStateChangeEvent, 11) ENUM_END(OSFunctionType) ENUM_BEG(OSHandleError, uint32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(InvalidArgument, 0xBAD50002) ENUM_VALUE(InvalidHandle, 0xBAD50004) ENUM_VALUE(TableFull, 0xBAD50005) ENUM_VALUE(InternalError, 0xBAD50006) ENUM_END(OSHandleError) ENUM_BEG(OSMapMemoryPermission, uint32_t) ENUM_VALUE(ReadOnly, 0x1) ENUM_VALUE(ReadWrite, 0x2) ENUM_END(OSMapMemoryPermission) ENUM_BEG(OSMemoryType, uint32_t) ENUM_VALUE(MEM1, 1) ENUM_VALUE(MEM2, 2) ENUM_END(OSMemoryType) FLAGS_BEG(OSMessageFlags, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(Blocking, 1 << 0) FLAGS_VALUE(HighPriority, 1 << 1) FLAGS_END(OSMessageFlags) ENUM_BEG(OSSharedDataType, uint32_t) ENUM_VALUE(FontChinese, 0) ENUM_VALUE(FontKorean, 1) ENUM_VALUE(FontStandard, 2) ENUM_VALUE(FontTaiwanese, 3) ENUM_VALUE(Max, 4) ENUM_END(OSSharedDataType) ENUM_BEG(OSShutdownReason, uint32_t) ENUM_VALUE(NoShutdown, 0) ENUM_END(OSShutdownReason) FLAGS_BEG(OSThreadAttributes, uint8_t) //! Allow the thread to run on CPU0. FLAGS_VALUE(AffinityCPU0, 1 << 0) //! Allow the thread to run on CPU1. FLAGS_VALUE(AffinityCPU1, 1 << 1) //! Allow the thread to run on CPU2. FLAGS_VALUE(AffinityCPU2, 1 << 2) //! Allow the thread to run any CPU. FLAGS_VALUE(AffinityAny, (1 << 0) | (1 << 1) | (1 << 2)) //! Start the thread detached. FLAGS_VALUE(Detached, 1 << 3) //! Enables tracking of stack usage. FLAGS_VALUE(StackUsage, 1 << 5) FLAGS_END(OSThreadAttributes) FLAGS_BEG(OSThreadCancelState, uint32_t) //! Thread cancel is enabled FLAGS_VALUE(Enabled, 0) //! Thread cancel is disabled by OSSetThreadCancelState FLAGS_VALUE(Disabled, 1 << 0) //! Thread cancel is disabled because the thread owns a mutex FLAGS_VALUE(DisabledByMutex, 1 << 16) //! Thread cancel is disabled because the thread owns an uninterruptible spinlock FLAGS_VALUE(DisabledBySpinlock, 1 << 17) //! Thread cancel is disabled because the thread has a user stack pointer FLAGS_VALUE(DisabledByUserStackPointer, 1 << 18) //! Thread cancel is disabled because the thread owns a fast mutex FLAGS_VALUE(DisabledByFastMutex, 1 << 19) FLAGS_END(OSThreadCancelState) ENUM_BEG(OSThreadQuantum, uint32_t) ENUM_VALUE(Infinite, 0) ENUM_VALUE(MinMicroseconds, 10) ENUM_VALUE(MaxMicroseconds, 0xFFFFF) ENUM_END(OSThreadQuantum) ENUM_BEG(OSThreadRequest, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Suspend, 1) ENUM_VALUE(Cancel, 2) ENUM_END(OSThreadRequest) FLAGS_BEG(OSThreadState, uint8_t) FLAGS_VALUE(None, 0) //! Thread is ready to run FLAGS_VALUE(Ready, 1 << 0) //! Thread is running FLAGS_VALUE(Running, 1 << 1) //! Thread is waiting, i.e. on a mutex FLAGS_VALUE(Waiting, 1 << 2) //! Thread is about to terminate FLAGS_VALUE(Moribund, 1 << 3) FLAGS_END(OSThreadState) ENUM_BEG(OSThreadType, uint32_t) ENUM_VALUE(Driver, 0) ENUM_VALUE(AppIo, 1) ENUM_VALUE(App, 2) ENUM_END(OSThreadType) ENUM_BEG(FSAClientState, uint32_t) ENUM_VALUE(Free, 0) ENUM_VALUE(Allocated, 1) ENUM_VALUE(Closing, 2) ENUM_END(FSAClientState) ENUM_BEG(FSAIpcRequestType, uint16_t) ENUM_VALUE(Ioctl, 0) ENUM_VALUE(Ioctlv, 1) ENUM_END(FSAIpcRequestType) ENUM_BEG(FSCmdBlockStatus, uint32_t) ENUM_VALUE(Initialised, 0xD900A21) ENUM_VALUE(QueuedCommand, 0xD900A22) ENUM_VALUE(DeqeuedCommand, 0xD900A23) ENUM_VALUE(Cancelled, 0xD900A24) ENUM_VALUE(Completed, 0xD900A26) ENUM_END(FSCmdBlockStatus) ENUM_BEG(FSCmdCancelFlags, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Cancelling, 1) ENUM_END(FSCmdCancelFlags) FLAGS_BEG(FSCmdQueueStatus, uint32_t) FLAGS_VALUE(MaxActiveCommands, 1) FLAGS_VALUE(Suspended, 1 << 4) FLAGS_END(FSCmdQueueStatus) FLAGS_BEG(FSErrorFlag, uint32_t) FLAGS_VALUE(None, 0x0) FLAGS_VALUE(Max, 0x1) FLAGS_VALUE(AlreadyOpen, 0x2) FLAGS_VALUE(Exists, 0x4) FLAGS_VALUE(NotFound, 0x8) FLAGS_VALUE(NotFile, 0x10) FLAGS_VALUE(NotDir, 0x20) FLAGS_VALUE(AccessError, 0x40) FLAGS_VALUE(PermissionError, 0x80) FLAGS_VALUE(FileTooBig, 0x100) FLAGS_VALUE(StorageFull, 0x200) FLAGS_VALUE(UnsupportedCmd, 0x400) FLAGS_VALUE(JournalFull, 0x800) FLAGS_VALUE(All, 0xFFFFFFFF) FLAGS_END(FSErrorFlag) ENUM_BEG(FSMountSourceType, uint32_t) ENUM_VALUE(SdCard, 0) ENUM_VALUE(HostFileIO, 1) ENUM_VALUE(Bind, 2) ENUM_END(FSMountSourceType) ENUM_BEG(FSStatus, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Cancelled, -1) ENUM_VALUE(End, -2) ENUM_VALUE(Max, -3) ENUM_VALUE(AlreadyOpen, -4) ENUM_VALUE(Exists, -5) ENUM_VALUE(NotFound, -6) ENUM_VALUE(NotFile, -7) ENUM_VALUE(NotDirectory, -8) ENUM_VALUE(AccessError, -9) ENUM_VALUE(PermissionError, -10) ENUM_VALUE(FileTooBig, -11) ENUM_VALUE(StorageFull, -12) ENUM_VALUE(JournalFull, -13) ENUM_VALUE(UnsupportedCmd, -14) ENUM_VALUE(MediaNotReady, -15) ENUM_VALUE(MediaError, -17) ENUM_VALUE(Corrupted, -18) ENUM_VALUE(FatalError, -0x400) ENUM_END(FSStatus) ENUM_BEG(FSVolumeState, uint32_t) ENUM_VALUE(Initial, 0) ENUM_VALUE(Ready, 1) ENUM_VALUE(NoMedia, 2) ENUM_VALUE(InvalidMedia, 3) ENUM_VALUE(DirtyMedia, 4) ENUM_VALUE(WrongMedia, 5) ENUM_VALUE(MediaError, 6) ENUM_VALUE(DataCorrupted, 7) ENUM_VALUE(WriteProtected, 8) ENUM_VALUE(JournalFull, 9) ENUM_VALUE(Fatal, 10) ENUM_VALUE(Invalid, 11) ENUM_END(FSVolumeState) ENUM_BEG(GHSSyscallID, uint32_t) ENUM_VALUE(time, 0x0000E) ENUM_VALUE(close, 0x20005) ENUM_VALUE(unlink, 0x20008) ENUM_VALUE(system, 0x2000A) ENUM_VALUE(creat, 0x30006) ENUM_VALUE(access, 0x3000B) ENUM_VALUE(read, 0x40000) ENUM_VALUE(write, 0x40001) ENUM_VALUE(open, 0x40004) ENUM_VALUE(lseek, 0x40007) ENUM_VALUE(fnctl, 0x40012) ENUM_VALUE(fstat, 0x40018) ENUM_VALUE(stat, 0x40019) ENUM_END(GHSSyscallID) ENUM_BEG(MEMBaseHeapType, uint32_t) ENUM_VALUE(MEM1, 0) ENUM_VALUE(MEM2, 1) ENUM_VALUE(FG, 8) ENUM_VALUE(Max, 9) ENUM_VALUE(Invalid, 10) ENUM_END(MEMBaseHeapType) ENUM_BEG(MEMExpHeapMode, uint32_t) ENUM_VALUE(FirstFree, 0) ENUM_VALUE(NearestSize, 1) ENUM_END(MEMExpHeapMode) ENUM_BEG(MEMExpHeapDirection, uint32_t) ENUM_VALUE(FromStart, 0) ENUM_VALUE(FromEnd, 1) ENUM_END(MEMExpHeapDirection) FLAGS_BEG(MEMFrameHeapFreeMode, uint32_t) FLAGS_VALUE(Head, 1 << 0) FLAGS_VALUE(Tail, 1 << 1) FLAGS_END(MEMFrameHeapFreeMode) ENUM_BEG(MEMHeapTag, uint32_t) ENUM_VALUE(ExpandedHeap, 0x45585048) ENUM_VALUE(FrameHeap, 0x46524D48) ENUM_VALUE(UnitHeap, 0x554E5448) ENUM_VALUE(UserHeap, 0x55535248) ENUM_VALUE(BlockHeap, 0x424C4B48) ENUM_END(MEMHeapTag) ENUM_BEG(MEMHeapFillType, uint32_t) ENUM_VALUE(Unused, 0x0) ENUM_VALUE(Allocated, 0x1) ENUM_VALUE(Freed, 0x2) ENUM_VALUE(Max, 0x3) ENUM_END(MEMHeapFillType) FLAGS_BEG(MEMHeapFlags, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(ZeroAllocated, 1 << 0) FLAGS_VALUE(DebugMode, 1 << 1) FLAGS_VALUE(ThreadSafe, 1 << 2) FLAGS_END(MEMHeapFlags) FLAGS_BEG(MPTaskQueueState, uint32_t) FLAGS_VALUE(Initialised, 1 << 0) FLAGS_VALUE(Ready, 1 << 1) FLAGS_VALUE(Stopping, 1 << 2) FLAGS_VALUE(Stopped, 1 << 3) FLAGS_VALUE(Finished, 1 << 4) FLAGS_END(MPTaskQueueState) FLAGS_BEG(MPTaskState, uint32_t) FLAGS_VALUE(Initialised, 1 << 0) FLAGS_VALUE(Ready, 1 << 1) FLAGS_VALUE(Running, 1 << 2) FLAGS_VALUE(Finished, 1 << 3) FLAGS_END(MPTaskState) ENUM_BEG(SIRegisters, uint32_t) ENUM_VALUE(Controller0Command, 0) ENUM_VALUE(Controller0Status0, 1) ENUM_VALUE(Controller0Status1, 2) ENUM_VALUE(Controller1Command, 3) ENUM_VALUE(Controller1Status0, 4) ENUM_VALUE(Controller1Status1, 5) ENUM_VALUE(Controller2Command, 6) ENUM_VALUE(Controller2Status0, 7) ENUM_VALUE(Controller2Status1, 8) ENUM_VALUE(Controller3Command, 9) ENUM_VALUE(Controller3Status0, 10) ENUM_VALUE(Controller3Status1, 11) ENUM_VALUE(PollControl, 12) ENUM_VALUE(DeviceStatus, 13) ENUM_VALUE(ControllerError, 14) ENUM_END(SIRegisters) ENUM_NAMESPACE_EXIT(coreinit) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef COREINIT_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_enum_string.cpp ================================================ #include "coreinit_enum_string.h" #undef COREINIT_ENUM_H #include #include "coreinit_enum.h" #include ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_enum_string.h ================================================ #pragma once #include #include #include "coreinit_enum.h" #undef COREINIT_ENUM_H #include #include "coreinit_enum.h" #include ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_event.cpp ================================================ #include "coreinit.h" #include "coreinit_alarm.h" #include "coreinit_event.h" #include "coreinit_memheap.h" #include "coreinit_scheduler.h" #include "coreinit_time.h" #include "cafe/cafe_stackobject.h" namespace cafe::coreinit { static AlarmCallbackFn sEventAlarmHandler; /** * Initialises an event structure. */ void OSInitEvent(virt_ptr event, BOOL value, OSEventMode mode) { OSInitEventEx(event, value, mode, nullptr); } /** * Initialises an event structure. */ void OSInitEventEx(virt_ptr event, BOOL value, OSEventMode mode, virt_ptr name) { decaf_check(event); event->tag = OSEvent::Tag; event->mode = mode; event->value = value; event->name = name; OSInitThreadQueueEx(virt_addrof(event->queue), event); } /** * Signal an event. * * This will set the events signal value to true. * * In auto reset mode: * - Will wake up the first thread possible to wake * - If a thread is woken, the event value is reset to FALSE * * In manual reset mode: * - Wakes up all possible threads in the waiting queue * - The event value remains TRUE until the user calls OSResetEvent */ void OSSignalEvent(virt_ptr event) { internal::lockScheduler(); decaf_check(event); decaf_check(event->tag == OSEvent::Tag); if (event->value) { // Event has already been set internal::unlockScheduler(); return; } // Set the event event->value = TRUE; if (event->mode == OSEventMode::AutoReset) { if (!internal::ThreadQueue::empty(virt_addrof(event->queue))) { auto wakeThread = virt_ptr { nullptr }; // Find the first thread that we can wake for (auto thread = event->queue.head; thread; thread = thread->link.next) { decaf_check(thread->queue == virt_addrof(event->queue)); if (thread->waitEventTimeoutAlarm) { if (!internal::cancelAlarm(thread->waitEventTimeoutAlarm)) { // If we could not cancel the alarm, do not wake this thread continue; } } wakeThread = thread; break; } if (wakeThread) { // Reset the event event->value = FALSE; internal::wakeupOneThreadNoLock(wakeThread); } internal::rescheduleAllCoreNoLock(); } } else { // Wake all possible threads for (auto thread = event->queue.head; thread; ) { decaf_check(thread->queue == virt_addrof(event->queue)); // Save thread->link.next as it will be reset by wakeupOneThreadNoLock auto next = thread->link.next; if (thread->waitEventTimeoutAlarm) { if (!internal::cancelAlarm(thread->waitEventTimeoutAlarm)) { // If we could not cancel the alarm, do not wake this thread thread = next; continue; } } internal::wakeupOneThreadNoLock(thread); thread = next; } internal::rescheduleAllCoreNoLock(); } internal::unlockScheduler(); } /** * Signal the event and wakeup all waiting threads. * * In manual reset mode: * - Wakes up all possible threads in the waiting queue * - The event value will always be set to TRUE. * * In auto reset mode: * - Wakes up all possible threads in the waiting queue * - The event value will only be set to TRUE if no threads are woken. */ void OSSignalEventAll(virt_ptr event) { internal::lockScheduler(); decaf_check(event); decaf_check(event->tag == OSEvent::Tag); if (event->value) { // Event has already been set internal::unlockScheduler(); return; } // Manual reset always sets the event value to TRUE if (event->mode == OSEventMode::ManualReset) { event->value = TRUE; } if (!internal::ThreadQueue::empty(virt_addrof(event->queue))) { auto threadsWoken = 0u; // Wake any waiting threads for (auto thread = event->queue.head; thread; thread = thread->link.next) { decaf_check(thread->queue == virt_addrof(event->queue)); if (thread->waitEventTimeoutAlarm) { if (!internal::cancelAlarm(thread->waitEventTimeoutAlarm)) { // If we could not cancel the alarm, do not wake this thread continue; } } internal::wakeupOneThreadNoLock(thread); threadsWoken++; } // Auto reset will only set the event value if no threads were woken if (event->mode == OSEventMode::AutoReset && threadsWoken == 0) { event->value = TRUE; } internal::rescheduleAllCoreNoLock(); } internal::unlockScheduler(); } /** * Reset the event value to FALSE */ void OSResetEvent(virt_ptr event) { internal::lockScheduler(); decaf_check(event); decaf_check(event->tag == OSEvent::Tag); // Reset event event->value = FALSE; internal::unlockScheduler(); } /** * Wait for the event value to become TRUE. * * If the event value is already TRUE: * - Returns immediately, no wait is performed. * - Sets event value to FALSE if in AutoReset mode. * * If the event value is FALSE: * - The current thread will go to sleep until the event is signalled by another thread. */ void OSWaitEvent(virt_ptr event) { internal::lockScheduler(); decaf_check(event); // HACK: Naughty Bayonetta not initialising event before using it. // decaf_check(event->tag == OSEvent::Tag); if (event->tag != OSEvent::Tag) { OSInitEvent(event, false, OSEventMode::ManualReset); } // Check if the event is already set if (event->value) { if (event->mode == OSEventMode::AutoReset) { // Reset event event->value = FALSE; } } else { // Wait for event to be set internal::sleepThreadNoLock(virt_addrof(event->queue)); internal::rescheduleSelfNoLock(); } internal::unlockScheduler(); } struct EventAlarmData { virt_ptr event; virt_ptr thread; BOOL timeout; }; static void EventAlarmHandler(virt_ptr alarm, virt_ptr context) { // Wakeup the thread waiting on this alarm auto data = virt_cast(OSGetAlarmUserData(alarm)); data->timeout = TRUE; // Remove this alarm from the thread data->thread->waitEventTimeoutAlarm = nullptr; // System Alarm, we already have the scheduler lock internal::wakeupOneThreadNoLock(data->thread); } /** * Wait for an event value to be TRUE with a timeout * * Behaves the same than OSWaitEvent but with a timeout. * * Returns TRUE if the event was signalled, FALSE if wait timed out. */ BOOL OSWaitEventWithTimeout(virt_ptr event, OSTimeNanoseconds timeout) { auto data = StackObject { }; auto alarm = StackObject { }; internal::lockScheduler(); // Check if event is already set if (event->value) { if (event->mode == OSEventMode::AutoReset) { // Reset event event->value = FALSE; } internal::unlockScheduler(); return TRUE; } // Setup some alarm data for callback auto thread = OSGetCurrentThread(); data->event = event; data->thread = thread; data->timeout = FALSE; // Create an alarm to trigger timeout auto timeoutTicks = internal::nsToTicks(timeout); OSCreateAlarm(alarm); internal::setAlarmInternal(alarm, timeoutTicks, sEventAlarmHandler, data); // Set waitEventTimeoutAlarm so we can cancel it when event is signalled thread->waitEventTimeoutAlarm = alarm; // Wait for the event internal::sleepThreadNoLock(virt_addrof(event->queue)); internal::rescheduleAllCoreNoLock(); // Clear waitEventTimeoutAlarm thread->waitEventTimeoutAlarm = nullptr; auto result = FALSE; if (event->value) { if (event->mode == OSEventMode::AutoReset) { // Reset the event if its in auto-reset mode event->value = FALSE; } result = TRUE; } else if (!data->timeout) { result = TRUE; } internal::unlockScheduler(); return result; } void Library::registerEventSymbols() { RegisterFunctionExport(OSInitEvent); RegisterFunctionExport(OSInitEventEx); RegisterFunctionExport(OSSignalEvent); RegisterFunctionExport(OSSignalEventAll); RegisterFunctionExport(OSResetEvent); RegisterFunctionExport(OSWaitEvent); RegisterFunctionExport(OSWaitEventWithTimeout); RegisterFunctionInternal(EventAlarmHandler, sEventAlarmHandler); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_event.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_time.h" #include "coreinit_thread.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_event Event Object * \ingroup coreinit * * Standard event object implementation. There are two supported event object modes, check OSEventMode. * * Similar to Windows Event Objects. * @{ */ #pragma pack(push, 1) struct OSEvent { static constexpr uint32_t Tag = 0x65566E54; //! Should always be set to the value OSEvent::Tag. be2_val tag; //! Name set by OSInitEventEx. be2_ptr name; UNKNOWN(4); //! The current value of the event object. be2_val value; //! The threads currently waiting on this event object with OSWaitEvent. be2_struct queue; //! The mode of the event object, set by OSInitEvent. be2_val mode; }; CHECK_OFFSET(OSEvent, 0x0, tag); CHECK_OFFSET(OSEvent, 0x4, name); CHECK_OFFSET(OSEvent, 0xC, value); CHECK_OFFSET(OSEvent, 0x10, queue); CHECK_OFFSET(OSEvent, 0x20, mode); CHECK_SIZE(OSEvent, 0x24); #pragma pack(pop) void OSInitEvent(virt_ptr event, BOOL value, OSEventMode mode); void OSInitEventEx(virt_ptr event, BOOL value, OSEventMode mode, virt_ptr name); void OSSignalEvent(virt_ptr event); void OSSignalEventAll(virt_ptr event); void OSWaitEvent(virt_ptr event); void OSResetEvent(virt_ptr event); BOOL OSWaitEventWithTimeout(virt_ptr event, OSTimeNanoseconds timeout); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_exception.cpp ================================================ #include "coreinit.h" #include "coreinit_core.h" #include "coreinit_exception.h" #include "coreinit_interrupts.h" #include "coreinit_thread.h" #include "cafe/kernel/cafe_kernel_exception.h" namespace cafe::coreinit { struct StaticExceptionData { be2_array dsiCallback; be2_array isiCallback; be2_array programCallback; be2_array alignCallback; }; static virt_ptr sGlobalExceptions = nullptr; static void unkSyscall0x7A00(kernel::ExceptionType type, uint32_t value) { // TODO: I think this might be clear & enable or something? } OSExceptionCallbackFn OSSetExceptionCallback(OSExceptionType type, OSExceptionCallbackFn callback) { return OSSetExceptionCallbackEx(OSExceptionMode::Thread, type, callback); } OSExceptionCallbackFn OSSetExceptionCallbackEx(OSExceptionMode mode, OSExceptionType type, OSExceptionCallbackFn callback) { // Only certain exceptions are supported for callback if (type != OSExceptionType::DSI && type != OSExceptionType::ISI && type != OSExceptionType::Alignment && type != OSExceptionType::Program && type != OSExceptionType::PerformanceMonitor) { return nullptr; } auto restoreInterruptValue = OSDisableInterrupts(); auto previousCallback = internal::getExceptionCallback(mode, type); internal::setExceptionCallback(mode, type, callback); if (mode == OSExceptionMode::Thread && type == OSExceptionType::PerformanceMonitor) { } OSRestoreInterrupts(restoreInterruptValue); return previousCallback; } namespace internal { OSExceptionCallbackFn getExceptionCallback(OSExceptionMode mode, OSExceptionType type) { auto thread = OSGetCurrentThread(); auto core = OSGetCoreId(); auto callback = OSExceptionCallbackFn { nullptr }; if (mode == OSExceptionMode::Thread || mode == OSExceptionMode::ThreadAllCores || mode == OSExceptionMode::System) { switch (type) { case OSExceptionType::DSI: callback = thread->dsiCallback[core]; break; case OSExceptionType::ISI: callback = thread->isiCallback[core]; break; case OSExceptionType::Alignment: callback = thread->alignCallback[core]; break; case OSExceptionType::Program: callback = thread->programCallback[core]; break; case OSExceptionType::PerformanceMonitor: callback = thread->perfMonCallback[core]; break; } if (callback || mode != OSExceptionMode::System) { return callback; } } if (mode == OSExceptionMode::Global || mode == OSExceptionMode::GlobalAllCores || mode == OSExceptionMode::System) { switch (type) { case OSExceptionType::DSI: callback = sGlobalExceptions->dsiCallback[core]; case OSExceptionType::ISI: callback = sGlobalExceptions->isiCallback[core]; case OSExceptionType::Alignment: callback = sGlobalExceptions->alignCallback[core]; case OSExceptionType::Program: callback = sGlobalExceptions->programCallback[core]; default: return nullptr; } } return callback; } void setExceptionCallback(OSExceptionMode mode, OSExceptionType type, OSExceptionCallbackFn callback) { auto thread = OSGetCurrentThread(); auto core = OSGetCoreId(); if (mode == OSExceptionMode::Thread) { switch (type) { case OSExceptionType::DSI: thread->dsiCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::DSI, 0); break; case OSExceptionType::ISI: thread->isiCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::ISI, 0); break; case OSExceptionType::Alignment: thread->alignCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0); break; case OSExceptionType::Program: thread->programCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::Program, 0); break; case OSExceptionType::PerformanceMonitor: thread->perfMonCallback[core] = callback; break; } } else if (mode == OSExceptionMode::ThreadAllCores) { switch (type) { case OSExceptionType::DSI: thread->dsiCallback[0] = callback; thread->dsiCallback[1] = callback; thread->dsiCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::DSI, 0); break; case OSExceptionType::ISI: thread->isiCallback[0] = callback; thread->isiCallback[1] = callback; thread->isiCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::ISI, 0); break; case OSExceptionType::Alignment: thread->alignCallback[0] = callback; thread->alignCallback[1] = callback; thread->alignCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0); break; case OSExceptionType::Program: thread->programCallback[0] = callback; thread->programCallback[1] = callback; thread->programCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::Program, 0); break; case OSExceptionType::PerformanceMonitor: thread->perfMonCallback[0] = callback; thread->perfMonCallback[1] = callback; thread->perfMonCallback[2] = callback; break; } } else if (mode == OSExceptionMode::Global) { switch (type) { case OSExceptionType::DSI: sGlobalExceptions->dsiCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::DSI, 0); break; case OSExceptionType::ISI: sGlobalExceptions->isiCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::ISI, 0); break; case OSExceptionType::Alignment: sGlobalExceptions->alignCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0); break; case OSExceptionType::Program: sGlobalExceptions->programCallback[core] = callback; unkSyscall0x7A00(kernel::ExceptionType::Program, 0); break; } } else if (mode == OSExceptionMode::GlobalAllCores) { switch (type) { case OSExceptionType::DSI: sGlobalExceptions->dsiCallback[0] = callback; sGlobalExceptions->dsiCallback[1] = callback; sGlobalExceptions->dsiCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::DSI, 0); break; case OSExceptionType::ISI: sGlobalExceptions->isiCallback[0] = callback; sGlobalExceptions->isiCallback[1] = callback; sGlobalExceptions->isiCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::ISI, 0); break; case OSExceptionType::Alignment: sGlobalExceptions->alignCallback[0] = callback; sGlobalExceptions->alignCallback[1] = callback; sGlobalExceptions->alignCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0); break; case OSExceptionType::Program: sGlobalExceptions->programCallback[0] = callback; sGlobalExceptions->programCallback[1] = callback; sGlobalExceptions->programCallback[2] = callback; unkSyscall0x7A00(kernel::ExceptionType::Program, 0); break; } } } void initialiseExceptionHandlers() { // TODO: initialiseExceptionHandlers } } // namespace internal void Library::registerExceptionSymbols() { RegisterFunctionExport(OSSetExceptionCallback); RegisterFunctionExport(OSSetExceptionCallbackEx); RegisterDataInternal(sGlobalExceptions); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_exception.h ================================================ #pragma once #include "coreinit_context.h" #include "coreinit_enum.h" #include namespace cafe::coreinit { using OSExceptionCallbackFn = virt_func_ptr< BOOL(virt_ptr context)>; OSExceptionCallbackFn OSSetExceptionCallback(OSExceptionType type, OSExceptionCallbackFn callback); OSExceptionCallbackFn OSSetExceptionCallbackEx(OSExceptionMode mode, OSExceptionType type, OSExceptionCallbackFn callback); namespace internal { void initialiseExceptionHandlers(); OSExceptionCallbackFn getExceptionCallback(OSExceptionMode mode, OSExceptionType type); void setExceptionCallback(OSExceptionMode mode, OSExceptionType type, OSExceptionCallbackFn callback); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fastmutex.cpp ================================================ #include "coreinit.h" #include "coreinit_fastmutex.h" #include "coreinit_scheduler.h" namespace cafe::coreinit { using FastMutexQueue = internal::Queue; using ContendedQueue = internal::Queue; using ThreadSimpleQueue = internal::SortedQueue; /** * Initialise a fast mutex object. */ void OSFastMutex_Init(virt_ptr mutex, virt_ptr name) { mutex->tag = OSFastMutex::Tag; mutex->name = name; mutex->isContended = FALSE; mutex->lock.store(0); mutex->count = 0; ThreadSimpleQueue::init(virt_addrof(mutex->queue)); FastMutexQueue::initLink(mutex); ContendedQueue::initLink(mutex); } static void fastMutexHardLock(virt_ptr mutex) { auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); internal::lockScheduler(); internal::testThreadCancelNoLock(); auto lockValue = mutex->lock.load(); while (true) { if (lockValue) { // Check if waiter bit is set, if not we must try set it if (!(lockValue & 1)) { if (!mutex->lock.compare_exchange_weak(lockValue, lockValue | 1)) { continue; } } // We now have set the waiter bit and we were not the owner auto ownerThread = virt_cast(virt_addr { lockValue & ~1 }); if (!mutex->isContended) { ContendedQueue::append(virt_addrof(ownerThread->contendedFastMutexes), mutex); mutex->isContended = TRUE; } // Record which fast mutex we are trying to lock before we sleep thread->fastMutex = mutex; // Promote the priority of the owning thread to prevent priority inversion problems internal::promoteThreadPriorityNoLock(ownerThread, thread->priority); // Sleep on the queue waiting for a hard unlock internal::sleepThreadNoLock(virt_addrof(mutex->queue)); internal::rescheduleSelfNoLock(); // We are no longer attempting to lock this fast mutex thread->fastMutex = nullptr; lockValue = mutex->lock.load(); if (lockValue != 0) { continue; } } // Try to lock the FastMutex auto newValue = static_cast(virt_cast(thread)); if (!mutex->lock.compare_exchange_weak(lockValue, newValue)) { continue; } decaf_check(!(lockValue & 1)); decaf_check(mutex->count == 0); // Set thread as owner thread->cancelState |= OSThreadCancelState::DisabledByFastMutex; FastMutexQueue::append(virt_addrof(thread->fastMutexQueue), mutex); mutex->count = 1; break; } internal::unlockScheduler(); } /** * Lock a fast mutex object. */ void OSFastMutex_Lock(virt_ptr mutex) { auto thread = OSGetCurrentThread(); while (true) { if (thread->cancelState == OSThreadCancelState::Enabled && thread->requestFlag != OSThreadRequest::None) { internal::lockScheduler(); internal::testThreadCancelNoLock(); internal::unlockScheduler(); continue; } auto lockValue = mutex->lock.load(); if (lockValue) { auto lockThread = virt_cast(virt_addr { lockValue & ~1 }); if (lockThread == thread) { // We already own this FastMutex, increase recursion count mutex->count++; break; } else { // Another thread owns this FastMutex, now we must take the slow path! fastMutexHardLock(mutex); break; } } else { // Attempt to lock the thread auto newValue = static_cast(virt_cast(thread)); if (!mutex->lock.compare_exchange_weak(lockValue, newValue)) { continue; } // Set thread as owner thread->cancelState |= OSThreadCancelState::DisabledByFastMutex; FastMutexQueue::append(virt_addrof(thread->fastMutexQueue), mutex); mutex->count = 1; break; } } } static void fastMutexHardUnlock(virt_ptr mutex) { // Grab our current thread and make sure we are running auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); // Check to make sure the mutex is locked by us auto lockValue = mutex->lock.load(); auto lockThread = virt_cast(virt_addr { lockValue & ~1 }); decaf_check(thread == lockThread); internal::lockScheduler(); // Double check that the caller actually did everything properly... decaf_check(lockValue & 1); decaf_check(mutex->count == 0); if (mutex->isContended) { ContendedQueue::erase(virt_addrof(thread->contendedFastMutexes), mutex); mutex->isContended = FALSE; } // Adjust the priority if needed if (thread->priority > thread->basePriority) { thread->priority = internal::calculateThreadPriorityNoLock(thread); } // Free the cancel state if we arn't holding any more locks if (!thread->fastMutexQueue.head) { thread->cancelState &= ~OSThreadCancelState::DisabledByFastMutex; } // Wake up anyone who is hard-lock waiting on the mutex internal::wakeupThreadNoLock(virt_addrof(mutex->queue)); // Release the lock! mutex->lock.store(0); internal::testThreadCancelNoLock(); internal::rescheduleSelfNoLock(); internal::unlockScheduler(); } /** * Unlock a fast mutex object. */ void OSFastMutex_Unlock(virt_ptr mutex) { decaf_check(mutex->count > 0); auto thread = OSGetCurrentThread(); auto lockValue = mutex->lock.load(); auto lockThread = virt_cast(virt_addr { lockValue & ~1 }); // Lock not currently held by this thread, ignore. if (lockThread != thread) { return; } // Reduce mutex count mutex->count--; if (mutex->count != 0) { // If count is not 0, then we have not unlocked! return; } // Remove ourselves from the queue FastMutexQueue::erase(virt_addrof(thread->fastMutexQueue), mutex); lockValue = mutex->lock.load(); while (true) { // If someone contended on their lock, we need to hardUnlock if (lockValue & 1) { fastMutexHardUnlock(mutex); return; } // Try to clear the lock if (!mutex->lock.compare_exchange_weak(lockValue, 0)) { continue; } // Success! break; } // Clear the cancel state if we dont hold any more mutexes if (!thread->fastMutexQueue.head) { thread->cancelState &= ~OSThreadCancelState::DisabledByFastMutex; } // Lock the scheduler and consider cancelling if (thread->cancelState == OSThreadCancelState::Enabled) { internal::lockScheduler(); internal::testThreadCancelNoLock(); internal::unlockScheduler(); } } /** * Try to lock a fast mutex object. * * \return * Returns FALSE if the mutex was already locked. * Returns TRUE if the mutex is now locked by the current thread. */ BOOL OSFastMutex_TryLock(virt_ptr mutex) { auto thread = OSGetCurrentThread(); while (true) { if (thread->cancelState == OSThreadCancelState::Enabled && thread->requestFlag != OSThreadRequest::None) { internal::lockScheduler(); internal::testThreadCancelNoLock(); internal::unlockScheduler(); continue; } auto lockValue = mutex->lock.load(); if (lockValue) { auto lockThread = virt_cast(virt_addr { lockValue & ~1 }); if (lockThread == thread) { // We already own this FastMutex, increase recursion count mutex->count++; return TRUE; } else { // Another thread owns this FastMutex, we have failed! return FALSE; } } else { // Try to lock the FastMutex auto newValue = static_cast(virt_cast(thread)); if (!mutex->lock.compare_exchange_weak(lockValue, newValue)) { continue; } // Set thread as owner thread->cancelState |= OSThreadCancelState::DisabledByFastMutex; FastMutexQueue::append(virt_addrof(thread->fastMutexQueue), mutex); mutex->count = 1; return TRUE; } } } /** * Initialises a fast condition object. */ void OSFastCond_Init(virt_ptr condition, virt_ptr name) { condition->tag = OSFastCondition::Tag; condition->name = name; condition->unk = 0u; OSInitThreadQueueEx(virt_addrof(condition->queue), condition); } /** * Sleep the current thread until the condition variable has been signalled. * * The mutex must be locked when entering this function. * Will unlock the mutex and then sleep, reacquiring the mutex when woken. */ void OSFastCond_Wait(virt_ptr condition, virt_ptr mutex) { internal::lockScheduler(); auto thread = OSGetCurrentThread(); auto lockValue = mutex->lock.load(); auto lockThread = virt_cast(virt_addr { lockValue & ~1 }); decaf_check(lockValue & 1); decaf_check(lockThread == thread); if (mutex->isContended) { ContendedQueue::erase(virt_addrof(thread->contendedFastMutexes), mutex); mutex->isContended = FALSE; } if (thread->priority > thread->basePriority) { thread->priority = internal::calculateThreadPriorityNoLock(thread); } // Save the recursion count, then force an unlock of the mutex auto mutexCount = mutex->count; internal::disableScheduler(); internal::wakeupThreadNoLock(virt_addrof(mutex->queue)); mutex->count = 0; mutex->lock.store(0); internal::rescheduleAllCoreNoLock(); internal::enableScheduler(); // Sleep the current thread on the condition queue, wait to be signalled internal::sleepThreadNoLock(virt_addrof(condition->queue)); internal::rescheduleSelfNoLock(); // We must release the scheduler lock before trying to do a FastMutex lock internal::unlockScheduler(); // Acquire the mutex, and restore the recursion count OSFastMutex_Lock(mutex); mutex->count = mutexCount; } /** * Will wake up any threads waiting on the condition with OSFastCond_Wait. */ void OSFastCond_Signal(virt_ptr condition) { OSWakeupThread(virt_addrof(condition->queue)); } namespace internal { void unlockAllFastMutexNoLock(virt_ptr thread) { decaf_check(isSchedulerLocked()); while (thread->fastMutexQueue.head) { auto mutex = thread->fastMutexQueue.head; // Ensure thread owns the mutex auto lockValue = mutex->lock.load(); auto lockThread = virt_cast(virt_addr { lockValue & ~1 }); decaf_check(lockThread == thread); // Erase mutex from thread's mutex queue FastMutexQueue::erase(virt_addrof(thread->fastMutexQueue), mutex); // Erase mutex from thread's contended queue if necessary if (mutex->isContended) { ContendedQueue::erase(virt_addrof(thread->contendedFastMutexes), mutex); mutex->isContended = FALSE; } // Unlock the mutex wakeupThreadNoLock(virt_addrof(mutex->queue)); mutex->count = 0; mutex->lock.store(0); } decaf_check(!thread->fastMutexQueue.head); decaf_check(!thread->fastMutexQueue.tail); decaf_check(!thread->contendedFastMutexes.head); decaf_check(!thread->contendedFastMutexes.tail); } } // namespace internal void Library::registerFastMutexSymbols() { RegisterFunctionExport(OSFastMutex_Init); RegisterFunctionExport(OSFastMutex_Lock); RegisterFunctionExport(OSFastMutex_TryLock); RegisterFunctionExport(OSFastMutex_Unlock); RegisterFunctionExport(OSFastCond_Init); RegisterFunctionExport(OSFastCond_Wait); RegisterFunctionExport(OSFastCond_Signal); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fastmutex.h ================================================ #pragma once #include "coreinit_thread.h" #include #include namespace cafe::coreinit { /** * \defgroup coreinit_fastmutex Fast Mutex * \ingroup coreinit * * Similar to OSMutex but tries to acquire the mutex without using the global * scheduler lock, and does not test for thread cancel. * @{ */ #pragma pack(push, 1) struct OSFastMutex; struct OSFastMutexLink { be2_virt_ptr next; be2_virt_ptr prev; }; CHECK_OFFSET(OSFastMutexLink, 0x00, next); CHECK_OFFSET(OSFastMutexLink, 0x04, prev); CHECK_SIZE(OSFastMutexLink, 0x08); struct OSFastMutex { static constexpr uint32_t Tag = 0x664D7458u; //! Should always be set to the value OSFastMutex::Tag. be2_val tag; //! Name set by OSFastMutex_Init. be2_virt_ptr name; //! Is this thread in the queue be2_val isContended; //! Queue of threads waiting for this mutex to unlock. be2_struct queue; //! Link used inside OSThread's fast mutex queue. be2_struct link; //! Lock bits for the mutex, owner thread and some bits std::atomic lock; //! Current recursion lock count of mutex. be2_val count; //! Link used for contended mutexes be2_struct contendedLink; }; CHECK_OFFSET(OSFastMutex, 0x00, tag); CHECK_OFFSET(OSFastMutex, 0x04, name); CHECK_OFFSET(OSFastMutex, 0x08, isContended); CHECK_OFFSET(OSFastMutex, 0x0C, queue); CHECK_OFFSET(OSFastMutex, 0x14, link); CHECK_OFFSET(OSFastMutex, 0x1C, lock); CHECK_OFFSET(OSFastMutex, 0x20, count); CHECK_OFFSET(OSFastMutex, 0x24, contendedLink); CHECK_SIZE(OSFastMutex, 0x2c); struct OSFastCondition { static constexpr uint32_t Tag = 0x664E6456u; //! Should always be set to the value OSFastCondition::Tag. be2_val tag; //! Name set by OSFastCond_Init. be2_virt_ptr name; //! Unknown data be2_val unk; //! Queue of threads waiting for this condition to signal. be2_struct queue; }; CHECK_OFFSET(OSFastCondition, 0x00, tag); CHECK_OFFSET(OSFastCondition, 0x04, name); CHECK_OFFSET(OSFastCondition, 0x0C, queue); CHECK_SIZE(OSFastCondition, 0x1c); #pragma pack(pop) void OSFastMutex_Init(virt_ptr mutex, virt_ptr name); void OSFastMutex_Lock(virt_ptr mutex); void OSFastMutex_Unlock(virt_ptr mutex); BOOL OSFastMutex_TryLock(virt_ptr mutex); void OSFastCond_Init(virt_ptr condition, virt_ptr name); void OSFastCond_Wait(virt_ptr condition, virt_ptr mutex); void OSFastCond_Signal(virt_ptr condition); /** @} */ namespace internal { void unlockAllFastMutexNoLock(virt_ptr thread); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fiber.cpp ================================================ #include "coreinit.h" #include "coreinit_fiber.h" #include "coreinit_thread.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include namespace cafe::coreinit { /** * Switch to fiber. */ uint32_t OSSwitchFiber(OSFiberEntryFn entry, virt_addr userStack) { auto state = cpu::this_core::state(); auto oldStack = virt_addr { state->gpr[1] }; // Set new stack internal::setUserStackPointer(virt_cast(userStack)); state->gpr[1] = static_cast(userStack); // Call fiber function auto result = cafe::invoke(cpu::this_core::state(), entry); // Restore old stack state->gpr[1] = static_cast(oldStack); internal::removeUserStackPointer(virt_cast(oldStack)); return result; } /** * Switch to fiber with 4 arguments. */ uint32_t OSSwitchFiberEx(uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4, OSFiberExEntryFn entry, virt_addr userStack) { auto state = cpu::this_core::state(); auto oldStack = virt_addr { state->gpr[1] }; // Set new stack internal::setUserStackPointer(virt_cast(userStack)); state->gpr[1] = static_cast(userStack); // Call fiber function auto result = cafe::invoke(cpu::this_core::state(), entry, arg1, arg2, arg3, arg4); // Restore old stack state->gpr[1] = static_cast(oldStack); internal::removeUserStackPointer(virt_cast(oldStack)); return result; } void Library::registerFiberSymbols() { RegisterFunctionExport(OSSwitchFiber); RegisterFunctionExport(OSSwitchFiberEx); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fiber.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_fiber Fiber * \ingroup coreinit * @{ */ using OSFiberEntryFn = virt_func_ptr; using OSFiberExEntryFn = virt_func_ptr; uint32_t OSSwitchFiber(OSFiberEntryFn entry, virt_addr userStack); uint32_t OSSwitchFiberEx(uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4, OSFiberExEntryFn entry, virt_addr userStack); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs.cpp ================================================ #include "coreinit.h" #include "coreinit_appio.h" #include "coreinit_fs.h" #include "coreinit_fs_client.h" #include "coreinit_fs_driver.h" #include "coreinit_fastmutex.h" namespace cafe::coreinit { struct StaticFsData { be2_val initialised; be2_array clientListMutexName; be2_struct clientListMutex; be2_val numClients; be2_virt_ptr clientList; }; static virt_ptr sFsData = nullptr; /** * Initialise filesystem. */ void FSInit() { if (sFsData->initialised || internal::fsDriverDone()) { return; } sFsData->initialised = true; sFsData->clientList = nullptr; sFsData->numClients = 0u; sFsData->clientListMutexName = "{ FSClient }"; OSFastMutex_Init(virt_addrof(sFsData->clientListMutex), virt_addrof(sFsData->clientListMutexName)); internal::initialiseFsDriver(); } /** * Shutdown filesystem. */ void FSShutdown() { } /** * Get an FSAsyncResult from an OSMessage. */ virt_ptr FSGetAsyncResult(virt_ptr message) { return virt_cast(message->message); } /** * Get the number of registered FS clients. */ uint32_t FSGetClientNum() { return sFsData->numClients; } namespace internal { /** * Returns true if filesystem has been intialised. */ bool fsInitialised() { return sFsData->initialised; } /** * Returns true if client is registered. */ bool fsClientRegistered(virt_ptr client) { return fsClientRegistered(fsClientGetBody(client)); } /** * Returns true if client is registered. */ bool fsClientRegistered(virt_ptr clientBody) { auto registered = false; OSFastMutex_Lock(virt_addrof(sFsData->clientListMutex)); if (sFsData->clientList) { auto itr = sFsData->clientList; do { if (itr == clientBody) { registered = true; break; } itr = itr->link.next; } while (itr != sFsData->clientList); } OSFastMutex_Unlock(virt_addrof(sFsData->clientListMutex)); return registered; } /** * Register a client with the filesystem. */ bool fsRegisterClient(virt_ptr clientBody) { OSFastMutex_Lock(virt_addrof(sFsData->clientListMutex)); if (sFsData->clientList) { auto first = sFsData->clientList; auto next = first->link.next; first->link.next = clientBody; next->link.prev = clientBody; clientBody->link.next = next; clientBody->link.prev = first; } else { sFsData->clientList = clientBody; clientBody->link.next = clientBody; clientBody->link.prev = clientBody; } sFsData->numClients++; OSFastMutex_Unlock(virt_addrof(sFsData->clientListMutex)); return true; } /** * Deregister a client from the filesystem. */ bool fsDeregisterClient(virt_ptr clientBody) { OSFastMutex_Lock(virt_addrof(sFsData->clientListMutex)); // If was first item in the list, update list pointer if (sFsData->clientList == clientBody) { if (clientBody->link.prev != clientBody) { sFsData->clientList = clientBody->link.prev; } else { sFsData->clientList = nullptr; } } // Remove from list auto next = clientBody->link.next; auto prev = clientBody->link.prev; prev->link.next = next; next->link.prev = prev; clientBody->link.next = nullptr; clientBody->link.prev = nullptr; sFsData->numClients--; OSFastMutex_Unlock(virt_addrof(sFsData->clientListMutex)); return true; } /** * Initialise an FSAsyncResult structure for an FS command. * * \retval FSStatus::OK * Success. */ FSStatus fsAsyncResultInit(virt_ptr clientBody, virt_ptr asyncResult, virt_ptr asyncData) { asyncResult->asyncData = *asyncData; if (!asyncData->ioMsgQueue) { asyncResult->asyncData.ioMsgQueue = OSGetDefaultAppIOQueue(); } asyncResult->client = clientBody->client; asyncResult->ioMsg.data = asyncResult; asyncResult->ioMsg.type = OSFunctionType::FsCmdAsync; return FSStatus::OK; } } // namespace internal void Library::registerFsSymbols() { RegisterFunctionExport(FSInit); RegisterFunctionExport(FSShutdown); RegisterFunctionExport(FSGetAsyncResult); RegisterFunctionExport(FSGetClientNum); RegisterDataInternal(sFsData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_messagequeue.h" #include "ios/fs/ios_fs_fsa.h" #include /* Unimplemented functions: FSAppendFile FSAppendFileAsync FSCancelAllCommands FSCancelCommand FSDumpLastErrorLog FSFlushMultiQuota FSFlushMultiQuotaAsync FSGetEntryNum FSGetEntryNumAsync FSGetFileBlockAddress FSGetFileBlockAddressAsync FSGetFileSystemInfo FSGetFileSystemInfoAsync FSGetVolumeInfo FSGetVolumeInfoAsync FSMakeLink FSMakeLinkAsync FSMakeQuota FSMakeQuotaAsync FSOpenFileByStat FSOpenFileByStatAsync FSRegisterFlushQuota FSRegisterFlushQuotaAsync FSRemoveQuota FSRemoveQuotaAsync FSRollbackQuota FSRollbackQuotaAsync FSTimeToCalendarTime */ namespace cafe::coreinit { /** * \defgroup coreinit_fs Filesystem * \ingroup coreinit * * The typical flow of a FS command looks like: * * {Game thread} FSOpenFileExAsync * -> fsClientSubmitCommand (fsCmdBlockFinishCmd) * -> fsCmdQueueProcessMsg * -> fsClientHandleDequeuedCommand * -> fsaShimSubmitRequestAsync * -> IOS_IoctlAsync (fsClientHandleFsaAsyncCallback) * * {IPC interrupt} fsClientHandleFsaAsyncCallback * -> SendMessage AppIOQueue FsCmdHandler * * {AppIo thread} receive FsCmdHandler * -> fsCmdBlockHandleResult * -> fsCmdBlockReplyResult * -> blockBody->finishCmdFn (fsCmdBlockFinishCmd) * -> fsCmdBlockSetResult * -> SendMessage asyncData.ioMsgQueue FsCmdAsync * * {AppIo thread} receive FsCmdAsync * -> FSGetAsyncResult(msg)->userParams.callback * * @{ */ #pragma pack(push, 1) struct FSClient; struct FSClientBody; struct FSCmdBlock; struct FSCmdBlockBody; struct FSAsyncData; struct FSAsyncResult; struct FSMessage; struct FSMountSource; using FSDirEntry = ios::fs::FSADirEntry; using FSDirHandle = ios::fs::FSADirHandle; using FSEntryNum = ios::fs::FSAEntryNum; using FSFileHandle = ios::fs::FSAFileHandle; using FSFilePosition = ios::fs::FSAFilePosition; using FSReadFlag = ios::fs::FSAReadFlag; using FSStat = ios::fs::FSAStat; using FSStatFlags = ios::fs::FSAStatFlags; using FSWriteFlag = ios::fs::FSAWriteFlag; using FSAsyncCallbackFn = virt_func_ptr, virt_ptr, FSStatus, virt_ptr)>; static constexpr uint32_t FSMaxBytesPerRequest = 0x100000u; static constexpr uint32_t FSMaxPathLength = ios::fs::FSAPathLength; static constexpr uint32_t FSMaxMountPathLength = 0x80u; static constexpr uint8_t FSMinPriority = 0u; static constexpr uint8_t FSDefaultPriority = 16u; static constexpr uint8_t FSMaxPriority = 32u; struct FSMessage { //! Message data be2_virt_ptr data; UNKNOWN(8); //! Type of message be2_val type; }; CHECK_OFFSET(FSMessage, 0x00, data); CHECK_OFFSET(FSMessage, 0x0C, type); CHECK_SIZE(FSMessage, 0x10); /** * Async data passed to an FS*Async function. */ struct FSAsyncData { //! Callback to call when the command is complete. be2_val userCallback; //! Callback context be2_virt_ptr userContext; //! Queue to put a message on when command is complete. be2_virt_ptr ioMsgQueue; }; CHECK_OFFSET(FSAsyncData, 0x00, userCallback); CHECK_OFFSET(FSAsyncData, 0x04, userContext); CHECK_OFFSET(FSAsyncData, 0x08, ioMsgQueue); CHECK_SIZE(FSAsyncData, 0xC); /** * Stores the result of an async FS command. */ struct FSAsyncResult { //! User supplied async data. be2_struct asyncData; //! Message to put into asyncdata.ioMsgQueue. be2_struct ioMsg; //! FSClient which owns this result. be2_virt_ptr client; //! FSCmdBlock which owns this result. be2_virt_ptr block; //! The result of the command. be2_val status; }; CHECK_OFFSET(FSAsyncResult, 0x00, asyncData); CHECK_OFFSET(FSAsyncResult, 0x0c, ioMsg); CHECK_OFFSET(FSAsyncResult, 0x1c, client); CHECK_OFFSET(FSAsyncResult, 0x20, block); CHECK_OFFSET(FSAsyncResult, 0x24, status); CHECK_SIZE(FSAsyncResult, 0x28); /** * Information about a mount. */ struct FSMountSource { //! Mount type be2_val sourceType; //! Mount path be2_array path; }; CHECK_OFFSET(FSMountSource, 0x0, sourceType); CHECK_OFFSET(FSMountSource, 0x4, path); CHECK_SIZE(FSMountSource, 0x283); #pragma pack(pop) void FSInit(); void FSShutdown(); virt_ptr FSGetAsyncResult(virt_ptr message); uint32_t FSGetClientNum(); namespace internal { bool fsInitialised(); bool fsClientRegistered(virt_ptr client); bool fsClientRegistered(virt_ptr clientBody); bool fsRegisterClient(virt_ptr clientBody); bool fsDeregisterClient(virt_ptr clientBody); FSStatus fsAsyncResultInit(virt_ptr clientBody, virt_ptr asyncResult, virt_ptr asyncData); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_client.cpp ================================================ #include "coreinit.h" #include "coreinit_appio.h" #include "coreinit_fastmutex.h" #include "coreinit_fs.h" #include "coreinit_fs_client.h" #include "coreinit_fs_driver.h" #include "coreinit_fs_cmdblock.h" #include "coreinit_fsa_shim.h" #include "cafe/cafe_stackobject.h" #include #include #include #include namespace cafe::coreinit { static IOSAsyncCallbackFn sHandleFsaAsyncCallback = nullptr; static FSCmdQueueHandlerFn sHandleDequeuedCommand = nullptr; /** * Create a new FSClient. */ FSStatus FSAddClient(virt_ptr client, FSErrorFlag errorMask) { return FSAddClientEx(client, nullptr, errorMask); } /** * Create a new FSClient. */ FSStatus FSAddClientEx(virt_ptr client, virt_ptr attachParams, FSErrorFlag errorMask) { if (!internal::fsInitialised()) { return FSStatus::FatalError; } if (!client) { return FSStatus::FatalError; } if (internal::fsClientRegistered(client)) { internal::fsClientHandleFatalError(internal::fsClientGetBody(client), FSAStatus::AlreadyExists); return FSStatus::FatalError; } std::memset(client.get(), 0, sizeof(FSClient)); auto clientBody = internal::fsClientGetBody(client); auto clientHandle = internal::fsaShimOpen(); if (clientHandle < 0) { return FSStatus::FatalError; } if (!internal::fsRegisterClient(clientBody)) { internal::fsaShimClose(clientHandle); internal::fsClientHandleFatalError(clientBody, FSAStatus::MaxClients); return FSStatus::FatalError; } internal::fsCmdQueueCreate(virt_addrof(clientBody->cmdQueue), sHandleDequeuedCommand, 1); OSFastMutex_Init(virt_addrof(clientBody->mutex), nullptr); OSCreateAlarm(virt_addrof(clientBody->fsmAlarm)); internal::fsmInit(virt_addrof(clientBody->fsm), clientBody); clientBody->clientHandle = clientHandle; clientBody->lastDequeuedCommand = nullptr; clientBody->emulatedError = FSAStatus::OK; clientBody->unk0x14CC = 0u; // TODO: Initialise attach params related data if (attachParams) { // 0x1240 = 4 // 0x1248 = attachParams.callback // 0x124C = this // 0x1254 = &(this + 0x1248) // FSMessage ? // 0x1260 = 0xA // OSFunctionType::FsAttach ? // 0x1428 = 0 // 0x1440 = attachParams.context } else { // 0x1240 = 0 // 0x1428 = 0 } return FSStatus::OK; } /** * Destroy an FSClient. * * Might block thread to wait for last command to finish. */ FSStatus FSDelClient(virt_ptr client, FSErrorFlag errorMask) { auto clientBody = internal::fsClientGetBody(client); if (!internal::fsInitialised()) { return FSStatus::FatalError; } if (!clientBody->link.prev) { // Already deleted. return FSStatus::FatalError; } // Prevent any new commands from being started. internal::fsCmdQueueSuspend(virt_addrof(clientBody->cmdQueue)); // Wait for last active command to be completed. auto sleptMS = 0; auto timeoutMS = 10; while (clientBody->cmdQueue.activeCmds) { if (clientBody->fsm.clientVolumeState != FSVolumeState::Fatal) { // The FS client is in an error state. break; } if (clientBody->lastDequeuedCommand && clientBody->lastDequeuedCommand->status == FSCmdBlockStatus::Completed) { // The last command has completed. break; } if (sleptMS == timeoutMS) { // We have timed out waiting for last command to complete. break; } OSSleepTicks(internal::msToTicks(1)); sleptMS += 1; } // Cleanup. internal::fsCmdQueueDestroy(virt_addrof(clientBody->cmdQueue)); internal::fsaShimClose(clientBody->clientHandle); internal::fsDeregisterClient(clientBody); return FSStatus::OK; } /** * Get the current active command block for a client. * * This is the command which has been sent over IOS IPC to the FSA device. * * \return * Returns a pointer to FSCmdBlock or nullptr if there is no active command. */ virt_ptr FSGetCurrentCmdBlock(virt_ptr client) { auto clientBody = internal::fsClientGetBody(client); if (!clientBody) { return nullptr; } auto lastDequeued = clientBody->lastDequeuedCommand; if (!lastDequeued) { return nullptr; } return lastDequeued->cmdBlock; } /** * Get the emulated error for a client. * * \return * Emulated error code or a positive value on error. */ FSAStatus FSGetEmulatedError(virt_ptr client) { auto clientBody = internal::fsClientGetBody(client); if (!clientBody) { return static_cast(1); } return clientBody->emulatedError; } /** * Set an emulated error for a client. * * All subsequent commands will fail with this error until it is cleared with * FSSetEmulatedError(FSAStatus::OK). * * \retval FSStatus::OK * Returned on success. * * \retval FSStatus::FatalError * Returned on failure, returned if invalid client or error code. */ FSStatus FSSetEmulatedError(virt_ptr client, FSAStatus error) { auto clientBody = internal::fsClientGetBody(client); if (!clientBody) { return FSStatus::FatalError; } if (error >= FSAStatus::OK) { return FSStatus::FatalError; } if (error == FSAStatus::MediaNotReady) { return FSStatus::FatalError; } clientBody->emulatedError = error; return FSStatus::OK; } /** * Get last error for a cmd as an error code for the ErrEula error viewer. */ int32_t FSGetErrorCodeForViewer(virt_ptr client, virt_ptr block) { auto blockBody = internal::fsCmdBlockGetBody(block); auto clientBody = internal::fsClientGetBody(client); if (!blockBody) { if (clientBody->fsm.clientVolumeState == FSVolumeState::Fatal) { return static_cast(FSStatus::FatalError); } else { return static_cast(FSStatus::OK); } } if (blockBody->iosError >= IOSError::OK) { return static_cast(FSStatus::OK); } else { // TODO: Translate error block->unk0x9f4 for FSGetErrorCodeForViewer return blockBody->iosError; } } /** * Get last error as an error code for the ErrEula error viewer. */ int32_t FSGetLastErrorCodeForViewer(virt_ptr client) { return FSGetErrorCodeForViewer(client, FSGetCurrentCmdBlock(client)); } /** * Get the error code for the last executed command. */ FSAStatus FSGetLastError(virt_ptr client) { auto clientBody = internal::fsClientGetBody(client); if (!clientBody) { return static_cast(FSStatus::FatalError); } return clientBody->lastError; } /** * Get the volume state for a client. */ FSVolumeState FSGetVolumeState(virt_ptr client) { auto clientBody = internal::fsClientGetBody(client); if (!clientBody) { return FSVolumeState::Invalid; } return clientBody->fsm.clientVolumeState; } namespace internal { /** * Get an aligned FSClientBody from an FSClient. */ virt_ptr fsClientGetBody(virt_ptr client) { auto body = virt_cast(align_up(client, 0x40)); body->client = client; return body; } /** * Handle a fatal error. * * Will transition the volume state to fatal. */ void fsClientHandleFatalError(virt_ptr clientBody, FSAStatus error) { clientBody->lastError = error; clientBody->isLastErrorWithoutVolume = FALSE; fsmEnterState(virt_addrof(clientBody->fsm), FSVolumeState::Fatal, clientBody); } /** * Handle error returned from a fsaShimpPrepare* call. * * \return * FSStatus error code. */ FSStatus fsClientHandleShimPrepareError(virt_ptr clientBody, FSAStatus error) { fsClientHandleFatalError(clientBody, error); return fsaDecodeFsaStatusToFsStatus(error); } /** * Handle async result for a synchronous FS call. * * May block and wait for result to be received. * * \return * Returns postive value on success, FSStatus error code otherwise. */ FSStatus fsClientHandleAsyncResult(virt_ptr client, virt_ptr block, FSStatus result, FSErrorFlag errorMask) { auto errorFlags = FSErrorFlag::All; if (result >= 0) { auto message = StackObject { }; auto blockBody = fsCmdBlockGetBody(block); OSReceiveMessage(virt_addrof(blockBody->syncQueue), message, OSMessageFlags::Blocking); auto fsMessage = virt_cast(message); if (fsMessage->type != OSFunctionType::FsCmdAsync) { decaf_abort(fmt::format("Unsupported function type {}", fsMessage->type)); } return FSGetAsyncResult(message)->status; } switch (result) { case FSStatus::Cancelled: case FSStatus::End: errorFlags = FSErrorFlag::None; break; case FSStatus::Max: errorFlags = FSErrorFlag::Max; break; case FSStatus::AlreadyOpen: errorFlags = FSErrorFlag::AlreadyOpen; break; case FSStatus::Exists: errorFlags = FSErrorFlag::Exists; break; case FSStatus::NotFound: errorFlags = FSErrorFlag::NotFound; break; case FSStatus::NotFile: errorFlags = FSErrorFlag::NotFile; break; case FSStatus::NotDirectory: errorFlags = FSErrorFlag::NotDir; break; case FSStatus::AccessError: errorFlags = FSErrorFlag::AccessError; break; case FSStatus::PermissionError: errorFlags = FSErrorFlag::PermissionError; break; case FSStatus::FileTooBig: errorFlags = FSErrorFlag::FileTooBig; break; case FSStatus::StorageFull: errorFlags = FSErrorFlag::StorageFull; break; case FSStatus::JournalFull: errorFlags = FSErrorFlag::JournalFull; break; case FSStatus::UnsupportedCmd: errorFlags = FSErrorFlag::UnsupportedCmd; break; } if (errorFlags != FSErrorFlag::None && (errorFlags & errorMask) == 0) { auto clientBody = fsClientGetBody(client); fsClientHandleFatalError(clientBody, clientBody->lastError); return FSStatus::FatalError; } return result; } /** * Submit an FSCmdBlockBody to the client's FSCmdQueue. */ void fsClientSubmitCommand(virt_ptr clientBody, virt_ptr blockBody, FSFinishCmdFn finishCmdFn) { auto queue = virt_addrof(clientBody->cmdQueue); blockBody->finishCmdFn = finishCmdFn; blockBody->status = FSCmdBlockStatus::QueuedCommand; // Enqueue command OSFastMutex_Lock(virt_addrof(queue->mutex)); fsCmdQueueEnqueue(queue, blockBody, false); OSFastMutex_Unlock(virt_addrof(queue->mutex)); // Process command queue fsCmdQueueProcessCmd(queue); } /** * Handle the async IOS FSA IPC callback. */ static void fsClientHandleFsaAsyncCallback(IOSError error, virt_ptrcontext) { auto blockBody = virt_cast(context); auto clientBody = blockBody->clientBody; blockBody->iosError = error; blockBody->status = FSCmdBlockStatus::Completed; blockBody->fsaStatus = FSAShimDecodeIosErrorToFsaStatus(clientBody->clientHandle, error); if (fsInitialised() && !fsDriverDone()) { clientBody->fsCmdHandlerMsg.data = blockBody; clientBody->fsCmdHandlerMsg.type = OSFunctionType::FsCmdHandler; OSSendMessage(OSGetDefaultAppIOQueue(), virt_cast(virt_addrof(clientBody->fsCmdHandlerMsg)), OSMessageFlags::None); } } /** * Handle a FS command which has been dequeued from FSCmdQueue. * * Submits the IOS FSA IPC request for the FS command. * * \retval TRUE * Success. * * \retval FALSE * Unexpected error occurred. */ static BOOL fsClientHandleDequeuedCommand(virt_ptr blockBody) { auto clientBody = blockBody->clientBody; FSAStatus error; do { /* if (blockBody->cmdData.mount.unk0x00 < 2) { if (blockBody->fsaShimBuffer.command == FSACommand::Mount) { // TODO: __handleDequeuedCmd FSACommand::Mount } else if (blockBody->fsaShimBuffer.command == FSACommand::Unmount) { // TODO: __handleDequeuedCmd FSACommand::Unmount } } */ if (!fsInitialised() || fsDriverDone()) { error = FSAStatus::NotInit; } else { error = fsaShimSubmitRequestAsync(virt_addrof(blockBody->fsaShimBuffer), clientBody->emulatedError, sHandleFsaAsyncCallback, blockBody); } // TODO: more if cmd == mount shit if (error == FSAStatus::OK) { return TRUE; } else if (error == FSAStatus::NotInit) { gLog->error("Could not issue command {} due to uninitialised filesystem", blockBody->fsaShimBuffer.command); return TRUE; } } while (error == FSAStatus::Busy); gLog->error("Unexpected error {} whilst handling dequeued command {}", error, blockBody->fsaShimBuffer.command); fsmEnterState(virt_addrof(clientBody->fsm), FSVolumeState::Fatal, clientBody); return FALSE; } } // namespace internal void Library::registerFsClientSymbols() { RegisterFunctionExport(FSAddClient); RegisterFunctionExport(FSAddClientEx); RegisterFunctionExport(FSDelClient); RegisterFunctionExport(FSGetCurrentCmdBlock); RegisterFunctionExport(FSGetEmulatedError); RegisterFunctionExport(FSSetEmulatedError); RegisterFunctionExport(FSGetErrorCodeForViewer); RegisterFunctionExport(FSGetLastErrorCodeForViewer); RegisterFunctionExport(FSGetLastError); RegisterFunctionExport(FSGetVolumeState); RegisterFunctionInternal(internal::fsClientHandleDequeuedCommand, sHandleDequeuedCommand); RegisterFunctionInternal(internal::fsClientHandleFsaAsyncCallback, sHandleFsaAsyncCallback); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_client.h ================================================ #pragma once #include "coreinit_alarm.h" #include "coreinit_enum.h" #include "coreinit_ios.h" #include "coreinit_fastmutex.h" #include "coreinit_fs.h" #include "coreinit_fs_cmdblock.h" #include "coreinit_fs_cmdqueue.h" #include "coreinit_fs_statemachine.h" #include namespace cafe::coreinit { /** * \ingroup coreinit_fs * @{ */ struct FSClient; struct FSClientBody; struct FSClientBodyLink; struct FSCmdBlockBody; /** * Attach parameters passed to FSAddClientEx. */ struct FSAttachParams { be2_virt_ptr userCallback; be2_virt_ptr userContext; }; CHECK_OFFSET(FSAttachParams, 0x00, userCallback); CHECK_OFFSET(FSAttachParams, 0x04, userContext); CHECK_SIZE(FSAttachParams, 0x8); /** * Client container, use internal::getClientBody to get the actual data. */ struct FSClient { be2_array data; }; CHECK_SIZE(FSClient, 0x1700); /** * Link entry used for FSClientBodyQueue. */ struct FSClientBodyLink { be2_virt_ptr next; be2_virt_ptr prev; }; CHECK_OFFSET(FSClientBodyLink, 0x00, next); CHECK_OFFSET(FSClientBodyLink, 0x04, prev); CHECK_SIZE(FSClientBodyLink, 0x8); /** * The actual data of an FSClient. */ struct FSClientBody { UNKNOWN(0x1444); //! IOSHandle returned from fsaShimOpen. be2_val clientHandle; //! State machine. be2_struct fsm; //! Command queue of FS commands. be2_struct cmdQueue; //! The last dequeued command. be2_virt_ptr lastDequeuedCommand; //! Emulated error, set with FSSetEmulatedError. be2_val emulatedError; be2_val unk0x14CC; be2_val unk0x14D0; UNKNOWN(0x1560 - 0x14D4); //! Mutex used to protect FSClientBody data. be2_struct mutex; UNKNOWN(4); //! Alarm used by fsm for unknown reasons. be2_struct fsmAlarm; //! Error of last FS command. be2_val lastError; be2_val isLastErrorWithoutVolume; //! Message used to send FsCmdHandler message when FSA async callback is received. be2_struct fsCmdHandlerMsg; //! Device name of the last mount source returned by FSGetMountSourceNext. be2_array lastMountSourceDevice; //! Mount source type to find with FSGetMountSourceNext. be2_val findMountSourceType; //! Link used for linked list of clients. be2_struct link; //! Pointer to unaligned FSClient structure. be2_virt_ptr client; }; CHECK_OFFSET(FSClientBody, 0x1444, clientHandle); CHECK_OFFSET(FSClientBody, 0x1448, fsm); CHECK_OFFSET(FSClientBody, 0x1480, cmdQueue); CHECK_OFFSET(FSClientBody, 0x14C4, lastDequeuedCommand); CHECK_OFFSET(FSClientBody, 0x14C8, emulatedError); CHECK_OFFSET(FSClientBody, 0x14CC, unk0x14CC); CHECK_OFFSET(FSClientBody, 0x14D0, unk0x14D0); CHECK_OFFSET(FSClientBody, 0x1560, mutex); CHECK_OFFSET(FSClientBody, 0x1590, fsmAlarm); CHECK_OFFSET(FSClientBody, 0x15E8, lastError); CHECK_OFFSET(FSClientBody, 0x15EC, isLastErrorWithoutVolume); CHECK_OFFSET(FSClientBody, 0x15F0, fsCmdHandlerMsg); CHECK_OFFSET(FSClientBody, 0x1600, lastMountSourceDevice); CHECK_OFFSET(FSClientBody, 0x1610, findMountSourceType); CHECK_OFFSET(FSClientBody, 0x1614, link); CHECK_OFFSET(FSClientBody, 0x161C, client); FSStatus FSAddClientEx(virt_ptr client, virt_ptr attachParams, FSErrorFlag errorMask); FSStatus FSAddClient(virt_ptr client, FSErrorFlag errorMask); FSStatus FSDelClient(virt_ptr client, FSErrorFlag errorMask); virt_ptr FSGetCurrentCmdBlock(virt_ptr client); FSAStatus FSGetEmulatedError(virt_ptr client); FSStatus FSSetEmulatedError(virt_ptr client, FSAStatus error); int32_t FSGetErrorCodeForViewer(virt_ptr client, virt_ptr block); int32_t FSGetLastErrorCodeForViewer(virt_ptr client); FSAStatus FSGetLastError(virt_ptr client); FSVolumeState FSGetVolumeState(virt_ptr client); namespace internal { virt_ptr fsClientGetBody(virt_ptr client); void fsClientHandleFatalError(virt_ptr clientBody, FSAStatus error); FSStatus fsClientHandleShimPrepareError(virt_ptr clientBody, FSAStatus error); FSStatus fsClientHandleAsyncResult(virt_ptr client, virt_ptr block, FSStatus result, FSErrorFlag errorMask); void fsClientSubmitCommand(virt_ptr clientBody, virt_ptr blockBody, FSFinishCmdFn finishCmdFn); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmd.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_fs_client.h" #include "coreinit_fs_cmd.h" #include "coreinit_fs_cmdblock.h" #include "coreinit_fsa_shim.h" #include "cafe/cafe_stackobject.h" #include #include namespace cafe::coreinit { namespace internal { static FSStatus readFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask, virt_ptr asyncData); static FSStatus writeFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask, virt_ptr asyncData); static FSStatus getInfoByQueryAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSAQueryInfoType type, virt_ptr out, FSErrorFlag errorMask, virt_ptr asyncData); } // namespace internal /** * Allocate space at end of file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSAppendFile(virt_ptr client, virt_ptr block, uint32_t size, uint32_t count, FSFileHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSAppendFileAsync(client, block, size, count, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Allocate space at end of file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSAppendFileAsync(virt_ptr client, virt_ptr block, uint32_t size, uint32_t count, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestAppendFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle, size, count, 0); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Mount source path to target path. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSBindMount(virt_ptr client, virt_ptr block, virt_ptr sourcePath, virt_ptr targetPath, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSBindMountAsync(client, block, sourcePath, targetPath, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Mount source path to target path. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSBindMountAsync(virt_ptr client, virt_ptr block, virt_ptr sourcePath, virt_ptr targetPath, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask | FSErrorFlag::NotFound, asyncData); if (result != FSStatus::OK) { return result; } if (!sourcePath || !targetPath) { internal::COSError(COSReportModule::Unknown5, "FS: FSBindMount: source or target is null."); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (sourcePath[0] != '/') { internal::COSError( COSReportModule::Unknown5, fmt::format( "FS: FSBindMount: source must be absolute path, specified path is {}", sourcePath)); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } if (targetPath[0] != '/') { internal::COSError( COSReportModule::Unknown5, fmt::format( "FS: FSBindMount: target must be absolute path, specified path is {}", targetPath)); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } if (strncmp(sourcePath.get(), "/vol/storage_", 13)) { internal::COSError( COSReportModule::Unknown5, fmt::format( "FS: FSBindMount: source must start with \"/vol/storage_\", specified path is {}", sourcePath)); internal::fsClientHandleFatalError(clientBody, FSAStatus::PermissionError); return FSStatus::FatalError; } if (strncmp(targetPath.get(), "/vol/external", 13) == 0 || strncmp(targetPath.get(), "/vol/hfio", 9) == 0) { internal::COSError( COSReportModule::Unknown5, fmt::format("FS: FSBindMount: target must not start with \"/vol/external\" or \"/vol/hfio\", specified path is {}", targetPath)); internal::fsClientHandleFatalError(clientBody, FSAStatus::PermissionError); return FSStatus::FatalError; } blockBody->cmdData.mount.sourceType = FSMountSourceType::Bind; auto error = internal::fsaShimPrepareRequestMount(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, sourcePath, targetPath, 1, nullptr, 0); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Unmount target path. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSBindUnmount(virt_ptr client, virt_ptr block, virt_ptr targetPath, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSBindUnmountAsync(client, block, targetPath, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Unmount target path. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSBindUnmountAsync(virt_ptr client, virt_ptr block, virt_ptr targetPath, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask | FSErrorFlag::NotFound, asyncData); if (result != FSStatus::OK) { return result; } if (!targetPath) { internal::COSError(COSReportModule::Unknown5, "FS: FSBindUnmount: target is null."); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (targetPath[0] != '/') { internal::COSError( COSReportModule::Unknown5, fmt::format("FS: FSBindUnmount: target must be absolute path, specified path is {}", targetPath)); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } if (strncmp(targetPath.get(), "/vol/external", 13) == 0 || strncmp(targetPath.get(), "/vol/hfio", 9) == 0) { internal::COSError( COSReportModule::Unknown5, fmt::format("FS: FSBindUnmount: target must not start with \"/vol/external\" or \"/vol/hfio\", specified path is {}", targetPath)); internal::fsClientHandleFatalError(clientBody, FSAStatus::PermissionError); return FSStatus::FatalError; } blockBody->cmdData.unmount.sourceType = FSMountSourceType::Bind; auto error = internal::fsaShimPrepareRequestUnmount(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, targetPath, 0x80000000); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Change the client's working directory. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSChangeDir(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSChangeDirAsync(client, block, path, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Change the client's working directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSChangeDirAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!path) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestChangeDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Change file mode. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSChangeMode(virt_ptr client, virt_ptr block, virt_ptr path, uint32_t mode1, uint32_t mode2, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSChangeModeAsync(client, block, path, mode1, mode2, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Change file mode. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSChangeModeAsync(virt_ptr client, virt_ptr block, virt_ptr path, uint32_t mode1, uint32_t mode2, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!path) { internal::COSError(COSReportModule::Unknown5, "FS: FSChangeMode: path is null."); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestChangeMode( virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path, mode1, mode2); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Close a directory. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSCloseDir(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSCloseDirAsync(client, block, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Close a directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSCloseDirAsync(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestCloseDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Close a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSCloseFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSCloseFileAsync(client, block, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Close a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSCloseFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestCloseFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Flush the contents of a file to disk. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSFlushFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSFlushFileAsync(client, block, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Flush the contents of a file to disk (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSFlushFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestFlushFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * I don't know what this does :). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSFlushQuota(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSFlushQuotaAsync(client, block, path, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * I don't know what this does :) (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSFlushQuotaAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!path) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestFlushQuota(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Get the current working directory. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetCwd(virt_ptr client, virt_ptr block, virt_ptr returnedPath, uint32_t bytes, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetCwdAsync(client, block, returnedPath, bytes, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get the current working directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetCwdAsync(virt_ptr client, virt_ptr block, virt_ptr returnedPath, uint32_t bytes, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!returnedPath) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (bytes < FSMaxPathLength - 1) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam); return FSStatus::FatalError; } blockBody->cmdData.getCwd.returnedPath = returnedPath; blockBody->cmdData.getCwd.bytes = bytes; auto error = internal::fsaShimPrepareRequestGetCwd(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Get directory size. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * Directory not found. */ FSStatus FSGetDirSize(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outDirSize, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetDirSizeAsync(client, block, path, outDirSize, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get directory size. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetDirSizeAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outDirSize, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::getInfoByQueryAsync(client, block, path, FSAQueryInfoType::DirSize, outDirSize, errorMask, asyncData); } /** * Get free space for entry at path. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * Entry not found. */ FSStatus FSGetFreeSpaceSize(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outFreeSize, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetFreeSpaceSizeAsync(client, block, path, outFreeSize, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get free space for entry at path. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetFreeSpaceSizeAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outFreeSize, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::getInfoByQueryAsync(client, block, path, FSAQueryInfoType::FreeSpaceSize, outFreeSize, errorMask, asyncData); } /** * Get the first mount source which matches the specified type. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetMountSource(virt_ptr client, virt_ptr block, FSMountSourceType type, virt_ptr source, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetMountSourceAsync(client, block, type, source, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get the first mount source which matches the specified type (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetMountSourceAsync(virt_ptr client, virt_ptr block, FSMountSourceType type, virt_ptr source, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); if (!clientBody) { return FSStatus::FatalError; } if (type != FSMountSourceType::SdCard && type != FSMountSourceType::HostFileIO) { return FSStatus::FatalError; } clientBody->lastMountSourceDevice[0] = char { 0 }; clientBody->findMountSourceType = type; return FSGetMountSourceNextAsync(client, block, source, errorMask, asyncData); } /** * Get the next mount source. * * This can be called repeatedly after FSGetMountSource until failure. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::End * Returned when we have iterated over all the mount sources for this type. */ FSStatus FSGetMountSourceNext(virt_ptr client, virt_ptr block, virt_ptr source, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetMountSourceNextAsync(client, block, source, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get the next mount source (asynchronously). * * This can be called repeatedly after FSGetMountSource until failure. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetMountSourceNextAsync(virt_ptr client, virt_ptr block, virt_ptr source, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!source) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } blockBody->cmdData.getMountSourceNext.source = source; blockBody->cmdData.getMountSourceNext.dirHandle = -1; auto error = internal::fsaShimPrepareRequestOpenDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, make_stack_string("/dev")); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishGetMountSourceNextOpenCmd); return FSStatus::OK; } /** * Get the current read / write position of a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetPosFile(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outPos, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetPosFileAsync(client, block, handle, outPos, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get the current read / write position of a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetPosFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outPos, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!outPos) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } blockBody->cmdData.getPosFile.pos = outPos; auto error = internal::fsaShimPrepareRequestGetPosFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Get statistics about a filesystem entry. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * Entry not found. */ FSStatus FSGetStat(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outStat, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetStatAsync(client, block, path, outStat, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get statistics about a filesystem entry (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetStatAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outStat, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::getInfoByQueryAsync(client, block, path, FSAQueryInfoType::Stat, outStat, errorMask, asyncData); } /** * Get statistics about an opened file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetStatFile(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outStat, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSGetStatFileAsync(client, block, handle, outStat, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Get statistics about an opened file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSGetStatFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outStat, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } blockBody->cmdData.statFile.stat = outStat; auto error = internal::fsaShimPrepareRequestStatFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Checks if current file position is at the end of the file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::OK * Returns FSStatus::OK when not at the end of the file. * * \retval FSStatus::End * Returns FSStatus::End when at the end of the file. */ FSStatus FSIsEof(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSIsEofAsync(client, block, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Checks if current file position is at the end of the file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSIsEofAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestIsEof(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Create a directory. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSMakeDir(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSMakeDirAsync(client, block, path, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Create a directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSMakeDirAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!path) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestMakeDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path, 0x660); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Mount a mount source. * * The mounted path is returned in target. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSMount(virt_ptr client, virt_ptr block, virt_ptr source, virt_ptr target, uint32_t bytes, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSMountAsync(client, block, source, target, bytes, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Mount a mount source (asynchronously). * * The mounted path is returned in target. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSMountAsync(virt_ptr client, virt_ptr block, virt_ptr source, virt_ptr target, uint32_t bytes, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, static_cast(errorMask | FSErrorFlag::Exists), asyncData); if (result != FSStatus::OK) { return result; } if (!source || !target || bytes < FSMaxMountPathLength) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (source->sourceType != FSMountSourceType::SdCard && source->sourceType != FSMountSourceType::HostFileIO) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam); return FSStatus::FatalError; } // Set target path as /vol/ std::memcpy(target.get(), "/vol/", 5); string_copy(target.get() + 5, virt_addrof(source->path).get(), bytes - 6); blockBody->cmdData.mount.sourceType = source->sourceType; auto error = internal::fsaShimPrepareRequestMount(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, virt_addrof(source->path), target, 0, nullptr, 0); // Correct the device path. auto devicePath = virt_addrof(blockBody->fsaShimBuffer.request.mount.path); auto sourcePath = virt_addrof(source->path); if (strncmp(sourcePath.get(), "external", 8) == 0) { // external01 to /dev/sdcard01 std::memcpy(devicePath.get(), "/dev/sdcard", 11); string_copy(devicePath.get() + 11, sourcePath.get() + 8, FSMaxPathLength - 11); } else { // to /dev/ std::memcpy(devicePath.get(), "/dev/", 5); string_copy(devicePath.get() + 5, sourcePath.get(), FSMaxPathLength - 5); } if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishMountCmd); return FSStatus::OK; } /** * Open a directory for iterating it's content. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * Directory not found. * * \retval FSStatus::NotDirectory * Used OpenDir on a non-directory object such as a file. * * \retval FSStatus::PermissionError * Did not have permission to open the directory in specified mode. */ FSStatus FSOpenDir(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outHandle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSOpenDirAsync(client, block, path, outHandle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Open a directory for iterating it's content (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSOpenDirAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outHandle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!outHandle) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (!path) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } blockBody->cmdData.openDir.handle = outHandle; auto error = internal::fsaShimPrepareRequestOpenDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Open a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * File not found. * * \retval FSStatus::NotFile * Used OpenFile on a non-file object such as a directory. * * \retval FSStatus::PermissionError * Did not have permission to open file in specified mode. */ FSStatus FSOpenFile(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, virt_ptr outHandle, FSErrorFlag errorMask) { return FSOpenFileEx(client, block, path, mode, 0x660, 0, 0, outHandle, errorMask); } /** * Open a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSOpenFileAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, virt_ptr outHandle, FSErrorFlag errorMask, virt_ptr asyncData) { return FSOpenFileExAsync(client, block, path, mode, 0x660, 0, 0, outHandle, errorMask, asyncData); } /** * Open a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * File not found. * * \retval FSStatus::NotFile * Used OpenFile on a non-file object such as a directory. * * \retval FSStatus::PermissionError * Did not have permission to open file in specified mode. */ FSStatus FSOpenFileEx(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, uint32_t unk1, uint32_t unk2, uint32_t unk3, virt_ptr outHandle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSOpenFileExAsync(client, block, path, mode, unk1, unk2, unk3, outHandle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Open a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSOpenFileExAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, uint32_t unk1, uint32_t unk2, uint32_t unk3, virt_ptr outHandle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!outHandle) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (!path) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } if (!mode) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam); return FSStatus::FatalError; } blockBody->cmdData.openFile.handle = outHandle; auto error = internal::fsaShimPrepareRequestOpenFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path, mode, unk1, unk2, unk3); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Read the next entry in a directory. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSReadDir(virt_ptr client, virt_ptr block, FSDirHandle handle, virt_ptr outDirEntry, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSReadDirAsync(client, block, handle, outDirEntry, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Read the next entry in a directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSReadDirAsync(virt_ptr client, virt_ptr block, FSDirHandle handle, virt_ptr outDirEntry, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!outDirEntry) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } blockBody->cmdData.readDir.entry = outDirEntry; auto error = internal::fsaShimPrepareRequestReadDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Read a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSReadFile(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSReadFileAsync(client, block, buffer, size, count, handle, readFlags, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Read a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSReadFileAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::readFileWithPosAsync(client, block, buffer, size, count, 0, handle, static_cast(readFlags & ~FSReadFlag::ReadWithPos), errorMask, asyncData); } /** * Read a file at a specific position. * * The files position will be set before reading. * * This is equivalent to: * FSSetPosFile(file, pos) * FSReadFile(file, ...) * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSReadFileWithPos(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSReadFileWithPosAsync(client, block, buffer, size, count, pos, handle, readFlags, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Read a file at a specific position (asynchronously). * * The files position will be set before reading. * * This is equivalent to: * FSSetPosFile(file, pos) * FSReadFile(file, ...) * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSReadFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::readFileWithPosAsync(client, block, buffer, size, count, pos, handle, static_cast(readFlags | FSReadFlag::ReadWithPos), errorMask, asyncData); } /** * Delete a file or directory. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSRemove(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSRemoveAsync(client, block, path, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Delete a file or directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSRemoveAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!path) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestRemove(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Rename a file or directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. * * \retval FSStatus::NotFound * Entry not found. */ FSStatus FSRename(virt_ptr client, virt_ptr block, virt_ptr oldPath, virt_ptr newPath, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSRenameAsync(client, block, oldPath, newPath, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Rename a file or directory (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSRenameAsync(virt_ptr client, virt_ptr block, virt_ptr oldPath, virt_ptr newPath, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!oldPath) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } if (!newPath) { internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestRename(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, oldPath, newPath); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Rewind the read directory iterator back to the beginning. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSRewindDir(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSRewindDirAsync(client, block, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Rewind the read directory iterator back to the beginning (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSRewindDirAsync(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestRewindDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Set the current read / write position for a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSSetPosFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSFilePosition pos, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSSetPosFileAsync(client, block, handle, pos, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Set the current read / write position for a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSSetPosFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSFilePosition pos, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestSetPosFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle, pos); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Truncate a file to it's current position. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSTruncateFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSTruncateFileAsync(client, block, handle, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Truncate a file to it's current position (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSTruncateFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } auto error = internal::fsaShimPrepareRequestTruncateFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, handle); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Unmount a target which was previously mounted with FSMount. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSUnmount(virt_ptr client, virt_ptr block, virt_ptr target, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSUnmountAsync(client, block, target, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Unmount a target which was previously mounted with FSMount (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSUnmountAsync(virt_ptr client, virt_ptr block, virt_ptr target, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto blockBody = internal::fsCmdBlockGetBody(block); auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!target) { internal::COSError(COSReportModule::Unknown5, "FS: FSUnmount: target is null."); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } if (strncmp(target.get(), "/vol/external01", 15) == 0) { blockBody->cmdData.unmount.sourceType = FSMountSourceType::SdCard; } else if (strncmp(target.get(), "/vol/hfio01", 11) == 0) { blockBody->cmdData.unmount.sourceType = FSMountSourceType::HostFileIO; } else { internal::COSError(COSReportModule::Unknown5, "FS: FSUnmount: target specifies invalid mount path."); internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } auto error = internal::fsaShimPrepareRequestUnmount(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, target, 0x80000000); if (error) { return internal::fsClientHandleShimPrepareError(clientBody, error); } internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd); return FSStatus::OK; } /** * Write to a file. * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSWriteFile(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSWriteFileAsync(client, block, buffer, size, count, handle, writeFlags, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Write to a file (asynchronously). * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSWriteFileAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::writeFileWithPosAsync(client, block, buffer, size, count, 0, handle, static_cast(writeFlags & ~FSWriteFlag::WriteWithPos), errorMask, asyncData); } /** * Write to a file at a specific position. * * The files position will be set before writing. * * This is equivalent to: * FSSetPosFile(file, pos) * FSWriteFile(file, ...) * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSWriteFileWithPos(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask) { auto asyncData = StackObject { }; internal::fsCmdBlockPrepareSync(client, block, asyncData); auto result = FSWriteFileWithPosAsync(client, block, buffer, size, count, pos, handle, writeFlags, errorMask, asyncData); return internal::fsClientHandleAsyncResult(client, block, result, errorMask); } /** * Write to a file at a specific position (asynchronously). * * The files position will be set before writing. * * This is equivalent to: * FSSetPosFile(file, pos) * FSWriteFile(file, ...) * * \return * Returns negative FSStatus error code on failure, FSStatus::OK on success. */ FSStatus FSWriteFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask, virt_ptr asyncData) { return internal::writeFileWithPosAsync(client, block, buffer, size, count, pos, handle, static_cast(writeFlags | FSWriteFlag::WriteWithPos), errorMask, asyncData); } namespace internal { FSStatus readFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = fsClientGetBody(client); auto blockBody = fsCmdBlockGetBody(block); auto result = fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } // Ensure size * count is not > 32 bit. auto bytes = uint64_t { size } * uint64_t { count }; if (bytes > 0xFFFFFFFFull) { fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam); return FSStatus::FatalError; } auto bytesRemaining = size * count; blockBody->cmdData.readFile.chunkSize = size; blockBody->cmdData.readFile.bytesRemaining = bytesRemaining; blockBody->cmdData.readFile.bytesRead = 0u; // We only read up to FSMaxBytesPerRequest per request. if (bytesRemaining > FSMaxBytesPerRequest) { blockBody->cmdData.readFile.readSize = FSMaxBytesPerRequest; } else { blockBody->cmdData.readFile.readSize = bytesRemaining; } auto error = fsaShimPrepareRequestReadFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, buffer, 1, blockBody->cmdData.readFile.readSize, pos, handle, readFlags); if (error) { return fsClientHandleShimPrepareError(clientBody, error); } fsClientSubmitCommand(clientBody, blockBody, FinishReadCmd); return FSStatus::OK; } static FSStatus getInfoByQueryAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSAQueryInfoType type, virt_ptr out, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = fsClientGetBody(client); auto blockBody = fsCmdBlockGetBody(block); auto result = fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } if (!path) { fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath); return FSStatus::FatalError; } if (!out) { fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer); return FSStatus::FatalError; } blockBody->cmdData.getInfoByQuery.out = out; auto error = fsaShimPrepareRequestGetInfoByQuery(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, path, type); if (error) { return fsClientHandleShimPrepareError(clientBody, error); } fsClientSubmitCommand(clientBody, blockBody, FinishCmd); return FSStatus::OK; } FSStatus writeFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask, virt_ptr asyncData) { auto clientBody = fsClientGetBody(client); auto blockBody = fsCmdBlockGetBody(block); auto result = fsCmdBlockPrepareAsync(clientBody, blockBody, errorMask, asyncData); if (result != FSStatus::OK) { return result; } // Ensure size * count is not > 32 bit. auto bytes = uint64_t { size } * uint64_t { count }; if (bytes > 0xFFFFFFFFull) { fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam); return FSStatus::FatalError; } auto bytesRemaining = size * count; blockBody->cmdData.writeFile.chunkSize = size; blockBody->cmdData.writeFile.bytesRemaining = bytesRemaining; blockBody->cmdData.writeFile.bytesWritten = 0u; // We only read up to FSMaxBytesPerRequest per request. if (bytesRemaining > FSMaxBytesPerRequest) { blockBody->cmdData.writeFile.writeSize = FSMaxBytesPerRequest; } else { blockBody->cmdData.writeFile.writeSize = bytesRemaining; } auto error = fsaShimPrepareRequestWriteFile(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, buffer, 1, blockBody->cmdData.writeFile.writeSize, pos, handle, writeFlags); if (error) { return fsClientHandleShimPrepareError(clientBody, error); } fsClientSubmitCommand(clientBody, blockBody, FinishWriteCmd); return FSStatus::OK; } } // namespace internal void Library::registerFsCmdSymbols() { RegisterFunctionExport(FSAppendFile); RegisterFunctionExport(FSAppendFileAsync); RegisterFunctionExport(FSBindMount); RegisterFunctionExport(FSBindMountAsync); RegisterFunctionExport(FSBindUnmount); RegisterFunctionExport(FSBindUnmountAsync); RegisterFunctionExport(FSChangeDir); RegisterFunctionExport(FSChangeDirAsync); RegisterFunctionExport(FSChangeMode); RegisterFunctionExport(FSChangeModeAsync); RegisterFunctionExport(FSCloseDir); RegisterFunctionExport(FSCloseDirAsync); RegisterFunctionExport(FSCloseFile); RegisterFunctionExport(FSCloseFileAsync); RegisterFunctionExport(FSFlushFile); RegisterFunctionExport(FSFlushFileAsync); RegisterFunctionExport(FSFlushQuota); RegisterFunctionExport(FSFlushQuotaAsync); RegisterFunctionExport(FSGetCwd); RegisterFunctionExport(FSGetCwdAsync); RegisterFunctionExport(FSGetDirSize); RegisterFunctionExport(FSGetDirSizeAsync); RegisterFunctionExport(FSGetFreeSpaceSize); RegisterFunctionExport(FSGetFreeSpaceSizeAsync); RegisterFunctionExport(FSGetPosFile); RegisterFunctionExport(FSGetPosFileAsync); RegisterFunctionExport(FSGetStat); RegisterFunctionExport(FSGetStatAsync); RegisterFunctionExport(FSGetStatFile); RegisterFunctionExport(FSGetStatFileAsync); RegisterFunctionExport(FSGetMountSource); RegisterFunctionExport(FSGetMountSourceAsync); RegisterFunctionExport(FSGetMountSourceNext); RegisterFunctionExport(FSGetMountSourceNextAsync); RegisterFunctionExport(FSIsEof); RegisterFunctionExport(FSIsEofAsync); RegisterFunctionExport(FSMakeDir); RegisterFunctionExport(FSMakeDirAsync); RegisterFunctionExport(FSMount); RegisterFunctionExport(FSMountAsync); RegisterFunctionExport(FSOpenDir); RegisterFunctionExport(FSOpenDirAsync); RegisterFunctionExport(FSOpenFile); RegisterFunctionExport(FSOpenFileAsync); RegisterFunctionExport(FSOpenFileEx); RegisterFunctionExport(FSOpenFileExAsync); RegisterFunctionExport(FSReadDir); RegisterFunctionExport(FSReadDirAsync); RegisterFunctionExport(FSReadFile); RegisterFunctionExport(FSReadFileAsync); RegisterFunctionExport(FSReadFileWithPos); RegisterFunctionExport(FSReadFileWithPosAsync); RegisterFunctionExport(FSRemove); RegisterFunctionExport(FSRemoveAsync); RegisterFunctionExport(FSRename); RegisterFunctionExport(FSRenameAsync); RegisterFunctionExport(FSRewindDir); RegisterFunctionExport(FSRewindDirAsync); RegisterFunctionExport(FSSetPosFile); RegisterFunctionExport(FSSetPosFileAsync); RegisterFunctionExport(FSTruncateFile); RegisterFunctionExport(FSTruncateFileAsync); RegisterFunctionExport(FSUnmount); RegisterFunctionExport(FSUnmountAsync); RegisterFunctionExport(FSWriteFile); RegisterFunctionExport(FSWriteFileAsync); RegisterFunctionExport(FSWriteFileWithPos); RegisterFunctionExport(FSWriteFileWithPosAsync); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmd.h ================================================ #pragma once #include "coreinit_fs.h" #include "coreinit_fsa.h" namespace cafe::coreinit { FSStatus FSAppendFile(virt_ptr client, virt_ptr block, uint32_t size, uint32_t count, FSFileHandle handle, FSErrorFlag errorMask); FSStatus FSAppendFileAsync(virt_ptr client, virt_ptr block, uint32_t size, uint32_t count, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSBindMount(virt_ptr client, virt_ptr block, virt_ptr sourcePath, virt_ptr targetPath, FSErrorFlag errorMask); FSStatus FSBindMountAsync(virt_ptr client, virt_ptr block, virt_ptr sourcePath, virt_ptr targetPath, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSBindUnmount(virt_ptr client, virt_ptr block, virt_ptr targetPath, FSErrorFlag errorMask); FSStatus FSBindUnmountAsync(virt_ptr client, virt_ptr block, virt_ptr targetPath, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSChangeDir(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask); FSStatus FSChangeDirAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSChangeMode(virt_ptr client, virt_ptr block, virt_ptr path, uint32_t mode1, uint32_t mode2, FSErrorFlag errorMask); FSStatus FSChangeModeAsync(virt_ptr client, virt_ptr block, virt_ptr path, uint32_t mode1, uint32_t mode2, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSCloseDir(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask); FSStatus FSCloseDirAsync(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSCloseFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask); FSStatus FSCloseFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSFlushFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask); FSStatus FSFlushFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSFlushQuota(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask); FSStatus FSFlushQuotaAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetCwd(virt_ptr client, virt_ptr block, virt_ptr returnedPath, uint32_t bytes, FSErrorFlag errorMask); FSStatus FSGetCwdAsync(virt_ptr client, virt_ptr block, virt_ptr returnedPath, uint32_t bytes, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetDirSize(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr returnedDirSize, FSErrorFlag errorMask); FSStatus FSGetDirSizeAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr returnedDirSize, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetFreeSpaceSize(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr returnedFreeSize, FSErrorFlag errorMask); FSStatus FSGetFreeSpaceSizeAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr returnedFreeSize, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetMountSource(virt_ptr client, virt_ptr block, FSMountSourceType type, virt_ptr source, FSErrorFlag errorMask); FSStatus FSGetMountSourceAsync(virt_ptr client, virt_ptr block, FSMountSourceType type, virt_ptr outSource, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetMountSourceNext(virt_ptr client, virt_ptr block, virt_ptr outSource, FSErrorFlag errorMask); FSStatus FSGetMountSourceNextAsync(virt_ptr client, virt_ptr block, virt_ptr outSource, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetPosFile(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outPos, FSErrorFlag errorMask); FSStatus FSGetPosFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outPos, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetStat(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outStat, FSErrorFlag errorMask); FSStatus FSGetStatAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outStat, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSGetStatFile(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outStat, FSErrorFlag errorMask); FSStatus FSGetStatFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, virt_ptr outStat, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSIsEof(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask); FSStatus FSIsEofAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSMakeDir(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask); FSStatus FSMakeDirAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSMount(virt_ptr client, virt_ptr block, virt_ptr source, virt_ptr target, uint32_t bytes, FSErrorFlag errorMask); FSStatus FSMountAsync(virt_ptr client, virt_ptr block, virt_ptr source, virt_ptr target, uint32_t bytes, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSOpenDir(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outHandle, FSErrorFlag errorMask); FSStatus FSOpenDirAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr outHandle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSOpenFile(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, virt_ptr outHandle, FSErrorFlag errorMask); FSStatus FSOpenFileAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, virt_ptr outHandle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSOpenFileEx(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, uint32_t unk1, uint32_t unk2, uint32_t unk3, virt_ptr outHandle, FSErrorFlag errorMask); FSStatus FSOpenFileExAsync(virt_ptr client, virt_ptr block, virt_ptr path, virt_ptr mode, uint32_t unk1, uint32_t unk2, uint32_t unk3, virt_ptr outHandle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSReadDir(virt_ptr client, virt_ptr block, FSDirHandle handle, virt_ptr outDirEntry, FSErrorFlag errorMask); FSStatus FSReadDirAsync(virt_ptr client, virt_ptr block, FSDirHandle handle, virt_ptr outDirEntry, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSReadFile(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask); FSStatus FSReadFileAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSReadFileWithPos(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask); FSStatus FSReadFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSReadFlag readFlags, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSRemove(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask); FSStatus FSRemoveAsync(virt_ptr client, virt_ptr block, virt_ptr path, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSRename(virt_ptr client, virt_ptr block, virt_ptr oldPath, virt_ptr newPath, FSErrorFlag errorMask); FSStatus FSRenameAsync(virt_ptr client, virt_ptr block, virt_ptr oldPath, virt_ptr newPath, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSRewindDir(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask); FSStatus FSRewindDirAsync(virt_ptr client, virt_ptr block, FSDirHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSSetPosFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSFilePosition pos, FSErrorFlag errorMask); FSStatus FSSetPosFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSFilePosition pos, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSTruncateFile(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask); FSStatus FSTruncateFileAsync(virt_ptr client, virt_ptr block, FSFileHandle handle, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSUnmount(virt_ptr client, virt_ptr block, virt_ptr target, FSErrorFlag errorMask); FSStatus FSUnmountAsync(virt_ptr client, virt_ptr block, virt_ptr target, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSWriteFile(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask); FSStatus FSWriteFileAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask, virt_ptr asyncData); FSStatus FSWriteFileWithPos(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask); FSStatus FSWriteFileWithPosAsync(virt_ptr client, virt_ptr block, virt_ptr buffer, uint32_t size, uint32_t count, FSFilePosition pos, FSFileHandle handle, FSWriteFlag writeFlags, FSErrorFlag errorMask, virt_ptr asyncData); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdblock.cpp ================================================ #include "coreinit.h" #include "coreinit_appio.h" #include "coreinit_fs.h" #include "coreinit_fs_client.h" #include "coreinit_fs_driver.h" #include "coreinit_fs_cmdblock.h" #include "coreinit_fsa_shim.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include #include #include #include #include #include #include namespace cafe::coreinit { namespace internal { FSFinishCmdFn FinishCmd = nullptr; FSFinishCmdFn FinishMountCmd = nullptr; FSFinishCmdFn FinishReadCmd = nullptr; FSFinishCmdFn FinishWriteCmd = nullptr; FSFinishCmdFn FinishGetMountSourceNextOpenCmd = nullptr; FSFinishCmdFn FinishGetMountSourceNextReadCmd = nullptr; FSFinishCmdFn FinishGetMountSourceNextCloseCmd = nullptr; } /** * Initialise an FSCmdBlock structure. */ void FSInitCmdBlock(virt_ptr block) { if (!block) { return; } std::memset(block.get(), 0, sizeof(FSCmdBlock)); auto blockBody = internal::fsCmdBlockGetBody(block); blockBody->status = FSCmdBlockStatus::Initialised; blockBody->priority = FSDefaultPriority; } /** * Get the command's priority. * * \return * Returns positive value on success, or FSStatus error code otherwise. */ FSStatus FSGetCmdPriority(virt_ptr block) { auto blockBody = internal::fsCmdBlockGetBody(block); if (!blockBody) { return FSStatus::FatalError; } return static_cast(blockBody->priority.value()); } /** * Set the command's priority. * * \retval FSStatus::OK * Success. * * \retval FSStatus::FatalError * FSCmdBlock in invalid state, or invalid priority. */ FSStatus FSSetCmdPriority(virt_ptr block, uint32_t priority) { auto blockBody = internal::fsCmdBlockGetBody(block); if (!blockBody) { return FSStatus::FatalError; } if (priority < FSMinPriority || priority > FSMaxPriority) { return FSStatus::FatalError; } if (blockBody->status != FSCmdBlockStatus::Initialised && blockBody->status != FSCmdBlockStatus::Cancelled) { // Cannot adjust a commands priority once it has been queued. return FSStatus::FatalError; } blockBody->priority = static_cast(priority); return FSStatus::OK; } /** * Get the FSMessage structure in an FSCmdBlock. * * \return * Returns the FSMessage structure from the FSCmdBlock's FSAsyncResult. */ virt_ptr FSGetFSMessage(virt_ptr block) { auto blockBody = internal::fsCmdBlockGetBody(block); if (!blockBody) { return nullptr; } return virt_addrof(blockBody->asyncResult.ioMsg); } /** * Get the value stored in FSCmdBlock by FSSetUserData. * * \return * Returns pointer set by FSSetUserData. */ virt_ptr FSGetUserData(virt_ptr block) { auto blockBody = internal::fsCmdBlockGetBody(block); return blockBody->userData; } /** * Store a user value in FSCmdBlock which can be retrieved by FSGetUserData. */ void FSSetUserData(virt_ptr block, virt_ptr userData) { auto blockBody = internal::fsCmdBlockGetBody(block); blockBody->userData = userData; } namespace internal { /** * Get an aligned FSCmdBlockBody from an FSCmdBlock. */ virt_ptr fsCmdBlockGetBody(virt_ptr cmdBlock) { if (!cmdBlock) { return nullptr; } auto body = virt_cast(align_up(virt_cast(cmdBlock), 0x40)); body->cmdBlock = cmdBlock; return body; } /** * Prepare a FSCmdBlock for an asynchronous operation. * * \return * Returns a positive value on success, FSStatus error code otherwise. */ FSStatus fsCmdBlockPrepareAsync(virt_ptr clientBody, virt_ptr blockBody, FSErrorFlag errorMask, virt_ptr asyncData) { decaf_check(clientBody); decaf_check(blockBody); if (!internal::fsInitialised()) { return FSStatus::FatalError; } if (blockBody->status != FSCmdBlockStatus::Initialised && blockBody->status != FSCmdBlockStatus::Cancelled) { gLog->error("Invalid FSCmdBlockData state {}", blockBody->status.value()); return FSStatus::FatalError; } if (asyncData->userCallback && asyncData->ioMsgQueue) { gLog->error("userCallback and ioMsgQueue are exclusive."); return FSStatus::FatalError; } blockBody->errorMask = errorMask; blockBody->clientBody = clientBody; return fsAsyncResultInit(clientBody, virt_addrof(blockBody->asyncResult), asyncData); } /** * Prepare a FSCmdBlock for a synchronous operation. */ void fsCmdBlockPrepareSync(virt_ptr client, virt_ptr block, virt_ptr asyncData) { auto blockBody = internal::fsCmdBlockGetBody(block); OSInitMessageQueue(virt_addrof(blockBody->syncQueue), virt_addrof(blockBody->syncQueueMsgs), 1); asyncData->ioMsgQueue = virt_addrof(blockBody->syncQueue); } /** * Requeues an FS command. */ void fsCmdBlockRequeue(virt_ptr queue, virt_ptr blockBody, BOOL insertAtFront, FSFinishCmdFn finishCmdFn) { OSFastMutex_Lock(virt_addrof(queue->mutex)); if (blockBody->cancelFlags & FSCmdCancelFlags::Cancelling) { blockBody->cancelFlags &= ~FSCmdCancelFlags::Cancelling; blockBody->status = FSCmdBlockStatus::Cancelled; blockBody->clientBody->lastDequeuedCommand = nullptr; OSFastMutex_Unlock(virt_addrof(queue->mutex)); fsCmdBlockReplyResult(blockBody, FSStatus::Cancelled); return; } blockBody->finishCmdFn = finishCmdFn; blockBody->status = FSCmdBlockStatus::QueuedCommand; fsCmdQueueFinishCmd(queue); if (insertAtFront) { fsCmdQueuePushFront(queue, blockBody); } else { fsCmdQueueEnqueue(queue, blockBody, true); } OSFastMutex_Unlock(virt_addrof(queue->mutex)); fsCmdQueueProcessCmd(queue); } /** * Set the result for an FSCmd. * * A message will be sent to the user's ioMsgQueue if one was provided or to * the AppIO queue where the user's callback will be called instead. */ void fsCmdBlockSetResult(virt_ptr blockBody, FSStatus status) { blockBody->asyncResult.block = blockBody->cmdBlock; blockBody->asyncResult.status = status; if (!OSSendMessage(blockBody->asyncResult.asyncData.ioMsgQueue, virt_cast(virt_addrof(blockBody->asyncResult.ioMsg)), OSMessageFlags::None)) { decaf_abort("fsCmdBlockReplyResult: Could not send async result message"); } } /** * Calls the blockBody->finishCmdFn with the result of the command. */ void fsCmdBlockReplyResult(virt_ptr blockBody, FSStatus status) { if (!blockBody) { return; } // Finish the current command auto queue = virt_addrof(blockBody->clientBody->cmdQueue); OSFastMutex_Lock(virt_addrof(queue->mutex)); fsCmdQueueFinishCmd(queue); OSFastMutex_Unlock(virt_addrof(queue->mutex)); if (blockBody->finishCmdFn) { cafe::invoke(cpu::this_core::state(), blockBody->finishCmdFn, blockBody, status); } // Start off next command fsCmdQueueProcessCmd(queue); } /** * Called from the AppIO thread to handle the result of an FS command. */ void fsCmdBlockHandleResult(virt_ptr blockBody) { auto clientBody = blockBody->clientBody; auto result = static_cast(blockBody->fsaStatus.value()); if (!fsClientRegistered(clientBody)) { if (blockBody->finishCmdFn) { cafe::invoke(cpu::this_core::state(), blockBody->finishCmdFn, blockBody, FSStatus::Cancelled); } return; } clientBody->lastError = blockBody->fsaStatus; if (blockBody->fsaStatus == FSAStatus::MediaNotReady) { fsmSetState(virt_addrof(clientBody->fsm), FSVolumeState::WrongMedia, clientBody); return; } else if (blockBody->fsaStatus == FSAStatus::WriteProtected) { fsmSetState(virt_addrof(clientBody->fsm), FSVolumeState::MediaError, clientBody); return; } if (blockBody->fsaStatus < FSAStatus::OK) { auto errorFlags = FSErrorFlag::All; switch (blockBody->fsaStatus) { case FSAStatus::Busy: fsCmdBlockRequeue(virt_addrof(clientBody->cmdQueue), blockBody, TRUE, blockBody->finishCmdFn); return; case FSAStatus::Cancelled: result = FSStatus::Cancelled; errorFlags = FSErrorFlag::None; break; case FSAStatus::EndOfDir: case FSAStatus::EndOfFile: result = FSStatus::End; errorFlags = FSErrorFlag::None; break; case FSAStatus::MaxMountpoints: case FSAStatus::MaxVolumes: case FSAStatus::MaxClients: case FSAStatus::MaxFiles: case FSAStatus::MaxDirs: errorFlags = FSErrorFlag::Max; result = FSStatus::Max; break; case FSAStatus::AlreadyOpen: errorFlags = FSErrorFlag::AlreadyOpen; result = FSStatus::AlreadyOpen; break; case FSAStatus::NotFound: errorFlags = FSErrorFlag::NotFound; result = FSStatus::NotFound; break; case FSAStatus::AlreadyExists: case FSAStatus::NotEmpty: errorFlags = FSErrorFlag::Exists; result = FSStatus::Exists; break; case FSAStatus::AccessError: errorFlags = FSErrorFlag::AccessError; result = FSStatus::AccessError; break; case FSAStatus::PermissionError: errorFlags = FSErrorFlag::PermissionError; result = FSStatus::PermissionError; break; case FSAStatus::DataCorrupted: // TODO: FSAStatus::DataCorrupted decaf_abort("TODO: Reverse me."); break; case FSAStatus::StorageFull: errorFlags = FSErrorFlag::StorageFull; result = FSStatus::StorageFull; break; case FSAStatus::JournalFull: errorFlags = FSErrorFlag::JournalFull; result = FSStatus::JournalFull; break; case FSAStatus::UnsupportedCmd: errorFlags = FSErrorFlag::UnsupportedCmd; result = FSStatus::UnsupportedCmd; break; case FSAStatus::NotFile: errorFlags = FSErrorFlag::NotFile; result = FSStatus::NotFile; break; case FSAStatus::NotDir: errorFlags = FSErrorFlag::NotDir; result = FSStatus::NotDirectory; break; case FSAStatus::FileTooBig: errorFlags = FSErrorFlag::FileTooBig; result = FSStatus::FileTooBig; break; case FSAStatus::MediaError: // TODO: FSAStatus::MediaError decaf_abort("TODO: Reverse me."); break; default: errorFlags = FSErrorFlag::All; } if (errorFlags != FSErrorFlag::None && (blockBody->errorMask & errorFlags) == 0) { fsmEnterState(virt_addrof(clientBody->fsm), FSVolumeState::Fatal, clientBody); // The game told us not to return if we receive this error, so must mean // that we really fucked something up :). decaf_abort(fmt::format("Unrecoverable FS error, command = {}, error = {}.", blockBody->fsaShimBuffer.command, blockBody->fsaStatus)); return; } } if (clientBody->lastDequeuedCommand == blockBody) { clientBody->lastDequeuedCommand = nullptr; } fsCmdBlockReplyResult(blockBody, result); } /** * Copies the IOS command results to FS output. * * Set as blockBlody->finishCmdFn. * Called from fsCmdBlockReplyResult. */ void fsCmdBlockFinishCmd(virt_ptr blockBody, FSStatus result) { auto clientBody = blockBody->clientBody; OSFastMutex_Lock(virt_addrof(clientBody->mutex)); blockBody->cancelFlags &= ~FSCmdCancelFlags::Cancelling; if (clientBody->lastDequeuedCommand == blockBody) { clientBody->lastDequeuedCommand = nullptr; } blockBody->status = FSCmdBlockStatus::Cancelled; OSFastMutex_Unlock(virt_addrof(clientBody->mutex)); if (result < 0) { fsCmdBlockSetResult(blockBody, result); return; } blockBody->unk0x9EA = uint8_t { 0 }; blockBody->unk0x9F4 = 0u; auto &shim = blockBody->fsaShimBuffer; switch (shim.command) { case FSACommand::Mount: case FSACommand::Unmount: case FSACommand::ChangeDir: case FSACommand::MakeDir: case FSACommand::Remove: case FSACommand::Rename: case FSACommand::RewindDir: case FSACommand::CloseDir: case FSACommand::ReadFile: case FSACommand::WriteFile: case FSACommand::SetPosFile: case FSACommand::IsEof: case FSACommand::CloseFile: case FSACommand::GetError: case FSACommand::FlushFile: case FSACommand::AppendFile: case FSACommand::TruncateFile: case FSACommand::MakeQuota: case FSACommand::FlushQuota: case FSACommand::RollbackQuota: case FSACommand::ChangeMode: case FSACommand::RegisterFlushQuota: case FSACommand::FlushMultiQuota: case FSACommand::RemoveQuota: case FSACommand::MakeLink: { fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::GetVolumeInfo: { auto info = blockBody->cmdData.getVolumeInfo.info; *info = shim.response.getVolumeInfo.volumeInfo; info->unk0x0C = 0u; info->unk0x10 = 0u; info->unk0x14 = -1; info->unk0x18 = -1; fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::OpenDir: { auto handle = blockBody->cmdData.openDir.handle; *handle = shim.response.openDir.handle; fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::ReadDir: { auto entry = blockBody->cmdData.readDir.entry; *entry = shim.response.readDir.entry; fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::OpenFile: case FSACommand::OpenFileByStat: { *blockBody->cmdData.openFile.handle = shim.response.openFile.handle; fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::GetPosFile: { auto pos = blockBody->cmdData.getPosFile.pos; *pos = shim.response.getPosFile.pos; fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::StatFile: { auto stat = blockBody->cmdData.statFile.stat; *stat = shim.response.statFile.stat; fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::GetFileBlockAddress: { auto address = blockBody->cmdData.getFileBlockAddress.address; if (address) { *address = shim.response.getFileBlockAddress.address; } fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::GetCwd: { auto bytes = blockBody->cmdData.getCwd.bytes; auto returnedPath = blockBody->cmdData.getCwd.returnedPath; if (bytes) { auto path = virt_addrof(shim.response.getCwd.path); auto len = static_cast(std::strlen(path.get())); decaf_check(len < bytes); string_copy(returnedPath.get(), path.get(), bytes); std::memset(returnedPath.get() + len, 0, bytes - len); } fsCmdBlockSetResult(blockBody, result); break; } case FSACommand::GetInfoByQuery: { switch (shim.request.getInfoByQuery.type) { case FSAQueryInfoType::FreeSpaceSize: { auto freeSpaceSize = blockBody->cmdData.getInfoByQuery.freeSpaceSize; *freeSpaceSize = shim.response.getInfoByQuery.freeSpaceSize; break; } case FSAQueryInfoType::DirSize: { auto dirSize = blockBody->cmdData.getInfoByQuery.dirSize; *dirSize = shim.response.getInfoByQuery.dirSize; break; } case FSAQueryInfoType::EntryNum: { auto entryNum = blockBody->cmdData.getInfoByQuery.entryNum; *entryNum = shim.response.getInfoByQuery.entryNum; break; } case FSAQueryInfoType::FileSystemInfo: { auto fileSystemInfo = blockBody->cmdData.getInfoByQuery.fileSystemInfo; *fileSystemInfo = shim.response.getInfoByQuery.fileSystemInfo; break; } case FSAQueryInfoType::Stat: { auto stat = blockBody->cmdData.getInfoByQuery.stat; *stat = shim.response.getInfoByQuery.stat; break; } default: decaf_abort(fmt::format("Unexpected QueryInfoType: {}", shim.request.getInfoByQuery.type)); } fsCmdBlockSetResult(blockBody, result); break; } default: decaf_abort(fmt::format("Invalid FSA command {}", shim.command)); } } /** * Finish a FSACommand::Mount command. */ void fsCmdBlockFinishMountCmd(virt_ptr blockBody, FSStatus result) { if (result != FSStatus::Exists) { fsCmdBlockFinishCmd(blockBody, result); } else { fsCmdBlockFinishCmd(blockBody, FSStatus::OK); } } /** * Finish a FSACommand::ReadFile command. * * Files are read in chunk of up to FSMaxBytesPerRequest bytes per time, this * finish function will keep requeuing the command until we have completed * the full read. */ void fsCmdBlockFinishReadCmd(virt_ptr blockBody, FSStatus result) { auto bytesRead = static_cast(result); if (result < 0) { return fsCmdBlockFinishCmd(blockBody, result); } // Update read state auto &readState = blockBody->cmdData.readFile; readState.bytesRead += bytesRead; readState.bytesRemaining -= bytesRead; // Check if the read is complete if (readState.bytesRemaining == 0 || bytesRead < readState.readSize) { auto chunksRead = readState.bytesRead / readState.chunkSize; return fsCmdBlockFinishCmd(blockBody, static_cast(chunksRead)); } // Check if we can read the final chunk yet if (readState.bytesRemaining > FSMaxBytesPerRequest) { readState.readSize = FSMaxBytesPerRequest; } else { readState.readSize = readState.bytesRemaining; } // Queue a new read request auto &readRequest = blockBody->fsaShimBuffer.request.readFile; readRequest.buffer = readRequest.buffer + bytesRead; readRequest.size = 1u; readRequest.count = readState.readSize; if (readRequest.readFlags & FSReadFlag::ReadWithPos) { readRequest.pos += bytesRead; } auto &shim = blockBody->fsaShimBuffer; shim.ioctlvVec[0].vaddr = virt_cast(virt_addrof(shim.request)); shim.ioctlvVec[1].vaddr = virt_cast(readRequest.buffer); shim.ioctlvVec[1].len = readRequest.size; shim.ioctlvVec[2].vaddr = virt_cast(virt_addrof(shim.response)); fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue), blockBody, FALSE, internal::FinishReadCmd); } /** * Finish a FSACommand::WriteFile command. * * Files are written in chunks of up to FSMaxBytesPerRequest bytes per time, * this finish function will keep requeuing the command until we have completed * the full write. */ void fsCmdBlockFinishWriteCmd(virt_ptr blockBody, FSStatus result) { auto bytesWritten = static_cast(result); if (result < 0) { return fsCmdBlockFinishCmd(blockBody, result); } // Update write state auto &writeState = blockBody->cmdData.writeFile; writeState.bytesWritten += bytesWritten; writeState.bytesRemaining -= bytesWritten; // Check if the write is complete if (writeState.bytesRemaining == 0 || bytesWritten < writeState.writeSize) { auto chunksWritten = writeState.bytesWritten / writeState.chunkSize; return fsCmdBlockFinishCmd(blockBody, static_cast(chunksWritten)); } // Check if we can write the final chunk yet if (writeState.bytesRemaining > FSMaxBytesPerRequest) { writeState.writeSize = FSMaxBytesPerRequest; } else { writeState.writeSize = writeState.bytesRemaining; } // Queue a new write request auto &writeRequest = blockBody->fsaShimBuffer.request.writeFile; writeRequest.buffer = writeRequest.buffer + bytesWritten; writeRequest.size = 1u; writeRequest.count = writeState.writeSize; if (writeRequest.writeFlags & FSWriteFlag::WriteWithPos) { writeRequest.pos += bytesWritten; } auto &shim = blockBody->fsaShimBuffer; shim.ioctlvVec[0].vaddr = virt_cast(virt_addrof(shim.request)); shim.ioctlvVec[1].vaddr = virt_cast(writeRequest.buffer); shim.ioctlvVec[1].len = writeRequest.size; shim.ioctlvVec[2].vaddr = virt_cast(virt_addrof(shim.response)); fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue), blockBody, FALSE, internal::FinishWriteCmd); } void fsCmdBlockFinishGetMountSourceNextOpenCmd(virt_ptr blockBody, FSStatus result) { auto clientBody = blockBody->clientBody; if (result != FSStatus::OK) { return fsCmdBlockFinishCmd(blockBody, result); } auto &cmdData = blockBody->cmdData.getMountSourceNext; if (cmdData.dirHandle == -1) { auto response = virt_addrof(blockBody->fsaShimBuffer.response.openDir); cmdData.dirHandle = response->handle; } fsaShimPrepareRequestReadDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, cmdData.dirHandle); fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue), blockBody, TRUE, internal::FinishGetMountSourceNextReadCmd); } void fsCmdBlockFinishGetMountSourceNextReadCmd(virt_ptr blockBody, FSStatus result) { auto clientBody = blockBody->clientBody; auto &cmdData = blockBody->cmdData.getMountSourceNext; cmdData.readError = result; if (result == FSStatus::OK) { auto mountSourceType = FSMountSourceType::Bind; auto deviceName = virt_addrof(blockBody->fsaShimBuffer.response.readDir.entry.name); // Check the mount source type if (std::strncmp(deviceName.get(), "sdcard", 6) == 0) { mountSourceType = FSMountSourceType::SdCard; } else if (std::strncmp(deviceName.get(), "hfio", 4) == 0) { mountSourceType = FSMountSourceType::HostFileIO; } if (mountSourceType != clientBody->findMountSourceType) { return fsCmdBlockFinishGetMountSourceNextOpenCmd(blockBody, FSStatus::OK); } /* Note that this strncmp will rely on the fact that open / read dir * returns results in a consistent alphabetical order. If that is not the * case then this comparison becomes unreliable. * * FIXME: Unfortunately that is indeed not the case with our filesystem, * but fuck it because we will ?never? have more than 1 sdcard and 1 hfio. */ if (std::strncmp(deviceName.get(), virt_addrof(clientBody->lastMountSourceDevice).get(), 0x10) <= 0) { // Already returned this device, get the next one! return fsCmdBlockFinishGetMountSourceNextOpenCmd(blockBody, FSStatus::OK); } // Write to FSMountSource output auto mountSource = cmdData.source; mountSource->sourceType = mountSourceType; if (mountSourceType == FSMountSourceType::SdCard) { // Map sdcardXX -> externalXX string_copy(virt_addrof(mountSource->path).get(), mountSource->path.size(), "external", 8); string_copy(virt_addrof(mountSource->path).get() + 8, mountSource->path.size() - 8, deviceName.get() + 6, 2); mountSource->path[10] = char { 0 }; } else if (mountSourceType == FSMountSourceType::HostFileIO) { string_copy(virt_addrof(mountSource->path).get(), deviceName.get(), mountSource->path.size()); } } fsaShimPrepareRequestCloseDir(virt_addrof(blockBody->fsaShimBuffer), clientBody->clientHandle, cmdData.dirHandle); fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue), blockBody, TRUE, internal::FinishGetMountSourceNextCloseCmd); } void fsCmdBlockFinishGetMountSourceNextCloseCmd(virt_ptr blockBody, FSStatus result) { fsCmdBlockFinishCmd(blockBody, blockBody->cmdData.getMountSourceNext.readError); } } // namespace internal void Library::registerFsCmdBlockSymbols() { RegisterFunctionExport(FSInitCmdBlock); RegisterFunctionExport(FSGetCmdPriority); RegisterFunctionExport(FSSetCmdPriority); RegisterFunctionExport(FSGetFSMessage); RegisterFunctionExport(FSGetUserData); RegisterFunctionExport(FSSetUserData); RegisterFunctionInternal(internal::fsCmdBlockFinishCmd, internal::FinishCmd); RegisterFunctionInternal(internal::fsCmdBlockFinishMountCmd, internal::FinishMountCmd); RegisterFunctionInternal(internal::fsCmdBlockFinishReadCmd, internal::FinishReadCmd); RegisterFunctionInternal(internal::fsCmdBlockFinishWriteCmd, internal::FinishWriteCmd); RegisterFunctionInternal(internal::fsCmdBlockFinishGetMountSourceNextOpenCmd, internal::FinishGetMountSourceNextOpenCmd); RegisterFunctionInternal(internal::fsCmdBlockFinishGetMountSourceNextReadCmd, internal::FinishGetMountSourceNextReadCmd); RegisterFunctionInternal(internal::fsCmdBlockFinishGetMountSourceNextCloseCmd, internal::FinishGetMountSourceNextCloseCmd); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdblock.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_fs.h" #include "coreinit_fsa.h" #include "coreinit_fsa_shim.h" #include "coreinit_messagequeue.h" #include namespace cafe::coreinit { /** * \ingroup coreinit_fs * @{ */ #pragma pack(push, 1) struct FSClientBody; struct FSCmdBlock; struct FSCmdBlockBody; struct FSCmdQueue; using FSFinishCmdFn = virt_func_ptr, FSStatus)>; struct FSCmdBlock { be2_array data; }; CHECK_SIZE(FSCmdBlock, 0xA80); struct FSCmdBlockBodyLink { be2_virt_ptr next; be2_virt_ptr prev; }; CHECK_OFFSET(FSCmdBlockBodyLink, 0x00, next); CHECK_OFFSET(FSCmdBlockBodyLink, 0x04, prev); CHECK_SIZE(FSCmdBlockBodyLink, 0x8); struct FSCmdBlockCmdDataGetCwd { be2_virt_ptr returnedPath; be2_val bytes; }; CHECK_OFFSET(FSCmdBlockCmdDataGetCwd, 0x0, returnedPath); CHECK_OFFSET(FSCmdBlockCmdDataGetCwd, 0x4, bytes); CHECK_SIZE(FSCmdBlockCmdDataGetCwd, 0x8); struct FSCmdBlockCmdDataGetFileBlockAddress { be2_virt_ptr address; }; CHECK_OFFSET(FSCmdBlockCmdDataGetFileBlockAddress, 0x0, address); CHECK_SIZE(FSCmdBlockCmdDataGetFileBlockAddress, 0x4); struct FSCmdBlockCmdDataGetInfoByQuery { union { be2_virt_ptr out; be2_virt_ptr dirSize; be2_virt_ptr entryNum; be2_virt_ptr fileSystemInfo; be2_virt_ptr freeSpaceSize; be2_virt_ptr stat; }; }; CHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, out); CHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, dirSize); CHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, entryNum); CHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, fileSystemInfo); CHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, freeSpaceSize); CHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, stat); CHECK_SIZE(FSCmdBlockCmdDataGetInfoByQuery, 0x4); struct FSCmdBlockCmdDataGetMountSourceNext { be2_virt_ptr source; be2_val dirHandle; be2_val readError; }; CHECK_OFFSET(FSCmdBlockCmdDataGetMountSourceNext, 0x0, source); CHECK_OFFSET(FSCmdBlockCmdDataGetMountSourceNext, 0x4, dirHandle); CHECK_OFFSET(FSCmdBlockCmdDataGetMountSourceNext, 0x8, readError); CHECK_SIZE(FSCmdBlockCmdDataGetMountSourceNext, 0xC); struct FSCmdBlockCmdDataGetPosFile { be2_virt_ptr pos; }; CHECK_OFFSET(FSCmdBlockCmdDataGetPosFile, 0x0, pos); CHECK_SIZE(FSCmdBlockCmdDataGetPosFile, 0x4); struct FSCmdBlockCmdDataGetVolumeInfo { be2_virt_ptr info; }; CHECK_OFFSET(FSCmdBlockCmdDataGetVolumeInfo, 0x0, info); CHECK_SIZE(FSCmdBlockCmdDataGetVolumeInfo, 0x4); struct FSCmdBlockCmdDataMount { UNKNOWN(0x4); be2_val sourceType; }; CHECK_OFFSET(FSCmdBlockCmdDataMount, 0x4, sourceType); CHECK_SIZE(FSCmdBlockCmdDataMount, 0x8); struct FSCmdBlockCmdDataOpenDir { be2_virt_ptr handle; }; CHECK_OFFSET(FSCmdBlockCmdDataOpenDir, 0x0, handle); CHECK_SIZE(FSCmdBlockCmdDataOpenDir, 0x4); struct FSCmdBlockCmdDataOpenFile { be2_virt_ptr handle; }; CHECK_OFFSET(FSCmdBlockCmdDataOpenFile, 0x0, handle); CHECK_SIZE(FSCmdBlockCmdDataOpenFile, 0x4); struct FSCmdBlockCmdDataReadDir { be2_virt_ptr entry; }; CHECK_OFFSET(FSCmdBlockCmdDataReadDir, 0x0, entry); CHECK_SIZE(FSCmdBlockCmdDataReadDir, 0x4); struct FSCmdBlockCmdDataReadFile { UNKNOWN(4); //! Total number of bytes remaining to read. be2_val bytesRemaining; //! Total bytes read so far. be2_val bytesRead; //! The size of each read chunk (size parameter on FSReadFile). be2_val chunkSize; //! The amount of bytes to read per IPC request. be2_val readSize; }; CHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0x4, bytesRemaining); CHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0x8, bytesRead); CHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0xC, chunkSize); CHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0x10, readSize); CHECK_SIZE(FSCmdBlockCmdDataReadFile, 0x14); struct FSCmdBlockCmdDataStatFile { be2_virt_ptr stat; }; CHECK_OFFSET(FSCmdBlockCmdDataStatFile, 0x0, stat); CHECK_SIZE(FSCmdBlockCmdDataStatFile, 0x4); struct FSCmdBlockCmdDataUnmount { UNKNOWN(0x4); be2_val sourceType; }; CHECK_OFFSET(FSCmdBlockCmdDataUnmount, 0x4, sourceType); CHECK_SIZE(FSCmdBlockCmdDataUnmount, 0x8); struct FSCmdBlockCmdDataWriteFile { UNKNOWN(4); //! Total number of bytes remaining to write. be2_val bytesRemaining; //! Total bytes written so far. be2_val bytesWritten; //! The size of each write chunk (size parameter on FSWriteFile). be2_val chunkSize; //! The amount of bytes to write per IPC request. be2_val writeSize; }; CHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0x4, bytesRemaining); CHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0x8, bytesWritten); CHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0xC, chunkSize); CHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0x10, writeSize); CHECK_SIZE(FSCmdBlockCmdDataWriteFile, 0x14); /** * Stores command specific data for an FSCmdBlockBody. */ struct FSCmdBlockCmdData { union { be2_struct getCwd; be2_struct getFileBlockAddress; be2_struct getInfoByQuery; be2_struct getMountSourceNext; be2_struct getPosFile; be2_struct getVolumeInfo; be2_struct mount; be2_struct openDir; be2_struct openFile; be2_struct readDir; be2_struct readFile; be2_struct statFile; be2_struct unmount; be2_struct writeFile; UNKNOWN(0x14); }; }; CHECK_SIZE(FSCmdBlockCmdData, 0x14); struct FSCmdBlockBody { //! FSA shim buffer used for FSA IPC communication. be2_struct fsaShimBuffer; //! Pointer to client which owns this command. be2_virt_ptr clientBody; //! State of command. be2_val status; //! Cancel state of command. be2_val cancelFlags; //! Command specific data. be2_struct cmdData; //! Link used for FSCmdQueue. be2_struct link; //! FSAStatus for the current command. be2_val fsaStatus; //! IOSError for the current command. be2_val iosError; //! Mask used to not return certain errors. be2_val errorMask; //! FSAsyncResult object used for this command. be2_struct asyncResult; //! User data accessed with FS{Get,Set}UserData. be2_virt_ptr userData; //! Queue used for synchronous FS commands to wait for finish. be2_struct syncQueue; //! Message used for syncQueue. be2_array syncQueueMsgs; //! Callback to call when command is finished. be2_val finishCmdFn; //! Priority of command, from 0 highest to 32 lowest, 16 is default. be2_val priority; be2_val unk0x9E9; be2_val unk0x9EA; UNKNOWN(0x9); be2_val unk0x9F4; //! Pointer to unaligned FSCmdBlock. be2_virt_ptr cmdBlock; }; CHECK_OFFSET(FSCmdBlockBody, 0x0, fsaShimBuffer); CHECK_OFFSET(FSCmdBlockBody, 0x938, clientBody); CHECK_OFFSET(FSCmdBlockBody, 0x93C, status); CHECK_OFFSET(FSCmdBlockBody, 0x940, cancelFlags); CHECK_OFFSET(FSCmdBlockBody, 0x944, cmdData); CHECK_OFFSET(FSCmdBlockBody, 0x958, link); CHECK_OFFSET(FSCmdBlockBody, 0x960, fsaStatus); CHECK_OFFSET(FSCmdBlockBody, 0x964, iosError); CHECK_OFFSET(FSCmdBlockBody, 0x968, errorMask); CHECK_OFFSET(FSCmdBlockBody, 0x96C, asyncResult); CHECK_OFFSET(FSCmdBlockBody, 0x994, userData); CHECK_OFFSET(FSCmdBlockBody, 0x998, syncQueue); CHECK_OFFSET(FSCmdBlockBody, 0x9D4, syncQueueMsgs); CHECK_OFFSET(FSCmdBlockBody, 0x9E4, finishCmdFn); CHECK_OFFSET(FSCmdBlockBody, 0x9E8, priority); CHECK_OFFSET(FSCmdBlockBody, 0x9E9, unk0x9E9); CHECK_OFFSET(FSCmdBlockBody, 0x9EA, unk0x9EA); CHECK_OFFSET(FSCmdBlockBody, 0x9F4, unk0x9F4); CHECK_OFFSET(FSCmdBlockBody, 0x9F8, cmdBlock); #pragma pack(pop) void FSInitCmdBlock(virt_ptr block); FSStatus FSGetCmdPriority(virt_ptr block); FSStatus FSSetCmdPriority(virt_ptr block, uint32_t priority); virt_ptr FSGetFSMessage(virt_ptr block); virt_ptr FSGetUserData(virt_ptr block); void FSSetUserData(virt_ptr block, virt_ptr userData); namespace internal { extern FSFinishCmdFn FinishCmd; extern FSFinishCmdFn FinishMountCmd; extern FSFinishCmdFn FinishReadCmd; extern FSFinishCmdFn FinishWriteCmd; extern FSFinishCmdFn FinishGetMountSourceNextOpenCmd; extern FSFinishCmdFn FinishGetMountSourceNextReadCmd; extern FSFinishCmdFn FinishGetMountSourceNextCloseCmd; virt_ptr fsCmdBlockGetBody(virt_ptr cmdBlock); FSStatus fsCmdBlockPrepareAsync(virt_ptr clientBody, virt_ptr blockBody, FSErrorFlag errorMask, virt_ptr asyncData); void fsCmdBlockPrepareSync(virt_ptr client, virt_ptr block, virt_ptr asyncData); void fsCmdBlockRequeue(virt_ptr queue, virt_ptr blockBody, BOOL insertAtFront, FSFinishCmdFn finishCmdFn); void fsCmdBlockSetResult(virt_ptr blockBody, FSStatus status); void fsCmdBlockReplyResult(virt_ptr blockBody, FSStatus status); void fsCmdBlockHandleResult(virt_ptr blockBody); void fsCmdBlockFinishCmd(virt_ptr blockBody, FSStatus status); void fsCmdBlockFinishReadCmd(virt_ptr blockBody, FSStatus status); void fsCmdBlockFinishWriteCmd(virt_ptr blockBody, FSStatus status); void fsCmdBlockFinishMountCmd(virt_ptr blockBody, FSStatus result); void fsCmdBlockFinishGetMountSourceNextOpenCmd(virt_ptr blockBody, FSStatus status); void fsCmdBlockFinishGetMountSourceNextReadCmd(virt_ptr blockBody, FSStatus result); void fsCmdBlockFinishGetMountSourceNextCloseCmd(virt_ptr blockBody, FSStatus result); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdqueue.cpp ================================================ #include "coreinit_fastmutex.h" #include "coreinit_fs_client.h" #include "coreinit_fs_cmdblock.h" #include "coreinit_fs_cmdqueue.h" #include "coreinit_internal_queue.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include namespace cafe::coreinit { namespace internal { struct FSCmdSortFuncLT { bool operator ()(virt_ptr lhs, virt_ptr rhs) const { return lhs->priority < rhs->priority; } }; struct FSCmdSortFuncLE { bool operator ()(virt_ptr lhs, virt_ptr rhs) const { return lhs->priority <= rhs->priority; } }; using CmdQueueLT = internal::SortedQueue; using CmdQueueLE = internal::SortedQueue; /** * Initialise an FSCmdQueue structure. */ bool fsCmdQueueCreate(virt_ptr queue, FSCmdQueueHandlerFn dequeueCmdHandler, uint32_t maxActiveCmds) { if (!queue || !dequeueCmdHandler) { return false; } queue->head = nullptr; queue->tail = nullptr; queue->dequeueCmdHandler = dequeueCmdHandler; queue->activeCmds = 0u; queue->maxActiveCmds = maxActiveCmds; OSFastMutex_Init(virt_addrof(queue->mutex), nullptr); return true; } /** * Destroy a FSCmdQueue structure. */ void fsCmdQueueDestroy(virt_ptr queue) { fsCmdQueueCancelAll(queue); } /** * Cancels all commands in a FSCmdQueue structure. */ void fsCmdQueueCancelAll(virt_ptr queue) { OSFastMutex_Lock(virt_addrof(queue->mutex)); while (auto cmd = fsCmdQueuePopFront(queue)) { cmd->status = FSCmdBlockStatus::Cancelled; } OSFastMutex_Unlock(virt_addrof(queue->mutex)); } /** * Prevent the FSCmdQueue from dequeuing new commands. * * Sets the FSCmdQueueStatus::Suspended flag. */ void fsCmdQueueSuspend(virt_ptr queue) { queue->status |= FSCmdQueueStatus::Suspended; } /** * Allow the FSCmdQueue to dequeuing new commands. * * Clears the FSCmdQueueStatus::Suspended flag. */ void fsCmdQueueResume(virt_ptr queue) { queue->status &= ~FSCmdQueueStatus::Suspended; } /** * Insert a command into the queue, sorted by priority. */ void fsCmdQueueEnqueue(virt_ptr queue, virt_ptr blockBody, bool sortLE) { if (sortLE) { CmdQueueLE::insert(queue, blockBody); } else { CmdQueueLT::insert(queue, blockBody); } } /** * Insert a command at the front of the queue. */ void fsCmdQueuePushFront(virt_ptr queue, virt_ptr blockBody) { blockBody->link.prev = nullptr; blockBody->link.next = queue->head; if (blockBody->link.next) { blockBody->link.next->link.prev = blockBody; } queue->head = blockBody; } /** * Pop a command from the front of the queue. */ virt_ptr fsCmdQueuePopFront(virt_ptr queue) { return CmdQueueLT::popFront(queue); } /** * Begin a command. * * Increases the active command count. * * \retval true * Returns true if a command can begin. * * \retval false * Returns false if there is already the max amount of active commands. */ bool fsCmdQueueBeginCmd(virt_ptr queue) { if (queue->status & FSCmdQueueStatus::MaxActiveCommands || queue->status & FSCmdQueueStatus::Suspended) { return false; } queue->activeCmds += 1; if (queue->activeCmds >= queue->maxActiveCmds) { queue->status |= FSCmdQueueStatus::MaxActiveCommands; } return true; } /** * Finish a command. * * Decreases the active command count. */ void fsCmdQueueFinishCmd(virt_ptr queue) { if (queue->activeCmds) { queue->activeCmds -= 1; } if (queue->activeCmds < queue->maxActiveCmds) { queue->status &= ~FSCmdQueueStatus::MaxActiveCommands; } } /** * Process a command from the queue. * * Pops a command from the front of the queue and calls the dequeued command * handler on it. */ bool fsCmdQueueProcessCmd(virt_ptr queue) { OSFastMutex_Lock(virt_addrof(queue->mutex)); auto activeCmd = fsCmdQueueBeginCmd(queue); OSFastMutex_Unlock(virt_addrof(queue->mutex)); if (!activeCmd) { return false; } OSFastMutex_Lock(virt_addrof(queue->mutex)); auto cmd = fsCmdQueuePopFront(queue); if (cmd) { auto clientBody = cmd->clientBody; clientBody->lastDequeuedCommand = cmd; cmd->status = FSCmdBlockStatus::DeqeuedCommand; OSFastMutex_Unlock(virt_addrof(queue->mutex)); // Call the dequeue command handler if (cafe::invoke(cpu::this_core::state(), queue->dequeueCmdHandler, cmd)) { return true; } OSFastMutex_Lock(virt_addrof(queue->mutex)); } fsCmdQueueFinishCmd(queue); OSFastMutex_Unlock(virt_addrof(queue->mutex)); return true; } } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdqueue.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_fastmutex.h" #include namespace cafe::coreinit { #pragma pack(push, 1) /** * \ingroup coreinit_fs * @{ */ struct FSCmdBlockBody; using FSCmdQueueHandlerFn = virt_func_ptr)>; struct FSCmdQueue { //! Head of the queue. be2_virt_ptr head; //! Tail of the queue. be2_virt_ptr tail; //! Mutex used to protect queue data. be2_struct mutex; //! Function to call when a command is dequeued. be2_val dequeueCmdHandler; //! Number of active commands. be2_val activeCmds; //! Max allowed active commands (should always be 1). be2_val maxActiveCmds; //! Status of the command queue. be2_val status; }; CHECK_OFFSET(FSCmdQueue, 0x0, head); CHECK_OFFSET(FSCmdQueue, 0x4, tail); CHECK_OFFSET(FSCmdQueue, 0x8, mutex); CHECK_OFFSET(FSCmdQueue, 0x34, dequeueCmdHandler); CHECK_OFFSET(FSCmdQueue, 0x38, activeCmds); CHECK_OFFSET(FSCmdQueue, 0x3C, maxActiveCmds); CHECK_OFFSET(FSCmdQueue, 0x40, status); CHECK_SIZE(FSCmdQueue, 0x44); #pragma pack(pop) namespace internal { bool fsCmdQueueCreate(virt_ptr queue, FSCmdQueueHandlerFn dequeueCmdHandler, uint32_t maxActiveCmds); void fsCmdQueueDestroy(virt_ptr queue); void fsCmdQueueCancelAll(virt_ptr queue); void fsCmdQueueSuspend(virt_ptr queue); void fsCmdQueueResume(virt_ptr queue); void fsCmdQueueEnqueue(virt_ptr queue, virt_ptr blockBody, bool sortLE); void fsCmdQueuePushFront(virt_ptr queue, virt_ptr blockBody); virt_ptr fsCmdQueuePopFront(virt_ptr queue); bool fsCmdQueueBeginCmd(virt_ptr queue); void fsCmdQueueFinishCmd(virt_ptr queue); bool fsCmdQueueProcessCmd(virt_ptr queue); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_driver.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_driver.h" namespace cafe::coreinit { struct StaticFsDriverData { be2_struct driverInterface; be2_array name; be2_val isDone; }; virt_ptr sFsDriverData = nullptr; static OSDriver_GetNameFn sFsDriverGetName; static OSDriver_OnInitFn sFsDriverOnInit; static OSDriver_OnAcquiredForegroundFn sFsDriverOnAcquiredForeground; static OSDriver_OnReleasedForegroundFn sFsDriverOnReleasedForeground; static OSDriver_OnDoneFn sFsDriverOnDone; namespace internal { static virt_ptr fsDriverGetName(OSDriver_UserDriverId id) { return virt_addrof(sFsDriverData->name); } void fsDriverOnInit(OSDriver_UserDriverId id) { } void fsDriverOnAcquiredForeground(OSDriver_UserDriverId id) { } void fsDriverOnReleasedForeground(OSDriver_UserDriverId id) { } void fsDriverOnDone(OSDriver_UserDriverId id) { sFsDriverData->isDone = TRUE; } bool fsDriverDone() { return !!sFsDriverData->isDone; } void initialiseFsDriver() { sFsDriverData->name = "FS"; sFsDriverData->isDone = FALSE; sFsDriverData->driverInterface.getName = sFsDriverGetName; sFsDriverData->driverInterface.onInit = sFsDriverOnInit; sFsDriverData->driverInterface.onAcquiredForeground = sFsDriverOnAcquiredForeground; sFsDriverData->driverInterface.onReleasedForeground = sFsDriverOnReleasedForeground; sFsDriverData->driverInterface.onDone = sFsDriverOnDone; auto driverError = OSDriver_Register(static_cast(-1), 40, virt_addrof(sFsDriverData->driverInterface), 0, nullptr, nullptr, nullptr); COSVerbose( COSReportModule::Unknown5, fmt::format("FS: Registered to OSDriver: result {}", driverError)); } } // namespace internal void Library::registerFsDriverSymbols() { RegisterFunctionInternal(internal::fsDriverGetName, sFsDriverGetName); RegisterFunctionInternal(internal::fsDriverOnInit, sFsDriverOnInit); RegisterFunctionInternal(internal::fsDriverOnAcquiredForeground, sFsDriverOnAcquiredForeground); RegisterFunctionInternal(internal::fsDriverOnReleasedForeground, sFsDriverOnReleasedForeground); RegisterFunctionInternal(internal::fsDriverOnDone, sFsDriverOnDone); RegisterDataInternal(sFsDriverData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_driver.h ================================================ #pragma once namespace cafe::coreinit { /** * \ingroup coreinit_fs * @{ */ namespace internal { void initialiseFsDriver(); bool fsDriverDone(); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_statemachine.cpp ================================================ #include "coreinit.h" #include "coreinit_appio.h" #include "coreinit_fs_statemachine.h" #include "coreinit_fs_client.h" #include "coreinit_fs_cmdblock.h" #include #include #include #include namespace cafe::coreinit { static AlarmCallbackFn sFsmAlarmCallback = nullptr; /** * Get an FSStateChangeInfo from an OSMessage. */ virt_ptr FSGetStateChangeInfo(virt_ptr message) { return virt_cast(message->message); } /** * Register a callback or message queue for state change notifications. */ void FSSetStateChangeNotification(virt_ptr client, virt_ptr asyncData) { auto clientBody = internal::fsClientGetBody(client); auto &fsm = clientBody->fsm; if (!asyncData) { fsm.sendStateChangeNotifications = FALSE; } else { if (asyncData->userCallback) { asyncData->ioMsgQueue = OSGetDefaultAppIOQueue(); } fsm.stateChangeInfo.asyncData = *asyncData; fsm.stateChangeInfo.msg.data = virt_addrof(fsm.stateChangeInfo); fsm.stateChangeInfo.msg.type = OSFunctionType::FsStateChangeEvent; fsm.stateChangeInfo.client = clientBody->client; fsm.sendStateChangeNotifications = TRUE; } } namespace internal { static FSVolumeState fsmOnStateChange(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); static FSVolumeState fsmOnStateChangeFromInitial(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); static FSVolumeState fsmOnStateChangeFromReady(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); static FSVolumeState fsmOnStateChangeFromNoMedia(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); static FSVolumeState fsmOnStateChangeFromMediaError(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); static FSVolumeState fsmOnStateChangeFromFatal(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); static void fsmNotifyStateChange(virt_ptr fsm); static void fsmStartAlarm(virt_ptr clientBody); static void fsmAlarmHandler(virt_ptr alarm, virt_ptr context); /** * Initialise the FS State Machine. */ void fsmInit(virt_ptr fsm, virt_ptr clientBody) { OSFastMutex_Lock(virt_addrof(clientBody->mutex)); fsm->state = FSVolumeState::Initial; fsm->unk0x0c = FALSE; internal::fsmEnterState(fsm, FSVolumeState::Ready, clientBody); fsm->clientVolumeState = fsm->state; OSFastMutex_Unlock(virt_addrof(clientBody->mutex)); } /** * Set the FSM state. */ void fsmSetState(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { OSFastMutex_Lock(virt_addrof(clientBody->mutex)); // Set the state auto newState = fsmOnStateChange(fsm, state, clientBody); // Handle the transition fsmEnterState(fsm, newState, clientBody); OSFastMutex_Unlock(virt_addrof(clientBody->mutex)); } /** * Enter an FSM state. */ void fsmEnterState(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { OSFastMutex_Lock(virt_addrof(clientBody->mutex)); while (state != FSVolumeState::Invalid) { fsmOnStateChange(fsm, FSVolumeState::NoMedia, clientBody); fsm->state = state; state = fsmOnStateChange(fsm, FSVolumeState::Ready, clientBody); } if (fsm->unk0x0c) { fsm->unk0x0c = FALSE; if (fsm->state != fsm->clientVolumeState) { fsm->clientVolumeState = fsm->state; gLog->error("Updated volume state of client 0x{:X} to {}", clientBody->client, fsm->clientVolumeState); if (fsm->clientVolumeState == FSVolumeState::Fatal || fsm->clientVolumeState == FSVolumeState::JournalFull) { gLog->error("Shit has become fucked {}", clientBody->lastError); } fsmNotifyStateChange(fsm); } } OSFastMutex_Unlock(virt_addrof(clientBody->mutex)); } /** * Send a state change notification message. */ void fsmNotifyStateChange(virt_ptr fsm) { if (fsm->sendStateChangeNotifications) { fsm->stateChangeInfo.state = fsm->state; OSSendMessage(fsm->stateChangeInfo.asyncData.ioMsgQueue, virt_cast(virt_addrof(fsm->stateChangeInfo.msg)), OSMessageFlags::None); } } /** * Start the FSM alarm. * * I don't really understand it's purpose. */ void fsmStartAlarm(virt_ptr clientBody) { OSCancelAlarm(virt_addrof(clientBody->fsmAlarm)); OSCreateAlarm(virt_addrof(clientBody->fsmAlarm)); OSSetAlarmUserData(virt_addrof(clientBody->fsmAlarm), clientBody); OSSetAlarm(virt_addrof(clientBody->fsmAlarm), internal::msToTicks(1000), sFsmAlarmCallback); } /** * Alarm handler for the FSM alarm. * * Does things. */ void fsmAlarmHandler(virt_ptr alarm, virt_ptr context) { auto clientBody = virt_cast(OSGetAlarmUserData(alarm)); OSFastMutex_Lock(virt_addrof(clientBody->mutex)); if (clientBody->fsm.unk0x08 == FSVolumeState::Invalid) { clientBody->fsm.unk0x08 = FSVolumeState::NoMedia; fsmSetState(virt_addrof(clientBody->fsm), FSVolumeState::NoMedia, clientBody); } else if (clientBody->fsm.unk0x08 == FSVolumeState::WrongMedia) { fsmSetState(virt_addrof(clientBody->fsm), FSVolumeState::InvalidMedia, clientBody); } OSFastMutex_Unlock(virt_addrof(clientBody->mutex)); } /** * Called when the FSM state has changed. * * \return * Returns the next state. */ FSVolumeState fsmOnStateChange(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { switch (fsm->state) { case FSVolumeState::Initial: return fsmOnStateChangeFromInitial(fsm, state, clientBody); case FSVolumeState::Ready: return fsmOnStateChangeFromReady(fsm, state, clientBody); case FSVolumeState::NoMedia: return fsmOnStateChangeFromNoMedia(fsm, state, clientBody); case FSVolumeState::InvalidMedia: case FSVolumeState::DirtyMedia: case FSVolumeState::WrongMedia: case FSVolumeState::MediaError: case FSVolumeState::DataCorrupted: case FSVolumeState::WriteProtected: return fsmOnStateChangeFromMediaError(fsm, state, clientBody); case FSVolumeState::JournalFull: case FSVolumeState::Fatal: return fsmOnStateChangeFromFatal(fsm, state, clientBody); default: decaf_abort(fmt::format("Invalid FSM state transition from {} to {}!", fsm->state, state)); } return FSVolumeState::Invalid; } /** * Called when the FSM state has changed from FSVolumeState::Initial. * * \return * Returns the next state. */ FSVolumeState fsmOnStateChangeFromInitial(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { switch (state) { case FSVolumeState::NoMedia: return FSVolumeState::Invalid; default: decaf_abort(fmt::format("Invalid FSM state transition from {} to {}!", fsm->state, state)); } return FSVolumeState::Invalid; } /** * Called when the FSM state has changed from FSVolumeState::Ready. * * \return * Returns the next state. */ FSVolumeState fsmOnStateChangeFromReady(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { switch (state) { case FSVolumeState::Ready: { if (clientBody->lastDequeuedCommand) { fsCmdBlockRequeue(virt_addrof(clientBody->cmdQueue), clientBody->lastDequeuedCommand, TRUE, clientBody->lastDequeuedCommand->finishCmdFn); } return FSVolumeState::Invalid; } case FSVolumeState::WrongMedia: { clientBody->fsm.unk0x08 = FSVolumeState::Invalid; fsmStartAlarm(clientBody); return FSVolumeState::NoMedia; } case FSVolumeState::MediaError: return FSVolumeState::WriteProtected; case FSVolumeState::DataCorrupted: return FSVolumeState::DataCorrupted; case FSVolumeState::WriteProtected: return FSVolumeState::MediaError; case FSVolumeState::NoMedia: case FSVolumeState::Invalid: return FSVolumeState::Invalid; default: decaf_abort(fmt::format("Invalid FSM state transition from {} to {}!", fsm->state, state)); } return FSVolumeState::Invalid; } /** * Called when the FSM state has changed from FSVolumeState::NoMedia. * * \return * Returns the next state. */ FSVolumeState fsmOnStateChangeFromNoMedia(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { switch (fsm->state) { case FSVolumeState::Ready: case FSVolumeState::Fatal: { return FSVolumeState::Invalid; } case FSVolumeState::NoMedia: { OSCancelAlarm(virt_addrof(clientBody->fsmAlarm)); fsm->unk0x0c = TRUE; return FSVolumeState::Invalid; } case FSVolumeState::InvalidMedia: { fsm->unk0x0c = TRUE; if (fsm->unk0x08 == FSVolumeState::NoMedia) { return FSVolumeState::Invalid; } return FSVolumeState::NoMedia; } case FSVolumeState::JournalFull: { clientBody->unk0x14D0 = 1u; return FSVolumeState::Invalid; } case FSVolumeState::Invalid: { auto lastCmd = clientBody->lastDequeuedCommand; clientBody->lastDequeuedCommand = nullptr; lastCmd->cancelFlags &= ~FSCmdCancelFlags::Cancelling; lastCmd->status = FSCmdBlockStatus::Cancelled; fsCmdBlockReplyResult(lastCmd, FSStatus::Cancelled); clientBody->unk0x14CC = 0u; clientBody->unk0x14D0 = 1u; fsm->unk0x0c = TRUE; return FSVolumeState::Ready; } default: decaf_abort(fmt::format("Invalid FSM state transition from {} to {}!", fsm->state, state)); } return FSVolumeState::Invalid; } /** * Called when the FSM state has changed from various media error related states. * * \return * Returns the next state. */ FSVolumeState fsmOnStateChangeFromMediaError(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { switch (fsm->state) { case FSVolumeState::Ready: case FSVolumeState::NoMedia: { return FSVolumeState::Invalid; } case FSVolumeState::InvalidMedia: { clientBody->unk0x14CC = 1u; return FSVolumeState::Invalid; } case FSVolumeState::JournalFull: { fsm->unk0x0c = TRUE; clientBody->unk0x14D0 = 1u; return FSVolumeState::Invalid; } case FSVolumeState::Fatal: { fsm->unk0x0c = TRUE; return FSVolumeState::NoMedia; } case FSVolumeState::Invalid: { auto lastCmd = clientBody->lastDequeuedCommand; clientBody->lastDequeuedCommand = nullptr; lastCmd->cancelFlags &= ~FSCmdCancelFlags::Cancelling; lastCmd->status = FSCmdBlockStatus::Cancelled; fsCmdBlockReplyResult(lastCmd, FSStatus::Cancelled); clientBody->unk0x14CC = 0u; clientBody->unk0x14D0 = 1u; fsm->unk0x0c = TRUE; return FSVolumeState::Ready; } default: decaf_abort(fmt::format("Invalid FSM state transition from {} to {}!", fsm->state, state)); } return FSVolumeState::Invalid; } /** * Called when the FSM state has changed from FSVolumeState::Fatal or * FSVolumeState::JournalFull. * * \return * Returns the next state. */ FSVolumeState fsmOnStateChangeFromFatal(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody) { switch (fsm->state) { case FSVolumeState::Ready: case FSVolumeState::JournalFull: { if (clientBody->isLastErrorWithoutVolume) { fsm->unk0x0c = TRUE; } return FSVolumeState::Invalid; } case FSVolumeState::NoMedia: case FSVolumeState::InvalidMedia: case FSVolumeState::DirtyMedia: case FSVolumeState::WrongMedia: case FSVolumeState::MediaError: case FSVolumeState::DataCorrupted: case FSVolumeState::WriteProtected: case FSVolumeState::Fatal: case FSVolumeState::Invalid: return FSVolumeState::Invalid; default: decaf_abort(fmt::format("Invalid FSM state transition from {} to {}!", fsm->state, state)); } return FSVolumeState::Invalid; } } // namespace internal void Library::registerFsStateMachineSymbols() { RegisterFunctionExport(FSGetStateChangeInfo); RegisterFunctionExport(FSSetStateChangeNotification); RegisterFunctionInternal(internal::fsmAlarmHandler, sFsmAlarmCallback); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_statemachine.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_fs.h" #include "coreinit_messagequeue.h" #include namespace cafe::coreinit { /** * \ingroup coreinit_fs * @{ */ struct FSClient; struct FSClientBody; using FSStateChangeCallbackFn = virt_func_ptr, FSVolumeState, virt_ptr)>; struct FSStateChangeAsync { //! Callback for state change notifications. be2_val userCallback; //! Context for state change notification callback. be2_virt_ptr userContext; //! Message queue to insert state change message on. be2_virt_ptr ioMsgQueue; }; CHECK_OFFSET(FSStateChangeAsync, 0x0, userCallback); CHECK_OFFSET(FSStateChangeAsync, 0x4, userContext); CHECK_OFFSET(FSStateChangeAsync, 0x8, ioMsgQueue); CHECK_SIZE(FSStateChangeAsync, 0xC); /** * Data returned from FSGetStateChangeInfo. */ struct FSStateChangeInfo { //! Async data. be2_struct asyncData; //! Message used for asyncData.ioMsgQueue. be2_struct msg; //! Client which this notification is for. be2_virt_ptr client; //! Volume state at the time at which the notification was sent. be2_val state; }; CHECK_OFFSET(FSStateChangeInfo, 0x00, asyncData); CHECK_OFFSET(FSStateChangeInfo, 0x0C, msg); CHECK_OFFSET(FSStateChangeInfo, 0x1C, client); CHECK_OFFSET(FSStateChangeInfo, 0x20, state); CHECK_SIZE(FSStateChangeInfo, 0x24); /** * FileSystem State Machine. * * I don't really understand the control flow of the FSM properly... */ struct FSFsm { be2_val state; be2_val clientVolumeState; be2_val unk0x08; be2_val unk0x0c; //! If TRUE then will send state change notifications. be2_val sendStateChangeNotifications; be2_struct stateChangeInfo; }; CHECK_OFFSET(FSFsm, 0x0, state); CHECK_OFFSET(FSFsm, 0x4, clientVolumeState); CHECK_OFFSET(FSFsm, 0x8, unk0x08); CHECK_OFFSET(FSFsm, 0xC, unk0x0c); CHECK_OFFSET(FSFsm, 0x10, sendStateChangeNotifications); CHECK_OFFSET(FSFsm, 0x14, stateChangeInfo); CHECK_SIZE(FSFsm, 0x38); virt_ptr FSGetStateChangeInfo(virt_ptr message); void FSSetStateChangeNotification(virt_ptr client, virt_ptr asyncData); namespace internal { void fsmInit(virt_ptr fsm, virt_ptr clientBody); void fsmSetState(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); void fsmEnterState(virt_ptr fsm, FSVolumeState state, virt_ptr clientBody); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa.cpp ================================================ #include "coreinit.h" #include "coreinit_appio.h" #include "coreinit_fsa.h" #include "coreinit_fsa_shim.h" #include "coreinit_ipcbufpool.h" #include "coreinit_messagequeue.h" #include "coreinit_spinlock.h" namespace cafe::coreinit { struct StaticFsaData { be2_val initialised; be2_struct bufPoolLock; be2_array ipcPoolBuffer; be2_val ipcPoolNumItems; be2_virt_ptr ipcPool; be2_struct clientListLock; be2_val activeClientCount; be2_array clientList; }; static virt_ptr sFsaData = nullptr; /** * Initialise FSA. */ FSAStatus FSAInit() { if (sFsaData->initialised) { return FSAStatus::OK; } sFsaData->initialised = TRUE; // Initialise buffer pool OSInitSpinLock(virt_addrof(sFsaData->bufPoolLock)); OSAcquireSpinLock(virt_addrof(sFsaData->bufPoolLock)); if (!sFsaData->ipcPool) { sFsaData->ipcPool = IPCBufPoolCreate(virt_addrof(sFsaData->ipcPoolBuffer), sFsaData->ipcPoolBuffer.size(), sizeof(FSAShimBuffer), virt_addrof(sFsaData->ipcPoolNumItems), 0); } OSReleaseSpinLock(virt_addrof(sFsaData->bufPoolLock)); // Initialise client list OSInitSpinLock(virt_addrof(sFsaData->clientListLock)); OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock)); std::memset(virt_addrof(sFsaData->clientList).get(), 0, sizeof(FSAClient) * sFsaData->clientList.size()); auto i = 0u; for (auto &client : sFsaData->clientList) { client.fsaHandle = -1; client.attachMessage.data = virt_addrof(client); client.attachMessage.type = OSFunctionType::FsaAttachEvent; FSAShimAllocateBuffer(virt_addrof(client.attachShimBuffer)); client.attachShimBuffer->command = FSACommand::GetAttach; ++i; } OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock)); return FSAStatus::OK; } /** * Shutdown FSA. */ FSAStatus FSAShutdown() { return FSAStatus::OK; } /** * Create a FSA client. */ FSAStatus FSAAddClient(virt_ptr attachAsyncData) { if (attachAsyncData) { attachAsyncData->ioMsgQueue = OSGetDefaultAppIOQueue(); } if (sFsaData->activeClientCount == sFsaData->clientList.size()) { return FSAStatus::MaxClients; } if (attachAsyncData) { if (!attachAsyncData->ioMsgQueue) { return FSAStatus::InvalidParam; } if (attachAsyncData->ioMsgQueue == OSGetDefaultAppIOQueue() && !attachAsyncData->userCallback) { return FSAStatus::InvalidParam; } } auto error = internal::fsaShimOpen(); if (error < IOSError::OK) { return FSAStatus::PermissionError; } auto fsaHandle = static_cast(error); OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock)); auto client = virt_ptr { nullptr }; for (auto &clientItr : sFsaData->clientList) { if (clientItr.state != FSAClientState::Free) { continue; } client = virt_addrof(clientItr); } if (!client) { OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock)); IOS_Close(fsaHandle); return FSAStatus::MaxClients; } client->state = FSAClientState::Allocated; client->fsaHandle = fsaHandle; OSInitEvent(virt_addrof(client->attachEvent), FALSE, OSEventMode::AutoReset); if (attachAsyncData) { decaf_abort("Unimplemented FSAClient with attachAsyncData"); } else { client->asyncAttachData.userCallback = nullptr; client->asyncAttachData.userContext = nullptr; client->asyncAttachData.ioMsgQueue = nullptr; } ++sFsaData->activeClientCount; OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock)); return static_cast(fsaHandle); } /** * Delete a FSA client. */ FSAStatus FSADelClient(FSAClientHandle handle) { auto client = FSAShimCheckClientHandle(handle); if (!client) { return FSAStatus::InvalidClientHandle; } // Start closing the client client->state = FSAClientState::Closing; auto error = internal::fsaShimClose(client->fsaHandle); if (error < IOSError::OK) { // Failed to close the client... client->state = FSAClientState::Allocated; return FSAShimDecodeIosErrorToFsaStatus(handle, error); } if (client->asyncAttachData.userCallback || client->asyncAttachData.ioMsgQueue) { // Wait for the GetAttach to callback due to handle closing OSWaitEvent(virt_addrof(client->attachEvent)); } OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock)); client->state = FSAClientState::Free; client->fsaHandle = -1; client->asyncAttachData.userCallback = nullptr; client->asyncAttachData.userContext = nullptr; client->asyncAttachData.ioMsgQueue = nullptr; --sFsaData->activeClientCount; OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock)); return FSAStatus::OK; } /** * Get an FSAAsyncResult from an OSMessage. */ virt_ptr FSAGetAsyncResult(virt_ptr message) { return virt_cast(message->message); } /** * __FSAShimCheckClientHandle * * Check if a FSAClientHandle is valid. */ virt_ptr FSAShimCheckClientHandle(FSAClientHandle handle) { if (handle < 0) { return nullptr; } OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock)); for (auto &client : sFsaData->clientList) { if (client.state == FSAClientState::Allocated && client.fsaHandle == handle) { OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock)); return virt_addrof(client); } } OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock)); return nullptr; } /** * __FSAShimAllocateBuffer * * Allocate a FSAShimBuffer object from the FSA ipc buffer pool. */ FSAStatus FSAShimAllocateBuffer(virt_ptr> outBuffer) { if (!sFsaData->ipcPool) { return FSAStatus::NotInit; } OSAcquireSpinLock(virt_addrof(sFsaData->bufPoolLock)); auto ptr = IPCBufPoolAllocate(sFsaData->ipcPool, sizeof(FSAShimBuffer)); OSReleaseSpinLock(virt_addrof(sFsaData->bufPoolLock)); if (!ptr) { return FSAStatus::OutOfResources; } std::memset(ptr.get(), 0, sizeof(FSAShimBuffer)); *outBuffer = virt_cast(ptr); return FSAStatus::OK; } /** * __FSAShimFreeBuffer * * Allocate a FSAShimBuffer object from the FSA ipc buffer pool. */ void FSAShimFreeBuffer(virt_ptr buffer) { OSAcquireSpinLock(virt_addrof(sFsaData->bufPoolLock)); IPCBufPoolFree(sFsaData->ipcPool, buffer); OSReleaseSpinLock(virt_addrof(sFsaData->bufPoolLock)); } namespace internal { /** * Initialise an FSAAsyncResult object. */ void fsaAsyncResultInit(virt_ptr asyncResult, virt_ptr asyncData, OSFunctionType func) { std::memset(asyncResult.get(), 0, sizeof(FSAAsyncResult)); asyncResult->userCallback = asyncData->userCallback; asyncResult->ioMsgQueue = asyncData->ioMsgQueue; asyncResult->userContext = asyncData->userContext; asyncResult->msg.type = func; asyncResult->msg.data = asyncResult; } /** * Convert an FSAStatus error code to an FSStatus error code. */ FSStatus fsaDecodeFsaStatusToFsStatus(FSAStatus error) { switch (error) { case FSAStatus::OK: return FSStatus::OK; case FSAStatus::NotInit: case FSAStatus::Busy: return FSStatus::FatalError; case FSAStatus::Cancelled: return FSStatus::Cancelled; case FSAStatus::EndOfDir: case FSAStatus::EndOfFile: return FSStatus::End; case FSAStatus::MaxMountpoints: case FSAStatus::MaxVolumes: case FSAStatus::MaxClients: case FSAStatus::MaxFiles: case FSAStatus::MaxDirs: return FSStatus::Max; case FSAStatus::AlreadyOpen: return FSStatus::AlreadyOpen; case FSAStatus::AlreadyExists: return FSStatus::Exists; case FSAStatus::NotFound: return FSStatus::NotFound; case FSAStatus::NotEmpty: return FSStatus::Exists; case FSAStatus::AccessError: return FSStatus::AccessError; case FSAStatus::PermissionError: return FSStatus::PermissionError; case FSAStatus::DataCorrupted: return FSStatus::FatalError; case FSAStatus::StorageFull: return FSStatus::StorageFull; case FSAStatus::JournalFull: return FSStatus::JournalFull; case FSAStatus::LinkEntry: return FSStatus::FatalError; case FSAStatus::UnavailableCmd: return FSStatus::FatalError; case FSAStatus::UnsupportedCmd: return FSStatus::UnsupportedCmd; case FSAStatus::InvalidParam: case FSAStatus::InvalidPath: case FSAStatus::InvalidBuffer: case FSAStatus::InvalidAlignment: case FSAStatus::InvalidClientHandle: case FSAStatus::InvalidFileHandle: case FSAStatus::InvalidDirHandle: return FSStatus::FatalError; case FSAStatus::NotFile: return FSStatus::NotFile; case FSAStatus::NotDir: return FSStatus::NotDirectory; case FSAStatus::FileTooBig: return FSStatus::FileTooBig; case FSAStatus::OutOfRange: case FSAStatus::OutOfResources: return FSStatus::FatalError; case FSAStatus::MediaNotReady: return FSStatus::FatalError; case FSAStatus::MediaError: return FSStatus::MediaError; case FSAStatus::WriteProtected: case FSAStatus::InvalidMedia: return FSStatus::FatalError; } return FSStatus::FatalError; } } // namespace internal void Library::registerFsaSymbols() { RegisterFunctionExport(FSAInit); RegisterFunctionExport(FSAShutdown); RegisterFunctionExport(FSAAddClient); RegisterFunctionExport(FSADelClient); RegisterFunctionExport(FSAGetAsyncResult); RegisterFunctionExportName("__FSAShimAllocateBuffer", FSAShimAllocateBuffer); RegisterFunctionExportName("__FSAShimFreeBuffer", FSAShimFreeBuffer); RegisterDataInternal(sFsaData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_event.h" #include "coreinit_fs.h" #include "coreinit_ios.h" #include "ios/fs/ios_fs_fsa.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_fsa FSA * \ingroup coreinit * @{ */ #pragma pack(push, 1) using FSAClientHandle = IOSHandle; using FSAAttachInfo = ios::fs::FSAAttachInfo; using FSABlockInfo = ios::fs::FSABlockInfo; using FSACommand = ios::fs::FSACommand; using FSADeviceInfo = ios::fs::FSADeviceInfo; using FSAFileHandle = ios::fs::FSAFileHandle; using FSAFileSystemInfo = ios::fs::FSAFileSystemInfo; using FSAQueryInfoType = ios::fs::FSAQueryInfoType; using FSAReadFlag = ios::fs::FSAReadFlag; using FSARequest = ios::fs::FSARequest; using FSAResponse = ios::fs::FSAResponse; using FSAStat = ios::fs::FSAStat; using FSAStatus = ios::fs::FSAStatus; using FSAVolumeInfo = ios::fs::FSAVolumeInfo; using FSAWriteFlag = ios::fs::FSAWriteFlag; struct FSAShimBuffer; struct OSMessage; struct OSMessageQueue; using FSAAsyncCallbackFn = virt_func_ptr request, virt_ptr response, virt_ptr userContext)>; using FSAClientAttachAsyncCallbackFn = virt_func_ptr attachInfo, virt_ptr userContext)>; /** * Async data passed to an FSA*Async function. */ struct FSAAsyncData { //! Callback to call when the command is complete. be2_val userCallback; //! Callback context be2_virt_ptr userContext; //! Queue to put a message on when command is complete. be2_virt_ptr ioMsgQueue; }; CHECK_OFFSET(FSAAsyncData, 0x00, userCallback); CHECK_OFFSET(FSAAsyncData, 0x04, userContext); CHECK_OFFSET(FSAAsyncData, 0x08, ioMsgQueue); CHECK_SIZE(FSAAsyncData, 0xC); struct FSAAsyncResult { //! Queue to put a message on when command is complete. be2_virt_ptr ioMsgQueue; //! Message used for ioMsgQueue. be2_struct msg; //! Callback to call when the command is complete. be2_val userCallback; //! Result. be2_val error; //! FSA command. be2_val command; //! Pointer to allocated FSA IPC Request. be2_virt_ptr request; //! Pointer to allocated FSA IPC Response. be2_virt_ptr response; //! Callback to call when the command is complete. be2_virt_ptr userContext; }; CHECK_OFFSET(FSAAsyncResult, 0x00, ioMsgQueue); CHECK_OFFSET(FSAAsyncResult, 0x04, msg); CHECK_OFFSET(FSAAsyncResult, 0x14, userCallback); CHECK_OFFSET(FSAAsyncResult, 0x18, error); CHECK_OFFSET(FSAAsyncResult, 0x1C, command); CHECK_OFFSET(FSAAsyncResult, 0x20, request); CHECK_OFFSET(FSAAsyncResult, 0x24, response); CHECK_OFFSET(FSAAsyncResult, 0x28, userContext); CHECK_SIZE(FSAAsyncResult, 0x2C); struct FSAClientAttachAsyncData { //! Callback to call when an attach has happened. be2_val userCallback; //! Callback context be2_virt_ptr userContext; //! Queue to put a message on when command is complete. be2_virt_ptr ioMsgQueue; }; CHECK_OFFSET(FSAClientAttachAsyncData, 0x00, userCallback); CHECK_OFFSET(FSAClientAttachAsyncData, 0x04, userContext); CHECK_OFFSET(FSAClientAttachAsyncData, 0x08, ioMsgQueue); CHECK_SIZE(FSAClientAttachAsyncData, 0xC); struct FSAClient { be2_val state; be2_val fsaHandle; be2_struct asyncAttachData; be2_struct attachMessage; be2_val lastGetAttachStatus; be2_val attachResponseWord0; be2_struct attachInfo; be2_virt_ptr attachShimBuffer; be2_struct attachEvent; }; CHECK_OFFSET(FSAClient, 0x00, state); CHECK_OFFSET(FSAClient, 0x04, fsaHandle); CHECK_OFFSET(FSAClient, 0x08, asyncAttachData); CHECK_OFFSET(FSAClient, 0x14, attachMessage); CHECK_OFFSET(FSAClient, 0x24, lastGetAttachStatus); CHECK_OFFSET(FSAClient, 0x28, attachResponseWord0); CHECK_OFFSET(FSAClient, 0x2C, attachInfo); CHECK_OFFSET(FSAClient, 0x1E8, attachShimBuffer); CHECK_OFFSET(FSAClient, 0x1EC, attachEvent); CHECK_SIZE(FSAClient, 0x210); #pragma pack(pop) FSAStatus FSAInit(); FSAStatus FSAShutdown(); FSAStatus FSAAddClient(virt_ptr attachAsyncData); FSAStatus FSADelClient(FSAClientHandle handle); virt_ptr FSAGetAsyncResult(virt_ptr message); virt_ptr FSAShimCheckClientHandle(FSAClientHandle handle); FSAStatus FSAShimAllocateBuffer(virt_ptr> outBuffer); void FSAShimFreeBuffer(virt_ptr buffer); namespace internal { void fsaAsyncResultInit(virt_ptr asyncResult, virt_ptr asyncData, OSFunctionType func); FSStatus fsaDecodeFsaStatusToFsStatus(FSAStatus error); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_cmd.cpp ================================================ #include "coreinit.h" #include "coreinit_fsa.h" #include "coreinit_fsa_shim.h" #include "coreinit_ios.h" #include "cafe/cafe_stackobject.h" #include #include namespace cafe::coreinit { namespace internal { static FSAStatus fsaGetInfoByQuery(FSAClientHandle clientHandle, virt_ptr path, FSAQueryInfoType type, virt_ptr out); } // namespace internal FSAStatus FSAChangeDir(FSAClientHandle clientHandle, virt_ptr path) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestChangeDir(*shimBuffer, clientHandle, path); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSACloseFile(FSAClientHandle clientHandle, FSAFileHandle fileHandle) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestCloseFile(*shimBuffer, clientHandle, fileHandle); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSAGetStat(FSAClientHandle clientHandle, virt_ptr path, virt_ptr stat) { return internal::fsaGetInfoByQuery(clientHandle, path, FSAQueryInfoType::Stat, stat); } FSAStatus FSAGetStatFile(FSAClientHandle clientHandle, FSFileHandle handle, virt_ptr outStat) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestStatFile(*shimBuffer, clientHandle, handle); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); if (status >= FSAStatus::OK) { *outStat = (*shimBuffer)->response.statFile.stat; } } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSAMakeDir(FSAClientHandle clientHandle, virt_ptr path, uint32_t permissions) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestMakeDir(*shimBuffer, clientHandle, path, permissions); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSAMount(FSAClientHandle clientHandle, virt_ptr path, virt_ptr target, uint32_t unk0, virt_ptr unkBuf, uint32_t unkBufLen) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestMount(*shimBuffer, clientHandle, path, target, unk0, unkBuf, unkBufLen); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSAOpenFile(FSAClientHandle clientHandle, virt_ptr path, virt_ptr mode, virt_ptr outHandle) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestOpenFile(*shimBuffer, clientHandle, path, mode, 0x660, 0, 0); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } if (status >= FSAStatus::OK) { *outHandle = (*shimBuffer)->response.openFile.handle; } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSAReadFile(FSAClientHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAReadFlag readFlags) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestReadFile(*shimBuffer, clientHandle, buffer, size, count, 0, fileHandle, readFlags & ~FSAReadFlag::ReadWithPos); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSARemove(FSAClientHandle clientHandle, virt_ptr path) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestRemove(*shimBuffer, clientHandle, path); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } FSAStatus FSAWriteFile(FSAClientHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAWriteFlag writeFlags) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestWriteFile(*shimBuffer, clientHandle, buffer, size, count, 0, fileHandle, writeFlags & ~FSAWriteFlag::WriteWithPos); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } FSAShimFreeBuffer(*shimBuffer); return status; } namespace internal { FSAStatus fsaGetInfoByQuery(FSAClientHandle clientHandle, virt_ptr path, FSAQueryInfoType type, virt_ptr out) { auto shimBuffer = StackObject> { }; if (!FSAShimCheckClientHandle(clientHandle)) { return FSAStatus::InvalidClientHandle; } auto status = FSAShimAllocateBuffer(shimBuffer); if (status < FSAStatus::OK) { return status; } status = internal::fsaShimPrepareRequestGetInfoByQuery(*shimBuffer, clientHandle, path, type); if (status >= FSAStatus::OK) { status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK); } if (status >= FSAStatus::OK) { switch (type) { case FSAQueryInfoType::FreeSpaceSize: { auto freeSpaceSize = virt_cast(out); *freeSpaceSize = (*shimBuffer)->response.getInfoByQuery.freeSpaceSize; break; } case FSAQueryInfoType::DirSize: { auto dirSize = virt_cast(out); *dirSize = (*shimBuffer)->response.getInfoByQuery.dirSize; break; } case FSAQueryInfoType::EntryNum: { auto entryNum = virt_cast(out); *entryNum = (*shimBuffer)->response.getInfoByQuery.entryNum; break; } case FSAQueryInfoType::FileSystemInfo: { auto fileSystemInfo = virt_cast(out); *fileSystemInfo = (*shimBuffer)->response.getInfoByQuery.fileSystemInfo; break; } case FSAQueryInfoType::DeviceInfo: { auto deviceInfo = virt_cast(out); *deviceInfo = (*shimBuffer)->response.getInfoByQuery.deviceInfo; break; } case FSAQueryInfoType::Stat: { auto stat = virt_cast(out); *stat = (*shimBuffer)->response.getInfoByQuery.stat; break; } case FSAQueryInfoType::BadBlockInfo: { auto badBlockInfo = virt_cast(out); *badBlockInfo = (*shimBuffer)->response.getInfoByQuery.badBlockInfo; break; } case FSAQueryInfoType::JournalFreeSpaceSize: { auto freeSpaceSize = virt_cast(out); *freeSpaceSize = (*shimBuffer)->response.getInfoByQuery.journalFreeSpaceSize; break; } default: decaf_abort(fmt::format("Unexpected QueryInfoType: {}", type)); } } FSAShimFreeBuffer(*shimBuffer); return status; } } // namespace internal void Library::registerFsaCmdSymbols() { RegisterFunctionExport(FSAChangeDir); RegisterFunctionExport(FSACloseFile); RegisterFunctionExport(FSAGetStat); RegisterFunctionExport(FSAGetStatFile); RegisterFunctionExport(FSAMakeDir); RegisterFunctionExport(FSAMount); RegisterFunctionExport(FSAOpenFile); RegisterFunctionExport(FSAReadFile); RegisterFunctionExport(FSARemove); RegisterFunctionExport(FSAWriteFile); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_cmd.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_fsa.h" namespace cafe::coreinit { FSAStatus FSAChangeDir(FSAClientHandle clientHandle, virt_ptr path); FSAStatus FSACloseFile(FSAClientHandle clientHandle, FSAFileHandle fileHandle); FSAStatus FSAGetStat(FSAClientHandle clientHandle, virt_ptr path, virt_ptr stat); FSAStatus FSAGetStatFile(FSAClientHandle clientHandle, FSFileHandle handle, virt_ptr outStat); FSAStatus FSAMakeDir(FSAClientHandle clientHandle, virt_ptr path, uint32_t permissions); FSAStatus FSAMount(FSAClientHandle clientHandle, virt_ptr path, virt_ptr target, uint32_t unk0, virt_ptr unkBuf, uint32_t unkBufLen); FSAStatus FSAOpenFile(FSAClientHandle clientHandle, virt_ptr path, virt_ptr mode, virt_ptr outHandle); FSAStatus FSAReadFile(FSAClientHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAReadFlag readFlags); FSAStatus FSARemove(FSAClientHandle clientHandle, virt_ptr path); FSAStatus FSAWriteFile(FSAClientHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAWriteFlag writeFlags); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_shim.cpp ================================================ #include "coreinit.h" #include "coreinit_fsa_shim.h" #include "cafe/cafe_stackobject.h" #include "ios/ios_error.h" #include #include #include #include namespace cafe::coreinit { /** * Using the power of magic, turns an IOSError into an FSAStatus. */ FSAStatus FSAShimDecodeIosErrorToFsaStatus(IOSHandle handle, IOSError error) { auto category = ios::getErrorCategory(error); auto code = ios::getErrorCode(error); auto fsaStatus = static_cast(error); if (error < 0) { switch (category) { case IOSErrorCategory::Kernel: if (code == IOSError::Access) { fsaStatus = FSAStatus::InvalidBuffer; } else if (code == IOSError::Invalid || code == IOSError::NoExists) { fsaStatus = FSAStatus::InvalidClientHandle; } else if (code == IOSError::QFull) { fsaStatus = FSAStatus::Busy; } else { fsaStatus = static_cast(code); } break; case IOSErrorCategory::FSA: case IOSErrorCategory::Unknown7: case IOSErrorCategory::Unknown8: case IOSErrorCategory::Unknown15: case IOSErrorCategory::Unknown19: case IOSErrorCategory::Unknown30: case IOSErrorCategory::Unknown45: if (ios::isKernelError(code)) { fsaStatus = static_cast(code - (IOSErrorCategory::FSA << 16)); } else { fsaStatus = static_cast(code); } break; } } return fsaStatus; } namespace internal { /** * Open FSA device. */ IOSError fsaShimOpen() { return IOS_Open(make_stack_string("/dev/fsa"), IOSOpenMode::None); } /** * Close FSA device. */ IOSError fsaShimClose(IOSHandle handle) { return IOS_Close(handle); } /** * Submit a synchronous FSA request. */ FSAStatus fsaShimSubmitRequest(virt_ptr shim, FSAStatus emulatedError) { auto iosError = IOSError::Invalid; if (!shim) { return FSAStatus::InvalidBuffer; } shim->request.emulatedError = emulatedError; if (shim->ipcReqType == FSAIpcRequestType::Ioctl) { iosError = IOS_Ioctl(shim->clientHandle, shim->command, virt_addrof(shim->request), sizeof(shim->request), virt_addrof(shim->response), sizeof(shim->response)); } else if (shim->ipcReqType == FSAIpcRequestType::Ioctlv) { iosError = IOS_Ioctlv(shim->clientHandle, shim->command, shim->ioctlvVecIn, shim->ioctlvVecOut, virt_addrof(shim->ioctlvVec)); } else { decaf_abort(fmt::format("Invalid reqType {}", shim->ipcReqType)); } return FSAShimDecodeIosErrorToFsaStatus(shim->clientHandle, iosError); } /** * Submit an asynchronous FSA request. */ FSAStatus fsaShimSubmitRequestAsync(virt_ptr shim, FSAStatus emulatedError, IOSAsyncCallbackFn callback, virt_ptr context) { auto iosError = IOSError::Invalid; if (!shim) { return FSAStatus::InvalidBuffer; } shim->request.emulatedError = emulatedError; if (shim->ipcReqType == FSAIpcRequestType::Ioctl) { iosError = IOS_IoctlAsync(shim->clientHandle, shim->command, virt_addrof(shim->request), sizeof(shim->request), virt_addrof(shim->response), sizeof(shim->response), callback, context); } else if (shim->ipcReqType == FSAIpcRequestType::Ioctlv) { iosError = IOS_IoctlvAsync(shim->clientHandle, shim->command, shim->ioctlvVecIn, shim->ioctlvVecOut, virt_addrof(shim->ioctlvVec), callback, context); } else { decaf_abort(fmt::format("Invalid reqType {}", shim->ipcReqType)); } return FSAShimDecodeIosErrorToFsaStatus(shim->clientHandle, iosError); } /** * Prepare a FSACommand::AppendFile request. */ FSAStatus fsaShimPrepareRequestAppendFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle handle, uint32_t size, uint32_t count, uint32_t unk) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::AppendFile; auto request = virt_addrof(shim->request.appendFile); request->size = size; request->count = count; request->handle = handle; request->unk0x0C = unk; return FSAStatus::OK; } /** * Prepare a FSACommand::ChangeDir request. */ FSAStatus fsaShimPrepareRequestChangeDir(virt_ptr shim, IOSHandle clientHandle, virt_ptr path) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::ChangeDir; auto request = virt_addrof(shim->request.changeDir); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); return FSAStatus::OK; } /** * Prepare a FSACommand::ChangeMode request. */ FSAStatus fsaShimPrepareRequestChangeMode(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, uint32_t mode1, uint32_t mode2) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::ChangeMode; auto request = virt_addrof(shim->request.changeMode); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); request->mode1 = mode1; request->mode2 = mode2; return FSAStatus::OK; } /** * Prepare a FSACommand::CloseDir request. */ FSAStatus fsaShimPrepareRequestCloseDir(virt_ptr shim, IOSHandle clientHandle, FSDirHandle dirHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::CloseDir; auto request = virt_addrof(shim->request.closeDir); request->handle = dirHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::CloseFile request. */ FSAStatus fsaShimPrepareRequestCloseFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::CloseFile; auto request = virt_addrof(shim->request.closeFile); request->handle = fileHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::FlushFile request. */ FSAStatus fsaShimPrepareRequestFlushFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::FlushFile; auto request = virt_addrof(shim->request.flushFile); request->handle = fileHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::FlushQuota request. */ FSAStatus fsaShimPrepareRequestFlushQuota(virt_ptr shim, IOSHandle clientHandle, virt_ptr path) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::FlushQuota; auto request = virt_addrof(shim->request.flushQuota); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); return FSAStatus::OK; } /** * Prepare a FSACommand::GetCwd request. */ FSAStatus fsaShimPrepareRequestGetCwd(virt_ptr shim, IOSHandle clientHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::GetCwd; return FSAStatus::OK; } /** * Prepare a FSACommand::GetInfoByQuery request. */ FSAStatus fsaShimPrepareRequestGetInfoByQuery(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, FSAQueryInfoType type) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } if (type > FSAQueryInfoType::FragmentBlockInfo) { return FSAStatus::InvalidParam; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::GetInfoByQuery; auto request = virt_addrof(shim->request.getInfoByQuery); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); request->type = type; return FSAStatus::OK; } /** * Prepare a FSACommand::GetPosFile request. */ FSAStatus fsaShimPrepareRequestGetPosFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::GetPosFile; auto request = virt_addrof(shim->request.getPosFile); request->handle = fileHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::IsEof request. */ FSAStatus fsaShimPrepareRequestIsEof(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::IsEof; auto request = virt_addrof(shim->request.isEof); request->handle = fileHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::MakeDir request. */ FSAStatus fsaShimPrepareRequestMakeDir(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, uint32_t permissions) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::MakeDir; auto request = virt_addrof(shim->request.makeDir); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); request->permission = permissions; return FSAStatus::OK; } /** * Prepare a FSACommand::Mount request. */ FSAStatus fsaShimPrepareRequestMount(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, virt_ptr target, uint32_t unk0, virt_ptr unkBuf, uint32_t unkBufLen) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctlv; shim->command = FSACommand::Mount; auto request = virt_addrof(shim->request.mount); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); string_copy(virt_addrof(request->target).get(), request->target.size(), target.get(), FSMaxPathLength); request->unk0x500 = unk0; request->unkBufLen = unkBufLen; shim->ioctlvVecIn = uint8_t { 2 }; shim->ioctlvVecOut = uint8_t { 1 }; shim->ioctlvVec[0].vaddr = virt_cast(virt_addrof(shim->request)); shim->ioctlvVec[0].len = static_cast(sizeof(FSARequest)); shim->ioctlvVec[1].vaddr = virt_cast(unkBuf); shim->ioctlvVec[1].len = unkBufLen; shim->ioctlvVec[2].vaddr = virt_cast(virt_addrof(shim->response)); shim->ioctlvVec[2].len = static_cast(sizeof(FSAResponse)); return FSAStatus::OK; } /** * Prepare a FSACommand::OpenFile request. */ FSAStatus fsaShimPrepareRequestOpenDir(virt_ptr shim, IOSHandle clientHandle, virt_ptr path) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::OpenDir; auto request = virt_addrof(shim->request.openDir); string_copy(virt_addrof(request->path).get(), request->path.size(), path.get(), FSMaxPathLength); auto response = virt_addrof(shim->response.openDir); response->handle = -1; return FSAStatus::OK; } /** * Prepare a FSACommand::OpenFile request. */ FSAStatus fsaShimPrepareRequestOpenFile(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, virt_ptr mode, uint32_t unk0x290, uint32_t unk0x294, uint32_t unk0x298) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } if (!mode || std::strlen(mode.get()) >= 15) { return FSAStatus::InvalidParam; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::OpenFile; auto &request = shim->request.openFile; string_copy(virt_addrof(request.path).get(), request.path.size(), path.get(), FSMaxPathLength); string_copy(virt_addrof(request.mode).get(), request.mode.size(), mode.get(), 16); request.unk0x290 = unk0x290; request.unk0x294 = unk0x294; request.unk0x298 = unk0x298; auto &response = shim->response.openFile; response.handle = -1; return FSAStatus::OK; } /** * Prepare a FSACommand::ReadDir request. */ FSAStatus fsaShimPrepareRequestReadDir(virt_ptr shim, IOSHandle clientHandle, FSDirHandle dirHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::ReadDir; auto &request = shim->request.readDir; request.handle = dirHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::ReadFile request. */ FSAStatus fsaShimPrepareRequestReadFile(virt_ptr shim, IOSHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, FSAReadFlag readFlags) { if (!shim || !buffer) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctlv; shim->command = FSACommand::ReadFile; shim->ioctlvVecIn = uint8_t { 1 }; shim->ioctlvVecOut = uint8_t { 2 }; shim->ioctlvVec[0].vaddr = virt_cast(virt_addrof(shim->request)); shim->ioctlvVec[0].len = static_cast(sizeof(FSARequest)); shim->ioctlvVec[1].vaddr = virt_cast(buffer); shim->ioctlvVec[1].len = size * count; shim->ioctlvVec[2].vaddr = virt_cast(virt_addrof(shim->response)); shim->ioctlvVec[2].len = static_cast(sizeof(FSAResponse)); auto &request = shim->request.readFile; request.buffer = virt_cast(buffer); request.size = size; request.count = count; request.pos = pos; request.handle = handle; request.readFlags = readFlags; return FSAStatus::OK; } /** * Prepare a FSACommand::Remove request. */ FSAStatus fsaShimPrepareRequestRemove(virt_ptr shim, IOSHandle clientHandle, virt_ptr path) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::Remove; auto &request = shim->request.remove; string_copy(virt_addrof(request.path).get(), request.path.size(), path.get(), FSMaxPathLength); return FSAStatus::OK; } /** * Prepare a FSACommand::Rename request. */ FSAStatus fsaShimPrepareRequestRename(virt_ptr shim, IOSHandle clientHandle, virt_ptr oldPath, virt_ptr newPath) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!oldPath || std::strlen(oldPath.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } if (!newPath || std::strlen(newPath.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::Rename; auto &request =shim->request.rename; string_copy(virt_addrof(request.oldPath).get(), request.oldPath.size(), oldPath.get(), FSMaxPathLength); string_copy(virt_addrof(request.newPath).get(), request.newPath.size(), newPath.get(), FSMaxPathLength); return FSAStatus::OK; } /** * Prepare a FSACommand::RewindDir request. */ FSAStatus fsaShimPrepareRequestRewindDir(virt_ptr shim, IOSHandle clientHandle, FSDirHandle dirHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::RewindDir; auto &request = shim->request.rewindDir; request.handle = dirHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::SetPosFile request. */ FSAStatus fsaShimPrepareRequestSetPosFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle, FSFilePosition pos) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::SetPosFile; auto &request = shim->request.setPosFile; request.handle = fileHandle; request.pos = pos; return FSAStatus::OK; } /** * Prepare a FSACommand::StatFile request. */ FSAStatus fsaShimPrepareRequestStatFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::StatFile; auto &request = shim->request.statFile; request.handle = fileHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::TruncateFile request. */ FSAStatus fsaShimPrepareRequestTruncateFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle) { if (!shim) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::TruncateFile; auto &request = shim->request.truncateFile; request.handle = fileHandle; return FSAStatus::OK; } /** * Prepare a FSACommand::Unmount request. */ FSAStatus fsaShimPrepareRequestUnmount(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, uint32_t unk0x280) { if (!shim) { return FSAStatus::InvalidBuffer; } if (!path || std::strlen(path.get()) >= FSMaxPathLength) { return FSAStatus::InvalidPath; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctl; shim->command = FSACommand::Unmount; auto &request = shim->request.unmount; string_copy(virt_addrof(request.path).get(), request.path.size(), path.get(), FSMaxPathLength); request.unk0x280 = unk0x280; return FSAStatus::OK; } /** * Prepare a FSACommand::WriteFile request. */ FSAStatus fsaShimPrepareRequestWriteFile(virt_ptr shim, IOSHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, FSAWriteFlag writeFlags) { if (!shim || !buffer) { return FSAStatus::InvalidBuffer; } shim->clientHandle = clientHandle; shim->ipcReqType = FSAIpcRequestType::Ioctlv; shim->command = FSACommand::WriteFile; shim->ioctlvVecIn = uint8_t { 2 }; shim->ioctlvVecOut = uint8_t { 1 }; shim->ioctlvVec[0].vaddr = virt_cast(virt_addrof(shim->request)); shim->ioctlvVec[0].len = static_cast(sizeof(FSARequest)); shim->ioctlvVec[1].vaddr = virt_cast(buffer); shim->ioctlvVec[1].len = size * count; shim->ioctlvVec[2].vaddr = virt_cast(virt_addrof(shim->response)); shim->ioctlvVec[2].len = static_cast(sizeof(FSAResponse)); auto &request = shim->request.writeFile; request.buffer = virt_cast(buffer); request.size = size; request.count = count; request.pos = pos; request.handle = handle; request.writeFlags = writeFlags; return FSAStatus::OK; } } // namespace internal void Library::registerFsaShimSymbols() { RegisterFunctionExportName("__FSAShimDecodeIosErrorToFsaStatus", FSAShimDecodeIosErrorToFsaStatus); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_shim.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_fs.h" #include "coreinit_fsa.h" #include "coreinit_ios.h" #include "coreinit_messagequeue.h" #include namespace cafe::coreinit { /** * \ingroup coreinit_fsa * @{ */ #pragma pack(push, 1) struct FSAShimBuffer; /** * Stores data regarding FSA IPC requests. */ struct FSAShimBuffer { //! Buffer for FSA IPC request. be2_struct request; UNKNOWN(0x60); //! Buffer for FSA IPC response. be2_struct response; UNKNOWN(0x880 - 0x813); //! Memory to use for ioctlv calls, unknown maximum count - but at least 3. be2_array ioctlvVec; UNKNOWN(0x900 - 0x8A4); //! Command for FSA. be2_val command; //! Handle to FSA device. be2_val clientHandle; //! IOS IPC request type to use. be2_val ipcReqType; //! Number of ioctlv input vectors. be2_val ioctlvVecIn; //! Number of ioctlv output vectors. be2_val ioctlvVecOut; //! FSAAsyncResult used for FSA* functions. be2_struct fsaAsyncResult; }; CHECK_OFFSET(FSAShimBuffer, 0x0, request); CHECK_OFFSET(FSAShimBuffer, 0x580, response); CHECK_OFFSET(FSAShimBuffer, 0x880, ioctlvVec); CHECK_OFFSET(FSAShimBuffer, 0x900, command); CHECK_OFFSET(FSAShimBuffer, 0x904, clientHandle); CHECK_OFFSET(FSAShimBuffer, 0x908, ipcReqType); CHECK_OFFSET(FSAShimBuffer, 0x90A, ioctlvVecIn); CHECK_OFFSET(FSAShimBuffer, 0x90B, ioctlvVecOut); CHECK_OFFSET(FSAShimBuffer, 0x90C, fsaAsyncResult); CHECK_SIZE(FSAShimBuffer, 0x938); #pragma pack(pop) FSAStatus FSAShimDecodeIosErrorToFsaStatus(IOSHandle handle, IOSError error); namespace internal { IOSError fsaShimOpen(); IOSError fsaShimClose(IOSHandle handle); FSAStatus fsaShimSubmitRequest(virt_ptr shim, FSAStatus emulatedError); FSAStatus fsaShimSubmitRequestAsync(virt_ptr shim, FSAStatus emulatedError, IOSAsyncCallbackFn callback, virt_ptr context); FSAStatus fsaShimPrepareRequestAppendFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle handle, uint32_t size, uint32_t count, uint32_t unk); FSAStatus fsaShimPrepareRequestChangeDir(virt_ptr shim, IOSHandle clientHandle, virt_ptr path); FSAStatus fsaShimPrepareRequestChangeMode(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, uint32_t mode1, uint32_t mode2); FSAStatus fsaShimPrepareRequestCloseDir(virt_ptr shim, IOSHandle clientHandle, FSDirHandle dirHandle); FSAStatus fsaShimPrepareRequestCloseFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle); FSAStatus fsaShimPrepareRequestFlushFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle); FSAStatus fsaShimPrepareRequestFlushQuota(virt_ptr shim, IOSHandle clientHandle, virt_ptr path); FSAStatus fsaShimPrepareRequestGetCwd(virt_ptr shim, IOSHandle clientHandle); FSAStatus fsaShimPrepareRequestGetInfoByQuery(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, FSAQueryInfoType type); FSAStatus fsaShimPrepareRequestGetPosFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle); FSAStatus fsaShimPrepareRequestIsEof(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle); FSAStatus fsaShimPrepareRequestMakeDir(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, uint32_t permissions); FSAStatus fsaShimPrepareRequestMount(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, virt_ptr target, uint32_t unk0, virt_ptr unkBuf, uint32_t unkBufLen); FSAStatus fsaShimPrepareRequestOpenDir(virt_ptr shim, IOSHandle clientHandle, virt_ptr path); FSAStatus fsaShimPrepareRequestOpenFile(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, virt_ptr mode, uint32_t unk0x290, uint32_t unk0x294, uint32_t unk0x298); FSAStatus fsaShimPrepareRequestReadDir(virt_ptr shim, IOSHandle clientHandle, FSDirHandle dirHandle); FSAStatus fsaShimPrepareRequestReadFile(virt_ptr shim, IOSHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, FSAReadFlag readFlags); FSAStatus fsaShimPrepareRequestRemove(virt_ptr shim, IOSHandle clientHandle, virt_ptr path); FSAStatus fsaShimPrepareRequestRename(virt_ptr shim, IOSHandle clientHandle, virt_ptr oldPath, virt_ptr newPath); FSAStatus fsaShimPrepareRequestRewindDir(virt_ptr shim, IOSHandle clientHandle, FSDirHandle dirHandle); FSAStatus fsaShimPrepareRequestSetPosFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle, FSFilePosition pos); FSAStatus fsaShimPrepareRequestStatFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle); FSAStatus fsaShimPrepareRequestTruncateFile(virt_ptr shim, IOSHandle clientHandle, FSFileHandle fileHandle); FSAStatus fsaShimPrepareRequestUnmount(virt_ptr shim, IOSHandle clientHandle, virt_ptr path, uint32_t unk0x280); FSAStatus fsaShimPrepareRequestWriteFile(virt_ptr shim, IOSHandle clientHandle, virt_ptr buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, FSAWriteFlag writeFlags); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ghs.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_driver.h" #include "coreinit_memdefaultheap.h" #include "coreinit_mutex.h" #include "coreinit_scheduler.h" #include "coreinit_spinlock.h" #include "coreinit_interrupts.h" #include "coreinit_osreport.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/kernel/cafe_kernel_process.h" #include #include namespace cafe::coreinit { using AtExitFn = virt_func_ptr; using AtExitCleanupFn = virt_func_ptr; using StdioCleanupFn = virt_func_ptr; using CppExceptionInitPtrFn = virt_func_ptr>)>; using CppExceptionCleanupPtrFn = virt_func_ptr)>; constexpr auto GHS_FLOCK_MAX = 100u; constexpr auto GHS_FOPEN_MAX = uint16_t { 20u }; struct ghs_atexit { be2_val callback; be2_virt_ptr next; }; CHECK_OFFSET(ghs_atexit, 0x0, callback); CHECK_OFFSET(ghs_atexit, 0x4, next); CHECK_SIZE(ghs_atexit, 8); BITFIELD_BEG(ghs_iobuf_bits, uint32_t) BITFIELD_ENTRY(0, 1, bool, readwrite); BITFIELD_ENTRY(1, 1, bool, writable); BITFIELD_ENTRY(2, 1, bool, readable); BITFIELD_ENTRY(18, 14, uint32_t, channel); BITFIELD_END struct ghs_iobuf { be2_virt_ptr nextPtr; be2_virt_ptr basePtr; be2_val bytesLeft; be2_val info; }; CHECK_OFFSET(ghs_iobuf, 0x0, nextPtr); CHECK_OFFSET(ghs_iobuf, 0x4, basePtr); CHECK_OFFSET(ghs_iobuf, 0x8, bytesLeft); CHECK_OFFSET(ghs_iobuf, 0xc, info); CHECK_SIZE(ghs_iobuf, 0x10); struct StaticGhsData { be2_struct ghsLock; be2_virt_ptr atExitCallbacks; be2_struct flockMutex; be2_val freeFlockIdx; be2_array flockInUse; be2_array flocks; }; static virt_ptr sGhsData = nullptr; //! __atexit_cleanup static virt_ptr atexit_cleanup = nullptr; //! __stdio_cleanup static virt_ptr stdio_cleanup = nullptr; //! __cpp_exception_init_ptr static virt_ptr cpp_exception_init_ptr = nullptr; //! __cpp_exception_cleanup_ptr static virt_ptr cpp_exception_cleanup_ptr = nullptr; //! __ghs_cpp_locks static virt_ptr ghs_cpp_locks = nullptr; //! __gh_FOPEN_MAX static virt_ptr gh_FOPEN_MAX = nullptr; //! _iob static virt_ptr ghs_iob = nullptr; //! _iob_lock static virt_ptr ghs_iob_lock = nullptr; //! errno static virt_ptr ghs_errno = nullptr; //! environ static virt_ptr> ghs_environ = nullptr; /** * __ghsLock */ void ghsLock() { OSUninterruptibleSpinLock_Acquire(virt_addrof(sGhsData->ghsLock)); } /** * __ghsUnlock */ void ghsUnlock() { OSUninterruptibleSpinLock_Release(virt_addrof(sGhsData->ghsLock)); } /** * __ghs_at_exit */ void ghs_at_exit(virt_ptr atExitCallback) { ghsLock(); atExitCallback->next = sGhsData->atExitCallbacks; sGhsData->atExitCallbacks = atExitCallback; ghsUnlock(); } /** * __ghs_at_exit_cleanup */ void ghs_at_exit_cleanup() { for (auto item = sGhsData->atExitCallbacks; item; item = item->next) { cafe::invoke(cpu::this_core::state(), item->callback); } } /** * __PPCExit */ void ghs_PPCExit(int32_t code) { internal::driverOnDone(); internal::pauseCoreTime(true); kernel::exitProcess(code); } /** * _Exit */ void ghs_Exit(int32_t code) { ghs_at_exit_cleanup(); ghs_PPCExit(code); } /** * exit */ void ghs_exit(int32_t code) { internal::COSVerbose(COSReportModule::Unknown1, "ATEXIT: RPX (calls RPX DTORs)"); if (*atexit_cleanup) { cafe::invoke(cpu::this_core::state(), *atexit_cleanup, code); } if (*stdio_cleanup) { cafe::invoke(cpu::this_core::state(), *stdio_cleanup); } ghs_Exit(code); } /** * __gh_errno_ptr */ virt_ptr gh_errno_ptr() { auto thread = OSGetCurrentThread(); if (!thread) { return ghs_errno; } else { return virt_addrof(thread->context.error); } } /** * __gh_get_errno */ int32_t gh_get_errno() { return *gh_errno_ptr(); } /** * __gh_set_errno */ void gh_set_errno(int32_t error) { *gh_errno_ptr() = error; } /** * __ghs_flock_create */ void ghs_flock_create(virt_ptr outFlockIdx) { auto flockIdx = sGhsData->freeFlockIdx; OSLockMutex(virt_addrof(sGhsData->flockMutex)); if (flockIdx >= 101 || sGhsData->flockInUse[flockIdx]) { for (flockIdx = 0u; flockIdx < sGhsData->flockInUse.size(); ++flockIdx) { if (!sGhsData->flockInUse[flockIdx]) { break; } } if (flockIdx >= sGhsData->flockInUse.size()) { internal::OSPanic("locks.c", 122, "All flocks are in use"); } } OSInitMutex(virt_addrof(sGhsData->flocks[flockIdx])); sGhsData->flockInUse[flockIdx] = uint8_t { 1 }; *outFlockIdx = flockIdx; sGhsData->freeFlockIdx = flockIdx + 1; OSUnlockMutex(virt_addrof(sGhsData->flockMutex)); } /** * __ghs_flock_destroy */ void ghs_flock_destroy(uint32_t flockIdx) { sGhsData->flockInUse[flockIdx] = uint8_t { 0 }; sGhsData->freeFlockIdx = flockIdx; } /** * __ghs_flock_file */ void ghs_flock_file(uint32_t flockIdx) { auto mutex = virt_addrof(sGhsData->flocks[flockIdx]); if (!OSIsInterruptEnabled()) { if (mutex->owner && mutex->owner != OSGetCurrentThread()) { internal::COSWarn(COSReportModule::Unknown1, "***\n" "*** STD LIBC FILE I/O:\n" "Locking a mutex owned by another thread while interrupts are off!!\n" "***\n"); } } OSLockMutex(mutex); } /** * __ghs_flock_ptr */ virt_ptr ghs_flock_ptr(virt_ptr iob) { auto index = static_cast(iob - virt_addrof(ghs_iob->at(0))); if (index > *gh_FOPEN_MAX) { index = *gh_FOPEN_MAX; } return virt_addrof(ghs_iob_lock->at(index)); } /** * __ghs_ftrylock_file */ int32_t ghs_ftrylock_file(uint32_t flockIdx) { if (OSTryLockMutex(virt_addrof(sGhsData->flocks[flockIdx]))) { return 0; } else { return 1234; } } /** * __gh_iob_init */ void gh_iob_init() { // stdin ghs_iob->at(0).info = ghs_iob->at(0).info.value() .readable(true) .channel(0); if (auto flock_ptr = ghs_flock_ptr(virt_addrof(ghs_iob->at(0)))) { ghs_flock_create(flock_ptr); } // stdout ghs_iob->at(1).info = ghs_iob->at(1).info.value() .writable(true) .channel(1); if (auto flock_ptr = ghs_flock_ptr(virt_addrof(ghs_iob->at(1)))) { ghs_flock_create(flock_ptr); } // stderr ghs_iob->at(2).info = ghs_iob->at(2).info.value() .writable(true) .channel(2); if (auto flock_ptr = ghs_flock_ptr(virt_addrof(ghs_iob->at(2)))) { ghs_flock_create(flock_ptr); } } /** * __gh_lock_init */ void gh_lock_init() { sGhsData->flockInUse.fill(0); sGhsData->freeFlockIdx = 0u; OSInitSpinLock(virt_addrof(sGhsData->ghsLock)); OSInitMutex(virt_addrof(sGhsData->flockMutex)); OSInitMutex(ghs_cpp_locks); } /** * __ghs_funlock_file */ void ghs_funlock_file(uint32_t flockIdx) { OSUnlockMutex(virt_addrof(sGhsData->flocks[flockIdx])); } /** * __ghs_mtx_init */ void ghs_mtx_init(virt_ptr> outMutex) { auto mutex = virt_cast(MEMAllocFromDefaultHeapEx(sizeof(OSMutex), 8)); OSInitMutex(mutex); *outMutex = mutex; } /** * __ghs_mtx_dst */ void ghs_mtx_dst(virt_ptr> mutex) { MEMFreeToDefaultHeap(*mutex); } /** * __ghs_mtx_lock */ void ghs_mtx_lock(virt_ptr> mutex) { OSLockMutex(*mutex); } /** * __ghs_mtx_unlock */ void ghs_mtx_unlock(virt_ptr> mutex) { OSUnlockMutex(*mutex); } /** * __get_eh_globals */ virt_ptr get_eh_globals() { return OSGetCurrentThread()->eh_globals; } /** * __get_eh_init_block */ virt_ptr get_eh_init_block() { return nullptr; } /** * __get_eh_mem_manage */ virt_ptr get_eh_mem_manage() { return virt_addrof(OSGetCurrentThread()->eh_mem_manage); } /** * __get_eh_store_globals */ virt_ptr get_eh_store_globals() { return virt_addrof(OSGetCurrentThread()->eh_store_globals); } /** * __get_eh_store_globals_tdeh */ virt_ptr get_eh_store_globals_tdeh() { return virt_addrof(OSGetCurrentThread()->eh_store_globals_tdeh); } namespace internal { void initialiseGhs() { *gh_FOPEN_MAX = GHS_FOPEN_MAX; gh_lock_init(); gh_iob_init(); } void ghsExceptionInit(virt_ptr thread) { if (*cpp_exception_init_ptr) { cafe::invoke(cpu::this_core::state(), *cpp_exception_init_ptr, virt_addrof(thread->eh_globals)); } } void ghsExceptionCleanup(virt_ptr thread) { if (*cpp_exception_cleanup_ptr && thread->eh_globals) { cafe::invoke(cpu::this_core::state(), *cpp_exception_cleanup_ptr, thread->eh_globals); } } } // namespace internal void Library::registerGhsSymbols() { RegisterFunctionExportName("__ghsLock", ghsLock); RegisterFunctionExportName("__ghsUnlock", ghsUnlock); RegisterFunctionExportName("__ghs_at_exit", ghs_at_exit); RegisterFunctionExportName("__ghs_at_exit_cleanup", ghs_at_exit_cleanup); RegisterFunctionExportName("exit", ghs_exit); RegisterFunctionExportName("_Exit", ghs_Exit); RegisterFunctionExportName("__PPCExit", ghs_PPCExit); RegisterFunctionExportName("__gh_errno_ptr", gh_errno_ptr); RegisterFunctionExportName("__gh_get_errno", gh_get_errno); RegisterFunctionExportName("__gh_set_errno", gh_set_errno); RegisterFunctionExportName("__gh_iob_init", gh_iob_init); RegisterFunctionExportName("__gh_lock_init", gh_lock_init); RegisterFunctionExportName("__ghs_flock_create", ghs_flock_create); RegisterFunctionExportName("__ghs_flock_destroy", ghs_flock_destroy); RegisterFunctionExportName("__ghs_flock_file", ghs_flock_file); RegisterFunctionExportName("__ghs_flock_ptr", ghs_flock_ptr); RegisterFunctionExportName("__ghs_ftrylock_file", ghs_ftrylock_file); RegisterFunctionExportName("__ghs_funlock_file", ghs_funlock_file); RegisterFunctionExportName("__ghs_mtx_init", ghs_mtx_init); RegisterFunctionExportName("__ghs_mtx_dst", ghs_mtx_dst); RegisterFunctionExportName("__ghs_mtx_lock", ghs_mtx_lock); RegisterFunctionExportName("__ghs_mtx_unlock", ghs_mtx_unlock); RegisterFunctionExportName("__get_eh_globals", get_eh_globals); RegisterFunctionExportName("__get_eh_init_block", get_eh_init_block); RegisterFunctionExportName("__get_eh_mem_manage", get_eh_mem_manage); RegisterFunctionExportName("__get_eh_store_globals", get_eh_store_globals); RegisterFunctionExportName("__get_eh_store_globals_tdeh", get_eh_store_globals_tdeh); RegisterDataExportName("__atexit_cleanup", atexit_cleanup); RegisterDataExportName("__stdio_cleanup", stdio_cleanup); RegisterDataExportName("__cpp_exception_init_ptr", cpp_exception_init_ptr); RegisterDataExportName("__cpp_exception_cleanup_ptr", cpp_exception_cleanup_ptr); RegisterDataExportName("__ghs_cpp_locks", ghs_cpp_locks); RegisterDataExportName("__gh_FOPEN_MAX", gh_FOPEN_MAX); RegisterDataExportName("_iob", ghs_iob); RegisterDataExportName("_iob_lock", ghs_iob_lock); RegisterDataExportName("errno", ghs_errno); RegisterDataExportName("environ", ghs_environ); RegisterDataInternal(sGhsData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ghs.h ================================================ #pragma once #include namespace cafe::coreinit { struct OSThread; void ghs_exit(int32_t code); void ghs_Exit(int32_t code); void ghs_PPCExit(int32_t code); int32_t gh_get_errno(); void gh_set_errno(int32_t error); namespace internal { void initialiseGhs(); void ghsExceptionInit(virt_ptr thread); void ghsExceptionCleanup(virt_ptr thread); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_handle.cpp ================================================ #include "coreinit.h" #include "coreinit_handle.h" #include "coreinit_spinlock.h" #include "coreinit_systemheap.h" #include "coreinit_time.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include #include namespace cafe::coreinit { static internal::SubTableAllocFn sAllocSubTable; static internal::SubTableFreeFn sFreeSubTable; namespace internal { static OSHandleError Handle_InitTable(virt_ptr table, SubTableAllocFn allocSubTableFn, SubTableFreeFn freeSubTableFn) { if (!table) { return OSHandleError::InvalidArgument; } std::memset(table.get(), 0, sizeof(HandleTable)); table->allocSubTableFn = allocSubTableFn; table->freeSubTableFn = freeSubTableFn; table->handleEntropy = 0xCAFEu; table->subTables[0] = virt_addrof(table->firstSubTable); table->subTableFreeEntries[0] = HandleSubTable::NumEntries; return OSHandleError::OK; } static OSHandleError Handle_Alloc(virt_ptr table, virt_ptr userData1, virt_ptr userData2, virt_ptr outHandle) { if (!table || !outHandle) { return OSHandleError::InvalidArgument; } auto firstFreeSubTableIdx = -1; auto subTableIdx = -1; // Find a sub table with a free entry for (auto i = 0; i < HandleTable::NumSubTables; ++i) { if (table->subTables[i]) { if (table->subTableFreeEntries[i]) { subTableIdx = i; break; } } else if (firstFreeSubTableIdx) { firstFreeSubTableIdx = i; } } if (subTableIdx == -1) { if (firstFreeSubTableIdx == -1) { // Table completely full return OSHandleError::TableFull; } // Allocate a new empty sub table subTableIdx = firstFreeSubTableIdx; auto subTable = cafe::invoke(cpu::this_core::state(), table->allocSubTableFn); std::memset(subTable.get(), 0, sizeof(HandleSubTable)); table->subTables[subTableIdx] = subTable; table->subTableFreeEntries[subTableIdx] = HandleSubTable::NumEntries; } auto subTable = table->subTables[subTableIdx]; auto entryIndex = static_cast(rand()) % HandleSubTable::NumEntries; auto firstEntryIndex = entryIndex; while (subTable->entries[entryIndex].handle) { ++entryIndex; if (entryIndex == HandleSubTable::NumEntries) { entryIndex = 0; } if (entryIndex == firstEntryIndex) { // Somehow we have not found a free entry, this is a major fuckup and // should actually be impossible... return OSHandleError::InternalError; } } auto handleEntropy = (table->handleEntropy + OSGetTick() % 0x20041) ^ 0x1A5A; if (!handleEntropy) { handleEntropy = 1; } table->handleEntropy = handleEntropy; // Thanks Hex-Rays! auto handleIndex = entryIndex | (subTableIdx << 9); auto v21 = ((handleIndex + 1) & 0x1FFFF) | ((handleEntropy << 17) & 0x7FE0000); auto v22 = (((v21 & 0x55555555) + ((v21 & 0xAAAAAAAA) >> 1)) & 0x33333333) + ((((v21 & 0x55555555) + ((v21 & 0xAAAAAAAA) >> 1)) & 0xCCCCCCCC) >> 2); auto handle = (0xF8000000 * ((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF) + ((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF00) >> 8) + (((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF) + ((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF00) >> 8)) >> 16)) & 0xF8000000) | (v21 & 0x7FFFFFF); --table->subTableFreeEntries[subTableIdx]; auto &entry = subTable->entries[entryIndex]; entry.handle = static_cast(handle); entry.userData1 = userData1; entry.userData2 = userData2; entry.refCount = 1u; *outHandle = handle; return OSHandleError::OK; } static OSHandleError Handle_TranslateAndAddRef(virt_ptr table, OSHandle handle, virt_ptr> outUserData1, virt_ptr> outUserData2) { if (!table || !handle) { return OSHandleError::InvalidArgument; } auto handleIndex = (handle - 1) & 0x1FFFF; auto subTableIndex = handleIndex >> 9; auto entryIndex = handleIndex & 0x1FF; if (subTableIndex > HandleTable::NumSubTables || entryIndex > HandleSubTable::NumEntries) { return OSHandleError::InvalidHandle; } auto subTable = table->subTables[subTableIndex]; if (!subTable) { return OSHandleError::InvalidHandle; } auto &entry = subTable->entries[entryIndex]; if (entry.handle != handle) { return OSHandleError::InvalidHandle; } entry.refCount++; if (outUserData1) { *outUserData1 = entry.userData1; } if (outUserData2) { *outUserData2 = entry.userData2; } return OSHandleError::OK; } static OSHandleError Handle_Release(virt_ptr table, OSHandle handle, virt_ptr outRefCount) { if (!table || !handle) { return OSHandleError::InvalidArgument; } auto handleIndex = (handle - 1) & 0x1FFFF; auto subTableIndex = handleIndex >> 9; auto entryIndex = handleIndex & 0x1FF; if (subTableIndex > HandleTable::NumSubTables || entryIndex > HandleSubTable::NumEntries) { return OSHandleError::InvalidHandle; } auto subTable = table->subTables[subTableIndex]; if (!subTable) { return OSHandleError::InvalidHandle; } auto &entry = subTable->entries[entryIndex]; if (entry.handle != handle) { return OSHandleError::InvalidHandle; } entry.refCount--; if (outRefCount) { *outRefCount = entry.refCount; } if (!entry.refCount) { std::memset(virt_addrof(entry).get(), 0, sizeof(HandleEntry)); table->subTableFreeEntries[subTableIndex]++; // Free the sub table if it is completely empty and was a dynamically // allocated sub table (index > 0). if (subTableIndex > 0 && table->subTableFreeEntries[subTableIndex] == HandleSubTable::NumEntries && table->freeSubTableFn) { cafe::invoke(cpu::this_core::state(), table->freeSubTableFn, subTable); table->subTableFreeEntries[subTableIndex] = 0u; table->subTables[subTableIndex] = nullptr; } } return OSHandleError::OK; } static virt_ptr allocSubTable() { return virt_cast( OSAllocFromSystem(sizeof(HandleSubTable), 4) ); } static void freeSubTable(virt_ptr table) { OSFreeToSystem(table); } } // namespace internal /** * Initialise a handle table. * * \param table * The handle table to initialise. * * \return * OSHandleError::OK on success, a OSHandleError error code otherwise. */ OSHandleError OSHandle_InitTable(virt_ptr table) { if (!table) { return OSHandleError::InvalidArgument; } std::memset(table.get(), 0, sizeof(OSHandleTable)); auto error = internal::Handle_InitTable(virt_addrof(table->handleTable), sAllocSubTable, sFreeSubTable); if (error == OSHandleError::OK) { OSInitSpinLock(virt_addrof(table->lock)); } return error; } /** * Allocate a new handle from the handle table. * * \param table * The handle table to allocate the handle from. * * \param userData1 * User data to set in the handle entry, can be read from * OSHandle_TranslateAndAddRef. * * \param userData2 * User data to set in the handle entry, can be read from * OSHandle_TranslateAndAddRef. * * \param[out] outHandle * Output parameter, set to the handle value for the newly acquired handle. * Must not be nullptr. * * \return * OSHandleError::OK on success, a OSHandleError error code otherwise. */ OSHandleError OSHandle_Alloc(virt_ptr table, virt_ptr userData1, virt_ptr userData2, virt_ptr outHandle) { if (!table || !outHandle) { return OSHandleError::InvalidArgument; } OSUninterruptibleSpinLock_Acquire(virt_addrof(table->lock)); auto error = internal::Handle_Alloc(virt_addrof(table->handleTable), userData1, userData2, outHandle); OSUninterruptibleSpinLock_Release(virt_addrof(table->lock)); return error; } /** * Increase the reference count of a handle. * * \param table * The handle table to acquire the handle from. * * \param handle * The handle to acquire. * * \return * OSHandleError::OK on success, a OSHandleError error code otherwise. */ OSHandleError OSHandle_AddRef(virt_ptr table, OSHandle handle) { return OSHandle_TranslateAndAddRef(table, handle, nullptr, nullptr); } /** * Increase the reference count of a handle and retrieve it's user data values. * * \param table * The handle table to acquire the handle from. * * \param handle * The handle to acquire. * * \param[out] outUserData1 * Optional output parameter, set to the userData1 value for the handle which * was passed into OSHandle_Alloc. * * \param[out] outUserData2 * Optional output parameter, set to the userData2 value for the handle which * was passed into OSHandle_Alloc. * * \return * OSHandleError::OK on success, a OSHandleError error code otherwise. */ OSHandleError OSHandle_TranslateAndAddRef(virt_ptr table, OSHandle handle, virt_ptr> outUserData1, virt_ptr> outUserData2) { if (!table || !handle) { return OSHandleError::InvalidArgument; } OSUninterruptibleSpinLock_Acquire(virt_addrof(table->lock)); auto error = internal::Handle_TranslateAndAddRef(virt_addrof(table->handleTable), handle, outUserData1, outUserData2); OSUninterruptibleSpinLock_Release(virt_addrof(table->lock)); return error; } /** * Reduce the reference count of a handle and free it if the count reaches 0. * * \param table * The handle table to release the handle from. * * \param handle * The handle to release. * * \param[out] outRefCount * Optional output parameter, set to the value of the new ref count. * * \return * OSHandleError::OK on success, a OSHandleError error code otherwise. */ OSHandleError OSHandle_Release(virt_ptr table, OSHandle handle, virt_ptr outRefCount) { if (!table || !handle) { return OSHandleError::InvalidArgument; } OSUninterruptibleSpinLock_Acquire(virt_addrof(table->lock)); auto error = internal::Handle_Release(virt_addrof(table->handleTable), handle, outRefCount); OSUninterruptibleSpinLock_Release(virt_addrof(table->lock)); return error; } void Library::registerHandleSymbols() { RegisterFunctionExport(OSHandle_AddRef); RegisterFunctionExport(OSHandle_Alloc); RegisterFunctionExport(OSHandle_InitTable); RegisterFunctionExport(OSHandle_Release); RegisterFunctionExport(OSHandle_TranslateAndAddRef); RegisterFunctionInternal(internal::allocSubTable, sAllocSubTable); RegisterFunctionInternal(internal::freeSubTable, sFreeSubTable); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_handle.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_spinlock.h" #include namespace cafe::coreinit { using OSHandle = uint32_t; namespace internal { struct HandleEntry; struct HandleSubTable; struct HandleTable; using SubTableAllocFn = virt_func_ptr()>; using SubTableFreeFn = virt_func_ptr)>; struct HandleEntry { be2_val handle; be2_virt_ptr userData1; be2_virt_ptr userData2; be2_val refCount; }; struct HandleSubTable { static constexpr auto NumEntries = 0x200u; be2_array entries; }; struct HandleTable { static constexpr auto NumSubTables = 0x100u; be2_val allocSubTableFn; be2_val freeSubTableFn; be2_val handleEntropy; be2_array subTableFreeEntries; be2_array, NumSubTables> subTables; be2_struct firstSubTable; }; CHECK_OFFSET(HandleTable, 0x00, allocSubTableFn); CHECK_OFFSET(HandleTable, 0x04, freeSubTableFn); CHECK_OFFSET(HandleTable, 0x08, handleEntropy); CHECK_OFFSET(HandleTable, 0x0C, subTableFreeEntries); CHECK_OFFSET(HandleTable, 0x40C, subTables); CHECK_OFFSET(HandleTable, 0x80C, firstSubTable); CHECK_SIZE(HandleTable, 0x280C); } // namespace internal struct OSHandleTable { be2_struct handleTable; PADDING(4); be2_struct lock; }; CHECK_SIZE(OSHandleTable, 0xA08 * 4); OSHandleError OSHandle_InitTable(virt_ptr table); OSHandleError OSHandle_Alloc(virt_ptr table, virt_ptr userData1, virt_ptr userData2, virt_ptr outHandle); OSHandleError OSHandle_AddRef(virt_ptr table, OSHandle handle); OSHandleError OSHandle_TranslateAndAddRef(virt_ptr table, OSHandle handle, virt_ptr> outUserData1, virt_ptr> outUserData2); OSHandleError OSHandle_Release(virt_ptr table, OSHandle handle, virt_ptr outRefCount); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_im.cpp ================================================ #include "coreinit.h" #include "coreinit_im.h" #include "coreinit_ios.h" #include "coreinit_mutex.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include namespace cafe::coreinit { struct StaticImData { be2_struct itbMutex; be2_array itbMutexName; be2_struct sharedRequest; }; static virt_ptr sImData = nullptr; static IOSAsyncCallbackFn sImIosAsyncCallback = nullptr; namespace internal { static void imAcquireItbMutex() { OSLockMutex(virt_addrof(sImData->itbMutex)); } static void imReleaseItbMutex() { OSUnlockMutex(virt_addrof(sImData->itbMutex)); } static void imCopyData(virt_ptr request) { if (request->copyDst && request->copySrc && request->copySize) { std::memcpy(request->copyDst.get(), request->copySrc.get(), request->copySize); } } static void imIosAsyncCallback(IOSError error, virt_ptr context) { auto request = virt_cast(context); if (error == IOSError::OK) { imCopyData(request); } cafe::invoke(cpu::this_core::state(), request->asyncCallback, error, request->asyncCallbackContext); } static IMError imSendRequest(virt_ptr request, uint32_t vecIn, uint32_t vecOut) { auto error = IOSError::OK; if (request->asyncCallback) { error = IOS_IoctlvAsync(request->handle, request->request, vecIn, vecOut, virt_addrof(request->ioctlVecs), sImIosAsyncCallback, request); } else { error = IOS_Ioctlv(request->handle, request->request, vecIn, vecOut, virt_addrof(request->ioctlVecs)); if (vecOut > 0 && error == IOSError::OK) { imCopyData(request); } } return static_cast(error); } } // namespace internal IMError IM_Open() { return static_cast(IOS_Open(cafe::make_stack_string("/dev/im"), IOSOpenMode::None)); } IMError IM_Close(IOSHandle handle) { return static_cast(IOS_Close(handle)); } IMError IM_GetHomeButtonParams(IOSHandle handle, virt_ptr request, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext) { std::memset(request.get(), 0, sizeof(IMRequest)); request->ioctlVecs[0].vaddr = virt_cast(virt_addrof(request->getHomeButtomParamResponse)); request->ioctlVecs[0].len = 8u; request->handle = handle; request->request = IMCommand::GetHomeButtonParams; request->asyncCallback = asyncCallback; request->asyncCallbackContext = asyncCallbackContext; request->copySrc = virt_addrof(request->getHomeButtomParamResponse); request->copyDst = output; request->copySize = 8u; return internal::imSendRequest(request, 0, 1); } IMError IM_GetParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext) { std::memset(request.get(), 0, sizeof(IMRequest)); request->getParameterRequest.parameter = parameter; request->ioctlVecs[0].vaddr = virt_cast(virt_addrof(request->getParameterRequest)); request->ioctlVecs[0].len = 8u; request->ioctlVecs[1].vaddr = virt_cast(virt_addrof(request->getParameterResponse)); request->ioctlVecs[1].len = 8u; request->handle = handle; request->request = IMCommand::GetParameter; request->asyncCallback = asyncCallback; request->asyncCallbackContext = asyncCallbackContext; request->copySrc = virt_addrof(request->getParameterResponse.value); request->copyDst = output; request->copySize = 4u; return internal::imSendRequest(request, 1, 1); } IMError IM_GetParameters(virt_ptr parameters) { auto result = IM_Open(); if (result < 0) { return result; } auto handle = static_cast(result); internal::imAcquireItbMutex(); result = IM_GetParameter(handle, virt_addrof(sImData->sharedRequest), IMParameter::ResetEnable, virt_addrof(parameters->resetEnabled), nullptr, nullptr); if (result != IMError::OK) { goto out; } result = IM_GetParameter(handle, virt_addrof(sImData->sharedRequest), IMParameter::DimEnabled, virt_addrof(parameters->dimEnabled), nullptr, nullptr); if (result != IMError::OK) { goto out; } result = IM_GetParameter(handle, virt_addrof(sImData->sharedRequest), IMParameter::DimPeriod, virt_addrof(parameters->dimPeriod), nullptr, nullptr); if (result != IMError::OK) { goto out; } result = IM_GetParameter(handle, virt_addrof(sImData->sharedRequest), IMParameter::APDEnabled, virt_addrof(parameters->apdEnabled), nullptr, nullptr); if (result != IMError::OK) { goto out; } result = IM_GetParameter(handle, virt_addrof(sImData->sharedRequest), IMParameter::APDPeriod, virt_addrof(parameters->apdPeriod), nullptr, nullptr); if (result != IMError::OK) { goto out; } out: internal::imReleaseItbMutex(); IM_Close(handle); return result; } IMError IM_GetNvParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext) { std::memset(request.get(), 0, sizeof(IMRequest)); request->getNvParameterRequest.parameter = parameter; request->ioctlVecs[0].vaddr = virt_cast(virt_addrof(request->getNvParameterRequest)); request->ioctlVecs[0].len = 8u; request->ioctlVecs[1].vaddr = virt_cast(virt_addrof(request->getNvParameterResponse)); request->ioctlVecs[1].len = 8u; request->handle = handle; request->request = IMCommand::GetNvParameter; request->asyncCallback = asyncCallback; request->asyncCallbackContext = asyncCallbackContext; request->copySrc = virt_addrof(request->getNvParameterResponse.value); request->copyDst = output; request->copySize = 4u; return internal::imSendRequest(request, 1, 1); } IMError IM_GetNvParameterWithoutHandleAndItb(IMParameter parameter, virt_ptr outValue) { auto result = IM_Open(); if (result < 0) { return result; } auto handle = static_cast(result); internal::imAcquireItbMutex(); result = IM_GetNvParameter(handle, virt_addrof(sImData->sharedRequest), parameter, outValue, nullptr, nullptr); internal::imReleaseItbMutex(); IM_Close(handle); return result; } IMError IM_GetRuntimeParameter(IMParameter parameter, virt_ptr outValue) { auto result = IM_Open(); if (result < 0) { return result; } auto handle = static_cast(result); internal::imAcquireItbMutex(); result = IM_GetParameter(handle, virt_addrof(sImData->sharedRequest), parameter, outValue, nullptr, nullptr); internal::imReleaseItbMutex(); IM_Close(handle); return result; } IMError IM_GetTimerRemaining(IOSHandle handle, virt_ptr request, IMTimer timer, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext) { std::memset(request.get(), 0, sizeof(IMRequest)); request->getTimerRemainingRequest.timer = timer; request->ioctlVecs[0].vaddr = virt_cast(virt_addrof(request->getTimerRemainingRequest)); request->ioctlVecs[0].len = 8u; request->ioctlVecs[1].vaddr = virt_cast(virt_addrof(request->getTimerRemainingResponse)); request->ioctlVecs[1].len = static_cast(sizeof(IMGetTimerRemainingResponse)); request->handle = handle; request->request = IMCommand::GetTimerRemaining; request->asyncCallback = asyncCallback; request->asyncCallbackContext = asyncCallbackContext; request->copySrc = virt_addrof(request->getTimerRemainingResponse.value); request->copyDst = output; request->copySize = 4u; return internal::imSendRequest(request, 1, 1); } IMError IM_GetTimerRemainingSeconds(IMTimer timer, virt_ptr outSeconds) { auto result = IM_Open(); if (result < 0) { return result; } auto handle = static_cast(result); internal::imAcquireItbMutex(); result = IM_GetTimerRemaining(handle, virt_addrof(sImData->sharedRequest), timer, outSeconds, nullptr, nullptr); internal::imReleaseItbMutex(); IM_Close(handle); return result; } IMError IM_SetParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, uint32_t value, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext) { std::memset(request.get(), 0, sizeof(IMRequest)); request->setParameterRequest.parameter = parameter; request->setParameterRequest.value = value; request->ioctlVecs[0].vaddr = virt_cast(virt_addrof(request->setParameterRequest)); request->ioctlVecs[0].len = 8u; request->handle = handle; request->request = IMCommand::SetParameter; request->asyncCallback = asyncCallback; request->asyncCallbackContext = asyncCallbackContext; return internal::imSendRequest(request, 1, 0); } IMError IM_SetNvParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, uint32_t value, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext) { std::memset(request.get(), 0, sizeof(IMRequest)); request->setNvParameterRequest.parameter = parameter; request->setNvParameterRequest.value = value; request->ioctlVecs[0].vaddr = virt_cast(virt_addrof(request->setNvParameterRequest)); request->ioctlVecs[0].len = 8u; request->handle = handle; request->request = IMCommand::SetNvParameter; request->asyncCallback = asyncCallback; request->asyncCallbackContext = asyncCallbackContext; return internal::imSendRequest(request, 1, 0); } IMError IM_SetRuntimeParameter(IMParameter parameter, uint32_t value) { auto result = IM_Open(); if (result < 0) { return result; } auto handle = static_cast(result); internal::imAcquireItbMutex(); result = IM_SetParameter(handle, virt_addrof(sImData->sharedRequest), parameter, value, nullptr, nullptr); internal::imReleaseItbMutex(); IM_Close(handle); return result; } IMError IMDisableAPD() { return IM_SetRuntimeParameter(IMParameter::APDEnabled, FALSE); } IMError IMDisableDim() { auto result = IM_SetRuntimeParameter(IMParameter::DimEnabled, FALSE); if (result != IMError::OK) { return result; } return IM_SetRuntimeParameter(IMParameter::ResetEnable, FALSE); } IMError IMEnableAPD() { auto prevValue = StackObject { }; auto result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::APDEnabled, prevValue); if (result != IMError::OK) { return result; } if (*prevValue == TRUE) { return IMError::OK; } return IM_SetRuntimeParameter(IMParameter::APDEnabled, TRUE); } IMError IMEnableDim() { auto prevValue = StackObject { }; auto result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::DimEnabled, prevValue); if (result != IMError::OK) { return result; } if (*prevValue == TRUE) { return IMError::OK; } result = IM_SetRuntimeParameter(IMParameter::DimEnabled, TRUE); if (result != IMError::OK) { return result; } result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::ResetEnable, prevValue); if (result != IMError::OK) { return result; } if (*prevValue == TRUE) { return IMError::OK; } return IM_SetRuntimeParameter(IMParameter::ResetEnable, FALSE); } IMError IMIsAPDEnabled(virt_ptr outValue) { return IM_GetRuntimeParameter(IMParameter::APDEnabled, outValue); } IMError IMIsAPDEnabledBySysSettings(virt_ptr outValue) { return IM_GetNvParameterWithoutHandleAndItb(IMParameter::APDEnabled, outValue); } IMError IMIsDimEnabled(virt_ptr outValue) { return IM_GetRuntimeParameter(IMParameter::DimEnabled, outValue); } IMError IMGetAPDPeriod(virt_ptr outValue) { return IM_GetRuntimeParameter(IMParameter::APDPeriod, outValue); } IMError IMGetDimEnableDRC(virt_ptr outValue) { return IM_GetRuntimeParameter(IMParameter::DimEnableDrc, outValue); } IMError IMGetDimEnableTV(virt_ptr outValue) { return IM_GetRuntimeParameter(IMParameter::DimEnableTv, outValue); } IMError IMGetDimPeriod(virt_ptr outValue) { return IM_GetRuntimeParameter(IMParameter::DimPeriod, outValue); } IMError IMGetTimeBeforeAPD(virt_ptr outSeconds) { return IM_GetTimerRemainingSeconds(IMTimer::APD, outSeconds); } IMError IMGetTimeBeforeDimming(virt_ptr outSeconds) { return IM_GetTimerRemainingSeconds(IMTimer::Dim, outSeconds); } IMError IMSetDimEnableDRC(BOOL value) { return IM_SetRuntimeParameter(IMParameter::DimEnableTv, value); } IMError IMSetDimEnableTV(BOOL value) { return IM_SetRuntimeParameter(IMParameter::DimEnableTv, value); } IMError IMStartAPDVideoMode() { auto prevValue = StackObject { }; auto result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::APDPeriod, prevValue); if (result != IMError::OK) { return result; } if (*prevValue == 14400) { return IMError::OK; } return IM_SetRuntimeParameter(IMParameter::APDPeriod, 14400); } namespace internal { void initialiseIm() { sImData->itbMutexName = "itb_mutex"; OSInitMutexEx(virt_addrof(sImData->itbMutex), virt_addrof(sImData->itbMutexName)); } } // namespace internal void Library::registerImSymbols() { RegisterFunctionExport(IM_Open); RegisterFunctionExport(IM_Close); RegisterFunctionExport(IM_GetHomeButtonParams); RegisterFunctionExport(IM_GetParameter); RegisterFunctionExport(IM_GetParameters); RegisterFunctionExport(IM_GetNvParameter); RegisterFunctionExport(IM_GetNvParameterWithoutHandleAndItb); RegisterFunctionExport(IM_GetRuntimeParameter); RegisterFunctionExport(IM_GetTimerRemaining); RegisterFunctionExport(IM_GetTimerRemainingSeconds); RegisterFunctionExport(IM_SetParameter); RegisterFunctionExport(IM_SetRuntimeParameter); RegisterFunctionExport(IMDisableAPD); RegisterFunctionExport(IMDisableDim); RegisterFunctionExport(IMEnableAPD); RegisterFunctionExport(IMEnableDim); RegisterFunctionExport(IMIsAPDEnabled); RegisterFunctionExport(IMIsAPDEnabledBySysSettings); RegisterFunctionExport(IMIsDimEnabled); RegisterFunctionExport(IMGetAPDPeriod); RegisterFunctionExport(IMGetDimEnableDRC); RegisterFunctionExport(IMGetDimEnableTV); RegisterFunctionExport(IMGetDimPeriod); RegisterFunctionExport(IMGetTimeBeforeAPD); RegisterFunctionExport(IMGetTimeBeforeDimming); RegisterFunctionExport(IMSetDimEnableDRC); RegisterFunctionExport(IMSetDimEnableTV); RegisterFunctionExport(IMStartAPDVideoMode); RegisterDataInternal(sImData); RegisterFunctionInternal(internal::imIosAsyncCallback, sImIosAsyncCallback); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_im.h ================================================ #pragma once #include "coreinit_ios.h" #include "ios/auxil/ios_auxil_im.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_im IM * \ingroup coreinit * @{ */ using IMError = ios::Error; using ios::auxil::IMCommand; using ios::auxil::IMParameter; using ios::auxil::IMTimer; using ios::auxil::IMGetNvParameterRequest; using ios::auxil::IMGetNvParameterResponse; using ios::auxil::IMGetParameterRequest; using ios::auxil::IMGetParameterResponse; using ios::auxil::IMGetHomeButtonParamResponse; using ios::auxil::IMSetParameterRequest; using ios::auxil::IMSetNvParameterRequest; using ios::auxil::IMGetTimerRemainingRequest; using ios::auxil::IMGetTimerRemainingResponse; struct IMParameters { be2_val resetEnabled; be2_val dimEnabled; be2_val dimPeriod; be2_val apdEnabled; be2_val apdPeriod; }; CHECK_OFFSET(IMParameters, 0x00, resetEnabled); CHECK_OFFSET(IMParameters, 0x04, dimEnabled); CHECK_OFFSET(IMParameters, 0x08, dimPeriod); CHECK_OFFSET(IMParameters, 0x0C, apdEnabled); CHECK_OFFSET(IMParameters, 0x10, apdPeriod); struct IMRequest { union { be2_struct getNvParameterRequest; be2_struct getNvParameterResponse; be2_struct getParameterRequest; be2_struct getParameterResponse; be2_struct getHomeButtomParamResponse; be2_struct setParameterRequest; be2_struct setNvParameterRequest; be2_struct getTimerRemainingRequest; be2_struct getTimerRemainingResponse; be2_array args; }; be2_array ioctlVecs; be2_val handle; be2_val request; be2_val asyncCallback; be2_virt_ptr asyncCallbackContext; be2_virt_ptr copySrc; be2_virt_ptr copyDst; be2_val copySize; }; CHECK_OFFSET(IMRequest, 0x80, ioctlVecs); CHECK_OFFSET(IMRequest, 0x98, handle); CHECK_OFFSET(IMRequest, 0x9C, request); CHECK_OFFSET(IMRequest, 0xA0, asyncCallback); CHECK_OFFSET(IMRequest, 0xA4, asyncCallbackContext); CHECK_OFFSET(IMRequest, 0xA8, copySrc); CHECK_OFFSET(IMRequest, 0xAC, copyDst); CHECK_OFFSET(IMRequest, 0xB0, copySize); IMError IM_Open(); IMError IM_Close(IOSHandle handle); IMError IM_GetHomeButtonParams(IOSHandle handle, virt_ptr request, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext); IMError IM_GetParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext); IMError IM_GetParameters(virt_ptr parameters); IMError IM_GetNvParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext); IMError IM_GetNvParameterWithoutHandleAndItb(IMParameter parameter, virt_ptr outValue); IMError IM_GetRuntimeParameter(IMParameter parameter, virt_ptr outValue); IMError IM_GetTimerRemaining(IOSHandle handle, virt_ptr request, IMTimer timer, virt_ptr output, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext); IMError IM_GetTimerRemainingSeconds(IMTimer timer, virt_ptr outSeconds); IMError IM_SetParameter(IOSHandle handle, virt_ptr request, IMParameter parameter, uint32_t value, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncCallbackContext); IMError IM_SetRuntimeParameter(IMParameter parameter, uint32_t value); IMError IMDisableAPD(); IMError IMDisableDim(); IMError IMEnableAPD(); IMError IMEnableDim(); IMError IMIsAPDEnabled(virt_ptr outValue); IMError IMIsAPDEnabledBySysSettings(virt_ptr outValue); IMError IMIsDimEnabled(virt_ptr outValue); IMError IMGetDimEnableDRC(virt_ptr outValue); IMError IMGetDimEnableTV(virt_ptr outValue); IMError IMGetDimPeriod(virt_ptr outValue); IMError IMGetTimeBeforeAPD(virt_ptr outSeconds); IMError IMGetTimeBeforeDimming(virt_ptr outSeconds); IMError IMSetDimEnableDRC(BOOL value); IMError IMSetDimEnableTV(BOOL value); IMError IMStartAPDVideoMode(); namespace internal { void initialiseIm(); } // namespace interal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_internal_idlock.cpp ================================================ #include "coreinit_internal_idlock.h" #include namespace cafe::coreinit::internal { static uint32_t getCoreLockId() { auto id = cpu::this_core::id(); auto core = 1u << id; if (id == cpu::InvalidCoreId) { core = 1u << 31; } return core; } bool acquireIdLock(IdLock &lock, uint32_t id) { auto expected = 0u; if (id == 0) { return false; } while (!lock.owner.compare_exchange_weak(expected, id, std::memory_order_acquire)) { expected = 0; } return true; } bool acquireIdLock(IdLock &lock, virt_ptr owner) { return acquireIdLock(lock, static_cast(virt_cast(owner))); } bool acquireIdLockWithCoreId(IdLock &lock) { return acquireIdLock(lock, getCoreLockId()); } bool releaseIdLock(IdLock &lock, uint32_t id) { auto owner = lock.owner.exchange(0, std::memory_order_release); return (owner == id); } bool releaseIdLock(IdLock &lock, virt_ptr owner) { return releaseIdLock(lock, static_cast(virt_cast(owner))); } bool releaseIdLockWithCoreId(IdLock &lock) { return releaseIdLock(lock, getCoreLockId()); } bool isHoldingIdLock(IdLock &lock, uint32_t id) { return lock.owner.load(std::memory_order_acquire) == id; } bool isHoldingIdLock(IdLock &lock, virt_ptr owner) { return isHoldingIdLock(lock, static_cast(virt_cast(owner))); } bool isHoldingIdLockWithCoreId(IdLock &lock) { return isHoldingIdLock(lock, getCoreLockId()); } bool isLockHeldBySomeone(IdLock &lock) { return lock.owner.load(std::memory_order_acquire) != 0; } } // namespace namespace cafe::coreinit::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_internal_idlock.h ================================================ #pragma once #include #include namespace cafe::coreinit::internal { struct IdLock { std::atomic owner; }; bool acquireIdLock(IdLock &lock, uint32_t id); bool acquireIdLock(IdLock &lock, virt_ptr owner); bool acquireIdLockWithCoreId(IdLock &lock); bool releaseIdLock(IdLock &lock, uint32_t id); bool releaseIdLock(IdLock &lock, virt_ptr owner); bool releaseIdLockWithCoreId(IdLock &lock); bool isHoldingIdLock(IdLock &lock, uint32_t id); bool isHoldingIdLock(IdLock &lock, virt_ptr owner); bool isHoldingIdLockWithCoreId(IdLock &lock); bool isLockHeldBySomeone(IdLock &lock); } // namespace namespace cafe::coreinit::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_internal_queue.h ================================================ #pragma once #include #include namespace cafe::coreinit::internal { template ItemType::*LinkField> class Queue { protected: static constexpr LinkType & link(virt_ptr item) { return (item.get()->*LinkField); } public: static inline void init(virt_ptr queue) { queue->head = nullptr; queue->tail = nullptr; } static inline void initLink(virt_ptr item) { link(item).prev = nullptr; link(item).next = nullptr; } static inline bool empty(virt_ptr queue) { return queue->head == nullptr; } static inline void clear(virt_ptr queue) { for (auto item = queue->head; item; ) { auto next = link(item).next; link(item).next = nullptr; link(item).prev = nullptr; item = next; } queue->head = nullptr; queue->tail = nullptr; } static inline bool contains(virt_ptr queue, virt_ptr item) { for (auto itemIter = queue->head; itemIter != nullptr; itemIter = link(itemIter).next) { if (itemIter == item) { return true; } } return false; } static inline void append(virt_ptr queue, virt_ptr item) { decaf_check(link(item).next == nullptr); decaf_check(link(item).prev == nullptr); if (!queue->tail) { decaf_check(!queue->head); queue->head = item; queue->tail = item; link(item).next = nullptr; link(item).prev = nullptr; } else { link(item).prev = queue->tail; link(item).next = nullptr; link(queue->tail).next = item; queue->tail = item; } } static inline void erase(virt_ptr queue, virt_ptr item) { if (queue->head == item) { // Erase from head queue->head = link(item).next; if (queue->head) { link(queue->head).prev = nullptr; } else { queue->tail = nullptr; } } else if (queue->tail == item) { // Erase from tail queue->tail = link(item).prev; if (queue->tail) { link(queue->tail).next = nullptr; } } else { // Erase from middle auto prev = link(item).prev; auto next = link(item).next; if (prev && next) { link(prev).next = next; link(next).prev = prev; } } link(item).next = nullptr; link(item).prev = nullptr; } static inline virt_ptr popFront(virt_ptr queue) { auto result = queue->head; if (result) { queue->head = link(result).next; if (queue->head) { link(queue->head).prev = nullptr; } } if (result == queue->tail) { queue->tail = nullptr; } if (result) { link(result).next = nullptr; link(result).prev = nullptr; } return result; } }; template ItemType::*LinkField, typename IsLess> class SortedQueue : public Queue { private: // Hide append as it is not valid here using Queue::append; using Queue::link; public: static void inline insert(virt_ptr queue, virt_ptr item) { decaf_check(link(item).next == nullptr); decaf_check(link(item).prev == nullptr); if (!queue->head) { // Insert only item queue->head = item; queue->tail = item; } else { virt_ptr insertBefore = nullptr; // Find insert location based on sort function for (insertBefore = queue->head; insertBefore; insertBefore = link(insertBefore).next) { if (!IsLess {}(insertBefore, item)) { break; } } if (!insertBefore) { // Insert at tail link(queue->tail).next = item; link(item).next = nullptr; link(item).prev = queue->tail; queue->tail = item; } else { // Insert in head or middle link(item).next = insertBefore; link(item).prev = link(insertBefore).prev; if (link(insertBefore).prev) { link(link(insertBefore).prev).next = item; } link(insertBefore).prev = item; if (queue->head == insertBefore) { queue->head = item; } } } } }; } // namespace cafe::coreinit::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_interrupts.cpp ================================================ #include "coreinit.h" #include "coreinit_context.h" #include "coreinit_interrupts.h" #include "coreinit_scheduler.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/kernel/cafe_kernel_exception.h" #include "cafe/kernel/cafe_kernel_interrupts.h" #include namespace cafe::coreinit { struct StaticInterruptsData { be2_array registeredHandlers; }; static virt_ptr sInterruptsData = nullptr; /** * Enable interrupts on current core. * * \return Returns TRUE if interrupts were previously enabled, FALSE otherwise. */ BOOL OSEnableInterrupts() { return cpu::this_core::setInterruptMask(cpu::INTERRUPT_MASK) == cpu::INTERRUPT_MASK; } /** * Disable interrupts on current core. * * \return Returns TRUE if interrupts were previously enabled, FALSE otherwise. */ BOOL OSDisableInterrupts() { // We allow BreakpointException here so that the debugger can still trace through // OSDisableInterrupts calls. This is not an issue only because internally we // only care about the scheduler lock which is only used internally. return cpu::this_core::setInterruptMask(cpu::DBGBREAK_INTERRUPT) == cpu::INTERRUPT_MASK; } /** * Sets if interrupts are enabled for current core. * * \return Returns TRUE if interrupts were previously enabled, FALSE otherwise. */ BOOL OSRestoreInterrupts(BOOL enable) { if (enable) { return OSEnableInterrupts(); } else { return OSDisableInterrupts(); } } /** * Check whether interrupts are enabled for current core. * * \return Returns TRUE if interrupts are enabled on current core. */ BOOL OSIsInterruptEnabled() { return cpu::this_core::interruptMask() == cpu::INTERRUPT_MASK; } static void userInterruptHandler(OSInterruptType type, virt_ptr interruptedContext, virt_ptr userData) { auto userHandler = virt_func_cast(virt_cast(userData)); if (userHandler) { internal::disableScheduler(); cafe::invoke(cpu::this_core::state(), userHandler, type, interruptedContext); internal::enableScheduler(); } } OSUserInterruptHandler OSSetInterruptHandler(OSInterruptType type, OSUserInterruptHandler handler) { auto previous = OSUserInterruptHandler { nullptr }; if (type < OSInterruptType::Max) { previous = sInterruptsData->registeredHandlers[type]; sInterruptsData->registeredHandlers[type] = handler; } kernel::setUserModeInterruptHandler(type, &userInterruptHandler, virt_cast(virt_func_cast(handler))); return previous; } void OSClearAndEnableInterrupt(OSInterruptType type) { kernel::clearAndEnableInterrupt(type); } void OSDisableInterrupt(OSInterruptType type) { kernel::disableInterrupt(type); } namespace internal { static void userModeIciCallback(kernel::ExceptionType type, virt_ptr interruptedContext) { lockScheduler(); rescheduleSelfNoLock(); unlockScheduler(); } void initialiseIci() { kernel::setUserModeExceptionHandler(kernel::ExceptionType::ICI, userModeIciCallback); } } // namespace internal void Library::registerInterruptSymbols() { RegisterFunctionExport(OSEnableInterrupts); RegisterFunctionExport(OSDisableInterrupts); RegisterFunctionExport(OSRestoreInterrupts); RegisterFunctionExport(OSIsInterruptEnabled); RegisterFunctionExportName("__OSSetInterruptHandler", OSSetInterruptHandler); RegisterFunctionExportName("__OSClearAndEnableInterrupt", OSClearAndEnableInterrupt); RegisterFunctionExportName("__OSDisableInterrupt", OSDisableInterrupt); RegisterDataInternal(sInterruptsData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_interrupts.h ================================================ #pragma once #include "coreinit_context.h" #include "cafe/kernel/cafe_kernel_interrupts.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_interrupts Interrupts * \ingroup coreinit * @{ */ using OSInterruptType = kernel::InterruptType; using OSUserInterruptHandler = virt_func_ptr< void (OSInterruptType type, virt_ptr interruptedContext) >; BOOL OSEnableInterrupts(); BOOL OSDisableInterrupts(); BOOL OSRestoreInterrupts(BOOL enable); BOOL OSIsInterruptEnabled(); OSUserInterruptHandler OSSetInterruptHandler(OSInterruptType type, OSUserInterruptHandler handler); void OSClearAndEnableInterrupt(OSInterruptType type); void OSDisableInterrupt(OSInterruptType type); namespace internal { void initialiseIci(); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ios.cpp ================================================ #include "coreinit.h" #include "coreinit_ios.h" #include "coreinit_ipcdriver.h" #include "coreinit_thread.h" namespace cafe::coreinit { namespace internal { static IOSError ipcPrepareOpenRequest(virt_ptr ipcDriver, virt_ptr ipcRequest, virt_ptr device, int mode); static IOSError ipcPrepareIoctlRequest(virt_ptr ipcDriver, virt_ptr ipcRequest, uint32_t ioctlRequest, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen); static IOSError ipcPrepareIoctlvRequest(virt_ptr ipcDriver, virt_ptr ipcRequest, uint32_t ioctlvRequest, uint32_t vecIn, uint32_t vecOut, virt_ptr vec); } // namespace internal /** * Sends an IOS Open command over IPC and waits for the response. * * \return * Returns an IOSHandle (when the result is > 0) or an IOSError code otherwise. */ IOSError IOS_Open(virt_ptr device, IOSOpenMode mode) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, 0, IOSCommand::Open, 0, nullptr, nullptr); if (error < IOSError::OK) { goto fail; } error = internal::ipcPrepareOpenRequest(ipcDriver, ipcRequest, device, mode); if (error < IOSError::OK) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error < IOSError::OK) { goto fail; } error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest); ipcRequest = nullptr; if (error < IOSError::OK) { goto fail; } ipcDriver->iosOpenRequestSuccess++; internal::unpinThreadAffinity(affinity); return error; fail: ipcDriver->iosOpenRequestFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Open command over IPC and calls callback with the result. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_OpenAsync(virt_ptr device, IOSOpenMode mode, IOSAsyncCallbackFn callback, virt_ptr context) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, 0, IOSCommand::Open, 0, callback, context); if (error < IOSError::OK) { goto fail; } error = internal::ipcPrepareOpenRequest(ipcDriver, ipcRequest, device, mode); if (error < IOSError::OK) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error < IOSError::OK) { goto fail; } ipcDriver->iosOpenAsyncRequestSubmitSuccess++; internal::unpinThreadAffinity(affinity); return error; fail: ipcDriver->iosOpenAsyncRequestSubmitFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Close command over IPC and waits for the reply. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_Close(IOSHandle handle) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, handle, IOSCommand::Close, 0, nullptr, nullptr); if (error) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error) { goto fail; } error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest); ipcRequest = nullptr; if (error) { goto fail; } ipcDriver->iosCloseRequestSuccess++; internal::unpinThreadAffinity(affinity); return IOSError::OK; fail: ipcDriver->iosCloseRequestFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Close command over IPC and calls callback with the result. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_CloseAsync(IOSHandle handle, IOSAsyncCallbackFn callback, virt_ptr context) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, handle, IOSCommand::Close, 0, callback, context); if (error) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error) { goto fail; } ipcDriver->iosCloseAsyncRequestSubmitSuccess++; internal::unpinThreadAffinity(affinity); return IOSError::OK; fail: ipcDriver->iosCloseAsyncRequestSubmitFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Ioctl command over IPC and waits for the reply. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_Ioctl(IOSHandle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, handle, IOSCommand::Ioctl, 0, nullptr, nullptr); if (error) { goto fail; } error = internal::ipcPrepareIoctlRequest(ipcDriver, ipcRequest, request, inBuf, inLen, outBuf, outLen); if (error) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error) { goto fail; } error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest); ipcRequest = nullptr; if (error) { goto fail; } ipcDriver->iosIoctlRequestSuccess++; internal::unpinThreadAffinity(affinity); return IOSError::OK; fail: ipcDriver->iosIoctlRequestFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Ioctl command over IPC and calls callback with the result. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_IoctlAsync(IOSHandle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen, IOSAsyncCallbackFn callback, virt_ptr context) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, handle, IOSCommand::Ioctl, 0, callback, context); if (error) { goto fail; } error = internal::ipcPrepareIoctlRequest(ipcDriver, ipcRequest, request, inBuf, inLen, outBuf, outLen); if (error) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error) { goto fail; } ipcDriver->iosIoctlAsyncRequestSubmitSuccess++; internal::unpinThreadAffinity(affinity); return IOSError::OK; fail: ipcDriver->iosIoctlAsyncRequestSubmitFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Ioctlv command over IPC and waits for the reply. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_Ioctlv(IOSHandle handle, uint32_t request, uint32_t vecIn, uint32_t vecOut, virt_ptr vec) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, handle, IOSCommand::Ioctlv, 0, nullptr, nullptr); if (error) { goto fail; } error = internal::ipcPrepareIoctlvRequest(ipcDriver, ipcRequest, request, vecIn, vecOut, vec); if (error) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error) { goto fail; } error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest); ipcRequest = nullptr; if (error) { goto fail; } ipcDriver->iosIoctlvRequestSuccess++; internal::unpinThreadAffinity(affinity); return IOSError::OK; fail: ipcDriver->iosIoctlvRequestFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } /** * Sends an IOS Ioctlv command over IPC and calls callback with the result. * * \return * Returns IOSError::OK on success or an IOSError code otherwise. */ IOSError IOS_IoctlvAsync(IOSHandle handle, uint32_t request, uint32_t vecIn, uint32_t vecOut, virt_ptr vec, IOSAsyncCallbackFn callback, virt_ptr context) { virt_ptr ipcRequest = nullptr; auto ipcDriver = internal::getIPCDriver(); auto affinity = internal::pinThreadAffinity(); auto error = IOSError::OK; error = internal::ipcDriverAllocateRequest(ipcDriver, &ipcRequest, handle, IOSCommand::Ioctlv, 0, callback, context); if (error) { goto fail; } error = internal::ipcPrepareIoctlvRequest(ipcDriver, ipcRequest, request, vecIn, vecOut, vec); if (error) { goto fail; } error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest); if (error) { goto fail; } ipcDriver->iosIoctlvAsyncRequestSubmitSuccess++; internal::unpinThreadAffinity(affinity); return IOSError::OK; fail: ipcDriver->iosIoctlvAsyncRequestSubmitFail++; if (ipcRequest) { internal::ipcDriverFreeRequest(ipcDriver, ipcRequest); } internal::unpinThreadAffinity(affinity); return error; } namespace internal { /** * Prepares an IPCDriverRequest structure with the parameters for IOS_Open. * * \retval IOSError::Max * The name of the device is too long. * * \retval IOSError::OK * Success. */ IOSError ipcPrepareOpenRequest(virt_ptr ipcDriver, virt_ptr ipcRequest, virt_ptr device, int mode) { auto ipcBuffer = ipcRequest->ipcBuffer; auto deviceLen = strlen(device.get()); if (deviceLen >= 0x20) { return IOSError::Max; } ipcBuffer->nameBuffer.fill(0); std::memcpy(virt_addrof(ipcBuffer->nameBuffer).get(), device.get(), deviceLen); ipcBuffer->request.args.open.name = nullptr; ipcBuffer->request.args.open.nameLen = static_cast(deviceLen + 1); ipcBuffer->request.args.open.mode = static_cast(mode); ipcBuffer->buffer1 = virt_addrof(ipcBuffer->nameBuffer); return IOSError::OK; } /** * Prepares an IPCDriverRequest structure with the parameters for IOS_Ioctl. * * \retval IOSError::OK * Success. */ IOSError ipcPrepareIoctlRequest(virt_ptr ipcDriver, virt_ptr ipcRequest, uint32_t ioctlRequest, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen) { auto ipcBuffer = ipcRequest->ipcBuffer; ipcBuffer->request.args.ioctl.request = ioctlRequest; ipcBuffer->request.args.ioctl.inputBuffer = nullptr; ipcBuffer->request.args.ioctl.inputLength = inLen; ipcBuffer->request.args.ioctl.outputBuffer = nullptr; ipcBuffer->request.args.ioctl.outputLength = outLen; ipcBuffer->buffer1 = inBuf; ipcBuffer->buffer2 = outBuf; return IOSError::OK; } /** * Prepares an IPCDriverRequest structure with the parameters for IOS_Ioctlv. * * \retval IOSError::InvalidArg * One of the IOSVec structures has a NULL physical address. * * \retval IOSError::OK * Success. */ IOSError ipcPrepareIoctlvRequest(virt_ptr ipcDriver, virt_ptr ipcRequest, uint32_t ioctlvRequest, uint32_t vecIn, uint32_t vecOut, virt_ptr vec) { auto ipcBuffer = ipcRequest->ipcBuffer; ipcBuffer->request.args.ioctlv.request = ioctlvRequest; ipcBuffer->request.args.ioctlv.numVecIn = vecIn; ipcBuffer->request.args.ioctlv.numVecOut = vecOut; ipcBuffer->request.args.ioctlv.vecs = nullptr; ipcBuffer->buffer1 = vec; for (auto i = 0u; i < vecIn + vecOut; ++i) { if (!vec[i].vaddr && vec[i].len) { return IOSError::InvalidArg; } } return IOSError::OK; } } // namespace internal void Library::registerIosSymbols() { RegisterFunctionExport(IOS_Open); RegisterFunctionExport(IOS_OpenAsync); RegisterFunctionExport(IOS_Close); RegisterFunctionExport(IOS_CloseAsync); RegisterFunctionExport(IOS_Ioctl); RegisterFunctionExport(IOS_IoctlAsync); RegisterFunctionExport(IOS_Ioctlv); RegisterFunctionExport(IOS_IoctlvAsync); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ios.h ================================================ #pragma once #include "coreinit_enum.h" #include "ios/ios_ipc.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_ios IOS * \ingroup coreinit * @{ */ /* Unimplemented IOS functions: IOS_OpenAsyncEx IOS_CloseAsyncEx IOS_IoctlAsyncEx IOS_IoctlvAsyncEx IOS_Read IOS_ReadAsync IOS_ReadAsyncEx IOS_Seek IOS_SeekAsync IOS_SeekAsyncEx IOS_Write IOS_WriteAsync IOS_WriteAsyncEx */ #define IOS_FAILED(error) (error < ios::Error::OK) #define IOS_SUCCESS(error) (error >= ios::Error::OK) using IOSCommand = ios::Command; using IOSError = ios::Error; using IOSErrorCategory = ios::ErrorCategory; using IOSHandle = int32_t; using IOSOpenMode = ios::OpenMode; using IOSVec = ios::IoctlVec; using IOSAsyncCallbackFn = virt_func_ptr context)>; static constexpr uint32_t IOSVecAlign = ios::IoctlVecAlign; IOSError IOS_Open(virt_ptr device, IOSOpenMode mode); IOSError IOS_OpenAsync(virt_ptr device, IOSOpenMode mode, IOSAsyncCallbackFn callback, virt_ptr context); IOSError IOS_Close(IOSHandle handle); IOSError IOS_CloseAsync(IOSHandle handle, IOSAsyncCallbackFn callback, virt_ptr context); IOSError IOS_Ioctl(IOSHandle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen); IOSError IOS_IoctlAsync(IOSHandle handle, uint32_t request, virt_ptr inBuf, uint32_t inLen, virt_ptr outBuf, uint32_t outLen, IOSAsyncCallbackFn callback, virt_ptr context); IOSError IOS_Ioctlv(IOSHandle handle, uint32_t request, uint32_t vecIn, uint32_t vecOut, virt_ptr vec); IOSError IOS_IoctlvAsync(IOSHandle handle, uint32_t request, uint32_t vecIn, uint32_t vecOut, virt_ptr vec, IOSAsyncCallbackFn callback, virt_ptr context); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcbufpool.cpp ================================================ #include "coreinit.h" #include "coreinit_ios.h" #include "coreinit_ipcbufpool.h" #include "coreinit_mutex.h" #include namespace cafe::coreinit { constexpr uint32_t IPCBufPool::MagicHeader; namespace internal { static void ipcBufPoolFifoInit(virt_ptr fifo, int32_t numMessages, virt_ptr> messages); static IOSError ipcBufPoolFifoPush(virt_ptr fifo, virt_ptr message); static IOSError ipcBufPoolFifoPop(virt_ptr fifo, virt_ptr *outMessage); static int32_t ipcBufPoolGetMessageIndex(virt_ptr pool, virt_ptr message); } // namespace internal /** * Create a IPCBufPool structure from buffer. */ virt_ptr IPCBufPoolCreate(virt_ptr buffer, uint32_t size, uint32_t messageSize, virt_ptr outNumMessages, uint32_t unk0x0c) { if (!buffer || size == 0 || messageSize == 0) { return nullptr; } std::memset(buffer.get(), 0, size); // IPC messages should be 64 byte aligned messageSize = align_up(messageSize, 64); auto alignedBuffer = align_up(buffer, 64); auto pool = virt_cast(alignedBuffer); pool->magic = IPCBufPool::MagicHeader; pool->buffer = buffer; pool->size = size; pool->unk0x0C = unk0x0c; pool->unk0x10 = 0u; pool->messageSize0x14 = messageSize; pool->messageSize0x18 = messageSize; OSInitMutexEx(virt_addrof(pool->mutex), nullptr); auto numMessages = static_cast((size - sizeof(IPCBufPool)) / messageSize); if (numMessages <= 1) { return nullptr; } auto messageIndexSize = static_cast(numMessages * sizeof(be2_virt_ptr)); numMessages -= 1 + (messageIndexSize / messageSize); *outNumMessages = numMessages; pool->messageCount = numMessages; auto messageIndex = virt_cast *>(virt_cast(alignedBuffer) + sizeof(IPCBufPool)); auto messages = virt_cast(messageIndex) + messageIndexSize; pool->messages = messages; pool->messageIndexSize = messageIndexSize; // Initialise FIFO. internal::ipcBufPoolFifoInit(virt_addrof(pool->fifo), static_cast(numMessages), messageIndex); for (auto i = 0u; i < numMessages; ++i) { internal::ipcBufPoolFifoPush(virt_addrof(pool->fifo), messages + i * messageSize); } return pool; } /** * Allocate a message from an IPCBufPool. */ virt_ptr IPCBufPoolAllocate(virt_ptr pool, uint32_t size) { if (pool->magic != IPCBufPool::MagicHeader) { return nullptr; } if (size > pool->messageSize0x14) { return nullptr; } auto message = virt_ptr { nullptr }; OSLockMutex(virt_addrof(pool->mutex)); internal::ipcBufPoolFifoPop(virt_addrof(pool->fifo), &message); OSUnlockMutex(virt_addrof(pool->mutex)); return message; } /** * Free a message back to a IPCBufPool. */ IOSError IPCBufPoolFree(virt_ptr pool, virt_ptr message) { auto error = IOSError::OK; if (pool->magic != IPCBufPool::MagicHeader) { return IOSError::Invalid; } OSLockMutex(virt_addrof(pool->mutex)); auto index = internal::ipcBufPoolGetMessageIndex(pool, message); if (index >= 0) { auto messages = virt_cast(pool->messages); auto ipcMessage = messages + index * pool->messageSize0x18; internal::ipcBufPoolFifoPush(virt_addrof(pool->fifo), ipcMessage); } else { error = IOSError::Invalid; } OSUnlockMutex(virt_addrof(pool->mutex)); return error; } /** * Get some information about an IPCBufPool object. */ IOSError IPCBufPoolGetAttributes(virt_ptr pool, virt_ptr attribs) { if (pool->magic != IPCBufPool::MagicHeader) { return IOSError::Invalid; } OSLockMutex(virt_addrof(pool->mutex)); attribs->messageSize = pool->messageSize0x14; attribs->poolSize = pool->messageCount; attribs->numMessages = pool->fifo.count; OSUnlockMutex(virt_addrof(pool->mutex)); return IOSError::OK; } namespace internal { /** * Initialise an IPCBufPoolFIFO structure. */ void ipcBufPoolFifoInit(virt_ptr fifo, int32_t numMessages, virt_ptr> messages) { fifo->pushIndex = 0; fifo->popIndex = -1; fifo->count = 0; fifo->maxCount = numMessages; fifo->messages = messages; } /** * Push a message into a IPCBufPoolFIFO structure. */ IOSError ipcBufPoolFifoPush(virt_ptr fifo, virt_ptr message) { if (fifo->pushIndex == fifo->popIndex) { return IOSError::QFull; } fifo->messages[fifo->pushIndex] = message; if (fifo->popIndex == -1) { fifo->popIndex = fifo->pushIndex; } fifo->count += 1; fifo->pushIndex = (fifo->pushIndex + 1) % fifo->maxCount; return IOSError::OK; } /** * Pop a message from a IPCBufPoolFIFO structure. */ IOSError ipcBufPoolFifoPop(virt_ptr fifo, virt_ptr *outMessage) { if (fifo->popIndex == -1) { return IOSError::QEmpty; } auto message = fifo->messages[fifo->popIndex]; fifo->count -= 1; if (fifo->count == 0) { fifo->popIndex = -1; } else { fifo->popIndex = (fifo->popIndex + 1) % fifo->maxCount; } *outMessage = message; return IOSError::OK; } /** * Get the index of a message in an IPCBufPool by it's pointer. */ int32_t ipcBufPoolGetMessageIndex(virt_ptr pool, virt_ptr message) { if (message < pool->messages) { return -1; } auto offset = virt_cast(message) - virt_cast(pool->messages); auto index = offset / pool->messageSize0x18; if (index > pool->messageCount) { return -1; } return static_cast(index); } } // namespace internal void Library::registerIpcBufPoolSymbols() { RegisterFunctionExport(IPCBufPoolCreate); RegisterFunctionExport(IPCBufPoolAllocate); RegisterFunctionExport(IPCBufPoolFree); RegisterFunctionExport(IPCBufPoolGetAttributes); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcbufpool.h ================================================ #pragma once #include "coreinit_ios.h" #include "coreinit_mutex.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_ipcbufpool IPC Buffer Pool * \ingroup coreinit * @{ */ #pragma pack(push, 1) /** * FIFO queue for IPCBufPool. * * Functions similar to a ring buffer. */ struct IPCBufPoolFIFO { //! The current message index to push to. be2_val pushIndex; //! The current message index to pop from. be2_val popIndex; //! The number of messages in the queue. be2_val count; //! Tracks the total number of messages in the count. be2_val maxCount; //! Messages in the queue. be2_virt_ptr> messages; }; CHECK_OFFSET(IPCBufPoolFIFO, 0x00, pushIndex); CHECK_OFFSET(IPCBufPoolFIFO, 0x04, popIndex); CHECK_OFFSET(IPCBufPoolFIFO, 0x08, count); CHECK_OFFSET(IPCBufPoolFIFO, 0x0C, maxCount); CHECK_OFFSET(IPCBufPoolFIFO, 0x10, messages); CHECK_SIZE(IPCBufPoolFIFO, 0x14); /** * Attributes returned by IPCBufPoolGetAttributes. */ struct IPCBufPoolAttributes { //! Size of a message in the buffer pool. be2_val messageSize; //! Size of the buffer pool. be2_val poolSize; //! Number of pending messages in the pool fifo. be2_val numMessages; }; CHECK_OFFSET(IPCBufPoolAttributes, 0x00, messageSize); CHECK_OFFSET(IPCBufPoolAttributes, 0x04, poolSize); CHECK_OFFSET(IPCBufPoolAttributes, 0x08, numMessages); CHECK_SIZE(IPCBufPoolAttributes, 0x0C); /** * A simple message buffer pool used for IPC communication. */ struct IPCBufPool { static constexpr uint32_t MagicHeader = 0x0BADF00Du; //! Magic header always set to IPCBufPool::MagicHeader. be2_val magic; //! Pointer to buffer used for this IPCBufPool. be2_virt_ptr buffer; //! Size of buffer. be2_val size; be2_val unk0x0C; be2_val unk0x10; //! Message size from IPCBufPoolCreate. be2_val messageSize0x14; //! Message size from IPCBufPoolCreate. be2_val messageSize0x18; //! Number of messages in the IPCBufPoolFIFO. be2_val messageCount; //! Pointer to start of messages. be2_virt_ptr messages; //! Number of bytes used for the message pointers in IPCBufPoolFIFO. be2_val messageIndexSize; //! FIFO queue of messages. be2_struct fifo; //! Mutex used to secure access to fifo. be2_struct mutex; UNKNOWN(0x4); }; CHECK_OFFSET(IPCBufPool, 0x00, magic); CHECK_OFFSET(IPCBufPool, 0x04, buffer); CHECK_OFFSET(IPCBufPool, 0x08, size); CHECK_OFFSET(IPCBufPool, 0x0C, unk0x0C); CHECK_OFFSET(IPCBufPool, 0x10, unk0x10); CHECK_OFFSET(IPCBufPool, 0x14, messageSize0x14); CHECK_OFFSET(IPCBufPool, 0x18, messageSize0x18); CHECK_OFFSET(IPCBufPool, 0x1C, messageCount); CHECK_OFFSET(IPCBufPool, 0x20, messages); CHECK_OFFSET(IPCBufPool, 0x24, messageIndexSize); CHECK_OFFSET(IPCBufPool, 0x28, fifo); CHECK_OFFSET(IPCBufPool, 0x3C, mutex); CHECK_SIZE(IPCBufPool, 0x6C); #pragma pack(pop) virt_ptr IPCBufPoolCreate(virt_ptr buffer, uint32_t size, uint32_t messageSize, virt_ptr outNumMessages, uint32_t unk0x0c); virt_ptr IPCBufPoolAllocate(virt_ptr pool, uint32_t size); IOSError IPCBufPoolFree(virt_ptr pool, virt_ptr message); IOSError IPCBufPoolGetAttributes(virt_ptr pool, virt_ptr attribs); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcdriver.cpp ================================================ #include "coreinit.h" #include "coreinit_core.h" #include "coreinit_ipcdriver.h" #include "coreinit_messagequeue.h" #include "coreinit_scheduler.h" #include "coreinit_thread.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" namespace cafe::coreinit { struct StaticIpcDriverData { struct PerCoreData { be2_struct driver; be2_struct queue; be2_array messages; be2_array ipcBuffers; be2_struct thread; be2_array threadName; be2_array threadStack; }; be2_array perCoreData; be2_array submitEventName = "{ IPC Synchronous }"; }; static virt_ptr sIpcDriverData = nullptr; static OSThreadEntryPointFn sIpcDriverThreadEntry = nullptr; namespace internal { void ipcDriverProcessReplies(kernel::InterruptType type, virt_ptr interruptedContext); } // namespace internal /** * Initialise the IPC driver. */ void IPCDriverInit() { auto coreId = OSGetCoreId(); auto &perCoreData = sIpcDriverData->perCoreData[coreId]; auto driver = virt_addrof(perCoreData.driver); OSInitEvent(virt_addrof(driver->waitFreeFifoEvent), FALSE, OSEventMode::AutoReset); driver->status = IPCDriverStatus::Initialised; driver->coreId = coreId; driver->ipcBuffers = virt_addrof(perCoreData.ipcBuffers); OSInitMessageQueue(virt_addrof(perCoreData.queue), virt_addrof(perCoreData.messages), static_cast(perCoreData.messages.size())); auto thread = virt_addrof(perCoreData.thread); auto stack = virt_addrof(perCoreData.threadStack); auto stackSize = perCoreData.threadStack.size(); coreinit__OSCreateThreadType(thread, sIpcDriverThreadEntry, driver->coreId, nullptr, virt_cast(stack + stackSize), static_cast(stackSize), 15, static_cast(1 << driver->coreId), OSThreadType::Driver); perCoreData.threadName = fmt::format("IPC Core {}", coreId); OSSetThreadName(thread, virt_addrof(perCoreData.threadName)); OSResumeThread(thread); } /** * Open the IPC driver. * * \retval IOSError::OK * Success. * * \retval IOSError::NotReady * The IPC driver status must be Closed or Initialised. */ IOSError IPCDriverOpen() { auto driver = internal::getIPCDriver(); // Verify driver state if (driver->status != IPCDriverStatus::Closed && driver->status != IPCDriverStatus::Initialised) { return IOSError::NotReady; } // Initialise requests for (auto i = 0u; i < IPCBufferCount; ++i) { auto &request = driver->requests[i]; request.ipcBuffer = virt_addrof(driver->ipcBuffers[i]); request.asyncCallback = nullptr; request.asyncContext = nullptr; } driver->initialisedRequests = TRUE; // Initialise FIFO internal::ipcDriverFifoInit(virt_addrof(driver->freeFifo)); internal::ipcDriverFifoInit(virt_addrof(driver->outboundFifo)); // Push all items into free queue for (auto i = 0u; i < IPCBufferCount; ++i) { internal::ipcDriverFifoPush(virt_addrof(driver->freeFifo), virt_addrof(driver->requests[i])); } // Open the ipck driver auto error = kernel::ipckDriverUserOpen(driver->replyQueue.replies.size(), virt_addrof(driver->replyQueue), internal::ipcDriverProcessReplies); if (error == ios::Error::OK) { driver->status = IPCDriverStatus::Open; } return error; } /** * Close the IPC driver. * * \retval IOSError::OK * Success. */ IOSError IPCDriverClose() { auto &driver = sIpcDriverData->perCoreData[OSGetCoreId()].driver; driver.status = IPCDriverStatus::Closed; return IOSError::OK; } namespace internal { /** * Get the IPC driver for the current core */ virt_ptr getIPCDriver() { auto coreId = OSGetCoreId(); return virt_addrof(sIpcDriverData->perCoreData[coreId].driver); } /** * Initialise IPCDriverFIFO */ void ipcDriverFifoInit(virt_ptr fifo) { fifo->pushIndex = 0; fifo->popIndex = -1; fifo->count = 0; fifo->maxCount = 0; for (auto i = 0; i < IPCBufferCount; ++i) { fifo->requests[i] = nullptr; } } /** * Push a request into an IPCDriverFIFO structure * * \retval IOSError::OK Success * \retval IOSError::QFull There was no free space in the queue to push the request. */ IOSError ipcDriverFifoPush(virt_ptr fifo, virt_ptr request) { if (fifo->pushIndex == fifo->popIndex) { return IOSError::QFull; } fifo->requests[fifo->pushIndex] = request; if (fifo->popIndex == -1) { fifo->popIndex = fifo->pushIndex; } fifo->count += 1; fifo->pushIndex = (fifo->pushIndex + 1) % IPCBufferCount; if (fifo->count > fifo->maxCount) { fifo->maxCount = fifo->count; } return IOSError::OK; } /** * Pop a request into an IPCDriverFIFO structure. * * \retval IOSError::OK * Success * * \retval IOSError::QEmpty * There was no requests to pop from the queue. */ IOSError ipcDriverFifoPop(virt_ptr fifo, virt_ptr *requestOut) { if (fifo->popIndex == -1) { return IOSError::QEmpty; } auto request = fifo->requests[fifo->popIndex]; fifo->count -= 1; if (fifo->count == 0) { fifo->popIndex = -1; } else { fifo->popIndex = (fifo->popIndex + 1) % IPCBufferCount; } *requestOut = request; return IOSError::OK; } /** * Allocates and initialises a IPCDriverRequest. * * This function can block with OSWaitEvent until there is a free request to * pop from the freeFifo queue. * * \return * Returns IOSError::OK on success, an IOSError code otherwise. */ IOSError ipcDriverAllocateRequest(virt_ptr driver, virt_ptr *requestOut, IOSHandle handle, IOSCommand command, uint32_t requestUnk0x04, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncContext) { virt_ptr request = nullptr; auto error = IOSError::OK; do { error = ipcDriverFifoPop(virt_addrof(driver->freeFifo), &request); if (error) { driver->failedAllocateRequestBlock += 1; if (error == IOSError::QEmpty) { driver->waitingFreeFifo = TRUE; OSWaitEvent(virt_addrof(driver->waitFreeFifoEvent)); } } } while (error == IOSError::QEmpty); if (error != IOSError::OK) { return error; } request->allocated = TRUE; request->unk0x04 = requestUnk0x04; request->asyncCallback = asyncCallback; request->asyncContext = asyncContext; auto ipcBuffer = request->ipcBuffer; std::memset(virt_addrof(ipcBuffer->request).get(), 0, sizeof(kernel::IpcRequest)); ipcBuffer->request.command = command; ipcBuffer->request.handle = handle; ipcBuffer->request.flags = 0u; ipcBuffer->request.clientPid = 0; ipcBuffer->request.reply = IOSError::OK; *requestOut = request; return IOSError::OK; } /** * Free a IPCDriverRequest. * * \retval IOSError::OK * Success. * * \retval IOSError::QFull * The driver's freeFifo queue was full thus we were unable to free the request. */ IOSError ipcDriverFreeRequest(virt_ptr driver, virt_ptr request) { auto error = ipcDriverFifoPush(virt_addrof(driver->freeFifo), request); request->allocated = FALSE; if (error != IOSError::OK) { driver->failedFreeRequestBlock += 1; } return error; } /** * Submits an IPCDriverRequest to the kernel IPC driver. * * \retval IOSError::OK * Success. */ IOSError ipcDriverSubmitRequest(virt_ptr driver, virt_ptr request) { OSInitEventEx(virt_addrof(request->finishEvent), FALSE, OSEventMode::AutoReset, virt_addrof(sIpcDriverData->submitEventName)); driver->requestsSubmitted++; kernel::ipckDriverUserSubmitRequest(request->ipcBuffer); return IOSError::OK; } /** * Blocks and waits for a response to an IPCDriverRequest. * * \return * Returns IOSError::OK or an IOSHandle on success, or an IOSError code otherwise. */ IOSError ipcDriverWaitResponse(virt_ptr driver, virt_ptr request) { OSWaitEvent(virt_addrof(request->finishEvent)); auto response = request->ipcBuffer->request.reply; ipcDriverFreeRequest(driver, request); OSSignalEventAll(virt_addrof(driver->waitFreeFifoEvent)); return response; } /** * Callback by kernel IPC driver to indicate there are pending replies to process. */ void ipcDriverProcessReplies(kernel::InterruptType type, virt_ptr interruptedContext) { disableScheduler(); auto driver = getIPCDriver(); auto &coreData = sIpcDriverData->perCoreData[driver->coreId]; for (auto i = 0u; i < driver->replyQueue.numReplies; ++i) { auto buffer = driver->replyQueue.replies[i]; auto index = static_cast(buffer - driver->ipcBuffers); decaf_check(index >= 0); decaf_check(index <= IPCBufferCount); auto &request = driver->requests[index]; decaf_check(request.ipcBuffer == buffer); if (!request.asyncCallback) { OSSignalEvent(virt_addrof(request.finishEvent)); } else { auto message = StackObject { }; message->message = virt_cast(virt_func_cast(request.asyncCallback)); message->args[0] = static_cast(request.ipcBuffer->request.reply.value()); message->args[1] = static_cast(virt_cast(request.asyncContext)); message->args[2] = 0u; OSSendMessage(virt_addrof(coreData.queue), message, OSMessageFlags::None); ipcDriverFreeRequest(driver, virt_addrof(request)); } driver->requestsProcessed++; driver->replyQueue.replies[i] = nullptr; } driver->replyQueue.numReplies = 0u; enableScheduler(); } static uint32_t ipcDriverThreadEntry(uint32_t coreId, virt_ptr) { auto msg = StackObject { }; auto &coreData = sIpcDriverData->perCoreData[coreId]; while (true) { OSReceiveMessage(virt_addrof(coreData.queue), msg, OSMessageFlags::Blocking); if (msg->args[2]) { // Received shutdown message break; } // Received callback message auto callback = virt_func_cast(virt_cast(msg->message)); auto error = static_cast(msg->args[0]); auto context = virt_cast(virt_addr { msg->args[1].value() }); cafe::invoke(cpu::this_core::state(), callback, error, context); } IPCDriverClose(); return 0; } } // namespace internal void Library::registerIpcDriverSymbols() { RegisterFunctionExport(IPCDriverInit); RegisterFunctionExport(IPCDriverOpen); RegisterFunctionExport(IPCDriverClose); RegisterDataInternal(sIpcDriverData); RegisterFunctionInternal(internal::ipcDriverThreadEntry, sIpcDriverThreadEntry); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcdriver.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_event.h" #include "coreinit_ios.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_ipcdriver IPC Driver * \ingroup coreinit * @{ */ constexpr auto IPCBufferCount = 0x30; using IPCKDriverRequest = kernel::IPCKDriverRequest; using IPCKDriverReplyQueue = kernel::IPCKDriverReplyQueue; /** * Contains all data required for IPCDriver about an IPC request. * * ipcBuffer contains the actual data sent over IPC. */ struct IPCDriverRequest { //! Whether the current request has been allocated. be2_val allocated; //! Unknown. be2_val unk0x04; //! Callback to be called when reply is received. be2_val asyncCallback; //! Context for asyncCallback. be2_virt_ptr asyncContext; UNKNOWN(0x4); //! Pointer to the IPCK request. be2_virt_ptr ipcBuffer; //! OSEvent called when request is finished. be2_struct finishEvent; }; CHECK_OFFSET(IPCDriverRequest, 0x00, allocated); CHECK_OFFSET(IPCDriverRequest, 0x04, unk0x04); CHECK_OFFSET(IPCDriverRequest, 0x08, asyncCallback); CHECK_OFFSET(IPCDriverRequest, 0x0C, asyncContext); CHECK_OFFSET(IPCDriverRequest, 0x14, ipcBuffer); CHECK_OFFSET(IPCDriverRequest, 0x18, finishEvent); CHECK_SIZE(IPCDriverRequest, 0x3C); /** * FIFO queue for IPCDriverRequests. * * Functions similar to a ring buffer. */ struct IPCDriverFIFO { //! The current item index to push to be2_val pushIndex; //! The current item index to pop from be2_val popIndex; //! The number of items in the queue be2_val count; //! Tracks the highest amount of items there has been in the queue be2_val maxCount; //! Items in the queue be2_array, IPCBufferCount> requests; }; CHECK_OFFSET(IPCDriverFIFO, 0x00, pushIndex); CHECK_OFFSET(IPCDriverFIFO, 0x04, popIndex); CHECK_OFFSET(IPCDriverFIFO, 0x08, count); CHECK_OFFSET(IPCDriverFIFO, 0x0C, maxCount); CHECK_OFFSET(IPCDriverFIFO, 0x10, requests); CHECK_SIZE(IPCDriverFIFO, 0xD0); /** * IPC driver. */ struct IPCDriver { //! The current state of the IPCDriver be2_val status; UNKNOWN(0x4); //! The core this driver was opened on. be2_val coreId; UNKNOWN(0x4); //! A pointer to the memory used for IPCBuffers. be2_virt_ptr ipcBuffers; //! The current outgoing IPCDriverRequest. be2_virt_ptr currentSendTransaction; be2_val iosOpenRequestFail; be2_val iosOpenRequestSuccess; be2_val iosOpenAsyncRequestSubmitFail; be2_val iosOpenAsyncRequestSubmitSuccess; be2_val iosOpenAsyncRequestFail; be2_val iosOpenAsyncRequestSuccess; be2_val iosOpenAsyncExRequestSubmitFail; be2_val iosOpenAsyncExRequestSubmitSuccess; be2_val iosOpenAsyncExRequestFail; be2_val iosOpenAsyncExRequestSuccess; be2_val iosCloseRequestFail; be2_val iosCloseRequestSuccess; be2_val iosCloseAsyncRequestSubmitFail; be2_val iosCloseAsyncRequestSubmitSuccess; be2_val iosCloseAsyncRequestFail; be2_val iosCloseAsyncRequestSuccess; be2_val iosCloseAsyncExRequestSubmitFail; be2_val iosCloseAsyncExRequestSubmitSuccess; be2_val iosCloseAsyncExRequestFail; be2_val iosCloseAsyncExRequestSuccess; be2_val iosReadRequestFail; be2_val iosReadRequestSuccess; be2_val iosReadAsyncRequestSubmitFail; be2_val iosReadAsyncRequestSubmitSuccess; be2_val iosReadAsyncRequestFail; be2_val iosReadAsyncRequestSuccess; be2_val iosReadAsyncExRequestSubmitFail; be2_val iosReadAsyncExRequestSubmitSuccess; be2_val iosReadAsyncExRequestFail; be2_val iosReadAsyncExRequestSuccess; be2_val iosWriteRequestFail; be2_val iosWriteRequestSuccess; be2_val iosWriteAsyncRequestSubmitFail; be2_val iosWriteAsyncRequestSubmitSuccess; be2_val iosWriteAsyncRequestFail; be2_val iosWriteAsyncRequestSuccess; be2_val iosWriteAsyncExRequestSubmitFail; be2_val iosWriteAsyncExRequestSubmitSuccess; be2_val iosWriteAsyncExRequestFail; be2_val iosWriteAsyncExRequestSuccess; be2_val iosSeekRequestFail; be2_val iosSeekRequestSuccess; be2_val iosSeekAsyncRequestSubmitFail; be2_val iosSeekAsyncRequestSubmitSuccess; be2_val iosSeekAsyncRequestFail; be2_val iosSeekAsyncRequestSuccess; be2_val iosSeekAsyncExRequestSubmitFail; be2_val iosSeekAsyncExRequestSubmitSuccess; be2_val iosSeekAsyncExRequestFail; be2_val iosSeekAsyncExRequestSuccess; be2_val iosIoctlRequestFail; be2_val iosIoctlRequestSuccess; be2_val iosIoctlAsyncRequestSubmitFail; be2_val iosIoctlAsyncRequestSubmitSuccess; be2_val iosIoctlAsyncRequestFail; be2_val iosIoctlAsyncRequestSuccess; be2_val iosIoctlAsyncExRequestSubmitFail; be2_val iosIoctlAsyncExRequestSubmitSuccess; be2_val iosIoctlAsyncExRequestFail; be2_val iosIoctlAsyncExRequestSuccess; be2_val iosIoctlvRequestFail; be2_val iosIoctlvRequestSuccess; be2_val iosIoctlvAsyncRequestSubmitFail; be2_val iosIoctlvAsyncRequestSubmitSuccess; be2_val iosIoctlvAsyncRequestFail; be2_val iosIoctlvAsyncRequestSuccess; be2_val iosIoctlvAsyncExRequestSubmitFail; be2_val iosIoctlvAsyncExRequestSubmitSuccess; be2_val iosIoctlvAsyncExRequestFail; be2_val iosIoctlvAsyncExRequestSuccess; be2_val requestsProcessed; be2_val requestsSubmitted; be2_val repliesReceived; be2_val asyncTransactionsCompleted; UNKNOWN(4); be2_val syncTransactionsCompleted; be2_val invalidReplyAddress; be2_val unexpectedReplyInterrupt; be2_val unexpectedAckInterrupt; be2_val invalidReplyMessagePointer; be2_val invalidReplyMessagePointerNotAlloc; be2_val invalidReplyCommand; be2_val failedAllocateRequestBlock; be2_val failedFreeRequestBlock; be2_val failedRequestSubmitOutboundFIFOFull; //! FIFO of free IPCDriverRequests. be2_struct freeFifo; //! FIFO of IPCDriverRequests which have been sent over IPC and are awaiting a reply. be2_struct outboundFifo; //! An event object used to wait for a request to be available for allocation from freeFifo. be2_struct waitFreeFifoEvent; //! Set to TRUE if there is a someone waiting on waitFreeFifoEvent. be2_val waitingFreeFifo; //! Set to TRUE once this->requests has been initialised. be2_val initialisedRequests; //! Pending replies from IOS to process. be2_struct replyQueue; //! IPCDriverRequests memory to be used in freeFifo / outboundFifo. be2_array requests; UNKNOWN(0x1740 - 0xF3C); }; CHECK_OFFSET(IPCDriver, 0x00, status); CHECK_OFFSET(IPCDriver, 0x08, coreId); CHECK_OFFSET(IPCDriver, 0x10, ipcBuffers); CHECK_OFFSET(IPCDriver, 0x14, currentSendTransaction); CHECK_OFFSET(IPCDriver, 0x18, iosOpenRequestFail); CHECK_OFFSET(IPCDriver, 0x1C, iosOpenRequestSuccess); CHECK_OFFSET(IPCDriver, 0x20, iosOpenAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x24, iosOpenAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x28, iosOpenAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0x2C, iosOpenAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0x30, iosOpenAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x34, iosOpenAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x38, iosOpenAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0x3C, iosOpenAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0x40, iosCloseRequestFail); CHECK_OFFSET(IPCDriver, 0x44, iosCloseRequestSuccess); CHECK_OFFSET(IPCDriver, 0x48, iosCloseAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x4C, iosCloseAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x50, iosCloseAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0x54, iosCloseAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0x58, iosCloseAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x5C, iosCloseAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x60, iosCloseAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0x64, iosCloseAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0x68, iosReadRequestFail); CHECK_OFFSET(IPCDriver, 0x6C, iosReadRequestSuccess); CHECK_OFFSET(IPCDriver, 0x70, iosReadAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x74, iosReadAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x78, iosReadAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0x7C, iosReadAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0x80, iosReadAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x84, iosReadAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x88, iosReadAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0x8C, iosReadAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0x90, iosWriteRequestFail); CHECK_OFFSET(IPCDriver, 0x94, iosWriteRequestSuccess); CHECK_OFFSET(IPCDriver, 0x98, iosWriteAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x9C, iosWriteAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0xA0, iosWriteAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0xA4, iosWriteAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0xA8, iosWriteAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0xAC, iosWriteAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0xB0, iosWriteAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0xB4, iosWriteAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0xB8, iosSeekRequestFail); CHECK_OFFSET(IPCDriver, 0xBC, iosSeekRequestSuccess); CHECK_OFFSET(IPCDriver, 0xC0, iosSeekAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0xC4, iosSeekAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0xC8, iosSeekAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0xCC, iosSeekAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0xD0, iosSeekAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0xD4, iosSeekAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0xD8, iosSeekAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0xDC, iosSeekAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0xE0, iosIoctlRequestFail); CHECK_OFFSET(IPCDriver, 0xE4, iosIoctlRequestSuccess); CHECK_OFFSET(IPCDriver, 0xE8, iosIoctlAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0xEC, iosIoctlAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0xF0, iosIoctlAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0xF4, iosIoctlAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0xF8, iosIoctlAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0xFC, iosIoctlAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x100, iosIoctlAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0x104, iosIoctlAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0x108, iosIoctlvRequestFail); CHECK_OFFSET(IPCDriver, 0x10C, iosIoctlvRequestSuccess); CHECK_OFFSET(IPCDriver, 0x110, iosIoctlvAsyncRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x114, iosIoctlvAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x118, iosIoctlvAsyncRequestFail); CHECK_OFFSET(IPCDriver, 0x11C, iosIoctlvAsyncRequestSuccess); CHECK_OFFSET(IPCDriver, 0x120, iosIoctlvAsyncExRequestSubmitFail); CHECK_OFFSET(IPCDriver, 0x124, iosIoctlvAsyncExRequestSubmitSuccess); CHECK_OFFSET(IPCDriver, 0x128, iosIoctlvAsyncExRequestFail); CHECK_OFFSET(IPCDriver, 0x12C, iosIoctlvAsyncExRequestSuccess); CHECK_OFFSET(IPCDriver, 0x130, requestsProcessed); CHECK_OFFSET(IPCDriver, 0x134, requestsSubmitted); CHECK_OFFSET(IPCDriver, 0x138, repliesReceived); CHECK_OFFSET(IPCDriver, 0x13C, asyncTransactionsCompleted); CHECK_OFFSET(IPCDriver, 0x144, syncTransactionsCompleted); CHECK_OFFSET(IPCDriver, 0x148, invalidReplyAddress); CHECK_OFFSET(IPCDriver, 0x14C, unexpectedReplyInterrupt); CHECK_OFFSET(IPCDriver, 0x150, unexpectedAckInterrupt); CHECK_OFFSET(IPCDriver, 0x154, invalidReplyMessagePointer); CHECK_OFFSET(IPCDriver, 0x158, invalidReplyMessagePointerNotAlloc); CHECK_OFFSET(IPCDriver, 0x15C, invalidReplyCommand); CHECK_OFFSET(IPCDriver, 0x160, failedAllocateRequestBlock); CHECK_OFFSET(IPCDriver, 0x164, failedFreeRequestBlock); CHECK_OFFSET(IPCDriver, 0x168, failedRequestSubmitOutboundFIFOFull); CHECK_OFFSET(IPCDriver, 0x16C, freeFifo); CHECK_OFFSET(IPCDriver, 0x23C, outboundFifo); CHECK_OFFSET(IPCDriver, 0x30C, waitFreeFifoEvent); CHECK_OFFSET(IPCDriver, 0x330, waitingFreeFifo); CHECK_OFFSET(IPCDriver, 0x334, initialisedRequests); CHECK_OFFSET(IPCDriver, 0x338, replyQueue); CHECK_OFFSET(IPCDriver, 0x3FC, requests); CHECK_SIZE(IPCDriver, 0x1740); void IPCDriverInit(); IOSError IPCDriverOpen(); IOSError IPCDriverClose(); namespace internal { virt_ptr getIPCDriver(); void ipcDriverFifoInit(virt_ptr fifo); IOSError ipcDriverFifoPush(virt_ptr fifo, virt_ptr request); IOSError ipcDriverFifoPop(virt_ptr fifo, virt_ptr *outRequest); IOSError ipcDriverAllocateRequest(virt_ptr driver, virt_ptr *outRequest, IOSHandle handle, IOSCommand command, uint32_t requestUnk0x04, IOSAsyncCallbackFn asyncCallback, virt_ptr asyncContext); IOSError ipcDriverFreeRequest(virt_ptr driver, virt_ptr request); IOSError ipcDriverSubmitRequest(virt_ptr driver, virt_ptr request); IOSError ipcDriverWaitResponse(virt_ptr driver, virt_ptr request); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_lockedcache.cpp ================================================ #include "coreinit.h" #include "coreinit_core.h" #include "coreinit_lockedcache.h" #include "coreinit_memory.h" #include "coreinit_mutex.h" #include "coreinit_systeminfo.h" #include "coreinit_thread.h" #include #include namespace cafe::coreinit { constexpr auto LCBlockSize = 512u; constexpr auto LCMaxSize = 16u * 1024; struct LockedCacheState { //! Lock for this structure. be2_struct mutex; //! Base address of locked cache memory. be2_val baseAddress; //! Free size in bytes remaining in Locked Cache. be2_val freeSize; //! The bitmask of the currently allocated blocks in the locked cache. be2_val allocBitMask; //! Storage of the bitmasks of current allocations, only set for the first //! bit index of an allocation. be2_array allocatedMasks; //! The size of an allocation in bytes, only set for the first bit index of //! an allocation. be2_array allocatedSize; //! Reference count for LCEnableDMA / LCDisableDMA. be2_val dmaRefCount; }; CHECK_OFFSET(LockedCacheState, 0x00, mutex); CHECK_OFFSET(LockedCacheState, 0x2C, baseAddress); CHECK_OFFSET(LockedCacheState, 0x30, freeSize); CHECK_OFFSET(LockedCacheState, 0x34, allocBitMask); CHECK_OFFSET(LockedCacheState, 0x38, allocatedMasks); CHECK_OFFSET(LockedCacheState, 0xB8, allocatedSize); CHECK_OFFSET(LockedCacheState, 0x138, dmaRefCount); CHECK_SIZE(LockedCacheState, 0x13C); struct StaticLockedCacheData { be2_array coreState; }; virt_ptr sLockedCacheData = nullptr; virt_ptr getCoreLockedCacheState() { return virt_addrof(sLockedCacheData->coreState[OSGetCoreId()]); } /** * Check if Locked Cache hardware is available on this process. */ BOOL LCHardwareIsAvailable() { if (OSGetCoreId() != 2) { return OSGetForegroundBucket(nullptr, nullptr); } auto upid = OSGetUPID(); if (upid == kernel::UniqueProcessId::Game || upid == kernel::UniqueProcessId::HomeMenu) { return TRUE; } return FALSE; } /** * Allocate some memory from the current core's Locked Cache. */ virt_ptr LCAlloc(uint32_t size) { if (size > LCMaxSize) { return nullptr; } auto lcState = getCoreLockedCacheState(); auto result = virt_ptr { nullptr }; OSLockMutex(virt_addrof(lcState->mutex)); if (lcState->freeSize >= size) { auto numBlocks = align_up(size, LCBlockSize) / LCBlockSize; auto bitMask = make_bitmask(numBlocks); auto index = 0u; // Find a free spot in the allocBitMask which can fit bitMask while (lcState->allocBitMask & (bitMask << index)) { if (index >= 32 - numBlocks) { break; } index++; } if ((index < 32 - numBlocks) || (index == 0 && numBlocks == 32)) { // Do the allocation! auto mask = bitMask << index; auto blockSize = numBlocks * LCBlockSize; lcState->allocBitMask |= mask; lcState->freeSize -= blockSize; lcState->allocatedMasks[index] = mask; lcState->allocatedSize[index] = blockSize; result = virt_cast(lcState->baseAddress + index * LCBlockSize); } } OSUnlockMutex(virt_addrof(lcState->mutex)); return result; } /** * Free some memory to the current core's Locked Cache. */ void LCDealloc(virt_ptr ptr) { auto lcState = getCoreLockedCacheState(); auto addr = virt_cast(ptr); if (addr < lcState->baseAddress || addr >= lcState->baseAddress + LCMaxSize) { return; } OSLockMutex(virt_addrof(lcState->mutex)); auto index = static_cast((addr - lcState->baseAddress) / LCBlockSize); auto mask = lcState->allocatedMasks[index]; auto size = lcState->allocatedSize[index]; lcState->allocBitMask &= ~mask; lcState->freeSize += size; lcState->allocatedMasks[index] = 0u; lcState->allocatedSize[index] = 0u; OSUnlockMutex(virt_addrof(lcState->mutex)); } /** * Get the maximum size of the current core's Locked Cache. */ uint32_t LCGetMaxSize() { return LCMaxSize; } /** * Get the largest allocatable size of the current core's Locked Cache. */ uint32_t LCGetAllocatableSize() { auto lcState = getCoreLockedCacheState(); OSLockMutex(virt_addrof(lcState->mutex)); // Find the largest span of 0 in the allocBitMask auto largestBitSpan = 0u; auto currentSpanSize = 0u; for (auto i = 0u; i < 32; ++i) { if (lcState->allocBitMask & (1 << i)) { largestBitSpan = std::max(currentSpanSize, largestBitSpan); currentSpanSize = 0u; continue; } currentSpanSize++; } OSUnlockMutex(virt_addrof(lcState->mutex)); return largestBitSpan * LCBlockSize; } /** * Get the total amount of unallocated memory in the current core's Locked Cache. */ uint32_t LCGetUnallocated() { auto lcState = getCoreLockedCacheState(); return lcState->freeSize; } /** * Check if DMA is enabled for the current core. */ BOOL LCIsDMAEnabled() { auto lcState = getCoreLockedCacheState(); return lcState->dmaRefCount > 0 ? TRUE : FALSE; } /** * Enable DMA for the current core. * * Only a thread with affinity set to run only on current core can enable DMA. */ BOOL LCEnableDMA() { auto thread = OSGetCurrentThread(); auto core = OSGetCoreId(); auto affinity = thread->attr & OSThreadAttributes::AffinityAny; // Ensure thread can only execute on current core if (core == 0 && affinity != OSThreadAttributes::AffinityCPU0) { return FALSE; } if (core == 1 && affinity != OSThreadAttributes::AffinityCPU1) { return FALSE; } if (core == 2 && affinity != OSThreadAttributes::AffinityCPU2) { return FALSE; } auto lcState = getCoreLockedCacheState(); lcState->dmaRefCount++; return TRUE; } /** * Disable DMA for current core. */ void LCDisableDMA() { auto lcState = getCoreLockedCacheState(); lcState->dmaRefCount--; if (lcState->dmaRefCount == 0) { LCWaitDMAQueue(0); } } /** * Get the total number of pending DMA requests. */ uint32_t LCGetDMAQueueLength() { return 0; } /** * Add a DMA load request to the queue. * * We fake this by performing the load immediately. */ void LCLoadDMABlocks(virt_ptr dst, virt_ptr src, uint32_t size) { // TODO: Notify GPU // Signal the GPU to update the source range if necessary, as with // DCInvalidateRange(). // gx2::internal::notifyGpuFlush(const_cast(src), size); if (size == 0) { size = 128; } std::memcpy(dst.get(), src.get(), size * 32); } /** * Add a DMA store request to the queue. * * We fake this by performing the store immediately. */ void LCStoreDMABlocks(virt_ptr dst, virt_ptr src, uint32_t size) { if (size == 0) { size = 128; } std::memcpy(dst.get(), src.get(), size * 32); // TODO: Notify GPU // Also signal the memory store to the GPU, as with DCFlushRange(). // gx2::internal::notifyCpuFlush(dst, size); } /** * Wait until the DMA queue is a certain length. * * As we fake DMA this can return immediately. */ void LCWaitDMAQueue(uint32_t queueLength) { } namespace internal { void initialiseLockedCache(uint32_t coreId) { auto &state = sLockedCacheData->coreState[coreId]; OSInitMutex(virt_addrof(state.mutex)); state.baseAddress = getLockedCacheBaseAddress(coreId); state.freeSize = 0x4000u; state.allocBitMask = 0u; state.allocatedMasks.fill(0u); state.allocatedSize.fill(0u); state.dmaRefCount = 0u; } } // namespace internal void Library::registerLockedCacheSymbols() { RegisterFunctionExport(LCHardwareIsAvailable); RegisterFunctionExport(LCAlloc); RegisterFunctionExport(LCDealloc); RegisterFunctionExport(LCGetMaxSize); RegisterFunctionExport(LCGetAllocatableSize); RegisterFunctionExport(LCGetUnallocated); RegisterFunctionExport(LCIsDMAEnabled); RegisterFunctionExport(LCEnableDMA); RegisterFunctionExport(LCDisableDMA); RegisterFunctionExport(LCGetDMAQueueLength); RegisterFunctionExport(LCLoadDMABlocks); RegisterFunctionExport(LCStoreDMABlocks); RegisterFunctionExport(LCWaitDMAQueue); RegisterDataInternal(sLockedCacheData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_lockedcache.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_lockedcache Locked Cache * \ingroup coreinit * @{ */ BOOL LCHardwareIsAvailable(); virt_ptr LCAlloc(uint32_t size); void LCDealloc(virt_ptr addr); uint32_t LCGetMaxSize(); uint32_t LCGetAllocatableSize(); uint32_t LCGetUnallocated(); BOOL LCIsDMAEnabled(); BOOL LCEnableDMA(); void LCDisableDMA(); uint32_t LCGetDMAQueueLength(); void LCLoadDMABlocks(virt_ptr dst, virt_ptr src, uint32_t size); void LCStoreDMABlocks(virt_ptr dst, virt_ptr src, uint32_t size); void LCWaitDMAQueue(uint32_t queueLength); namespace internal { void initialiseLockedCache(uint32_t coreId); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_log.cpp ================================================ #include "coreinit.h" #include "coreinit_log.h" namespace cafe::coreinit { void OSLogPrintf(uint32_t unk1, uint32_t unk2, uint32_t unk3, const char *fmt, ...) { // TODO: OSLogPrintf } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_log.h ================================================ #pragma once #include namespace cafe::coreinit { void OSLogPrintf(uint32_t unk1, uint32_t unk2, uint32_t unk3, const char *fmt, ...); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_mcp.cpp ================================================ #include "coreinit.h" #include "coreinit_fsa.h" #include "coreinit_ios.h" #include "coreinit_ipcbufpool.h" #include "coreinit_mcp.h" #include "cafe/cafe_stackobject.h" #include "ios/ios_error.h" namespace cafe::coreinit { using ios::mcp::MCPCommand; using ios::mcp::MCPDeviceFlags; using ios::mcp::MCPResponseGetTitleId; using ios::mcp::MCPResponseGetOwnTitleInfo; using ios::mcp::MCPResponseUpdateCheckContext; using ios::mcp::MCPResponseUpdateCheckResume; using ios::mcp::MCPRequestDeviceList; using ios::mcp::MCPRequestGetOwnTitleInfo; using ios::mcp::MCPRequestSearchTitleList; using ios::mcp::MCPTitleListSearchFlags; static constexpr uint32_t SmallMessageCount = 0x100; static constexpr uint32_t SmallMessageSize = 0x80; static constexpr uint32_t LargeMessageCount = 4; static constexpr uint32_t LargeMessageSize = 0x1000; struct StaticMcpData { be2_val initialised; be2_virt_ptr smallMessagePool; be2_virt_ptr largeMessagePool; be2_array smallMessageBuffer; be2_array largeMessageBuffer; be2_val smallMessageCount; be2_val largeMessageCount; }; static virt_ptr sMcpData = nullptr; IOSError MCP_Open() { return IOS_Open(make_stack_string("/dev/mcp"), IOSOpenMode::None); } void MCP_Close(IOSHandle handle) { IOS_Close(handle); } IOSError MCP_DeviceList(IOSHandle handle, virt_ptr numDevices, virt_ptr deviceList, uint32_t deviceListSizeBytes) { if (!numDevices || !deviceList) { return static_cast(MCPError::InvalidParam); } auto request = virt_cast( internal::mcpAllocateMessage(sizeof(MCPRequestDeviceList))); if (!request) { return static_cast(MCPError::Alloc); } request->flags = MCPDeviceFlags::Unk1; auto result = IOS_Ioctl(handle, MCPCommand::DeviceList, request, sizeof(uint32_t), deviceList, deviceListSizeBytes); if (result >= 0) { *numDevices = static_cast(result); } internal::mcpFreeMessage(request); return result; // This function does not translate result to MCPError } IOSError MCP_FullDeviceList(IOSHandle handle, virt_ptr numDevices, virt_ptr deviceList, uint32_t deviceListSizeBytes) { if (!numDevices || !deviceList) { return static_cast(MCPError::InvalidParam); } auto request = virt_cast( internal::mcpAllocateMessage(sizeof(MCPRequestDeviceList))); if (!request) { return static_cast(MCPError::Alloc); } request->flags = MCPDeviceFlags::Unk1 | MCPDeviceFlags::Unk2 | MCPDeviceFlags::Unk8; auto result = IOS_Ioctl(handle, MCPCommand::DeviceList, request, sizeof(uint32_t), deviceList, deviceListSizeBytes); if (result >= 0) { *numDevices = static_cast(result); } internal::mcpFreeMessage(request); return result; // This function does not translate result to MCPError } int32_t MCP_GetErrorCodeForViewer(MCPError error) { if (error >= 0) { return 1629999; } auto group = (~static_cast(error) >> 16) & 0x3FF; if (group != 4) { return 1629999; } if (error & 0x8000) { return -0x47E0 - (error | 0xFFFF0000) + 0x190000; } else { return -0x47E0 - (error & 0xFFFF) + 0x190000; } } MCPError MCP_GetOwnTitleInfo(IOSHandle handle, virt_ptr titleInfo) { auto result = MCPError::OK; auto request = virt_cast( internal::mcpAllocateMessage(sizeof(MCPRequestGetOwnTitleInfo))); if (!request) { return MCPError::Alloc; } auto response = virt_cast( internal::mcpAllocateMessage(sizeof(MCPResponseGetOwnTitleInfo))); if (!response) { internal::mcpFreeMessage(request); return MCPError::Alloc; } // TODO: __KernelGetInfo(0, &request->unk0x00, 0xA8, 0); request->unk0x00 = 0u; auto iosError = IOS_Ioctl(handle, MCPCommand::GetOwnTitleInfo, request, sizeof(uint32_t), response, sizeof(MCPResponseGetOwnTitleInfo)); result = internal::mcpDecodeIosErrorToMcpError(iosError); if (result >= 0) { std::memcpy(titleInfo.get(), virt_addrof(response->titleInfo).get(), sizeof(MCPTitleListType)); } internal::mcpFreeMessage(request); internal::mcpFreeMessage(response); return result; } MCPError MCP_GetSysProdSettings(IOSHandle handle, virt_ptr settings) { if (!settings) { return MCPError::InvalidParam; } auto message = internal::mcpAllocateMessage(sizeof(IOSVec)); if (!message) { return MCPError::Alloc; } auto outVecs = virt_cast(message); outVecs[0].vaddr = virt_cast(settings); outVecs[0].len = static_cast(sizeof(MCPSysProdSettings)); auto iosError = IOS_Ioctlv(handle, MCPCommand::GetSysProdSettings, 0, 1, outVecs); auto mcpError = internal::mcpDecodeIosErrorToMcpError(iosError); internal::mcpFreeMessage(message); return mcpError; } MCPError MCP_GetTitleId(IOSHandle handle, virt_ptr outTitleId) { auto result = MCPError::OK; auto output = internal::mcpAllocateMessage(sizeof(MCPResponseGetTitleId)); if (!output) { return MCPError::Alloc; } auto iosError = IOS_Ioctl(handle, MCPCommand::GetTitleId, nullptr, 0, output, sizeof(MCPResponseGetTitleId)); result = internal::mcpDecodeIosErrorToMcpError(iosError); if (result >= 0) { auto response = virt_cast(output); *outTitleId = response->titleId; } internal::mcpFreeMessage(output); return result; } MCPError MCP_GetTitleInfo(IOSHandle handle, uint64_t titleId, virt_ptr titleInfo) { auto searchTitle = StackObject { }; std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType)); searchTitle->titleId = titleId; auto iosError = internal::mcpSearchTitleList(handle, searchTitle, MCPTitleListSearchFlags::TitleId, titleInfo, 1); if (iosError != 1) { return MCPError::System; } return MCPError::OK; } MCPError MCP_TitleCount(IOSHandle handle) { auto result = IOS_Ioctl(handle, MCPCommand::TitleCount, nullptr, 0, nullptr, 0); if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } return static_cast(result); } MCPError MCP_TitleList(IOSHandle handle, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes) { auto result = IOSError::OK; if (!titleList || !titleListSizeBytes) { result = static_cast(MCP_TitleCount(handle)); } else { auto searchTitle = StackObject { }; std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType)); result = internal::mcpSearchTitleList(handle, searchTitle, MCPTitleListSearchFlags::None, titleList, titleListSizeBytes / sizeof(MCPTitleListType)); } if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } *outTitleCount = static_cast(result); return MCPError::OK; } MCPError MCP_TitleListByAppType(IOSHandle handle, MCPAppType appType, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes) { auto searchTitle = StackObject { }; std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType)); searchTitle->appType = appType; auto result = internal::mcpSearchTitleList(handle, searchTitle, MCPTitleListSearchFlags::AppType, titleList, titleListSizeBytes / sizeof(MCPTitleListType)); if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } *outTitleCount = static_cast(result); return MCPError::OK; } MCPError MCP_TitleListByUniqueId(IOSHandle handle, uint32_t uniqueId, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes) { auto searchTitle = StackObject { }; std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType)); searchTitle->titleId = uniqueId << 8; searchTitle->appType = MCPAppType::Unk0x0800000E; auto searchFlags = MCPTitleListSearchFlags::UniqueId | MCPTitleListSearchFlags::AppType; auto result = internal::mcpSearchTitleList(handle, searchTitle, searchFlags, titleList, titleListSizeBytes / sizeof(MCPTitleListType)); if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } *outTitleCount = static_cast(result); return MCPError::OK; } MCPError MCP_TitleListByUniqueIdAndIndexedDeviceAndAppType(IOSHandle handle, uint32_t uniqueId, virt_ptr indexedDevice, uint8_t unk0x60, MCPAppType appType, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes) { auto searchTitle = StackObject { }; std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType)); searchTitle->titleId = uniqueId << 8; searchTitle->appType = appType; searchTitle->unk0x60 = unk0x60; std::memcpy(virt_addrof(searchTitle->indexedDevice).get(), indexedDevice.get(), 4); auto searchFlags = MCPTitleListSearchFlags::UniqueId | MCPTitleListSearchFlags::AppType | MCPTitleListSearchFlags::Unk0x60 | MCPTitleListSearchFlags::IndexedDevice; auto result = internal::mcpSearchTitleList(handle, searchTitle, searchFlags, titleList, titleListSizeBytes / sizeof(MCPTitleListType)); if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } *outTitleCount = static_cast(result); return MCPError::OK; } MCPError MCP_UpdateCheckContext(IOSHandle handle, virt_ptr outResult) { auto message = internal::mcpAllocateMessage(sizeof(MCPResponseUpdateCheckContext)); if (!message) { return MCPError::Alloc; } auto response = virt_cast(message); auto result = IOS_Ioctl(handle, MCPCommand::UpdateCheckContext, nullptr, 0, response, sizeof(MCPResponseUpdateCheckContext)); if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } internal::mcpFreeMessage(message); *outResult = response->result; return MCPError::OK; } MCPError MCP_UpdateCheckResume(IOSHandle handle, virt_ptr outResult) { auto message = internal::mcpAllocateMessage(sizeof(MCPResponseUpdateCheckResume)); if (!message) { return MCPError::Alloc; } auto response = virt_cast(message); auto result = IOS_Ioctl(handle, MCPCommand::UpdateCheckResume, nullptr, 0, response, sizeof(MCPResponseUpdateCheckResume)); if (result < 0) { return internal::mcpDecodeIosErrorToMcpError(result); } internal::mcpFreeMessage(message); *outResult = response->result; return MCPError::OK; } MCPError MCP_UpdateGetProgress(IOSHandle handle, virt_ptr outUpdateProgress) { if (!outUpdateProgress || !align_check(outUpdateProgress.get(), 64)) { return MCPError::InvalidParam; } auto message = internal::mcpAllocateMessage(sizeof(IOSVec)); if (!message) { return MCPError::Alloc; } auto outVecs = virt_cast(message); outVecs[0].vaddr = virt_cast(outUpdateProgress); outVecs[0].len = static_cast(sizeof(MCPUpdateProgress)); auto iosError = IOS_Ioctlv(handle, MCPCommand::UpdateGetProgress, 0, 1, outVecs); auto mcpError = internal::mcpDecodeIosErrorToMcpError(iosError); internal::mcpFreeMessage(message); return mcpError; } namespace internal { virt_ptr mcpAllocateMessage(uint32_t size) { auto message = virt_ptr { nullptr }; if (size == 0) { return nullptr; } else if (size <= SmallMessageSize) { message = IPCBufPoolAllocate(sMcpData->smallMessagePool, size); } else { message = IPCBufPoolAllocate(sMcpData->largeMessagePool, size); } if (message) { std::memset(message.get(), 0, size); } return message; } MCPError mcpFreeMessage(virt_ptr message) { if (IPCBufPoolFree(sMcpData->smallMessagePool, message) == IOSError::OK) { return MCPError::OK; } if (IPCBufPoolFree(sMcpData->largeMessagePool, message) == IOSError::OK) { return MCPError::OK; } return MCPError::Opcode; } MCPError mcpDecodeIosErrorToMcpError(IOSError error) { auto category = ios::getErrorCategory(error); auto code = ios::getErrorCode(error); auto mcpError = static_cast(error); if (error < 0) { switch (category) { case IOSErrorCategory::Kernel: if (code > -1000) { mcpError = static_cast(code + MCPError::KernelErrorBase); } else if(code < -1999) { mcpError = static_cast(code - (IOSErrorCategory::MCP << 16)); } break; case IOSErrorCategory::FSA: if (code == FSAStatus::AlreadyOpen) { mcpError = MCPError::AlreadyOpen; } else if (code == FSAStatus::DataCorrupted) { mcpError = MCPError::DataCorrupted; } else if (code == FSAStatus::StorageFull) { mcpError = MCPError::StorageFull; } else if (code == FSAStatus::WriteProtected) { mcpError = MCPError::WriteProtected; } else { mcpError = static_cast(error + 0xFFFF0000 - 4000); } break; case IOSErrorCategory::MCP: mcpError = static_cast(error); break; } } return mcpError; } IOSError mcpSearchTitleList(IOSHandle handle, virt_ptr searchTitle, MCPTitleListSearchFlags searchFlags, virt_ptr titleList, uint32_t titleListLength) { auto message = mcpAllocateMessage(sizeof(MCPRequestSearchTitleList)); if (!message) { return static_cast(MCPError::Alloc); } auto request = virt_cast(message); request->searchTitle = *searchTitle; request->searchFlags = searchFlags; auto iosError = IOS_Ioctl(handle, MCPCommand::SearchTitleList, request, sizeof(MCPRequestSearchTitleList), titleList, titleListLength * sizeof(MCPTitleListType)); mcpFreeMessage(message); return iosError; } void initialiseMcp() { sMcpData->smallMessagePool = IPCBufPoolCreate(virt_addrof(sMcpData->smallMessageBuffer), static_cast(sMcpData->smallMessageBuffer.size()), SmallMessageSize, virt_addrof(sMcpData->smallMessageCount), 1); sMcpData->largeMessagePool = IPCBufPoolCreate(virt_addrof(sMcpData->largeMessageBuffer), static_cast(sMcpData->largeMessageBuffer.size()), LargeMessageSize, virt_addrof(sMcpData->largeMessageCount), 1); sMcpData->initialised = true; } } // namespace internal void Library::registerMcpSymbols() { RegisterFunctionExport(MCP_Open); RegisterFunctionExport(MCP_Close); RegisterFunctionExport(MCP_DeviceList); RegisterFunctionExport(MCP_FullDeviceList); RegisterFunctionExport(MCP_GetErrorCodeForViewer); RegisterFunctionExport(MCP_GetOwnTitleInfo); RegisterFunctionExport(MCP_GetSysProdSettings); RegisterFunctionExport(MCP_GetTitleId); RegisterFunctionExport(MCP_GetTitleInfo); RegisterFunctionExport(MCP_TitleCount); RegisterFunctionExport(MCP_TitleList); RegisterFunctionExport(MCP_TitleListByAppType); RegisterFunctionExport(MCP_TitleListByUniqueId); RegisterFunctionExport(MCP_TitleListByUniqueIdAndIndexedDeviceAndAppType); RegisterFunctionExport(MCP_UpdateCheckContext); RegisterFunctionExport(MCP_UpdateCheckResume); RegisterFunctionExport(MCP_UpdateGetProgress); RegisterDataInternal(sMcpData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_mcp.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_ios.h" #include "ios/mcp/ios_mcp_mcp.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_mcp MCP * \ingroup coreinit * @{ */ using ios::mcp::MCPAppType; using ios::mcp::MCPCountryCode; using ios::mcp::MCPDevice; using ios::mcp::MCPError; using ios::mcp::MCPRegion; using ios::mcp::MCPSysProdSettings; using ios::mcp::MCPTitleListType; using ios::mcp::MCPTitleListSearchFlags; using ios::mcp::MCPUpdateProgress; IOSError MCP_Open(); void MCP_Close(IOSHandle handle); IOSError MCP_DeviceList(IOSHandle handle, virt_ptr numDevices, virt_ptr deviceList, uint32_t deviceListSizeBytes); IOSError MCP_FullDeviceList(IOSHandle handle, virt_ptr numDevices, virt_ptr deviceList, uint32_t deviceListSizeBytes); int32_t MCP_GetErrorCodeForViewer(MCPError error); MCPError MCP_GetOwnTitleInfo(IOSHandle handle, virt_ptr titleInfo); MCPError MCP_GetSysProdSettings(IOSHandle handle, virt_ptr settings); MCPError MCP_GetTitleId(IOSHandle handle, virt_ptr outTitleId); MCPError MCP_GetTitleInfo(IOSHandle handle, uint64_t titleId, virt_ptr titleInfo); MCPError MCP_TitleCount(IOSHandle handle); MCPError MCP_TitleList(IOSHandle handle, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes); MCPError MCP_TitleListByAppType(IOSHandle handle, MCPAppType appType, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes); MCPError MCP_TitleListByUniqueId(IOSHandle handle, uint32_t uniqueId, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes); MCPError MCP_TitleListByUniqueIdAndIndexedDeviceAndAppType(IOSHandle handle, uint32_t uniqueId, virt_ptr indexedDevice, uint8_t unk0x60, MCPAppType appType, virt_ptr outTitleCount, virt_ptr titleList, uint32_t titleListSizeBytes); MCPError MCP_UpdateCheckContext(IOSHandle handle, virt_ptr outResult); MCPError MCP_UpdateCheckResume(IOSHandle handle, virt_ptr outResult); MCPError MCP_UpdateGetProgress(IOSHandle handle, virt_ptr outUpdateProgress); namespace internal { void initialiseMcp(); virt_ptr mcpAllocateMessage(uint32_t size); MCPError mcpFreeMessage(virt_ptr message); MCPError mcpDecodeIosErrorToMcpError(IOSError error); IOSError mcpSearchTitleList(IOSHandle handle, virt_ptr searchTitle, MCPTitleListSearchFlags searchFlags, virt_ptr titleListOut, uint32_t titleListLength); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memallocator.cpp ================================================ #include "coreinit.h" #include "coreinit_enum.h" #include "coreinit_memallocator.h" #include "coreinit_memblockheap.h" #include "coreinit_memdefaultheap.h" #include "coreinit_memexpheap.h" #include "coreinit_memframeheap.h" #include "coreinit_memunitheap.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include #include #include namespace cafe::coreinit { struct StaticAllocatorData { be2_struct defaultHeapFunctions; be2_struct blockHeapFunctions; be2_struct expHeapFunctions; be2_struct frameHeapFunctions; be2_struct unitHeapFunctions; }; static virt_ptr sAllocatorData = nullptr; static MEMAllocatorAllocFn sDefaultHeapAlloc = nullptr; static MEMAllocatorFreeFn sDefaultHeapFree = nullptr; static MEMAllocatorAllocFn sBlockHeapAlloc = nullptr; static MEMAllocatorFreeFn sBlockHeapFree = nullptr; static MEMAllocatorAllocFn sExpHeapAlloc = nullptr; static MEMAllocatorFreeFn sExpHeapFree = nullptr; static MEMAllocatorAllocFn sFrameHeapAlloc = nullptr; static MEMAllocatorFreeFn sFrameHeapFree = nullptr; static MEMAllocatorAllocFn sUnitHeapAlloc = nullptr; static MEMAllocatorFreeFn sUnitHeapFree = nullptr; /** * Initialise an Allocator struct for the default heap. */ void MEMInitAllocatorForDefaultHeap(virt_ptr allocator) { allocator->heap = MEMGetBaseHeapHandle(MEMBaseHeapType::MEM2); allocator->align = 0; allocator->funcs = virt_addrof(sAllocatorData->defaultHeapFunctions); } /** * Initialise an Allocator struct for a block heap. */ void MEMInitAllocatorForBlockHeap(virt_ptr allocator, MEMHeapHandle handle, int32_t alignment) { allocator->heap = handle; allocator->align = alignment; allocator->funcs = virt_addrof(sAllocatorData->blockHeapFunctions); } /** * Initialise an Allocator struct for an expanded heap. */ void MEMInitAllocatorForExpHeap(virt_ptr allocator, MEMHeapHandle handle, int32_t alignment) { allocator->heap = handle; allocator->align = alignment; allocator->funcs = virt_addrof(sAllocatorData->expHeapFunctions); } /** * Initialise an Allocator struct for a frame heap. */ void MEMInitAllocatorForFrmHeap(virt_ptr allocator, MEMHeapHandle handle, int32_t alignment) { allocator->heap = handle; allocator->align = alignment; allocator->funcs = virt_addrof(sAllocatorData->frameHeapFunctions); } /** * Initialise an Allocator struct for a unit heap. */ void MEMInitAllocatorForUnitHeap(virt_ptr allocator, MEMHeapHandle handle) { allocator->heap = handle; allocator->align = 0; allocator->funcs = virt_addrof(sAllocatorData->unitHeapFunctions); } /** * Allocate memory from an Allocator. * * \return Returns pointer to new allocated memory. */ virt_ptr MEMAllocFromAllocator(virt_ptr allocator, uint32_t size) { return cafe::invoke(cpu::this_core::state(), allocator->funcs->alloc, allocator, size); } /** * Free memory from an Allocator. */ void MEMFreeToAllocator(virt_ptr allocator, virt_ptr block) { return cafe::invoke(cpu::this_core::state(), allocator->funcs->free, allocator, block); } static virt_ptr allocatorDefaultHeapAlloc(virt_ptr allocator, uint32_t size) { return MEMAllocFromDefaultHeap(size); } static void allocatorDefaultHeapFree(virt_ptr allocator, virt_ptr block) { MEMFreeToDefaultHeap(block); } static virt_ptr allocatorBlockHeapAlloc(virt_ptr allocator, uint32_t size) { return MEMAllocFromBlockHeapEx(allocator->heap, size, allocator->align); } static void allocatorBlockHeapFree(virt_ptr allocator, virt_ptr block) { MEMFreeToBlockHeap(allocator->heap, block); } static virt_ptr allocatorExpHeapAlloc(virt_ptr allocator, uint32_t size) { return MEMAllocFromExpHeapEx(allocator->heap, size, allocator->align); } static void allocatorExpHeapFree(virt_ptr allocator, virt_ptr block) { MEMFreeToExpHeap(allocator->heap, block); } static virt_ptr allocatorFrameHeapAlloc(virt_ptr allocator, uint32_t size) { return MEMAllocFromFrmHeapEx(allocator->heap, size, allocator->align); } static void allocatorFrameHeapFree(virt_ptr allocator, virt_ptr block) { /* Woooowwww I sure hope no one uses frame heap in an allocator... * * coreinit.rpl does not actually free memory here, probably because * using a frame heap for an allocator where you do not know the exact * order of alloc and free is a really dumb idea */ gLog->warn("Allocator did not free memory allocated from frame heap"); } static virt_ptr allocatorUnitHeapAlloc(virt_ptr allocator, uint32_t size) { return MEMAllocFromUnitHeap(allocator->heap); } static void allocatorUnitHeapFree(virt_ptr allocator, virt_ptr block) { MEMFreeToUnitHeap(allocator->heap, block); } namespace internal { void initialiseAllocatorStaticData() { sAllocatorData->defaultHeapFunctions.alloc = sDefaultHeapAlloc; sAllocatorData->defaultHeapFunctions.free = sDefaultHeapFree; sAllocatorData->blockHeapFunctions.alloc = sBlockHeapAlloc; sAllocatorData->blockHeapFunctions.free = sBlockHeapFree; sAllocatorData->expHeapFunctions.alloc = sExpHeapAlloc; sAllocatorData->expHeapFunctions.free = sExpHeapFree; sAllocatorData->frameHeapFunctions.alloc = sFrameHeapAlloc; sAllocatorData->frameHeapFunctions.free = sFrameHeapFree; sAllocatorData->unitHeapFunctions.alloc = sUnitHeapAlloc; sAllocatorData->unitHeapFunctions.free = sUnitHeapFree; } } // namespace internal void Library::registerMemAllocatorSymbols() { RegisterFunctionExport(MEMInitAllocatorForDefaultHeap); RegisterFunctionExport(MEMInitAllocatorForBlockHeap); RegisterFunctionExport(MEMInitAllocatorForExpHeap); RegisterFunctionExport(MEMInitAllocatorForFrmHeap); RegisterFunctionExport(MEMInitAllocatorForUnitHeap); RegisterFunctionExport(MEMAllocFromAllocator); RegisterFunctionExport(MEMFreeToAllocator); RegisterDataInternal(sAllocatorData); RegisterFunctionInternal(allocatorDefaultHeapAlloc, sDefaultHeapAlloc); RegisterFunctionInternal(allocatorDefaultHeapFree, sDefaultHeapFree); RegisterFunctionInternal(allocatorBlockHeapAlloc, sBlockHeapAlloc); RegisterFunctionInternal(allocatorBlockHeapFree, sBlockHeapFree); RegisterFunctionInternal(allocatorExpHeapAlloc, sExpHeapAlloc); RegisterFunctionInternal(allocatorExpHeapFree, sExpHeapFree); RegisterFunctionInternal(allocatorFrameHeapAlloc, sFrameHeapAlloc); RegisterFunctionInternal(allocatorFrameHeapFree, sFrameHeapFree); RegisterFunctionInternal(allocatorUnitHeapAlloc, sUnitHeapAlloc); RegisterFunctionInternal(allocatorUnitHeapFree, sUnitHeapFree); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memallocator.h ================================================ #pragma once #include "coreinit_memheap.h" #include namespace cafe::coreinit { struct MEMAllocator; struct MEMBlockHeap; struct MEMExpandedHeap; struct MEMFrameHeap; struct MEMUnitHeap; /** * \defgroup coreinit_allocator Allocator * \ingroup coreinit * @{ */ using MEMAllocatorAllocFn = virt_func_ptr(virt_ptr, uint32_t)>; using MEMAllocatorFreeFn = virt_func_ptr, virt_ptr)>; struct MEMAllocatorFunctions { be2_val alloc; be2_val free; }; CHECK_OFFSET(MEMAllocatorFunctions, 0x0, alloc); CHECK_OFFSET(MEMAllocatorFunctions, 0x4, free); CHECK_SIZE(MEMAllocatorFunctions, 0x8); struct MEMAllocator { be2_virt_ptr funcs; be2_val heap; be2_val align; UNKNOWN(4); }; CHECK_OFFSET(MEMAllocator, 0x0, funcs); CHECK_OFFSET(MEMAllocator, 0x4, heap); CHECK_OFFSET(MEMAllocator, 0x8, align); CHECK_SIZE(MEMAllocator, 0x10); void MEMInitAllocatorForDefaultHeap(virt_ptr allocator); void MEMInitAllocatorForBlockHeap(virt_ptr allocator, MEMHeapHandle handle, int32_t alignment); void MEMInitAllocatorForExpHeap(virt_ptr allocator, MEMHeapHandle handle, int32_t alignment); void MEMInitAllocatorForFrmHeap(virt_ptr allocator, MEMHeapHandle handle, int32_t alignment); void MEMInitAllocatorForUnitHeap(virt_ptr allocator, MEMHeapHandle handle); virt_ptr MEMAllocFromAllocator(virt_ptr allocator, uint32_t size); void MEMFreeToAllocator(virt_ptr allocator, virt_ptr block); namespace internal { void initialiseAllocatorStaticData(); } /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memblockheap.cpp ================================================ #include "coreinit.h" #include "coreinit_memheap.h" #include "coreinit_memblockheap.h" #include "coreinit_memory.h" #include #include #include namespace cafe::coreinit { /** * Initialise block heap. */ MEMHeapHandle MEMInitBlockHeap(virt_ptr base, virt_ptr start, virt_ptr end, virt_ptr tracking, uint32_t size, uint32_t flags) { auto heap = virt_cast(base); if (!heap || !start || !end || start >= end) { return nullptr; } decaf_check(base); auto dataStart = virt_cast(start); auto dataEnd = virt_cast(end); // Register heap internal::registerHeap(virt_addrof(heap->header), MEMHeapTag::BlockHeap, dataStart, dataEnd, static_cast(flags)); // Setup default tracker heap->defaultTrack.blockCount = 1u; heap->defaultTrack.blocks = virt_addrof(heap->defaultBlock); // Setup default block heap->defaultBlock.start = dataStart; heap->defaultBlock.end = dataEnd; heap->defaultBlock.isFree = TRUE; heap->defaultBlock.next = nullptr; heap->defaultBlock.prev = nullptr; // Add default block to block list heap->firstBlock = virt_addrof(heap->defaultBlock); heap->lastBlock = virt_addrof(heap->defaultBlock); auto handle = virt_cast(heap); MEMAddBlockHeapTracking(handle, tracking, size); return handle; } /** * Destroy block heap. */ virt_ptr MEMDestroyBlockHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) { return nullptr; } memset(heap, 0, sizeof(MEMBlockHeap)); internal::unregisterHeap(virt_addrof(heap->header)); return heap; } /** * Adds more tracking block memory to block heap. */ int MEMAddBlockHeapTracking(MEMHeapHandle handle, virt_ptr tracking, uint32_t size) { auto heap = virt_cast(handle); if (!heap || !tracking || heap->header.tag != MEMHeapTag::BlockHeap) { return -4; } // Size must be enough to contain at least 1 tracking structure and 1 block if (size < sizeof(MEMBlockHeapTracking) + sizeof(MEMBlockHeapBlock)) { return -4; } auto blockCount = static_cast((size - sizeof(MEMBlockHeapTracking)) / sizeof(MEMBlockHeapBlock)); auto blocks = virt_cast(tracking + 1); // Setup tracking data tracking->blockCount = blockCount; tracking->blocks = blocks; // Setup block linked list for (auto i = 0u; i < blockCount; ++i) { auto &block = blocks[i]; block.prev = nullptr; block.next = virt_addrof(blocks[i + 1]); } // Insert at start of block list internal::HeapLock lock { virt_addrof(heap->header) }; blocks[blockCount - 1].next = heap->firstFreeBlock; heap->firstFreeBlock = blocks; heap->numFreeBlocks += blockCount; return 0; } static virt_ptr findBlockOwning(virt_ptr heap, virt_ptr data) { auto addr = virt_cast(data); if (addr < heap->header.dataStart) { return nullptr; } if (addr >= heap->header.dataEnd) { return nullptr; } auto distFromEnd = heap->header.dataEnd - addr; auto distFromStart = addr - heap->header.dataStart; if (distFromStart < distFromEnd) { // Look forward from firstBlock auto block = heap->firstBlock; while (block) { if (block->end > addr) { return block; } block = block->next; } } else { // Go backwards from lastBlock auto block = heap->lastBlock; while (block) { if (block->start <= addr) { return block; } block = block->prev; } } return nullptr; } static bool allocInsideBlock(virt_ptr heap, virt_ptr block, virt_ptr start, uint32_t size) { // Ensure we are actually inside this block auto end = start + size; if (size == 0 || end > block->end) { return false; } // First lets check we have enough free blocks auto needFreeBlocks = 0u; if (start != block->start) { needFreeBlocks++; } if (end != block->end) { needFreeBlocks++; } if (heap->numFreeBlocks < needFreeBlocks) { return false; } // Create free block for remaining memory at start of block if (start != block->start) { // Get a new free block auto freeBlock = heap->firstFreeBlock; heap->firstFreeBlock = freeBlock->next; heap->numFreeBlocks--; // Setup free block freeBlock->start = block->start; freeBlock->end = start; freeBlock->isFree = TRUE; freeBlock->prev = block->prev; freeBlock->next = block; if (freeBlock->prev) { freeBlock->prev->next = freeBlock; } else { heap->firstBlock = freeBlock; } // Adjust current block block->start = start; block->prev = freeBlock; } // Create free block for remaining memory at end of block if (end != block->end) { // Get a new free block auto freeBlock = heap->firstFreeBlock; heap->firstFreeBlock = freeBlock->next; heap->numFreeBlocks--; // Setup free block freeBlock->start = end; freeBlock->end = block->end; freeBlock->isFree = TRUE; freeBlock->prev = block; freeBlock->next = block->next; if (block->next) { block->next->prev = freeBlock; } else { heap->lastBlock = freeBlock; } // Adjust current block block->end = end; block->next = freeBlock; } // Set intial memory values if (heap->header.flags & MEMHeapFlags::ZeroAllocated) { memset(block->start, 0, size); } else if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated); memset(block->start, value, size); } // Set block to allocated and return success! block->isFree = FALSE; return true; } /** * Try to allocate from block heap at a specific address. */ virt_ptr MEMAllocFromBlockHeapAt(MEMHeapHandle handle, virt_ptr addr, uint32_t size) { auto heap = virt_cast(handle); if (!heap || !addr || !size || heap->header.tag != MEMHeapTag::BlockHeap) { return nullptr; } if (!heap->firstFreeBlock) { return nullptr; } internal::HeapLock lock { virt_addrof(heap->header) }; auto block = findBlockOwning(heap, addr); if (!block) { gLog->warn("MEMAllocFromBlockHeapAt: Could not find block containing addr 0x{:08X}", addr); return nullptr; } if (!block->isFree) { gLog->warn("MEMAllocFromBlockHeapAt: Requested address is not free 0x{:08X}", addr); return nullptr; } if (!allocInsideBlock(heap, block, virt_cast(addr), size)) { return nullptr; } return addr; } /** * Allocate from block heap. */ virt_ptr MEMAllocFromBlockHeapEx(MEMHeapHandle handle, uint32_t size, int32_t align) { auto heap = virt_cast(handle); auto block = virt_ptr { nullptr }; auto alignedStart = virt_ptr { nullptr }; auto result = virt_ptr { nullptr }; if (!heap || !size || heap->header.tag != MEMHeapTag::BlockHeap) { return nullptr; } internal::HeapLock lock { virt_addrof(heap->header) }; if (align == 0) { align = 4; } if (align >= 0) { // Find first free block with enough size for (block = heap->firstBlock; block; block = block->next) { if (block->isFree) { alignedStart = align_up(block->start, align); if (alignedStart + size < block->end) { break; } } } } else { // Find last free block with enough size for (block = heap->lastBlock; block; block = block->prev) { if (block->isFree) { alignedStart = align_down(block->end - size, -align); if (alignedStart >= block->start) { break; } } } } if (!block) { gLog->warn("MEMAllocFromBlockHeapEx: Could not find free block size: 0x{:X} align: 0x{:X}, allocatable: 0x{:X} free: 0x{:X}", size, align, MEMGetAllocatableSizeForBlockHeapEx(virt_cast(heap), align), MEMGetTotalFreeSizeForBlockHeap(virt_cast(heap))); } else if (allocInsideBlock(heap, block, alignedStart, size)) { result = alignedStart; } return result; } /** * Free memory back to block heap. */ void MEMFreeToBlockHeap(MEMHeapHandle handle, virt_ptr data) { auto heap = virt_cast(handle); if (!heap || !data || heap->header.tag != MEMHeapTag::BlockHeap) { return; } internal::HeapLock lock { virt_addrof(heap->header) }; auto block = findBlockOwning(heap, data); if (!block) { gLog->warn("MEMFreeToBlockHeap: Could not find block containing data 0x{:08X}", data); return; } if (block->isFree) { gLog->warn("MEMFreeToBlockHeap: Tried to free an already free block"); return; } if (block->start != data) { gLog->warn("MEMFreeToBlockHeap: Tried to free block 0x{:08X} from middle 0x{:08X}", block->start, data); return; } if (heap->header.flags & MEMHeapFlags::DebugMode) { auto fill = MEMGetFillValForHeap(MEMHeapFillType::Freed); auto size = block->end - block->start; std::memset(block->start.get(), fill, size); } // Merge with previous free block if possible if (auto prev = block->prev) { if (prev->isFree) { prev->end = block->end; prev->next = block->next; if (auto next = prev->next) { next->prev = prev; } else { heap->lastBlock = prev; } block->prev = nullptr; block->next = heap->firstFreeBlock; heap->numFreeBlocks++; heap->firstFreeBlock = block; block = prev; } } block->isFree = TRUE; // Merge with next free block if possible if (auto next = block->next) { if (next->isFree) { block->end = next->end; block->next = next->next; if (next->next) { next->next->prev = block; } else { heap->lastBlock = block; } next->next = heap->firstFreeBlock; heap->firstFreeBlock = next; heap->numFreeBlocks++; } } } /** * Find the largest possible allocatable size in block heap for an alignment. */ uint32_t MEMGetAllocatableSizeForBlockHeapEx(MEMHeapHandle handle, int32_t align) { auto heap = virt_cast(handle); if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) { return 0; } internal::HeapLock lock { virt_addrof(heap->header) }; auto allocatableSize = 0u; // Adjust align if (align < 0) { align = -align; } else if (align == 0) { align = 4; } for (auto block = heap->firstBlock; block; block = block->next) { if (!block->isFree) { continue; } // Align start address and check it is still inside block auto startAddr = block->start; auto endAddr = block->end; auto alignedStart = align_up(startAddr, align); if (alignedStart >= endAddr) { continue; } // See if this block is largest free block so far auto freeSize = static_cast(endAddr - alignedStart); if (freeSize > allocatableSize) { allocatableSize = freeSize; } } return allocatableSize; } /** * Return number of tracking blocks remaining in heap. */ uint32_t MEMGetTrackingLeftInBlockHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) { return 0; } return heap->numFreeBlocks; } /** * Return total free size in the heap. */ uint32_t MEMGetTotalFreeSizeForBlockHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) { return 0; } internal::HeapLock lock { virt_addrof(heap->header) }; auto freeSize = 0u; for (auto block = heap->firstBlock; block; block = block->next) { if (!block->isFree) { continue; } auto startAddr = block->start; auto endAddr = block->end; freeSize += static_cast(endAddr - startAddr); } return freeSize; } void Library::registerMemBlockHeapSymbols() { RegisterFunctionExport(MEMInitBlockHeap); RegisterFunctionExport(MEMDestroyBlockHeap); RegisterFunctionExport(MEMAddBlockHeapTracking); RegisterFunctionExport(MEMAllocFromBlockHeapAt); RegisterFunctionExport(MEMAllocFromBlockHeapEx); RegisterFunctionExport(MEMFreeToBlockHeap); RegisterFunctionExport(MEMGetAllocatableSizeForBlockHeapEx); RegisterFunctionExport(MEMGetTrackingLeftInBlockHeap); RegisterFunctionExport(MEMGetTotalFreeSizeForBlockHeap); } } // cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memblockheap.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_memheap.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_blockheap Block Heap * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct MEMBlockHeapBlock; struct MEMBlockHeapTracking { UNKNOWN(0x8); //! Pointer to first memory block be2_virt_ptr blocks; //! Number of blocks in this tracking heap be2_val blockCount; }; CHECK_OFFSET(MEMBlockHeapTracking, 0x08, blocks); CHECK_OFFSET(MEMBlockHeapTracking, 0x0C, blockCount); CHECK_SIZE(MEMBlockHeapTracking, 0x10); struct MEMBlockHeapBlock { //! First address of the data region this block has allocated be2_virt_ptr start; //! End address of the data region this block has allocated be2_virt_ptr end; //! TRUE if the block is free, FALSE if allocated be2_val isFree; //! Link to previous block, note that this is only set for allocated blocks be2_virt_ptr prev; //! Link to next block, always set be2_virt_ptr next; }; CHECK_OFFSET(MEMBlockHeapBlock, 0x00, start); CHECK_OFFSET(MEMBlockHeapBlock, 0x04, end); CHECK_OFFSET(MEMBlockHeapBlock, 0x08, isFree); CHECK_OFFSET(MEMBlockHeapBlock, 0x0c, prev); CHECK_OFFSET(MEMBlockHeapBlock, 0x10, next); CHECK_SIZE(MEMBlockHeapBlock, 0x14); struct MEMBlockHeap { be2_struct header; //! Default tracking heap, tracks only defaultBlock be2_struct defaultTrack; //! Default block, used so we don't have an empty block list be2_struct defaultBlock; //! First block in this heap be2_virt_ptr firstBlock; //! Last block in this heap be2_virt_ptr lastBlock; //! First free block be2_virt_ptr firstFreeBlock; //! Free block count be2_val numFreeBlocks; }; CHECK_OFFSET(MEMBlockHeap, 0x00, header); CHECK_OFFSET(MEMBlockHeap, 0x40, defaultTrack); CHECK_OFFSET(MEMBlockHeap, 0x50, defaultBlock); CHECK_OFFSET(MEMBlockHeap, 0x64, firstBlock); CHECK_OFFSET(MEMBlockHeap, 0x68, lastBlock); CHECK_OFFSET(MEMBlockHeap, 0x6C, firstFreeBlock); CHECK_OFFSET(MEMBlockHeap, 0x70, numFreeBlocks); CHECK_SIZE(MEMBlockHeap, 0x74); #pragma pack(pop) MEMHeapHandle MEMInitBlockHeap(virt_ptr base, virt_ptr start, virt_ptr end, virt_ptr tracking, uint32_t size, uint32_t flags); virt_ptr MEMDestroyBlockHeap(MEMHeapHandle handle); int MEMAddBlockHeapTracking(MEMHeapHandle handle, virt_ptr tracking, uint32_t size); virt_ptr MEMAllocFromBlockHeapAt(MEMHeapHandle handle, virt_ptr ptr, uint32_t size); virt_ptr MEMAllocFromBlockHeapEx(MEMHeapHandle handle, uint32_t size, int32_t align); void MEMFreeToBlockHeap(MEMHeapHandle handle, virt_ptr data); uint32_t MEMGetAllocatableSizeForBlockHeapEx(MEMHeapHandle handle, int32_t align); uint32_t MEMGetTrackingLeftInBlockHeap(MEMHeapHandle handle); uint32_t MEMGetTotalFreeSizeForBlockHeap(MEMHeapHandle handle); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memdefaultheap.cpp ================================================ #include "coreinit.h" #include "coreinit_dynload.h" #include "coreinit_memdefaultheap.h" #include "coreinit_memexpheap.h" #include "coreinit_memframeheap.h" #include "coreinit_memheap.h" #include "coreinit_memory.h" #include "cafe/cafe_stackobject.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include namespace cafe::coreinit { using MEMAllocFromDefaultHeapFn = virt_func_ptr(uint32_t size)>; using MEMAllocFromDefaultHeapExFn = virt_func_ptr(uint32_t size, int32_t align)>; using MEMFreeToDefaultHeapFn = virt_func_ptr)>; struct StaticDefaultHeapData { be2_val defaultHeapHandle; be2_val defaultHeapAllocCount; be2_val defaultHeapFreeCount; }; static virt_ptr sDefaultHeapData = nullptr; static virt_ptr sMEMAllocFromDefaultHeap = nullptr; static virt_ptr sMEMAllocFromDefaultHeapEx = nullptr; static virt_ptr sMEMFreeToDefaultHeap = nullptr; static MEMAllocFromDefaultHeapFn sDefaultAllocFromDefaultHeap = nullptr; static MEMAllocFromDefaultHeapExFn sDefaultAllocFromDefaultHeapEx = nullptr; static MEMFreeToDefaultHeapFn sDefaultFreeToDefaultHeap = nullptr; static OSDynLoad_AllocFn sDefaultDynLoadAlloc = nullptr; static OSDynLoad_FreeFn sDefaultDynLoadFree = nullptr; static virt_ptr defaultAllocFromDefaultHeap(uint32_t size) { return MEMAllocFromExpHeapEx(sDefaultHeapData->defaultHeapHandle, size, 0x40u); } static virt_ptr defaultAllocFromDefaultHeapEx(uint32_t size, int32_t alignment) { return MEMAllocFromExpHeapEx(sDefaultHeapData->defaultHeapHandle, size, alignment); } static void defaultFreeToDefaultHeap(virt_ptr block) { return MEMFreeToExpHeap(sDefaultHeapData->defaultHeapHandle, block); } static OSDynLoad_Error defaultDynLoadAlloc(int32_t size, int32_t align, virt_ptr> outPtr) { if (!outPtr) { return OSDynLoad_Error::InvalidAllocatorPtr; } if (align >= 0 && align < 4) { align = 4; } else if (align < 0 && align > -4) { align = -4; } auto ptr = MEMAllocFromDefaultHeapEx(size, align); *outPtr = ptr; if (!ptr) { return OSDynLoad_Error::OutOfMemory; } return OSDynLoad_Error::OK; } static void defaultDynLoadFree(virt_ptr ptr) { MEMFreeToDefaultHeap(ptr); } void CoreInitDefaultHeap(virt_ptr outHeapHandleMEM1, virt_ptr outHeapHandleFG, virt_ptr outHeapHandleMEM2) { auto addr = StackObject { }; auto size = StackObject { }; *sMEMAllocFromDefaultHeap = sDefaultAllocFromDefaultHeap; *sMEMAllocFromDefaultHeapEx = sDefaultAllocFromDefaultHeapEx; *sMEMFreeToDefaultHeap = sDefaultFreeToDefaultHeap; sDefaultHeapData->defaultHeapAllocCount = 0u; sDefaultHeapData->defaultHeapFreeCount = 0u; *outHeapHandleMEM1 = nullptr; *outHeapHandleFG = nullptr; *outHeapHandleMEM2 = nullptr; if (OSGetForegroundBucket(nullptr, nullptr)) { OSGetMemBound(OSMemoryType::MEM1, addr, size); *outHeapHandleMEM1 = MEMCreateFrmHeapEx(virt_cast(*addr), *size, 0); OSGetForegroundBucketFreeArea(addr, size); *outHeapHandleFG = MEMCreateFrmHeapEx(virt_cast(*addr), *size, 0); } OSGetMemBound(OSMemoryType::MEM2, addr, size); sDefaultHeapData->defaultHeapHandle = MEMCreateExpHeapEx(virt_cast(*addr), *size, MEMHeapFlags::ThreadSafe); *outHeapHandleMEM2 = sDefaultHeapData->defaultHeapHandle; OSDynLoad_SetAllocator(sDefaultDynLoadAlloc, sDefaultDynLoadFree); OSDynLoad_SetTLSAllocator(sDefaultDynLoadAlloc, sDefaultDynLoadFree); } virt_ptr MEMAllocFromDefaultHeap(uint32_t size) { return cafe::invoke(cpu::this_core::state(), *sMEMAllocFromDefaultHeap, size); } virt_ptr MEMAllocFromDefaultHeapEx(uint32_t size, int32_t align) { return cafe::invoke(cpu::this_core::state(), *sMEMAllocFromDefaultHeapEx, size, align); } void MEMFreeToDefaultHeap(virt_ptr ptr) { return cafe::invoke(cpu::this_core::state(), *sMEMFreeToDefaultHeap, ptr); } void Library::registerMemDefaultHeapSymbols() { RegisterFunctionExport(CoreInitDefaultHeap); RegisterDataExportName("MEMAllocFromDefaultHeap", sMEMAllocFromDefaultHeap); RegisterDataExportName("MEMAllocFromDefaultHeapEx", sMEMAllocFromDefaultHeapEx); RegisterDataExportName("MEMFreeToDefaultHeap", sMEMFreeToDefaultHeap); RegisterDataInternal(sDefaultHeapData); RegisterFunctionInternal(defaultAllocFromDefaultHeap, sDefaultAllocFromDefaultHeap); RegisterFunctionInternal(defaultAllocFromDefaultHeapEx, sDefaultAllocFromDefaultHeapEx); RegisterFunctionInternal(defaultFreeToDefaultHeap, sDefaultFreeToDefaultHeap); RegisterFunctionInternal(defaultDynLoadAlloc, sDefaultDynLoadAlloc); RegisterFunctionInternal(defaultDynLoadFree, sDefaultDynLoadFree); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memdefaultheap.h ================================================ #pragma once #include "coreinit_memheap.h" #include namespace cafe::coreinit { void CoreInitDefaultHeap(virt_ptr outHeapHandleMEM1, virt_ptr outHeapHandleFG, virt_ptr outHeapHandleMEM2); virt_ptr MEMAllocFromDefaultHeap(uint32_t size); virt_ptr MEMAllocFromDefaultHeapEx(uint32_t size, int32_t align); void MEMFreeToDefaultHeap(virt_ptr ptr); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memexpheap.cpp ================================================ #include "coreinit.h" #include "coreinit_memexpheap.h" #include "coreinit_memory.h" #include #include namespace cafe::coreinit { static constexpr auto FreeTag = uint16_t { 0x4654 }; // 'FR' static constexpr auto UsedTag = uint16_t { 0x5544 }; // 'UD' static virt_ptr getBlockMemStart(virt_ptr block) { auto attribs = block->attribs.value(); return virt_cast(block) - attribs.alignment(); } static virt_ptr getBlockMemEnd(virt_ptr block) { return virt_cast(block) + sizeof(MEMExpHeapBlock) + block->blockSize; } static virt_ptr getBlockDataStart(virt_ptr block) { return virt_cast(block) + sizeof(MEMExpHeapBlock); } static virt_ptr getUsedMemBlock(virt_ptr mem) { auto block = virt_cast(mem) - 1; decaf_check(block->tag == UsedTag); return block; } static bool listContainsBlock(virt_ptr list, virt_ptr block) { for (auto i = list->head; i; i = i->next) { if (i == block) { return true; } } return false; } static void insertBlock(virt_ptr list, virt_ptr prev, virt_ptr block) { decaf_check(!block->prev); decaf_check(!block->next); if (!prev) { block->next = list->head; block->prev = nullptr; list->head = block; } else { block->next = prev->next; block->prev = prev; prev->next = block; } if (block->next) { block->next->prev = block; } else { list->tail = block; } } static void removeBlock(virt_ptr list, virt_ptr block) { decaf_check(listContainsBlock(list, block)); if (block->prev) { block->prev->next = block->next; } else { list->head = block->next; } if (block->next) { block->next->prev = block->prev; } else { list->tail = block->prev; } block->prev = nullptr; block->next = nullptr; } static uint32_t getAlignedBlockSize(virt_ptr block, uint32_t alignment, MEMExpHeapDirection dir) { if (dir == MEMExpHeapDirection::FromStart) { auto dataStart = virt_cast(block) + sizeof(MEMExpHeapBlock); auto dataEnd = dataStart + block->blockSize; auto alignedDataStart = align_up(dataStart, alignment); if (alignedDataStart >= dataEnd) { return 0; } return static_cast(dataEnd - alignedDataStart); } else if (dir == MEMExpHeapDirection::FromEnd) { auto dataStart = virt_cast(block) + sizeof(MEMExpHeapBlock); auto dataEnd = dataStart + block->blockSize; auto alignedDataEnd = align_down(dataEnd, alignment); if (alignedDataEnd <= dataStart) { return 0; } return static_cast(alignedDataEnd - dataStart); } else { decaf_abort("Unexpected ExpHeap direction"); } } static virt_ptr createUsedBlockFromFreeBlock(virt_ptr heap, virt_ptr freeBlock, uint32_t size, uint32_t alignment, MEMExpHeapDirection dir) { auto expHeapAttribs = heap->attribs.value(); auto freeBlockAttribs = freeBlock->attribs.value(); auto freeBlockPrev = freeBlock->prev; auto freeMemStart = getBlockMemStart(freeBlock); auto freeMemEnd = getBlockMemEnd(freeBlock); // Free blocks should never have alignment... decaf_check(!freeBlockAttribs.alignment()); removeBlock(virt_addrof(heap->freeList), freeBlock); // Find where we are going to start auto alignedDataStart = virt_ptr { }; if (dir == MEMExpHeapDirection::FromStart) { alignedDataStart = align_up(freeMemStart + sizeof(MEMExpHeapBlock), alignment); } else if (dir == MEMExpHeapDirection::FromEnd) { alignedDataStart = align_down(freeMemEnd - size, alignment); } else { decaf_abort("Unexpected ExpHeap direction"); } // Grab the block header pointer and validate everything is sane auto alignedBlock = virt_cast(alignedDataStart) - 1; decaf_check(alignedDataStart - sizeof(MEMExpHeapBlock) >= freeMemStart); decaf_check(alignedDataStart + size <= freeMemEnd); // Calculate the alignment waste auto topSpaceRemain = (alignedDataStart - freeMemStart) - sizeof(MEMExpHeapBlock); auto bottomSpaceRemain = static_cast((freeMemEnd - alignedDataStart) - size); if (expHeapAttribs.reuseAlignSpace() || dir == MEMExpHeapDirection::FromEnd) { // If the user wants to reuse the alignment space, or we allocated from the bottom, // we should try to release the top space back to the heap free list. if (topSpaceRemain > sizeof(MEMExpHeapBlock) + 4) { // We have enough room to put some of the memory back to the free list freeBlock = virt_cast(freeMemStart); freeBlock->attribs = MEMExpHeapBlockAttribs::get(0); freeBlock->blockSize = static_cast(topSpaceRemain - sizeof(MEMExpHeapBlock)); freeBlock->next = nullptr; freeBlock->prev = nullptr; freeBlock->tag = FreeTag; insertBlock(virt_addrof(heap->freeList), freeBlockPrev, freeBlock); topSpaceRemain = 0; } } if (expHeapAttribs.reuseAlignSpace() || dir == MEMExpHeapDirection::FromStart) { // If the user wants to reuse the alignment space, or we allocated from the top, // we should try to release the bottom space back to the heap free list. if (bottomSpaceRemain > sizeof(MEMExpHeapBlock) + 4) { // We have enough room to put some of the memory back to the free list freeBlock = virt_cast(freeMemEnd - bottomSpaceRemain); freeBlock->attribs = MEMExpHeapBlockAttribs::get(0); freeBlock->blockSize = static_cast(bottomSpaceRemain - sizeof(MEMExpHeapBlock)); freeBlock->next = nullptr; freeBlock->prev = nullptr; freeBlock->tag = FreeTag; insertBlock(virt_addrof(heap->freeList), freeBlockPrev, freeBlock); bottomSpaceRemain = 0; } } // Update the structure with the new allocation alignedBlock->attribs = MEMExpHeapBlockAttribs::get(0) .alignment(static_cast(topSpaceRemain)) .allocDir(dir); alignedBlock->blockSize = size + bottomSpaceRemain; alignedBlock->prev = nullptr; alignedBlock->next = nullptr; alignedBlock->tag = UsedTag; insertBlock(virt_addrof(heap->usedList), nullptr, alignedBlock); if (heap->header.flags & MEMHeapFlags::ZeroAllocated) { memset(alignedDataStart, 0, size); } else if (heap->header.flags & MEMHeapFlags::DebugMode) { auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Allocated); memset(alignedDataStart, fillVal, size); } return alignedBlock; } static void releaseMemory(virt_ptr heap, virt_ptr memStart, virt_ptr memEnd) { decaf_check(memEnd - memStart >= sizeof(MEMExpHeapBlock) + 4); // Fill the released memory with debug data if needed if (heap->header.flags & MEMHeapFlags::DebugMode) { auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Freed); std::memset(memStart.get(), fillVal, memEnd - memStart); } // Find the preceeding block to the memory we are releasing virt_ptr prevBlock = nullptr; virt_ptr nextBlock = heap->freeList.head; for (auto block = heap->freeList.head; block; block = block->next) { if (getBlockMemStart(block) < memStart) { prevBlock = block; nextBlock = block->next; } else if (block >= prevBlock) { break; } } virt_ptr freeBlock = nullptr; if (prevBlock) { // If there is a previous block, we need to check if we // should just steal that block rather than making one. auto prevMemEnd = getBlockMemEnd(prevBlock); if (memStart == prevMemEnd) { // Previous block absorbs the new memory prevBlock->blockSize += static_cast(memEnd - memStart); // Our free block becomes the previous one freeBlock = prevBlock; } } if (!freeBlock) { // We did not steal the previous block to free into, // we need to allocate our own here. freeBlock = virt_cast(memStart); freeBlock->attribs = MEMExpHeapBlockAttribs::get(0); freeBlock->blockSize = static_cast((memEnd - memStart) - sizeof(MEMExpHeapBlock)); freeBlock->next = nullptr; freeBlock->prev = nullptr; freeBlock->tag = FreeTag; insertBlock(virt_addrof(heap->freeList), prevBlock, freeBlock); } if (nextBlock) { // If there is a next block, we need to possibly merge it down // into this one. auto nextBlockStart = getBlockMemStart(nextBlock); if (nextBlockStart == memEnd) { // The next block needs to be merged into the freeBlock, as they // are directly adjacent to each other in memory. auto nextBlockEnd = getBlockMemEnd(nextBlock); freeBlock->blockSize += static_cast(nextBlockEnd - nextBlockStart); removeBlock(virt_addrof(heap->freeList), nextBlock); } } } MEMHeapHandle MEMCreateExpHeapEx(virt_ptr base, uint32_t size, uint32_t flags) { auto heapData = virt_cast(base); auto alignedStart = align_up(heapData, 4); auto alignedEnd = align_down(heapData + size, 4); if (alignedEnd < alignedStart || alignedEnd - alignedStart < 0x6C) { // Not enough room for the header return nullptr; } decaf_check(base); // Get our heap header auto heap = virt_cast(alignedStart); // Register Heap internal::registerHeap(virt_addrof(heap->header), MEMHeapTag::ExpandedHeap, alignedStart + sizeof(MEMExpHeap), alignedEnd, static_cast(flags)); // Create an initial block of the data auto dataStart = alignedStart + sizeof(MEMExpHeap); auto firstBlock = virt_cast(dataStart); firstBlock->attribs = MEMExpHeapBlockAttribs::get(0); firstBlock->blockSize = static_cast((alignedEnd - dataStart) - sizeof(MEMExpHeapBlock)); firstBlock->next = nullptr; firstBlock->prev = nullptr; firstBlock->tag = FreeTag; heap->freeList.head = firstBlock; heap->freeList.tail = firstBlock; heap->usedList.head = nullptr; heap->usedList.tail = nullptr; heap->groupId = uint16_t { 0 }; heap->attribs = MEMExpHeapAttribs::get(0); return virt_cast(heap); } virt_ptr MEMDestroyExpHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::ExpandedHeap); internal::unregisterHeap(virt_addrof(heap->header)); return heap; } virt_ptr MEMAllocFromExpHeapEx(MEMHeapHandle handle, uint32_t size, int32_t alignment) { auto heap = virt_cast(handle); decaf_check(heap->header.tag == MEMHeapTag::ExpandedHeap); auto expHeapFlags = heap->attribs.value(); if (size == 0) { size = 1; } decaf_check(alignment != 0); internal::HeapLock lock { virt_addrof(heap->header) }; virt_ptr newBlock = nullptr; size = align_up(size, 4); if (alignment > 0) { auto foundBlock = virt_ptr { nullptr }; auto bestAlignedSize = 0xFFFFFFFFu; alignment = std::max(4, alignment); decaf_check((alignment & 0x3) == 0); for (auto block = heap->freeList.head; block; block = block->next) { auto alignedSize = getAlignedBlockSize(block, alignment, MEMExpHeapDirection::FromStart); if (alignedSize >= size) { if (expHeapFlags.allocMode() == MEMExpHeapMode::FirstFree) { foundBlock = block; break; } else { if (alignedSize < bestAlignedSize) { foundBlock = block; bestAlignedSize = alignedSize; } } } } if (foundBlock) { newBlock = createUsedBlockFromFreeBlock(heap, foundBlock, size, alignment, MEMExpHeapDirection::FromStart); } } else { alignment = std::max(4, -alignment); decaf_check((alignment & 0x3) == 0); auto foundBlock = virt_ptr { nullptr }; auto bestAlignedSize = 0xFFFFFFFFu; for (auto block = heap->freeList.head; block; block = block->next) { auto alignedSize = getAlignedBlockSize(block, alignment, MEMExpHeapDirection::FromEnd); if (alignedSize >= size) { if (expHeapFlags.allocMode() == MEMExpHeapMode::FirstFree) { foundBlock = block; break; } else { if (alignedSize < bestAlignedSize) { foundBlock = block; bestAlignedSize = alignedSize; } } } } if (foundBlock) { newBlock = createUsedBlockFromFreeBlock(heap, foundBlock, size, alignment, MEMExpHeapDirection::FromEnd); } } if (!newBlock) { MEMDumpHeap(virt_addrof(heap->header)); return nullptr; } return getBlockDataStart(newBlock); } void MEMFreeToExpHeap(MEMHeapHandle handle, virt_ptr mem) { auto heap = virt_cast(handle); decaf_check(heap->header.tag == MEMHeapTag::ExpandedHeap); if (!mem) { return; } internal::HeapLock lock { virt_addrof(heap->header) }; // Find the block auto dataStart = virt_cast(mem); auto block = virt_cast(dataStart - sizeof(MEMExpHeapBlock)); // Get the bounding region for this block auto memStart = getBlockMemStart(block); auto memEnd = getBlockMemEnd(block); // Remove the block from the used list removeBlock(virt_addrof(heap->usedList), block); // Release the memory back to the heap free list releaseMemory(heap, memStart, memEnd); } MEMExpHeapMode MEMSetAllocModeForExpHeap(MEMHeapHandle handle, MEMExpHeapMode mode) { auto heap = virt_cast(handle); internal::HeapLock lock { virt_addrof(heap->header) }; auto expHeapAttribs = heap->attribs.value(); heap->attribs = expHeapAttribs.allocMode(mode); return expHeapAttribs.allocMode(); } MEMExpHeapMode MEMGetAllocModeForExpHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); internal::HeapLock lock { virt_addrof(heap->header) }; auto expHeapAttribs = heap->attribs.value(); return expHeapAttribs.allocMode(); } uint32_t MEMAdjustExpHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); internal::HeapLock lock { virt_addrof(heap->header) }; auto lastFreeBlock = heap->freeList.tail; if (!lastFreeBlock) { return 0; } auto blockData = virt_cast(lastFreeBlock) + sizeof(MEMExpHeapBlock); if (blockData + lastFreeBlock->blockSize != heap->header.dataEnd) { // This block is not for the end of the heap return 0; } // Remove the block from the free list decaf_check(!lastFreeBlock->next); if (lastFreeBlock->prev) { lastFreeBlock->prev->next = nullptr; } // Move the heaps end pointer to the true start point of this block heap->header.dataEnd = getBlockMemStart(lastFreeBlock); auto heapMemStart = virt_cast(heap); auto heapMemEnd = virt_cast(heap->header.dataEnd); return static_cast(heapMemEnd - heapMemStart); } uint32_t MEMResizeForMBlockExpHeap(MEMHeapHandle handle, virt_ptr ptr, uint32_t size) { auto heap = virt_cast(handle); internal::HeapLock lock { virt_addrof(heap->header) }; size = align_up(size, 4); auto block = getUsedMemBlock(ptr); if (size < block->blockSize) { auto releasedSpace = block->blockSize - size; if (releasedSpace > sizeof(MEMExpHeapBlock) + 0x4) { auto releasedMemEnd = getBlockMemEnd(block); auto releasedMemStart = releasedMemEnd - releasedSpace; block->blockSize -= releasedSpace; releaseMemory(heap, releasedMemStart, releasedMemEnd); } } else if (size > block->blockSize) { auto blockMemEnd = getBlockMemEnd(block); auto freeBlock = virt_ptr { nullptr }; for (auto i = heap->freeList.head; i; i = i->next) { auto freeBlockMemStart = getBlockMemStart(i); if (freeBlockMemStart == blockMemEnd) { freeBlock = i; break; } // Free list is sorted, so we only need to search a little bit if (freeBlockMemStart > blockMemEnd) { break; } } if (!freeBlock) { return 0; } // Grab the data we need from the free block auto freeBlockMemStart = getBlockMemStart(freeBlock); auto freeBlockMemEnd = getBlockMemEnd(freeBlock); auto freeMemSize = static_cast(freeBlockMemEnd - freeBlockMemStart); // Drop the free block from the list of free regions removeBlock(virt_addrof(heap->freeList), freeBlock); // Adjust the sizing of the free area and the block auto newAllocSize = (size - block->blockSize); freeMemSize -= newAllocSize; block->blockSize = size; if (heap->header.flags & MEMHeapFlags::ZeroAllocated) { memset(freeBlockMemStart, 0, newAllocSize); } else if(heap->header.flags & MEMHeapFlags::DebugMode) { auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Allocated); memset(freeBlockMemStart, fillVal, newAllocSize); } // If we have enough room to create a new free block, lets release // the memory back to the heap. Otherwise we just tack the remainder // onto the end of the block we resized. if (freeMemSize >= sizeof(MEMExpHeapBlock) + 0x4) { releaseMemory(heap, freeBlockMemEnd - freeMemSize, freeBlockMemEnd); } else { block->blockSize += freeMemSize; } } return block->blockSize; } uint32_t MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); auto freeSize = 0u; internal::HeapLock lock { virt_addrof(heap->header) }; for (auto block = heap->freeList.head; block; block = block->next) { freeSize += block->blockSize; } return freeSize; } uint32_t MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle handle, int32_t alignment) { auto heap = virt_cast(handle); auto largestFree = 0u; internal::HeapLock lock { virt_addrof(heap->header) }; if (alignment > 0) { decaf_check((alignment & 0x3) == 0); for (auto block = heap->freeList.head; block; block = block->next) { auto alignedSize = getAlignedBlockSize(block, alignment, MEMExpHeapDirection::FromStart); if (alignedSize > largestFree) { largestFree = alignedSize; } } } else { alignment = -alignment; decaf_check((alignment & 0x3) == 0); for (auto block = heap->freeList.head; block; block = block->next) { auto alignedSize = getAlignedBlockSize(block, alignment, MEMExpHeapDirection::FromEnd); if (alignedSize > largestFree) { largestFree = alignedSize; } } } return largestFree; } uint16_t MEMSetGroupIDForExpHeap(MEMHeapHandle handle, uint16_t id) { auto heap = virt_cast(handle); internal::HeapLock lock { virt_addrof(heap->header) }; auto originalGroupId = heap->groupId; heap->groupId = id; return originalGroupId; } uint16_t MEMGetGroupIDForExpHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); internal::HeapLock lock { virt_addrof(heap->header) }; return heap->groupId; } uint32_t MEMGetSizeForMBlockExpHeap(virt_ptr ptr) { auto addr = virt_cast(ptr); auto block = virt_cast(addr - sizeof(MEMExpHeapBlock)); return block->blockSize; } uint16_t MEMGetGroupIDForMBlockExpHeap(virt_ptr ptr) { auto addr = virt_cast(ptr); auto block = virt_cast(addr - sizeof(MEMExpHeapBlock)); return block->attribs.value().groupId(); } MEMExpHeapDirection MEMGetAllocDirForMBlockExpHeap(virt_ptr ptr) { auto addr = virt_cast(ptr); auto block = virt_cast(addr - sizeof(MEMExpHeapBlock)); return block->attribs.value().allocDir(); } namespace internal { void dumpExpandedHeap(virt_ptr heap) { internal::HeapLock lock { virt_addrof(heap->header) }; gLog->debug("MEMExpHeap({})", heap); gLog->debug("Status Address Size Group"); for (auto block = heap->freeList.head; block; block = block->next) { auto attribs = static_cast(block->attribs); gLog->debug("FREE {} 0x{:8x} {:d}", block, static_cast(block->blockSize), attribs.groupId()); } for (auto block = heap->usedList.head; block; block = block->next) { auto attribs = static_cast(block->attribs); gLog->debug("USED {} 0x{:8x} {:d}", block, static_cast(block->blockSize), attribs.groupId()); } } } // namespace internal void Library::registerMemExpHeapSymbols() { RegisterFunctionExport(MEMCreateExpHeapEx); RegisterFunctionExport(MEMDestroyExpHeap); RegisterFunctionExport(MEMAllocFromExpHeapEx); RegisterFunctionExport(MEMFreeToExpHeap); RegisterFunctionExport(MEMSetAllocModeForExpHeap); RegisterFunctionExport(MEMGetAllocModeForExpHeap); RegisterFunctionExport(MEMAdjustExpHeap); RegisterFunctionExport(MEMResizeForMBlockExpHeap); RegisterFunctionExport(MEMGetTotalFreeSizeForExpHeap); RegisterFunctionExport(MEMGetAllocatableSizeForExpHeapEx); RegisterFunctionExport(MEMSetGroupIDForExpHeap); RegisterFunctionExport(MEMGetGroupIDForExpHeap); RegisterFunctionExport(MEMGetSizeForMBlockExpHeap); RegisterFunctionExport(MEMGetGroupIDForMBlockExpHeap); RegisterFunctionExport(MEMGetAllocDirForMBlockExpHeap); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memexpheap.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_memheap.h" #include #include namespace cafe::coreinit { /** * \defgroup coreinit_expheap Expanded Heap * \ingroup coreinit * @{ */ BITFIELD_BEG(MEMExpHeapAttribs, uint16_t) BITFIELD_ENTRY(0, 1, MEMExpHeapMode, allocMode); BITFIELD_ENTRY(1, 1, bool, reuseAlignSpace); BITFIELD_END BITFIELD_BEG(MEMExpHeapBlockAttribs, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, groupId); BITFIELD_ENTRY(8, 23, uint32_t, alignment); BITFIELD_ENTRY(31, 1, MEMExpHeapDirection, allocDir); BITFIELD_END #pragma pack(push, 1) struct MEMExpHeapBlock { be2_val attribs; be2_val blockSize; be2_virt_ptr prev; be2_virt_ptr next; be2_val tag; PADDING(0x02); }; CHECK_OFFSET(MEMExpHeapBlock, 0x00, attribs); CHECK_OFFSET(MEMExpHeapBlock, 0x04, blockSize); CHECK_OFFSET(MEMExpHeapBlock, 0x08, prev); CHECK_OFFSET(MEMExpHeapBlock, 0x0c, next); CHECK_OFFSET(MEMExpHeapBlock, 0x10, tag); CHECK_SIZE(MEMExpHeapBlock, 0x14); struct MEMExpHeapBlockList { be2_virt_ptr head; be2_virt_ptr tail; }; CHECK_OFFSET(MEMExpHeapBlockList, 0x00, head); CHECK_OFFSET(MEMExpHeapBlockList, 0x04, tail); CHECK_SIZE(MEMExpHeapBlockList, 0x08); struct MEMExpHeap { be2_struct header; be2_struct freeList; be2_struct usedList; be2_val groupId; be2_val attribs; }; CHECK_OFFSET(MEMExpHeap, 0x00, header); CHECK_OFFSET(MEMExpHeap, 0x40, freeList); CHECK_OFFSET(MEMExpHeap, 0x48, usedList); CHECK_OFFSET(MEMExpHeap, 0x50, groupId); CHECK_OFFSET(MEMExpHeap, 0x52, attribs); CHECK_SIZE(MEMExpHeap, 0x54); #pragma pack(pop) MEMHeapHandle MEMCreateExpHeapEx(virt_ptr base, uint32_t size, uint32_t flags); virt_ptr MEMDestroyExpHeap(MEMHeapHandle handle); virt_ptr MEMAllocFromExpHeapEx(MEMHeapHandle handle, uint32_t size, int32_t alignment); void MEMFreeToExpHeap(MEMHeapHandle handle, virt_ptr block); MEMExpHeapMode MEMSetAllocModeForExpHeap(MEMHeapHandle handle, MEMExpHeapMode mode); MEMExpHeapMode MEMGetAllocModeForExpHeap(MEMHeapHandle handle); uint32_t MEMAdjustExpHeap(MEMHeapHandle handle); uint32_t MEMResizeForMBlockExpHeap(MEMHeapHandle handle, virt_ptr block, uint32_t size); uint32_t MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle handle); uint32_t MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle handle, int32_t alignment); uint16_t MEMSetGroupIDForExpHeap(MEMHeapHandle handle, uint16_t id); uint16_t MEMGetGroupIDForExpHeap(MEMHeapHandle handle); uint32_t MEMGetSizeForMBlockExpHeap(virt_ptr block); uint16_t MEMGetGroupIDForMBlockExpHeap(virt_ptr block); MEMExpHeapDirection MEMGetAllocDirForMBlockExpHeap(virt_ptr block); namespace internal { void dumpExpandedHeap(virt_ptr handle); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memframeheap.cpp ================================================ #include "coreinit.h" #include "coreinit_memheap.h" #include "coreinit_memframeheap.h" #include "coreinit_memory.h" namespace cafe::coreinit { MEMHeapHandle MEMCreateFrmHeapEx(virt_ptr base, uint32_t size, uint32_t flags) { auto baseMem = virt_cast(base); // Align start and end to 4 byte boundary auto start = align_up(baseMem, 4); auto end = align_down(baseMem + size, 4); if (start >= end) { return nullptr; } if (end - start < sizeof(MEMFrameHeap)) { return nullptr; } decaf_check(base); // Setup the frame heap auto heap = virt_cast(start); internal::registerHeap(virt_addrof(heap->header), MEMHeapTag::FrameHeap, start + sizeof(MEMFrameHeap), end, static_cast(flags)); heap->head = heap->header.dataStart; heap->tail = heap->header.dataEnd; heap->previousState = nullptr; return virt_cast(heap); } virt_ptr MEMDestroyFrmHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::unregisterHeap(virt_addrof(heap->header)); return heap; } virt_ptr MEMAllocFromFrmHeapEx(MEMHeapHandle handle, uint32_t size, int alignment) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); // Yes coreinit.rpl actually does this if (size == 0) { size = 1; } internal::HeapLock lock { virt_addrof(heap->header) }; auto block = virt_ptr { nullptr }; if (alignment < 0) { // Allocate from bottom auto tail = align_down(heap->tail - size, -alignment); if (tail < heap->head) { // Not enough space! return nullptr; } heap->tail = tail; block = tail; } else { // Allocate from head auto addr = align_up(heap->head, alignment); auto head = addr + size; if (head > heap->tail) { // Not enough space! return nullptr; } heap->head = head; block = addr; } lock.unlock(); if (heap->header.flags & MEMHeapFlags::ZeroAllocated) { memset(block, 0, size); } else if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated); memset(block, value, size); } return block; } void MEMFreeToFrmHeap(MEMHeapHandle handle, MEMFrameHeapFreeMode mode) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::HeapLock lock { virt_addrof(heap->header) }; if (mode & MEMFrameHeapFreeMode::Head) { if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed); std::memset(heap->header.dataStart.get(), value, heap->head - heap->header.dataStart); } heap->head = heap->header.dataStart; heap->previousState = nullptr; } if (mode & MEMFrameHeapFreeMode::Tail) { if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed); std::memset(heap->tail.get(), value, heap->header.dataEnd - heap->tail); } heap->tail = heap->header.dataEnd; heap->previousState = nullptr; } } BOOL MEMRecordStateForFrmHeap(MEMHeapHandle handle, uint32_t tag) { auto result = FALSE; auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::HeapLock lock { virt_addrof(heap->header) }; auto state = virt_cast( MEMAllocFromFrmHeapEx(handle, sizeof(MEMFrameHeapState), 4)); if (state) { state->tag = tag; state->head = heap->head; state->tail = heap->tail; state->previous = heap->previousState; heap->previousState = state; result = TRUE; } return result; } BOOL MEMFreeByStateToFrmHeap(MEMHeapHandle handle, uint32_t tag) { auto result = FALSE; auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::HeapLock lock { virt_addrof(heap->header) }; // Find the state to reset to auto state = heap->previousState; if (tag != 0) { while (state) { if (state->tag == tag) { break; } state = state->previous; } } // Reset to state if (state) { if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed); std::memset(state->head.get(), value, heap->head - state->head); std::memset(heap->tail.get(), value, state->tail - heap->tail); } heap->head = state->head; heap->tail = state->tail; heap->previousState = state->previous; result = TRUE; } return result; } uint32_t MEMAdjustFrmHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::HeapLock lock { virt_addrof(heap->header) }; auto result = 0u; // We can only adjust the heap if we have no tail allocated memory if (heap->tail == heap->header.dataEnd) { heap->header.dataEnd = heap->head; heap->tail = heap->head; auto heapMemStart = virt_cast(heap); result = static_cast(heap->header.dataEnd - heapMemStart); } return result; } uint32_t MEMResizeForMBlockFrmHeap(MEMHeapHandle handle, virt_ptr address, uint32_t size) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::HeapLock lock { virt_addrof(heap->header) }; auto result = 0u; decaf_check(address > heap->head); decaf_check(address < heap->tail); decaf_check(heap->previousState == nullptr || heap->previousState < address); if (size == 0) { size = 1; } auto addrMem = virt_cast(address); auto end = align_up(addrMem + size, 4); if (end > heap->tail) { // Not enough free space result = 0; } else if (end == heap->head) { // Same size result = size; } else if (end < heap->head) { // Decrease size if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed); std::memset(end.get(), value, heap->head - addrMem); } heap->head = end; result = size; } else if (end > heap->head) { // Increase size if (heap->header.flags & MEMHeapFlags::ZeroAllocated) { std::memset(heap->head.get(), 0, addrMem - heap->head); } else if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated); std::memset(heap->head.get(), value, addrMem - heap->head); } heap->head = end; result = size; } return result; } uint32_t MEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle handle, int alignment) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::FrameHeap); internal::HeapLock lock { virt_addrof(heap->header) }; auto alignedHead = align_up(heap->head, alignment); auto result = 0u; if (alignedHead < heap->tail) { result = static_cast(heap->tail - alignedHead); } return result; } void Library::registerMemFrmHeapSymbols() { RegisterFunctionExport(MEMCreateFrmHeapEx); RegisterFunctionExport(MEMDestroyFrmHeap); RegisterFunctionExport(MEMAllocFromFrmHeapEx); RegisterFunctionExport(MEMFreeToFrmHeap); RegisterFunctionExport(MEMRecordStateForFrmHeap); RegisterFunctionExport(MEMFreeByStateToFrmHeap); RegisterFunctionExport(MEMAdjustFrmHeap); RegisterFunctionExport(MEMResizeForMBlockFrmHeap); RegisterFunctionExport(MEMGetAllocatableSizeForFrmHeapEx); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memframeheap.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_memheap.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_frameheap Frame Heap * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct MEMFrameHeapState { //! Tag used to identify the state for MEMFreeByStateToFrmHeap. be2_val tag; //! Saved head address for frame heap. be2_virt_ptr head; //! Saved tail address for frame heap. be2_virt_ptr tail; //! Pointer to the previous recorded frame heap state. be2_virt_ptr previous; }; CHECK_OFFSET(MEMFrameHeapState, 0x00, tag); CHECK_OFFSET(MEMFrameHeapState, 0x04, head); CHECK_OFFSET(MEMFrameHeapState, 0x08, tail); CHECK_OFFSET(MEMFrameHeapState, 0x0C, previous); CHECK_SIZE(MEMFrameHeapState, 0x10); struct MEMFrameHeap { be2_struct header; //! Current address of the head of the frame heap. be2_virt_ptr head; //! Current address of the tail of the frame heap. be2_virt_ptr tail; //! Pointer to the previous recorded frame heap state. be2_virt_ptr previousState; }; CHECK_OFFSET(MEMFrameHeap, 0x00, header); CHECK_OFFSET(MEMFrameHeap, 0x40, head); CHECK_OFFSET(MEMFrameHeap, 0x44, tail); CHECK_OFFSET(MEMFrameHeap, 0x48, previousState); CHECK_SIZE(MEMFrameHeap, 0x4C); #pragma pack(pop) MEMHeapHandle MEMCreateFrmHeapEx(virt_ptr base, uint32_t size, uint32_t flags); virt_ptr MEMDestroyFrmHeap(MEMHeapHandle heap); virt_ptr MEMAllocFromFrmHeapEx(MEMHeapHandle heap, uint32_t size, int alignment); void MEMFreeToFrmHeap(MEMHeapHandle heap, MEMFrameHeapFreeMode mode); BOOL MEMRecordStateForFrmHeap(MEMHeapHandle heap, uint32_t tag); BOOL MEMFreeByStateToFrmHeap(MEMHeapHandle heap, uint32_t tag); uint32_t MEMAdjustFrmHeap(MEMHeapHandle heap); uint32_t MEMResizeForMBlockFrmHeap(MEMHeapHandle heap, virt_ptr address, uint32_t size); uint32_t MEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle heap, int alignment); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memheap.cpp ================================================ #include "coreinit.h" #include "coreinit_memexpheap.h" #include "coreinit_memheap.h" #include "coreinit_memlist.h" #include "coreinit_memory.h" #include "coreinit_memunitheap.h" #include "coreinit_spinlock.h" #include "cafe/cafe_stackobject.h" #include #include #include #include namespace cafe::coreinit { struct StaticMemHeapData { be2_val initialisedLock; be2_struct lock; be2_val initialisedLists; be2_struct foregroundList; be2_struct mem1List; be2_struct mem2List; be2_array arenas; be2_array fillValues; }; static virt_ptr sMemHeapData = nullptr; static virt_ptr findListContainingHeap(virt_ptr heap) { auto start = StackObject { }; auto end = StackObject { }; auto size = StackObject { }; OSGetForegroundBucket(start, size); *end = *start + *size; if (virt_cast(heap->dataStart) >= *start && virt_cast(heap->dataEnd) <= *end) { return virt_addrof(sMemHeapData->foregroundList); } OSGetMemBound(OSMemoryType::MEM1, start, size); *end = *start + *size; if (virt_cast(heap->dataStart) >= *start && virt_cast(heap->dataEnd) <= *end) { return virt_addrof(sMemHeapData->mem1List); } else { return virt_addrof(sMemHeapData->mem2List); } } static virt_ptr findListContainingBlock(virt_ptr block) { auto start = StackObject { }; auto end = StackObject { }; auto size = StackObject { }; OSGetForegroundBucket(start, size); *end = *start + *size; if (virt_cast(block) >= *start && virt_cast(block) <= *end) { return virt_addrof(sMemHeapData->foregroundList); } OSGetMemBound(OSMemoryType::MEM1, start, size); *end = *start + *size; if (virt_cast(block) >= *start && virt_cast(block) <= *end) { return virt_addrof(sMemHeapData->mem1List); } else { return virt_addrof(sMemHeapData->mem2List); } } static virt_ptr findHeapContainingBlock(virt_ptr list, virt_ptr block) { virt_ptr heap = nullptr; while ((heap = virt_cast(MEMGetNextListObject(list, heap)))) { if (virt_cast(block) >= virt_cast(heap->dataStart) && virt_cast(block) < virt_cast(heap->dataEnd)) { auto child = findHeapContainingBlock(virt_addrof(heap->list), block); return child ? child : heap; } } return nullptr; } void MEMDumpHeap(virt_ptr heap) { switch (heap->tag) { case MEMHeapTag::ExpandedHeap: internal::dumpExpandedHeap(virt_cast(heap)); break; case MEMHeapTag::UnitHeap: internal::dumpUnitHeap(virt_cast(heap)); break; case MEMHeapTag::FrameHeap: case MEMHeapTag::UserHeap: case MEMHeapTag::BlockHeap: gLog->warn("Unimplemented MEMDumpHeap for tag {:08x}", heap->tag); } } virt_ptr MEMFindContainHeap(virt_ptr block) { if (auto list = findListContainingBlock(block)) { return findHeapContainingBlock(list, block); } return nullptr; } MEMBaseHeapType MEMGetArena(virt_ptr heap) { for (auto i = 0u; i < sMemHeapData->arenas.size(); ++i) { if (sMemHeapData->arenas[i] == heap) { return static_cast(i); } } return MEMBaseHeapType::Invalid; } MEMHeapHandle MEMGetBaseHeapHandle(MEMBaseHeapType type) { if (type < sMemHeapData->arenas.size()) { return sMemHeapData->arenas[type]; } else { return nullptr; } } MEMHeapHandle MEMSetBaseHeapHandle(MEMBaseHeapType type, MEMHeapHandle heap) { if (type < sMemHeapData->arenas.size()) { auto previous = sMemHeapData->arenas[type]; sMemHeapData->arenas[type] = heap; return previous; } else { return nullptr; } } MEMHeapHandle MEMCreateUserHeapHandle(virt_ptr heap, uint32_t size) { auto dataStart = virt_cast(heap) + sizeof(MEMHeapHeader); auto dataEnd = dataStart + size; internal::registerHeap(heap, coreinit::MEMHeapTag::UserHeap, dataStart, dataEnd, MEMHeapFlags::None); return heap; } uint32_t MEMGetFillValForHeap(MEMHeapFillType type) { OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock)); auto value = sMemHeapData->fillValues[type]; OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock)); return value; } void MEMSetFillValForHeap(MEMHeapFillType type, uint32_t value) { OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock)); sMemHeapData->fillValues[type] = value; OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock)); } namespace internal { void registerHeap(virt_ptr heap, MEMHeapTag tag, virt_ptr dataStart, virt_ptr dataEnd, MEMHeapFlags flags) { // Setup heap header heap->tag = tag; heap->dataStart = dataStart; heap->dataEnd = dataEnd; heap->flags = flags; if (heap->flags & MEMHeapFlags::DebugMode) { auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Unused); std::memset(dataStart.get(), fillVal, dataEnd - dataStart); } MEMInitList(virt_addrof(heap->list), offsetof(MEMHeapHeader, link)); if (!sMemHeapData->initialisedLock) { OSInitSpinLock(virt_addrof(sMemHeapData->lock)); sMemHeapData->initialisedLock = TRUE; } if (!sMemHeapData->initialisedLists) { MEMInitList(virt_addrof(sMemHeapData->foregroundList), offsetof(MEMHeapHeader, link)); MEMInitList(virt_addrof(sMemHeapData->mem1List), offsetof(MEMHeapHeader, link)); MEMInitList(virt_addrof(sMemHeapData->mem2List), offsetof(MEMHeapHeader, link)); sMemHeapData->initialisedLists = TRUE; } OSInitSpinLock(virt_addrof(heap->lock)); // Add to heap list OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock)); if (auto list = findListContainingHeap(heap)) { MEMAppendListObject(list, heap); } OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock)); } void unregisterHeap(virt_ptr heap) { OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock)); if (auto list = findListContainingHeap(heap)) { MEMRemoveListObject(list, heap); } OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock)); } void initialiseMemHeap() { OSInitSpinLock(virt_addrof(sMemHeapData->lock)); sMemHeapData->arenas.fill(nullptr); sMemHeapData->fillValues[0] = 0xC3C3C3C3u; sMemHeapData->fillValues[1] = 0xF3F3F3F3u; sMemHeapData->fillValues[2] = 0xD3D3D3D3u; } } // namespace internal void Library::registerMemHeapSymbols() { RegisterFunctionExport(MEMGetBaseHeapHandle); RegisterFunctionExport(MEMSetBaseHeapHandle); RegisterFunctionExport(MEMCreateUserHeapHandle); RegisterFunctionExport(MEMGetArena); RegisterFunctionExport(MEMFindContainHeap); RegisterFunctionExport(MEMDumpHeap); RegisterFunctionExport(MEMGetFillValForHeap); RegisterFunctionExport(MEMSetFillValForHeap); RegisterDataInternal(sMemHeapData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memheap.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_memlist.h" #include "coreinit_spinlock.h" #include #include namespace cafe::coreinit { /** * \defgroup coreinit_memheap Memory Heap * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct MEMHeapHeader { //! Tag indicating which type of heap this is be2_val tag; //! Link for list this heap is in be2_struct link; //! List of all child heaps in this heap be2_struct list; //! Start address of allocatable memory be2_virt_ptr dataStart; //! End address of allocatable memory be2_virt_ptr dataEnd; //! Lock used when MEM_HEAP_FLAG_USE_LOCK is set. be2_struct lock; //! Flags set during heap creation. be2_val flags; UNKNOWN(0x0C); }; CHECK_OFFSET(MEMHeapHeader, 0x00, tag); CHECK_OFFSET(MEMHeapHeader, 0x04, link); CHECK_OFFSET(MEMHeapHeader, 0x0C, list); CHECK_OFFSET(MEMHeapHeader, 0x18, dataStart); CHECK_OFFSET(MEMHeapHeader, 0x1C, dataEnd); CHECK_OFFSET(MEMHeapHeader, 0x20, lock); CHECK_OFFSET(MEMHeapHeader, 0x30, flags); CHECK_SIZE(MEMHeapHeader, 0x40); using MEMHeapHandle = virt_ptr; #pragma pack(pop) void MEMDumpHeap(virt_ptr heap); virt_ptr MEMFindContainHeap(virt_ptr block); MEMBaseHeapType MEMGetArena(virt_ptr heap); MEMHeapHandle MEMGetBaseHeapHandle(MEMBaseHeapType type); MEMHeapHandle MEMSetBaseHeapHandle(MEMBaseHeapType type, MEMHeapHandle handle); MEMHeapHandle MEMCreateUserHeapHandle(virt_ptr heap, uint32_t size); uint32_t MEMGetFillValForHeap(MEMHeapFillType type); void MEMSetFillValForHeap(MEMHeapFillType type, uint32_t value); /** @} */ namespace internal { class HeapLock { public: HeapLock(virt_ptr header) { if (header->flags & MEMHeapFlags::ThreadSafe) { OSUninterruptibleSpinLock_Acquire(virt_addrof(header->lock)); mHeap = header; } else { mHeap = nullptr; } } ~HeapLock() { if (mHeap) { OSUninterruptibleSpinLock_Release(virt_addrof(mHeap->lock)); mHeap = nullptr; } } void unlock() { if (mHeap) { OSUninterruptibleSpinLock_Release(virt_addrof(mHeap->lock)); mHeap = nullptr; } } private: virt_ptr mHeap; }; void registerHeap(virt_ptr heap, MEMHeapTag tag, virt_ptr dataStart, virt_ptr dataEnd, MEMHeapFlags flags); void unregisterHeap(virt_ptr heap); void initialiseMemHeap(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memlist.cpp ================================================ #include "coreinit.h" #include "coreinit_memlist.h" namespace cafe::coreinit { static virt_ptr getLink(virt_ptr list, virt_ptr object) { return virt_cast(virt_cast(object) + list->offsetToMEMListLink); } static void setFirstObject(virt_ptr list, virt_ptr object) { auto link = getLink(list, object); list->head = object; list->tail = object; link->next = nullptr; link->prev = nullptr; list->count = uint16_t { 1 }; } void MEMInitList(virt_ptr list, uint16_t offsetToMEMListLink) { list->head = nullptr; list->tail = nullptr; list->count = uint16_t { 0 }; list->offsetToMEMListLink = offsetToMEMListLink; } void MEMAppendListObject(virt_ptr list, virt_ptr object) { if (!list->tail) { setFirstObject(list, object); } else { auto link = getLink(list, object); auto tail = getLink(list, list->tail); tail->next = object; link->prev = list->tail; link->next = nullptr; list->tail = object; list->count++; } } void MEMPrependListObject(virt_ptr list, virt_ptr object) { if (!list->head) { setFirstObject(list, object); } else { auto link = getLink(list, object); auto head = getLink(list, list->head); head->prev = object; link->prev = nullptr; link->next = list->head; list->head = object; list->count++; } } void MEMInsertListObject(virt_ptr list, virt_ptr before, virt_ptr object) { if (!before) { // Insert at end MEMAppendListObject(list, object); return; } if (list->head == before) { // Insert before head MEMPrependListObject(list, object); return; } // Insert to middle of list auto link = getLink(list, object); auto other = getLink(list, before); link->prev = other->prev; link->next = before; other->prev = object; list->count++; } void MEMRemoveListObject(virt_ptr list, virt_ptr object) { virt_ptr head = nullptr; if (!object) { return; } if (list->head == object && list->tail == object) { // Clear list list->head = nullptr; list->tail = nullptr; list->count = uint16_t { 0 }; return; } if (list->head == object) { // Remove from head list->head = MEMGetNextListObject(list, list->head); if (list->head) { getLink(list, list->head)->prev = nullptr; } list->count--; return; } if (list->tail == object) { // Remove from tail list->tail = MEMGetPrevListObject(list, list->tail); if (list->tail) { getLink(list, list->tail)->next = nullptr; } list->count--; return; } do { head = MEMGetNextListObject(list, head); } while (head && head != object); if (head == object) { // Remove from middle of list auto link = getLink(list, object); auto next = link->next; auto prev = link->prev; getLink(list, prev)->next = next; getLink(list, next)->prev = prev; list->count--; } } virt_ptr MEMGetNextListObject(virt_ptr list, virt_ptr object) { if (!object) { return list->head; } return getLink(list, object)->next; } virt_ptr MEMGetPrevListObject(virt_ptr list, virt_ptr object) { if (!object) { return list->tail; } return getLink(list, object)->prev; } virt_ptr MEMGetNthListObject(virt_ptr list, uint16_t n) { auto head = list->head; for (auto i = 0u; i < n && head; ++i) { head = MEMGetNextListObject(list, head); } return head; } void Library::registerMemListSymbols() { RegisterFunctionExport(MEMInitList); RegisterFunctionExport(MEMAppendListObject); RegisterFunctionExport(MEMPrependListObject); RegisterFunctionExport(MEMInsertListObject); RegisterFunctionExport(MEMRemoveListObject); RegisterFunctionExport(MEMGetNextListObject); RegisterFunctionExport(MEMGetPrevListObject); RegisterFunctionExport(MEMGetNthListObject); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memlist.h ================================================ #pragma once #include namespace cafe::coreinit { /** * \defgroup coreinit_memlist Memory List * \ingroup coreinit * * A linked list used for memory heaps. * @{ */ #pragma pack(push, 1) struct MEMListLink { be2_virt_ptr prev; be2_virt_ptr next; }; CHECK_OFFSET(MEMListLink, 0x0, prev); CHECK_OFFSET(MEMListLink, 0x4, next); CHECK_SIZE(MEMListLink, 0x8); struct MEMList { be2_virt_ptr head; be2_virt_ptr tail; be2_val count; be2_val offsetToMEMListLink; }; CHECK_OFFSET(MEMList, 0x0, head); CHECK_OFFSET(MEMList, 0x4, tail); CHECK_OFFSET(MEMList, 0x8, count); CHECK_OFFSET(MEMList, 0xa, offsetToMEMListLink); CHECK_SIZE(MEMList, 0xc); #pragma pack(pop) void MEMInitList(virt_ptr list, uint16_t offsetToMEMListLink); void MEMAppendListObject(virt_ptr list, virt_ptr object); void MEMPrependListObject(virt_ptr list, virt_ptr object); void MEMInsertListObject(virt_ptr list, virt_ptr before, virt_ptr object); void MEMRemoveListObject(virt_ptr list, virt_ptr object); virt_ptr MEMGetNextListObject(virt_ptr list, virt_ptr object); virt_ptr MEMGetPrevListObject(virt_ptr list, virt_ptr object); virt_ptr MEMGetNthListObject(virt_ptr list, uint16_t n); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memory.cpp ================================================ #include "coreinit.h" #include "coreinit_internal_idlock.h" #include "coreinit_memory.h" #include "coreinit_systemheap.h" #include "coreinit_systeminfo.h" #include "cafe/cafe_stackobject.h" #include "cafe/kernel/cafe_kernel_mmu.h" #include "cafe/kernel/cafe_kernel_shareddata.h" #include #include namespace cafe::coreinit { struct StaticMemoryData { internal::IdLock boundsLock; be2_val foregroundPhysicalAddress; be2_val foregroundBaseAddress; be2_val foregroundSize; be2_val mem1BaseAddress; be2_val mem1Size; be2_val mem2BaseAddress; be2_val mem2Size; }; static virt_ptr sMemoryData = nullptr; enum ForegroundAreaId { Application = 0, TransitionAudioBuffer = 1, SavedFrameUnk2 = 2, SavedFrameUnk3 = 3, SavedFrameUnk4 = 4, SavedFrameUnk5 = 5, Unknown6 = 6, CopyArea = 7, }; struct ForegroundArea { ForegroundAreaId id; uint32_t offset; uint32_t size; }; constexpr std::array ForegroundAreas { ForegroundArea { ForegroundAreaId::Application, 0, 0x2800000 }, ForegroundArea { ForegroundAreaId::CopyArea, 0x2800000, 0x400000 }, ForegroundArea { ForegroundAreaId::TransitionAudioBuffer, 0x2C00000, 0x900000 }, ForegroundArea { ForegroundAreaId::SavedFrameUnk2, 0x3500000, 0x3C0000 }, ForegroundArea { ForegroundAreaId::SavedFrameUnk3, 0x38C0000, 0x1C0000 }, ForegroundArea { ForegroundAreaId::SavedFrameUnk4, 0x3A80000, 0x3C0000 }, ForegroundArea { ForegroundAreaId::SavedFrameUnk5, 0x3E40000, 0x1BF000 }, ForegroundArea { ForegroundAreaId::Unknown6, 0x3FFF000, 0x1000 }, }; static virt_ptr getForegroundAreaPointer(ForegroundAreaId id) { for (auto &area : ForegroundAreas) { if (area.id == id) { return virt_cast(sMemoryData->foregroundBaseAddress + area.offset); } } return nullptr; } static uint32_t getForegroundAreaSize(ForegroundAreaId id) { for (auto &area : ForegroundAreas) { if (area.id == id) { return area.size; } } return 0; } virt_ptr OSBlockMove(virt_ptr dst, virt_ptr src, uint32_t size, BOOL flush) { memmove(dst, src, size); return dst; } virt_ptr OSBlockSet(virt_ptr dst, int val, uint32_t size) { memset(dst, val, size); return dst; } /** * Get the foreground memory bucket address and size. * * \return * Returns TRUE if the current process is in the foreground. */ BOOL OSGetForegroundBucket(virt_ptr addr, virt_ptr size) { auto range = kernel::getForegroundBucket(); if (addr) { *addr = range.first; } if (size) { *size = range.second; } return range.first && range.second; } /** * Get the area of the foreground bucket which the application can use. * * \return * Returns TRUE if the current process is in the foreground. */ BOOL OSGetForegroundBucketFreeArea(virt_ptr addr, virt_ptr size) { if (addr) { *addr = virt_cast(getForegroundAreaPointer(ForegroundAreaId::Application)); } if (size) { *size = getForegroundAreaSize(ForegroundAreaId::Application); } return !!sMemoryData->foregroundBaseAddress; } int32_t OSGetMemBound(OSMemoryType type, virt_ptr addr, virt_ptr size) { if (addr) { *addr = virt_addr { 0u }; } if (size) { *size = 0u; } internal::acquireIdLockWithCoreId(sMemoryData->boundsLock); switch (type) { case OSMemoryType::MEM1: if (addr) { *addr = sMemoryData->mem1BaseAddress; } if (size) { *size = sMemoryData->mem1Size; } break; case OSMemoryType::MEM2: if (addr) { *addr = sMemoryData->mem2BaseAddress; } if (size) { *size = sMemoryData->mem2Size; } break; default: internal::releaseIdLockWithCoreId(sMemoryData->boundsLock); return -1; } internal::releaseIdLockWithCoreId(sMemoryData->boundsLock); return 0; } void OSGetAvailPhysAddrRange(virt_ptr start, virt_ptr size) { auto range = kernel::getAvailablePhysicalAddressRange(); if (start) { *start = range.first; } if (size) { *size = range.second; } } void OSGetDataPhysAddrRange(virt_ptr start, virt_ptr size) { auto range = kernel::getDataPhysicalAddressRange(); if (start) { *start = range.first; } if (size) { *size = range.second; } } void OSGetMapVirtAddrRange(virt_ptr start, virt_ptr size) { auto range = kernel::getVirtualMapAddressRange(); if (start) { *start = range.first; } if (size) { *size = range.second; } } BOOL OSGetSharedData(OSSharedDataType type, uint32_t unk_r4, virt_ptr> outPtr, virt_ptr outSize) { auto area = kernel::SharedArea { }; if (!outPtr || !outSize) { return FALSE; } switch (type) { case OSSharedDataType::FontChinese: area = kernel::getSharedArea(kernel::SharedAreaId::FontChinese); break; case OSSharedDataType::FontKorean: area = kernel::getSharedArea(kernel::SharedAreaId::FontKorean); break; case OSSharedDataType::FontStandard: area = kernel::getSharedArea(kernel::SharedAreaId::FontStandard); break; case OSSharedDataType::FontTaiwanese: area = kernel::getSharedArea(kernel::SharedAreaId::FontTaiwanese); break; default: return FALSE; } *outPtr = virt_cast(area.address); *outSize = area.size; return TRUE; } virt_addr OSAllocVirtAddr(virt_addr address, uint32_t size, uint32_t alignment) { return kernel::allocateVirtualAddress(address, size, alignment); } BOOL OSFreeVirtAddr(virt_addr address, uint32_t size) { return kernel::freeVirtualAddress(address, size) ? TRUE : FALSE; } int32_t OSQueryVirtAddr(virt_addr address) { return static_cast(kernel::queryVirtualAddress(address)); } BOOL OSMapMemory(virt_addr virtAddress, phys_addr physAddress, uint32_t size, int permission) { return kernel::mapMemory(virtAddress, physAddress, size, static_cast(permission)) ? TRUE : FALSE; } BOOL OSUnmapMemory(virt_addr virtAddress, uint32_t size) { return kernel::unmapMemory(virtAddress, size) ? TRUE : FALSE; } /** * Translates a virtual (effective) address to a physical address. */ phys_addr OSEffectiveToPhysical(virt_addr address) { if (address >= virt_addr { 0x10000000 } && address < internal::getMem2EndAddress()) { return internal::getMem2PhysAddress() + static_cast(address - 0x10000000); } if (address >= virt_addr { 0xF4000000 } && address < virt_addr { 0xF6000000 }) { return phys_addr { static_cast(address - 0xF4000000) }; } if (sMemoryData->foregroundBaseAddress && address >= sMemoryData->foregroundBaseAddress && address < sMemoryData->foregroundBaseAddress + sMemoryData->foregroundSize) { return sMemoryData->foregroundPhysicalAddress + static_cast(address - sMemoryData->foregroundBaseAddress); } return kernel::effectiveToPhysical(address); } /** * Translates a physical address to a virtual (effective) address. */ virt_addr OSPhysicalToEffectiveCached(phys_addr address) { return kernel::physicalToEffectiveCached(address); } /** * Translates a physical address to a virtual (effective) address. */ virt_addr OSPhysicalToEffectiveUncached(phys_addr address) { return kernel::physicalToEffectiveUncached(address); } /** * memcpy for virtual memory. */ virt_ptr memcpy(virt_ptr dst, virt_ptr src, uint32_t size) { std::memcpy(dst.get(), src.get(), size); return dst; } /** * memmove for virtual memory. */ virt_ptr memmove(virt_ptr dst, virt_ptr src, uint32_t size) { std::memmove(dst.get(), src.get(), size); return dst; } /** * memset for virtual memory. */ virt_ptr memset(virt_ptr dst, int value, uint32_t size) { std::memset(dst.get(), value, size); return dst; } namespace internal { void initialiseMemory() { sMemoryData->mem1BaseAddress = virt_addr { 0xF4000000 }; sMemoryData->mem1Size = 0x2000000u; auto mem2BaseAddress = align_up(getMem2BaseAddress(), 4096); auto mem2EndAddress = align_down(getMem2EndAddress(), 4096); auto systemHeapBaseAddress = mem2BaseAddress; auto systemHeapSize = getSystemHeapSize(); initialiseSystemHeap(virt_cast(systemHeapBaseAddress), systemHeapSize); sMemoryData->mem2BaseAddress = align_up(mem2BaseAddress + systemHeapSize, 4096); sMemoryData->mem2Size = static_cast(mem2EndAddress - sMemoryData->mem2BaseAddress); OSGetForegroundBucket(virt_addrof(sMemoryData->foregroundBaseAddress), virt_addrof(sMemoryData->foregroundSize)); sMemoryData->foregroundPhysicalAddress = kernel::effectiveToPhysical(sMemoryData->foregroundBaseAddress); } } // namespace internal void Library::registerMemorySymbols() { RegisterFunctionExport(OSBlockMove); RegisterFunctionExport(OSBlockSet); RegisterFunctionExport(OSGetMemBound); RegisterFunctionExport(OSGetForegroundBucket); RegisterFunctionExport(OSGetForegroundBucketFreeArea); RegisterFunctionExport(OSGetAvailPhysAddrRange); RegisterFunctionExport(OSGetDataPhysAddrRange); RegisterFunctionExport(OSGetMapVirtAddrRange); RegisterFunctionExport(OSGetSharedData); RegisterFunctionExport(OSAllocVirtAddr); RegisterFunctionExport(OSFreeVirtAddr); RegisterFunctionExport(OSQueryVirtAddr); RegisterFunctionExport(OSMapMemory); RegisterFunctionExport(OSUnmapMemory); RegisterFunctionExport(OSEffectiveToPhysical); RegisterFunctionExportName("__OSPhysicalToEffectiveCached", OSPhysicalToEffectiveCached); RegisterFunctionExportName("__OSPhysicalToEffectiveUncached", OSPhysicalToEffectiveUncached); RegisterFunctionExport(memcpy); RegisterFunctionExport(memmove); RegisterFunctionExport(memset); RegisterDataInternal(sMemoryData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memory.h ================================================ #pragma once #include "coreinit_enum.h" #include namespace cafe::coreinit { virt_ptr OSBlockMove(virt_ptr dst, virt_ptr src, uint32_t size, BOOL flush); virt_ptr OSBlockSet(virt_ptr dst, int val, uint32_t size); BOOL OSGetForegroundBucket(virt_ptr addr, virt_ptr size); BOOL OSGetForegroundBucketFreeArea(virt_ptr addr, virt_ptr size); int OSGetMemBound(OSMemoryType type, virt_ptr addr, virt_ptr size); void OSGetAvailPhysAddrRange(virt_ptr start, virt_ptr size); void OSGetDataPhysAddrRange(virt_ptr start, virt_ptr size); void OSGetMapVirtAddrRange(virt_ptr start, virt_ptr size); BOOL OSGetSharedData(OSSharedDataType type, uint32_t unk_r4, virt_ptr> outPtr, virt_ptr outSize); virt_addr OSAllocVirtAddr(virt_addr address, uint32_t size, uint32_t alignment); BOOL OSFreeVirtAddr(virt_addr address, uint32_t size); int OSQueryVirtAddr(virt_addr virtAddress); BOOL OSMapMemory(virt_addr virtAddress, phys_addr physAddress, uint32_t size, int permission); BOOL OSUnmapMemory(virt_addr virtAddress, uint32_t size); phys_addr OSEffectiveToPhysical(virt_addr address); virt_addr OSPhysicalToEffectiveCached(phys_addr address); virt_addr OSPhysicalToEffectiveUncached(phys_addr address); virt_ptr memcpy(virt_ptr dst, virt_ptr src, uint32_t size); virt_ptr memmove(virt_ptr dst, virt_ptr src, uint32_t size); virt_ptr memset(virt_ptr dst, int value, uint32_t size); namespace internal { void initialiseMemory(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memunitheap.cpp ================================================ #include "coreinit.h" #include "coreinit_memunitheap.h" #include "coreinit_memory.h" #include #include #include #include namespace cafe::coreinit { /** * Initialise a unit heap. * * Adds it to the list of active heaps. */ MEMHeapHandle MEMCreateUnitHeapEx(virt_ptr base, uint32_t size, uint32_t blockSize, int32_t alignment, uint32_t flags) { auto baseMem = virt_cast(base); // Align start and end to 4 byte boundary auto start = align_up(baseMem, 4); auto end = align_down(baseMem + size, 4); if (start >= end) { return nullptr; } // Get first block aligned start auto dataStart = align_up(start + sizeof(MEMUnitHeap), alignment); if (dataStart >= end) { return nullptr; } // Calculate aligned block size and count auto alignedBlockSize = align_up(blockSize, alignment); auto blockCount = (end - dataStart) / alignedBlockSize; if (blockCount == 0) { return nullptr; } decaf_check(base); auto heap = virt_cast(start); // Register Heap internal::registerHeap(virt_addrof(heap->header), MEMHeapTag::UnitHeap, dataStart, dataStart + alignedBlockSize * blockCount, static_cast(flags)); // Setup the MEMUnitHeap auto firstBlock = virt_cast(dataStart); heap->freeBlocks = firstBlock; heap->blockSize = alignedBlockSize; // Setup free block linked list auto prev = virt_ptr { nullptr }; for (auto i = 0u; i < blockCount; ++i) { auto block = virt_cast(dataStart + alignedBlockSize * i); if (prev) { prev->next = block; } prev = block; } if (prev) { prev->next = nullptr; } return virt_cast(heap); } /** * Destroy unit heap. * * Remove it from the list of active heaps. */ virt_ptr MEMDestroyUnitHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::UnitHeap); internal::unregisterHeap(virt_addrof(heap->header)); return heap; } /** * Allocate a memory block from a unit heap */ virt_ptr MEMAllocFromUnitHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::UnitHeap); internal::HeapLock lock { virt_addrof(heap->header) }; auto block = heap->freeBlocks; if (block) { heap->freeBlocks = block->next; } lock.unlock(); if (block) { if (heap->header.flags & MEMHeapFlags::ZeroAllocated) { memset(block, 0, heap->blockSize); } else if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated); memset(block, value, heap->blockSize); } } return block; } /** * Free a memory block in a unit heap */ void MEMFreeToUnitHeap(MEMHeapHandle handle, virt_ptr block) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::UnitHeap); if (!block) { return; } if (heap->header.flags & MEMHeapFlags::DebugMode) { auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed); memset(block, value, heap->blockSize); } internal::HeapLock lock { virt_addrof(heap->header) }; auto freeBlock = virt_cast(block); freeBlock->next = heap->freeBlocks; heap->freeBlocks = freeBlock; } /** * Count the number of free blocks in a unit heap. */ uint32_t MEMCountFreeBlockForUnitHeap(MEMHeapHandle handle) { auto heap = virt_cast(handle); decaf_check(heap); decaf_check(heap->header.tag == MEMHeapTag::UnitHeap); internal::HeapLock lock { virt_addrof(heap->header) }; auto count = 0u; for (auto block = heap->freeBlocks; block; block = block->next) { count++; } return count; } /** * Calculate the size required for a unit heap containing blockCount blocks of blockSize. */ uint32_t MEMCalcHeapSizeForUnitHeap(uint32_t blockSize, uint32_t blockCount, int alignment) { auto alignedBlockSize = align_up(blockSize, alignment); auto totalBlockSize = alignedBlockSize * blockCount; auto headerSize = alignment - 4 + static_cast(sizeof(MEMUnitHeap)); return headerSize + totalBlockSize; } namespace internal { /** * Print debug information about the unit heap. */ void dumpUnitHeap(virt_ptr heap) { auto handle = virt_cast(heap); auto freeBlocks = MEMCountFreeBlockForUnitHeap(handle); auto freeSize = heap->blockSize * freeBlocks; auto totalSize = heap->header.dataEnd - heap->header.dataStart; auto usedSize = totalSize - freeSize; auto percent = static_cast(usedSize) / static_cast(totalSize); gLog->debug("MEMUnitHeap(0x{:8x})", heap); gLog->debug("{} out of {} bytes ({}%) used", usedSize, totalSize, percent); } } // namespace internal void Library::registerMemUnitHeapSymbols() { RegisterFunctionExport(MEMCreateUnitHeapEx); RegisterFunctionExport(MEMDestroyUnitHeap); RegisterFunctionExport(MEMAllocFromUnitHeap); RegisterFunctionExport(MEMFreeToUnitHeap); RegisterFunctionExport(MEMCountFreeBlockForUnitHeap); RegisterFunctionExport(MEMCalcHeapSizeForUnitHeap); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_memunitheap.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_memheap.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_unitheap Unit Heap * \ingroup coreinit * * A unit heap is a memory heap where every allocation is of a fixed size * determined at heap creation. * @{ */ #pragma pack(push, 1) struct MEMUnitHeapFreeBlock { be2_ptr next; }; CHECK_OFFSET(MEMUnitHeapFreeBlock, 0x00, next); CHECK_SIZE(MEMUnitHeapFreeBlock, 0x04); struct MEMUnitHeap { be2_struct header; be2_ptr freeBlocks; be2_val blockSize; }; CHECK_OFFSET(MEMUnitHeap, 0x00, header); CHECK_OFFSET(MEMUnitHeap, 0x40, freeBlocks); CHECK_OFFSET(MEMUnitHeap, 0x44, blockSize); CHECK_SIZE(MEMUnitHeap, 0x48); #pragma pack(pop) MEMHeapHandle MEMCreateUnitHeapEx(virt_ptr base, uint32_t size, uint32_t blockSize, int32_t alignment, uint32_t flags); virt_ptr MEMDestroyUnitHeap(MEMHeapHandle handle); virt_ptr MEMAllocFromUnitHeap(MEMHeapHandle handle); void MEMFreeToUnitHeap(MEMHeapHandle handle, virt_ptr block); uint32_t MEMCountFreeBlockForUnitHeap(MEMHeapHandle handle); uint32_t MEMCalcHeapSizeForUnitHeap(uint32_t blockSize, uint32_t count, int alignment); namespace internal { void dumpUnitHeap(virt_ptr heap); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_messagequeue.cpp ================================================ #include "coreinit.h" #include "coreinit_memory.h" #include "coreinit_messagequeue.h" #include "coreinit_scheduler.h" #include namespace cafe::coreinit { /** * Initialise a message queue structure. */ void OSInitMessageQueue(virt_ptr queue, virt_ptr messages, uint32_t size) { OSInitMessageQueueEx(queue, messages, size, nullptr); } /** * Initialise a message queue structure with a name. */ void OSInitMessageQueueEx(virt_ptr queue, virt_ptr messages, uint32_t size, virt_ptr name) { queue->tag = OSMessageQueue::Tag; queue->name = name; queue->messages = messages; queue->size = size; queue->first = 0u; queue->used = 0u; OSInitThreadQueueEx(virt_addrof(queue->sendQueue), queue); OSInitThreadQueueEx(virt_addrof(queue->recvQueue), queue); } /** * Insert a message into the queue. * * If the OSMessageFlags::HighPriority flag is set then the current thread will * block until there is space in the queue to insert the message, else it will * return immediately with the return value of FALSE. * * If the OSMessageFlags::HighPriority flag is set then the message will be * inserted at the front of the queue, otherwise it will be inserted at the back. * * \return Returns TRUE if the message was inserted in the queue. */ BOOL OSSendMessage(virt_ptr queue, virt_ptr message, OSMessageFlags flags) { unsigned index; internal::lockScheduler(); decaf_check(queue && queue->tag == OSMessageQueue::Tag); decaf_check(message); if (!(flags & OSMessageFlags::Blocking) && queue->used == queue->size) { // Do not block waiting for space to insert message. internal::unlockScheduler(); return FALSE; } // Wait for space in the message queue. while (queue->used == queue->size) { internal::sleepThreadNoLock(virt_addrof(queue->sendQueue)); internal::rescheduleSelfNoLock(); } if (flags & OSMessageFlags::HighPriority) { // High priorty messages are pushed to the front of the queue. if (queue->first == 0) { queue->first = queue->size - 1; } else { queue->first--; } index = queue->first; } else { // Normal messages are pushed to back of the queue. index = (queue->first + queue->used) % queue->size; } memcpy(queue->messages + index, message, sizeof(OSMessage)); queue->used++; // Wakeup threads waiting to read message internal::wakeupThreadNoLock(virt_addrof(queue->recvQueue)); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); return TRUE; } /** * Read and remove a message from the queue. * * If flags has OSMessageFlags::Blocking then the current thread will block * until there is a mesasge in the queue to read, else it will return * immediately with the return value of FALSE. * * \return Returns TRUE if a message was read from the queue. */ BOOL OSReceiveMessage(virt_ptr queue, virt_ptr message, OSMessageFlags flags) { internal::lockScheduler(); decaf_check(queue && queue->tag == OSMessageQueue::Tag); decaf_check(message); if (!(flags & OSMessageFlags::Blocking) && queue->used == 0) { // Do not block waiting for a message to arrive internal::unlockScheduler(); return FALSE; } // Wait for a message to appear in queue while (queue->used == 0) { internal::sleepThreadNoLock(virt_addrof(queue->recvQueue)); internal::rescheduleSelfNoLock(); } decaf_check(queue->used > 0); // Copy into message array memcpy(message, queue->messages + queue->first, sizeof(OSMessage)); queue->first = (queue->first + 1) % queue->size; queue->used--; // Wakeup threads waiting for space to send message internal::wakeupThreadNoLock(virt_addrof(queue->sendQueue)); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); return TRUE; } /** * Read and do NOT remove a message from the queue. * * \return Returns TRUE if a message was read from the queue. */ BOOL OSPeekMessage(virt_ptr queue, virt_ptr message) { internal::lockScheduler(); decaf_check(queue && queue->tag == OSMessageQueue::Tag); decaf_check(message); if (queue->used == 0) { internal::unlockScheduler(); return FALSE; } memcpy(message, queue->messages + queue->first, sizeof(OSMessage)); internal::unlockScheduler(); return TRUE; } void Library::registerMessageQueueSymbols() { RegisterFunctionExport(OSInitMessageQueue); RegisterFunctionExport(OSInitMessageQueueEx); RegisterFunctionExport(OSSendMessage); RegisterFunctionExport(OSReceiveMessage); RegisterFunctionExport(OSPeekMessage); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_messagequeue.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_thread.h" #include "coreinit_mutex.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_messagequeue Message Queue * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct OSMessage { be2_virt_ptr message; be2_val args[3]; }; CHECK_OFFSET(OSMessage, 0x00, message); CHECK_OFFSET(OSMessage, 0x04, args); CHECK_SIZE(OSMessage, 0x10); struct OSMessageQueue { static constexpr auto Tag = 0x6D536751u; be2_val tag; be2_virt_ptr name; UNKNOWN(4); be2_struct sendQueue; be2_struct recvQueue; be2_virt_ptr messages; be2_val size; be2_val first; be2_val used; }; CHECK_OFFSET(OSMessageQueue, 0x00, tag); CHECK_OFFSET(OSMessageQueue, 0x04, name); CHECK_OFFSET(OSMessageQueue, 0x0c, sendQueue); CHECK_OFFSET(OSMessageQueue, 0x1c, recvQueue); CHECK_OFFSET(OSMessageQueue, 0x2c, messages); CHECK_OFFSET(OSMessageQueue, 0x30, size); CHECK_OFFSET(OSMessageQueue, 0x34, first); CHECK_OFFSET(OSMessageQueue, 0x38, used); CHECK_SIZE(OSMessageQueue, 0x3c); #pragma pack(pop) void OSInitMessageQueue(virt_ptr queue, virt_ptr messages, uint32_t size); void OSInitMessageQueueEx(virt_ptr queue, virt_ptr messages, uint32_t size, virt_ptr name); BOOL OSSendMessage(virt_ptr queue, virt_ptr message, OSMessageFlags flags); BOOL OSReceiveMessage(virt_ptr queue, virt_ptr message, OSMessageFlags flags); BOOL OSPeekMessage(virt_ptr queue, virt_ptr message); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_mutex.cpp ================================================ #include "coreinit.h" #include "coreinit_mutex.h" #include "coreinit_scheduler.h" #include "coreinit_thread.h" #include "coreinit_internal_queue.h" #include namespace cafe::coreinit { using MutexQueue = internal::Queue; /** * Initialise a mutex structure. */ void OSInitMutex(virt_ptr mutex) { OSInitMutexEx(mutex, nullptr); } /** * Initialise a mutex structure with a name. */ void OSInitMutexEx(virt_ptr mutex, virt_ptr name) { mutex->tag = OSMutex::Tag; mutex->name = name; mutex->owner = nullptr; mutex->count = 0; OSInitThreadQueueEx(virt_addrof(mutex->queue), mutex); MutexQueue::initLink(mutex); } static void lockMutexNoLock(virt_ptr mutex) { auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); while (mutex->owner) { if (mutex->owner == thread) { mutex->count++; return; } // Mark this thread as waiting on the mutex thread->mutex = mutex; // Promote mutex owner priority internal::promoteThreadPriorityNoLock(mutex->owner, thread->priority); // Wait for other owner to unlock internal::sleepThreadNoLock(virt_addrof(mutex->queue)); internal::rescheduleSelfNoLock(); // We are no longer waiting on the mutex thread->mutex = nullptr; } // Set current thread to owner of mutex mutex->count++; mutex->owner = thread; MutexQueue::append(virt_addrof(thread->mutexQueue), mutex); thread->cancelState |= OSThreadCancelState::DisabledByMutex; } /** * Lock the mutex. * * If no one owns the mutex, set current thread as owner. * If the lock is owned by the current thread, increase the recursion count. * If the lock is owned by another thread, the current thread will sleep until * the owner has unlocked this mutex. * * Similar to std::recursive_mutex::lock. */ void OSLockMutex(virt_ptr mutex) { internal::lockScheduler(); internal::testThreadCancelNoLock(); lockMutexNoLock(mutex); internal::unlockScheduler(); } /** * Try to lock a mutex. * * If no one owns the mutex, set current thread as owner. * If the lock is owned by the current thread, increase the recursion count. * If the lock is owned by another thread, do not block, return FALSE. * * \return TRUE if the mutex is locked, FALSE if the mutex is owned by another thread. * * Similar to std::recursive_mutex::try_lock. */ BOOL OSTryLockMutex(virt_ptr mutex) { internal::lockScheduler(); auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); internal::testThreadCancelNoLock(); if (mutex->owner == thread) { mutex->count++; internal::unlockScheduler(); return TRUE; } else if (mutex->owner) { internal::unlockScheduler(); return FALSE; } // Set thread to owner of mutex mutex->count++; mutex->owner = thread; MutexQueue::append(virt_addrof(thread->mutexQueue), mutex); thread->cancelState |= OSThreadCancelState::DisabledByMutex; internal::unlockScheduler(); return TRUE; } /** * Unlocks the mutex. * * Will decrease the recursion count, will only unlock the mutex when the * recursion count reaches 0. * If any other threads are waiting to lock the mutex they will be woken. * * Similar to std::recursive_mutex::unlock. */ void OSUnlockMutex(virt_ptr mutex) { internal::lockScheduler(); auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); // Not the owner, ignore this call. if (mutex->owner != thread) { internal::unlockScheduler(); return; } // Decrement the mutexes lock count mutex->count--; // If we still own the mutex, lets just leave now if (mutex->count > 0) { internal::unlockScheduler(); return; } // Remove mutex from thread's mutex queue MutexQueue::erase(virt_addrof(thread->mutexQueue), mutex); // Clear the mutex owner mutex->owner = nullptr; // If we have a promoted priority, reset it. if (thread->priority < thread->basePriority) { thread->priority = internal::calculateThreadPriorityNoLock(thread); } // Clear the cancelState flag if we don't have any more mutexes locked if (!thread->mutexQueue.head) { thread->cancelState &= ~OSThreadCancelState::DisabledByMutex; } // Wakeup any threads trying to lock this mutex internal::wakeupThreadNoLock(virt_addrof(mutex->queue)); // Check if we are meant to cancel now internal::testThreadCancelNoLock(); // Reschedule everyone internal::rescheduleAllCoreNoLock(); // Unlock our scheduler and continue internal::unlockScheduler(); } /** * Initialise a condition variable structure. */ void OSInitCond(virt_ptr condition) { OSInitCondEx(condition, nullptr); } /** * Initialise a condition variable structure with a name. */ void OSInitCondEx(virt_ptr condition, virt_ptr name) { condition->tag = OSCondition::Tag; condition->name = name; OSInitThreadQueueEx(virt_addrof(condition->queue), condition); } /** * Sleep the current thread until the condition variable has been signalled. * * The mutex must be locked when entering this function. * Will unlock the mutex and then sleep, reacquiring the mutex when woken. * * Similar to std::condition_variable::wait. */ void OSWaitCond(virt_ptr condition, virt_ptr mutex) { internal::lockScheduler(); auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); decaf_check(mutex->owner == thread); // Save the count and then unlock the mutex auto mutexCount = mutex->count; mutex->count = 0; mutex->owner = nullptr; // Remove mutex from thread's mutex queue MutexQueue::erase(virt_addrof(thread->mutexQueue), mutex); // If we have a promoted priority, reset it. if (thread->priority < thread->basePriority) { thread->priority = internal::calculateThreadPriorityNoLock(thread); } // Wake anyone waiting on the mutex internal::disableScheduler(); internal::wakeupThreadNoLock(virt_addrof(mutex->queue)); internal::rescheduleAllCoreNoLock(); internal::enableScheduler(); // Sleep on the condition internal::sleepThreadNoLock(virt_addrof(condition->queue)); internal::rescheduleSelfNoLock(); // Relock the mutex lockMutexNoLock(mutex); mutex->count = mutexCount; internal::unlockScheduler(); } /** * Will wake up any threads waiting on the condition with OSWaitCond. * * Similar to std::condition_variable::notify_all. */ void OSSignalCond(virt_ptr condition) { OSWakeupThread(virt_addrof(condition->queue)); } namespace internal { void unlockAllMutexNoLock(virt_ptr thread) { while (auto mutex = thread->mutexQueue.head) { // Remove this mutex from our queue MutexQueue::erase(virt_addrof(thread->mutexQueue), mutex); // Release this mutex mutex->count = 0; mutex->owner = nullptr; // Wakeup any threads trying to lock this mutex internal::wakeupThreadNoLock(virt_addrof(mutex->queue)); } } } // namespace internal void Library::registerMutexSymbols() { RegisterFunctionExport(OSInitMutex); RegisterFunctionExport(OSInitMutexEx); RegisterFunctionExport(OSLockMutex); RegisterFunctionExport(OSTryLockMutex); RegisterFunctionExport(OSUnlockMutex); RegisterFunctionExport(OSInitCond); RegisterFunctionExport(OSInitCondEx); RegisterFunctionExport(OSWaitCond); RegisterFunctionExport(OSSignalCond); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_mutex.h ================================================ #pragma once #include "coreinit_thread.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_mutex Mutex * \ingroup coreinit * * Standard mutex and condition variable implementation. * * Similar to std::condition_variable. * Similar to std::recursive_mutex. * @{ */ #pragma pack(push, 1) struct OSMutex; struct OSMutexLink { be2_virt_ptr next; be2_virt_ptr prev; }; CHECK_OFFSET(OSMutexLink, 0x00, next); CHECK_OFFSET(OSMutexLink, 0x04, prev); CHECK_SIZE(OSMutexLink, 0x8); struct OSMutex { static constexpr auto Tag = 0x6D557458u; //! Should always be set to the value OSMutex::Tag. be2_val tag; //! Name set by OSInitMutexEx. be2_virt_ptr name; UNKNOWN(4); //! Queue of threads waiting for this mutex to unlock. be2_struct queue; //! Current owner of mutex. be2_virt_ptr owner; //! Current recursion lock count of mutex. be2_val count; //! Link used inside OSThread's mutex queue. be2_struct link; }; CHECK_OFFSET(OSMutex, 0x00, tag); CHECK_OFFSET(OSMutex, 0x04, name); CHECK_OFFSET(OSMutex, 0x0c, queue); CHECK_OFFSET(OSMutex, 0x1c, owner); CHECK_OFFSET(OSMutex, 0x20, count); CHECK_OFFSET(OSMutex, 0x24, link); CHECK_SIZE(OSMutex, 0x2c); struct OSCondition { static constexpr auto Tag = 0x634E6456u; //! Should always be set to the value OSCondition::Tag. be2_val tag; //! Name set by OSInitCondEx. be2_virt_ptr name; UNKNOWN(4); //! Queue of threads currently waiting on condition with OSWaitCond. be2_struct queue; }; CHECK_OFFSET(OSCondition, 0x00, tag); CHECK_OFFSET(OSCondition, 0x04, name); CHECK_OFFSET(OSCondition, 0x0c, queue); CHECK_SIZE(OSCondition, 0x1c); #pragma pack(pop) void OSInitMutex(virt_ptr mutex); void OSInitMutexEx(virt_ptr mutex, virt_ptr name); void OSLockMutex(virt_ptr mutex); void OSUnlockMutex(virt_ptr mutex); BOOL OSTryLockMutex(virt_ptr mutex); void OSInitCond(virt_ptr condition); void OSInitCondEx(virt_ptr condition, virt_ptr name); void OSWaitCond(virt_ptr condition, virt_ptr mutex); void OSSignalCond(virt_ptr condition); /** @} */ namespace internal { void unlockAllMutexNoLock(virt_ptr thread); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_osreport.cpp ================================================ #include "coreinit.h" #include "coreinit_cosreport.h" #include "coreinit_dynload.h" #include "coreinit_ghs.h" #include "coreinit_osreport.h" #include "coreinit_snprintf.h" #include "coreinit_systeminfo.h" #include "cafe/cafe_stackobject.h" #include #include #include #include #include namespace cafe::coreinit { void OSReport(virt_ptr fmt, var_args args) { auto vaList = make_va_list(args); COSVReport(COSReportModule::Unknown0, COSReportLevel::Error, fmt, vaList); free_va_list(vaList); } void OSReportInfo(virt_ptr fmt, var_args args) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Info) { auto vaList = make_va_list(args); COSVReport(COSReportModule::Unknown0, COSReportLevel::Info, fmt, vaList); free_va_list(vaList); } } void OSReportVerbose(virt_ptr fmt, var_args args) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Verbose) { auto vaList = make_va_list(args); COSVReport(COSReportModule::Unknown0, COSReportLevel::Verbose, fmt, vaList); free_va_list(vaList); } } void OSReportWarn(virt_ptr fmt, var_args args) { if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Warn) { auto vaList = make_va_list(args); COSVReport(COSReportModule::Unknown0, COSReportLevel::Warn, fmt, vaList); free_va_list(vaList); } } void OSVReport(virt_ptr fmt, virt_ptr vaList) { COSVReport(COSReportModule::Unknown0, COSReportLevel::Error, fmt, vaList); } void OSPanic(virt_ptr file, int32_t line, virt_ptr fmt, var_args args) { auto buffer = StackArray { }; auto vaList = make_va_list(args); size_t size = internal::formatStringV(buffer, buffer.size(), fmt, vaList); free_va_list(vaList); internal::OSPanic(file.get(), line, std::string_view { &buffer[0], size }); } void OSSendFatalError(virt_ptr error, virt_ptr functionName, uint32_t line) { if (error) { if (functionName) { string_copy(virt_addrof(error->functionName).get(), error->functionName.size(), functionName.get(), error->functionName.size()); error->functionName[error->functionName.size() - 1] = char { 0 }; } else { error->functionName[0] = char { 0 }; } error->line = line; } // TODO: Kernel call 0x6C00 systemFatal gLog->error("SystemFatal: messageType: {}", error->messageType); gLog->error("SystemFatal: errorCode: {}", error->errorCode); gLog->error("SystemFatal: internalErrorCode: {}", error->internalErrorCode); gLog->error("SystemFatal: processId: {}", error->processId); gLog->error("SystemFatal: functionName: {}", virt_addrof(error->functionName)); gLog->error("SystemFatal: line: {}", error->line); ghs_exit(-1); } void OSConsoleWrite(virt_ptr msg, uint32_t size) { gLog->info("[OSConsoleWrite] {}", std::string_view { msg.get(), size }); } namespace internal { void OSPanic(std::string_view file, unsigned line, std::string_view msg) { auto symbolNameBuffer = StackArray { }; gLog->error("OSPanic in \"{}\" at line {}: {}.", file, line, msg); // Format a guest stack trace auto core = cpu::this_core::state(); auto stackAddress = virt_addr { core->systemCallStackHead }; auto stackTraceBuffer = fmt::memory_buffer { }; fmt::format_to(std::back_inserter(stackTraceBuffer), "Guest stack trace:\n"); for (auto i = 0; i < 16; ++i) { if (!stackAddress || stackAddress == virt_addr { 0xFFFFFFFF }) { break; } auto backchain = virt_cast(stackAddress)[0]; auto address = virt_cast(stackAddress)[1]; fmt::format_to(std::back_inserter(stackTraceBuffer), "{}: {}", stackAddress, address); auto symbolAddress = OSGetSymbolName(address, symbolNameBuffer, 256); if (symbolAddress) { fmt::format_to(std::back_inserter(stackTraceBuffer), " {}+0x{:X}", symbolNameBuffer.get(), static_cast(address - symbolAddress)); } fmt::format_to(std::back_inserter(stackTraceBuffer), "\n"); stackAddress = backchain; } gLog->error("{}", fmt::to_string(stackTraceBuffer)); ghs_PPCExit(-1); } } // namespace internal void Library::registerOsReportSymbols() { RegisterFunctionExport(OSReport); RegisterFunctionExport(OSReportWarn); RegisterFunctionExport(OSReportInfo); RegisterFunctionExport(OSReportVerbose); RegisterFunctionExport(OSVReport); RegisterFunctionExport(OSPanic); RegisterFunctionExport(OSSendFatalError); RegisterFunctionExport(OSConsoleWrite); RegisterFunctionExportName("__OSConsoleWrite", OSConsoleWrite); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_osreport.h ================================================ #pragma once #include #include namespace cafe::coreinit { // TODO@: Enum with unknown values using OSFatalErrorMessageType = uint32_t; struct OSFatalError { be2_val messageType; be2_val errorCode; be2_val processId; be2_val internalErrorCode; be2_val line; be2_array functionName; UNKNOWN(0xD4 - 0x54); }; CHECK_OFFSET(OSFatalError, 0x00, messageType); CHECK_OFFSET(OSFatalError, 0x04, errorCode); CHECK_OFFSET(OSFatalError, 0x08, processId); CHECK_OFFSET(OSFatalError, 0x0C, internalErrorCode); CHECK_OFFSET(OSFatalError, 0x10, line); CHECK_OFFSET(OSFatalError, 0x14, functionName); CHECK_SIZE(OSFatalError, 0xD4); void OSSendFatalError(virt_ptr error, virt_ptr functionName, uint32_t line); namespace internal { void OSPanic(std::string_view file, unsigned line, std::string_view msg); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_overlayarena.cpp ================================================ #include "coreinit.h" #include "coreinit_overlayarena.h" #include "cafe/kernel/cafe_kernel_mmu.h" namespace cafe::coreinit { struct StaticOverlayArenaData { be2_val enabled; be2_val baseAddress; be2_val size; }; static virt_ptr sOverlayArenaData = nullptr; BOOL OSIsEnabledOverlayArena() { return sOverlayArenaData->enabled; } void OSEnableOverlayArena(uint32_t unk, virt_ptr outAddr, virt_ptr outSize) { if (!sOverlayArenaData->enabled) { auto overlayArena = kernel::enableOverlayArena(); sOverlayArenaData->baseAddress = overlayArena.first; sOverlayArenaData->size = overlayArena.second; sOverlayArenaData->enabled = TRUE; } OSGetOverlayArenaRange(outAddr, outSize); } void OSDisableOverlayArena() { if (sOverlayArenaData->enabled) { kernel::disableOverlayArena(); sOverlayArenaData->baseAddress = cpu::VirtualAddress { 0u }; sOverlayArenaData->size = 0u; sOverlayArenaData->enabled = FALSE; } } void OSGetOverlayArenaRange(virt_ptr outAddr, virt_ptr outSize) { if (outAddr) { *outAddr = sOverlayArenaData->baseAddress; } if (outSize) { *outSize = sOverlayArenaData->size; } } void Library::registerOverlayArenaSymbols() { RegisterFunctionExport(OSIsEnabledOverlayArena); RegisterFunctionExport(OSEnableOverlayArena); RegisterFunctionExport(OSDisableOverlayArena); RegisterFunctionExport(OSGetOverlayArenaRange); RegisterDataInternal(sOverlayArenaData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_overlayarena.h ================================================ #pragma once #include namespace cafe::coreinit { BOOL OSIsEnabledOverlayArena(); void OSEnableOverlayArena(uint32_t unk, virt_ptr outAddr, virt_ptr outSize); void OSDisableOverlayArena(); void OSGetOverlayArenaRange(virt_ptr outAddr, virt_ptr outSize); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_rendezvous.cpp ================================================ #include "coreinit.h" #include "coreinit_rendezvous.h" #include "coreinit_time.h" #include namespace cafe::coreinit { /** * Initialise a rendezvous structure. */ void OSInitRendezvous(virt_ptr rendezvous) { rendezvous->core[0].store(0, std::memory_order_release); rendezvous->core[1].store(0, std::memory_order_release); rendezvous->core[2].store(0, std::memory_order_release); } /** * Wait on a rendezvous with infinite timeout. */ BOOL OSWaitRendezvous(virt_ptr rendezvous, uint32_t coreMask) { return OSWaitRendezvousWithTimeout(rendezvous, coreMask, -1); } /** * Wait on a rendezvous with a timeout. * * This will wait with a timeout until all cores matching coreMask have * reached the rendezvous point. * * \return Returns TRUE on success, FALSE on timeout. */ BOOL OSWaitRendezvousWithTimeout(virt_ptr rendezvous, uint32_t coreMask, OSTimeNanoseconds timeoutNS) { auto core = OSGetCoreId(); auto success = FALSE; auto endTime = OSGetTime() + internal::nsToTicks(timeoutNS); auto waitCore0 = (coreMask & (1 << 0)) != 0; auto waitCore1 = (coreMask & (1 << 1)) != 0; auto waitCore2 = (coreMask & (1 << 2)) != 0; // Set our core flag rendezvous->core[core].store(1, std::memory_order_release); do { if (waitCore0 && rendezvous->core[0].load(std::memory_order_acquire)) { waitCore0 = false; } if (waitCore1 && rendezvous->core[1].load(std::memory_order_acquire)) { waitCore1 = false; } if (waitCore2 && rendezvous->core[2].load(std::memory_order_acquire)) { waitCore2 = false; } if (!waitCore0 && !waitCore1 && !waitCore2) { success = TRUE; break; } if (timeoutNS != -1 && OSGetTime() >= endTime) { break; } // We must manually check for interrupts here, as we are busy-looping. // Note that this is only safe as no locks are held during the wait. cpu::this_core::checkInterrupts(); // TODO: Change this to something like cafe::kernel::checkInterrupts } while (true); return success; } void Library::registerRendezvousSymbols() { RegisterFunctionExport(OSInitRendezvous); RegisterFunctionExport(OSWaitRendezvous); RegisterFunctionExport(OSWaitRendezvousWithTimeout); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_rendezvous.h ================================================ #pragma once #include "coreinit_core.h" #include "coreinit_time.h" #include #include namespace cafe::coreinit { /** * \defgroup coreinit_fiber Fiber * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct OSRendezvous { std::atomic core[CoreCount]; UNKNOWN(4); }; CHECK_OFFSET(OSRendezvous, 0x00, core); CHECK_SIZE(OSRendezvous, 0x10); #pragma pack(pop) void OSInitRendezvous(virt_ptr rendezvous); BOOL OSWaitRendezvous(virt_ptr rendezvous, uint32_t coreMask); BOOL OSWaitRendezvousWithTimeout(virt_ptr rendezvous, uint32_t coreMask, OSTimeNanoseconds timeout); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_scheduler.cpp ================================================ #include "coreinit.h" #include "coreinit_fastmutex.h" #include "coreinit_interrupts.h" #include "coreinit_internal_idlock.h" #include "coreinit_internal_queue.h" #include "coreinit_mutex.h" #include "coreinit_scheduler.h" #include "coreinit_thread.h" #include "cafe/kernel/cafe_kernel_context.h" #include "debugger/debugger.h" #include #include #include #include #include #include #include #include namespace cafe::coreinit { struct StaticSchedulerData { struct PerCoreData { be2_val schedulerEnabled; be2_struct runQueue; be2_virt_ptr currentThread; std::chrono::time_point lastSwitchTime; std::chrono::time_point pauseTime; }; internal::IdLock schedulerLock; be2_struct activeThreadQueue; be2_array perCoreData; }; static virt_ptr sSchedulerData = nullptr; namespace internal { using ActiveQueue = Queue; using CoreRunQueue0 = SortedQueue; using CoreRunQueue1 = SortedQueue; using CoreRunQueue2 = SortedQueue; virt_ptr getCoreRunningThread(uint32_t coreId) { if (!sSchedulerData) { return nullptr; } return sSchedulerData->perCoreData[coreId].currentThread; } uint64_t getCoreThreadRunningTime(uint32_t coreId) { auto &perCoreData = sSchedulerData->perCoreData[coreId]; auto now = std::chrono::high_resolution_clock::now(); if (perCoreData.pauseTime != std::chrono::time_point::max()) { now = perCoreData.pauseTime; } return (now - perCoreData.lastSwitchTime).count(); } void pauseCoreTime(bool isPaused) { auto coreId = cpu::this_core::id(); auto &perCoreData = sSchedulerData->perCoreData[coreId]; auto now = std::chrono::high_resolution_clock::now(); if (isPaused) { perCoreData.pauseTime = now; } else { perCoreData.lastSwitchTime += now - perCoreData.pauseTime; perCoreData.pauseTime = std::chrono::time_point::max(); } } virt_ptr getFirstActiveThread() { if (!sSchedulerData) { return nullptr; } return sSchedulerData->activeThreadQueue.head; } virt_ptr getCurrentThread() { return getCoreRunningThread(cpu::this_core::id()); } void lockScheduler() { internal::acquireIdLockWithCoreId(sSchedulerData->schedulerLock); } bool isSchedulerLocked() { return internal::isHoldingIdLockWithCoreId(sSchedulerData->schedulerLock); } void unlockScheduler() { internal::releaseIdLockWithCoreId(sSchedulerData->schedulerLock); } bool isSchedulerEnabled() { auto coreId = cpu::this_core::id(); return sSchedulerData->perCoreData[coreId].schedulerEnabled; } void enableScheduler() { auto coreId = cpu::this_core::id(); sSchedulerData->perCoreData[coreId].schedulerEnabled = true; } void disableScheduler() { auto coreId = cpu::this_core::id(); sSchedulerData->perCoreData[coreId].schedulerEnabled = false; } void markThreadActiveNoLock(virt_ptr thread) { auto activeThreadQueue = virt_addrof(sSchedulerData->activeThreadQueue); decaf_check(!ActiveQueue::contains(activeThreadQueue, thread)); ActiveQueue::append(activeThreadQueue, thread); checkActiveThreadsNoLock(); } void markThreadInactiveNoLock(virt_ptr thread) { auto activeThreadQueue = virt_addrof(sSchedulerData->activeThreadQueue); decaf_check(ActiveQueue::contains(activeThreadQueue, thread)); ActiveQueue::erase(activeThreadQueue, thread); checkActiveThreadsNoLock(); } bool isThreadActiveNoLock(virt_ptr thread) { if (thread->state == OSThreadState::None) { return false; } auto activeThreadQueue = virt_addrof(sSchedulerData->activeThreadQueue); return ActiveQueue::contains(activeThreadQueue, thread); } static void queueThreadNoLock(virt_ptr thread) { decaf_check(isSchedulerLocked()); decaf_check(!OSIsThreadSuspended(thread)); decaf_check(thread->state == OSThreadState::Ready); // Schedule this thread on any cores which can run it! if (thread->attr & OSThreadAttributes::AffinityCPU0) { CoreRunQueue0::insert(virt_addrof(sSchedulerData->perCoreData[0].runQueue), thread); } if (thread->attr & OSThreadAttributes::AffinityCPU1) { CoreRunQueue1::insert(virt_addrof(sSchedulerData->perCoreData[1].runQueue), thread); } if (thread->attr & OSThreadAttributes::AffinityCPU2) { CoreRunQueue2::insert(virt_addrof(sSchedulerData->perCoreData[2].runQueue), thread); } } static void unqueueThreadNoLock(virt_ptr thread) { CoreRunQueue0::erase(virt_addrof(sSchedulerData->perCoreData[0].runQueue), thread); CoreRunQueue1::erase(virt_addrof(sSchedulerData->perCoreData[1].runQueue), thread); CoreRunQueue2::erase(virt_addrof(sSchedulerData->perCoreData[2].runQueue), thread); } void setThreadAffinityNoLock(virt_ptr thread, uint32_t affinity) { thread->attr &= ~OSThreadAttributes::AffinityAny; thread->attr |= affinity; if (thread->state == OSThreadState::Ready) { if (thread->suspendCounter == 0) { unqueueThreadNoLock(thread); queueThreadNoLock(thread); } } } static virt_ptr peekNextThreadNoLock(uint32_t core) { decaf_check(isSchedulerLocked()); auto thread = sSchedulerData->perCoreData[core].runQueue.head; if (thread) { decaf_check(thread->state == OSThreadState::Ready); decaf_check(thread->suspendCounter == 0); decaf_check(thread->attr & static_cast(1 << core)); } return thread; } static void validateThread(virt_ptr thread) { decaf_check(*thread->stackEnd == 0xDEADBABE); decaf_check((thread->attr & OSThreadAttributes::AffinityAny) != 0); } int32_t checkActiveThreadsNoLock() { auto threadCount = 0; // Count threads before this one for (virt_ptr threadIter = sSchedulerData->activeThreadQueue.head; threadIter; threadIter = threadIter->activeLink.next) { validateThread(threadIter); threadCount++; } return threadCount; } void checkRunningThreadNoLock(bool yielding) { decaf_check(isSchedulerLocked()); auto coreId = cpu::this_core::id(); auto &perCoreData = sSchedulerData->perCoreData[coreId]; checkActiveThreadsNoLock(); if (!perCoreData.schedulerEnabled) { return; } auto currThread = perCoreData.currentThread; auto nextThread = peekNextThreadNoLock(coreId); if (!currThread && !nextThread) { // No coreinit threads running, we must be in the kernel WFI loop. return; } auto switchTime = std::chrono::high_resolution_clock::now(); if (currThread) { if (currThread->state == OSThreadState::Running) { // If we're not ready to suspend check the priority vs next thread if (currThread->suspendCounter <= 0) { if (!nextThread) { // There is no other viable thread, keep running current. return; } if (currThread->priority < nextThread->priority) { // Next thread has lower priority, keep running current. return; } else if (!yielding && currThread->priority == nextThread->priority) { // Next thread has same priority, but we are not yielding. return; } } // Add thread back to run queue currThread->state = OSThreadState::Ready; queueThreadNoLock(currThread); } // Update thread run time auto diff = switchTime - perCoreData.lastSwitchTime; currThread->coreTimeConsumedNs += diff.count(); } // Trace log the thread switch if (gLog->should_log(Logger::Level::trace)) { fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "Core {} leaving", coreId); if (currThread) { fmt::format_to(std::back_inserter(out), " thread {}", currThread->id); if (currThread->name) { fmt::format_to(std::back_inserter(out), " [{}]", currThread->name); } } else { fmt::format_to(std::back_inserter(out), " idle"); } fmt::format_to(std::back_inserter(out), " to"); if (nextThread) { fmt::format_to(std::back_inserter(out), " thread {}", nextThread->id); if (nextThread->name) { fmt::format_to(std::back_inserter(out), " [{}]", nextThread->name); } } else { fmt::format_to(std::back_inserter(out), " idle"); } gLog->trace("{}", std::string_view { out.data(), out.size() }); } if (nextThread) { // Remove next thread from Run Queue nextThread->state = OSThreadState::Running; nextThread->wakeCount++; unqueueThreadNoLock(nextThread); } // Switch thread perCoreData.currentThread = nextThread; perCoreData.lastSwitchTime = switchTime; // Make sure interrupts are enabled auto prevState = coreinit::OSEnableInterrupts(); internal::unlockScheduler(); kernel::switchContext(nextThread ? virt_addrof(nextThread->context) : nullptr); internal::lockScheduler(); // Restore interrupts to whatever state they were in coreinit::OSRestoreInterrupts(prevState); checkActiveThreadsNoLock(); } void rescheduleSelfNoLock() { checkRunningThreadNoLock(false); } void rescheduleNoLock(uint32_t core) { if (core == cpu::this_core::id()) { rescheduleSelfNoLock(); } else { cpu::interrupt(core, cpu::GENERIC_INTERRUPT); } } void rescheduleOtherCoreNoLock() { auto core = cpu::this_core::id(); for (auto i = 0u; i < 3; ++i) { if (i != core) { rescheduleNoLock(i); } } } void rescheduleAllCoreNoLock() { // Reschedule other cores first, or we might exit early! rescheduleOtherCoreNoLock(); rescheduleSelfNoLock(); } int32_t resumeThreadNoLock(virt_ptr thread, int32_t counter) { decaf_check(isThreadActiveNoLock(thread)); auto old = thread->suspendCounter; thread->suspendCounter -= counter; if (thread->suspendCounter < 0) { thread->suspendCounter = 0; return old; } if (thread->suspendCounter == 0) { if (thread->state == OSThreadState::Ready) { thread->priority = calculateThreadPriorityNoLock(thread); queueThreadNoLock(thread); } } return old; } void setCoreRunningThread(uint32_t coreId, virt_ptr thread) { sSchedulerData->perCoreData[coreId].currentThread = thread; } bool setThreadRunQuantumNoLock(virt_ptr thread, OSTime ticks) { decaf_check(isSchedulerLocked()); decaf_abort("Unsupported call to setThreadRunQuantumNoLock"); } void sleepThreadNoLock(virt_ptr queue) { auto thread = OSGetCurrentThread(); decaf_check(thread->queue == nullptr); decaf_check(thread->state == OSThreadState::Running); thread->queue = queue; thread->state = OSThreadState::Waiting; if (queue) { ThreadQueue::insert(queue, thread); } } void sleepThreadNoLock(virt_ptr queue) { // This is super-strange, it is used by OSFastMutex, and after a few // comparisons, I'm 99% sure they just cast... I cast it here instead // of inside OSFastMutex so that its closer to the use above to help // ensure nobody mistakenly breaks it... sleepThreadNoLock(virt_cast(queue)); } void suspendThreadNoLock(virt_ptr thread) { thread->requestFlag = OSThreadRequest::None; thread->suspendCounter += thread->needSuspend; thread->needSuspend = 0; thread->state = OSThreadState::Ready; wakeupThreadNoLock(virt_addrof(thread->suspendQueue)); } void testThreadCancelNoLock() { auto thread = OSGetCurrentThread(); if (thread->cancelState == OSThreadCancelState::Enabled) { if (thread->requestFlag == OSThreadRequest::Suspend) { suspendThreadNoLock(thread); rescheduleAllCoreNoLock(); } if (thread->requestFlag == OSThreadRequest::Cancel) { unlockScheduler(); OSExitThread(-1); } } } void wakeupOneThreadNoLock(virt_ptr thread) { if (thread->state == OSThreadState::Running || thread->state == OSThreadState::Ready) { // This thread is already running or ready return; } decaf_check(thread->queue); thread->state = OSThreadState::Ready; ThreadQueue::erase(thread->queue, thread); thread->queue = nullptr; queueThreadNoLock(thread); } void wakeupThreadNoLock(virt_ptr queue) { auto next = queue->head; for (auto thread = next; next; thread = next) { next = thread->link.next; wakeupOneThreadNoLock(thread); } } void wakeupThreadNoLock(virt_ptr queue) { // See sleepThreadNoLock(OSSimpleQueue*) for more details on this hack. wakeupThreadNoLock(virt_cast(queue)); } void wakeupThreadWaitForSuspensionNoLock(virt_ptr queue, int32_t suspendResult) { for (auto thread = queue->head; thread; thread = thread->link.next) { thread->suspendResult = suspendResult; wakeupOneThreadNoLock(thread); } ThreadQueue::clear(queue); } int32_t calculateThreadPriorityNoLock(virt_ptr thread) { decaf_check(isSchedulerLocked()); auto priority = thread->basePriority; // If thread is holding a spinlock, it is always highest priority if (thread->context.spinLockCount > 0) { return 0; } // For all mutex we own, boost our priority over anyone waiting to own our mutex for (auto mutex = thread->mutexQueue.head; mutex; mutex = mutex->link.next) { // We only need to check the head of mutex thread queue as it is in priority order auto other = mutex->queue.head; if (other && other->priority < priority) { priority = other->priority; } } // For all fast mutex we own, boost our priority over anyone waiting to own our fast mutex for (auto fastMutex = thread->fastMutexQueue.head; fastMutex; fastMutex = fastMutex->link.next) { // We only need to check the head of mutex thread queue as it is in priority order auto other = fastMutex->queue.head; if (other && other->priority < priority) { priority = other->priority; } } return priority; } virt_ptr setThreadActualPriorityNoLock(virt_ptr thread, int32_t priority) { decaf_check(isSchedulerLocked()); thread->priority = priority; if (thread->state == OSThreadState::Ready) { if (thread->suspendCounter == 0) { unqueueThreadNoLock(thread); queueThreadNoLock(thread); } } else if (thread->state == OSThreadState::Waiting) { // Move towards head of queue if needed while (thread->link.prev && priority < thread->link.prev->priority) { auto prev = thread->link.prev; auto next = thread->link.next; thread->link.prev = prev->link.prev; thread->link.next = prev; prev->link.prev = thread; prev->link.next = next; if (next) { next->link.prev = prev; } } // Move towards tail of queue if needed while (thread->link.next && thread->link.next->priority < priority) { auto prev = thread->link.prev; auto next = thread->link.next; thread->link.prev = next; thread->link.next = next->link.next; next->link.prev = prev; next->link.next = thread; if (prev) { prev->link.next = next; } } // If we are waiting for a mutex, return its owner if (thread->mutex) { return thread->mutex->owner; } } return nullptr; } void updateThreadPriorityNoLock(virt_ptr thread) { // Update the threads priority, and any thread chain of mutex owners while (thread) { auto priority = calculateThreadPriorityNoLock(thread); thread = setThreadActualPriorityNoLock(thread, priority); } } void promoteThreadPriorityNoLock(virt_ptr thread, int32_t priority) { while (thread && priority < thread->priority) { thread = setThreadActualPriorityNoLock(thread, priority); } } void initialiseScheduler() { OSInitThreadQueue(virt_addrof(sSchedulerData->activeThreadQueue)); for (auto i = 0u; i < sSchedulerData->perCoreData.size(); ++i) { auto &perCoreData = sSchedulerData->perCoreData[i]; perCoreData.schedulerEnabled = true; perCoreData.currentThread = nullptr; OSInitThreadQueue(virt_addrof(perCoreData.runQueue)); perCoreData.lastSwitchTime = std::chrono::high_resolution_clock::now(); perCoreData.pauseTime = std::chrono::time_point::max(); } } } // namespace internal void Library::registerSchedulerSymbols() { RegisterDataInternal(sSchedulerData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_scheduler.h ================================================ #pragma once #include "coreinit_time.h" #include namespace cafe::coreinit { struct OSThread; struct OSThreadQueue; struct OSThreadSimpleQueue; namespace internal { virt_ptr getCoreRunningThread(uint32_t coreId); uint64_t getCoreThreadRunningTime(uint32_t coreId); void pauseCoreTime(bool isPaused); virt_ptr getFirstActiveThread(); virt_ptr getCurrentThread(); void lockScheduler(); bool isSchedulerLocked(); void unlockScheduler(); bool isSchedulerEnabled(); void enableScheduler(); void disableScheduler(); void markThreadActiveNoLock(virt_ptr thread); void markThreadInactiveNoLock(virt_ptr thread); bool isThreadActiveNoLock(virt_ptr thread); void setThreadAffinityNoLock(virt_ptr thread, uint32_t affinity); int32_t checkActiveThreadsNoLock(); void checkRunningThreadNoLock(bool yielding); void rescheduleNoLock(uint32_t core); void rescheduleSelfNoLock(); void rescheduleOtherCoreNoLock(); void rescheduleAllCoreNoLock(); int32_t resumeThreadNoLock(virt_ptr thread, int32_t counter); void setCoreRunningThread(uint32_t coreId, virt_ptr thread); bool setThreadRunQuantumNoLock(virt_ptr thread, OSTime ticks); void sleepThreadNoLock(virt_ptr queue); void sleepThreadNoLock(virt_ptr queue); void suspendThreadNoLock(virt_ptr thread); void testThreadCancelNoLock(); void wakeupOneThreadNoLock(virt_ptr thread); void wakeupThreadNoLock(virt_ptr queue); void wakeupThreadNoLock(virt_ptr queue); void wakeupThreadWaitForSuspensionNoLock(virt_ptr queue, int32_t suspendResult); int32_t calculateThreadPriorityNoLock(virt_ptr thread); virt_ptr setThreadActualPriorityNoLock(virt_ptr thread, int32_t priority); void updateThreadPriorityNoLock(virt_ptr thread); void promoteThreadPriorityNoLock(virt_ptr thread, int32_t priority); void initialiseScheduler(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_screen.cpp ================================================ #include "coreinit.h" #include "coreinit_memory.h" #include "coreinit_screen.h" #include "coreinit_screenfont.h" #include "cafe/libraries/gx2/gx2_display.h" #include "cafe/libraries/gx2/gx2_event.h" #include "cafe/libraries/gx2/gx2_cbpool.h" #include "cafe/libraries/gx2/gx2_state.h" #include #include #include #include namespace cafe::coreinit { struct ScreenSize { uint32_t width; uint32_t height; uint32_t pitch; }; static const auto BytesPerPixel = 4; static const ScreenSize sScreenSizes[] = { ScreenSize { 1280, 720, 1280 }, ScreenSize { 854, 480, 854 } }; struct StaticScreenData { be2_array, OSScreenID::Max> buffers; }; static virt_ptr sScreenData = nullptr; void OSScreenInit() { gx2::GX2Init(nullptr); gx2::GX2SetTVBuffer(nullptr, OSScreenGetBufferSizeEx(OSScreenID::TV), gx2::GX2TVRenderMode::Wide720p, gx2::GX2SurfaceFormat::UNORM_R8_G8_B8_A8, gx2::GX2BufferingMode::Single); gx2::GX2SetDRCBuffer(nullptr, OSScreenGetBufferSizeEx(OSScreenID::TV), gx2::GX2DrcRenderMode::Single, gx2::GX2SurfaceFormat::UNORM_R8_G8_B8_A8, gx2::GX2BufferingMode::Single); sScreenData->buffers.fill(nullptr); } uint32_t OSScreenGetBufferSizeEx(OSScreenID id) { decaf_check(id < OSScreenID::Max); auto &size = sScreenSizes[id]; return size.pitch * size.height * BytesPerPixel; } void OSScreenEnableEx(OSScreenID id, BOOL enable) { } void OSScreenClearBufferEx(OSScreenID id, uint32_t colour) { decaf_check(id < OSScreenID::Max); auto size = OSScreenGetBufferSizeEx(id) / 4; auto buffer = sScreenData->buffers[id]; // Force alpha to 255 colour |= 0xff000000; for (auto i = 0u; i < size; ++i) { buffer[i] = colour; } } void OSScreenSetBufferEx(OSScreenID id, virt_ptr addr) { decaf_check(id < OSScreenID::Max); sScreenData->buffers[id] = virt_cast(addr); } void OSScreenPutPixelEx(OSScreenID id, uint32_t x, uint32_t y, uint32_t colour) { decaf_check(id < OSScreenID::Max); auto buffer = sScreenData->buffers[id]; auto size = sScreenSizes[id]; // Force alpha to 255 colour |= 0xff000000; if (buffer && x < size.width && y < size.height) { auto offset = x + y * size.pitch; buffer[offset] = colour; } } static void putChar(OSScreenID id, uint32_t x, uint32_t y, char chr) { auto index = chr & 0x7F; if (index < ' ') { index = 0; } else { index -= ' '; } auto font = sScreenFontBitmap + index * sScreenFontPitch; for (auto v = 0; v < sScreenFontHeight; ++v) { for (auto h = 0; h < sScreenFontWidth; ++h) { auto bitmap = font[v * 2 + h / 8]; auto bit = bitmap >> (h % 8); if (bit & 1) { OSScreenPutPixelEx(id, x + h, y + v, 0xFFFFFFFF); } } } } void OSScreenPutFontEx(OSScreenID id, uint32_t row, uint32_t column, virt_ptr msg) { static const auto offsetX = 50; static const auto offsetY = 32; static const auto adjustX = 12; static const auto adjustY = 24; auto x = offsetX + row * adjustX; auto y = offsetY + column * adjustY; while (msg && *msg) { putChar(id, x, y, *msg); x += adjustX; msg++; } } void OSScreenFlipBuffersEx(OSScreenID id) { decaf_check(id < OSScreenID::Max); auto buffer = sScreenData->buffers[id]; // Send the custom flip command gx2::internal::writePM4(latte::pm4::DecafOSScreenFlip { static_cast(id), OSEffectiveToPhysical(virt_cast(buffer)) }); // Wait until flip gx2::GX2Flush(); gx2::GX2WaitForFlip(); } void Library::registerScreenSymbols() { RegisterFunctionExport(OSScreenInit); RegisterFunctionExport(OSScreenGetBufferSizeEx); RegisterFunctionExport(OSScreenEnableEx); RegisterFunctionExport(OSScreenClearBufferEx); RegisterFunctionExport(OSScreenSetBufferEx); RegisterFunctionExport(OSScreenPutPixelEx); RegisterFunctionExport(OSScreenPutFontEx); RegisterFunctionExport(OSScreenFlipBuffersEx); RegisterDataInternal(sScreenData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_screen.h ================================================ #pragma once #include namespace cafe::coreinit { enum OSScreenID : uint32_t { TV = 0, DRC = 1, Max }; void OSScreenInit(); uint32_t OSScreenGetBufferSizeEx(OSScreenID id); void OSScreenEnableEx(OSScreenID id, BOOL enable); void OSScreenClearBufferEx(OSScreenID id, uint32_t colour); void OSScreenSetBufferEx(OSScreenID id, virt_ptr addr); void OSScreenPutPixelEx(OSScreenID id, uint32_t x, uint32_t y, uint32_t colour); void OSScreenPutFontEx(OSScreenID id, uint32_t row, uint32_t column, virt_ptr msg); void OSScreenFlipBuffersEx(OSScreenID id); } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_screenfont.h ================================================ #pragma once #include static const uint32_t sScreenFontWidth = 12; static const uint32_t sScreenFontHeight = 19; static const uint32_t sScreenFontPitch = 38; // Character bitmaps for Deja Vu Sans Mono 16pt generated by The Dot Factory static const uint8_t sScreenFontBitmap[] = { // @0 ' ' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @38 '!' (12 pixels wide) 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @76 '"' (12 pixels wide) 0x00, 0x00, 0x98, 0x01, 0x98, 0x01, 0x98, 0x01, 0x98, 0x01, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @114 '#' (12 pixels wide) 0x20, 0x03, 0x30, 0x01, 0x30, 0x01, 0x90, 0x01, 0xFE, 0x07, 0xFE, 0x07, 0x98, 0x00, 0x88, 0x00, 0xC8, 0x00, 0xFF, 0x03, 0xFF, 0x03, 0x4C, 0x00, 0x44, 0x00, 0x64, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @152 '$' (12 pixels wide) 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0x4C, 0x02, 0x4C, 0x00, 0x4C, 0x00, 0x78, 0x00, 0xC0, 0x03, 0x40, 0x06, 0x40, 0x06, 0x44, 0x06, 0xFC, 0x03, 0xF8, 0x01, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, // @190 '%' (12 pixels wide) 0x00, 0x00, 0x1E, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x1E, 0x03, 0xE0, 0x00, 0x38, 0x00, 0xC6, 0x03, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @228 '&' (12 pixels wide) 0x00, 0x00, 0xF0, 0x01, 0xF8, 0x01, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x00, 0x78, 0x00, 0xEC, 0x0C, 0xC6, 0x0C, 0x86, 0x0D, 0x86, 0x07, 0x0E, 0x03, 0xFC, 0x06, 0x78, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @266 ''' (12 pixels wide) 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @304 '(' (12 pixels wide) 0x00, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x60, 0x00, 0x60, 0x00, 0x20, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, 0x00, 0x60, 0x00, 0x60, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x00, 0x00, // @342 ')' (12 pixels wide) 0x00, 0x00, 0x30, 0x00, 0x20, 0x00, 0x60, 0x00, 0x60, 0x00, 0x40, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x60, 0x00, 0x60, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, // @380 '*' (12 pixels wide) 0x00, 0x00, 0x20, 0x00, 0x22, 0x02, 0xAC, 0x01, 0x70, 0x00, 0x70, 0x00, 0xAC, 0x01, 0x22, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @418 '+' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @456 ',' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // @494 '-' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @532 '.' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @570 '/' (12 pixels wide) 0x00, 0x00, 0x00, 0x03, 0x80, 0x01, 0x80, 0x01, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // @608 '0' (12 pixels wide) 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x01, 0x0E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x66, 0x03, 0x66, 0x03, 0x06, 0x03, 0x06, 0x03, 0x0E, 0x03, 0x8C, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @646 '1' (12 pixels wide) 0x00, 0x00, 0x70, 0x00, 0x7C, 0x00, 0x6C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @684 '2' (12 pixels wide) 0x00, 0x00, 0xFC, 0x00, 0xFE, 0x01, 0x82, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x18, 0x00, 0x0C, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @722 '3' (12 pixels wide) 0x00, 0x00, 0xFC, 0x00, 0xFE, 0x01, 0x82, 0x03, 0x00, 0x03, 0x00, 0x03, 0x80, 0x03, 0xF8, 0x01, 0xF8, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x03, 0x82, 0x03, 0xFE, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @760 '4' (12 pixels wide) 0x00, 0x00, 0xC0, 0x01, 0xC0, 0x01, 0xE0, 0x01, 0xA0, 0x01, 0x90, 0x01, 0x98, 0x01, 0x88, 0x01, 0x8C, 0x01, 0x86, 0x01, 0xFE, 0x07, 0xFE, 0x07, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @798 '5' (12 pixels wide) 0x00, 0x00, 0xFC, 0x01, 0xFC, 0x01, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0xFC, 0x00, 0xFC, 0x01, 0x84, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x82, 0x03, 0xFE, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @836 '6' (12 pixels wide) 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x01, 0x1C, 0x01, 0x0E, 0x00, 0x06, 0x00, 0xF6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @874 '7' (12 pixels wide) 0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xC0, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @912 '8' (12 pixels wide) 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @950 '9' (12 pixels wide) 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x03, 0x80, 0x03, 0xC4, 0x01, 0xFC, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @988 ':' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1026 ';' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // @1064 '<' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x80, 0x07, 0xF0, 0x01, 0x3C, 0x00, 0x06, 0x00, 0x3C, 0x00, 0xF0, 0x01, 0x80, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1102 '=' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1140 '>' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1E, 0x00, 0xF8, 0x00, 0xC0, 0x03, 0x00, 0x06, 0xC0, 0x03, 0xF8, 0x00, 0x1E, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1178 '?' (12 pixels wide) 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x01, 0x08, 0x03, 0x00, 0x03, 0x80, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1216 '@' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x98, 0x01, 0x0C, 0x03, 0x0C, 0x03, 0xC6, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0xC6, 0x03, 0x0C, 0x00, 0x0C, 0x00, 0x18, 0x00, 0xF0, 0x00, 0x00, 0x00, // @1254 'A' (12 pixels wide) 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x8C, 0x01, 0xFC, 0x01, 0xFC, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1292 'B' (12 pixels wide) 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1330 'C' (12 pixels wide) 0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0x1C, 0x02, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x1C, 0x02, 0xF8, 0x03, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1368 'D' (12 pixels wide) 0x00, 0x00, 0x7E, 0x00, 0xFE, 0x00, 0xC6, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xC6, 0x01, 0xFE, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1406 'E' (12 pixels wide) 0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1444 'F' (12 pixels wide) 0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x01, 0xFE, 0x01, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1482 'G' (12 pixels wide) 0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0x1C, 0x02, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0xC6, 0x03, 0xC6, 0x03, 0x06, 0x03, 0x06, 0x03, 0x0C, 0x03, 0x0C, 0x03, 0xF8, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1520 'H' (12 pixels wide) 0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1558 'I' (12 pixels wide) 0x00, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1596 'J' (12 pixels wide) 0x00, 0x00, 0xF8, 0x01, 0xF8, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xC2, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1634 'K' (12 pixels wide) 0x00, 0x00, 0x06, 0x07, 0x86, 0x03, 0xC6, 0x01, 0xC6, 0x00, 0x66, 0x00, 0x36, 0x00, 0x3E, 0x00, 0x7E, 0x00, 0xE6, 0x00, 0xC6, 0x00, 0xC6, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1672 'L' (12 pixels wide) 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1710 'M' (12 pixels wide) 0x00, 0x00, 0x8E, 0x03, 0x8E, 0x03, 0x8E, 0x03, 0xDE, 0x03, 0x56, 0x03, 0x56, 0x03, 0x56, 0x03, 0x76, 0x03, 0x26, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1748 'N' (12 pixels wide) 0x00, 0x00, 0x0E, 0x03, 0x0E, 0x03, 0x1E, 0x03, 0x1E, 0x03, 0x16, 0x03, 0x36, 0x03, 0x26, 0x03, 0x26, 0x03, 0x66, 0x03, 0x46, 0x03, 0xC6, 0x03, 0xC6, 0x03, 0x86, 0x03, 0x86, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1786 'O' (12 pixels wide) 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1824 'P' (12 pixels wide) 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1862 'Q' (12 pixels wide) 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, // @1900 'R' (12 pixels wide) 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x00, 0xC6, 0x01, 0x86, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x06, 0x06, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1938 'S' (12 pixels wide) 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x0E, 0x01, 0x06, 0x00, 0x06, 0x00, 0x1E, 0x00, 0xFC, 0x00, 0xF0, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x03, 0x82, 0x03, 0xFE, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @1976 'T' (12 pixels wide) 0x00, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2014 'U' (12 pixels wide) 0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2052 'V' (12 pixels wide) 0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2090 'W' (12 pixels wide) 0x00, 0x00, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x07, 0x07, 0x76, 0x03, 0x76, 0x03, 0x56, 0x03, 0x56, 0x03, 0x56, 0x03, 0xDE, 0x03, 0x8E, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2128 'X' (12 pixels wide) 0x00, 0x00, 0x8E, 0x03, 0x8C, 0x01, 0xDC, 0x01, 0xD8, 0x00, 0x78, 0x00, 0x70, 0x00, 0x20, 0x00, 0x70, 0x00, 0x70, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x8C, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2166 'Y' (12 pixels wide) 0x00, 0x00, 0x0E, 0x07, 0x0C, 0x03, 0x9C, 0x03, 0x98, 0x01, 0xF8, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2204 'Z' (12 pixels wide) 0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x80, 0x01, 0xC0, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x38, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2242 '[' (12 pixels wide) 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, // @2280 '\' (12 pixels wide) 0x00, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x00, 0x30, 0x00, 0x60, 0x00, 0x60, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, // @2318 ']' (12 pixels wide) 0x00, 0x00, 0x78, 0x00, 0x78, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x78, 0x00, 0x78, 0x00, 0x00, 0x00, // @2356 '^' (12 pixels wide) 0x00, 0x00, 0x60, 0x00, 0xF0, 0x00, 0x98, 0x01, 0x0C, 0x03, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2394 '_' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x07, 0xFF, 0x07, // @2432 '`' (12 pixels wide) 0x18, 0x00, 0x30, 0x00, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2470 'a' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x03, 0x04, 0x03, 0x00, 0x03, 0xF8, 0x03, 0xFE, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x03, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2508 'b' (12 pixels wide) 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xF6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFE, 0x01, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2546 'c' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFC, 0x01, 0x0C, 0x01, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x01, 0xFC, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2584 'd' (12 pixels wide) 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x78, 0x03, 0xFC, 0x03, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2622 'e' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x03, 0x06, 0x03, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x0C, 0x02, 0xFC, 0x03, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2660 'f' (12 pixels wide) 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x07, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x07, 0xFC, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2698 'g' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x03, 0xFC, 0x03, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x03, 0x84, 0x03, 0xFC, 0x01, 0xF8, 0x00, // @2736 'h' (12 pixels wide) 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xE6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2774 'i' (12 pixels wide) 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x7C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2812 'j' (12 pixels wide) 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x7C, 0x00, 0x3C, 0x00, // @2850 'k' (12 pixels wide) 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x86, 0x03, 0xC6, 0x01, 0xE6, 0x00, 0x76, 0x00, 0x3E, 0x00, 0x7E, 0x00, 0x66, 0x00, 0xC6, 0x00, 0xC6, 0x01, 0x86, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2888 'l' (12 pixels wide) 0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xE0, 0x03, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2926 'm' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6, 0x03, 0xFE, 0x07, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @2964 'n' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3002 'o' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3040 'p' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFE, 0x01, 0xF6, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, // @3078 'q' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x03, 0xFC, 0x03, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, // @3116 'r' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x01, 0xD8, 0x03, 0x38, 0x02, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3154 's' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x01, 0xFE, 0x03, 0x06, 0x02, 0x3E, 0x00, 0xFC, 0x01, 0xC0, 0x03, 0x00, 0x03, 0x02, 0x03, 0xFE, 0x03, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3192 't' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xF0, 0x03, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3230 'u' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3268 'v' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3306 'w' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x03, 0x06, 0x06, 0x03, 0x26, 0x03, 0x76, 0x03, 0x56, 0x03, 0xDC, 0x01, 0xDC, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3344 'x' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x03, 0xDC, 0x01, 0xD8, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xF8, 0x00, 0xD8, 0x00, 0x8C, 0x01, 0x8E, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3382 'y' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x9C, 0x01, 0xD8, 0x00, 0xD8, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x60, 0x00, 0x70, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3C, 0x00, 0x1C, 0x00, // @3420 'z' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x18, 0x00, 0x1C, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @3458 '{' (12 pixels wide) 0x00, 0x00, 0xC0, 0x03, 0xE0, 0x03, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x70, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x03, 0xC0, 0x03, // @3496 '|' (12 pixels wide) 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, // @3534 '}' (12 pixels wide) 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x7C, 0x00, 0x3C, 0x00, // @3572 '~' (12 pixels wide) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x04, 0xFE, 0x07, 0xC2, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_semaphore.cpp ================================================ #include "coreinit.h" #include "coreinit_semaphore.h" #include "coreinit_scheduler.h" #include namespace cafe::coreinit { /** * Initialise semaphore object with count. */ void OSInitSemaphore(virt_ptr semaphore, int32_t count) { OSInitSemaphoreEx(semaphore, count, nullptr); } /** * Initialise semaphore object with count and name. */ void OSInitSemaphoreEx(virt_ptr semaphore, int32_t count, virt_ptr name) { semaphore->tag = OSSemaphore::Tag; semaphore->name = name; semaphore->count = count; OSInitThreadQueueEx(virt_addrof(semaphore->queue), semaphore); } /** * Decrease the semaphore value. * * If the value is less than or equal to zero the current thread will be put to * sleep until the count is above zero and it can decrement it safely. */ int32_t OSWaitSemaphore(virt_ptr semaphore) { internal::lockScheduler(); // Wait until we can decrease semaphore while (semaphore->count <= 0) { internal::sleepThreadNoLock(virt_addrof(semaphore->queue)); internal::rescheduleSelfNoLock(); } auto previous = semaphore->count; // Decrease semaphore semaphore->count--; internal::unlockScheduler(); return previous; } /** * Try to decrease the semaphore value. * * If the value is greater than zero then it will be decremented, else the function * will return immediately with a value <= 0 indicating a failure. * * \return Returns previous semaphore count, before the decrement in this function. * If the value is >0 then it means the call was succesful. */ int32_t OSTryWaitSemaphore(virt_ptr semaphore) { internal::lockScheduler(); auto previous = semaphore->count; // Try to decrease semaphore if (semaphore->count > 0) { semaphore->count--; } internal::unlockScheduler(); return previous; } /** * Increase the semaphore value. * * If any threads are waiting for semaphore, they are woken. */ int32_t OSSignalSemaphore(virt_ptr semaphore) { internal::lockScheduler(); auto previous = semaphore->count; // Increase semaphore semaphore->count++; // Wakeup any waiting threads internal::wakeupThreadNoLock(virt_addrof(semaphore->queue)); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); return previous; } /** * Get the current semaphore count. */ int32_t OSGetSemaphoreCount(virt_ptr semaphore) { internal::lockScheduler(); auto count = semaphore->count; internal::unlockScheduler(); return count; } void Library::registerSemaphoreSymbols() { RegisterFunctionExport(OSInitSemaphore); RegisterFunctionExport(OSInitSemaphoreEx); RegisterFunctionExport(OSWaitSemaphore); RegisterFunctionExport(OSTryWaitSemaphore); RegisterFunctionExport(OSSignalSemaphore); RegisterFunctionExport(OSGetSemaphoreCount); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_semaphore.h ================================================ #pragma once #include "coreinit_thread.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_semaphore Semaphore * \ingroup coreinit * * Similar to Windows Semaphore Objects. * @{ */ #pragma pack(push, 1) struct OSSemaphore { static constexpr uint32_t Tag = 0x73506852u; //! Should always be set to the value OSSemaphore::Tag. be2_val tag; //! Name set by OSInitMutexEx. be2_virt_ptr name; UNKNOWN(4); //! Current count of semaphore be2_val count; //! Queue of threads waiting on semaphore object with OSWaitSemaphore be2_struct queue; }; CHECK_OFFSET(OSSemaphore, 0x00, tag); CHECK_OFFSET(OSSemaphore, 0x04, name); CHECK_OFFSET(OSSemaphore, 0x0C, count); CHECK_OFFSET(OSSemaphore, 0x10, queue); CHECK_SIZE(OSSemaphore, 0x20); #pragma pack(pop) void OSInitSemaphore(virt_ptr semaphore, int32_t count); void OSInitSemaphoreEx(virt_ptr semaphore, int32_t count, virt_ptr name); int32_t OSWaitSemaphore(virt_ptr semaphore); int32_t OSTryWaitSemaphore(virt_ptr semaphore); int32_t OSSignalSemaphore(virt_ptr semaphore); int32_t OSGetSemaphoreCount(virt_ptr semaphore); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_snprintf.cpp ================================================ #include "coreinit.h" #include "coreinit_snprintf.h" #include "cafe/cafe_ppc_interface_varargs.h" #include #include #include #include #include #include #include namespace cafe::coreinit { static int32_t os_snprintf(virt_ptr buffer, uint32_t size, virt_ptr fmt, var_args va_args) { auto list = make_va_list(va_args); auto result = internal::formatStringV(buffer, size, fmt, list); free_va_list(list); return result; } namespace internal { static const char c_flags[] = { '-', '+', ' ', '#', '0' }; static const char c_width[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*' }; static const char c_precision[] = { '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*' }; static const char c_length[] = { 'h', 'l', 'j', 'z', 't', 'L' }; static const char c_specifier[] = { 'd', 'i', 'u', 'o', 'x', 'X', 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A', 'c', 's', 'p', 'n' }; bool formatStringV(virt_ptr fmt, virt_ptr list, fmt::memory_buffer &output) { std::string flags, width, length, precision, formatter; auto args = list->begin(); for (auto i = 0; fmt[i]; ) { if (fmt[i] != '%') { output.push_back(fmt[i++]); continue; } ++i; if (fmt[i] == '%') { // %% becomes % output.push_back('%'); ++i; continue; } char specifier = 0; flags.clear(); width.clear(); length.clear(); precision.clear(); formatter.clear(); while (std::find(std::begin(c_flags), std::end(c_flags), fmt[i]) != std::end(c_flags)) { flags.push_back(fmt[i]); ++i; } while (std::find(std::begin(c_width), std::end(c_width), fmt[i]) != std::end(c_width)) { width.push_back(fmt[i]); ++i; } if (fmt[i] == '.') { while (std::find(std::begin(c_precision), std::end(c_precision), fmt[i]) != std::end(c_precision)) { precision.push_back(fmt[i]); ++i; } } while (std::find(std::begin(c_length), std::end(c_length), fmt[i]) != std::end(c_length)) { length.push_back(fmt[i]); ++i; } if (std::find(std::begin(c_specifier), std::end(c_specifier), fmt[i]) != std::end(c_specifier)) { specifier = fmt[i]; ++i; } switch (specifier) { case 'd': case 'i': case 'u': case 'o': case 'x': case 'X': case 'c': formatter = "%" + flags + width + precision + length + specifier; if (length.compare("ll") == 0 && specifier != 'c') { fmt::format_to(std::back_inserter(output), "{}", fmt::sprintf(formatter, args.next())); } else { fmt::format_to(std::back_inserter(output), "{}", fmt::sprintf(formatter, args.next())); } break; case 'g': case 'G': case 'f': case 'F': case 'e': case 'E': case 'a': case 'A': formatter = "%" + flags + width + precision + length + specifier; if (length.compare("L") == 0) { fmt::format_to(std::back_inserter(output), "{}", fmt::sprintf(formatter, static_cast(args.next()))); } else { fmt::format_to(std::back_inserter(output), "{}", fmt::sprintf(formatter, args.next())); } break; case 'p': // We actually ignore formatter and just use %08X for %p formatter = "%" + flags + width + precision + length + specifier; fmt::format_to(std::back_inserter(output), "{:08X}", static_cast(virt_cast(args.next>()))); break; case 's': { auto s = args.next>(); if (s) { fmt::format_to(std::back_inserter(output), "{}", s.get()); } else { fmt::format_to(std::back_inserter(output), ""); } } break; case 'n': if (length.compare("hh") == 0) { *(args.next>()) = static_cast(output.size()); } else if (length.compare("h") == 0) { *(args.next>()) = static_cast(output.size()); } else if (length.compare("ll") == 0) { *(args.next>()) = static_cast(output.size()); } else { *(args.next>()) = static_cast(output.size()); } break; default: gLog->error("Unimplemented format specifier: {}", specifier); return false; } } return true; } int32_t formatStringV(virt_ptr buffer, uint32_t len, virt_ptr fmt, virt_ptr list) { auto str = fmt::memory_buffer { }; if (!formatStringV(fmt, list, str)) { return -1; } if (str.size() >= len - 1) { // Copy as much as possible std::memcpy(buffer.get(), str.data(), len - 2); buffer[len - 1] = char { 0 }; len -= 1; } else { // Copy whole string into buffer std::memcpy(buffer.get(), str.data(), str.size()); buffer[str.size()] = char { 0 }; len = static_cast(str.size()); } return static_cast(len); } } // namespace internal void Library::registerSnprintfSymbols() { RegisterFunctionExportName("__os_snprintf", os_snprintf); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_snprintf.h ================================================ #include "cafe/cafe_ppc_interface_varargs.h" #include #include namespace cafe::coreinit { namespace internal { int32_t formatStringV(virt_ptr buffer, uint32_t len, virt_ptr fmt, virt_ptr list); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_spinlock.cpp ================================================ #include "coreinit.h" #include "coreinit_interrupts.h" #include "coreinit_spinlock.h" #include "coreinit_scheduler.h" #include "coreinit_thread.h" #include "libcpu/mem.h" #include #include namespace cafe::coreinit { static void increaseSpinLockCount(virt_ptr thread) { internal::lockScheduler(); thread->context.spinLockCount++; thread->priority = 0; internal::unlockScheduler(); } static void decreaseSpinLockCount(virt_ptr thread) { internal::lockScheduler(); thread->context.spinLockCount--; thread->priority = internal::calculateThreadPriorityNoLock(thread); internal::unlockScheduler(); } static bool spinAcquireLock(virt_ptr spinlock) { auto thread = OSGetCurrentThread(); if (spinlock->owner.load(std::memory_order_acquire) == thread) { ++spinlock->recursion; return false; } auto expected = virt_ptr { nullptr }; auto owner = virt_ptr { thread }; while (!spinlock->owner.compare_exchange_weak(expected, owner, std::memory_order_release, std::memory_order_relaxed)) { expected = nullptr; } increaseSpinLockCount(thread); return true; } static bool spinTryLock(virt_ptr spinlock) { auto thread = OSGetCurrentThread(); if (spinlock->owner.load(std::memory_order_acquire) == thread) { ++spinlock->recursion; return true; } auto expected = virt_ptr { nullptr }; auto owner = virt_ptr { thread }; if (spinlock->owner.compare_exchange_weak(expected, owner, std::memory_order_release, std::memory_order_relaxed)) { increaseSpinLockCount(thread); return true; } else { return false; } } static bool spinTryLockWithTimeout(virt_ptr spinlock, OSTime durationTicks) { auto thread = OSGetCurrentThread(); if (spinlock->owner.load(std::memory_order_acquire) == thread) { ++spinlock->recursion; return true; } auto expected = virt_ptr { nullptr }; auto owner = virt_ptr { thread }; auto timeout = OSGetSystemTime() + durationTicks; while (!spinlock->owner.compare_exchange_weak(expected, owner, std::memory_order_release, std::memory_order_relaxed)) { if (OSGetSystemTime() >= timeout) { return false; } expected = nullptr; } increaseSpinLockCount(thread); return true; } static bool spinReleaseLock(virt_ptr spinlock) { auto thread = OSGetCurrentThread(); if (spinlock->recursion > 0u) { --spinlock->recursion; return false; } auto owner = virt_ptr { thread }; if (spinlock->owner.load(std::memory_order_acquire) == owner) { spinlock->owner.store(virt_ptr { nullptr }); decreaseSpinLockCount(thread); return true; } decaf_abort("Attempt to release spin lock which is not owned."); return true; } void OSInitSpinLock(virt_ptr spinlock) { spinlock->owner.store(virt_ptr { nullptr }); spinlock->recursion = 0u; } BOOL OSAcquireSpinLock(virt_ptr spinlock) { OSTestThreadCancel(); spinAcquireLock(spinlock); return TRUE; } BOOL OSTryAcquireSpinLock(virt_ptr spinlock) { OSTestThreadCancel(); return spinTryLock(spinlock) ? TRUE : FALSE; } BOOL OSTryAcquireSpinLockWithTimeout(virt_ptr spinlock, OSTimeNanoseconds timeoutNS) { auto timeoutTicks = internal::nsToTicks(timeoutNS); OSTestThreadCancel(); return spinTryLockWithTimeout(spinlock, timeoutTicks) ? TRUE : FALSE; } BOOL OSReleaseSpinLock(virt_ptr spinlock) { spinReleaseLock(spinlock); OSTestThreadCancel(); return TRUE; } /** * Acquires an Uninterruptible Spin Lock. * * Will block until spin lock is acquired. * Disables interrupts before returning, only if non recursive lock. * * \return Returns TRUE if the lock was acquired. */ BOOL OSUninterruptibleSpinLock_Acquire(virt_ptr spinlock) { if (spinAcquireLock(spinlock)) { spinlock->restoreInterruptState = OSDisableInterrupts(); } // Update thread's cancel state if (auto thread = OSGetCurrentThread()) { thread->cancelState |= OSThreadCancelState::DisabledBySpinlock; } return TRUE; } /** * Try acquire an Uninterruptible Spin Lock. * * Will return immediately if the lock has already been acquired by another thread. * If lock is acquired, interrupts will be disabled. * * \return Returns TRUE if the lock was acquired. */ BOOL OSUninterruptibleSpinLock_TryAcquire(virt_ptr spinlock) { if (!spinTryLock(spinlock)) { return FALSE; } spinlock->restoreInterruptState = OSDisableInterrupts(); // Update thread's cancel state if (auto thread = OSGetCurrentThread()) { thread->cancelState |= OSThreadCancelState::DisabledBySpinlock; } return TRUE; } /** * Try acquire an Uninterruptible Spin Lock with a timeout. * * Will return after a timeout if unable to acquire lock. * If lock is acquired, interrupts will be disabled. * * \return Returns TRUE if the lock was acquired. */ BOOL OSUninterruptibleSpinLock_TryAcquireWithTimeout(virt_ptr spinlock, OSTimeNanoseconds timeoutNS) { auto timeoutTicks = internal::nsToTicks(timeoutNS); if (!spinTryLockWithTimeout(spinlock, timeoutTicks)) { return FALSE; } spinlock->restoreInterruptState = OSDisableInterrupts(); // Update thread's cancel state if (auto thread = OSGetCurrentThread()) { thread->cancelState |= OSThreadCancelState::DisabledBySpinlock; } return TRUE; } /** * Release an Uninterruptible Spin Lock. * * Interrupts will be restored to their previous state before * the lock was acquired. * * \return Returns TRUE if the lock was released. */ BOOL OSUninterruptibleSpinLock_Release(virt_ptr spinlock) { if (spinReleaseLock(spinlock)) { OSRestoreInterrupts(spinlock->restoreInterruptState); } // Update thread's cancel state if (auto thread = OSGetCurrentThread()) { thread->cancelState &= ~OSThreadCancelState::DisabledBySpinlock; } return TRUE; } void Library::registerSpinLockSymbols() { RegisterFunctionExport(OSInitSpinLock); RegisterFunctionExport(OSAcquireSpinLock); RegisterFunctionExport(OSTryAcquireSpinLock); RegisterFunctionExport(OSTryAcquireSpinLockWithTimeout); RegisterFunctionExport(OSReleaseSpinLock); RegisterFunctionExport(OSUninterruptibleSpinLock_Acquire); RegisterFunctionExport(OSUninterruptibleSpinLock_TryAcquire); RegisterFunctionExport(OSUninterruptibleSpinLock_TryAcquireWithTimeout); RegisterFunctionExport(OSUninterruptibleSpinLock_Release); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_spinlock.h ================================================ #pragma once #include "coreinit_time.h" #include #include namespace cafe::coreinit { /** * \defgroup coreinit_spinlock Spinlock * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct OSThread; struct OSSpinLock { //! Address of OSThread* for owner of this lock. be2_atomic> owner; UNKNOWN(0x4); //! Recursion count of spin lock. be2_val recursion; //! Used by OSUninterruptibleSpinLock_{Acquire,Release} to restore previous. //! state of interrupts. be2_val restoreInterruptState; }; CHECK_OFFSET(OSSpinLock, 0x0, owner); CHECK_OFFSET(OSSpinLock, 0x8, recursion); CHECK_OFFSET(OSSpinLock, 0xC, restoreInterruptState); CHECK_SIZE(OSSpinLock, 0x10); #pragma pack(pop) void OSInitSpinLock(virt_ptr spinlock); BOOL OSAcquireSpinLock(virt_ptr spinlock); BOOL OSTryAcquireSpinLock(virt_ptr spinlock); BOOL OSTryAcquireSpinLockWithTimeout(virt_ptr spinlock, OSTimeNanoseconds timeoutNS); BOOL OSReleaseSpinLock(virt_ptr spinlock); BOOL OSUninterruptibleSpinLock_Acquire(virt_ptr spinlock); BOOL OSUninterruptibleSpinLock_TryAcquire(virt_ptr spinlock); BOOL OSUninterruptibleSpinLock_TryAcquireWithTimeout(virt_ptr spinlock, OSTimeNanoseconds timeoutNS); BOOL OSUninterruptibleSpinLock_Release(virt_ptr spinlock); struct ScopedSpinLock { ScopedSpinLock(virt_ptr lock_) : lock(lock_) { OSUninterruptibleSpinLock_Acquire(lock); } ~ScopedSpinLock() { OSUninterruptibleSpinLock_Release(lock); } virt_ptr lock; }; /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemheap.cpp ================================================ #include "coreinit.h" #include "coreinit_cache.h" #include "coreinit_cosreport.h" #include "coreinit_memexpheap.h" #include "coreinit_systemheap.h" #include "coreinit_systeminfo.h" #include #include namespace cafe::coreinit { struct StaticSystemHeapData { be2_val handle; be2_val numAllocs; be2_val numFrees; }; static virt_ptr sSystemHeapData = nullptr; /** * Allocate memory from the system heap. * * \param size * The size of the memory block to allocate. * * \param align * The alignment of the memory block, see MEMAllocFromExpHeapEx for more info. * * \return * NULL on error, or pointer to newly allocated memory on success. */ virt_ptr OSAllocFromSystem(uint32_t size, int32_t align) { auto ptr = MEMAllocFromExpHeapEx(sSystemHeapData->handle, size, align); if (internal::isAppDebugLevelVerbose()) { internal::COSInfo( COSReportModule::Unknown2, fmt::format("SYSTEM_HEAP:{},ALLOC,=\"{}\",-{}", sSystemHeapData->numAllocs, ptr, size)); ++sSystemHeapData->numAllocs; } return ptr; } /** * Free memory to the system heap. * * \param ptr * The memory to free, this pointer must have previously be returned by * OSAllocFromSystem. */ void OSFreeToSystem(virt_ptr ptr) { if (internal::isAppDebugLevelVerbose()) { internal::COSInfo( COSReportModule::Unknown2, fmt::format("SYSTEM_HEAP:{},FREE,=\"{}\",{}", sSystemHeapData->numAllocs, ptr, 0)); ++sSystemHeapData->numAllocs; } MEMFreeToExpHeap(sSystemHeapData->handle, ptr); } namespace internal { void dumpSystemHeap() { MEMDumpHeap(sSystemHeapData->handle); } void initialiseSystemHeap(virt_ptr base, uint32_t size) { if (internal::isAppDebugLevelVerbose()) { COSInfo(COSReportModule::Unknown2, "RPL_SYSHEAP:Event,Change,Hex Addr,Bytes,Available"); COSInfo( COSReportModule::Unknown2, fmt::format("RPL_SYSHEAP:SYSHEAP START,CREATE,=\"{}\",{}", base, size)); } sSystemHeapData->handle = MEMCreateExpHeapEx(base, size, MEMHeapFlags::ThreadSafe); sSystemHeapData->numAllocs = 0u; sSystemHeapData->numFrees = 0u; OSMemoryBarrier(); } } // namespace internal void Library::registerSystemHeapSymbols() { RegisterFunctionExport(OSAllocFromSystem); RegisterFunctionExport(OSFreeToSystem); RegisterDataInternal(sSystemHeapData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemheap.h ================================================ #pragma once #include namespace cafe::coreinit { virt_ptr OSAllocFromSystem(uint32_t size, int32_t align); void OSFreeToSystem(virt_ptr ptr); namespace internal { void dumpSystemHeap(); void initialiseSystemHeap(virt_ptr base, uint32_t size); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_systeminfo.cpp ================================================ #include "coreinit.h" #include "coreinit_systeminfo.h" #include "coreinit_time.h" #include "cafe/kernel/cafe_kernel_info.h" #include #include namespace cafe::coreinit { struct StaticSystemInfoData { be2_struct systemInfo; be2_val screenCapturePermission; be2_val enableHomeButtonMenu; be2_struct kernelInfo0; be2_struct kernelInfo6; be2_array argstr; }; static virt_ptr sSystemInfoData = nullptr; virt_ptr OSGetSystemInfo() { return virt_addrof(sSystemInfoData->systemInfo); } OSSystemMode OSGetSystemMode() { return kernel::getSystemMode(); } BOOL OSSetScreenCapturePermission(BOOL enabled) { auto old = sSystemInfoData->screenCapturePermission; sSystemInfoData->screenCapturePermission = enabled; return old; } BOOL OSGetScreenCapturePermission() { return sSystemInfoData->screenCapturePermission; } uint32_t OSGetConsoleType() { // Value from a WiiU retail v4.0.0 return 0x3000050; } uint32_t OSGetSecurityLevel() { return 0; } BOOL OSIsHomeButtonMenuEnabled() { return sSystemInfoData->enableHomeButtonMenu; } BOOL OSEnableHomeButtonMenu(BOOL enable) { sSystemInfoData->enableHomeButtonMenu = enable; return TRUE; } void OSBlockThreadsOnExit() { // TODO: OSBlockThreadsOnExit } uint64_t OSGetTitleID() { return sSystemInfoData->kernelInfo0.titleId; } uint64_t OSGetOSID() { return sSystemInfoData->kernelInfo6.osTitleId; } kernel::UniqueProcessId OSGetUPID() { return sSystemInfoData->kernelInfo0.upid; } OSAppFlags OSGetAppFlags() { return sSystemInfoData->kernelInfo0.appFlags; } OSShutdownReason OSGetShutdownReason() { return OSShutdownReason::NoShutdown; } void OSGetArgcArgv(virt_ptr argc, virt_ptr> argv) { *argc = 0u; *argv = nullptr; } BOOL OSIsDebuggerPresent() { return FALSE; } BOOL OSIsDebuggerInitialized() { return FALSE; } int32_t ENVGetEnvironmentVariable(virt_ptr key, virt_ptr buffer, uint32_t length) { if (buffer) { *buffer = char { 0 }; } return 0; } namespace internal { virt_ptr getArgStr() { return virt_addrof(sSystemInfoData->argstr); } virt_ptr getCoreinitLoaderHandle() { return sSystemInfoData->kernelInfo0.coreinit.loaderHandle; } virt_addr getDefaultThreadStackBase(uint32_t coreId) { if (coreId == 0) { return sSystemInfoData->kernelInfo0.stackBase0; } else if (coreId == 1) { return sSystemInfoData->kernelInfo0.stackBase1; } else if (coreId == 2) { return sSystemInfoData->kernelInfo0.stackBase2; } else { decaf_abort(fmt::format("Invalid coreId {}", coreId)); } } virt_addr getDefaultThreadStackEnd(uint32_t coreId) { if (coreId == 0) { return sSystemInfoData->kernelInfo0.stackEnd0; } else if (coreId == 1) { return sSystemInfoData->kernelInfo0.stackEnd1; } else if (coreId == 2) { return sSystemInfoData->kernelInfo0.stackEnd2; } else { decaf_abort(fmt::format("Invalid coreId {}", coreId)); } } virt_addr getLockedCacheBaseAddress(uint32_t coreId) { if (coreId == 0) { return sSystemInfoData->kernelInfo0.lockedCacheBase0; } else if (coreId == 1) { return sSystemInfoData->kernelInfo0.lockedCacheBase1; } else if (coreId == 2) { return sSystemInfoData->kernelInfo0.lockedCacheBase2; } else { decaf_abort(fmt::format("Invalid coreId {}", coreId)); } } virt_addr getMem2BaseAddress() { return sSystemInfoData->kernelInfo0.dataAreaStart; } virt_addr getMem2EndAddress() { return sSystemInfoData->kernelInfo0.dataAreaEnd; } phys_addr getMem2PhysAddress() { return sSystemInfoData->kernelInfo0.physDataAreaStart; } virt_addr getSdaBase() { return sSystemInfoData->kernelInfo0.sdaBase; } virt_addr getSda2Base() { return sSystemInfoData->kernelInfo0.sda2Base; } uint32_t getSystemHeapSize() { return sSystemInfoData->kernelInfo0.systemHeapSize; } bool isAppDebugLevelVerbose() { return sSystemInfoData->kernelInfo0.appFlags.value() .debugLevel() >= OSAppFlagsDebugLevel::Verbose; } bool isAppDebugLevelNotice() { return sSystemInfoData->kernelInfo0.appFlags.value() .debugLevel() >= OSAppFlagsDebugLevel::Notice; } void initialiseSystemInfo() { sSystemInfoData->systemInfo.busSpeed = cpu::busClockSpeed; sSystemInfoData->systemInfo.coreSpeed = cpu::coreClockSpeed; sSystemInfoData->systemInfo.baseTime = internal::getBaseTime(); sSystemInfoData->systemInfo.l2CacheSize[0] = 512 * 1024u; sSystemInfoData->systemInfo.l2CacheSize[1] = 2 * 1024 * 1024u; sSystemInfoData->systemInfo.l2CacheSize[2] = 512 * 1024u; sSystemInfoData->systemInfo.cpuRatio = 5u; sSystemInfoData->screenCapturePermission = TRUE; sSystemInfoData->enableHomeButtonMenu = TRUE; kernel::getInfo(kernel::InfoType::Type0, virt_addrof(sSystemInfoData->kernelInfo0), sizeof(kernel::Info0)); kernel::getInfo(kernel::InfoType::Type6, virt_addrof(sSystemInfoData->kernelInfo6), sizeof(kernel::Info6)); kernel::getInfo(kernel::InfoType::ArgStr, virt_addrof(sSystemInfoData->argstr), sSystemInfoData->argstr.size()); } } // namespace internal void Library::registerSystemInfoSymbols() { RegisterFunctionExport(OSGetSystemInfo); RegisterFunctionExport(OSGetSystemMode); RegisterFunctionExport(OSSetScreenCapturePermission); RegisterFunctionExport(OSGetScreenCapturePermission); RegisterFunctionExport(OSGetConsoleType); RegisterFunctionExport(OSGetSecurityLevel); RegisterFunctionExport(OSEnableHomeButtonMenu); RegisterFunctionExport(OSIsHomeButtonMenuEnabled); RegisterFunctionExport(OSBlockThreadsOnExit); RegisterFunctionExport(OSGetTitleID); RegisterFunctionExport(OSGetOSID); RegisterFunctionExport(OSGetUPID); RegisterFunctionExport(OSGetShutdownReason); RegisterFunctionExport(OSGetArgcArgv); RegisterFunctionExport(OSIsDebuggerPresent); RegisterFunctionExport(OSIsDebuggerInitialized); RegisterFunctionExport(ENVGetEnvironmentVariable); RegisterFunctionExportName("_OSGetAppFlags", OSGetAppFlags); RegisterDataInternal(sSystemInfoData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_systeminfo.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_time.h" #include "cafe/kernel/cafe_kernel_info.h" #include "cafe/kernel/cafe_kernel_process.h" #include #include namespace cafe::coreinit { #pragma pack(push, 1) struct OSSystemInfo { be2_val busSpeed; be2_val coreSpeed; be2_val baseTime; be2_array l2CacheSize; be2_val cpuRatio; }; CHECK_OFFSET(OSSystemInfo, 0x0, busSpeed); CHECK_OFFSET(OSSystemInfo, 0x4, coreSpeed); CHECK_OFFSET(OSSystemInfo, 0x8, baseTime); CHECK_OFFSET(OSSystemInfo, 0x10, l2CacheSize); CHECK_OFFSET(OSSystemInfo, 0x1C, cpuRatio); CHECK_SIZE(OSSystemInfo, 0x20); using OSAppFlags = kernel::ProcessFlags; using OSAppFlagsDebugLevel = kernel::DebugLevel; using OSSystemMode = kernel::SystemMode; #pragma pack(pop) virt_ptr OSGetSystemInfo(); OSSystemMode OSGetSystemMode(); BOOL OSSetScreenCapturePermission(BOOL enabled); BOOL OSGetScreenCapturePermission(); uint32_t OSGetConsoleType(); uint32_t OSGetSecurityLevel(); BOOL OSEnableHomeButtonMenu(BOOL enable); BOOL OSIsHomeButtonMenuEnabled(); void OSBlockThreadsOnExit(); uint64_t OSGetTitleID(); uint64_t OSGetOSID(); kernel::UniqueProcessId OSGetUPID(); OSAppFlags OSGetAppFlags(); OSShutdownReason OSGetShutdownReason(); void OSGetArgcArgv(virt_ptr argc, virt_ptr> argv); BOOL OSIsDebuggerPresent(); BOOL OSIsDebuggerInitialized(); int32_t ENVGetEnvironmentVariable(virt_ptr key, virt_ptr buffer, uint32_t length); namespace internal { virt_ptr getArgStr(); virt_ptr getCoreinitLoaderHandle(); virt_addr getDefaultThreadStackBase(uint32_t coreId); virt_addr getDefaultThreadStackEnd(uint32_t coreId); virt_addr getLockedCacheBaseAddress(uint32_t coreId); virt_addr getMem2BaseAddress(); virt_addr getMem2EndAddress(); phys_addr getMem2PhysAddress(); virt_addr getSdaBase(); virt_addr getSda2Base(); uint32_t getSystemHeapSize(); bool isAppDebugLevelVerbose(); bool isAppDebugLevelNotice(); void initialiseSystemInfo(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemmessagequeue.cpp ================================================ #include "coreinit.h" #include "coreinit_systemmessagequeue.h" namespace cafe::coreinit { constexpr auto SystemMessageQueueLength = 16u; struct StaticSystemMessageQueueData { be2_struct queue; be2_array queueName; be2_array messages; }; static virt_ptr sSystemMessageQueueData = nullptr; /** * Get a pointer to the system message queue. * * Used for acquiring & releasing foreground messages. */ virt_ptr OSGetSystemMessageQueue() { return virt_addrof(sSystemMessageQueueData->queue); } namespace internal { void initialiseSystemMessageQueue() { sSystemMessageQueueData->queueName = "{ SystemMQ }"; OSInitMessageQueueEx(virt_addrof(sSystemMessageQueueData->queue), virt_addrof(sSystemMessageQueueData->messages), sSystemMessageQueueData->messages.size(), virt_addrof(sSystemMessageQueueData->queueName)); } } // namespace internal void Library::registerSystemMessageQueueSymbols() { RegisterFunctionExport(OSGetSystemMessageQueue); RegisterDataInternal(sSystemMessageQueueData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemmessagequeue.h ================================================ #pragma once #include "coreinit_messagequeue.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_systemmessagequeue System Message Queue * \ingroup coreinit * @{ */ virt_ptr OSGetSystemMessageQueue(); namespace internal { void initialiseSystemMessageQueue(); } // namespace internal /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_taskqueue.cpp ================================================ #include "coreinit.h" #include "coreinit_core.h" #include "coreinit_spinlock.h" #include "coreinit_taskqueue.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" namespace cafe::coreinit { /** * Initialise a task queue structure. */ void MPInitTaskQ(virt_ptr queue, virt_ptr> taskBuffer, uint32_t taskBufferLen) { OSInitSpinLock(virt_addrof(queue->lock)); queue->self = queue; queue->state.store(MPTaskQueueState::Initialised); queue->tasks = 0u; queue->tasksReady = 0u; queue->tasksRunning = 0u; queue->tasksFinished = 0u; queue->queueIndex = 0u; queue->queueSize = 0u; queue->queue = taskBuffer; queue->queueMaxSize = taskBufferLen; } /** * Terminates a task queue. * * Yes this really does only return TRUE in coreinit.rpl */ BOOL MPTermTaskQ(virt_ptr queue) { return TRUE; } /** * Get the status of a task queue. */ BOOL MPGetTaskQInfo(virt_ptr queue, virt_ptr info) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); info->state = queue->state.load(); info->tasks = queue->tasks; info->tasksReady = queue->tasksReady; info->tasksRunning = queue->tasksRunning; info->tasksFinished = queue->tasksFinished; OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; } /** * Starts a task queue. * * Sets the task state to Ready. * * \return Returns true if state was previously Initialised or Stopped. */ BOOL MPStartTaskQ(virt_ptr queue) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); if (queue->state.load() == MPTaskQueueState::Initialised || queue->state.load() == MPTaskQueueState::Stopped) { queue->state.store(MPTaskQueueState::Ready); OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return FALSE; } /** * Stops a task queue. * * If there are tasks running the state is set to Stopping. * If there are no tasks running the state is set to Stopped. * * \return Returns FALSE if the task queue is not in the Ready state. */ BOOL MPStopTaskQ(virt_ptr queue) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); if (queue->state.load() != MPTaskQueueState::Ready) { OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return FALSE; } if (queue->tasksRunning == 0) { queue->state.store(MPTaskQueueState::Stopped); } else { queue->state.store(MPTaskQueueState::Stopping); } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; } /** * Resets the state of the task queue. * * This does not remove any tasks from the queue. It just resets the task queue * such that all tasks are ready to be run again. */ BOOL MPResetTaskQ(virt_ptr queue) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); if (queue->state.load() != MPTaskQueueState::Finished && queue->state.load() != MPTaskQueueState::Stopped) { OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return FALSE; } queue->state.store(MPTaskQueueState::Initialised); queue->tasks = queue->queueSize; queue->tasksReady = queue->queueSize; queue->tasksRunning = 0u; queue->tasksFinished = 0u; queue->queueIndex = 0u; for (auto i = 0u; i < queue->tasks; ++i) { auto task = queue->queue[i]; task->result = 0u; task->coreID = 3u; task->duration = 0u; task->state = MPTaskState::Ready; } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; } /** * Add a task to the end of the queue. * * Returns FALSE if the task state is not set to Initialised. * Returns FALSE if the task queue is full. * Returns FALSE if the task queue state is not valid. * * \return Returns TRUE if the task was added to the queue. */ BOOL MPEnqueTask(virt_ptr queue, virt_ptr task) { if (task->state != MPTaskState::Initialised) { return FALSE; } OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); if (queue->queueSize >= queue->queueMaxSize) { OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return FALSE; } if (queue->state.load() < MPTaskQueueState::Initialised || queue->state.load() > MPTaskQueueState::Finished) { OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return FALSE; } task->queue = queue; task->state = MPTaskState::Ready; queue->tasks++; queue->tasksReady++; queue->queue[queue->queueSize] = task; queue->queueSize++; if (queue->state.load() == MPTaskQueueState::Finished) { queue->state.store(MPTaskQueueState::Ready); } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; } /** * Dequeue 1 task at queueIndex * * Does not remove tasks from queue buffer. * * \return Returns dequeued task. */ virt_ptr MPDequeTask(virt_ptr queue) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); if (queue->state.load() != MPTaskQueueState::Ready || queue->queueIndex == queue->queueSize) { OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return nullptr; } auto task = queue->queue[queue->queueIndex]; queue->queueIndex++; OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return task; } /** * Dequeue N tasks from queueIndex * * Does not remove tasks from queue buffer. * * \return Returns number of tasks dequeued. */ uint32_t MPDequeTasks(virt_ptr queue, virt_ptr> taskBuffer, uint32_t taskBufferLen) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); uint32_t count, available; if (queue->state.load() != MPTaskQueueState::Ready) { OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return 0; } available = queue->queueSize - queue->queueIndex; count = std::min(available, taskBufferLen); for (auto i = 0u; i < count; ++i) { taskBuffer[i] = queue->queue[queue->queueIndex]; queue->queueIndex++; } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return count; } /** * Busy wait until state matches mask. * * \return Always returns TRUE. */ BOOL MPWaitTaskQ(virt_ptr queue, MPTaskQueueState mask) { while ((queue->state.load() & mask) == 0); return TRUE; } /** * Busy wait with timeout until state matches mask. * * \return Returns FALSE if wait timed out. */ BOOL MPWaitTaskQWithTimeout(virt_ptr queue, MPTaskQueueState mask, OSTimeNanoseconds timeoutNS) { auto start = OSGetTime(); auto end = start + internal::nsToTicks(timeoutNS); while ((queue->state.load() & mask) == 0) { if (OSGetTime() >= end) { break; } } return (queue->state.load() & mask) != 0; } /** * Print debug information about task queue. */ BOOL MPPrintTaskQStats(virt_ptr queue, uint32_t unk) { // TODO: Implement MPPrintTaskQStats return TRUE; } /** * Initialises a task structure. */ void MPInitTask(virt_ptr task, MPTaskFunc func, uint32_t userArg1, uint32_t userArg2) { task->self = task; task->queue = nullptr; task->state = MPTaskState::Initialised; task->func = func; task->userArg1 = userArg1; task->userArg2 = userArg2; task->result = 0u; task->coreID = 3u; task->duration = 0u; task->userData = nullptr; } /** * Terminates a task. * * Yes this really does only return TRUE in coreinit.rpl */ BOOL MPTermTask(virt_ptr task) { return TRUE; } /** * Get information about a task. * * \return Returns TRUE if successful. */ BOOL MPGetTaskInfo(virt_ptr task, virt_ptr info) { info->coreID = task->coreID; info->duration = task->duration; info->result = task->result; info->state = task->state; return TRUE; } /** * Returns a task's user data which can be set with MPSetTaskUserData. */ virt_ptr MPGetTaskUserData(virt_ptr task) { return task->userData; } /** * Sets a task's user data which can be retrieved with MPGetTaskUserData. */ void MPSetTaskUserData(virt_ptr task, virt_ptr userData) { task->userData = userData; } /** * Run N tasks from queue. * * \param tasks Number of tasks to dequeue and run at once * * Does not remove tasks from queue. * Can be run from multiple threads at once. * * Side Effects: * - Sets state to Stopped if state is Stopping and tasksRunning reaches 0. * - Sets state to Finished if all tasks are finished. * - TasksReady -> TasksRunning -> TasksFinished. * * Returns TRUE if at least 1 task is run. */ BOOL MPRunTasksFromTaskQ(virt_ptr queue, uint32_t tasks) { BOOL result = FALSE; while (queue->state.load() == MPTaskQueueState::Ready) { OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); auto available = queue->queueSize - queue->queueIndex; auto count = std::min(available, tasks); auto first = queue->queueIndex; queue->tasksReady -= count; queue->tasksRunning += count; queue->queueIndex += count; OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); if (count == 0) { // Nothing to run, lets go home! break; } // Result is TRUE if at least 1 task is run result = TRUE; // Mark all tasks as running for (auto i = 0u; i < count; ++i) { auto task = queue->queue[first + i]; task->state = MPTaskState::Running; task->coreID = OSGetCoreId(); } // Run all tasks for (auto i = 0u; i < count; ++i) { auto task = queue->queue[first + i]; auto start = OSGetTime(); task->result = cafe::invoke(cpu::this_core::state(), task->func, task->userArg1, task->userArg2); task->state = MPTaskState::Finished; task->duration = OSGetTime() - start; } OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); queue->tasksRunning -= count; queue->tasksFinished += count; if (queue->state.load() == MPTaskQueueState::Stopping && queue->tasksRunning == 0) { queue->state.store(MPTaskQueueState::Stopped); } if (queue->tasks == queue->tasksFinished) { queue->state.store(MPTaskQueueState::Finished); } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); } return result; } /** * Run a specific task. * * The task must belong to a queue. * The task must be in the Ready state. * * \return Returns TRUE if task was run. */ BOOL MPRunTask(virt_ptr task) { auto queue = task->queue; if (task->state != MPTaskState::Ready) { return FALSE; } if (!queue || queue->state.load() == MPTaskQueueState::Stopping || queue->state.load() == MPTaskQueueState::Stopped) { return FALSE; } OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); queue->tasksReady--; queue->tasksRunning++; OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); task->state = MPTaskState::Running; task->coreID = OSGetCoreId(); auto start = OSGetTime(); task->result = cafe::invoke(cpu::this_core::state(), task->func, task->userArg1, task->userArg2); task->duration = OSGetTime() - start; task->state = MPTaskState::Finished; OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock)); queue->tasksRunning--; queue->tasksFinished++; if (queue->state.load() == MPTaskQueueState::Stopping && queue->tasksRunning == 0) { queue->state.store(MPTaskQueueState::Stopped); } if (queue->tasks == queue->tasksFinished) { queue->state.store(MPTaskQueueState::Finished); } OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock)); return TRUE; } void Library::registerTaskQueueSymbols() { RegisterFunctionExport(MPInitTaskQ); RegisterFunctionExport(MPTermTaskQ); RegisterFunctionExport(MPGetTaskQInfo); RegisterFunctionExport(MPStartTaskQ); RegisterFunctionExport(MPStopTaskQ); RegisterFunctionExport(MPResetTaskQ); RegisterFunctionExport(MPEnqueTask); RegisterFunctionExport(MPDequeTask); RegisterFunctionExport(MPDequeTasks); RegisterFunctionExport(MPWaitTaskQ); RegisterFunctionExport(MPWaitTaskQWithTimeout); RegisterFunctionExport(MPPrintTaskQStats); RegisterFunctionExport(MPInitTask); RegisterFunctionExport(MPTermTask); RegisterFunctionExport(MPGetTaskInfo); RegisterFunctionExport(MPGetTaskUserData); RegisterFunctionExport(MPSetTaskUserData); RegisterFunctionExport(MPRunTasksFromTaskQ); RegisterFunctionExport(MPRunTask); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_taskqueue.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_spinlock.h" #include "coreinit_time.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_taskqueue Task Queue * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct MPTaskInfo; struct MPTask; struct MPTaskQueueInfo; struct MPTaskQueue; using MPTaskFunc = virt_func_ptr; struct MPTaskInfo { be2_val state; be2_val result; be2_val coreID; be2_val duration; }; CHECK_OFFSET(MPTaskInfo, 0x00, state); CHECK_OFFSET(MPTaskInfo, 0x04, result); CHECK_OFFSET(MPTaskInfo, 0x08, coreID); CHECK_OFFSET(MPTaskInfo, 0x0C, duration); CHECK_SIZE(MPTaskInfo, 0x14); struct MPTask { be2_virt_ptr self; be2_virt_ptr queue; be2_val state; be2_val func; be2_val userArg1; be2_val userArg2; be2_val result; be2_val coreID; be2_val duration; be2_virt_ptr userData; }; CHECK_OFFSET(MPTask, 0x00, self); CHECK_OFFSET(MPTask, 0x04, queue); CHECK_OFFSET(MPTask, 0x08, state); CHECK_OFFSET(MPTask, 0x0C, func); CHECK_OFFSET(MPTask, 0x10, userArg1); CHECK_OFFSET(MPTask, 0x14, userArg2); CHECK_OFFSET(MPTask, 0x18, result); CHECK_OFFSET(MPTask, 0x1C, coreID); CHECK_OFFSET(MPTask, 0x20, duration); CHECK_OFFSET(MPTask, 0x28, userData); CHECK_SIZE(MPTask, 0x2C); struct MPTaskQueueInfo { be2_val state; be2_val tasks; be2_val tasksReady; be2_val tasksRunning; be2_val tasksFinished; }; CHECK_OFFSET(MPTaskQueueInfo, 0x00, state); CHECK_OFFSET(MPTaskQueueInfo, 0x04, tasks); CHECK_OFFSET(MPTaskQueueInfo, 0x08, tasksReady); CHECK_OFFSET(MPTaskQueueInfo, 0x0C, tasksRunning); CHECK_OFFSET(MPTaskQueueInfo, 0x10, tasksFinished); CHECK_SIZE(MPTaskQueueInfo, 0x14); struct MPTaskQueue { be2_virt_ptr self; be2_atomic state; be2_val tasks; be2_val tasksReady; be2_val tasksRunning; UNKNOWN(4); be2_val tasksFinished; UNKNOWN(8); be2_val queueIndex; UNKNOWN(8); be2_val queueSize; UNKNOWN(4); be2_virt_ptr> queue; be2_val queueMaxSize; be2_struct lock; }; CHECK_OFFSET(MPTaskQueue, 0x00, self); CHECK_OFFSET(MPTaskQueue, 0x04, state); CHECK_OFFSET(MPTaskQueue, 0x08, tasks); CHECK_OFFSET(MPTaskQueue, 0x0C, tasksReady); CHECK_OFFSET(MPTaskQueue, 0x10, tasksRunning); CHECK_OFFSET(MPTaskQueue, 0x18, tasksFinished); CHECK_OFFSET(MPTaskQueue, 0x24, queueIndex); CHECK_OFFSET(MPTaskQueue, 0x30, queueSize); CHECK_OFFSET(MPTaskQueue, 0x38, queue); CHECK_OFFSET(MPTaskQueue, 0x3C, queueMaxSize); CHECK_OFFSET(MPTaskQueue, 0x40, lock); CHECK_SIZE(MPTaskQueue, 0x50); #pragma pack(pop) void MPInitTaskQ(virt_ptr queue, virt_ptr> taskBuffer, uint32_t taskBufferLen); BOOL MPTermTaskQ(virt_ptr queue); BOOL MPGetTaskQInfo(virt_ptr queue, virt_ptr info); BOOL MPStartTaskQ(virt_ptr queue); BOOL MPStopTaskQ(virt_ptr queue); BOOL MPResetTaskQ(virt_ptr queue); BOOL MPEnqueTask(virt_ptr queue, virt_ptr task); virt_ptr MPDequeTask(virt_ptr queue); uint32_t MPDequeTasks(virt_ptr queue, virt_ptr> taskBuffer, uint32_t taskBufferLen); BOOL MPWaitTaskQ(virt_ptr queue, MPTaskQueueState mask); BOOL MPWaitTaskQWithTimeout(virt_ptr queue, MPTaskQueueState wmask, OSTimeNanoseconds timeoutNS); BOOL MPPrintTaskQStats(virt_ptr queue, uint32_t unk); void MPInitTask(virt_ptr task, MPTaskFunc func, uint32_t userArg1, uint32_t userArg2); BOOL MPTermTask(virt_ptr task); BOOL MPGetTaskInfo(virt_ptr task, virt_ptr info); virt_ptr MPGetTaskUserData(virt_ptr task); void MPSetTaskUserData(virt_ptr task, virt_ptr userData); BOOL MPRunTasksFromTaskQ(virt_ptr queue, uint32_t count); BOOL MPRunTask(virt_ptr task); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_thread.cpp ================================================ #include "coreinit.h" #include "coreinit_alarm.h" #include "coreinit_core.h" #include "coreinit_context.h" #include "coreinit_cosreport.h" #include "coreinit_dynload.h" #include "coreinit_enum_string.h" #include "coreinit_fastmutex.h" #include "coreinit_ghs.h" #include "coreinit_lockedcache.h" #include "coreinit_ipcdriver.h" #include "coreinit_interrupts.h" #include "coreinit_memheap.h" #include "coreinit_memory.h" #include "coreinit_mutex.h" #include "coreinit_rendezvous.h" #include "coreinit_scheduler.h" #include "coreinit_systeminfo.h" #include "coreinit_thread.h" #include "cafe/cafe_stackobject.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/kernel/cafe_kernel.h" #include "cafe/kernel/cafe_kernel_context.h" #include "cafe/libraries/cafe_hle.h" #include #include #include #include #include #include #include #include namespace cafe::coreinit { static uint16_t sThreadId = 1; static AlarmCallbackFn sSleepAlarmHandler = nullptr; static OSThreadEntryPointFn sThreadEntryPoint = nullptr; static OSThreadEntryPointFn sDeallocatorThreadEntryPoint = nullptr; static OSThreadEntryPointFn sDefaultThreadEntryPoint = nullptr; constexpr auto DeallocatorThreadSize = 0x2000u; struct StaticThreadData { struct PerCoreData { be2_struct defaultThread; be2_array defaultThreadName; be2_struct timeSliceAlarm; be2_struct deallocationQueue; be2_struct deallocationThreadQueue; be2_struct deallocatorThread; be2_array deallocatorThreadStack; be2_array deallocatorThreadName; }; be2_array perCoreData; be2_struct defaultThreadInitRendezvous; be2_val defaultThreadInitRendezvousWaitMask; }; static virt_ptr sThreadData = nullptr; static void clearThreadStackWithValue(virt_ptr thread, uint32_t value) { auto clearStart = virt_ptr { nullptr }; auto clearEnd = virt_ptr { nullptr }; if (OSGetCurrentThread() == thread) { clearStart = thread->stackEnd + 4; clearEnd = virt_cast(OSGetStackPointer()); } else { // We assume that the thread must be paused while this is happening... // This might be a bad assumption to make, but otherwise we run // into some sketchy race conditions. clearStart = thread->stackEnd + 4; clearEnd = virt_cast(virt_addr { thread->context.gpr[1].value() }); } for (auto addr = clearStart; addr < clearEnd; addr += 4) { *addr = value; } } /** * Cancels a thread. * * This sets the threads requestFlag to OSThreadRequest::Cancel, the thread will * be terminated next time OSTestThreadCancel is called. */ void OSCancelThread(virt_ptr thread) { bool reschedule = false; internal::lockScheduler(); if (thread->requestFlag == OSThreadRequest::Suspend) { internal::wakeupThreadWaitForSuspensionNoLock( virt_addrof(thread->suspendQueue), -1); reschedule = true; } if (thread->suspendCounter != 0) { if (thread->cancelState == OSThreadCancelState::Enabled) { internal::resumeThreadNoLock(thread, thread->suspendCounter); reschedule = true; } } if (reschedule) { internal::rescheduleAllCoreNoLock(); } thread->suspendCounter = 0; thread->needSuspend = 0; thread->requestFlag = OSThreadRequest::Cancel; internal::unlockScheduler(); if (OSGetCurrentThread() == thread) { if (thread->cancelState == OSThreadCancelState::Enabled) { OSExitThread(-1); } } } /** * Returns the count of active threads. */ int32_t OSCheckActiveThreads() { internal::lockScheduler(); auto threadCount = internal::checkActiveThreadsNoLock(); internal::unlockScheduler(); return threadCount; } /** * Get the maximum amount of stack the thread has used. */ int32_t OSCheckThreadStackUsage(virt_ptr thread) { auto addr = virt_ptr { nullptr }; internal::lockScheduler(); for (addr = thread->stackEnd + 4; addr < thread->stackStart; addr += 4) { if (*addr != 0xFEFEFEFE) { break; } } auto result = virt_cast(thread->stackStart) - virt_cast(addr); internal::unlockScheduler(); return static_cast(result); } /** * Clear current stack with a value. */ void OSClearStack(uint32_t value) { auto thread = OSGetCurrentThread(); auto stackTop = OSGetStackPointer(); for (auto ptr = thread->stackEnd + 1; ptr < stackTop; ++ptr) { *ptr = value; } } /** * Disable tracking of thread stack usage */ void OSClearThreadStackUsage(virt_ptr thread) { internal::lockScheduler(); if (!thread) { thread = OSGetCurrentThread(); } thread->attr &= ~OSThreadAttributes::StackUsage; internal::unlockScheduler(); } /** * Clears a thread's suspend counter and resumes it. */ void OSContinueThread(virt_ptr thread) { internal::lockScheduler(); internal::resumeThreadNoLock(thread, thread->suspendCounter); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); } /** * Thread entry. */ static uint32_t threadEntry(uint32_t argc, virt_ptr argv) { auto thread = OSGetCurrentThread(); auto interruptsState = OSDisableInterrupts(); internal::ghsExceptionInit(thread); OSRestoreInterrupts(interruptsState); return cafe::invoke(cpu::this_core::state(), thread->entryPoint, argc, argv); } /** * Setup thread run state, shared by OSRunThread and OSCreateThread */ static void initialiseThreadState(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stack, uint32_t stackSize, int32_t priority, uint32_t pir, OSThreadType type) { // Setup thread state thread->priority = priority; thread->basePriority = priority; thread->tag = OSThread::Tag; thread->suspendResult = -1; thread->needSuspend = 0; thread->exitValue = -1; thread->type = type; thread->state = entry ? OSThreadState::Ready : OSThreadState::None; thread->mutex = nullptr; thread->deallocator = nullptr; thread->coreTimeConsumedNs = 0ull; thread->cleanupCallback = nullptr; thread->requestFlag = OSThreadRequest::None; thread->fastMutex = nullptr; thread->waitEventTimeoutAlarm = nullptr; thread->runQuantumTicks = 0ll; thread->cancelState = OSThreadCancelState::Enabled; thread->entryPoint = entry; thread->suspendCounter = entry ? 1 : 0; thread->eh_globals = nullptr; thread->eh_mem_manage.fill(nullptr); thread->eh_store_globals.fill(nullptr); thread->eh_store_globals_tdeh.fill(nullptr); thread->tlsSectionCount = uint16_t { 0u }; thread->tlsSections = nullptr; thread->contendedFastMutexes.head = nullptr; thread->contendedFastMutexes.tail = nullptr; thread->mutexQueue.head = nullptr; thread->mutexQueue.tail = nullptr; thread->mutexQueue.parent = thread; thread->alarmCancelled = 0; thread->specific.fill(0u); thread->wakeCount = 0ull; thread->unk0x610 = 0ll; thread->unk0x618 = 0ll; thread->unk0x620 = 0x7FFFFFFFFFFFFFFFll; thread->unk0x628 = 0ll; OSInitThreadQueueEx(virt_addrof(thread->joinQueue), thread); OSInitThreadQueueEx(virt_addrof(thread->suspendQueue), thread); // Setup thread stack auto stackInit = virt_cast(align_down(virt_cast(stack), 8)); *(stackInit - 1) = 0u; *(stackInit - 2) = 0u; thread->stackStart = virt_cast(stack); thread->stackEnd = virt_cast(virt_cast(stack) - stackSize); *thread->stackEnd = 0xDEADBABE; // Setup thread context OSInitContext(virt_addrof(thread->context), virt_func_cast(sThreadEntryPoint), align_down(virt_cast(stack), 8) - 8); thread->context.pir = pir; thread->context.lr = hle::getLibrary(hle::LibraryId::coreinit)->findSymbolAddress("OSExitThread"); thread->context.gpr[3] = argc; thread->context.gpr[4] = static_cast(virt_cast(argv)); thread->context.fpscr = 4u; thread->context.psf.fill(0.0); thread->context.fpr.fill(0.0); thread->context.gqr[2] = 0x40004u; thread->context.gqr[3] = 0x50005u; thread->context.gqr[4] = 0x60006u; thread->context.gqr[5] = 0x70007u; thread->context.coretime.fill(0); } static BOOL createThread(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stack, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes, OSThreadType type) { auto currentThread = internal::getCurrentThread(); // If no affinity is defined, we need to copy the affinity from the calling thread if ((attributes & OSThreadAttributes::AffinityAny) == 0) { auto curAttr = currentThread->attr; attributes = attributes | (curAttr & OSThreadAttributes::AffinityAny); } auto realPriority = priority; if (type == OSThreadType::Driver) { if (priority < 0 || priority >= 32) { decaf_abort("Thread priority was out of range"); } realPriority = priority; } else if (type == OSThreadType::AppIo) { if (priority < 0 || priority >= 32) { decaf_abort("Thread priority was out of range"); } realPriority = priority + 32; } else if (type == OSThreadType::App) { if (priority < 0 || priority >= 32) { decaf_abort("Thread priority was out of range"); } realPriority = priority + 64; } else { return FALSE; } // Setup thread state internal::lockScheduler(); std::memset(thread.get(), 0, sizeof(OSThread)); initialiseThreadState(thread, entry, argc, argv, stack, stackSize, realPriority, OSGetCoreId(), type); thread->name = nullptr; thread->context.attr = attributes & OSThreadAttributes::AffinityAny; thread->attr = attributes; thread->id = sThreadId++; thread->dsiCallback = currentThread->dsiCallback; thread->isiCallback = currentThread->isiCallback; thread->programCallback = currentThread->programCallback; thread->perfMonCallback = currentThread->perfMonCallback; thread->alignCallback = currentThread->alignCallback; // Copy FPU exception status thread->context.fpscr |= currentThread->context.fpscr & 0xF8; if (entry) { internal::markThreadActiveNoLock(thread); } internal::unlockScheduler(); gLog->info("Thread Created: ptr {}, id 0x{:X}, basePriority {}, attr 0x{:02X}, entry {}, stackStart {}, stackEnd {}", thread, thread->id, thread->basePriority, thread->attr, virt_func_cast(entry), thread->stackStart, thread->stackEnd); return TRUE; } BOOL OSCreateThread(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stack, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes) { return createThread(thread, entry, argc, argv, stack, stackSize, priority, attributes, OSThreadType::App); } BOOL OSCreateThreadType(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stack, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes, OSThreadType type) { if (type != OSThreadType::AppIo && type != OSThreadType::App) { return FALSE; } return createThread(thread, entry, argc, argv, stack, stackSize, priority, attributes, type); } BOOL coreinit__OSCreateThreadType(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stack, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes, OSThreadType type) { return createThread(thread, entry, argc, argv, stack, stackSize, priority, attributes, type); } /** * Detach thread. */ void OSDetachThread(virt_ptr thread) { internal::lockScheduler(); // HACK: Unfortunately this check is not valid in all games. One Piece performs // OSJoinThread on a thread, and then subsequently calls OSDetachThread on it // for whatever reason. Coreinit doesnt check this, so we can't do this check. //decaf_check(internal::isThreadActiveNoLock(thread)); thread->attr |= OSThreadAttributes::Detached; if (thread->state == OSThreadState::Moribund) { // Thread has already ended so we can remove it from the active list internal::markThreadInactiveNoLock(thread); if (thread->deallocator) { internal::queueThreadDeallocation(thread); } thread->state = OSThreadState::None; // TODO: thread->id = 0x8000; } internal::wakeupThreadNoLock(virt_addrof(thread->joinQueue)); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); } /** * Exit the current thread with a exit code. * * This function is implicitly called when the thread entry point returns. */ [[noreturn]] void OSExitThread(int value) { auto thread = OSGetCurrentThread(); // Call any thread cleanup callbacks if (thread->cleanupCallback) { thread->cancelState |= OSThreadCancelState::Disabled; cafe::invoke(cpu::this_core::state(), thread->cleanupCallback, thread, virt_cast(thread->stackEnd)); } // Cleanup the GHS exceptions we previously created internal::ghsExceptionCleanup(thread); // Free any TLS data which was allocated to this thread if (thread->tlsSections) { internal::dynLoadTlsFree(thread); } // Disable interrupts and lock the scheduler OSDisableInterrupts(); internal::lockScheduler(); // Actually proccess the thread exit internal::exitThreadNoLock(value); // noreturn } /** * Get the next and previous thread in the thread's active queue. */ void OSGetActiveThreadLink(virt_ptr thread, virt_ptr link) { *link = thread->activeLink; } /** * Return pointer to OSThread object for the current thread. */ virt_ptr OSGetCurrentThread() { return internal::getCurrentThread(); } /** * Returns the default thread for a specific core. */ virt_ptr OSGetDefaultThread(uint32_t coreID) { if (coreID >= CoreCount) { return nullptr; } return virt_addrof(sThreadData->perCoreData[coreID].defaultThread); } /** * Return current stack pointer, value of r1 register. */ virt_ptr OSGetStackPointer() { return virt_cast(virt_addr { cpu::this_core::state()->systemCallStackHead }); } /** * Return user stack pointer. */ virt_ptr OSGetUserStackPointer(virt_ptr thread) { auto stack = virt_ptr { nullptr }; internal::lockScheduler(); if (OSIsThreadSuspended(thread)) { stack = thread->userStackPointer; if (!stack) { stack = virt_cast(virt_addr { thread->context.gpr[1].value() }); } } internal::unlockScheduler(); return stack; } /** * Get a thread's affinity. */ uint32_t OSGetThreadAffinity(virt_ptr thread) { return thread->attr & OSThreadAttributes::AffinityAny; } /** * Get a thread's name. */ virt_ptr OSGetThreadName(virt_ptr thread) { return thread->name; } /** * Get a thread's base priority. */ int32_t OSGetThreadPriority(virt_ptr thread) { if (thread->type == OSThreadType::Driver) { return thread->basePriority; } else if(thread->type == OSThreadType::AppIo) { return thread->basePriority - 32; } else if (thread->type == OSThreadType::App) { return thread->basePriority - 64; } decaf_abort("Unexpected thread type in OSGetThreadPriority"); } /** * Get a thread's specific value set by OSSetThreadSpecific. */ uint32_t OSGetThreadSpecific(uint32_t id) { decaf_check(id >= 0 && id < 0x10); return OSGetCurrentThread()->specific[id]; } /** * Initialise a thread queue object. */ void OSInitThreadQueue(virt_ptr queue) { OSInitThreadQueueEx(queue, nullptr); } /** * Initialise a thread queue object with a parent. */ void OSInitThreadQueueEx(virt_ptr queue, virt_ptr parent) { queue->head = nullptr; queue->tail = nullptr; queue->parent = parent; } /** * Returns TRUE if a thread is suspended. */ BOOL OSIsThreadSuspended(virt_ptr thread) { return thread->suspendCounter > 0; } /** * Returns TRUE if a thread is terminated. */ BOOL OSIsThreadTerminated(virt_ptr thread) { return thread->state == OSThreadState::None || thread->state == OSThreadState::Moribund; } /** * Wait until thread is terminated. * * \param thread Thread to wait for * \param exitValue Pointer to store thread exit value in. * \returns Returns TRUE if thread has terminated, FALSE otherwise. */ BOOL OSJoinThread(virt_ptr thread, virt_ptr outExitValue) { internal::lockScheduler(); // If the thread has not ended, let's wait for it // note only one thread is allowed in the join queue if (!(thread->attr & OSThreadAttributes::Detached) && thread->state != OSThreadState::Moribund && !thread->joinQueue.head) { internal::sleepThreadNoLock(virt_addrof(thread->joinQueue)); internal::rescheduleSelfNoLock(); if (!internal::isThreadActiveNoLock(thread)) { // This would only happen for detached threads. internal::unlockScheduler(); return FALSE; } } if (thread->state != OSThreadState::Moribund) { internal::unlockScheduler(); return FALSE; } if (outExitValue) { *outExitValue = thread->exitValue; } internal::markThreadInactiveNoLock(thread); thread->state = OSThreadState::None; if (thread->deallocator) { internal::queueThreadDeallocation(thread); internal::rescheduleSelfNoLock(); } internal::unlockScheduler(); return TRUE; } void OSPrintCurrentThreadState() { auto thread = OSGetCurrentThread(); if (!thread) { return; } auto state = cpu::this_core::state(); fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "id = {}\n", thread->id); if (thread->name) { fmt::format_to(std::back_inserter(out), "name = {}\n", thread->name); } fmt::format_to(std::back_inserter(out), "cia = 0x{:08X}\n", state->cia); fmt::format_to(std::back_inserter(out), "lr = 0x{:08X}\n", state->lr); fmt::format_to(std::back_inserter(out), "cr = 0x{:08X}\n", state->cr.value); fmt::format_to(std::back_inserter(out), "xer = 0x{:08X}\n", state->xer.value); fmt::format_to(std::back_inserter(out), "ctr = 0x{:08X}\n", state->ctr); for (auto i = 0u; i < 32; ++i) { fmt::format_to(std::back_inserter(out), "r{:<2} = 0x{:08X}\n", i, state->gpr[i]); } fmt::format_to(std::back_inserter(out), "fpscr = 0x{:08X}\n", state->fpscr.value); for (auto i = 0u; i < 32; ++i) { fmt::format_to(std::back_inserter(out), "f{:<2} = {}\n", i, state->fpr[i].value); } for (auto i = 0u; i < 32; ++i) { fmt::format_to(std::back_inserter(out), "ps{:<2} = {:<16} ps{:<2} = {}\n", i, state->fpr[i].paired0, i, state->fpr[i].paired1); } gLog->info(std::string_view { out.data(), out.size() }); } /** * Resumes a thread. * * Decrements the thread's suspend counter, if the counter reaches 0 the thread * is resumed. * * \returns Returns the previous value of the suspend counter. */ int32_t OSResumeThread(virt_ptr thread) { internal::lockScheduler(); auto oldSuspendCounter = internal::resumeThreadNoLock(thread, 1); if (oldSuspendCounter - 1 == 0) { internal::rescheduleAllCoreNoLock(); } internal::unlockScheduler(); return oldSuspendCounter; } /** * Run a function on an already created thread. * * Can only be used on idle threads. */ BOOL OSRunThread(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv) { BOOL result = FALSE; internal::lockScheduler(); if (OSIsThreadTerminated(thread)) { if (thread->state == OSThreadState::Moribund) { internal::markThreadInactiveNoLock(thread); } auto stackSize = virt_cast(thread->stackStart) - virt_cast(thread->stackEnd); initialiseThreadState(thread, entry, argc, argv, thread->stackStart, static_cast(stackSize), thread->basePriority, thread->context.pir, thread->type); internal::markThreadActiveNoLock(thread); internal::resumeThreadNoLock(thread, 1); internal::rescheduleAllCoreNoLock(); result = TRUE; } internal::unlockScheduler(); return result; } /** * Set a thread's affinity. */ BOOL OSSetThreadAffinity(virt_ptr thread, uint32_t affinity) { internal::lockScheduler(); internal::setThreadAffinityNoLock(thread, affinity); if (thread->state == OSThreadState::Ready && affinity != 0) { internal::rescheduleAllCoreNoLock(); } internal::unlockScheduler(); return TRUE; } /** * Set a thread's cancellation state. */ BOOL OSSetThreadCancelState(BOOL cancelEnabled) { auto thread = OSGetCurrentThread(); auto oldCancelEnabled = TRUE; if (thread->cancelState & OSThreadCancelState::Disabled) { oldCancelEnabled = FALSE; } if (cancelEnabled) { thread->cancelState &= ~OSThreadCancelState::Disabled; } else { thread->cancelState |= OSThreadCancelState::Disabled; } return oldCancelEnabled; } /** * Set the callback to be called just before a thread is terminated. * * \return Returns the previous callback function. */ OSThreadCleanupCallbackFn OSSetThreadCleanupCallback(virt_ptr thread, OSThreadCleanupCallbackFn callback) { internal::lockScheduler(); auto old = thread->cleanupCallback; thread->cleanupCallback = callback; internal::unlockScheduler(); return old; } /** * Set the callback to be called just after a thread is terminated. */ OSThreadDeallocatorFn OSSetThreadDeallocator(virt_ptr thread, OSThreadDeallocatorFn deallocator) { internal::lockScheduler(); auto old = thread->deallocator; thread->deallocator = deallocator; internal::unlockScheduler(); return old; } /** * Set a thread's name. */ void OSSetThreadName(virt_ptr thread, virt_ptr name) { thread->name = name; } /** * Set a thread's priority. */ BOOL OSSetThreadPriority(virt_ptr thread, int32_t priority) { auto realPriority = priority; if (thread->type == OSThreadType::Driver) { realPriority = priority; } else if (thread->type == OSThreadType::AppIo) { realPriority = priority + 32; } else if (thread->type == OSThreadType::App) { realPriority = priority + 64; } else { return FALSE; } internal::lockScheduler(); thread->basePriority = realPriority; internal::updateThreadPriorityNoLock(thread); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); return TRUE; } /** * Set a thread's run quantum. * * This is the maximum amount of time the thread can run for before being forced * to yield. */ BOOL OSSetThreadRunQuantum(virt_ptr thread, uint32_t quantumUS) { if (quantumUS != OSThreadQuantum::Infinite) { if (quantumUS < OSThreadQuantum::MinMicroseconds) { return FALSE; } if (quantumUS > OSThreadQuantum::MaxMicroseconds) { return FALSE; } } auto ticks = internal::usToTicks(quantumUS); auto result = FALSE; internal::lockScheduler(); result = internal::setThreadRunQuantumNoLock(thread, ticks); internal::unlockScheduler(); return result; } /** * Set a thread specific value. * * Can be read with OSGetThreadSpecific. */ void OSSetThreadSpecific(uint32_t id, uint32_t value) { OSGetCurrentThread()->specific[id] = value; } /** * Set thread stack usage tracking. */ BOOL OSSetThreadStackUsage(virt_ptr thread) { internal::lockScheduler(); if (!thread) { thread = OSGetCurrentThread(); } else if (thread->state == OSThreadState::Running) { internal::unlockScheduler(); return FALSE; } clearThreadStackWithValue(thread, 0xfefefefe); thread->attr |= OSThreadAttributes::StackUsage; internal::unlockScheduler(); return TRUE; } /** * Sleep the current thread and add it to a thread queue. * * Will sleep until the thread queue is woken with OSWakeupThread. */ void OSSleepThread(virt_ptr queue) { internal::lockScheduler(); internal::sleepThreadNoLock(queue); internal::rescheduleSelfNoLock(); internal::unlockScheduler(); } static void sleepAlarmHandler(virt_ptr alarm, virt_ptr context) { // Wakeup the thread waiting on this alarm auto data = virt_cast(OSGetAlarmUserData(alarm)); // System Alarm, we already have the scheduler lock internal::wakeupOneThreadNoLock(data); } /** * Sleep the current thread for a period of time. */ void OSSleepTicks(OSTime ticks) { // Create an alarm to trigger wakeup auto alarm = StackObject { }; auto queue = StackObject { }; OSCreateAlarm(alarm); OSInitThreadQueue(queue); internal::lockScheduler(); internal::setAlarmInternal(alarm, ticks, sSleepAlarmHandler, OSGetCurrentThread()); internal::sleepThreadNoLock(queue); internal::rescheduleSelfNoLock(); internal::unlockScheduler(); } /** * Suspend a thread. * * Increases a thread's suspend counter, if the counter is >0 then the thread is * suspended. * * \returns Returns the thread's previous suspend counter value */ int32_t OSSuspendThread(virt_ptr thread) { internal::lockScheduler(); int32_t result = -1; if (thread->state == OSThreadState::Moribund || thread->state == OSThreadState::None) { internal::unlockScheduler(); return -1; } if (thread->requestFlag == OSThreadRequest::Cancel) { internal::unlockScheduler(); return -1; } auto curThread = OSGetCurrentThread(); if (curThread == thread) { if (thread->cancelState == OSThreadCancelState::Enabled) { thread->needSuspend++; result = thread->suspendCounter; internal::suspendThreadNoLock(thread); internal::rescheduleAllCoreNoLock(); } } else { if (thread->suspendCounter != 0) { result = thread->suspendCounter++; } else { thread->needSuspend++; thread->requestFlag = OSThreadRequest::Suspend; internal::sleepThreadNoLock(virt_addrof(thread->suspendQueue)); internal::rescheduleSelfNoLock(); result = thread->suspendResult; } } internal::unlockScheduler(); return result; } /** * Check to see if the current thread should be cancelled or suspended. * * This is implicitly called in: * - OSLockMutex * - OSTryLockMutex * - OSUnlockMutex * - OSAcquireSpinLock * - OSTryAcquireSpinLock * - OSTryAcquireSpinLockWithTimeout * - OSReleaseSpinLock * - OSCancelThread */ void OSTestThreadCancel() { internal::lockScheduler(); internal::testThreadCancelNoLock(); internal::unlockScheduler(); } /** * Wake up all threads in queue. * * Clears the thread queue. */ void OSWakeupThread(virt_ptr queue) { internal::lockScheduler(); internal::wakeupThreadNoLock(queue); internal::rescheduleAllCoreNoLock(); internal::unlockScheduler(); } /** * Yield execution to waiting threads with same priority. * * This will never switch to a thread with a lower priority than the current * thread. */ void OSYieldThread() { internal::lockScheduler(); internal::checkRunningThreadNoLock(true); internal::unlockScheduler(); } namespace internal { /** * Set a user stack pointer for the current thread. */ void setUserStackPointer(virt_ptr stack) { auto thread = OSGetCurrentThread(); if (stack >= thread->stackEnd && stack < thread->stackStart) { // Cannot modify stack to within current stack frame. return; } auto current = OSGetStackPointer(); if (current < thread->stackEnd || current >= thread->stackStart) { // If current stack is outside stack frame, then we must already have // a user stack pointer, and we shouldn't overwrite it. return; } thread->userStackPointer = stack; OSTestThreadCancel(); thread->cancelState |= OSThreadCancelState::DisabledByUserStackPointer; } /** * Remove the user stack pointer for the current thread. */ void removeUserStackPointer(virt_ptr stack) { auto thread = OSGetCurrentThread(); if (stack < thread->stackEnd || stack >= thread->stackStart) { // If restore stack pointer is outside stack frame, then it is not // really restoring the original stack. return; } thread->cancelState &= ~OSThreadCancelState::DisabledByUserStackPointer; thread->userStackPointer = nullptr; OSTestThreadCancel(); } /** * Set the current thread to run only on the current core. * * \return * Returns the old thread affinity, to be restored with unpinThreadAffinity. */ uint32_t pinThreadAffinity() { auto core = OSGetCoreId(); auto thread = OSGetCurrentThread(); internal::lockScheduler(); auto oldAffinity = thread->attr & OSThreadAttributes::AffinityAny; thread->attr &= ~OSThreadAttributes::AffinityAny; thread->attr |= 1 << core; internal::unlockScheduler(); return oldAffinity; } /** * Restores the thread affinity. */ void unpinThreadAffinity(uint32_t affinity) { auto thread = OSGetCurrentThread(); internal::lockScheduler(); thread->attr &= ~OSThreadAttributes::AffinityAny; thread->attr |= affinity; internal::unlockScheduler(); } void exitThreadNoLock(int32_t value) { auto thread = OSGetCurrentThread(); decaf_check(thread->state == OSThreadState::Running); decaf_check(internal::isThreadActiveNoLock(thread)); // Clear the context associated with this thread if (thread->attr & OSThreadAttributes::Detached) { internal::markThreadInactiveNoLock(thread); thread->state = OSThreadState::None; // TODO: thread->id = 0x8000; if (thread->deallocator) { queueThreadDeallocation(thread); } } else { thread->exitValue = value; thread->state = OSThreadState::Moribund; } internal::disableScheduler(); internal::unlockAllMutexNoLock(thread); internal::unlockAllFastMutexNoLock(thread); internal::wakeupThreadNoLock(virt_addrof(thread->joinQueue)); internal::wakeupThreadWaitForSuspensionNoLock(virt_addrof(thread->suspendQueue), -1); internal::rescheduleAllCoreNoLock(); internal::enableScheduler(); cafe::kernel::exitThreadNoLock(); internal::rescheduleSelfNoLock(); // We do not need to unlockScheduler as OSExitThread never returns. decaf_abort("Exited thread was rescheduled..."); } void queueThreadDeallocation(virt_ptr thread) { auto &perCoreData = sThreadData->perCoreData[cpu::this_core::id()]; ThreadQueue::insert(virt_addrof(perCoreData.deallocationQueue), thread); wakeupThreadNoLock(virt_addrof(perCoreData.deallocationThreadQueue)); } static uint32_t deallocatorThreadEntry(uint32_t coreId, virt_ptr) { auto &perCoreData = sThreadData->perCoreData[cpu::this_core::id()]; auto waitQueue = virt_addrof(perCoreData.deallocationThreadQueue); auto queue = virt_addrof(perCoreData.deallocationQueue); auto oldInterrupts = OSDisableInterrupts(); while (true) { auto thread = ThreadQueue::popFront(queue); if (!thread) { lockScheduler(); sleepThreadNoLock(waitQueue); rescheduleSelfNoLock(); unlockScheduler(); continue; } if (thread->deallocator) { OSRestoreInterrupts(oldInterrupts); cafe::invoke(cpu::this_core::state(), thread->deallocator, thread, virt_cast(thread->stackEnd)); oldInterrupts = OSDisableInterrupts(); } } OSRestoreInterrupts(oldInterrupts); } static void initialiseDeallocatorThread() { auto coreId = OSGetCoreId(); auto &perCoreData = sThreadData->perCoreData[coreId]; OSInitThreadQueue(virt_addrof(perCoreData.deallocationThreadQueue)); OSInitThreadQueue(virt_addrof(perCoreData.deallocationQueue)); auto thread = virt_addrof(perCoreData.deallocatorThread); auto stack = virt_addrof(perCoreData.deallocatorThreadStack); auto stackSize = perCoreData.deallocatorThreadStack.size(); perCoreData.deallocatorThreadName = fmt::format("{{SYS Thread Terminator Core {}}}", coreId); coreinit__OSCreateThreadType(thread, sDeallocatorThreadEntryPoint, coreId, nullptr, virt_cast(stack + stackSize), stackSize, 1, static_cast(1 << coreId), OSThreadType::AppIo); OSSetThreadName(thread, virt_addrof(perCoreData.deallocatorThreadName)); OSResumeThread(thread); } static uint32_t defaultThreadEntry(uint32_t coreId, virt_ptr /* unused */) { auto thread = OSGetDefaultThread(coreId); lockScheduler(); setCoreRunningThread(coreId, thread); OSSetCurrentContext(virt_addrof(thread->context)); OSSetCurrentFPUContext(0); unlockScheduler(); initialiseIci(); initialiseExceptionHandlers(); initialiseAlarmThread(); initialiseLockedCache(coreId); IPCDriverInit(); IPCDriverOpen(); internal::COSWarn(COSReportModule::Unknown1, fmt::format(" Core {} Complete, MSR 0x{:08X}, Default Thread {}", coreId, 0, thread)); OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous), sThreadData->defaultThreadInitRendezvousWaitMask); initialiseDeallocatorThread(); OSExitThread(0); } static void initialiseThreadForCore(uint32_t coreId) { auto currentCoreId = OSGetCoreId(); auto thread = virt_addrof(sThreadData->perCoreData[coreId].defaultThread); sThreadData->perCoreData[coreId].defaultThreadName = fmt::format("Default Thread {}", coreId); thread->name = virt_addrof(sThreadData->perCoreData[coreId].defaultThreadName); thread->tag = OSThread::Tag; thread->exitValue = -1; thread->type = OSThreadType::App; thread->attr = OSThreadAttributes::Detached; thread->state = OSThreadState::Running; thread->priority = 80; thread->basePriority = 80; thread->id = sThreadId++; OSInitThreadQueueEx(virt_addrof(thread->joinQueue), thread); OSInitThreadQueueEx(virt_addrof(thread->suspendQueue), thread); thread->mutexQueue.parent = thread; thread->stackStart = virt_cast(internal::getDefaultThreadStackBase(coreId)); thread->stackEnd = virt_cast(internal::getDefaultThreadStackEnd(coreId)); if (currentCoreId == coreId) { // Save and restore our host context because OSInitContext nulls it auto hostContext = thread->context.hostContext; OSInitContext(virt_addrof(thread->context), virt_addr { 0 }, virt_cast(thread->stackStart)); thread->context.hostContext = hostContext; } else { OSInitContext(virt_addrof(thread->context), virt_func_cast(sDefaultThreadEntryPoint), virt_cast(thread->stackStart)); } thread->context.pir = coreId; thread->context.starttime = OSGetSystemTime(); thread->context.gqr[2] = 0x40004u; thread->context.gqr[3] = 0x50005u; thread->context.gqr[4] = 0x60006u; thread->context.gqr[5] = 0x70007u; if (coreId == 0) { thread->attr |= OSThreadAttributes::AffinityCPU0; thread->context.attr |= OSThreadAttributes::AffinityCPU0; } else if (coreId == 1) { thread->attr |= OSThreadAttributes::AffinityCPU1; thread->context.attr |= OSThreadAttributes::AffinityCPU1; } else if (coreId == 2) { thread->attr |= OSThreadAttributes::AffinityCPU2; thread->context.attr |= OSThreadAttributes::AffinityCPU2; } clearThreadStackWithValue(thread, 0); *thread->stackEnd = 0xDEADBABE; if (currentCoreId == coreId) { setCoreRunningThread(coreId, thread); } markThreadActiveNoLock(thread); } void initialiseThreads() { auto mainCoreId = OSGetMainCoreId(); auto mainThread = OSGetDefaultThread(mainCoreId); internal::lockScheduler(); for (auto i = 0u; i < sThreadData->perCoreData.size(); ++i) { auto &perCoreData = sThreadData->perCoreData[i]; OSCreateAlarm(virt_addrof(perCoreData.timeSliceAlarm)); initialiseThreadForCore(i); } // Hijack the kernel context fiber into our own. kernel::hijackCurrentHostContext(virt_addrof(mainThread->context)); OSSetCurrentContext(virt_addrof(mainThread->context)); OSSetCurrentFPUContext(0); internal::unlockScheduler(); internal::COSWarn(COSReportModule::Unknown1, fmt::format("UserMode Core & Thread Initialization ({} Cores)", OSGetCoreCount())); internal::COSWarn(COSReportModule::Unknown1, fmt::format(" Core {} Complete, Default Thread {}", mainCoreId, mainThread)); sThreadData->defaultThreadInitRendezvousWaitMask = 1u << 1; // Run default thread initilisation on Core 0 if (auto thread = virt_addrof(sThreadData->perCoreData[0].defaultThread)) { OSInitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous)); thread->context.gpr[3] = 0u; kernel::setSubCoreEntryContext(0, virt_addrof(thread->context)); OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous), 1 << 0); } // Run default thread initilisation on Core 2 if (auto thread = virt_addrof(sThreadData->perCoreData[2].defaultThread)) { OSInitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous)); thread->context.gpr[3] = 2u; kernel::setSubCoreEntryContext(2, virt_addrof(thread->context)); OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous), 1 << 2); } OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous), 1 << 2); initialiseDeallocatorThread(); } } // namespace internal void Library::registerThreadSymbols() { RegisterFunctionExport(OSCancelThread); RegisterFunctionExport(OSCheckActiveThreads); RegisterFunctionExport(OSCheckThreadStackUsage); RegisterFunctionExport(OSClearStack); RegisterFunctionExport(OSClearThreadStackUsage); RegisterFunctionExport(OSContinueThread); RegisterFunctionExport(OSCreateThread); RegisterFunctionExport(OSCreateThreadType); RegisterFunctionExportName("__OSCreateThreadType", coreinit__OSCreateThreadType); RegisterFunctionExport(OSDetachThread); RegisterFunctionExport(OSExitThread); RegisterFunctionExport(OSGetActiveThreadLink); RegisterFunctionExport(OSGetCurrentThread); RegisterFunctionExport(OSGetDefaultThread); RegisterFunctionExport(OSGetStackPointer); RegisterFunctionExport(OSGetUserStackPointer); RegisterFunctionExport(OSGetThreadAffinity); RegisterFunctionExport(OSGetThreadName); RegisterFunctionExport(OSGetThreadPriority); RegisterFunctionExport(OSGetThreadSpecific); RegisterFunctionExport(OSInitThreadQueue); RegisterFunctionExport(OSInitThreadQueueEx); RegisterFunctionExport(OSIsThreadSuspended); RegisterFunctionExport(OSIsThreadTerminated); RegisterFunctionExport(OSJoinThread); RegisterFunctionExport(OSPrintCurrentThreadState); RegisterFunctionExport(OSResumeThread); RegisterFunctionExport(OSRunThread); RegisterFunctionExport(OSSetThreadAffinity); RegisterFunctionExport(OSSetThreadCancelState); RegisterFunctionExport(OSSetThreadCleanupCallback); RegisterFunctionExport(OSSetThreadDeallocator); RegisterFunctionExport(OSSetThreadName); RegisterFunctionExport(OSSetThreadPriority); RegisterFunctionExport(OSSetThreadRunQuantum); RegisterFunctionExport(OSSetThreadSpecific); RegisterFunctionExport(OSSetThreadStackUsage); RegisterFunctionExport(OSSleepThread); RegisterFunctionExport(OSSleepTicks); RegisterFunctionExport(OSSuspendThread); RegisterFunctionExport(OSTestThreadCancel); RegisterFunctionExport(OSWakeupThread); RegisterFunctionExport(OSYieldThread); RegisterDataInternal(sThreadData); RegisterFunctionInternal(threadEntry, sThreadEntryPoint); RegisterFunctionInternal(sleepAlarmHandler, sSleepAlarmHandler); RegisterFunctionInternal(internal::deallocatorThreadEntry, sDeallocatorThreadEntryPoint); RegisterFunctionInternal(internal::defaultThreadEntry, sDefaultThreadEntryPoint); } } // namespace Internalcafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_thread.h ================================================ #pragma once #include "coreinit_context.h" #include "coreinit_core.h" #include "coreinit_enum.h" #include "coreinit_exception.h" #include "coreinit_internal_queue.h" #include "coreinit_time.h" #include #include namespace cafe::coreinit { /** * \defgroup coreinit_thread Thread * \ingroup coreinit * * The thread scheduler in the Wii U uses co-operative scheduling, this is different * to the usual pre-emptive scheduling that most operating systems use (such as * Windows, Linux, etc). In co-operative scheduling threads must voluntarily yield * execution to other threads. In pre-emptive threads are switched by the operating * system after an amount of time. * * With the Wii U's scheduling model the thread with the highest priority which * is in a non-waiting state will always be running (where 0 is the highest * priority and 31 is the lowest). Execution will only switch to other threads * once this thread has been forced to wait, such as when waiting to acquire a * mutex, or when the thread voluntarily yields execution to other threads which * have the same priority using OSYieldThread. OSYieldThread will never yield to * a thread with lower priority than the current thread. * @{ */ #pragma pack(push, 1) struct OSAlarm; struct OSThread; using OSThreadEntryPointFn = virt_func_ptr)>; using OSThreadCleanupCallbackFn = virt_func_ptr, virt_ptr)>; using OSThreadDeallocatorFn = virt_func_ptr, virt_ptr)>; using OSContext = cafe::kernel::Context; CHECK_SIZE(OSContext, 0x320); struct OSMutex; struct OSMutexQueue { be2_virt_ptr head; be2_virt_ptr tail; be2_virt_ptr parent; UNKNOWN(4); }; CHECK_OFFSET(OSMutexQueue, 0x0, head); CHECK_OFFSET(OSMutexQueue, 0x4, tail); CHECK_OFFSET(OSMutexQueue, 0x8, parent); CHECK_SIZE(OSMutexQueue, 0x10); struct OSFastMutex; struct OSFastMutexQueue { be2_virt_ptr head; be2_virt_ptr tail; }; CHECK_OFFSET(OSFastMutexQueue, 0x00, head); CHECK_OFFSET(OSFastMutexQueue, 0x04, tail); CHECK_SIZE(OSFastMutexQueue, 0x08); struct OSThreadLink { be2_virt_ptr next; be2_virt_ptr prev; }; CHECK_OFFSET(OSThreadLink, 0x00, next); CHECK_OFFSET(OSThreadLink, 0x04, prev); CHECK_SIZE(OSThreadLink, 0x8); struct OSThreadQueue { be2_virt_ptr head; be2_virt_ptr tail; be2_virt_ptr parent; UNKNOWN(4); }; CHECK_OFFSET(OSThreadQueue, 0x00, head); CHECK_OFFSET(OSThreadQueue, 0x04, tail); CHECK_OFFSET(OSThreadQueue, 0x08, parent); CHECK_SIZE(OSThreadQueue, 0x10); struct OSThreadSimpleQueue { be2_virt_ptr head; be2_virt_ptr tail; }; CHECK_OFFSET(OSThreadSimpleQueue, 0x00, head); CHECK_OFFSET(OSThreadSimpleQueue, 0x04, tail); CHECK_SIZE(OSThreadSimpleQueue, 0x08); struct OSTLSSection { be2_virt_ptr data; UNKNOWN(4); }; CHECK_OFFSET(OSTLSSection, 0x00, data); CHECK_SIZE(OSTLSSection, 0x08); struct OSThread { static constexpr uint32_t Tag = 0x74487244; //! Kernel thread context be2_struct context; //! Should always be set to the value OSThread::Tag. be2_val tag; //! Bitfield of OScpu::Core be2_val state; //! Bitfield of OSThreadAttributes be2_val attr; //! Unique thread ID be2_val id; //! Suspend count (increased by OSSuspendThread). be2_val suspendCounter; //! Actual priority of thread. be2_val priority; //! Base priority of thread, 0 is highest priority, 31 is lowest priority. be2_val basePriority; //! Exit value of the thread be2_val exitValue; //! Core run queue stuff be2_virt_ptr coreRunQueue0; be2_virt_ptr coreRunQueue1; be2_virt_ptr coreRunQueue2; be2_struct coreRunQueueLink0; be2_struct coreRunQueueLink1; be2_struct coreRunQueueLink2; //! Queue the thread is currently waiting on be2_virt_ptr queue; //! Link used for thread queue be2_struct link; //! Queue of threads waiting to join this thread be2_struct joinQueue; //! Mutex this thread is waiting to lock be2_virt_ptr mutex; //! Queue of mutexes this thread owns be2_struct mutexQueue; //! Link for global active thread queue be2_struct activeLink; //! Stack start (top, highest address) be2_virt_ptr stackStart; //! Stack end (bottom, lowest address) be2_virt_ptr stackEnd; //! Thread entry point set in OSCreateThread be2_val entryPoint; UNKNOWN(0x408 - 0x3a0); //! GHS Exception handling thread-specifics be2_virt_ptr eh_globals; be2_array, 9> eh_mem_manage; be2_array, 6> eh_store_globals; be2_array, 76> eh_store_globals_tdeh; be2_val alarmCancelled; //! Thread specific values, accessed with OSSetThreadSpecific and OSGetThreadSpecific. be2_array specific; //! Thread type be2_val type; //! Thread name, accessed with OSSetThreadName and OSGetThreadName. be2_virt_ptr name; //! Alarm the thread is waiting on in OSWaitEventWithTimeout be2_virt_ptr waitEventTimeoutAlarm; //! The stack pointer passed in OSCreateThread. be2_virt_ptr userStackPointer; //! Called just before thread is terminated, set with OSSetThreadCleanupCallback be2_val cleanupCallback; //! Called just after a thread is terminated, set with OSSetThreadDeallocator be2_val deallocator; //! Current thread cancel state, controls whether the thread is allowed to cancel or not be2_val cancelState; //! Current thread request, used for cancelleing and suspending the thread. be2_val requestFlag; //! Pending suspend request count be2_val needSuspend; //! Result of thread suspend be2_val suspendResult; //! Queue of threads waiting for a thread to be suspended. be2_struct suspendQueue; UNKNOWN(0x4); //! How many ticks the thread should run for before suspension. be2_val runQuantumTicks; //! The total amount of core time consumed by this thread (Does not include time while Running) be2_val coreTimeConsumedNs; //! The number of times this thread has been awoken. be2_val wakeCount; be2_val unk0x610; be2_val unk0x618; be2_val unk0x620; be2_val unk0x628; //! Callback for DSI exception be2_array dsiCallback; //! Callback for ISI exception be2_array isiCallback; //! Callback for Program exception be2_array programCallback; //! Callback for PerfMon exception be2_array perfMonCallback; UNKNOWN(0x4); //! Number of TLS sections be2_val tlsSectionCount; UNKNOWN(0x2); //! TLS Sections be2_virt_ptr tlsSections; //! The fast mutex we are currently waiting for be2_virt_ptr fastMutex; //! The fast mutexes we are currently contended on be2_struct contendedFastMutexes; //! The fast mutexes we currently own locks on be2_struct fastMutexQueue; //! Callback for Alignment exception be2_array alignCallback; UNKNOWN(0x6A0 - 0x68C); }; CHECK_OFFSET(OSThread, 0x320, tag); CHECK_OFFSET(OSThread, 0x324, state); CHECK_OFFSET(OSThread, 0x325, attr); CHECK_OFFSET(OSThread, 0x326, id); CHECK_OFFSET(OSThread, 0x328, suspendCounter); CHECK_OFFSET(OSThread, 0x32c, priority); CHECK_OFFSET(OSThread, 0x330, basePriority); CHECK_OFFSET(OSThread, 0x334, exitValue); CHECK_OFFSET(OSThread, 0x338, coreRunQueue0); CHECK_OFFSET(OSThread, 0x33C, coreRunQueue1); CHECK_OFFSET(OSThread, 0x340, coreRunQueue2); CHECK_OFFSET(OSThread, 0x344, coreRunQueueLink0); CHECK_OFFSET(OSThread, 0x34C, coreRunQueueLink1); CHECK_OFFSET(OSThread, 0x354, coreRunQueueLink2); CHECK_OFFSET(OSThread, 0x35C, queue); CHECK_OFFSET(OSThread, 0x360, link); CHECK_OFFSET(OSThread, 0x368, joinQueue); CHECK_OFFSET(OSThread, 0x378, mutex); CHECK_OFFSET(OSThread, 0x37C, mutexQueue); CHECK_OFFSET(OSThread, 0x38C, activeLink); CHECK_OFFSET(OSThread, 0x394, stackStart); CHECK_OFFSET(OSThread, 0x398, stackEnd); CHECK_OFFSET(OSThread, 0x39C, entryPoint); CHECK_OFFSET(OSThread, 0x408, eh_globals); CHECK_OFFSET(OSThread, 0x40C, eh_mem_manage); CHECK_OFFSET(OSThread, 0x430, eh_store_globals); CHECK_OFFSET(OSThread, 0x448, eh_store_globals_tdeh); CHECK_OFFSET(OSThread, 0x578, alarmCancelled); CHECK_OFFSET(OSThread, 0x57C, specific); CHECK_OFFSET(OSThread, 0x5BC, type); CHECK_OFFSET(OSThread, 0x5C0, name); CHECK_OFFSET(OSThread, 0x5C4, waitEventTimeoutAlarm); CHECK_OFFSET(OSThread, 0x5C8, userStackPointer); CHECK_OFFSET(OSThread, 0x5CC, cleanupCallback); CHECK_OFFSET(OSThread, 0x5D0, deallocator); CHECK_OFFSET(OSThread, 0x5D4, cancelState); CHECK_OFFSET(OSThread, 0x5D8, requestFlag); CHECK_OFFSET(OSThread, 0x5DC, needSuspend); CHECK_OFFSET(OSThread, 0x5E0, suspendResult); CHECK_OFFSET(OSThread, 0x5E4, suspendQueue); CHECK_OFFSET(OSThread, 0x5F8, runQuantumTicks); CHECK_OFFSET(OSThread, 0x600, coreTimeConsumedNs); CHECK_OFFSET(OSThread, 0x608, wakeCount); CHECK_OFFSET(OSThread, 0x610, unk0x610); CHECK_OFFSET(OSThread, 0x618, unk0x618); CHECK_OFFSET(OSThread, 0x620, unk0x620); CHECK_OFFSET(OSThread, 0x628, unk0x628); CHECK_OFFSET(OSThread, 0x630, dsiCallback); CHECK_OFFSET(OSThread, 0x63C, isiCallback); CHECK_OFFSET(OSThread, 0x648, programCallback); CHECK_OFFSET(OSThread, 0x654, perfMonCallback); CHECK_OFFSET(OSThread, 0x664, tlsSectionCount); CHECK_OFFSET(OSThread, 0x668, tlsSections); CHECK_OFFSET(OSThread, 0x66C, fastMutex); CHECK_OFFSET(OSThread, 0x670, contendedFastMutexes); CHECK_OFFSET(OSThread, 0x678, fastMutexQueue); CHECK_OFFSET(OSThread, 0x680, alignCallback); CHECK_SIZE(OSThread, 0x6A0); #pragma pack(pop) void OSCancelThread(virt_ptr thread); int32_t OSCheckActiveThreads(); int32_t OSCheckThreadStackUsage(virt_ptr thread); void OSClearStack(uint32_t value); void OSClearThreadStackUsage(virt_ptr thread); void OSContinueThread(virt_ptr thread); BOOL OSCreateThread(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stackTop, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes); BOOL OSCreateThreadType(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stackTop, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes, OSThreadType type); BOOL coreinit__OSCreateThreadType(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv, virt_ptr stackTop, uint32_t stackSize, int32_t priority, OSThreadAttributes attributes, OSThreadType type); void OSDetachThread(virt_ptr thread); [[noreturn]] void OSExitThread(int value); void OSGetActiveThreadLink(virt_ptr thread, virt_ptr link); virt_ptr OSGetCurrentThread(); virt_ptr OSGetDefaultThread(uint32_t coreID); virt_ptr OSGetStackPointer(); virt_ptr OSGetUserStackPointer(virt_ptr thread); uint32_t OSGetThreadAffinity(virt_ptr thread); virt_ptr OSGetThreadName(virt_ptr thread); int32_t OSGetThreadPriority(virt_ptr thread); uint32_t OSGetThreadSpecific(uint32_t id); void OSInitThreadQueue(virt_ptr queue); void OSInitThreadQueueEx(virt_ptr queue, virt_ptr parent); BOOL OSIsThreadSuspended(virt_ptr thread); BOOL OSIsThreadTerminated(virt_ptr thread); BOOL OSJoinThread(virt_ptr thread, virt_ptr exitValue); void OSPrintCurrentThreadState(); int32_t OSResumeThread(virt_ptr thread); BOOL OSRunThread(virt_ptr thread, OSThreadEntryPointFn entry, uint32_t argc, virt_ptr argv); BOOL OSSetThreadAffinity(virt_ptr thread, uint32_t affinity); BOOL OSSetThreadCancelState(BOOL state); OSThreadCleanupCallbackFn OSSetThreadCleanupCallback(virt_ptr thread, OSThreadCleanupCallbackFn callback); OSThreadDeallocatorFn OSSetThreadDeallocator(virt_ptr thread, OSThreadDeallocatorFn deallocator); void OSSetThreadName(virt_ptr thread, virt_ptr name); BOOL OSSetThreadPriority(virt_ptr thread, int32_t priority); BOOL OSSetThreadRunQuantum(virt_ptr thread, uint32_t quantumUS); void OSSetThreadSpecific(uint32_t id, uint32_t value); BOOL OSSetThreadStackUsage(virt_ptr thread); void OSSleepThread(virt_ptr queue); void OSSleepTicks(OSTime ticks); int32_t OSSuspendThread(virt_ptr thread); void OSTestThreadCancel(); void OSWakeupThread(virt_ptr queue); void OSYieldThread(); /** @} */ namespace internal { void initialiseThreads(); void setUserStackPointer(virt_ptr stack); void removeUserStackPointer(virt_ptr stack); uint32_t pinThreadAffinity(); void unpinThreadAffinity(uint32_t affinity); void queueThreadDeallocation(virt_ptr thread); void exitThreadNoLock(int32_t value); struct ThreadIsLess { bool operator()(virt_ptr lhs, virt_ptr rhs) const { return lhs->priority <= rhs->priority; } }; using ThreadQueue = SortedQueue; } // namespace internal } // namespace coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_time.cpp ================================================ #include "coreinit.h" #include "coreinit_time.h" #include "coreinit_systeminfo.h" #include "decaf_config.h" #include "decaf_configstorage.h" #include #include #include namespace cafe::coreinit { struct TimeData { std::chrono::time_point epochTime; std::chrono::time_point baseClock; cpu::TimerDuration baseTicks; }; static virt_ptr sTimeData = nullptr; static std::atomic sTimeScaleEnabled = false; static std::atomic sTimeScale = 1.0; static uint64_t scaledTimebase() { auto timeBase = cpu::this_core::state()->tb(); if (!sTimeScaleEnabled.load(std::memory_order_relaxed)) { return timeBase; } auto timeScale = sTimeScale.load(std::memory_order_relaxed); return static_cast(static_cast(timeBase) * timeScale); } /** * Time since epoch */ OSTime OSGetTime() { return OSGetSystemTime() + sTimeData->baseTicks.count(); } /** * Time since system start up */ OSTime OSGetSystemTime() { return scaledTimebase(); } /** * Ticks since epoch */ OSTick OSGetTick() { return OSGetSystemTick() + static_cast(sTimeData->baseTicks.count()); } /** * Ticks since system start up */ OSTick OSGetSystemTick() { return static_cast(scaledTimebase()); } /** * Convert OSTime to OSCalendarTime */ void OSTicksToCalendarTime(OSTime time, virt_ptr calendarTime) { auto chrono = coreinit::internal::toTimepoint(time); std::time_t system_time_t = std::chrono::system_clock::to_time_t(chrono); std::tm tm = platform::localtime(system_time_t); calendarTime->tm_sec = tm.tm_sec; calendarTime->tm_min = tm.tm_min; calendarTime->tm_hour = tm.tm_hour; calendarTime->tm_mday = tm.tm_mday; calendarTime->tm_mon = tm.tm_mon; calendarTime->tm_year = tm.tm_year + 1900; // posix tm_year is year - 1900 calendarTime->tm_wday = tm.tm_wday; calendarTime->tm_yday = tm.tm_yday; auto timeOffset = std::chrono::duration_cast(chrono.time_since_epoch()) - std::chrono::duration_cast(chrono.time_since_epoch()); auto msOffset = std::chrono::duration_cast(timeOffset); auto uOffset = std::chrono::duration_cast(timeOffset - msOffset); calendarTime->tm_msec = static_cast(msOffset.count()); calendarTime->tm_usec = static_cast(uOffset.count()); } /** * Convert OSCalendarTime to OSTime */ OSTime OSCalendarTimeToTicks(virt_ptr calendarTime) { std::tm tm = { 0 }; tm.tm_sec = calendarTime->tm_sec; tm.tm_min = calendarTime->tm_min; tm.tm_hour = calendarTime->tm_hour; tm.tm_mday = calendarTime->tm_mday; tm.tm_mon = calendarTime->tm_mon; tm.tm_year = calendarTime->tm_year - 1900; tm.tm_wday = calendarTime->tm_wday; tm.tm_yday = calendarTime->tm_yday; tm.tm_isdst = -1; auto system_time = platform::make_gm_time(tm); auto chrono = std::chrono::system_clock::from_time_t(system_time); // Add on tm_usec, tm_msec which is missing from std::tm chrono += std::chrono::microseconds { calendarTime->tm_usec }; chrono += std::chrono::milliseconds { calendarTime->tm_msec }; return coreinit::internal::toOSTime(chrono); } namespace internal { OSTime msToTicks(OSTimeMilliseconds milliseconds) { auto timerSpeed = static_cast(OSGetSystemInfo()->busSpeed / 4); return static_cast(milliseconds) * (timerSpeed / 1000); } OSTime usToTicks(OSTimeMicroseconds microseconds) { auto timerSpeed = static_cast(OSGetSystemInfo()->busSpeed / 4); return static_cast(microseconds) * (timerSpeed / 1000000); } OSTime nsToTicks(OSTimeNanoseconds nanoseconds) { // Division is done in two parts to try to maintain accuracy, 31250 * 32000 = 1*10^9 auto timerSpeed = static_cast(OSGetSystemInfo()->busSpeed / 4); return (static_cast(nanoseconds) * (timerSpeed / 31250)) / 32000; } OSTimeMilliseconds ticksToMs(OSTick ticks) { auto timerSpeed = static_cast(OSGetSystemInfo()->busSpeed / 4); return (static_cast(ticks) * 1000) / timerSpeed; } OSTime getBaseTime() { return sTimeData->baseTicks.count(); } std::chrono::time_point toTimepoint(OSTime time) { auto ticksSinceBaseTime = cpu::TimerDuration { time } - sTimeData->baseTicks; auto clocksSinceBaseTime = std::chrono::duration_cast(ticksSinceBaseTime); return sTimeData->baseClock + clocksSinceBaseTime; } OSTime toOSTime(std::chrono::time_point chrono) { auto clocksSinceBaseTime = chrono - sTimeData->baseClock; auto ticksSinceBaseTime = std::chrono::duration_cast(clocksSinceBaseTime); return (sTimeData->baseTicks + ticksSinceBaseTime).count(); } void initialiseTime() { static std::once_flag sRegisteredConfigChangeListener; std::call_once(sRegisteredConfigChangeListener, []() { decaf::registerConfigChangeListener( [](const decaf::Settings &settings) { sTimeScale = settings.system.time_scale; sTimeScaleEnabled = settings.system.time_scale_enabled; }); }); sTimeScale = decaf::config()->system.time_scale; sTimeScaleEnabled = decaf::config()->system.time_scale_enabled; // Calculate the Wii U epoch (01/01/2000) std::tm tm = { 0 }; tm.tm_sec = 0; tm.tm_min = 0; tm.tm_hour = 0; tm.tm_mday = 1; tm.tm_mon = 1; tm.tm_year = 2000 - 1900; tm.tm_isdst = -1; sTimeData->epochTime = std::chrono::system_clock::from_time_t(platform::make_gm_time(tm)); sTimeData->baseClock = std::chrono::system_clock::now(); auto ticksSinceEpoch = std::chrono::duration_cast(sTimeData->baseClock - sTimeData->epochTime); auto ticksSinceStart = cpu::TimerDuration { cpu::this_core::state()->tb() }; sTimeData->baseTicks = ticksSinceEpoch - ticksSinceStart; } } // namespace internal void Library::registerTimeSymbols() { RegisterFunctionExport(OSGetTime); RegisterFunctionExport(OSGetTick); RegisterFunctionExport(OSGetSystemTime); RegisterFunctionExport(OSGetSystemTick); RegisterFunctionExport(OSTicksToCalendarTime); RegisterFunctionExport(OSCalendarTimeToTicks); RegisterDataInternal(sTimeData); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_time.h ================================================ #pragma once #include #include namespace cafe::coreinit { /** * \defgroup coreinit_time Time * \ingroup coreinit * @{ */ #pragma pack(push, 1) struct OSCalendarTime { // These are all from the POSIX tm struct, we are missing tm_isdst though. be2_val tm_sec; be2_val tm_min; be2_val tm_hour; be2_val tm_mday; be2_val tm_mon; be2_val tm_year; be2_val tm_wday; be2_val tm_yday; // Also Wii U has some extra fields not found in posix tm! be2_val tm_msec; be2_val tm_usec; }; CHECK_OFFSET(OSCalendarTime, 0x00, tm_sec); CHECK_OFFSET(OSCalendarTime, 0x04, tm_min); CHECK_OFFSET(OSCalendarTime, 0x08, tm_hour); CHECK_OFFSET(OSCalendarTime, 0x0C, tm_mday); CHECK_OFFSET(OSCalendarTime, 0x10, tm_mon); CHECK_OFFSET(OSCalendarTime, 0x14, tm_year); CHECK_OFFSET(OSCalendarTime, 0x18, tm_wday); CHECK_OFFSET(OSCalendarTime, 0x1C, tm_yday); CHECK_OFFSET(OSCalendarTime, 0x20, tm_msec); CHECK_OFFSET(OSCalendarTime, 0x24, tm_usec); CHECK_SIZE(OSCalendarTime, 0x28); #pragma pack(pop) using OSTick = int32_t; //! OSTime is ticks since epoch using OSTime = int64_t; using OSTimeSeconds = int64_t; using OSTimeMilliseconds = int64_t; using OSTimeMicroseconds = int64_t; using OSTimeNanoseconds = int64_t; OSTime OSGetTime(); OSTime OSGetSystemTime(); OSTick OSGetTick(); OSTick OSGetSystemTick(); void OSTicksToCalendarTime(OSTime time, virt_ptr calendarTime); OSTime OSCalendarTimeToTicks(virt_ptr calendarTime); /** @} */ namespace internal { OSTime msToTicks(OSTimeMilliseconds milliseconds); OSTime usToTicks(OSTimeMicroseconds microseconds); OSTime nsToTicks(OSTimeNanoseconds nanoseconds); OSTimeMilliseconds ticksToMs(OSTick ticks); OSTime getBaseTime(); std::chrono::time_point toTimepoint(OSTime time); OSTime toOSTime(std::chrono::time_point chrono); void initialiseTime(); } // namespace internal } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_userconfig.cpp ================================================ #include "coreinit.h" #include "coreinit_ios.h" #include "coreinit_ipcbufpool.h" #include "coreinit_mutex.h" #include "coreinit_userconfig.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include #include namespace cafe::coreinit { using ios::auxil::UCDeleteSysConfigRequest; using ios::auxil::UCReadSysConfigRequest; using ios::auxil::UCWriteSysConfigRequest; static constexpr uint32_t SmallMessageCount = 0x100; static constexpr uint32_t SmallMessageSize = 0x80; static constexpr uint32_t LargeMessageCount = 0x40; static constexpr uint32_t LargeMessageSize = 0x1000; struct StaticUserConfigData { be2_struct lock; be2_val initialised; be2_virt_ptr smallMessagePool; be2_virt_ptr largeMessagePool; be2_array smallMessageBuffer; be2_array largeMessageBuffer; be2_val smallMessageCount; be2_val largeMessageCount; }; static virt_ptr sUserConfigData = nullptr; static IOSAsyncCallbackFn sUcIosAsyncCallback = nullptr; namespace internal { static virt_ptr ucAllocateMessage(uint32_t size) { auto message = virt_ptr { nullptr }; if (size == 0) { return nullptr; } else if (size <= SmallMessageSize) { message = IPCBufPoolAllocate(sUserConfigData->smallMessagePool, size); } else { message = IPCBufPoolAllocate(sUserConfigData->largeMessagePool, size); } std::memset(message.get(), 0, size); return message; } static void ucFreeMessage(virt_ptr message) { IPCBufPoolFree(sUserConfigData->smallMessagePool, message); IPCBufPoolFree(sUserConfigData->largeMessagePool, message); } static UCError ucSetupAsyncParams(UCCommand command, uint32_t unk_r4, uint32_t count, virt_ptr settings, virt_ptr vecs, virt_ptr asyncParams) { if (!settings || !vecs || !asyncParams) { return UCError::InvalidParam; } asyncParams->command = command; asyncParams->unk0x0C = unk_r4; asyncParams->count = count; asyncParams->settings = settings; asyncParams->vecs = vecs; return UCError::OK; } static UCError ucHandleIosResult(UCError result, UCCommand command, uint32_t unk_r5, uint32_t count, virt_ptr settings, virt_ptr vecs, virt_ptr asyncParams, UCAsyncCallbackFn callback, virt_ptr callbackContext) { if (!settings && !vecs) { if (result == UCError::OK) { return UCError::InvalidParam; } else { return static_cast(result); } } if (result != UCError::NoIPCBuffers) { if (settings && vecs) { if (result == UCError::OK && asyncParams) { // Return as we have a pending async result...! return UCError::OK; } if (command == UCCommand::ReadSysConfig) { auto request = virt_cast(vecs[0].vaddr); for (auto i = 0u; i < count; ++i) { settings[i].error = request->settings[i].error; if (settings[i].error) { result = settings[i].error; continue; } if (settings[i].dataSize) { if (!settings[i].data) { result = UCError::InvalidParam; continue; } auto src = virt_cast(vecs[i + 1].vaddr); switch (settings[i].dataSize) { case 0: continue; case 1: *virt_cast(settings[i].data) = *virt_cast(src); break; case 2: *virt_cast(settings[i].data) = *virt_cast(src); break; case 4: *virt_cast(settings[i].data) = *virt_cast(src); break; default: std::memset(settings[i].data.get(), 0, 4); // why??? std::memcpy(settings[i].data.get(), src.get(), settings[i].dataSize); } } } } else if (command == UCCommand::WriteSysConfig || command == UCCommand::DeleteSysConfig) { auto request = virt_cast(vecs[0].vaddr); for (auto i = 0u; i < count; ++i) { settings[i].error = request->settings[i].error; if (settings[i].error) { result = settings[i].error; } } } else { decaf_abort(fmt::format("Unimplemented result handler for UCCommand {}", command)); } } if (callback) { cafe::invoke(cpu::this_core::state(), callback, result, command, count, settings, callbackContext); } } if (vecs) { for (auto i = 0u; i < count + 1; ++i) { internal::ucFreeMessage(virt_cast(vecs[i].vaddr)); } internal::ucFreeMessage(vecs); } return static_cast(result); } static void ucIosAsyncCallback(IOSError status, virt_ptr context) { auto asyncParams = virt_cast(context); ucHandleIosResult(UCError::OK, asyncParams->command, asyncParams->unk0x0C, asyncParams->count, asyncParams->settings, asyncParams->vecs, nullptr, asyncParams->callback, asyncParams->context); } } // namespace internal UCError UCOpen() { OSInitMutex(virt_addrof(sUserConfigData->lock)); OSLockMutex(virt_addrof(sUserConfigData->lock)); if (!sUserConfigData->initialised) { if (!sUserConfigData->smallMessagePool) { sUserConfigData->smallMessagePool = IPCBufPoolCreate(virt_addrof(sUserConfigData->smallMessageBuffer), static_cast(sUserConfigData->smallMessageBuffer.size()), SmallMessageSize, virt_addrof(sUserConfigData->smallMessageCount), 1); } if (!sUserConfigData->largeMessagePool) { sUserConfigData->largeMessagePool = IPCBufPoolCreate(virt_addrof(sUserConfigData->largeMessageBuffer), static_cast(sUserConfigData->largeMessageBuffer.size()), LargeMessageSize, virt_addrof(sUserConfigData->largeMessageCount), 1); } if (sUserConfigData->smallMessagePool && sUserConfigData->largeMessagePool) { sUserConfigData->initialised = true; } } OSUnlockMutex(virt_addrof(sUserConfigData->lock)); if (!sUserConfigData->initialised) { return UCError::Error; } return static_cast(IOS_Open(make_stack_string("/dev/usr_cfg"), IOSOpenMode::None)); } UCError UCClose(IOSHandle handle) { return static_cast(IOS_Close(handle)); } UCError UCDeleteSysConfig(IOSHandle handle, uint32_t count, virt_ptr settings) { return UCDeleteSysConfigAsync(handle, count, settings, nullptr); } UCError UCDeleteSysConfigAsync(IOSHandle handle, uint32_t count, virt_ptr settings, virt_ptr asyncParams) { auto result = UCError::OK; uint32_t msgBufSize = 0, vecBufSize = 0; virt_ptr msgBuf = nullptr, vecBuf = nullptr; virt_ptr request = nullptr; virt_ptr vecs = nullptr; if (!settings) { result = UCError::InvalidParam; goto fail; } msgBufSize = static_cast(count * sizeof(UCSysConfig) + sizeof(UCDeleteSysConfigRequest)); msgBuf = internal::ucAllocateMessage(msgBufSize); if (!msgBuf) { result = UCError::NoIPCBuffers; goto fail; } request = virt_cast(msgBuf); request->unk0x00 = 0u; request->count = count; std::memcpy(request->settings, settings.get(), sizeof(UCSysConfig) * count); vecBufSize = static_cast((count + 1) * sizeof(IOSVec)); vecBuf = internal::ucAllocateMessage(vecBufSize); if (!vecBuf) { result = UCError::NoIPCBuffers; goto fail; } vecs = virt_cast(vecBuf); vecs[0].vaddr = virt_cast(msgBuf); vecs[0].len = msgBufSize; for (auto i = 0u; i < count; ++i) { auto size = settings[i].dataSize; vecs[1 + i].len = size; if (size > 0) { vecs[1 + i].vaddr = virt_cast(internal::ucAllocateMessage(size)); if (!vecs[1 + i].vaddr) { result = UCError::NoIPCBuffers; goto fail; } } else { vecs[1 + i].vaddr = 0u; } } if (!asyncParams) { result = static_cast(IOS_Ioctlv(handle, UCCommand::DeleteSysConfig, 0, count + 1, vecs)); } else { internal::ucSetupAsyncParams(UCCommand::DeleteSysConfig, 0, count, settings, vecs, asyncParams); result = static_cast(IOS_IoctlvAsync(handle, UCCommand::DeleteSysConfig, 0, count + 1, vecs, sUcIosAsyncCallback, asyncParams)); } goto out; fail: if (msgBuf) { internal::ucFreeMessage(msgBuf); msgBuf = nullptr; } if (vecBuf) { for (auto i = 0u; i < count; ++i) { if (vecs[1 + i].vaddr) { internal::ucFreeMessage(virt_cast(vecs[1 + i].vaddr)); } } internal::ucFreeMessage(vecBuf); vecBuf = nullptr; vecs = nullptr; } out: return internal::ucHandleIosResult(result, UCCommand::DeleteSysConfig, 0, count, settings, vecs, asyncParams, nullptr, nullptr); } UCError UCReadSysConfig(IOSHandle handle, uint32_t count, virt_ptr settings) { return UCReadSysConfigAsync(handle, count, settings, nullptr); } UCError UCReadSysConfigAsync(IOSHandle handle, uint32_t count, virt_ptr settings, virt_ptr asyncParams) { auto result = UCError::OK; uint32_t msgBufSize = 0, vecBufSize = 0; virt_ptr msgBuf = nullptr, vecBuf = nullptr; virt_ptr request = nullptr; virt_ptr vecs = nullptr; if (!settings) { result = UCError::InvalidParam; goto fail; } msgBufSize = static_cast(count * sizeof(UCSysConfig) + sizeof(UCReadSysConfigRequest)); msgBuf = internal::ucAllocateMessage(msgBufSize); if (!msgBuf) { result = UCError::NoIPCBuffers; goto fail; } request = virt_cast(msgBuf); request->unk0x00 = 0u; request->count = count; std::memcpy(request->settings, settings.get(), sizeof(UCSysConfig) * count); vecBufSize = static_cast((count + 1) * sizeof(IOSVec)); vecBuf = internal::ucAllocateMessage(vecBufSize); if (!vecBuf) { result = UCError::NoIPCBuffers; goto fail; } vecs = virt_cast(vecBuf); vecs[0].vaddr = virt_cast(msgBuf); vecs[0].len = msgBufSize; for (auto i = 0u; i < count; ++i) { auto size = settings[i].dataSize; vecs[1 + i].len = size; if (size > 0) { vecs[1 + i].vaddr = virt_cast(internal::ucAllocateMessage(size)); if (!vecs[1 + i].vaddr) { result = UCError::NoIPCBuffers; goto fail; } } else { vecs[1 + i].vaddr = 0u; } } if (!asyncParams) { result = static_cast(IOS_Ioctlv(handle, UCCommand::ReadSysConfig, 0, count + 1, vecs)); } else { internal::ucSetupAsyncParams(UCCommand::ReadSysConfig, 0, count, settings, vecs, asyncParams); result = static_cast(IOS_IoctlvAsync(handle, UCCommand::ReadSysConfig, 0, count + 1, vecs, sUcIosAsyncCallback, asyncParams)); } goto out; fail: if (msgBuf) { internal::ucFreeMessage(msgBuf); msgBuf = nullptr; } if (vecBuf) { for (auto i = 0u; i < count; ++i) { if (vecs[1 + i].vaddr) { internal::ucFreeMessage(virt_cast(vecs[1 + i].vaddr)); } } internal::ucFreeMessage(vecBuf); vecBuf = nullptr; vecs = nullptr; } out: return internal::ucHandleIosResult(result, UCCommand::ReadSysConfig, 0, count, settings, vecs, asyncParams, nullptr, nullptr); } UCError UCWriteSysConfig(IOSHandle handle, uint32_t count, virt_ptr settings) { return UCWriteSysConfigAsync(handle, count, settings, nullptr); } UCError UCWriteSysConfigAsync(IOSHandle handle, uint32_t count, virt_ptr settings, virt_ptr asyncParams) { auto result = UCError::OK; uint32_t msgBufSize = 0, vecBufSize = 0; virt_ptr vecBuf = nullptr, msgBuf = nullptr; virt_ptr request = nullptr; virt_ptr vecs = nullptr; if (!settings) { result = UCError::InvalidParam; goto fail; } msgBufSize = static_cast(count * sizeof(UCSysConfig) + sizeof(UCWriteSysConfigRequest)); msgBuf = internal::ucAllocateMessage(msgBufSize); if (!msgBuf) { result = UCError::NoIPCBuffers; goto fail; } request = virt_cast(msgBuf); request->unk0x00 = 0u; request->count = count; std::memcpy(request->settings, settings.get(), count * sizeof(UCSysConfig)); vecBufSize = static_cast((count + 1) * sizeof(IOSVec)); vecBuf = internal::ucAllocateMessage(vecBufSize); if (!vecBuf) { result = UCError::NoIPCBuffers; goto fail; } vecs = virt_cast(vecBuf); vecs[0].vaddr = virt_cast(msgBuf); vecs[0].len = msgBufSize; for (auto i = 0u; i < count; ++i) { auto size = settings[i].dataSize; vecs[1 + i].len = size; if (size > 0) { vecs[1 + i].vaddr = virt_cast(internal::ucAllocateMessage(size)); if (!vecs[1 + i].vaddr) { result = UCError::NoIPCBuffers; goto fail; } if (settings[i].data) { std::memcpy(virt_cast(vecs[1 + i].vaddr).get(), settings[i].data.get(), settings[i].dataSize); } } else { vecs[1 + i].vaddr = 0u; } } if (!asyncParams) { result = static_cast(IOS_Ioctlv(handle, UCCommand::WriteSysConfig, 0, count + 1, vecs)); } else { internal::ucSetupAsyncParams(UCCommand::WriteSysConfig, 0, count, settings, vecs, asyncParams); result = static_cast(IOS_IoctlvAsync(handle, UCCommand::WriteSysConfig, 0, count + 1, vecs, sUcIosAsyncCallback, asyncParams)); } goto out; fail: if (msgBuf) { internal::ucFreeMessage(msgBuf); msgBuf = nullptr; } if (vecBuf) { for (auto i = 0u; i < count; ++i) { if (vecs[1 + i].vaddr) { internal::ucFreeMessage(virt_cast(vecs[1 + i].vaddr)); } } internal::ucFreeMessage(vecBuf); vecBuf = nullptr; vecs = nullptr; } out: return internal::ucHandleIosResult(result, UCCommand::WriteSysConfig, 0, count, settings, vecs, asyncParams, nullptr, nullptr); } void Library::registerUserConfigSymbols() { RegisterFunctionExport(UCOpen); RegisterFunctionExport(UCClose); RegisterFunctionExport(UCDeleteSysConfig); RegisterFunctionExport(UCDeleteSysConfigAsync); RegisterFunctionExport(UCReadSysConfig); RegisterFunctionExport(UCReadSysConfigAsync); RegisterFunctionExport(UCWriteSysConfig); RegisterFunctionExport(UCWriteSysConfigAsync); RegisterDataInternal(sUserConfigData); RegisterFunctionInternal(internal::ucIosAsyncCallback, sUcIosAsyncCallback); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/coreinit/coreinit_userconfig.h ================================================ #pragma once #include "coreinit_enum.h" #include "coreinit_ios.h" #include "ios/auxil/ios_auxil_usr_cfg.h" #include namespace cafe::coreinit { /** * \defgroup coreinit_userconfig User Config * \ingroup coreinit * @{ */ #pragma pack(push, 1) using ios::auxil::UCCommand; using ios::auxil::UCDataType; using ios::auxil::UCError; // This is a copy of ios::auxil::UCSysConfig but with a virt_ptr struct UCSysConfig { be2_array name; be2_val access; be2_val dataType; be2_val error; be2_val dataSize; be2_virt_ptr data; }; CHECK_OFFSET(UCSysConfig, 0x00, name); CHECK_OFFSET(UCSysConfig, 0x40, access); CHECK_OFFSET(UCSysConfig, 0x44, dataType); CHECK_OFFSET(UCSysConfig, 0x48, error); CHECK_OFFSET(UCSysConfig, 0x4C, dataSize); CHECK_OFFSET(UCSysConfig, 0x50, data); CHECK_SIZE(UCSysConfig, 0x54); using UCAsyncCallbackFn = virt_func_ptr settings, virt_ptr context)>; struct UCAsyncParams { be2_val callback; be2_virt_ptr context; be2_val command; be2_val unk0x0C; be2_val count; be2_virt_ptr settings; be2_virt_ptr vecs; }; CHECK_OFFSET(UCAsyncParams, 0x00, callback); CHECK_OFFSET(UCAsyncParams, 0x04, context); CHECK_OFFSET(UCAsyncParams, 0x08, command); CHECK_OFFSET(UCAsyncParams, 0x0C, unk0x0C); CHECK_OFFSET(UCAsyncParams, 0x10, count); CHECK_OFFSET(UCAsyncParams, 0x14, settings); CHECK_OFFSET(UCAsyncParams, 0x18, vecs); CHECK_SIZE(UCAsyncParams, 0x1C); #pragma pack(pop) UCError UCOpen(); UCError UCClose(IOSHandle handle); UCError UCDeleteSysConfig(IOSHandle handle, uint32_t count, virt_ptr settings); UCError UCDeleteSysConfigAsync(IOSHandle handle, uint32_t count, virt_ptr settings, virt_ptr asyncParams); UCError UCReadSysConfig(IOSHandle handle, uint32_t count, virt_ptr settings); UCError UCReadSysConfigAsync(IOSHandle handle, uint32_t count, virt_ptr settings, virt_ptr asyncParams); UCError UCWriteSysConfig(IOSHandle handle, uint32_t count, virt_ptr settings); UCError UCWriteSysConfigAsync(IOSHandle handle, uint32_t count, virt_ptr settings, virt_ptr asyncParams); /** @} */ } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/dc/dc.cpp ================================================ #include "dc.h" namespace cafe::dc { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::dc ================================================ FILE: src/libdecaf/src/cafe/libraries/dc/dc.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::dc { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::dc, "dc.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::dc ================================================ FILE: src/libdecaf/src/cafe/libraries/dmae/dmae.cpp ================================================ #include "dmae.h" #include "dmae_ring.h" namespace cafe::dmae { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { DMAEInit(); return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerRingSymbols(); } } // namespace cafe::coreinit ================================================ FILE: src/libdecaf/src/cafe/libraries/dmae/dmae.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::dmae { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::dmae, "dmae.rpl") { } protected: virtual void registerSymbols() override; private: void registerRingSymbols(); }; } // namespace cafe::dmae ================================================ FILE: src/libdecaf/src/cafe/libraries/dmae/dmae_enum.h ================================================ #ifndef CAFE_DMAE_ENUM_H #define CAFE_DMAE_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(dmae) ENUM_BEG(DMAEEndianSwapMode, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Swap8In16, 1) ENUM_VALUE(Swap8In32, 2) ENUM_END(DMAEEndianSwapMode) ENUM_NAMESPACE_EXIT(dmae) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_DMAE_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/dmae/dmae_ring.cpp ================================================ #include "dmae.h" #include "dmae_ring.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include "cafe/cafe_stackobject.h" #include namespace cafe::dmae { struct StaticRingData { be2_struct mutex; be2_val timeout; be2_val lastSubmittedTimestamp; }; static virt_ptr sRingData = nullptr; void DMAEInit() { coreinit::OSInitMutex(virt_addrof(sRingData->mutex)); } DMAETimestamp DMAEGetLastSubmittedTimeStamp() { coreinit::OSLockMutex(virt_addrof(sRingData->mutex)); auto timestamp = sRingData->lastSubmittedTimestamp; coreinit::OSUnlockMutex(virt_addrof(sRingData->mutex)); return timestamp; } DMAETimestamp DMAEGetRetiredTimeStamp() { return DMAEGetLastSubmittedTimeStamp(); } uint32_t DMAEGetTimeout() { return sRingData->timeout; } void DMAESetTimeout(uint32_t timeout) { sRingData->timeout = timeout; } uint64_t DMAECopyMem(virt_ptr dst, virt_ptr src, uint32_t numWords, DMAEEndianSwapMode endian) { coreinit::OSLockMutex(virt_addrof(sRingData->mutex)); if (endian == DMAEEndianSwapMode::None) { std::memcpy(dst.get(), src.get(), numWords * 4); } else if (endian == DMAEEndianSwapMode::Swap8In16) { auto dstWords = reinterpret_cast(dst.get()); auto srcWords = reinterpret_cast(src.get()); for (auto i = 0u; i < numWords * 2; ++i) { *dstWords++ = byte_swap(*srcWords++); } } else if (endian == DMAEEndianSwapMode::Swap8In32) { auto dstDwords = reinterpret_cast(dst.get()); auto srcDwords = reinterpret_cast(src.get()); for (auto i = 0u; i < numWords; ++i) { *dstDwords++ = byte_swap(*srcDwords++); } } auto timestamp = coreinit::OSGetTime(); sRingData->lastSubmittedTimestamp = timestamp; coreinit::OSUnlockMutex(virt_addrof(sRingData->mutex)); return timestamp; } uint64_t DMAEFillMem(virt_ptr dst, uint32_t value, uint32_t numDwords) { coreinit::OSLockMutex(virt_addrof(sRingData->mutex)); auto dstValue = byte_swap(value); auto dstDwords = reinterpret_cast(dst.get()); for (auto i = 0u; i < numDwords; ++i) { dstDwords[i] = dstValue; } auto timestamp = coreinit::OSGetTime(); sRingData->lastSubmittedTimestamp = timestamp; coreinit::OSUnlockMutex(virt_addrof(sRingData->mutex)); return timestamp; } BOOL DMAEWaitDone(DMAETimestamp timestamp) { return TRUE; } void Library::registerRingSymbols() { RegisterFunctionExport(DMAEInit); RegisterFunctionExport(DMAEGetLastSubmittedTimeStamp); RegisterFunctionExport(DMAEGetRetiredTimeStamp); RegisterFunctionExport(DMAEGetTimeout); RegisterFunctionExport(DMAESetTimeout); RegisterFunctionExport(DMAECopyMem); RegisterFunctionExport(DMAEFillMem); RegisterFunctionExport(DMAEWaitDone); RegisterDataInternal(sRingData); } } // namespace cafe::dmae ================================================ FILE: src/libdecaf/src/cafe/libraries/dmae/dmae_ring.h ================================================ #pragma once #include "dmae_enum.h" #include namespace cafe::dmae { using DMAETimestamp = int64_t; void DMAEInit(); DMAETimestamp DMAEGetLastSubmittedTimeStamp(); DMAETimestamp DMAEGetRetiredTimeStamp(); uint32_t DMAEGetTimeout(); void DMAESetTimeout(uint32_t timeout); uint64_t DMAECopyMem(virt_ptr dst, virt_ptr src, uint32_t numWords, DMAEEndianSwapMode endian); uint64_t DMAEFillMem(virt_ptr dst, uint32_t value, uint32_t numDwords); BOOL DMAEWaitDone(DMAETimestamp timestamp); } // namespace cafe::dmae ================================================ FILE: src/libdecaf/src/cafe/libraries/drmapp/drmapp.cpp ================================================ #include "drmapp.h" namespace cafe::drmapp { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::drmapp ================================================ FILE: src/libdecaf/src/cafe/libraries/drmapp/drmapp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::drmapp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::drmapp, "drmapp.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::drmapp ================================================ FILE: src/libdecaf/src/cafe/libraries/erreula/erreula.cpp ================================================ #include "erreula.h" namespace cafe::nn_erreula { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerErrorViewerSymbols(); } } // namespace cafe::nn_erreula ================================================ FILE: src/libdecaf/src/cafe/libraries/erreula/erreula.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_erreula { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::erreula, "erreula.rpl") { } protected: virtual void registerSymbols() override; private: void registerErrorViewerSymbols(); }; } // namespace cafe::nn_erreula ================================================ FILE: src/libdecaf/src/cafe/libraries/erreula/erreula_enum.h ================================================ #ifndef CAFE_ERREULA_ENUM_H #define CAFE_ERREULA_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_erreula) ENUM_BEG(ControllerType, uint32_t) ENUM_VALUE(WiiRemote0, 0) ENUM_VALUE(WiiRemote1, 1) ENUM_VALUE(WiiRemote2, 2) ENUM_VALUE(WiiRemote3, 3) ENUM_VALUE(DrcGamepad, 4) ENUM_END(ControllerType) ENUM_BEG(ErrorType, uint32_t) ENUM_VALUE(Code, 0) ENUM_VALUE(Message, 1) ENUM_VALUE(Message1Button, 2) ENUM_VALUE(Message2Button, 3) ENUM_END(ErrorType) ENUM_BEG(ErrorViewerState, uint32_t) ENUM_VALUE(Hidden, 0) ENUM_VALUE(FadeIn, 1) ENUM_VALUE(Visible, 2) ENUM_VALUE(FadeOut, 3) ENUM_END(ErrorViewerState) ENUM_BEG(LangType, uint32_t) ENUM_VALUE(Japanese, 0) ENUM_VALUE(English, 1) // TODO: More values.. ENUM_END(LangType) ENUM_BEG(RegionType, uint32_t) ENUM_VALUE(Japan, 0) ENUM_VALUE(USA, 1) ENUM_VALUE(Europe, 2) ENUM_VALUE(China, 3) ENUM_VALUE(Korea, 4) ENUM_VALUE(Taiwan, 5) ENUM_END(RegionType) ENUM_BEG(RenderTarget, uint32_t) ENUM_VALUE(Tv, 0) ENUM_VALUE(Drc, 1) ENUM_VALUE(Both, 2) ENUM_END(RenderTarget) ENUM_BEG(ResultType, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Exited, 1) // TODO: More values.. ENUM_END(ResultType) ENUM_NAMESPACE_EXIT(nn_erreula) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_ERREULA_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/erreula/erreula_errorviewer.cpp ================================================ #include "erreula.h" #include "erreula_errorviewer.h" #include "cafe/libraries/cafe_hle_stub.h" #include "decaf_erreula.h" #include namespace cafe::nn_erreula { struct StaticErrEulaData { be2_val errorViewerState; be2_val buttonPressed; be2_val button2Pressed; be2_virt_ptr workMemory; }; static virt_ptr sErrEulaData = nullptr; // Uses a real mutex because we interact with outside world via ErrEulaDriver static std::mutex sMutex; void ErrEulaAppearError(virt_ptr args) { { std::unique_lock lock { sMutex }; sErrEulaData->errorViewerState = ErrorViewerState::Visible; } if (auto driver = decaf::errEulaDriver()) { switch (args->errorArg.errorType) { case ErrorType::Code: driver->onOpenErrorCode(args->errorArg.errorCode); break; case ErrorType::Message: driver->onOpenErrorMessage(args->errorArg.errorMessage.getRawPointer()); break; case ErrorType::Message1Button: driver->onOpenErrorMessage(args->errorArg.errorMessage.getRawPointer(), args->errorArg.button1Label.getRawPointer()); break; case ErrorType::Message2Button: driver->onOpenErrorMessage(args->errorArg.errorMessage.getRawPointer(), args->errorArg.button1Label.getRawPointer(), args->errorArg.button2Label.getRawPointer()); break; } } } void ErrEulaCalc(virt_ptr info) { } BOOL ErrEulaCreate(virt_ptr workMemory, RegionType region, LangType language, virt_ptr fsClient) { return TRUE; } void ErrEulaDestroy() { } void ErrEulaDisappearError() { { std::unique_lock lock { sMutex }; sErrEulaData->errorViewerState = ErrorViewerState::Hidden; } if (auto driver = decaf::errEulaDriver()) { driver->onClose(); } } void ErrEulaDrawDRC() { } void ErrEulaDrawTV() { } ErrorViewerState ErrEulaGetStateErrorViewer() { std::unique_lock lock { sMutex }; return sErrEulaData->errorViewerState; } BOOL ErrEulaIsDecideSelectButtonError() { std::unique_lock lock { sMutex }; return sErrEulaData->buttonPressed; } BOOL ErrEulaIsDecideSelectLeftButtonError() { std::unique_lock lock{ sMutex }; return sErrEulaData->buttonPressed; } BOOL ErrEulaIsDecideSelectRightButtonError() { std::unique_lock lock{ sMutex }; return sErrEulaData->button2Pressed; } void ErrEulaSetControllerRemo(ControllerType type) { decaf_warn_stub(); } void ErrEulaAppearHomeNixSign(virt_ptr arg) { decaf_warn_stub(); } BOOL ErrEulaIsAppearHomeNixSign() { decaf_warn_stub(); return FALSE; } void ErrEulaDisappearHomeNixSign() { decaf_warn_stub(); } void ErrEulaChangeLang(LangType language) { decaf_warn_stub(); } BOOL ErrEulaIsSelectCursorActive() { decaf_warn_stub(); return FALSE; } ResultType ErrEulaGetResultType() { decaf_warn_stub(); return static_cast(1); } uint32_t ErrEulaGetResultCode() { decaf_warn_stub(); return 0; } uint32_t ErrEulaGetSelectButtonNumError() { decaf_warn_stub(); return 0; } void ErrEulaSetVersion(int32_t version) { decaf_warn_stub(); } void ErrEulaPlayAppearSE(bool value) { decaf_warn_stub(); } bool ErrEulaJump(virt_ptr a1, uint32_t a2) { decaf_warn_stub(); return false; } namespace internal { void buttonClicked() { std::unique_lock lock { sMutex }; if (sErrEulaData->errorViewerState != ErrorViewerState::Visible) { return; } sErrEulaData->buttonPressed = true; } void button1Clicked() { std::unique_lock lock { sMutex }; if (sErrEulaData->errorViewerState != ErrorViewerState::Visible) { return; } sErrEulaData->buttonPressed = true; } void button2Clicked() { std::unique_lock lock{ sMutex }; if (sErrEulaData->errorViewerState != ErrorViewerState::Visible) { return; } sErrEulaData->button2Pressed = true; } } // internal void Library::registerErrorViewerSymbols() { RegisterFunctionExportName("ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", ErrEulaAppearError); RegisterFunctionExportName("ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", ErrEulaCalc); RegisterFunctionExportName("ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", ErrEulaCreate); RegisterFunctionExportName("ErrEulaDestroy__3RplFv", ErrEulaDestroy); RegisterFunctionExportName("ErrEulaDisappearError__3RplFv", ErrEulaDisappearError); RegisterFunctionExportName("ErrEulaDrawDRC__3RplFv", ErrEulaDrawDRC); RegisterFunctionExportName("ErrEulaDrawTV__3RplFv", ErrEulaDrawTV); RegisterFunctionExportName("ErrEulaGetStateErrorViewer__3RplFv", ErrEulaGetStateErrorViewer); RegisterFunctionExportName("ErrEulaIsDecideSelectButtonError__3RplFv", ErrEulaIsDecideSelectButtonError); RegisterFunctionExportName("ErrEulaIsDecideSelectLeftButtonError__3RplFv", ErrEulaIsDecideSelectLeftButtonError); RegisterFunctionExportName("ErrEulaIsDecideSelectRightButtonError__3RplFv", ErrEulaIsDecideSelectRightButtonError); RegisterFunctionExportName("ErrEulaSetControllerRemo__3RplFQ3_2nn7erreula14ControllerType", ErrEulaSetControllerRemo); RegisterFunctionExportName("ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", ErrEulaAppearHomeNixSign); RegisterFunctionExportName("ErrEulaIsAppearHomeNixSign__3RplFv", ErrEulaIsAppearHomeNixSign); RegisterFunctionExportName("ErrEulaDisappearHomeNixSign__3RplFv", ErrEulaDisappearHomeNixSign); RegisterFunctionExportName("ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", ErrEulaChangeLang); RegisterFunctionExportName("ErrEulaIsSelectCursorActive__3RplFv", ErrEulaIsSelectCursorActive); RegisterFunctionExportName("ErrEulaGetResultType__3RplFv", ErrEulaGetResultType); RegisterFunctionExportName("ErrEulaGetResultCode__3RplFv", ErrEulaGetResultCode); RegisterFunctionExportName("ErrEulaGetSelectButtonNumError__3RplFv", ErrEulaGetSelectButtonNumError); RegisterFunctionExportName("ErrEulaSetVersion__3RplFi", ErrEulaSetVersion); RegisterFunctionExportName("ErrEulaPlayAppearSE__3RplFb", ErrEulaPlayAppearSE); RegisterFunctionExportName("ErrEulaJump__3RplFPCcUi", ErrEulaJump); RegisterDataInternal(sErrEulaData); } } // namespace cafe::nn_erreula ================================================ FILE: src/libdecaf/src/cafe/libraries/erreula/erreula_errorviewer.h ================================================ #pragma once #include "erreula_enum.h" #include namespace cafe::coreinit { struct FSClient; } // namespace cafe::coreinit namespace cafe::kpad { struct KPADStatus; } // namespace cafe::kpad namespace cafe::vpad { struct VPADStatus; } // namespace cafe::vpad namespace cafe::nn_erreula { struct ErrorArg { be2_val errorType; be2_val renderTarget; be2_val controllerType; be2_val unknown0x0C; be2_val errorCode; be2_val unknown0x14; be2_virt_ptr errorMessage; be2_virt_ptr button1Label; be2_virt_ptr button2Label; be2_virt_ptr errorTitle; be2_val unknown0x28; PADDING(3); }; CHECK_OFFSET(ErrorArg, 0x00, errorType); CHECK_OFFSET(ErrorArg, 0x04, renderTarget); CHECK_OFFSET(ErrorArg, 0x08, controllerType); CHECK_OFFSET(ErrorArg, 0x0C, unknown0x0C); CHECK_OFFSET(ErrorArg, 0x10, errorCode); CHECK_OFFSET(ErrorArg, 0x14, unknown0x14); CHECK_OFFSET(ErrorArg, 0x18, errorMessage); CHECK_OFFSET(ErrorArg, 0x1C, button1Label); CHECK_OFFSET(ErrorArg, 0x20, button2Label); CHECK_OFFSET(ErrorArg, 0x24, errorTitle); CHECK_OFFSET(ErrorArg, 0x28, unknown0x28); CHECK_SIZE(ErrorArg, 0x2C); struct AppearArg { be2_struct errorArg; }; CHECK_OFFSET(AppearArg, 0x00, errorArg); CHECK_SIZE(AppearArg, 0x2C); struct ControllerInfo { be2_virt_ptr vpad; be2_array, 4> kpad; }; CHECK_OFFSET(ControllerInfo, 0x00, vpad); CHECK_OFFSET(ControllerInfo, 0x04, kpad); CHECK_SIZE(ControllerInfo, 0x14); struct HomeNixSignArg { be2_val unknown0x00; }; CHECK_OFFSET(HomeNixSignArg, 0x00, unknown0x00); CHECK_SIZE(HomeNixSignArg, 0x04); void ErrEulaAppearError(virt_ptr args); void ErrEulaCalc(virt_ptr info); BOOL ErrEulaCreate(virt_ptr workMemory, RegionType region, LangType language, virt_ptr fsClient); void ErrEulaDestroy(); void ErrEulaDisappearError(); void ErrEulaDrawDRC(); void ErrEulaDrawTV(); ErrorViewerState ErrEulaGetStateErrorViewer(); BOOL ErrEulaIsDecideSelectButtonError(); BOOL ErrEulaIsDecideSelectLeftButtonError(); BOOL ErrEulaIsDecideSelectRightButtonError(); void ErrEulaSetControllerRemo(ControllerType type); void ErrEulaAppearHomeNixSign(virt_ptr arg); BOOL ErrEulaIsAppearHomeNixSign(); void ErrEulaDisappearHomeNixSign(); void ErrEulaChangeLang(LangType language); BOOL ErrEulaIsSelectCursorActive(); ResultType ErrEulaGetResultType(); uint32_t ErrEulaGetResultCode(); uint32_t ErrEulaGetSelectButtonNumError(); void ErrEulaSetVersion(int32_t version); void ErrEulaPlayAppearSE(bool value); bool ErrEulaJump(virt_ptr a1, uint32_t a2); namespace internal { void buttonClicked(); void button1Clicked(); void button2Clicked(); } // internal } // namespace cafe::nn_erreula ================================================ FILE: src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_enum.h ================================================ #ifndef GHS_ENUM_H #define GHS_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(ghs) FLAGS_BEG(DestructorFlags, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(FreeMemory, 0x40) FLAGS_END(DestructorFlags) ENUM_NAMESPACE_EXIT(ghs) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef GHS_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_malloc.cpp ================================================ #include "cafe_ghs_malloc.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include "cafe/libraries/coreinit/coreinit_ghs.h" #include "cafe/libraries/coreinit/coreinit_osreport.h" namespace cafe::ghs { using namespace cafe::coreinit; struct MallocGuard { static constexpr uint32_t GuardWord = 0xCAFE4321; be2_val guardWord; be2_val allocSize; }; CHECK_OFFSET(MallocGuard, 0, guardWord); CHECK_OFFSET(MallocGuard, 4, allocSize); CHECK_SIZE(MallocGuard, 8); virt_ptr malloc(uint32_t size) { auto ptr = MEMAllocFromDefaultHeapEx(size + sizeof(MallocGuard), 8); if (!ptr) { gh_set_errno(12); return nullptr; } auto guard = virt_cast(ptr); guard->guardWord = MallocGuard::GuardWord; guard->allocSize = size; return virt_cast(guard + 1); } void free(virt_ptr ptr) { if (!ptr) { return; } auto guard = virt_cast(ptr) - 1; if (guard->guardWord != MallocGuard::GuardWord) { internal::OSPanic("cos_def_malloc.c", 93, "Failed assertion *rawptr == COS_DEF_MALLOC_GUARDWORD"); } MEMFreeToDefaultHeap(ptr); } } // namespace cafe::ghs ================================================ FILE: src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_malloc.h ================================================ #pragma once #include namespace cafe::ghs { virt_ptr malloc(uint32_t size); void free(virt_ptr ptr); } // namespace cafe::ghs ================================================ FILE: src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_typeinfo.cpp ================================================ #include "cafe_ghs_typeinfo.h" #include "cafe_ghs_malloc.h" #include "cafe/libraries/coreinit/coreinit_ghs.h" #include "cafe/libraries/coreinit/coreinit_osreport.h" namespace cafe::ghs { void std_typeinfo_Destructor(virt_ptr self, ghs::DestructorFlags flags) { if (self && (flags & ghs::DestructorFlags::FreeMemory)) { ghs::free(self); } } void pure_virtual_called() { coreinit::internal::OSPanic("ghs", 0, "__pure_virtual_called"); coreinit::ghs_exit(6); } } // namespace cafe::ghs ================================================ FILE: src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_typeinfo.h ================================================ #pragma once #include "cafe_ghs_enum.h" #include namespace cafe::ghs { struct BaseTypeDescriptor; struct TypeDescriptor; struct VirtualTable; using TypeIDStorage = uint32_t; struct BaseTypeDescriptor { be2_virt_ptr typeDescriptor; be2_val flags; }; CHECK_OFFSET(BaseTypeDescriptor, 0x00, typeDescriptor); CHECK_OFFSET(BaseTypeDescriptor, 0x04, flags); CHECK_SIZE(BaseTypeDescriptor, 0x08); struct TypeDescriptor { //! Pointer to virtual table for std::typeinfo be2_virt_ptr typeInfoVTable; //! Name of this type be2_virt_ptr name; //! Unique ID for this type be2_val typeID; //! Pointer to a list of base types, the last base type has flags=0x1600 be2_virt_ptr baseTypes; }; CHECK_OFFSET(TypeDescriptor, 0x00, typeInfoVTable); CHECK_OFFSET(TypeDescriptor, 0x04, name); CHECK_OFFSET(TypeDescriptor, 0x08, typeID); CHECK_OFFSET(TypeDescriptor, 0x0C, baseTypes); CHECK_SIZE(TypeDescriptor, 0x10); struct VirtualTable { be2_val flags; be2_virt_ptr ptr; }; CHECK_OFFSET(VirtualTable, 0x00, flags); CHECK_OFFSET(VirtualTable, 0x04, ptr); CHECK_SIZE(VirtualTable, 0x08); void std_typeinfo_Destructor(virt_ptr self, ghs::DestructorFlags flags); void pure_virtual_called(); } // namespace cafe::ghs ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2.cpp ================================================ #include "gx2.h" #include "gx2_debug.h" #include "gx2r_resource.h" namespace cafe::gx2 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { internal::initialiseDebug(); internal::initialiseGx2rAllocator(); return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerLibraryDependency("coreinit"); registerLibraryDependency("tcl"); registerApertureSymbols(); registerCbPoolSymbols(); registerClearSymbols(); registerContextStateSymbols(); registerCounterSymbols(); registerDebugCaptureSymbols(); registerDisplaySymbols(); registerDisplayListSymbols(); registerDrawSymbols(); registerEventSymbols(); registerFenceSymbols(); registerFetchShadersSymbols(); registerFormatSymbols(); registerMemorySymbols(); registerQuerySymbols(); registerRegistersSymbols(); registerSamplerSymbols(); registerShadersSymbols(); registerStateSymbols(); registerSurfaceSymbols(); registerTempSymbols(); registerTessellationSymbols(); registerTextureSymbols(); registerGx2rBufferSymbols(); registerGx2rDisplayListSymbols(); registerGx2rDrawSymbols(); registerGx2rMemorySymbols(); registerGx2rResourceSymbols(); registerGx2rShadersSymbols(); registerGx2rSurfaceSymbols(); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::gx2 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::gx2, "gx2.rpl") { } protected: virtual void registerSymbols() override; private: void registerApertureSymbols(); void registerCbPoolSymbols(); void registerClearSymbols(); void registerContextStateSymbols(); void registerCounterSymbols(); void registerDebugCaptureSymbols(); void registerDisplaySymbols(); void registerDisplayListSymbols(); void registerDrawSymbols(); void registerEventSymbols(); void registerFenceSymbols(); void registerFetchShadersSymbols(); void registerFormatSymbols(); void registerMemorySymbols(); void registerQuerySymbols(); void registerRegistersSymbols(); void registerSamplerSymbols(); void registerShadersSymbols(); void registerStateSymbols(); void registerSurfaceSymbols(); void registerTempSymbols(); void registerTessellationSymbols(); void registerTextureSymbols(); void registerGx2rBufferSymbols(); void registerGx2rDisplayListSymbols(); void registerGx2rDrawSymbols(); void registerGx2rMemorySymbols(); void registerGx2rResourceSymbols(); void registerGx2rShadersSymbols(); void registerGx2rSurfaceSymbols(); }; } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_addrlib.cpp ================================================ #include "gx2_addrlib.h" #include "gx2_surface.h" #include "gx2_format.h" #include #include #include #include namespace cafe::gx2::internal { bool getSurfaceInfo(GX2Surface *surface, uint32_t level, ADDR_COMPUTE_SURFACE_INFO_OUTPUT *output) { ADDR_E_RETURNCODE result = ADDR_OK; auto hwFormat = static_cast(surface->format & 0x3f); auto height = 1u; auto width = std::max(1u, surface->width >> level); auto numSlices = 1u; switch (surface->dim) { case GX2SurfaceDim::Texture1D: height = 1; numSlices = 1; break; case GX2SurfaceDim::Texture2D: height = std::max(1u, surface->height >> level); numSlices = 1; break; case GX2SurfaceDim::Texture3D: height = std::max(1u, surface->height >> level); numSlices = std::max(1u, surface->depth >> level); break; case GX2SurfaceDim::TextureCube: height = std::max(1u, surface->height >> level); numSlices = std::max(6u, surface->depth); break; case GX2SurfaceDim::Texture1DArray: height = 1; numSlices = surface->depth; break; case GX2SurfaceDim::Texture2DArray: height = std::max(1u, surface->height >> level); numSlices = surface->depth; break; case GX2SurfaceDim::Texture2DMSAA: height = std::max(1u, surface->height >> level); numSlices = 1; break; case GX2SurfaceDim::Texture2DMSAAArray: height = std::max(1u, surface->height >> level); numSlices = surface->depth; break; } std::memset(output, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT)); output->size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT); if (surface->tileMode == GX2TileMode::LinearSpecial) { auto numSamples = 1 << surface->aa; auto elemSize = 1u; if (hwFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && hwFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { elemSize = 4; } output->bpp = GX2GetSurfaceFormatBitsPerElement(surface->format); output->pixelBits = output->bpp; output->baseAlign = 1; output->pitchAlign = 1; output->heightAlign = 1; output->depthAlign = 1; width = align_up(width, elemSize); height = align_up(height, elemSize); output->depth = numSlices; output->pitch = std::max(1u, width / elemSize); output->height = std::max(1u, height / elemSize); output->pixelPitch = width; output->pixelHeight = height; output->surfSize = static_cast(output->height) * output->pitch * numSamples * output->depth * (output->bpp / 8); if (surface->dim == GX2SurfaceDim::Texture3D) { output->sliceSize = static_cast(output->surfSize); } else { output->sliceSize = static_cast(output->surfSize / output->depth); } output->pitchTileMax = (output->pitch / 8) - 1; output->heightTileMax = (output->height / 8) - 1; output->sliceTileMax = (output->height * output->pitch / 64) - 1; result = ADDR_OK; } else { ADDR_COMPUTE_SURFACE_INFO_INPUT input; memset(&input, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT)); input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT); input.tileMode = static_cast(surface->tileMode & 0xF); input.format = static_cast(hwFormat); input.bpp = GX2GetSurfaceFormatBitsPerElement(surface->format); input.width = width; input.height = height; input.numSlices = numSlices; input.numSamples = 1 << surface->aa; input.numFrags = input.numSamples; input.slice = 0; input.mipLevel = level; if (surface->dim == GX2SurfaceDim::TextureCube) { input.flags.cube = 1; } if (surface->use & GX2SurfaceUse::DepthBuffer) { input.flags.depth = 1; } if (surface->use & GX2SurfaceUse::ScanBuffer) { input.flags.display = 1; } if (surface->dim == GX2SurfaceDim::Texture3D) { input.flags.volume = 1; } input.flags.inputBaseMap = (level == 0); result = AddrComputeSurfaceInfo(gpu::getAddrLibHandle(), &input, output); } return (result == ADDR_OK); } bool copySurface(GX2Surface *surfaceSrc, uint32_t srcLevel, uint32_t srcSlice, GX2Surface *surfaceDst, uint32_t dstLevel, uint32_t dstSlice, uint8_t *dstImage, uint8_t *dstMipmap) { ADDR_COMPUTE_SURFACE_INFO_OUTPUT srcInfoOutput; ADDR_COMPUTE_SURFACE_INFO_OUTPUT dstInfoOutput; ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT srcAddrInput; ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT dstAddrInput; ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT srcSwizzleInput; ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT srcSwizzleOutput; ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT dstSwizzleInput; ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT dstSwizzleOutput; auto handle = gpu::getAddrLibHandle(); // Initialise addrlib input/output structures std::memset(&srcAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT)); std::memset(&dstAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT)); std::memset(&srcSwizzleInput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT)); std::memset(&srcSwizzleOutput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT)); std::memset(&dstSwizzleInput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT)); std::memset(&dstSwizzleOutput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT)); srcAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); dstAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); srcSwizzleInput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT); srcSwizzleOutput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT); dstSwizzleInput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT); dstSwizzleOutput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT); // Setup src auto bpp = GX2GetSurfaceFormatBitsPerElement(surfaceSrc->format); getSurfaceInfo(surfaceSrc, srcLevel, &srcInfoOutput); srcAddrInput.slice = srcSlice; srcAddrInput.sample = 0; srcAddrInput.bpp = bpp; srcAddrInput.pitch = srcInfoOutput.pitch; srcAddrInput.height = srcInfoOutput.height; srcAddrInput.numSlices = std::max(1u, srcInfoOutput.depth); srcAddrInput.numSamples = 1 << surfaceSrc->aa; srcAddrInput.tileMode = static_cast(surfaceSrc->tileMode.value()); srcAddrInput.isDepth = !!(surfaceSrc->use & GX2SurfaceUse::DepthBuffer); srcAddrInput.tileBase = 0; srcAddrInput.compBits = 0; srcAddrInput.numFrags = 0; // Setup src swizzle srcSwizzleInput.base256b = (surfaceSrc->swizzle >> 8) & 0xFF; AddrExtractBankPipeSwizzle(handle, &srcSwizzleInput, &srcSwizzleOutput); srcAddrInput.bankSwizzle = srcSwizzleOutput.bankSwizzle; srcAddrInput.pipeSwizzle = srcSwizzleOutput.pipeSwizzle; // Setup dst getSurfaceInfo(surfaceDst, dstLevel, &dstInfoOutput); dstAddrInput.slice = dstSlice; dstAddrInput.sample = 0; dstAddrInput.bpp = bpp; dstAddrInput.pitch = dstInfoOutput.pitch; dstAddrInput.height = dstInfoOutput.height; dstAddrInput.numSlices = std::max(1u, dstInfoOutput.depth); dstAddrInput.numSamples = 1 << surfaceDst->aa; dstAddrInput.tileMode = dstInfoOutput.tileMode; dstAddrInput.isDepth = !!(surfaceDst->use & GX2SurfaceUse::DepthBuffer); dstAddrInput.tileBase = 0; dstAddrInput.compBits = 0; dstAddrInput.numFrags = 0; // Setup dst swizzle dstSwizzleInput.base256b = (surfaceDst->swizzle >> 8) & 0xFF; AddrExtractBankPipeSwizzle(handle, &dstSwizzleInput, &dstSwizzleOutput); dstAddrInput.bankSwizzle = dstSwizzleOutput.bankSwizzle; dstAddrInput.pipeSwizzle = dstSwizzleOutput.pipeSwizzle; // Setup width auto srcWidth = std::max(1u, surfaceSrc->width >> srcLevel); auto srcHeight = std::max(1u, surfaceSrc->height >> srcLevel); auto hwFormatSrc = static_cast(surfaceSrc->format & 0x3F); if (hwFormatSrc >= latte::SQ_DATA_FORMAT::FMT_BC1 && hwFormatSrc <= latte::SQ_DATA_FORMAT::FMT_BC5) { srcWidth = (srcWidth + 3) / 4; srcHeight = (srcHeight + 3) / 4; } auto dstWidth = std::max(1u, surfaceDst->width >> dstLevel); auto dstHeight = std::max(1u, surfaceDst->height >> dstLevel); auto hwFormatDst = static_cast(surfaceDst->format & 0x3F); if (hwFormatDst >= latte::SQ_DATA_FORMAT::FMT_BC1 && hwFormatDst <= latte::SQ_DATA_FORMAT::FMT_BC5) { dstWidth = (dstWidth + 3) / 4; dstHeight = (dstHeight + 3) / 4; } uint8_t *srcBasePtr = nullptr; uint8_t *dstBasePtr = nullptr; if (srcLevel == 0) { srcBasePtr = surfaceSrc->image.get(); } else if (srcLevel == 1) { srcBasePtr = surfaceSrc->mipmaps.get(); } else { srcBasePtr = surfaceSrc->mipmaps.get() + surfaceSrc->mipLevelOffset[srcLevel - 1]; } if (dstLevel == 0) { dstBasePtr = dstImage ? dstImage : surfaceDst->image.get(); } else if (dstLevel == 1) { dstBasePtr = dstMipmap ? dstMipmap : surfaceDst->mipmaps.get(); } else { dstBasePtr = dstMipmap ? dstMipmap : surfaceDst->mipmaps.get(); dstBasePtr += surfaceDst->mipLevelOffset[dstLevel - 1]; } // LinearSpecial is a special mode available through GX2 which forces that the // tiling mode be linear (possibly unaligned). Because this also signals the // surface info calculation to pick its own tiling, we need to handle this // specially here... Note that while ADDR_TM_LINEAR_GENERAL is a valid AMD // specified mode, it is not actually normally supported by the R600 hardware // which is the reason for LinearSpecial (which forces SW handling in GX2). if (surfaceSrc->tileMode == GX2TileMode::LinearSpecial) { srcAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL; } if (surfaceDst->tileMode == GX2TileMode::LinearSpecial) { dstAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL; } return gpu::copySurfacePixels( dstBasePtr, dstWidth, dstHeight, dstAddrInput, srcBasePtr, srcWidth, srcHeight, srcAddrInput); } uint32_t getSurfaceSliceSwizzle(GX2TileMode tileMode, uint32_t baseSwizzle, uint32_t slice) { ADDR_COMPUTE_SLICESWIZZLE_INPUT input; ADDR_COMPUTE_SLICESWIZZLE_OUTPUT output; auto tileSwizzle = uint32_t { 0 }; std::memset(&input, 0, sizeof(ADDR_COMPUTE_SLICESWIZZLE_INPUT)); std::memset(&output, 0, sizeof(ADDR_COMPUTE_SLICESWIZZLE_OUTPUT)); input.size = sizeof(ADDR_COMPUTE_SLICESWIZZLE_INPUT); output.size = sizeof(ADDR_COMPUTE_SLICESWIZZLE_OUTPUT); if (tileMode >= GX2TileMode::Tiled2DThin1 && tileMode != GX2TileMode::LinearSpecial) { input.tileMode = static_cast(tileMode); input.baseSwizzle = baseSwizzle; input.slice = slice; input.baseAddr = 0; auto handle = gpu::getAddrLibHandle(); AddrComputeSliceSwizzle(handle, &input, &output); tileSwizzle = output.tileSwizzle; } return tileSwizzle; } uint32_t calcSliceSize(GX2Surface *surface, ADDR_COMPUTE_SURFACE_INFO_OUTPUT *info) { return info->pitch * info->height * (1 << surface->aa) * (info->bpp / 8); } } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_addrlib.h ================================================ #pragma once #include "gx2_enum.h" #include #include #include #include namespace cafe::gx2 { struct GX2Surface; namespace internal { bool getSurfaceInfo(GX2Surface *surface, uint32_t level, ADDR_COMPUTE_SURFACE_INFO_OUTPUT *output); bool copySurface(GX2Surface *surfaceSrc, uint32_t srcLevel, uint32_t srcSlice, GX2Surface *surfaceDst, uint32_t dstLevel, uint32_t dstSlice, uint8_t *dstImage = nullptr, uint8_t *dstMipmap = nullptr); uint32_t getSurfaceSliceSwizzle(GX2TileMode tileMode, uint32_t baseSwizzle, uint32_t slice); uint32_t calcSliceSize(GX2Surface *surface, ADDR_COMPUTE_SURFACE_INFO_OUTPUT *info); } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_aperture.cpp ================================================ #include "gx2.h" #include "gx2_aperture.h" #include "gx2_format.h" #include "gx2_surface.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include "cafe/libraries/tcl/tcl_aperture.h" #include #include namespace cafe::gx2 { using namespace cafe::coreinit; using namespace cafe::tcl; struct ApertureInfo { gpu7::tiling::RetileInfo retileInfo; be2_val tiledAddress; be2_val tiledSize; be2_val untiledAddress; be2_val untiledSize; be2_val slice; be2_val numSlices; }; struct StaticApertureData { be2_array apertureInfo; }; static virt_ptr sApertureData = nullptr; void GX2AllocateTilingApertureEx(virt_ptr surface, uint32_t level, uint32_t depth, GX2EndianSwapMode endian, virt_ptr outHandle, virt_ptr outAddress) { auto surfaceDescription = gpu7::tiling::SurfaceDescription{ }; surfaceDescription.tileMode = static_cast(surface->tileMode); surfaceDescription.format = static_cast(surface->format & 0x3f); surfaceDescription.bpp = GX2GetSurfaceFormatBitsPerElement(surface->format) / 8; surfaceDescription.numSlices = surface->depth; surfaceDescription.numSamples = 1 << surface->aa; surfaceDescription.numFrags = 1 << surface->aa; surfaceDescription.numLevels = surface->mipLevels; surfaceDescription.pipeSwizzle = (surface->swizzle >> 8) & 1; surfaceDescription.bankSwizzle = (surface->swizzle >> 9) & 3; surfaceDescription.width = surface->width; surfaceDescription.height = surface->height; surfaceDescription.use = static_cast(surface->use); surfaceDescription.dim = static_cast(surface->dim); if (GX2SurfaceIsCompressed(surface->format)) { surfaceDescription.width = (surfaceDescription.width + 3) / 4; surfaceDescription.height = (surfaceDescription.height + 3) / 4; } auto surfaceInfo = gpu7::tiling::computeSurfaceInfo(surfaceDescription, level); auto addr = virt_addr { 0 }; if (level == 0) { addr = virt_cast(surface->image); } else if (level == 1) { addr = virt_cast(surface->mipmaps); } else if (level > 1) { addr = virt_cast(surface->mipmaps) + surface->mipLevelOffset[level - 1]; } // TODO: (addr + surfaceInfo.sliceSize * depth) is not actually correct for // all tile modes. if (TCLAllocTilingAperture(OSEffectiveToPhysical(addr + surfaceInfo.sliceSize * depth), surfaceInfo.pitch, surfaceInfo.height, surfaceInfo.bpp, surfaceInfo.tileMode, endian, outHandle, outAddress) == TCLStatus::OK) { auto &info = sApertureData->apertureInfo[*outHandle]; info.retileInfo = gpu7::tiling::computeRetileInfo(surfaceInfo); info.tiledAddress = addr; info.tiledSize = surfaceInfo.sliceSize; info.untiledAddress = *outAddress; info.untiledSize = surfaceInfo.sliceSize; info.slice = depth; info.numSlices = 1u; gpu7::tiling::cpu::untile(info.retileInfo, virt_cast(info.untiledAddress).get(), virt_cast(info.tiledAddress).get(), info.slice, info.numSlices); } } void GX2FreeTilingAperture(GX2ApertureHandle handle) { auto &info = sApertureData->apertureInfo[handle]; if (info.tiledAddress && info.untiledAddress) { gpu7::tiling::cpu::tile(info.retileInfo, virt_cast(info.untiledAddress).get(), virt_cast(info.tiledAddress).get(), info.slice, info.numSlices); } TCLFreeTilingAperture(handle); info.tiledAddress = virt_addr { 0u }; info.tiledSize = 0u; info.untiledAddress = virt_addr { 0u }; info.untiledSize = 0u; } namespace internal { bool translateAperture(virt_addr &address, uint32_t &size) { for (auto i = 0u; i < sApertureData->apertureInfo.size(); ++i) { auto &info = sApertureData->apertureInfo[i]; if (address >= info.untiledAddress && address < info.untiledAddress + info.untiledSize) { address = info.tiledAddress; size = info.tiledSize; return true; } } return false; } } // namespace internal void Library::registerApertureSymbols() { RegisterFunctionExport(GX2AllocateTilingApertureEx); RegisterFunctionExport(GX2FreeTilingAperture); RegisterDataInternal(sApertureData); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_aperture.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2_aperture Tiling Aperture * \ingroup gx2 * @{ */ struct GX2Surface; using GX2ApertureHandle = uint32_t; void GX2AllocateTilingApertureEx(virt_ptr surface, uint32_t level, uint32_t depth, GX2EndianSwapMode endian, virt_ptr outHandle, virt_ptr outAddress); void GX2FreeTilingAperture(GX2ApertureHandle handle); namespace internal { bool translateAperture(virt_addr &address, uint32_t &size); } // namespace internal /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_cbpool.cpp ================================================ #include "gx2.h" #include "gx2_debugcapture.h" #include "gx2_displaylist.h" #include "gx2_event.h" #include "gx2_cbpool.h" #include "gx2_internal_pm4cap.h" #include "gx2_state.h" #include "gx2_query.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include "cafe/libraries/tcl/tcl_ring.h" #include #include #include #include namespace cafe::gx2 { constexpr auto MinimumCommandBufferNumWords = uint32_t { 0x100 }; constexpr auto MaximumCommandBufferNumWords = uint32_t { 0x20000 }; using namespace cafe::coreinit; using namespace cafe::tcl; struct StaticCbPoolData { be2_struct mainCoreCommandBuffer; be2_array activeCommandBuffer; be2_val profilingWasEnabledBeforeUserDisplayList; be2_val gpuLastReadCommandBuffer; be2_val lastSubmittedTimestamp; }; static virt_ptr sCbPoolData = nullptr; GX2Timestamp GX2GetRetiredTimeStamp() { auto timestamp = StackObject { }; TCLReadTimestamp(TCLTimestampID::CPRetired, timestamp); return *timestamp; } GX2Timestamp GX2GetLastSubmittedTimeStamp() { return sCbPoolData->lastSubmittedTimestamp; } /** * Submit a user timestamp to the GPU. */ void GX2SubmitUserTimeStamp(virt_ptr dst, GX2Timestamp timestamp, GX2PipeEvent type, BOOL triggerInterrupt) { using namespace latte; using namespace latte::pm4; auto addr = OSEffectiveToPhysical(virt_cast(dst)); auto dataLo = static_cast(timestamp & 0xFFFFFFFFu); auto dataHi = static_cast(timestamp >> 32); switch (type) { case GX2PipeEvent::Top: { internal::writePM4(MemWrite { MW_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN32), MW_ADDR_HI::get(0) .CNTR_SEL(MW_WRITE_DATA), dataLo, dataHi }); if (triggerInterrupt) { internal::writeType0(Register::CP_INT_STATUS, CP_INT_STATUS::get(0) .IB1_INT_STAT(true) .value); } break; } case GX2PipeEvent::Bottom: case GX2PipeEvent::BottomAfterFlush: { internal::writePM4(EventWriteEOP { VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(type == GX2PipeEvent::BottomAfterFlush ? VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_TS_EVENT : VGT_EVENT_TYPE::BOTTOM_OF_PIPE_TS) .EVENT_INDEX(VGT_EVENT_INDEX::TS), EW_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN32), EWP_ADDR_HI::get(0) .DATA_SEL(EWP_DATA_64) .INT_SEL(triggerInterrupt ? EWP_INT_SEL::EWP_INT_WRITE_CONFIRM : EWP_INT_SEL::EWP_INT_NONE), dataLo, dataHi }); break; } default: decaf_abort(fmt::format("Unexpected GX2SubmitUserTimestamp type {}", type)); } } /** * Wait for retired timestamp. */ BOOL GX2WaitTimeStamp(GX2Timestamp timestamp) { auto timeoutTicks = coreinit::internal::msToTicks(GX2GetGPUTimeout()); if (TCLWaitTimestamp(TCLTimestampID::CPRetired, timestamp, timeoutTicks) == TCLStatus::OK) { return TRUE; } // TODO: Set GPU hang state return FALSE; } namespace internal { void flushCommandBuffer(uint32_t requiredNumWords, BOOL a2); void allocateCommandBuffer(uint32_t requiredNumWords); void padCommandBuffer(virt_ptr cb); /** * Get the active command buffer for the current core. */ virt_ptr getActiveCommandBuffer() { return virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]); } /** * Get an active command buffer with space to write numWords. */ virt_ptr getWriteCommandBuffer(uint32_t numWords) { auto cb = getActiveCommandBuffer(); if (cb->bufferPosWords + numWords > cb->bufferSizeWords) { flushCommandBuffer(numWords, TRUE); } cb->writeGatherPtr = cb->buffer + cb->bufferPosWords; cb->bufferPosWords += numWords; cb->cmdSizeTarget = numWords; cb->cmdSize = 0u; return cb; } /** * Initialise command buffer pool. */ void initialiseCommandBufferPool(virt_ptr base, uint32_t size) { auto &mainCoreCb = sCbPoolData->mainCoreCommandBuffer; mainCoreCb.isUserBuffer = FALSE; mainCoreCb.cbPoolBase = virt_cast(base); mainCoreCb.cbPoolNumWords = size / 4; mainCoreCb.buffer = virt_cast(base); mainCoreCb.bufferPosWords = 0u; sCbPoolData->activeCommandBuffer[getMainCoreId()] = mainCoreCb; sCbPoolData->gpuLastReadCommandBuffer = virt_cast(mainCoreCb.cbPoolBase + mainCoreCb.cbPoolNumWords); // Allocate initial command buffer allocateCommandBuffer(MinimumCommandBufferNumWords); } /** * Initialise a command buffer. */ void initialiseCommandBuffer(virt_ptr cb, virt_ptr buffer, uint32_t bufferSizeWords, BOOL isUserBuffer) { // Normally this would be set to the write gather address, but we are faking // write gathering...! cb->writeGatherPtr = buffer; cb->isUserBuffer = isUserBuffer; cb->buffer = buffer; cb->bufferPosWords = 0u; cb->bufferSizeWords = bufferSizeWords; cb->cmdSize = 0u; cb->cmdSizeTarget = 0u; } /** * Allocate a command buffer with space for requiredNumWords. */ void allocateCommandBuffer(uint32_t requiredNumWords) { auto coreId = cpu::this_core::id(); auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]); decaf_check(coreId == getMainCoreId()); decaf_check(requiredNumWords < MaximumCommandBufferNumWords); requiredNumWords = std::max(requiredNumWords, MinimumCommandBufferNumWords); // Ring buffer between write pointer and read pointer auto writePosition = virt_cast(cb->buffer + cb->bufferPosWords); auto readPosition = virt_addr { sCbPoolData->gpuLastReadCommandBuffer }; auto ringBufferStart = virt_cast(cb->cbPoolBase); auto ringBufferEnd = virt_cast(cb->cbPoolBase + cb->cbPoolNumWords); auto freeBytes = ptrdiff_t { 0 }; auto requiredBytes = requiredNumWords * 4; #ifdef GX2_ENABLE_CBPOOL_TIMEOUT auto allocateStartTime = OSGetSystemTime(); auto gpuTimeoutTicks = coreinit::internal::msToTicks(GX2GetGPUTimeout()); #endif while (freeBytes < requiredBytes) { auto retiredTimestamp = GX2GetRetiredTimeStamp(); if (writePosition < readPosition) { freeBytes = readPosition - writePosition; } else { // When writePosition == readPosition, we check the retired and // submitted timestamp to decide if the ringbuffer is empty or full. if (writePosition == readPosition && GX2GetLastSubmittedTimeStamp() < retiredTimestamp) { // Ringbuffer is full freeBytes = 0; } else if (ringBufferEnd - writePosition >= requiredBytes) { freeBytes = ringBufferEnd - writePosition; } else { // Not enough space at end of the ringbuffer, try from the start instead freeBytes = readPosition - ringBufferStart; writePosition = ringBufferStart; } } if (freeBytes < requiredBytes) { #ifdef GX2_ENABLE_CBPOOL_TIMEOUT // Check if we have hit timeout waiting for gpu if (OSGetSystemTime() - allocateStartTime >= gpuTimeoutTicks) { // TODO: Handle GPU timeout break; } #endif // Wait for next retired buffer to try get some more free pool space GX2WaitTimeStamp(retiredTimestamp + 1); } } decaf_check(freeBytes >= requiredBytes); auto allocNumWords = std::min(MaximumCommandBufferNumWords, static_cast(freeBytes / 4)); initialiseCommandBuffer(cb, virt_cast(writePosition), allocNumWords, FALSE); } /** * Begin a user command buffer (aka a display list). */ void beginUserCommandBuffer(virt_ptr displayList, uint32_t bytes, BOOL profilingEnabled) { auto coreId = cpu::this_core::id(); auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]); decaf_check(!cb->isUserBuffer); if (coreId == getMainCoreId()) { padCommandBuffer(cb); sCbPoolData->mainCoreCommandBuffer = *cb; sCbPoolData->profilingWasEnabledBeforeUserDisplayList = getProfilingEnabled(); } setProfilingEnabled(profilingEnabled); initialiseCommandBuffer(cb, displayList, bytes / 4, TRUE); } /** * End a user command buffer. */ uint32_t endUserCommandBuffer(virt_ptr displayList) { auto coreId = cpu::this_core::id(); auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]); decaf_check(cb->isUserBuffer); // Pad the command buffer so it is ready for submission padCommandBuffer(cb); auto bufferSize = cb->bufferPosWords * 4; if (coreId == getMainCoreId()) { // Restore the main command buffer *cb = sCbPoolData->mainCoreCommandBuffer; setProfilingEnabled(sCbPoolData->profilingWasEnabledBeforeUserDisplayList); } else { // Clear the user display list cb->isUserBuffer = FALSE; cb->bufferPosWords = 0u; cb->bufferSizeWords = 0u; cb->buffer = nullptr; } return bufferSize; } /** * Pad a command buffer with NOPs to align to 8 words. */ void padCommandBuffer(virt_ptr cb) { auto alignedBufferPos = align_up(cb->bufferPosWords, 8); if (alignedBufferPos != cb->bufferPosWords) { auto padNumWords = alignedBufferPos - cb->bufferPosWords; cb = getWriteCommandBuffer(padNumWords); for (auto i = 0u; i < padNumWords; ++i) { *cb->writeGatherPtr = 0x80000000u; } } } /** * Queue a command buffer in the gpu ring buffer. */ void queueCommandBuffer(virt_ptr cbBase, uint32_t cbSize, virt_ptr gpuLastReadPointer, BOOL writeConfirmTimestamp) { auto submitCommand = StackArray { }; auto submitCommandNumWords = uint32_t { 0u }; decaf_check(cbBase); decaf_check(cbSize); auto indirectBufferCall = latte::pm4::IndirectBufferCallPriv { OSEffectiveToPhysical(virt_cast(cbBase)), cbSize }; writePM4(submitCommand, submitCommandNumWords, indirectBufferCall); if (gpuLastReadPointer) { auto memWrite = latte::pm4::MemWrite { latte::pm4::MW_ADDR_LO::get(0) .ADDR_LO(OSEffectiveToPhysical(virt_cast(gpuLastReadPointer)) >> 2) .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN32), latte::pm4::MW_ADDR_HI::get(0) .CNTR_SEL(latte::pm4::MW_WRITE_DATA) .DATA32(true), static_cast(virt_cast(cbBase + cbSize)), 0u }; writePM4(submitCommand, submitCommandNumWords, memWrite); } if (getProfileMode() & GX2ProfileMode::SkipExecuteCommandBuffers) { // Change IndirectBufferCallPriv to a NOP submitCommand[0] = latte::pm4::HeaderType3::get(0) .type(latte::pm4::PacketType::Type3) .opcode(latte::pm4::IT_OPCODE::NOP) .size(2) .value; } // TODO: Call pm4 capture flush command buffer auto submitFlags = StackObject { }; *submitFlags = TCLSubmitFlags::UpdateTimestamp; if (!writeConfirmTimestamp) { *submitFlags |= TCLSubmitFlags::NoWriteConfirmTimestamp; } captureCommandBuffer(submitCommand, submitCommandNumWords); if (!debugCaptureEnabled()) { TCLSubmitToRing(submitCommand, submitCommandNumWords, submitFlags, virt_addrof(sCbPoolData->lastSubmittedTimestamp)); } else { debugCaptureSubmit(submitCommand, submitCommandNumWords, submitFlags, virt_addrof(sCbPoolData->lastSubmittedTimestamp)); } } /** * Flush the currently active command buffer. */ void flushCommandBuffer(uint32_t requiredNumWords, BOOL writeConfirmTimestamp) { auto coreId = cpu::this_core::id(); auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]); if (cb->isUserBuffer) { auto usedSize = GX2EndDisplayList(cb->buffer); auto newSize = 4 * requiredNumWords; auto newDisplayList = displayListOverrun(cb->buffer, usedSize, newSize); GX2BeginDisplayListEx(newDisplayList.first, newDisplayList.second, getProfilingEnabled()); } else { decaf_check(coreId == getMainCoreId()); if (cb->bufferPosWords != 0) { padCommandBuffer(cb); queueCommandBuffer(cb->buffer, cb->bufferPosWords, virt_addrof(sCbPoolData->gpuLastReadCommandBuffer), writeConfirmTimestamp); } allocateCommandBuffer(requiredNumWords); decaf_check(requiredNumWords <= cb->bufferSizeWords - cb->bufferPosWords); } } /** * Inform debug capture about the pointers that we send over PM4 for commands * such as EVENT_WRITE_EOP. */ void debugCaptureCbPoolPointers() { debugCaptureAlloc(virt_addrof(sCbPoolData->gpuLastReadCommandBuffer), sizeof(sCbPoolData->gpuLastReadCommandBuffer), 8); debugCaptureAlloc(virt_addrof(sCbPoolData->lastSubmittedTimestamp), sizeof(sCbPoolData->lastSubmittedTimestamp), 8); debugCaptureInvalidate(virt_addrof(sCbPoolData->gpuLastReadCommandBuffer), sizeof(sCbPoolData->gpuLastReadCommandBuffer)); debugCaptureInvalidate(virt_addrof(sCbPoolData->lastSubmittedTimestamp), sizeof(sCbPoolData->lastSubmittedTimestamp)); } void debugCaptureCbPoolPointersFree() { debugCaptureFree(virt_addrof(sCbPoolData->gpuLastReadCommandBuffer)); debugCaptureFree(virt_addrof(sCbPoolData->lastSubmittedTimestamp)); } } // namespace internal void Library::registerCbPoolSymbols() { RegisterFunctionExport(GX2GetLastSubmittedTimeStamp); RegisterFunctionExport(GX2GetRetiredTimeStamp); RegisterFunctionExport(GX2SubmitUserTimeStamp); RegisterFunctionExport(GX2WaitTimeStamp); RegisterDataInternal(sCbPoolData); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_cbpool.h ================================================ #pragma once #include "gx2_enum.h" #include "gx2_internal_writegatherptr.h" #include #include #include namespace cafe::gx2 { using GX2Timestamp = uint64_t; GX2Timestamp GX2GetRetiredTimeStamp(); GX2Timestamp GX2GetLastSubmittedTimeStamp(); void GX2SubmitUserTimeStamp(virt_ptr dst, GX2Timestamp timestamp, GX2PipeEvent type, BOOL triggerInterrupt); BOOL GX2WaitTimeStamp(GX2Timestamp time); namespace internal { struct ActiveCommandBuffer { //! Write gather address for writing to buffer. be2_write_gather_ptr writeGatherPtr; //! Buffer for commands. be2_virt_ptr buffer; //! Write position of buffer in words. be2_val bufferPosWords; //! Size of buffer in words. be2_val bufferSizeWords; //! Size of the current writing command. be2_val cmdSize; //! Maximum size of the current writing command. be2_val cmdSizeTarget; be2_val isUserBuffer; be2_virt_ptr cbPoolBase; be2_val cbPoolNumWords; be2_val cbPoolNumCommandBuffers; UNKNOWN(0x4); }; void initialiseCommandBufferPool(virt_ptr base, uint32_t size); virt_ptr getActiveCommandBuffer(); virt_ptr getWriteCommandBuffer(uint32_t numWords); void beginUserCommandBuffer(virt_ptr displayList, uint32_t bytes, BOOL profilingEnabled); uint32_t endUserCommandBuffer(virt_ptr displayList); void flushCommandBuffer(uint32_t requiredNumWords, BOOL writeConfirmTimestamp); void queueCommandBuffer(virt_ptr cbBase, uint32_t cbSize, virt_ptr gpuLastReadPointer, BOOL writeConfirmTimestamp); void debugCaptureCbPoolPointers(); void debugCaptureCbPoolPointersFree(); /** * Write the pm4 serialiser to the given buffer */ template inline void writePM4(virt_ptr buffer, uint32_t &bufferPosWords, const Type &command) { // Remove const for the .serialise function auto &cmd = const_cast(command); // Calculate the total size this object will be latte::pm4::PacketSizer sizer; cmd.serialise(sizer); auto totalSize = sizer.getSize() + 1; // Serialize the packet to the given buffer auto writer = latte::pm4::PacketWriter { buffer.getRawPointer(), bufferPosWords, Type::Opcode, totalSize }; cmd.serialise(writer); } /** * Write a PM4 command to the active command buffer for the current core. */ template inline void writePM4(const Type &command) { // Remove const for the .serialise function. auto &cmd = const_cast(command); // Calculate the total size this command will be. latte::pm4::PacketSizer sizer; cmd.serialise(sizer); // Allocate a command buffer. auto totalSize = sizer.getSize() + 1; auto cmdSize = uint32_t { 0 }; auto cb = getWriteCommandBuffer(totalSize); // Serialize the packet to the command buffer. auto writer = latte::pm4::PacketWriter { cb->writeGatherPtr.get().getRawPointer(), cmdSize, Type::Opcode, totalSize }; cmd.serialise(writer); cb->cmdSize = cmdSize; // Verify the size was as expected. decaf_check(cb->cmdSize == cb->cmdSizeTarget); } /** * Write a PM4 command to the provided command buffer. */ template void writePM4(uint32_t *buffer, uint32_t &bufferPosWords, const Type &command) { // Remove const for the .serialise function auto &cmd = const_cast(command); // Calculate the total size this object will be latte::pm4::PacketSizer sizer; cmd.serialise(sizer); auto totalSize = sizer.getSize() + 1; // Serialize the packet to the given buffer auto writer = latte::pm4::PacketWriter { buffer, bufferPosWords, Type::Opcode, totalSize }; cmd.serialise(writer); } inline void writeType0(latte::Register baseIndex, gsl::span values) { auto numValues = static_cast(values.size()); auto cb = getWriteCommandBuffer(numValues + 1); auto header = latte::pm4::HeaderType0::get(0) .type(latte::pm4::PacketType::Type0) .baseIndex(baseIndex / 4) .count(numValues - 1); // Write data to command buffer *cb->writeGatherPtr = header.value; for (auto value : values) { *cb->writeGatherPtr = value; } // Verify the size was as expected. cb->cmdSize = numValues + 1; decaf_check(cb->cmdSize == cb->cmdSizeTarget); } inline void writeType0(latte::Register id, uint32_t value) { writeType0(id, { &value, 1 }); } } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_clear.cpp ================================================ #include "gx2.h" #include "gx2_clear.h" #include "gx2_cbpool.h" #include "gx2_debugcapture.h" #include "gx2_surface.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include namespace cafe::gx2 { using namespace cafe::coreinit; void GX2ClearColor(virt_ptr colorBuffer, float red, float green, float blue, float alpha) { internal::debugCaptureTagGroup(GX2DebugTag::ClearColor, "{}, {:.2f}, {:.2f}, {:.2f}, {:.2f}", colorBuffer, red, green, blue, alpha); auto address = OSEffectiveToPhysical(virt_cast(colorBuffer->surface.image)); auto cb_color_frag = latte::CB_COLORN_FRAG::get(0); auto cb_color_base = latte::CB_COLORN_BASE::get(0) .BASE_256B(address >> 8); if (colorBuffer->surface.aa != 0) { auto aaAddress = OSEffectiveToPhysical(virt_cast(colorBuffer->aaBuffer)); cb_color_frag = cb_color_frag .BASE_256B(aaAddress >> 8); } GX2InitColorBufferRegs(colorBuffer); internal::writePM4(latte::pm4::DecafClearColor { red, green, blue, alpha, cb_color_base, cb_color_frag, colorBuffer->regs.cb_color_size, colorBuffer->regs.cb_color_info, colorBuffer->regs.cb_color_view, colorBuffer->regs.cb_color_mask }); internal::debugCaptureTagGroup(GX2DebugTag::ClearColor, "{}, {:.2f}, {:.2f}, {:.2f}, {:.2f}", colorBuffer, red, green, blue, alpha); } void DecafClearDepthStencil(virt_ptr depthBuffer, GX2ClearFlags clearFlags) { auto addrImage = OSEffectiveToPhysical(virt_cast(depthBuffer->surface.image)); auto addrHiZ = OSEffectiveToPhysical(virt_cast(depthBuffer->hiZPtr)); auto db_depth_base = latte::DB_DEPTH_BASE::get(0) .BASE_256B(addrImage >> 8); auto db_depth_htile_data_base = latte::DB_DEPTH_HTILE_DATA_BASE::get(0) .BASE_256B(addrHiZ >> 8); GX2InitDepthBufferRegs(depthBuffer); internal::writePM4(latte::pm4::DecafClearDepthStencil { clearFlags, db_depth_base, db_depth_htile_data_base, depthBuffer->regs.db_depth_info, depthBuffer->regs.db_depth_size, depthBuffer->regs.db_depth_view, }); } void GX2ClearDepthStencilEx(virt_ptr depthBuffer, float depth, uint8_t stencil, GX2ClearFlags clearFlags) { internal::debugCaptureTagGroup(GX2DebugTag::ClearDepthStencil, "{}, {:.2f}, {}, {}", depthBuffer, depth, stencil, clearFlags); uint32_t values[] = { stencil, bit_cast(depth) }; internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_STENCIL_CLEAR, gsl::make_span(values) }); DecafClearDepthStencil(depthBuffer, clearFlags); internal::debugCaptureTagGroup(GX2DebugTag::ClearDepthStencil, "{}, {:.2f}, {}, {}", depthBuffer, depth, stencil, clearFlags); } void GX2ClearBuffersEx(virt_ptr colorBuffer, virt_ptr depthBuffer, float red, float green, float blue, float alpha, float depth, uint8_t stencil, GX2ClearFlags clearFlags) { internal::debugCaptureTagGroup( GX2DebugTag::ClearBuffers, "{}, {}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {}, {}", colorBuffer, depthBuffer, red, green, blue, alpha, depth, stencil, clearFlags); GX2ClearColor(colorBuffer, red, green, blue, alpha); GX2ClearDepthStencilEx(depthBuffer, depth, stencil, clearFlags); internal::debugCaptureTagGroup( GX2DebugTag::ClearBuffers, "{}, {}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {}, {}", colorBuffer, depthBuffer, red, green, blue, alpha, depth, stencil, clearFlags); } void GX2SetClearDepth(virt_ptr depthBuffer, float depth) { depthBuffer->depthClear = depth; internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_DEPTH_CLEAR, bit_cast(depth) }); } void GX2SetClearStencil(virt_ptr depthBuffer, uint8_t stencil) { depthBuffer->stencilClear = stencil; internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_STENCIL_CLEAR, stencil }); } void GX2SetClearDepthStencil(virt_ptr depthBuffer, float depth, uint8_t stencil) { depthBuffer->depthClear = depth; depthBuffer->stencilClear = stencil; uint32_t values[] = { stencil, bit_cast(depth) }; internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_STENCIL_CLEAR, gsl::make_span(values) }); } void Library::registerClearSymbols() { RegisterFunctionExport(GX2ClearColor); RegisterFunctionExport(GX2ClearDepthStencilEx); RegisterFunctionExport(GX2ClearBuffersEx); RegisterFunctionExport(GX2SetClearDepth); RegisterFunctionExport(GX2SetClearStencil); RegisterFunctionExport(GX2SetClearDepthStencil); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_clear.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2 Clear Functions * \ingroup gx2 * @{ */ struct GX2ColorBuffer; struct GX2DepthBuffer; void GX2ClearColor(virt_ptr colorBuffer, float red, float green, float blue, float alpha); void GX2ClearDepthStencilEx(virt_ptr depthBuffer, float depth, uint8_t stencil, GX2ClearFlags clearMode); void GX2ClearBuffersEx(virt_ptr colorBuffer, virt_ptr depthBuffer, float red, float green, float blue, float alpha, float depth, uint8_t stencil, GX2ClearFlags clearMode); void GX2SetClearDepth(virt_ptr depthBuffer, float depth); void GX2SetClearStencil(virt_ptr depthBuffer, uint8_t stencil); void GX2SetClearDepthStencil(virt_ptr depthBuffer, float depth, uint8_t stencil); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_contextstate.cpp ================================================ #include "gx2.h" #include "gx2_contextstate.h" #include "gx2_debugcapture.h" #include "gx2_draw.h" #include "gx2_cbpool.h" #include "gx2_memory.h" #include "gx2_registers.h" #include "gx2_shaders.h" #include "gx2_state.h" #include "gx2_tessellation.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include namespace cafe::gx2 { using namespace cafe::coreinit; using namespace latte::pm4; static std::pair ConfigRegisterRange[] = { { 0x300, 6 }, { 0x900, 0x48 }, { 0x980, 0x48 }, { 0xA00, 0x48 }, { 0x310, 0xC }, { 0x542, 1 }, { 0x235, 1 }, { 0x232, 2 }, { 0x23A, 1 }, { 0x256, 1 }, { 0x60C, 1 }, { 0x5C5, 1 }, { 0x2C8, 1 }, { 0x363, 1 }, { 0x404, 2 }, }; static std::pair ContextRegisterRange[] = { { 0, 2 }, { 3, 3 }, { 0xA, 4 }, { 0x10, 0x38 }, { 0x50, 0x34 }, { 0x8E, 4 }, { 0x94, 0x40 }, { 0x100, 9 }, { 0x10C, 3 }, { 0x10F, 0x60 }, { 0x185, 0xA }, { 0x191, 0x27 }, { 0x1E0, 9 }, { 0x200, 1 }, { 0x202, 7 }, { 0xE0, 0x20 }, { 0x210, 0x29 }, { 0x250, 0x34 }, { 0x290, 1 }, { 0x292, 2 }, { 0x2A1, 1 }, { 0x2A5, 1 }, { 0x2A8, 2 }, { 0x2AC, 3 }, { 0x2CA, 1 }, { 0x2CC, 1 }, { 0x2CE, 1 }, { 0x300, 9 }, { 0x30C, 1 }, { 0x312, 1 }, { 0x316, 2 }, { 0x343, 2 }, { 0x349, 3 }, { 0x34C, 2 }, { 0x351, 1 }, { 0x37E, 6 }, { 0x2B4, 3 }, { 0x2B8, 3 }, { 0x2BC, 3 }, { 0x2C0, 3 }, { 0x2C8, 1 }, { 0x29B, 1 }, { 0x8C, 1 }, { 0xD5, 1 }, { 0x284, 0xC }, }; static std::pair AluConstRange[] = { { 0, 0x800 }, }; static std::pair LoopConstRange[] = { { 0, 0x60 }, }; static std::pair ResourceRange[] = { { 0, 0x70 }, { 0x380, 0x70 }, { 0x460, 0x70 }, { 0x7E0, 0x70 }, { 0x8B9, 7 }, { 0x8C0, 0x70 }, { 0x930, 0x70 }, { 0xCB0, 0x70 }, { 0xD89, 7 }, }; static std::pair SamplerRange[] = { { 0, 0x36 }, { 0x36, 0x36 }, { 0x6C, 0x36 }, }; static std::pair EmptyRange[] = { { 0, 0 }, }; static auto EmptyRangeSpan = gsl::make_span(EmptyRange); static void loadState(virt_ptr state, bool skipLoad) { internal::enableStateShadowing(); internal::writePM4(LoadConfigReg { OSEffectiveToPhysical(virt_cast(virt_addrof(state->shadowState.config))), skipLoad ? EmptyRangeSpan : gsl::make_span(ConfigRegisterRange) }); internal::writePM4(LoadContextReg { OSEffectiveToPhysical(virt_cast(virt_addrof(state->shadowState.context))), skipLoad ? EmptyRangeSpan : gsl::make_span(ContextRegisterRange) }); internal::writePM4(LoadAluConst { OSEffectiveToPhysical(virt_cast(virt_addrof(state->shadowState.alu))), skipLoad ? EmptyRangeSpan : gsl::make_span(AluConstRange) }); internal::writePM4(LoadLoopConst { OSEffectiveToPhysical(virt_cast(virt_addrof(state->shadowState.loop))), skipLoad ? EmptyRangeSpan : gsl::make_span(LoopConstRange) }); internal::writePM4(latte::pm4::LoadResource { OSEffectiveToPhysical(virt_cast(virt_addrof(state->shadowState.resource))), skipLoad ? EmptyRangeSpan : gsl::make_span(ResourceRange) }); internal::writePM4(LoadSampler { OSEffectiveToPhysical(virt_cast(virt_addrof(state->shadowState.sampler))), skipLoad ? EmptyRangeSpan : gsl::make_span(SamplerRange) }); if (!skipLoad) { GX2Invalidate(GX2InvalidateMode::Shader, nullptr, 0xFFFFFFFF); } } void GX2SetupContextStateEx(virt_ptr state, GX2ContextStateFlags flags) { // Create our internal shadow display list std::memset(state.get(), 0, sizeof(GX2ContextState)); state->profilingEnabled = (flags & GX2ContextStateFlags::ProfilingEnabled) ? TRUE : FALSE; internal::setProfilingEnabled(state->profilingEnabled); // Clear load state loadState(state, true); // Initialise default state internal::initialiseRegisters(); GX2SetDefaultState(); // Setup shadow display list if (!(flags & GX2ContextStateFlags::NoShadowDisplayList)) { GX2BeginDisplayListEx(virt_addrof(state->shadowDisplayList), GX2ContextState::MaxDisplayListSize * 4, state->profilingEnabled); loadState(state, false); state->shadowDisplayListSize = GX2EndDisplayList(virt_addrof(state->shadowDisplayList)); } } void GX2GetContextStateDisplayList(virt_ptr state, virt_ptr> outDisplayList, virt_ptr outSize) { if (outDisplayList) { *outDisplayList = virt_addrof(state->shadowDisplayList); } if (outSize) { *outSize = state->shadowDisplayListSize; } } void GX2SetContextState(virt_ptr state) { if (state) { if (!state->shadowDisplayListSize) { loadState(state, false); } else { GX2CallDisplayList(virt_addrof(state->shadowDisplayList), state->shadowDisplayListSize); } } else { internal::disableStateShadowing(); } } void GX2SetDefaultState() { internal::debugCaptureTagGroup(GX2DebugTag::SetDefaultState); GX2SetShaderModeEx(GX2ShaderMode::UniformRegister, // mode 48, // numVsGpr 64, // numVsStackEntries 0, // numGsGpr 0, // numGsStackEntries 200, // numPsGpr 192); // numPsStackEntries GX2SetDepthStencilControl(TRUE, // depthTest TRUE, // depthWrite GX2CompareFunction::Less, // depthCompare FALSE, // stencilTest FALSE, // backfaceStencil GX2CompareFunction::Always, // frontStencilFunc GX2StencilFunction::Replace, // frontStencilZPass GX2StencilFunction::Replace, // frontStencilZFail GX2StencilFunction::Replace, // frontStencilFail GX2CompareFunction::Always, // backStencilFunc GX2StencilFunction::Replace, // backStencilZPass GX2StencilFunction::Replace, // backStencilZFail GX2StencilFunction::Replace); // backStencilFail GX2SetPolygonControl(GX2FrontFace::CounterClockwise, // frontFace FALSE, // cullFront FALSE, // cullBack FALSE, // polyMode GX2PolygonMode::Triangle, // polyModeFront GX2PolygonMode::Triangle, // polyModeBack FALSE, // polyOffsetFrontEnable FALSE, // polyOffsetBackEnable FALSE); // polyOffsetParaEnable GX2SetStencilMask(0xff, // frontMask 0xff, // frontWriteMask 1, // frontRef 0xff, // backMask 0xff, // backWriteMask 1); // backRef GX2SetPolygonOffset(0.0f, // frontOffset 0.0f, // frontScale 0.0f, // backOffset 0.0f, // backScale 0.0f); // clamp GX2SetPointSize(1.0f, // width 1.0f); // height GX2SetPointLimits(1.0f, // min 1.0f); // max GX2SetLineWidth(1.0f); GX2SetPrimitiveRestartIndex(0xffffffffu); GX2SetAlphaTest(FALSE, // alphaTest GX2CompareFunction::Less, // func 0.0f); // ref GX2SetAlphaToMask(FALSE, // enable GX2AlphaToMaskMode::NonDithered); // mode GX2SetTargetChannelMasks(GX2ChannelMask::RGBA, GX2ChannelMask::RGBA, GX2ChannelMask::RGBA, GX2ChannelMask::RGBA, GX2ChannelMask::RGBA, GX2ChannelMask::RGBA, GX2ChannelMask::RGBA, GX2ChannelMask::RGBA); GX2SetColorControl(GX2LogicOp::Copy, // rop3 0, // targetBlendEnable FALSE, // multiWriteEnable TRUE); // colorWriteEnable for (auto i = 0u; i < 8u; ++i) { GX2SetBlendControl(static_cast(i), // target GX2BlendMode::SrcAlpha, // colorSrcBlend GX2BlendMode::InvSrcAlpha, // colorDstBlend GX2BlendCombineMode::Add, // colorCombine TRUE, // useAlphaBlend GX2BlendMode::SrcAlpha, // alphaSrcBlend GX2BlendMode::InvSrcAlpha, // alphaDstBlend GX2BlendCombineMode::Add); // alphaCombine } GX2SetBlendConstantColor(0.0f, 0.0f, 0.0f, 0.0f); // RGBA GX2SetStreamOutEnable(0); GX2SetRasterizerClipControl(TRUE, // rasteriser TRUE); // zclipEnable // TODO: Figure out what GX2PrimitiveMode 0x84 is GX2SetTessellation(GX2TessellationMode::Discrete, static_cast(0x84), GX2IndexType::U32); GX2SetMaxTessellationLevel(1.0f); GX2SetMinTessellationLevel(1.0f); internal::writePM4(SetContextReg { latte::Register::DB_RENDER_CONTROL, 0 }); internal::debugCaptureTagGroup(GX2DebugTag::SetDefaultState); } namespace internal { void initialiseRegisters() { std::array zeroes; zeroes.fill(0); uint32_t values28030_28034[] = { latte::PA_SC_SCREEN_SCISSOR_TL::get(0).value, latte::PA_SC_SCREEN_SCISSOR_BR::get(0) .BR_X(8192) .BR_Y(8192).value }; internal::writePM4(SetContextRegs { latte::Register::PA_SC_SCREEN_SCISSOR_TL, gsl::make_span(values28030_28034) }); internal::writePM4(SetContextReg { latte::Register::PA_SC_LINE_CNTL, latte::PA_SC_LINE_CNTL::get(0) .value }); internal::writePM4(SetContextReg { latte::Register::PA_SU_VTX_CNTL, latte::PA_SU_VTX_CNTL::get(0) .PIX_CENTER(latte::PA_SU_VTX_CNTL_PIX_CENTER::OGL) .ROUND_MODE(latte::PA_SU_VTX_CNTL_ROUND_MODE::TRUNCATE) .QUANT_MODE(latte::PA_SU_VTX_CNTL_QUANT_MODE::QUANT_1_256TH) .value }); // PA_CL_POINT_X_RAD, PA_CL_POINT_Y_RAD, PA_CL_POINT_POINT_SIZE, PA_CL_POINT_POINT_CULL_RAD internal::writePM4(SetContextRegs { latte::Register::PA_CL_POINT_X_RAD, gsl::make_span(zeroes.data(), 4) }); // PA_CL_UCP_0_X ... PA_CL_UCP_5_W internal::writePM4(SetContextRegs { latte::Register::PA_CL_UCP_0_X, gsl::make_span(zeroes.data(), 24) }); internal::writePM4(SetContextReg { latte::Register::PA_CL_VTE_CNTL, latte::PA_CL_VTE_CNTL::get(0) .VPORT_X_SCALE_ENA(true) .VPORT_X_OFFSET_ENA(true) .VPORT_Y_SCALE_ENA(true) .VPORT_Y_OFFSET_ENA(true) .VPORT_Z_SCALE_ENA(true) .VPORT_Z_OFFSET_ENA(true) .VTX_W0_FMT(true) .value }); internal::writePM4(SetContextReg { latte::Register::PA_CL_NANINF_CNTL, latte::PA_CL_NANINF_CNTL::get(0) .value }); uint32_t values28200_28208[] = { 0, latte::PA_SC_WINDOW_SCISSOR_TL::get(0) .WINDOW_OFFSET_DISABLE(true) .value, latte::PA_SC_WINDOW_SCISSOR_BR::get(0) .BR_X(8192) .BR_Y(8192) .value, }; internal::writePM4(SetContextRegs { latte::Register::PA_SC_WINDOW_OFFSET, gsl::make_span(values28200_28208) }); internal::writePM4(SetContextReg { latte::Register::PA_SC_LINE_STIPPLE, latte::PA_SC_LINE_STIPPLE::get(0) .value }); uint32_t values28A0C_28A10[] = { latte::PA_SC_MPASS_PS_CNTL::get(0) .value, latte::PA_SC_MODE_CNTL::get(0) .MSAA_ENABLE(true) .FORCE_EOV_CNTDWN_ENABLE(true) .FORCE_EOV_REZ_ENABLE(true) .value }; internal::writePM4(SetContextRegs { latte::Register::PA_SC_LINE_STIPPLE, gsl::make_span(values28A0C_28A10) }); uint32_t values28250_28254[] = { latte::PA_SC_VPORT_SCISSOR_0_TL::get(0) .WINDOW_OFFSET_DISABLE(true) .value, latte::PA_SC_VPORT_SCISSOR_0_BR::get(0) .BR_X(8192) .BR_Y(8192) .value, }; internal::writePM4(SetContextRegs { latte::Register::PA_SC_VPORT_SCISSOR_0_TL, gsl::make_span(values28250_28254) }); // TODO: Register 0x8B24 unknown internal::writePM4(SetConfigReg { static_cast(0x8B24), 0xFF3FFF }); internal::writePM4(SetContextReg { latte::Register::PA_SC_CLIPRECT_RULE, latte::PA_SC_CLIPRECT_RULE::get(0) .CLIP_RULE(0xFFFF) .value }); internal::writePM4(SetConfigReg { latte::Register::VGT_GS_VERTEX_REUSE, latte::VGT_GS_VERTEX_REUSE::get(0) .VERT_REUSE(16) .value }); internal::writePM4(SetContextReg { latte::Register::VGT_OUTPUT_PATH_CNTL, latte::VGT_OUTPUT_PATH_CNTL::get(0) .PATH_SELECT(latte::VGT_OUTPUT_PATH_SELECT::TESS_EN) .value }); // TODO: This is an unknown value 16 * 0xb14(r31) * 0xb18(r31) internal::writePM4(SetConfigReg { latte::Register::VGT_ES_PER_GS, latte::VGT_ES_PER_GS::get(0) .ES_PER_GS(16 * 1 * 1) .value }); internal::writePM4(SetConfigReg { latte::Register::VGT_GS_PER_ES, latte::VGT_GS_PER_ES::get(0) .GS_PER_ES(256) .value }); internal::writePM4(SetConfigReg { latte::Register::VGT_GS_PER_VS, latte::VGT_GS_PER_VS::get(0) .GS_PER_VS(4) .value }); internal::writePM4(SetContextReg { latte::Register::VGT_INDX_OFFSET, latte::VGT_INDX_OFFSET::get(0) .INDX_OFFSET(0) .value }); internal::writePM4(SetContextReg { latte::Register::VGT_REUSE_OFF, latte::VGT_REUSE_OFF::get(0) .REUSE_OFF(false) .value }); internal::writePM4(SetContextReg { latte::Register::VGT_MULTI_PRIM_IB_RESET_EN, latte::VGT_MULTI_PRIM_IB_RESET_EN::get(0) .RESET_EN(true) .value }); uint32_t values28C58_28C5C[] = { latte::VGT_VERTEX_REUSE_BLOCK_CNTL::get(0) .VTX_REUSE_DEPTH(14) .value, latte::VGT_OUT_DEALLOC_CNTL::get(0) .DEALLOC_DIST(16) .value, }; internal::writePM4(SetContextRegs { latte::Register::VGT_VERTEX_REUSE_BLOCK_CNTL, gsl::make_span(values28C58_28C5C) }); internal::writePM4(SetContextReg { latte::Register::VGT_HOS_REUSE_DEPTH, latte::VGT_HOS_REUSE_DEPTH::get(0) .REUSE_DEPTH(16) .value }); internal::writePM4(SetContextReg { latte::Register::VGT_STRMOUT_DRAW_OPAQUE_OFFSET, latte::VGT_STRMOUT_DRAW_OPAQUE_OFFSET::get(0) .OFFSET(0) .value }); internal::writePM4(SetContextReg { latte::Register::VGT_VTX_CNT_EN, latte::VGT_VTX_CNT_EN::get(0) .VTX_CNT_EN(false) .value }); uint32_t values28400_28404[] = { latte::VGT_MAX_VTX_INDX::get(0) .MAX_INDX(0xffffffffu) .value, latte::VGT_MIN_VTX_INDX::get(0) .MIN_INDX(0x00000000u) .value }; internal::writePM4(SetContextRegs { latte::Register::VGT_MAX_VTX_INDX, gsl::make_span(values28400_28404) }); internal::writePM4(SetConfigReg { latte::Register::TA_CNTL_AUX, latte::TA_CNTL_AUX::get(0) .UNK0(true) .SYNC_GRADIENT(true) .SYNC_WALKER(true) .SYNC_ALIGNER(true) .value }); // TODO: Register 0x9714 unknown internal::writePM4(SetConfigReg { static_cast(0x9714), 1 }); // TODO: Register 0x8D8C unknown internal::writePM4(SetConfigReg { static_cast(0x8D8C), 0x4000 }); // SQ_ESTMP_RING_BASE ... SQ_REDUC_RING_SIZE internal::writePM4(SetConfigRegs { latte::Register::SQ_ESTMP_RING_BASE, gsl::make_span(zeroes.data(), 12) }); // SQ_ESTMP_RING_ITEMSIZE ... SQ_REDUC_RING_ITEMSIZE internal::writePM4(SetContextRegs { latte::Register::SQ_ESTMP_RING_ITEMSIZE, gsl::make_span(zeroes.data(), 6) }); internal::writePM4(SetControlConstant { latte::Register::SQ_VTX_START_INST_LOC, latte::SQ_VTX_START_INST_LOC::get(0) .OFFSET(0) .value }); // SPI_FOG_CNTL ... SPI_FOG_FUNC_BIAS internal::writePM4(SetContextRegs { latte::Register::SPI_FOG_CNTL, gsl::make_span(zeroes.data(), 3) }); internal::writePM4(SetContextReg { latte::Register::SPI_INTERP_CONTROL_0, latte::SPI_INTERP_CONTROL_0::get(0) .FLAT_SHADE_ENA(true) .PNT_SPRITE_ENA(false) .PNT_SPRITE_OVRD_X(latte::SPI_PNT_SPRITE_SEL::SEL_S) .PNT_SPRITE_OVRD_Y(latte::SPI_PNT_SPRITE_SEL::SEL_T) .PNT_SPRITE_OVRD_Z(latte::SPI_PNT_SPRITE_SEL::SEL_0) .PNT_SPRITE_OVRD_W(latte::SPI_PNT_SPRITE_SEL::SEL_1) .PNT_SPRITE_TOP_1(true) .value }); internal::writePM4(SetConfigReg { latte::Register::SPI_CONFIG_CNTL_1, latte::SPI_CONFIG_CNTL_1::get(0) .value }); // TODO: Register 0x286C8 unknown internal::writePM4(SetAllContextsReg { static_cast(0x286C8), 1 }); // TODO: Register 0x28354 unknown auto unkValue = 0u; // 0x143C(r31) if (unkValue > 0x5270) { internal::writePM4(SetContextReg { static_cast(0x28354), 0xFF }); } else { internal::writePM4(SetContextReg { static_cast(0x28354), 0x1FF }); } uint32_t values28D28_28D2C[] = { latte::DB_SRESULTS_COMPARE_STATE0::get(0) .value, latte::DB_SRESULTS_COMPARE_STATE1::get(0) .value }; internal::writePM4(SetContextRegs { latte::Register::DB_SRESULTS_COMPARE_STATE0, gsl::make_span(values28D28_28D2C) }); internal::writePM4(SetContextReg { latte::Register::DB_RENDER_OVERRIDE, latte::DB_RENDER_OVERRIDE::get(0) .value }); // TODO: Register 0x9830 unknown internal::writePM4(SetConfigReg { static_cast(0x9830), 0 }); // TODO: Register 0x983C unknown internal::writePM4(SetConfigReg { static_cast(0x983C), 0x1000000 }); uint32_t values28C30_28C3C[] = { latte::CB_CLRCMP_CONTROL::get(0) .CLRCMP_FCN_SEL(latte::CB_CLRCMP_SEL::SRC) .value, latte::CB_CLRCMP_SRC::get(0) .CLRCMP_SRC(0) .value, latte::CB_CLRCMP_DST::get(0) .CLRCMP_DST(0) .value, latte::CB_CLRCMP_MSK::get(0) .CLRCMP_MSK(0xFFFFFFFF) .value }; internal::writePM4(SetContextRegs { latte::Register::CB_CLRCMP_CONTROL, gsl::make_span(values28C30_28C3C) }); // TODO: Register 0x9A1C unknown internal::writePM4(SetConfigReg { static_cast(0x9A1C), 0 }); internal::writePM4(SetContextReg { latte::Register::PA_SC_AA_MASK, latte::PA_SC_AA_MASK::get(0) .AA_MASK_ULC(0xFF) .AA_MASK_URC(0xFF) .AA_MASK_LLC(0xFF) .AA_MASK_LRC(0xFF) .value }); // TODO: Register 0x28230 unknown internal::writePM4(SetContextReg { static_cast(0x28230), 0xAAAAAAAA }); // TODO: GX2SetAAMode(0); } } // namespace internal void Library::registerContextStateSymbols() { RegisterFunctionExport(GX2SetupContextStateEx); RegisterFunctionExport(GX2GetContextStateDisplayList); RegisterFunctionExport(GX2SetContextState); RegisterFunctionExport(GX2SetDefaultState); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_contextstate.h ================================================ #pragma once #include "gx2_displaylist.h" #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2_contextstate Context State * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2ShadowRegisters { be2_array config; be2_array context; be2_array alu; be2_array loop; PADDING((0x80 - 0x60) * 4); be2_array resource; PADDING((0xDC0 - 0xD9E) * 4); be2_array sampler; PADDING((0xC0 - 0xA2) * 4); }; CHECK_OFFSET(GX2ShadowRegisters, 0x0000, config); CHECK_OFFSET(GX2ShadowRegisters, 0x2C00, context); CHECK_OFFSET(GX2ShadowRegisters, 0x3C00, alu); CHECK_OFFSET(GX2ShadowRegisters, 0x5C00, loop); CHECK_OFFSET(GX2ShadowRegisters, 0x5E00, resource); CHECK_OFFSET(GX2ShadowRegisters, 0x9500, sampler); CHECK_SIZE(GX2ShadowRegisters, 0x9800); // Internal display list is used to create LOAD_ dlist for the shadow state struct GX2ContextState { static const auto MaxDisplayListSize = 192u; be2_struct shadowState; be2_val profilingEnabled; be2_val shadowDisplayListSize; // stw 0x9808, value 0 in GX2SetupContextStateEx UNKNOWN(0x9e00 - 0x9808); be2_array shadowDisplayList; }; CHECK_OFFSET(GX2ContextState, 0x0000, shadowState); CHECK_OFFSET(GX2ContextState, 0x9800, profilingEnabled); CHECK_OFFSET(GX2ContextState, 0x9804, shadowDisplayListSize); CHECK_OFFSET(GX2ContextState, 0x9E00, shadowDisplayList); CHECK_SIZE(GX2ContextState, 0xA100); #pragma pack(pop) void GX2SetupContextStateEx(virt_ptr state, GX2ContextStateFlags flags); void GX2GetContextStateDisplayList(virt_ptr state, virt_ptr> outDisplayList, virt_ptr outSize); void GX2SetContextState(virt_ptr state); void GX2SetDefaultState(); namespace internal { void initialiseRegisters(); } // namespace internal /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_counter.cpp ================================================ #include "gx2.h" #include "gx2_counter.h" #include "cafe/libraries/cafe_hle_stub.h" #include namespace cafe::gx2 { BOOL GX2InitCounterInfo(virt_ptr info, uint32_t unk0, uint32_t unk1) { decaf_warn_stub(); return TRUE; } void GX2ResetCounterInfo(virt_ptr info) { decaf_warn_stub(); std::memset(info.get(), 0, sizeof(GX2CounterInfo)); } uint64_t GX2GetCounterResult(virt_ptr info, uint32_t unk0) { decaf_warn_stub(); return 0; } void Library::registerCounterSymbols() { RegisterFunctionExport(GX2InitCounterInfo); RegisterFunctionExport(GX2ResetCounterInfo); RegisterFunctionExport(GX2GetCounterResult); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_counter.h ================================================ #pragma once #include namespace cafe::gx2 { /** * \defgroup gx2_counter Counter * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2CounterInfo { UNKNOWN(0x4A0); }; CHECK_SIZE(GX2CounterInfo, 0x4A0); #pragma pack(pop) BOOL GX2InitCounterInfo(virt_ptr info, uint32_t unk0, uint32_t unk1); void GX2ResetCounterInfo(virt_ptr info); uint64_t GX2GetCounterResult(virt_ptr info, uint32_t unk0); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_debug.cpp ================================================ #include "gx2.h" #include "gx2_debug.h" #include "gx2_enum_string.h" #include "gx2_fetchshader.h" #include "gx2_cbpool.h" #include "gx2_internal_gfd.h" #include "gx2_shaders.h" #include "gx2_texture.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_snprintf.h" #include "decaf_config.h" #include "decaf_configstorage.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace cafe::gx2::internal { static std::atomic sDumpTextures; static std::atomic sDumpShaders; static void createDumpDirectory() { platform::createDirectory("dump"); } static void debugDumpData(const std::string &filename, virt_ptr data, size_t size) { auto file = std::ofstream { filename, std::ofstream::out | std::ofstream::binary }; file.write(static_cast(data.get()), size); file.close(); } void initialiseDebug() { static std::once_flag sRegisteredConfigChangeListener; std::call_once(sRegisteredConfigChangeListener, []() { decaf::registerConfigChangeListener( [](const decaf::Settings &settings) { sDumpShaders = settings.gx2.dump_shaders; sDumpTextures = settings.gx2.dump_textures; }); }); sDumpShaders = decaf::config()->gx2.dump_shaders; sDumpTextures = decaf::config()->gx2.dump_textures; } void debugDumpTexture(virt_ptr texture) { if (!sDumpTextures.load(std::memory_order_relaxed)) { return; } createDumpDirectory(); // Write text dump of GX2Texture structure to texture_X.txt auto filename = fmt::format("texture_{}", texture); if (platform::fileExists("dump/" + filename + ".txt")) { return; } auto file = std::ofstream { "dump/" + filename + ".txt", std::ofstream::out }; auto out = fmt::memory_buffer {}; fmt::format_to(std::back_inserter(out), "surface.dim = {}\n", gx2::to_string(texture->surface.dim)); fmt::format_to(std::back_inserter(out), "surface.width = {}\n", texture->surface.width); fmt::format_to(std::back_inserter(out), "surface.height = {}\n", texture->surface.height); fmt::format_to(std::back_inserter(out), "surface.depth = {}\n", texture->surface.depth); fmt::format_to(std::back_inserter(out), "surface.mipLevels = {}\n", texture->surface.mipLevels); fmt::format_to(std::back_inserter(out), "surface.format = {}\n", gx2::to_string(texture->surface.format)); fmt::format_to(std::back_inserter(out), "surface.aa = {}\n", gx2::to_string(texture->surface.aa)); fmt::format_to(std::back_inserter(out), "surface.use = {}\n", gx2::to_string(texture->surface.use)); fmt::format_to(std::back_inserter(out), "surface.resourceFlags = {}\n", texture->surface.resourceFlags); fmt::format_to(std::back_inserter(out), "surface.imageSize = {}\n", texture->surface.imageSize); fmt::format_to(std::back_inserter(out), "surface.image = {}\n", texture->surface.image); fmt::format_to(std::back_inserter(out), "surface.mipmapSize = {}\n", texture->surface.mipmapSize); fmt::format_to(std::back_inserter(out), "surface.mipmaps = {}\n", texture->surface.mipmaps); fmt::format_to(std::back_inserter(out), "surface.tileMode = {}\n", gx2::to_string(texture->surface.tileMode)); fmt::format_to(std::back_inserter(out), "surface.swizzle = {}\n", texture->surface.swizzle); fmt::format_to(std::back_inserter(out), "surface.alignment = {}\n", texture->surface.alignment); fmt::format_to(std::back_inserter(out), "surface.pitch = {}\n", texture->surface.pitch); fmt::format_to(std::back_inserter(out), "viewFirstMip = {}\n", texture->viewFirstMip); fmt::format_to(std::back_inserter(out), "viewNumMips = {}\n", texture->viewNumMips); fmt::format_to(std::back_inserter(out), "viewFirstSlice = {}\n", texture->viewFirstSlice); fmt::format_to(std::back_inserter(out), "viewNumSlices = {}\n", texture->viewNumSlices); file << std::string_view { out.data(), out.size() }; if (!texture->surface.image || !texture->surface.imageSize) { return; } // Write GTX file gfd::GFDFile gtx; gfd::GFDTexture gfdTexture; gx2ToGFDTexture(texture.get(), gfdTexture); gtx.textures.push_back(gfdTexture); gfd::writeFile(gtx, "dump/" + filename + ".gtx"); } static void addShader(gfd::GFDFile &file, virt_ptr shader) { gfd::GFDVertexShader gfdShader; gx2ToGFDVertexShader(shader.get(), gfdShader); file.vertexShaders.emplace_back(std::move(gfdShader)); } static void addShader(gfd::GFDFile &file, virt_ptr shader) { gfd::GFDPixelShader gfdShader; gx2ToGFDPixelShader(shader.get(), gfdShader); file.pixelShaders.emplace_back(std::move(gfdShader)); } static void addShader(gfd::GFDFile &file, virt_ptr shader) { // TODO: Add FetchShader support to .gsh } template static void debugDumpShader(const std::string_view &filename, const std::string_view &info, virt_ptr shader, bool isSubroutine = false) { std::string output; // Write binary of shader data to shader_pixel_X.bin createDumpDirectory(); auto outputBin = fmt::format("dump/{}.bin", filename); if (platform::fileExists(outputBin)) { return; } if (shader->data) { gLog->debug("Dumping shader {}", filename); debugDumpData(outputBin, shader->data, shader->size); } // Write GSH file if (shader->data) { gfd::GFDFile gsh; addShader(gsh, shader); gfd::writeFile(gsh, fmt::format("dump/{}.gsh", filename)); } // Write text of shader to shader_pixel_X.txt auto file = std::ofstream { fmt::format("dump/{}.txt", filename), std::ofstream::out }; // Disassemble if (shader->data) { output = latte::disassemble(gsl::make_span(shader->data.get(), shader->size), isSubroutine); } file << info << std::endl << "Disassembly:" << std::endl << output << std::endl; } static void formatUniformBlocks(fmt::memory_buffer &out, uint32_t count, virt_ptr blocks) { fmt::format_to(std::back_inserter(out), " uniformBlockCount: {}\n", count); for (auto i = 0u; i < count; ++i) { fmt::format_to(std::back_inserter(out), " Block {}\n", i); fmt::format_to(std::back_inserter(out), " name: {}\n", blocks[i].name); fmt::format_to(std::back_inserter(out), " offset: {}\n", blocks[i].offset); fmt::format_to(std::back_inserter(out), " size: {}\n", blocks[i].size); } } static void formatAttribVars(fmt::memory_buffer &out, uint32_t count, virt_ptr vars) { fmt::format_to(std::back_inserter(out), " attribVarCount: {}\n", count); for (auto i = 0u; i < count; ++i) { fmt::format_to(std::back_inserter(out), " Var {}\n", i); fmt::format_to(std::back_inserter(out), " name: {}\n", vars[i].name); fmt::format_to(std::back_inserter(out), " type: {}\n", gx2::to_string(vars[i].type)); fmt::format_to(std::back_inserter(out), " count: {}\n", vars[i].count); fmt::format_to(std::back_inserter(out), " location: {}\n", vars[i].location); } } static void formatInitialValues(fmt::memory_buffer &out, uint32_t count, virt_ptr vars) { fmt::format_to(std::back_inserter(out), " intialValueCount: {}\n", count); for (auto i = 0u; i < count; ++i) { fmt::format_to(std::back_inserter(out), " Var {}\n", i); fmt::format_to(std::back_inserter(out), " offset: {}\n", vars[i].offset); fmt::format_to(std::back_inserter(out), " value: ({}, {}, {}, {})\n", vars[i].value[0], vars[i].value[1], vars[i].value[2], vars[i].value[3]); } } static void formatLoopVars(fmt::memory_buffer &out, uint32_t count, virt_ptr vars) { fmt::format_to(std::back_inserter(out), " loopVarCount: {}\n", count); for (auto i = 0u; i < count; ++i) { fmt::format_to(std::back_inserter(out), " Var {}\n", i); fmt::format_to(std::back_inserter(out), " value: {}\n", vars[i].value); fmt::format_to(std::back_inserter(out), " offset: {}\n", vars[i].offset); } } static void formatUniformVars(fmt::memory_buffer &out, uint32_t count, virt_ptr vars) { fmt::format_to(std::back_inserter(out), " uniformVarCount: {}\n", count); for (auto i = 0u; i < count; ++i) { fmt::format_to(std::back_inserter(out), " Var {}\n", i); fmt::format_to(std::back_inserter(out), " name: {}\n", vars[i].name); fmt::format_to(std::back_inserter(out), " type: {}\n", gx2::to_string(vars[i].type)); fmt::format_to(std::back_inserter(out), " count: {}\n", vars[i].count); fmt::format_to(std::back_inserter(out), " offset: {}\n", vars[i].offset); fmt::format_to(std::back_inserter(out), " block: {}\n", vars[i].block); } } static void formatSamplerVars(fmt::memory_buffer &out, uint32_t count, virt_ptr vars) { fmt::format_to(std::back_inserter(out), " samplerVarCount: {}\n", count); for (auto i = 0u; i < count; ++i) { fmt::format_to(std::back_inserter(out), " Var {}\n", i); fmt::format_to(std::back_inserter(out), " name: {}\n", vars[i].name); fmt::format_to(std::back_inserter(out), " type: {}\n", gx2::to_string(vars[i].type)); fmt::format_to(std::back_inserter(out), " location: {}\n", vars[i].location); } } void debugDumpShader(virt_ptr shader) { if (!sDumpShaders.load(std::memory_order_relaxed)) { return; } fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "GX2FetchShader:\n"); fmt::format_to(std::back_inserter(out), " address: {}\n", shader->data); fmt::format_to(std::back_inserter(out), " size: {}\n", shader->size); debugDumpShader(fmt::format("shader_fetch_{}", shader), std::string_view { out.data(), out.size() }, shader, true); } void debugDumpShader(virt_ptr shader) { if (!sDumpShaders.load(std::memory_order_relaxed)) { return; } fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "GX2PixelShader:\n"); fmt::format_to(std::back_inserter(out), " address: {}\n", shader->data); fmt::format_to(std::back_inserter(out), " size: {}\n", shader->size); fmt::format_to(std::back_inserter(out), " mode: {}\n", gx2::to_string(shader->mode)); formatUniformBlocks(out, shader->uniformBlockCount, shader->uniformBlocks); formatUniformVars(out, shader->uniformVarCount, shader->uniformVars); formatInitialValues(out, shader->initialValueCount, shader->initialValues); formatLoopVars(out, shader->loopVarCount, shader->loopVars); formatSamplerVars(out, shader->samplerVarCount, shader->samplerVars); debugDumpShader(fmt::format("shader_pixel_{}", shader), std::string_view { out.data(), out.size() }, shader); } void debugDumpShader(virt_ptr shader) { if (!sDumpShaders.load(std::memory_order_relaxed)) { return; } fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "GX2VertexShader:\n"); fmt::format_to(std::back_inserter(out), " address: {}\n", shader->data); fmt::format_to(std::back_inserter(out), " size: {}\n", shader->size); fmt::format_to(std::back_inserter(out), " mode: {}\n", gx2::to_string(shader->mode)); formatUniformBlocks(out, shader->uniformBlockCount, shader->uniformBlocks); formatUniformVars(out, shader->uniformVarCount, shader->uniformVars); formatInitialValues(out, shader->initialValueCount, shader->initialValues); formatLoopVars(out, shader->loopVarCount, shader->loopVars); formatSamplerVars(out, shader->samplerVarCount, shader->samplerVars); formatAttribVars(out, shader->attribVarCount, shader->attribVars); debugDumpShader(fmt::format("shader_vertex_{}", shader), std::string_view { out.data(), out.size() }, shader); } } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_debug.h ================================================ #pragma once #include namespace cafe::gx2 { struct GX2Texture; struct GX2FetchShader; struct GX2PixelShader; struct GX2VertexShader; namespace internal { void initialiseDebug(); void debugDumpTexture(virt_ptr texture); void debugDumpShader(virt_ptr shader); void debugDumpShader(virt_ptr shader); void debugDumpShader(virt_ptr shader); } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_debug_dds.cpp ================================================ #include "gx2_debug_dds.h" #include "gx2_format.h" #include "gx2_surface.h" #include #include namespace cafe::gx2 { constexpr inline uint32_t make_fourcc(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) { return c0 | (c1 << 8) | (c2 << 16) | (c3 << 24); } static const uint32_t DDS_MAGIC = make_fourcc('D', 'D', 'S', ' '); enum DXGI_FORMAT : uint32_t { DXGI_FORMAT_UNKNOWN = 0, DXGI_FORMAT_R32G32B32A32_TYPELESS = 1, DXGI_FORMAT_R32G32B32A32_FLOAT = 2, DXGI_FORMAT_R32G32B32A32_UINT = 3, DXGI_FORMAT_R32G32B32A32_SINT = 4, DXGI_FORMAT_R32G32B32_TYPELESS = 5, DXGI_FORMAT_R32G32B32_FLOAT = 6, DXGI_FORMAT_R32G32B32_UINT = 7, DXGI_FORMAT_R32G32B32_SINT = 8, DXGI_FORMAT_R16G16B16A16_TYPELESS = 9, DXGI_FORMAT_R16G16B16A16_FLOAT = 10, DXGI_FORMAT_R16G16B16A16_UNORM = 11, DXGI_FORMAT_R16G16B16A16_UINT = 12, DXGI_FORMAT_R16G16B16A16_SNORM = 13, DXGI_FORMAT_R16G16B16A16_SINT = 14, DXGI_FORMAT_R32G32_TYPELESS = 15, DXGI_FORMAT_R32G32_FLOAT = 16, DXGI_FORMAT_R32G32_UINT = 17, DXGI_FORMAT_R32G32_SINT = 18, DXGI_FORMAT_R32G8X24_TYPELESS = 19, DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20, DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21, DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22, DXGI_FORMAT_R10G10B10A2_TYPELESS = 23, DXGI_FORMAT_R10G10B10A2_UNORM = 24, DXGI_FORMAT_R10G10B10A2_UINT = 25, DXGI_FORMAT_R11G11B10_FLOAT = 26, DXGI_FORMAT_R8G8B8A8_TYPELESS = 27, DXGI_FORMAT_R8G8B8A8_UNORM = 28, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29, DXGI_FORMAT_R8G8B8A8_UINT = 30, DXGI_FORMAT_R8G8B8A8_SNORM = 31, DXGI_FORMAT_R8G8B8A8_SINT = 32, DXGI_FORMAT_R16G16_TYPELESS = 33, DXGI_FORMAT_R16G16_FLOAT = 34, DXGI_FORMAT_R16G16_UNORM = 35, DXGI_FORMAT_R16G16_UINT = 36, DXGI_FORMAT_R16G16_SNORM = 37, DXGI_FORMAT_R16G16_SINT = 38, DXGI_FORMAT_R32_TYPELESS = 39, DXGI_FORMAT_D32_FLOAT = 40, DXGI_FORMAT_R32_FLOAT = 41, DXGI_FORMAT_R32_UINT = 42, DXGI_FORMAT_R32_SINT = 43, DXGI_FORMAT_R24G8_TYPELESS = 44, DXGI_FORMAT_D24_UNORM_S8_UINT = 45, DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46, DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47, DXGI_FORMAT_R8G8_TYPELESS = 48, DXGI_FORMAT_R8G8_UNORM = 49, DXGI_FORMAT_R8G8_UINT = 50, DXGI_FORMAT_R8G8_SNORM = 51, DXGI_FORMAT_R8G8_SINT = 52, DXGI_FORMAT_R16_TYPELESS = 53, DXGI_FORMAT_R16_FLOAT = 54, DXGI_FORMAT_D16_UNORM = 55, DXGI_FORMAT_R16_UNORM = 56, DXGI_FORMAT_R16_UINT = 57, DXGI_FORMAT_R16_SNORM = 58, DXGI_FORMAT_R16_SINT = 59, DXGI_FORMAT_R8_TYPELESS = 60, DXGI_FORMAT_R8_UNORM = 61, DXGI_FORMAT_R8_UINT = 62, DXGI_FORMAT_R8_SNORM = 63, DXGI_FORMAT_R8_SINT = 64, DXGI_FORMAT_A8_UNORM = 65, DXGI_FORMAT_R1_UNORM = 66, DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67, DXGI_FORMAT_R8G8_B8G8_UNORM = 68, DXGI_FORMAT_G8R8_G8B8_UNORM = 69, DXGI_FORMAT_BC1_TYPELESS = 70, DXGI_FORMAT_BC1_UNORM = 71, DXGI_FORMAT_BC1_UNORM_SRGB = 72, DXGI_FORMAT_BC2_TYPELESS = 73, DXGI_FORMAT_BC2_UNORM = 74, DXGI_FORMAT_BC2_UNORM_SRGB = 75, DXGI_FORMAT_BC3_TYPELESS = 76, DXGI_FORMAT_BC3_UNORM = 77, DXGI_FORMAT_BC3_UNORM_SRGB = 78, DXGI_FORMAT_BC4_TYPELESS = 79, DXGI_FORMAT_BC4_UNORM = 80, DXGI_FORMAT_BC4_SNORM = 81, DXGI_FORMAT_BC5_TYPELESS = 82, DXGI_FORMAT_BC5_UNORM = 83, DXGI_FORMAT_BC5_SNORM = 84, DXGI_FORMAT_B5G6R5_UNORM = 85, DXGI_FORMAT_B5G5R5A1_UNORM = 86, DXGI_FORMAT_B8G8R8A8_UNORM = 87, DXGI_FORMAT_B8G8R8X8_UNORM = 88, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89, DXGI_FORMAT_B8G8R8A8_TYPELESS = 90, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91, DXGI_FORMAT_B8G8R8X8_TYPELESS = 92, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93, DXGI_FORMAT_BC6H_TYPELESS = 94, DXGI_FORMAT_BC6H_UF16 = 95, DXGI_FORMAT_BC6H_SF16 = 96, DXGI_FORMAT_BC7_TYPELESS = 97, DXGI_FORMAT_BC7_UNORM = 98, DXGI_FORMAT_BC7_UNORM_SRGB = 99, DXGI_FORMAT_AYUV = 100, DXGI_FORMAT_Y410 = 101, DXGI_FORMAT_Y416 = 102, DXGI_FORMAT_NV12 = 103, DXGI_FORMAT_P010 = 104, DXGI_FORMAT_P016 = 105, DXGI_FORMAT_420_OPAQUE = 106, DXGI_FORMAT_YUY2 = 107, DXGI_FORMAT_Y210 = 108, DXGI_FORMAT_Y216 = 109, DXGI_FORMAT_NV11 = 110, DXGI_FORMAT_AI44 = 111, DXGI_FORMAT_IA44 = 112, DXGI_FORMAT_P8 = 113, DXGI_FORMAT_A8P8 = 114, DXGI_FORMAT_B4G4R4A4_UNORM = 115, DXGI_FORMAT_P208 = 130, DXGI_FORMAT_V208 = 131, DXGI_FORMAT_V408 = 132, DXGI_FORMAT_FORCE_UINT = 0xffffffff }; enum D3D10_RESOURCE_DIMENSION { D3D10_RESOURCE_DIMENSION_UNKNOWN = 0, D3D10_RESOURCE_DIMENSION_BUFFER = 1, D3D10_RESOURCE_DIMENSION_TEXTURE1D = 2, D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3, D3D10_RESOURCE_DIMENSION_TEXTURE3D = 4 }; enum D3D10_RESOURCE_MISC_FLAG { D3D10_RESOURCE_MISC_GENERATE_MIPS = 0x1L, D3D10_RESOURCE_MISC_SHARED = 0x2L, D3D10_RESOURCE_MISC_TEXTURECUBE = 0x4L, D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX = 0x10L, D3D10_RESOURCE_MISC_GDI_COMPATIBLE = 0x20L }; #pragma pack(push, 1) struct DdsPixelFormat { uint32_t dwSize; uint32_t dwFlags; uint32_t dwFourCC; uint32_t dwRGBBitCount; uint32_t dwRBitMask; uint32_t dwGBitMask; uint32_t dwBBitMask; uint32_t dwABitMask; }; struct DdsHeader { uint32_t dwSize; uint32_t dwFlags; uint32_t dwHeight; uint32_t dwWidth; uint32_t dwPitchOrLinearSize; uint32_t dwDepth; uint32_t dwMipMapCount; uint32_t dwReserved1[11]; DdsPixelFormat ddspf; uint32_t dwCaps; uint32_t dwCaps2; uint32_t dwCaps3; uint32_t dwCaps4; uint32_t dwReserved2; }; struct DdsHeaderDX10 { DXGI_FORMAT dxgiFormat; D3D10_RESOURCE_DIMENSION resourceDimension; uint32_t miscFlag; uint32_t arraySize; uint32_t reserved; }; #pragma pack(pop) #define DDSD_CAPS 0x00000001L #define DDSD_HEIGHT 0x00000002L #define DDSD_WIDTH 0x00000004L #define DDSD_PITCH 0x00000008L #define DDSD_BACKBUFFERCOUNT 0x00000020L #define DDSD_ZBUFFERBITDEPTH 0x00000040L #define DDSD_ALPHABITDEPTH 0x00000080L #define DDSD_LPSURFACE 0x00000800L #define DDSD_PIXELFORMAT 0x00001000L #define DDSD_CKDESTOVERLAY 0x00002000L #define DDSD_CKDESTBLT 0x00004000L #define DDSD_CKSRCOVERLAY 0x00008000L #define DDSD_CKSRCBLT 0x00010000L #define DDSD_MIPMAPCOUNT 0x00020000L #define DDSD_REFRESHRATE 0x00040000L #define DDSD_LINEARSIZE 0x00080000L #define DDSD_TEXTURESTAGE 0x00100000L #define DDSD_FVF 0x00200000L #define DDSD_SRCVBHANDLE 0x00400000L #define DDSD_DEPTH 0x00800000L #define DDSD_ALL 0x007ff9eeL #define DDSCAPS_RESERVED1 0x00000001L #define DDSCAPS_ALPHA 0x00000002L #define DDSCAPS_BACKBUFFER 0x00000004L #define DDSCAPS_COMPLEX 0x00000008L #define DDSCAPS_FLIP 0x00000010L #define DDSCAPS_FRONTBUFFER 0x00000020L #define DDSCAPS_OFFSCREENPLAIN 0x00000040L #define DDSCAPS_OVERLAY 0x00000080L #define DDSCAPS_PALETTE 0x00000100L #define DDSCAPS_PRIMARYSURFACE 0x00000200L #define DDSCAPS_RESERVED3 0x00000400L #define DDSCAPS_SYSTEMMEMORY 0x00000800L #define DDSCAPS_TEXTURE 0x00001000L #define DDSCAPS_3DDEVICE 0x00002000L #define DDSCAPS_VIDEOMEMORY 0x00004000L #define DDSCAPS_VISIBLE 0x00008000L #define DDSCAPS_WRITEONLY 0x00010000L #define DDSCAPS_ZBUFFER 0x00020000L #define DDSCAPS_OWNDC 0x00040000L #define DDSCAPS_LIVEVIDEO 0x00080000L #define DDSCAPS_HWCODEC 0x00100000L #define DDSCAPS_MODEX 0x00200000L #define DDSCAPS_MIPMAP 0x00400000L #define DDSCAPS_RESERVED2 0x00800000L #define DDSCAPS_ALLOCONLOAD 0x04000000L #define DDSCAPS_VIDEOPORT 0x08000000L #define DDSCAPS_LOCALVIDMEM 0x10000000L #define DDSCAPS_NONLOCALVIDMEM 0x20000000L #define DDSCAPS_STANDARDVGAMODE 0x40000000L #define DDSCAPS_OPTIMIZED 0x80000000L #define DDSCAPS2_HARDWAREDEINTERLACE 0x00000002L #define DDSCAPS2_HINTDYNAMIC 0x00000004L #define DDSCAPS2_HINTSTATIC 0x00000008L #define DDSCAPS2_TEXTUREMANAGE 0x00000010L #define DDSCAPS2_RESERVED1 0x00000020L #define DDSCAPS2_RESERVED2 0x00000040L #define DDSCAPS2_HINTANTIALIASING 0x00000100L #define DDSCAPS2_CUBEMAP 0x00000200L #define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400L #define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800L #define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000L #define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000L #define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000L #define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000L #define DDSCAPS2_CUBEMAP_ALLFACES ( DDSCAPS2_CUBEMAP_POSITIVEX |\ DDSCAPS2_CUBEMAP_NEGATIVEX |\ DDSCAPS2_CUBEMAP_POSITIVEY |\ DDSCAPS2_CUBEMAP_NEGATIVEY |\ DDSCAPS2_CUBEMAP_POSITIVEZ |\ DDSCAPS2_CUBEMAP_NEGATIVEZ ) #define DDSCAPS2_MIPMAPSUBLEVEL 0x00010000L #define DDSCAPS2_D3DTEXTUREMANAGE 0x00020000L #define DDSCAPS2_DONOTPERSIST 0x00040000L #define DDSCAPS2_STEREOSURFACELEFT 0x00080000L #define DDSCAPS2_VOLUME 0x00200000L #define DDPF_ALPHAPIXELS 0x00000001L #define DDPF_ALPHA 0x00000002L #define DDPF_FOURCC 0x00000004L #define DDPF_PALETTEINDEXED4 0x00000008L #define DDPF_PALETTEINDEXEDTO8 0x00000010L #define DDPF_PALETTEINDEXED8 0x00000020L #define DDPF_RGB 0x00000040L #define DDPF_COMPRESSED 0x00000080L #define DDPF_RGBTOYUV 0x00000100L #define DDPF_YUV 0x00000200L #define DDPF_ZBUFFER 0x00000400L #define DDPF_PALETTEINDEXED1 0x00000800L #define DDPF_PALETTEINDEXED2 0x00001000L #define DDPF_ZPIXELS 0x00002000L #define DDPF_STENCILBUFFER 0x00004000L #define DDPF_ALPHAPREMULT 0x00008000L #define DDPF_LUMINANCE 0x00020000L #define DDPF_BUMPLUMINANCE 0x00040000L #define DDPF_BUMPDUDV 0x00080000L enum D3DFORMAT { D3DFMT_UNKNOWN = 0, D3DFMT_R8G8B8 = 20, D3DFMT_A8R8G8B8 = 21, D3DFMT_X8R8G8B8 = 22, D3DFMT_R5G6B5 = 23, D3DFMT_X1R5G5B5 = 24, D3DFMT_A1R5G5B5 = 25, D3DFMT_A4R4G4B4 = 26, D3DFMT_R3G3B2 = 27, D3DFMT_A8 = 28, D3DFMT_A8R3G3B2 = 29, D3DFMT_X4R4G4B4 = 30, D3DFMT_A2B10G10R10 = 31, D3DFMT_A8B8G8R8 = 32, D3DFMT_X8B8G8R8 = 33, D3DFMT_G16R16 = 34, D3DFMT_A2R10G10B10 = 35, D3DFMT_A16B16G16R16 = 36, D3DFMT_A8P8 = 40, D3DFMT_P8 = 41, D3DFMT_L8 = 50, D3DFMT_A8L8 = 51, D3DFMT_A4L4 = 52, D3DFMT_V8U8 = 60, D3DFMT_L6V5U5 = 61, D3DFMT_X8L8V8U8 = 62, D3DFMT_Q8W8V8U8 = 63, D3DFMT_V16U16 = 64, D3DFMT_A2W10V10U10 = 67, D3DFMT_UYVY = make_fourcc('U', 'Y', 'V', 'Y'), D3DFMT_R8G8_B8G8 = make_fourcc('R', 'G', 'B', 'G'), D3DFMT_YUY2 = make_fourcc('Y', 'U', 'Y', '2'), D3DFMT_G8R8_G8B8 = make_fourcc('G', 'R', 'G', 'B'), D3DFMT_DXT1 = make_fourcc('D', 'X', 'T', '1'), D3DFMT_DXT2 = make_fourcc('D', 'X', 'T', '2'), D3DFMT_DXT3 = make_fourcc('D', 'X', 'T', '3'), D3DFMT_DXT4 = make_fourcc('D', 'X', 'T', '4'), D3DFMT_DXT5 = make_fourcc('D', 'X', 'T', '5'), D3DFMT_ATI1 = make_fourcc('A', 'T', 'I', '1'), D3DFMT_AT1N = make_fourcc('A', 'T', '1', 'N'), D3DFMT_ATI2 = make_fourcc('A', 'T', 'I', '2'), D3DFMT_AT2N = make_fourcc('A', 'T', '2', 'N'), D3DFMT_ETC = make_fourcc('E', 'T', 'C', ' '), D3DFMT_ETC1 = make_fourcc('E', 'T', 'C', '1'), D3DFMT_ATC = make_fourcc('A', 'T', 'C', ' '), D3DFMT_ATCA = make_fourcc('A', 'T', 'C', 'A'), D3DFMT_ATCI = make_fourcc('A', 'T', 'C', 'I'), D3DFMT_POWERVR_2BPP = make_fourcc('P', 'T', 'C', '2'), D3DFMT_POWERVR_4BPP = make_fourcc('P', 'T', 'C', '4'), D3DFMT_D16_LOCKABLE = 70, D3DFMT_D32 = 71, D3DFMT_D15S1 = 73, D3DFMT_D24S8 = 75, D3DFMT_D24X8 = 77, D3DFMT_D24X4S4 = 79, D3DFMT_D16 = 80, D3DFMT_D32F_LOCKABLE = 82, D3DFMT_D24FS8 = 83, D3DFMT_L16 = 81, D3DFMT_VERTEXDATA = 100, D3DFMT_INDEX16 = 101, D3DFMT_INDEX32 = 102, D3DFMT_Q16W16V16U16 = 110, D3DFMT_MULTI2_ARGB8 = make_fourcc('M','E','T','1'), D3DFMT_R16F = 111, D3DFMT_G16R16F = 112, D3DFMT_A16B16G16R16F = 113, D3DFMT_R32F = 114, D3DFMT_G32R32F = 115, D3DFMT_A32B32G32R32F = 116, D3DFMT_CxV8U8 = 117, D3DFMT_DX10 = make_fourcc('D', 'X', '1', '0'), D3DFMT_FORCE_DWORD = 0x7fffffff }; static DdsHeader encodeHeader(const GX2Surface* surface) { DdsHeader dds; memset(&dds, 0, sizeof(DdsHeader)); dds.dwSize = sizeof(DdsHeader); dds.dwWidth = surface->width; dds.dwHeight = surface->height; dds.dwMipMapCount = surface->mipLevels; dds.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT; if ((surface->format >= GX2SurfaceFormat::UNORM_BC1 && surface->format <= GX2SurfaceFormat::UNORM_BC5) || (surface->format >= GX2SurfaceFormat::SRGB_BC1 && surface->format <= GX2SurfaceFormat::SRGB_BC3) || (surface->format >= GX2SurfaceFormat::SNORM_BC4 && surface->format <= GX2SurfaceFormat::SNORM_BC5)) { dds.dwFlags |= DDSD_LINEARSIZE; dds.dwPitchOrLinearSize = surface->imageSize + surface->mipmapSize; } else { dds.dwFlags |= DDSD_PITCH; } dds.ddspf.dwSize = sizeof(DdsPixelFormat); dds.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_COMPLEX | DDSCAPS_MIPMAP; if (surface->dim == GX2SurfaceDim::TextureCube) { dds.dwCaps2 |= DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALLFACES; } else if (surface->dim == GX2SurfaceDim::Texture3D) { dds.dwFlags |= DDSD_DEPTH; dds.dwDepth = surface->depth; dds.dwCaps2 |= DDSCAPS2_VOLUME; } else if (surface->dim == GX2SurfaceDim::Texture2DArray) { dds.dwFlags |= DDSD_DEPTH; dds.dwDepth = surface->depth; } return dds; } static DdsHeader encodeHeader10(const GX2Surface* surface) { DdsHeader dds; memset(&dds, 0, sizeof(DdsHeader)); dds.dwSize = sizeof(DdsHeader); dds.dwWidth = surface->width; dds.dwHeight = surface->height; dds.dwMipMapCount = surface->mipLevels; dds.dwFlags = DDSD_WIDTH | DDSD_HEIGHT; dds.ddspf.dwSize = sizeof(DdsPixelFormat); dds.dwCaps = DDSCAPS_TEXTURE; if (surface->dim == GX2SurfaceDim::TextureCube) { dds.dwCaps2 |= DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALLFACES; } else if (surface->dim == GX2SurfaceDim::Texture3D) { dds.dwFlags |= DDSD_DEPTH; dds.dwDepth = surface->depth; dds.dwCaps2 |= DDSCAPS2_VOLUME; } else if (surface->dim == GX2SurfaceDim::Texture2DArray) { dds.dwFlags |= DDSD_DEPTH; dds.dwDepth = surface->depth; } if (surface->mipLevels > 1) { dds.dwFlags |= DDSD_MIPMAPCOUNT; dds.dwCaps |= DDSCAPS_MIPMAP; } return dds; } static void writeData(std::ofstream &file, DdsHeader *header, const void *image, size_t imageSize, const void *mipmaps, size_t mipmapSize) { file.write(reinterpret_cast(header), sizeof(DdsHeader)); if (imageSize > 0) { file.write(reinterpret_cast(image), imageSize); } if (mipmapSize > 0) { file.write(reinterpret_cast(mipmaps), mipmapSize); } } static bool encodeFourCC(std::ofstream &file, const GX2Surface* surface, const void *imagePtr, const void *mipPtr, uint32_t fourCC, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFourCC = fourCC; header.ddspf.dwFlags |= DDPF_FOURCC; writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize); return true; } static bool encodeFourCCWithPitch(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, uint32_t fourCC, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFourCC = fourCC; header.ddspf.dwFlags |= DDPF_FOURCC | flags; header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format); writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize); return true; } static bool encodeMasked(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, uint32_t maskR, uint32_t maskG, uint32_t maskB, uint32_t maskA, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFlags |= DDPF_RGB | flags; header.ddspf.dwRBitMask = maskR; header.ddspf.dwGBitMask = maskG; header.ddspf.dwBBitMask = maskB; header.ddspf.dwABitMask = maskA; header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format); header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format); writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize); return true; } static bool encodeLuminance(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, uint32_t maskL, uint32_t maskA, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFlags |= DDPF_LUMINANCE | flags; header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format); header.ddspf.dwRBitMask = maskL; header.ddspf.dwABitMask = maskA; header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format); writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize); return true; } static void swizzle565(const void *srcPtr, void *dstPtr, size_t size) { auto src = reinterpret_cast(srcPtr); auto dst = reinterpret_cast(dstPtr); for (auto i = 0u; i < (size / 2); ++i) { auto pixel = src[i]; pixel = ((pixel & 0xF800) >> 11) | (pixel & 0x07E0) | ((pixel & 0x1F) << 11); dst[i] = pixel; } } static void swizzle1555(const void *srcPtr, void *dstPtr, size_t size) { auto src = reinterpret_cast(srcPtr); auto dst = reinterpret_cast(dstPtr); for (auto i = 0u; i < (size / 2); ++i) { auto pixel = src[i]; pixel = (pixel & 0x8000) | ((pixel & 0x7c00) >> 10) | ((pixel & 0x001f) << 10) | (pixel & 0x03e0); dst[i] = pixel; } } static void swizzle4444(const void *srcPtr, void *dstPtr, size_t size) { auto src = reinterpret_cast(srcPtr); auto dst = reinterpret_cast(dstPtr); for (auto i = 0u; i < (size / 2); ++i) { auto pixel = src[i]; pixel = (pixel & 0xf000) | ((pixel & 0x0f00) >> 8) | (pixel & 0x00f0) | ((pixel & 0x000f) << 8); dst[i] = pixel; } } static bool encode565(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFlags |= DDPF_RGB | flags; header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format); header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format); std::vector image, mipmap; image.resize(surface->imageSize); mipmap.resize(surface->mipmapSize); swizzle565(imagePtr, image.data(), image.size()); swizzle565(mipPtr, mipmap.data(), mipmap.size()); writeData(file, &header, image.data(), image.size(), mipmap.data(), mipmap.size()); return true; } static bool encode1555(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFlags |= DDPF_RGB | DDPF_ALPHAPIXELS | flags; header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format); header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format); header.ddspf.dwRBitMask = 0x7c00; header.ddspf.dwGBitMask = 0x3e0; header.ddspf.dwBBitMask = 0x1f; header.ddspf.dwABitMask = 0x8000; std::vector image, mipmap; image.resize(surface->imageSize); mipmap.resize(surface->mipmapSize); swizzle1555(imagePtr, image.data(), image.size()); swizzle1555(mipPtr, mipmap.data(), mipmap.size()); writeData(file, &header, image.data(), image.size(), mipmap.data(), mipmap.size()); return true; } static bool encode4444(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, uint32_t flags = 0) { DdsHeader header = encodeHeader(surface); header.ddspf.dwFlags |= DDPF_RGB | DDPF_ALPHAPIXELS | flags; header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format); header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format); header.ddspf.dwRBitMask = 0xf00; header.ddspf.dwGBitMask = 0xf0; header.ddspf.dwBBitMask = 0xf; header.ddspf.dwABitMask = 0xf000; std::vector image, mipmap; image.resize(surface->imageSize); mipmap.resize(surface->mipmapSize); swizzle4444(imagePtr, image.data(), image.size()); swizzle4444(mipPtr, mipmap.data(), mipmap.size()); writeData(file, &header, image.data(), image.size(), mipmap.data(), mipmap.size()); return true; } static bool encodeDX10(std::ofstream &file, const GX2Surface *surface, const void *imagePtr, const void *mipPtr, DXGI_FORMAT dxgiFormat) { DdsHeader header = encodeHeader10(surface); header.ddspf.dwFlags = DDPF_FOURCC; header.ddspf.dwFourCC = make_fourcc('D', 'X', '1', '0'); switch (surface->format) { case GX2SurfaceFormat::UNORM_BC1: case GX2SurfaceFormat::SRGB_BC1: case GX2SurfaceFormat::UNORM_BC4: case GX2SurfaceFormat::SNORM_BC4: header.dwPitchOrLinearSize = header.dwWidth * 2; break; case GX2SurfaceFormat::UNORM_BC2: case GX2SurfaceFormat::UNORM_BC3: case GX2SurfaceFormat::UNORM_BC5: case GX2SurfaceFormat::SRGB_BC2: case GX2SurfaceFormat::SRGB_BC3: case GX2SurfaceFormat::SNORM_BC5: header.dwPitchOrLinearSize = header.dwWidth * 4; break; case GX2SurfaceFormat::FLOAT_R11_G11_B10: header.dwPitchOrLinearSize = header.dwWidth * 4; break; } file.write(reinterpret_cast(&header), sizeof(DdsHeader)); // Setup dx10 header DdsHeaderDX10 headerDX10; memset(&headerDX10, 0, sizeof(DdsHeaderDX10)); headerDX10.dxgiFormat = dxgiFormat; if (surface->dim == GX2SurfaceDim::Texture2D) { headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D; headerDX10.miscFlag = 0; headerDX10.arraySize = 1; } else if (surface->dim == GX2SurfaceDim::TextureCube) { headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D; headerDX10.miscFlag = D3D10_RESOURCE_MISC_TEXTURECUBE; headerDX10.arraySize = 6; } else { headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE3D; headerDX10.miscFlag = 0; headerDX10.arraySize = surface->depth; } file.write(reinterpret_cast(&headerDX10), sizeof(DdsHeaderDX10)); if (surface->imageSize > 0) { file.write(reinterpret_cast(imagePtr), surface->imageSize); } if (surface->mipmapSize > 0) { file.write(reinterpret_cast(mipPtr), surface->mipmapSize); } return true; } static void encodeCubemap(const GX2Surface *surface, const void *imagePtr, const void *mipPtr, void *cubeData) { // DDS cubemap has slices interleaved with mipmaps... fuck you. int minMipSize = 1; if (surface->format == GX2SurfaceFormat::UNORM_BC1 || surface->format == GX2SurfaceFormat::SRGB_BC1) { minMipSize = 8; } else if (surface->format >= GX2SurfaceFormat::UNORM_BC2 && surface->format <= GX2SurfaceFormat::SNORM_BC5) { minMipSize = 16; } else { minMipSize = 1; } auto dst = reinterpret_cast(cubeData); auto srcImage = reinterpret_cast(imagePtr); auto srcMip = reinterpret_cast(mipPtr); for (auto i = 0; i < 6; ++i) { int sliceSize = surface->imageSize / 6; std::memcpy(dst, srcImage + (sliceSize * i), sliceSize); dst += sliceSize; int mipOffset = 0; for (auto j = 0u; j < surface->mipLevels - 1; ++j) { int mipSize = sliceSize >> ((j + 1) * 2); mipSize = std::max(mipSize, minMipSize); memcpy(dst, srcMip + mipOffset + (mipSize * i), mipSize); dst += mipSize; mipOffset += mipSize * 6; } } } namespace debug { bool saveDDS(const std::string &filename, const GX2Surface *surface, const void *imagePtr, const void *mipPtr) { std::ofstream fh { filename, std::ofstream::binary }; if (!fh.is_open()) { return false; } fh.write(reinterpret_cast(&DDS_MAGIC), sizeof(DDS_MAGIC)); std::vector cubeAdjustedImage; std::vector cubeAdjustedMip; if (surface->dim == GX2SurfaceDim::TextureCube) { std::vector cubeData; cubeData.resize(surface->imageSize + surface->mipmapSize); encodeCubemap(surface, imagePtr, mipPtr, cubeData.data()); cubeAdjustedImage.resize(surface->imageSize); std::memcpy(cubeAdjustedImage.data(), cubeData.data(), surface->imageSize); imagePtr = cubeAdjustedImage.data(); cubeAdjustedMip.resize(surface->mipmapSize); std::memcpy(cubeAdjustedMip.data(), cubeData.data() + surface->imageSize, surface->mipmapSize); mipPtr = cubeAdjustedMip.data(); } bool result = false; switch (surface->format) { case GX2SurfaceFormat::UNORM_R16_G16_B16_A16: result = encodeFourCCWithPitch(fh, surface, imagePtr, mipPtr, D3DFMT_A16B16G16R16, DDPF_ALPHAPIXELS); break; case GX2SurfaceFormat::FLOAT_R16_G16_B16_A16: result = encodeFourCCWithPitch(fh, surface, imagePtr, mipPtr, D3DFMT_A16B16G16R16F, DDPF_ALPHAPIXELS); break; case GX2SurfaceFormat::FLOAT_R32_G32_B32_A32: result = encodeFourCCWithPitch(fh, surface, imagePtr, mipPtr, D3DFMT_A32B32G32R32F, DDPF_ALPHAPIXELS); break; case GX2SurfaceFormat::UNORM_R10_G10_B10_A2: result = encodeMasked(fh, surface, imagePtr, mipPtr, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000, DDPF_ALPHAPIXELS); break; case GX2SurfaceFormat::UNORM_R8_G8_B8_A8: case GX2SurfaceFormat::SRGB_R8_G8_B8_A8: result = encodeMasked(fh, surface, imagePtr, mipPtr, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, DDPF_ALPHAPIXELS); break; case GX2SurfaceFormat::UNORM_R5_G6_B5: result = encode565(fh, surface, imagePtr, mipPtr); break; case GX2SurfaceFormat::UNORM_R5_G5_B5_A1: result = encode1555(fh, surface, imagePtr, mipPtr); break; case GX2SurfaceFormat::UNORM_R4_G4_B4_A4: result = encode4444(fh, surface, imagePtr, mipPtr); break; case GX2SurfaceFormat::UNORM_R8: result = encodeLuminance(fh, surface, imagePtr, mipPtr, 0xff, 0); break; case GX2SurfaceFormat::UNORM_R8_G8: result = encodeLuminance(fh, surface, imagePtr, mipPtr, 0xff, 0xff00, DDPF_ALPHAPIXELS); break; case GX2SurfaceFormat::UNORM_BC1: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('D', 'X', 'T', '1')); break; case GX2SurfaceFormat::UNORM_BC2: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('D', 'X', 'T', '3')); break; case GX2SurfaceFormat::UNORM_BC3: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('D', 'X', 'T', '5')); break; case GX2SurfaceFormat::UNORM_BC4: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '1')); break; case GX2SurfaceFormat::SNORM_BC4: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '1')); break; case GX2SurfaceFormat::UNORM_BC5: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '2')); break; case GX2SurfaceFormat::SNORM_BC5: result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '2')); break; case GX2SurfaceFormat::SRGB_BC1: result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_BC1_UNORM_SRGB); break; case GX2SurfaceFormat::SRGB_BC2: result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_BC2_UNORM_SRGB); break; case GX2SurfaceFormat::SRGB_BC3: result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_BC3_UNORM_SRGB); break; case GX2SurfaceFormat::FLOAT_R11_G11_B10: result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_R11G11B10_FLOAT); break; } return result; } } // namespace debug } // namepsace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_debug_dds.h ================================================ #pragma once #include namespace cafe::gx2 { struct GX2Surface; namespace debug { bool saveDDS(const std::string &filename, const GX2Surface *surface, const void *imagePtr, const void *mipPtr); } // namespace debug } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_debugcapture.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_debugcapture.h" #include "gx2_display.h" #include "gx2_event.h" #include "gx2_state.h" #include "gx2r_resource.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_ppc_interface_varargs.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_snprintf.h" #include namespace cafe::gx2 { struct StaticDebugCaptureData { be2_val enabled; be2_val previousSwapInterval; be2_struct captureInterface; be2_val numCaptureFramesRemaining; be2_array pendingCaptureFilename; }; static virt_ptr sDebugCaptureData = nullptr; /** * Initialise the debug capture interface. */ BOOL GX2DebugSetCaptureInterface(virt_ptr captureInterface) { // Normally this is only allowed when: // OSGetSecurityLevel () != 1 && // OSGetConsoleType() != 0x13000048 && // OSGetConsoleType() != 0x13000040 if (!captureInterface || captureInterface->version != GX2DebugCaptureInterfaceVersion::Version1 || !captureInterface->onShutdown || !captureInterface->setAllocator || !captureInterface->onCaptureStart || !captureInterface->onCaptureEnd || !captureInterface->isCaptureEnabled || !captureInterface->onAlloc || !captureInterface->onInvalidate || !captureInterface->setScanbuffer || !captureInterface->onSwapScanBuffers || !captureInterface->submitToRing) { return FALSE; } sDebugCaptureData->captureInterface = *captureInterface; sDebugCaptureData->enabled = TRUE; cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.setAllocator, internal::getDefaultGx2rAlloc(), internal::getDefaultGx2rFree()); return TRUE; } /** * Start a debug capture. */ void GX2DebugCaptureStart(virt_ptr filename, BOOL noCallDrawDone) { if (!sDebugCaptureData->enabled) { return; } if (!noCallDrawDone) { GX2DrawDone(); sDebugCaptureData->previousSwapInterval = GX2GetSwapInterval(); GX2SetSwapInterval(0); } cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onCaptureStart, filename); internal::debugCaptureCbPoolPointers(); cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.setScanbuffer, internal::getTvScanBuffer(), internal::getDrcScanBuffer()); } /** * End a debug capture. */ void GX2DebugCaptureEnd(BOOL noCallFlush) { if (!sDebugCaptureData->enabled) { return; } if (!noCallFlush) { GX2Flush(); } internal::debugCaptureCbPoolPointersFree(); cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onCaptureEnd); GX2SetSwapInterval(sDebugCaptureData->previousSwapInterval); } /** * Capture the next frame. */ void GX2DebugCaptureFrame(virt_ptr filename) { if (!sDebugCaptureData->enabled) { return; } GX2DebugCaptureFrames(filename, 1); } /** * Capture the next n frames. */ void GX2DebugCaptureFrames(virt_ptr filename, uint32_t numFrames) { if (!sDebugCaptureData->enabled) { return; } string_copy(virt_addrof(sDebugCaptureData->pendingCaptureFilename).get(), filename.get(), sDebugCaptureData->pendingCaptureFilename.size() - 1); sDebugCaptureData->numCaptureFramesRemaining = numFrames; } /** * Insert a debug tag into the pm4 stream. * * Only written when debug capture is enabled. */ void GX2DebugTagUserString(GX2DebugTag tag, virt_ptr fmt, var_args va) { if (internal::debugCaptureEnabled()) { auto list = make_va_list(va); GX2DebugTagUserStringVA(tag, fmt, list); free_va_list(list); } } /** * Insert a debug tag into the pm4 stream. * * Only written when debug capture is enabled. */ void GX2DebugTagUserStringVA(GX2DebugTag tag, virt_ptr fmt, virt_ptr vaList) { if (internal::debugCaptureEnabled()) { auto buffer = StackArray { }; std::memset(buffer.get(), 0, 0x404); if (fmt) { coreinit::internal::formatStringV(buffer, 0x3FF, fmt, vaList); } // Convert string to words! auto length = static_cast(strlen(buffer.get())); auto numWords = align_up(length + 1, 4) / 4; // Write NOP packet internal::writePM4(latte::pm4::NopBE { GX2DebugTag::User | tag, gsl::make_span(virt_cast(buffer).get(), numWords) }); } } /** * Notify gx2 of a graphics memory allocation. */ void GX2NotifyMemAlloc(virt_ptr ptr, uint32_t size, uint32_t align) { if (internal::debugCaptureEnabled()) { internal::debugCaptureAlloc(ptr, size, align); } } /** * Notify gx2 of a graphics memory free. */ void GX2NotifyMemFree(virt_ptr ptr) { if (internal::debugCaptureEnabled()) { internal::debugCaptureFree(ptr); } } namespace internal { BOOL debugCaptureEnabled() { if (!sDebugCaptureData->enabled) { return FALSE; } return cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.isCaptureEnabled); } void debugCaptureAlloc(virt_ptr ptr, uint32_t size, uint32_t align) { cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onAlloc, ptr, size, align); } void debugCaptureFree(virt_ptr ptr) { cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onFree, ptr); } void debugCaptureInvalidate(virt_ptr ptr, uint32_t size) { cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onInvalidate, ptr, size); } void debugCaptureShutdown() { cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onShutdown); sDebugCaptureData->enabled = FALSE; } tcl::TCLStatus debugCaptureSubmit(virt_ptr buffer, uint32_t numWords, virt_ptr submitFlags, virt_ptr lastSubmittedTimestamp) { return cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.submitToRing, buffer, numWords, submitFlags, lastSubmittedTimestamp); } void debugCaptureSwap(virt_ptr tvScanBuffer, virt_ptr drcScanBuffer) { if (!sDebugCaptureData->enabled) { return; } auto enabled = debugCaptureEnabled(); if (!enabled) { // Check if we need to start a capture if (sDebugCaptureData->numCaptureFramesRemaining) { GX2DebugCaptureStart(virt_addrof(sDebugCaptureData->pendingCaptureFilename), FALSE); } return; } // Capture frame GX2DrawDone(); cafe::invoke(cpu::this_core::state(), sDebugCaptureData->captureInterface.onSwapScanBuffers, tvScanBuffer, drcScanBuffer); if (!sDebugCaptureData->numCaptureFramesRemaining) { return; } // Check if we need to end a capture if (sDebugCaptureData->numCaptureFramesRemaining == 1) { GX2DebugCaptureEnd(FALSE); } sDebugCaptureData->numCaptureFramesRemaining--; } void debugCaptureTagGroup(GX2DebugTag tagId, std::string_view str) { if (!sDebugCaptureData->enabled) { return; } auto id = tagId | GX2DebugTag::Group; if (str.empty()) { internal::writePM4(latte::pm4::Nop { id, { } }); } else { std::vector buffer; buffer.resize(align_up(str.size() + 1, 4) / 4, 0u); std::memcpy(buffer.data(), str.data(), str.size()); internal::writePM4(latte::pm4::NopBE { id, { reinterpret_cast *>(buffer.data()), buffer.size() } }); } } } // namespace internal void Library::registerDebugCaptureSymbols() { RegisterFunctionExportName("_GX2DebugSetCaptureInterface", GX2DebugSetCaptureInterface); RegisterFunctionExport(GX2DebugCaptureStart); RegisterFunctionExport(GX2DebugCaptureEnd); RegisterFunctionExport(GX2DebugCaptureFrame); RegisterFunctionExport(GX2DebugCaptureFrames); RegisterFunctionExport(GX2DebugTagUserString); RegisterFunctionExport(GX2DebugTagUserStringVA); RegisterFunctionExport(GX2NotifyMemAlloc); RegisterFunctionExport(GX2NotifyMemFree); RegisterDataInternal(sDebugCaptureData); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_debugcapture.h ================================================ #pragma once #include "gx2_enum.h" #include "gx2r_resource.h" #include "cafe/cafe_ppc_interface_varargs.h" #include "cafe/libraries/tcl/tcl_ring.h" #include #include #include namespace cafe::gx2 { struct GX2Surface; using GX2DebugCaptureInterfaceOnShutdownFn = virt_func_ptr; using GX2DebugCaptureInterfaceSetAllocatorFn = virt_func_ptr; using GX2DebugCaptureInterfaceOnCaptureStartFn = virt_func_ptr filename)>; using GX2DebugCaptureInterfaceOnCaptureEndFn = virt_func_ptr; using GX2DebugCaptureInterfaceIsCaptureEnabledFn = virt_func_ptr; using GX2DebugCaptureInterfaceOnAllocFn = virt_func_ptr ptr, uint32_t size, uint32_t align)>; using GX2DebugCaptureInterfaceOnFreeFn = virt_func_ptr ptr)>; using GX2DebugCaptureInterfaceOnInvalidateFn = virt_func_ptr ptr, uint32_t size)>; // Note: Real API from gx2.rpl does not actually pass drc scanbuffer, but it // would be useful for our pm4 capture to have it. using GX2DebugCaptureInterfaceSetScanbufferFn = virt_func_ptr tvScanbuffer, virt_ptr drcScanbuffer)>; using GX2DebugCaptureInterfaceOnSwapFn = virt_func_ptr tvScanbuffer, virt_ptr drcScanbuffer)>; using GX2DebugCaptureInterfaceSubmitFn = virt_func_ptr buffer, uint32_t numWords, virt_ptr submitFlags, virt_ptr lastSubmittedTimestamp)>; struct GX2DebugCaptureInterface { //! Must be set to GX2DebugCaptureInterfaceVersion::Version1 be2_val version; //! Called from GX2Shutdown. be2_val onShutdown; //! Called from GX2DebugSetCaptureInterface with the default gx2 allocators. be2_val setAllocator; //! Called from GX2DebugCaptureStart, the filename is first argument passed //! in to GX2DebugCaptureStart. be2_val onCaptureStart; //! Called from GX2DebugCaptureEnd. be2_val onCaptureEnd; //! Check if capture is enabled. be2_val isCaptureEnabled; //! Called when graphics memory is allocated. be2_val onAlloc; //! Called when graphics memory is freed. be2_val onFree; //! Called when graphics memory is invalidated. be2_val onInvalidate; //! Called from GX2DebugCaptureStart with the TV scan buffer. be2_val setScanbuffer; //! Called from GX2SwapScanBuffers with the TV scan buffer. be2_val onSwapScanBuffers; //! Called when a command buffer is ready to be submitted to ring buffer. //! Note that it seems we must call TCLSubmitToRing from this callback //! because gx2 will not do it when capturing. be2_val submitToRing; }; CHECK_OFFSET(GX2DebugCaptureInterface, 0x00, version); CHECK_OFFSET(GX2DebugCaptureInterface, 0x04, onShutdown); CHECK_OFFSET(GX2DebugCaptureInterface, 0x08, setAllocator); CHECK_OFFSET(GX2DebugCaptureInterface, 0x0C, onCaptureStart); CHECK_OFFSET(GX2DebugCaptureInterface, 0x10, onCaptureEnd); CHECK_OFFSET(GX2DebugCaptureInterface, 0x14, isCaptureEnabled); CHECK_OFFSET(GX2DebugCaptureInterface, 0x18, onAlloc); CHECK_OFFSET(GX2DebugCaptureInterface, 0x1C, onFree); CHECK_OFFSET(GX2DebugCaptureInterface, 0x20, onInvalidate); CHECK_OFFSET(GX2DebugCaptureInterface, 0x24, setScanbuffer); CHECK_OFFSET(GX2DebugCaptureInterface, 0x28, onSwapScanBuffers); CHECK_OFFSET(GX2DebugCaptureInterface, 0x2C, submitToRing); CHECK_SIZE(GX2DebugCaptureInterface, 0x30); BOOL GX2DebugSetCaptureInterface(virt_ptr captureInterface); void GX2DebugCaptureStart(virt_ptr filename, BOOL noCallDrawDone); void GX2DebugCaptureEnd(BOOL noCallDrawDone); void GX2DebugCaptureFrame(virt_ptr filename); void GX2DebugCaptureFrames(virt_ptr filename, uint32_t numFrames); void GX2DebugTagUserString(GX2DebugTag tag, virt_ptr fmt, var_args va); void GX2DebugTagUserStringVA(GX2DebugTag tag, virt_ptr fmt, virt_ptr vaList); void GX2NotifyMemAlloc(virt_ptr ptr, uint32_t size, uint32_t align); void GX2NotifyMemFree(virt_ptr ptr); namespace internal { BOOL debugCaptureEnabled(); void debugCaptureAlloc(virt_ptr ptr, uint32_t size, uint32_t align); void debugCaptureFree(virt_ptr ptr); void debugCaptureInvalidate(virt_ptr ptr, uint32_t size); void debugCaptureShutdown(); tcl::TCLStatus debugCaptureSubmit(virt_ptr buffer, uint32_t numWords, virt_ptr submitFlags, virt_ptr lastSubmittedTimestamp); void debugCaptureSwap(virt_ptr tvScanbuffer, virt_ptr drcScanbuffer); void debugCaptureTagGroup(GX2DebugTag tagId, std::string_view str = {}); template inline void debugCaptureTagGroup(GX2DebugTag tagId, const char *fmt, Args &&... args) { if (debugCaptureEnabled()) { debugCaptureTagGroup(tagId, std::string_view { fmt::format(fmt, args...) }); } } } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_display.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_debug.h" #include "gx2_debugcapture.h" #include "gx2_internal_pm4cap.h" #include "gx2_display.h" #include "gx2_enum_string.h" #include "gx2_event.h" #include "gx2_format.h" #include "gx2_surface.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include #include namespace cafe::gx2 { using namespace cafe::coreinit; struct StaticDisplayData { be2_struct tvScanBuffer; be2_struct drcScanBuffer; be2_val tvScanMode = GX2TVScanMode::P1080; be2_val tvRenderMode = GX2TVRenderMode::Disabled; be2_val tvBufferingMode; be2_val drcRenderMode = GX2DrcRenderMode::Disabled; be2_val drcBufferingMode; be2_val drcConnectCallback; be2_val swapInterval = 1u; }; static virt_ptr sDisplayData = nullptr; static std::pair getTVSize(GX2TVRenderMode mode) { switch (mode) { case GX2TVRenderMode::Standard480p: return { 640, 480 }; case GX2TVRenderMode::Wide480p: return { 854, 480 }; case GX2TVRenderMode::Wide720p: return { 1280, 720 }; case GX2TVRenderMode::Unk720p: return { 1280, 720 }; case GX2TVRenderMode::Wide1080p: return { 1920, 1080 }; default: decaf_abort(fmt::format("Invalid GX2TVRenderMode {}", to_string(mode))); } } static unsigned getBpp(GX2SurfaceFormat format) { auto bpp = internal::getSurfaceFormatBytesPerElement(format); decaf_assert(bpp > 0, fmt::format("Unexpected GX2SurfaceFormat {}", to_string(format))); return bpp; } static unsigned getNumBuffers(GX2BufferingMode mode) { switch (mode) { case GX2BufferingMode::Single: return 1; case GX2BufferingMode::Double: return 2; case GX2BufferingMode::Triple: return 3; default: decaf_abort(fmt::format("Invalid GX2BufferingMode {}", to_string(mode))); } } static void initialiseScanBuffer(virt_ptr buffer, uint32_t width, uint32_t height, GX2SurfaceFormat format) { std::memset(buffer.get(), 0, sizeof(GX2ColorBuffer)); buffer->surface.use = GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::Texture; buffer->surface.width = width; buffer->surface.height = height; buffer->surface.mipLevels = 1u; buffer->surface.dim = GX2SurfaceDim::Texture2D; buffer->surface.swizzle = 0u; buffer->surface.depth = 1u; buffer->surface.tileMode = GX2TileMode::Default; buffer->surface.format = format; buffer->surface.mipmaps = nullptr; buffer->surface.aa = GX2AAMode::Mode1X; buffer->viewFirstSlice = 0u; buffer->viewNumSlices = 1u; buffer->viewMip = 0u; GX2CalcSurfaceSizeAndAlignment(virt_addrof(buffer->surface)); GX2InitColorBufferRegs(buffer); buffer->surface.use |= GX2SurfaceUse::ScanBuffer; } void GX2CalcDRCSize(GX2DrcRenderMode drcRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode, virt_ptr outSize, virt_ptr outUnk) { auto bytesPerPixel = internal::getSurfaceFormatBytesPerElement(surfaceFormat); auto numBuffers = getNumBuffers(bufferingMode); *outSize = 864 * 480 * bytesPerPixel * numBuffers; *outUnk = 0u; } void GX2CalcTVSize(GX2TVRenderMode tvRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode, virt_ptr outSize, virt_ptr outUnk) { unsigned width, height; std::tie(width, height) = getTVSize(tvRenderMode); auto bytesPerPixel = getBpp(surfaceFormat); auto numBuffers = getNumBuffers(bufferingMode); *outSize = width * height * bytesPerPixel * numBuffers; *outUnk = 0u; } void GX2CopyColorBufferToScanBuffer(virt_ptr buffer, GX2ScanTarget scanTarget) { internal::debugCaptureTagGroup(GX2DebugTag::CopyColorBufferToScanBuffer, "{}, {}", buffer, scanTarget); auto addrImage = OSEffectiveToPhysical(virt_cast(buffer->surface.image)); auto cb_color_frag = latte::CB_COLORN_FRAG::get(0); auto cb_color_base = latte::CB_COLORN_BASE::get(0) .BASE_256B(addrImage >> 8); if (buffer->surface.aa != 0) { auto addrAA = OSEffectiveToPhysical(virt_cast(buffer->aaBuffer)); cb_color_frag = cb_color_frag.BASE_256B(addrAA >> 8); } // TODO: We should check this function, this was added // as a temporary solution to new crashes. if (buffer->viewNumSlices == 0) { buffer->viewNumSlices = 1u; } GX2InitColorBufferRegs(buffer); internal::writePM4(latte::pm4::DecafCopyColorToScan { latte::pm4::ScanTarget(scanTarget), cb_color_base, cb_color_frag, buffer->surface.width, buffer->surface.height, buffer->regs.cb_color_size, buffer->regs.cb_color_info, buffer->regs.cb_color_view, buffer->regs.cb_color_mask }); internal::debugCaptureTagGroup(GX2DebugTag::CopyColorBufferToScanBuffer, "{}, {}", buffer, scanTarget); } BOOL GX2GetLastFrame(GX2ScanTarget scanTarget, virt_ptr texture) { return FALSE; } BOOL GX2GetLastFrameB(GX2ScanTarget scanTarget, virt_ptr texture) { return FALSE; } BOOL GX2GetLastFrameGamma(GX2ScanTarget scanTarget, virt_ptr outGamma) { return FALSE; } BOOL GX2GetLastFrameGammaA(GX2ScanTarget scanTarget, virt_ptr outGamma) { return GX2GetLastFrameGamma(scanTarget, outGamma); } BOOL GX2GetLastFrameGammaB(GX2ScanTarget scanTarget, virt_ptr outGamma) { return FALSE; } GX2TVScanMode GX2GetSystemTVScanMode() { return sDisplayData->tvScanMode; } GX2DrcRenderMode GX2GetSystemDRCMode() { return sDisplayData->drcRenderMode; } GX2AspectRatio GX2GetSystemTVAspectRatio() { switch (sDisplayData->tvScanMode) { case GX2TVScanMode::None: case GX2TVScanMode::I480: case GX2TVScanMode::P480: return GX2AspectRatio::Normal; case GX2TVScanMode::P720: case GX2TVScanMode::I1080: case GX2TVScanMode::P1080: return GX2AspectRatio::Widescreen; default: decaf_abort(fmt::format("Invalid GX2TVScanMode {}", to_string(sDisplayData->tvScanMode))); } } uint32_t GX2GetSwapInterval() { return sDisplayData->swapInterval; } BOOL GX2IsVideoOutReady() { return TRUE; } void GX2SetDRCBuffer(virt_ptr buffer, uint32_t size, GX2DrcRenderMode drcRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode) { constexpr auto width = 854u, height = 480u; initialiseScanBuffer(virt_addrof(sDisplayData->drcScanBuffer), width, height, surfaceFormat); sDisplayData->drcScanBuffer.surface.image = virt_cast(buffer); sDisplayData->drcRenderMode = drcRenderMode; sDisplayData->drcBufferingMode = bufferingMode; // Using a command buffer is to communicate this data to the GPU is our // decaf specific hack, therefore we must write it directly instead of // infecting the GX2 command buffer with our fake command. std::array commandBuffer; auto commandBufferPos = 0u; internal::writePM4(commandBuffer.data(), commandBufferPos, latte::pm4::DecafSetBuffer { latte::pm4::ScanTarget::DRC, OSEffectiveToPhysical(virt_cast(buffer)), bufferingMode, // bufferingMode is conveniently equal to the number of buffers static_cast(width), static_cast(height) }); gpu::ringbuffer::write({ commandBuffer.data(), commandBufferPos }); } GX2DRCConnectCallbackFunction GX2SetDRCConnectCallback(uint32_t id, GX2DRCConnectCallbackFunction callback) { auto old = sDisplayData->drcConnectCallback; sDisplayData->drcConnectCallback = callback; if (callback) { cafe::invoke(cpu::this_core::state(), callback, id, TRUE); } return old; } void GX2SetDRCEnable(BOOL enable) { } void GX2SetDRCScale(uint32_t x, uint32_t y) { } void GX2SetSwapInterval(uint32_t interval) { if (interval == sDisplayData->swapInterval) { return; } sDisplayData->swapInterval = interval; } void GX2SetTVBuffer(virt_ptr buffer, uint32_t size, GX2TVRenderMode tvRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode) { unsigned width, height; std::tie(width, height) = getTVSize(tvRenderMode); initialiseScanBuffer(virt_addrof(sDisplayData->tvScanBuffer), width, height, surfaceFormat); sDisplayData->tvScanBuffer.surface.image = virt_cast(buffer); sDisplayData->tvRenderMode = tvRenderMode; sDisplayData->tvBufferingMode = bufferingMode; /* auto pitch = width; AVMSetTVScale(width, height); AVMSetTVBufferAttr(bufferingMode, tvRenderMode, pitch); */ // Using a command buffer is to communicate this data to the GPU is our // decaf specific hack, therefore we must write it directly instead of // infecting the GX2 command buffer with our fake command. std::array commandBuffer; auto commandBufferPos = 0u; internal::writePM4(commandBuffer.data(), commandBufferPos, latte::pm4::DecafSetBuffer { latte::pm4::ScanTarget::TV, OSEffectiveToPhysical(virt_cast(buffer)), bufferingMode, // bufferingMode is conveniently equal to the number of buffers static_cast(width), static_cast(height) }); gpu::ringbuffer::write({ commandBuffer.data(), commandBufferPos }); } void GX2SetTVEnable(BOOL enable) { } void GX2SetTVScale(uint32_t x, uint32_t y) { } void GX2SwapScanBuffers() { internal::debugCaptureTagGroup(GX2DebugTag::SwapScanBuffers); internal::onSwap(); internal::writePM4(latte::pm4::DecafSwapBuffers { }); internal::debugCaptureTagGroup(GX2DebugTag::SwapScanBuffers); internal::captureSwap(); internal::debugCaptureSwap(virt_addrof(sDisplayData->tvScanBuffer.surface), virt_addrof(sDisplayData->drcScanBuffer.surface)); } namespace internal { virt_ptr getTvScanBuffer() { return virt_addrof(sDisplayData->tvScanBuffer.surface); } virt_ptr getDrcScanBuffer() { return virt_addrof(sDisplayData->drcScanBuffer.surface); } } // namespace internal void Library::registerDisplaySymbols() { RegisterFunctionExport(GX2CalcDRCSize); RegisterFunctionExport(GX2CalcTVSize); RegisterFunctionExport(GX2CopyColorBufferToScanBuffer); RegisterFunctionExport(GX2GetLastFrame); RegisterFunctionExportName("_GX2GetLastFrameB", GX2GetLastFrameB); RegisterFunctionExport(GX2GetLastFrameGamma); RegisterFunctionExport(GX2GetLastFrameGammaA); RegisterFunctionExport(GX2GetLastFrameGammaB); RegisterFunctionExport(GX2GetSystemTVScanMode); RegisterFunctionExport(GX2GetSystemDRCMode); RegisterFunctionExport(GX2GetSystemTVAspectRatio); RegisterFunctionExport(GX2GetSwapInterval); RegisterFunctionExport(GX2IsVideoOutReady); RegisterFunctionExport(GX2SetDRCBuffer); RegisterFunctionExport(GX2SetDRCConnectCallback); RegisterFunctionExport(GX2SetDRCEnable); RegisterFunctionExport(GX2SetDRCScale); RegisterFunctionExport(GX2SetSwapInterval); RegisterFunctionExport(GX2SetTVBuffer); RegisterFunctionExport(GX2SetTVEnable); RegisterFunctionExport(GX2SetTVScale); RegisterFunctionExport(GX2SwapScanBuffers); RegisterDataInternal(sDisplayData); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_display.h ================================================ #pragma once #include "gx2_enum.h" #include "gx2_surface.h" #include "gx2_texture.h" #include namespace cafe::gx2 { using GX2DRCConnectCallbackFunction = virt_func_ptr; void GX2CalcTVSize(GX2TVRenderMode tvRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode, virt_ptr outSize, virt_ptr outUnk); void GX2CalcDRCSize(GX2DrcRenderMode drcRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode, virt_ptr outSize, virt_ptr outUnk); void GX2CopyColorBufferToScanBuffer(virt_ptr buffer, GX2ScanTarget scanTarget); BOOL GX2GetLastFrame(GX2ScanTarget scanTarget, virt_ptr texture); BOOL GX2GetLastFrameB(GX2ScanTarget scanTarget, virt_ptr texture); BOOL GX2GetLastFrameGamma(GX2ScanTarget scanTarget, virt_ptr outGamma); BOOL GX2GetLastFrameGammaA(GX2ScanTarget scanTarget, virt_ptr outGamma); BOOL GX2GetLastFrameGammaB(GX2ScanTarget scanTarget, virt_ptr outGamma); GX2TVScanMode GX2GetSystemTVScanMode(); GX2DrcRenderMode GX2GetSystemDRCMode(); GX2AspectRatio GX2GetSystemTVAspectRatio(); uint32_t GX2GetSwapInterval(); BOOL GX2IsVideoOutReady(); void GX2SetDRCBuffer(virt_ptr buffer, uint32_t size, GX2DrcRenderMode drcRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode); GX2DRCConnectCallbackFunction GX2SetDRCConnectCallback(uint32_t id, GX2DRCConnectCallbackFunction callback); void GX2SetDRCEnable(BOOL enable); void GX2SetDRCScale(uint32_t x, uint32_t y); void GX2SetSwapInterval(uint32_t interval); void GX2SetTVBuffer(virt_ptr buffer, uint32_t size, GX2TVRenderMode tvRenderMode, GX2SurfaceFormat surfaceFormat, GX2BufferingMode bufferingMode); void GX2SetTVEnable(BOOL enable); void GX2SetTVScale(uint32_t x, uint32_t y); void GX2SwapScanBuffers(); namespace internal { virt_ptr getTvScanBuffer(); virt_ptr getDrcScanBuffer(); } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_displaylist.cpp ================================================ #include "gx2.h" #include "gx2_debugcapture.h" #include "gx2_displaylist.h" #include "gx2_enum_string.h" #include "gx2_fetchshader.h" #include "gx2_cbpool.h" #include "gx2_shaders.h" #include "gx2_state.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include #include namespace cafe::gx2 { using namespace cafe::coreinit; void GX2BeginDisplayListEx(virt_ptr displayList, uint32_t bytes, BOOL profilingEnabled) { internal::beginUserCommandBuffer(virt_cast(displayList), bytes, profilingEnabled); } uint32_t GX2EndDisplayList(virt_ptr displayList) { auto size = internal::endUserCommandBuffer(virt_cast(displayList)); if (internal::debugCaptureEnabled()) { internal::debugCaptureInvalidate(displayList, size); } return size; } BOOL GX2GetDisplayListWriteStatus() { return internal::getActiveCommandBuffer()->isUserBuffer; } BOOL GX2GetCurrentDisplayList(virt_ptr> outDisplayList, virt_ptr outSize) { auto cb = internal::getActiveCommandBuffer(); if (!cb->isUserBuffer) { return FALSE; } if (outDisplayList) { *outDisplayList = cb->buffer; } if (outSize) { *outSize = 4 * cb->bufferSizeWords; } return TRUE; } void GX2DirectCallDisplayList(virt_ptr displayList, uint32_t bytes) { auto cb = internal::getActiveCommandBuffer(); if (!cb->isUserBuffer) { internal::flushCommandBuffer(256, FALSE); } internal::queueCommandBuffer(virt_cast(displayList), bytes / sizeof(uint32_t), nullptr, TRUE); } void GX2CallDisplayList(virt_ptr displayList, uint32_t bytes) { internal::writePM4(latte::pm4::IndirectBufferCallPriv { OSEffectiveToPhysical(virt_cast(displayList)), bytes / 4 }); } void GX2CopyDisplayList(virt_ptr displayList, uint32_t bytes) { auto numWords = bytes / 4; auto cb = internal::getWriteCommandBuffer(numWords); cb->writeGatherPtr.write(virt_cast(displayList), numWords); cb->cmdSize = numWords; decaf_check(cb->cmdSize == cb->cmdSizeTarget); } void GX2PatchDisplayList(virt_ptr displayList, GX2PatchShaderType type, uint32_t byteOffset, virt_ptr shader) { auto addr = virt_addr { 0u }; switch (type) { case GX2PatchShaderType::FetchShader: { auto fetchShader = virt_cast(shader); addr = virt_cast(fetchShader->data); break; } case GX2PatchShaderType::VertexShader: { auto vertexShader = virt_cast(shader); if (vertexShader->data) { addr = virt_cast(vertexShader->data); } else { addr = virt_cast(vertexShader->gx2rData.buffer); } break; } case GX2PatchShaderType::GeometryVertexShader: { auto geometryShader = virt_cast(shader); if (geometryShader->vertexShaderData) { addr = virt_cast(geometryShader->vertexShaderData); } else { addr = virt_cast(geometryShader->gx2rVertexShaderData.buffer); } break; } case GX2PatchShaderType::GeometryShader: { auto geometryShader = virt_cast(shader); if (geometryShader->data) { addr = virt_cast(geometryShader->data); } else { addr = virt_cast(geometryShader->gx2rData.buffer); } break; } case GX2PatchShaderType::PixelShader: { auto pixelShader = virt_cast(shader); if (pixelShader->data) { addr = virt_cast(pixelShader->data); } else { addr = virt_cast(pixelShader->gx2rData.buffer); } break; } default: decaf_abort(fmt::format("Unsupported GX2PatchShaderType {}", to_string(type))); } // Apply the actual patch auto words = virt_cast(displayList); auto idx = byteOffset / 4; words[idx + 2] = OSEffectiveToPhysical(addr) >> 8; } void Library::registerDisplayListSymbols() { RegisterFunctionExport(GX2BeginDisplayListEx); RegisterFunctionExport(GX2EndDisplayList); RegisterFunctionExport(GX2DirectCallDisplayList); RegisterFunctionExport(GX2CallDisplayList); RegisterFunctionExport(GX2GetDisplayListWriteStatus); RegisterFunctionExport(GX2GetCurrentDisplayList); RegisterFunctionExport(GX2CopyDisplayList); RegisterFunctionExport(GX2PatchDisplayList); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_displaylist.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2_displaylist Display List * \ingroup gx2 * @{ */ constexpr auto GX2DisplayListAlign = 0x20u; void GX2BeginDisplayListEx(virt_ptr displayList, uint32_t bytes, BOOL profilingEnabled); uint32_t GX2EndDisplayList(virt_ptr displayList); void GX2DirectCallDisplayList(virt_ptr displayList, uint32_t bytes); void GX2CallDisplayList(virt_ptr displayList, uint32_t bytes); BOOL GX2GetDisplayListWriteStatus(); BOOL GX2GetCurrentDisplayList(virt_ptr> outDisplayList, virt_ptr outSize); void GX2CopyDisplayList(virt_ptr displayList, uint32_t bytes); void GX2PatchDisplayList(virt_ptr displayList, GX2PatchShaderType type, uint32_t byteOffset, virt_ptr shader); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_draw.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_draw.h" #include "gx2_enum_string.h" #include "gx2_shaders.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include namespace cafe::gx2 { using namespace cafe::coreinit; void GX2SetAttribBuffer(uint32_t index, uint32_t size, uint32_t stride, virt_ptr buffer) { latte::pm4::SetVtxResource res; std::memset(&res, 0, sizeof(latte::pm4::SetVtxResource)); res.id = (latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + index) * 7; res.baseAddress = OSEffectiveToPhysical(virt_cast(buffer)); res.word1 = res.word1 .SIZE(size - 1); res.word2 = res.word2 .STRIDE(stride); res.word6 = res.word6 .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER); internal::writePM4(res); } void GX2DrawEx(GX2PrimitiveMode mode, uint32_t count, uint32_t offset, uint32_t numInstances) { auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0); internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_BASE_VTX_LOC, offset }); internal::writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_PRIMITIVE_TYPE, mode }); internal::writePM4(latte::pm4::NumInstances { numInstances }); internal::writePM4(latte::pm4::DrawIndexAuto { count, vgt_draw_initiator }); } void GX2DrawEx2(GX2PrimitiveMode mode, uint32_t count, uint32_t offset, uint32_t numInstances, uint32_t baseInstance) { internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_START_INST_LOC, latte::SQ_VTX_START_INST_LOC::get(0) .OFFSET(baseInstance) .value }); GX2DrawEx(mode, count, offset, numInstances); internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_START_INST_LOC, latte::SQ_VTX_START_INST_LOC::get(0) .OFFSET(0) .value }); } void GX2DrawIndexedEx(GX2PrimitiveMode mode, uint32_t count, GX2IndexType indexType, virt_ptr indices, uint32_t offset, uint32_t numInstances) { auto index_type = latte::VGT_INDEX_TYPE::INDEX_16; auto swap_mode = latte::VGT_DMA_SWAP::NONE; switch (indexType) { case GX2IndexType::U16: index_type = latte::VGT_INDEX_TYPE::INDEX_16; swap_mode = latte::VGT_DMA_SWAP::SWAP_16_BIT; break; case GX2IndexType::U16_LE: index_type = latte::VGT_INDEX_TYPE::INDEX_16; swap_mode = latte::VGT_DMA_SWAP::NONE; break; case GX2IndexType::U32: index_type = latte::VGT_INDEX_TYPE::INDEX_32; swap_mode = latte::VGT_DMA_SWAP::SWAP_32_BIT; break; case GX2IndexType::U32_LE: index_type = latte::VGT_INDEX_TYPE::INDEX_32; swap_mode = latte::VGT_DMA_SWAP::NONE; break; default: decaf_abort(fmt::format("Invalid GX2IndexType {}", to_string(indexType))); } auto vgt_dma_index_type = latte::VGT_DMA_INDEX_TYPE::get(0) .INDEX_TYPE(index_type) .SWAP_MODE(swap_mode); auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0) .SOURCE_SELECT(latte::VGT_DI_SRC_SEL::DMA); if (mode & 0x80) { vgt_draw_initiator = vgt_draw_initiator .MAJOR_MODE(latte::VGT_DI_MAJOR_MODE::MODE1); } internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_BASE_VTX_LOC, offset }); internal::writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_PRIMITIVE_TYPE, mode }); internal::writePM4(latte::pm4::IndexType { vgt_dma_index_type }); internal::writePM4(latte::pm4::NumInstances { numInstances }); internal::writePM4(latte::pm4::DrawIndex2 { static_cast(-1), OSEffectiveToPhysical(virt_cast(indices)), count, vgt_draw_initiator }); } void GX2DrawIndexedEx2(GX2PrimitiveMode mode, uint32_t count, GX2IndexType indexType, virt_ptr indices, uint32_t offset, uint32_t numInstances, uint32_t baseInstance) { internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_START_INST_LOC, latte::SQ_VTX_START_INST_LOC::get(0) .OFFSET(baseInstance) .value }); GX2DrawIndexedEx(mode, count, indexType, indices, offset, numInstances); internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_START_INST_LOC, latte::SQ_VTX_START_INST_LOC::get(0) .OFFSET(0) .value }); } void GX2DrawIndexedImmediateEx(GX2PrimitiveMode mode, uint32_t count, GX2IndexType indexType, virt_ptr indices, uint32_t offset, uint32_t numInstances) { auto index_type = latte::VGT_INDEX_TYPE::INDEX_16; auto swap_mode = latte::VGT_DMA_SWAP::NONE; switch (indexType) { case GX2IndexType::U16: index_type = latte::VGT_INDEX_TYPE::INDEX_16; swap_mode = latte::VGT_DMA_SWAP::SWAP_16_BIT; break; case GX2IndexType::U16_LE: index_type = latte::VGT_INDEX_TYPE::INDEX_16; swap_mode = latte::VGT_DMA_SWAP::NONE; break; case GX2IndexType::U32: index_type = latte::VGT_INDEX_TYPE::INDEX_32; swap_mode = latte::VGT_DMA_SWAP::SWAP_32_BIT; break; case GX2IndexType::U32_LE: index_type = latte::VGT_INDEX_TYPE::INDEX_32; swap_mode = latte::VGT_DMA_SWAP::NONE; break; default: decaf_abort(fmt::format("Invalid GX2IndexType {}", to_string(indexType))); } auto vgt_dma_index_type = latte::VGT_DMA_INDEX_TYPE::get(0) .INDEX_TYPE(index_type) .SWAP_MODE(swap_mode); auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0) .SOURCE_SELECT(latte::VGT_DI_SRC_SEL::IMMEDIATE); internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_BASE_VTX_LOC, offset }); internal::writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_PRIMITIVE_TYPE, mode }); internal::writePM4(latte::pm4::IndexType { vgt_dma_index_type }); internal::writePM4(latte::pm4::NumInstances { numInstances }); auto numWords = 0u; if (index_type == latte::VGT_INDEX_TYPE::INDEX_16) { numWords = (count + 1) / 2; } else if (index_type == latte::VGT_INDEX_TYPE::INDEX_32) { numWords = count; } else { decaf_abort(fmt::format("Invalid index_type {}", index_type)); } if (indexType == GX2IndexType::U16) { internal::writePM4(latte::pm4::DrawIndexImmdBE16 { count, vgt_draw_initiator, gsl::make_span(virt_cast(indices).get(), numWords) }); } else { internal::writePM4(latte::pm4::DrawIndexImmdBE { count, vgt_draw_initiator, gsl::make_span(virt_cast(indices).get(), numWords), }); } } void GX2DrawStreamOut(GX2PrimitiveMode mode, virt_ptr buffer) { internal::writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_BASE_VTX_LOC, 0u }); internal::writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_PRIMITIVE_TYPE, mode & GX2PrimitiveModeFlags::ModeMask }); internal::writePM4(latte::pm4::NumInstances { 0 }); auto stride = 0u; if (buffer->buffer) { stride = buffer->stride; } else { stride = buffer->gx2rData.elemSize; } internal::writePM4(latte::pm4::SetContextReg { latte::Register::VGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE, stride >> 2 }); internal::writePM4(latte::pm4::CopyDw { latte::pm4::COPY_DW_SELECT::get(0) .SRC(latte::pm4::COPY_DW_SEL_MEMORY) .DST(latte::pm4::COPY_DW_SEL_REGISTER), OSEffectiveToPhysical(virt_cast(buffer->context)), 0u, latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE, 0u }); auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0) .SOURCE_SELECT(latte::VGT_DI_SRC_SEL::AUTO_INDEX) .USE_OPAQUE(true); if (mode & GX2PrimitiveModeFlags::Tessellate) { internal::writePM4(latte::pm4::IndexType { latte::VGT_DMA_INDEX_TYPE::get(0) .INDEX_TYPE(latte::VGT_INDEX_TYPE::INDEX_32) .SWAP_MODE(latte::VGT_DMA_SWAP::SWAP_32_BIT) }); vgt_draw_initiator = vgt_draw_initiator .MAJOR_MODE(latte::VGT_DI_MAJOR_MODE::MODE1); } // TODO: This type3 packet should have the predicate bool set to true internal::writePM4(latte::pm4::DrawIndexAuto { 0u, vgt_draw_initiator }); } void GX2SetPrimitiveRestartIndex(uint32_t index) { internal::writePM4(latte::pm4::SetContextReg { latte::Register::VGT_MULTI_PRIM_IB_RESET_INDX, index }); } void Library::registerDrawSymbols() { RegisterFunctionExport(GX2SetAttribBuffer); RegisterFunctionExport(GX2DrawEx); RegisterFunctionExport(GX2DrawEx2); RegisterFunctionExport(GX2DrawIndexedEx); RegisterFunctionExport(GX2DrawIndexedEx2); RegisterFunctionExport(GX2DrawIndexedImmediateEx); RegisterFunctionExport(GX2DrawStreamOut); RegisterFunctionExport(GX2SetPrimitiveRestartIndex); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_draw.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { struct GX2OutputStream; /** * \defgroup gx2_draw Draw * \ingroup gx2 * @{ */ void GX2SetAttribBuffer(uint32_t index, uint32_t size, uint32_t stride, virt_ptr buffer); void GX2DrawEx(GX2PrimitiveMode mode, uint32_t count, uint32_t offset, uint32_t numInstances); void GX2DrawEx2(GX2PrimitiveMode mode, uint32_t count, uint32_t offset, uint32_t numInstances, uint32_t baseInstance); void GX2DrawIndexedEx(GX2PrimitiveMode mode, uint32_t count, GX2IndexType indexType, virt_ptr indices, uint32_t offset, uint32_t numInstances); void GX2DrawIndexedEx2(GX2PrimitiveMode mode, uint32_t count, GX2IndexType indexType, virt_ptr indices, uint32_t offset, uint32_t numInstances, uint32_t baseInstance); void GX2DrawIndexedImmediateEx(GX2PrimitiveMode mode, uint32_t count, GX2IndexType indexType, virt_ptr indices, uint32_t offset, uint32_t numInstances); void GX2DrawStreamOut(GX2PrimitiveMode mode, virt_ptr buffer); void GX2SetPrimitiveRestartIndex(uint32_t index); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_enum.h ================================================ #ifndef CAFE_GX2_ENUM_H #define CAFE_GX2_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(gx2) ENUM_BEG(GX2AAMode, uint32_t) ENUM_VALUE(Mode1X, 0) ENUM_VALUE(Mode2X, 1) ENUM_VALUE(Mode4X, 2) ENUM_VALUE(Mode8X, 3) ENUM_END(GX2AAMode) ENUM_BEG(GX2AspectRatio, uint32_t) ENUM_VALUE(Normal, 0) ENUM_VALUE(Widescreen, 1) ENUM_END(GX2AspectRatio) ENUM_BEG(GX2AttribFormatType, uint32_t) ENUM_VALUE(TYPE_8, 0x00) ENUM_VALUE(TYPE_4_4, 0x01) ENUM_VALUE(TYPE_16, 0x02) ENUM_VALUE(TYPE_16_FLOAT, 0x03) ENUM_VALUE(TYPE_8_8, 0x04) ENUM_VALUE(TYPE_32, 0x05) ENUM_VALUE(TYPE_32_FLOAT, 0x06) ENUM_VALUE(TYPE_16_16, 0x07) ENUM_VALUE(TYPE_16_16_FLOAT, 0x08) ENUM_VALUE(TYPE_10_11_11_FLOAT, 0x09) ENUM_VALUE(TYPE_8_8_8_8, 0x0A) ENUM_VALUE(TYPE_10_10_10_2, 0x0B) ENUM_VALUE(TYPE_32_32, 0x0C) ENUM_VALUE(TYPE_32_32_FLOAT, 0x0D) ENUM_VALUE(TYPE_16_16_16_16, 0x0E) ENUM_VALUE(TYPE_16_16_16_16_FLOAT, 0x0F) ENUM_VALUE(TYPE_32_32_32, 0x10) ENUM_VALUE(TYPE_32_32_32_FLOAT, 0x11) ENUM_VALUE(TYPE_32_32_32_32, 0x12) ENUM_VALUE(TYPE_32_32_32_32_FLOAT, 0x13) ENUM_END(GX2AttribFormatType) ENUM_BEG(GX2AttribFormatFlags, uint32_t) ENUM_VALUE(INTEGER, 0x100) ENUM_VALUE(SIGNED, 0x200) ENUM_VALUE(DEGAMMA, 0x400) ENUM_VALUE(SCALED, 0x800) ENUM_END(GX2AttribFormatFlags) ENUM_BEG(GX2AttribFormat, uint32_t) ENUM_VALUE(UNORM_8, 0x0) ENUM_VALUE(UNORM_8_8, 0x04) ENUM_VALUE(UNORM_8_8_8_8, 0x0a) ENUM_VALUE(UINT_8, 0x100) ENUM_VALUE(UINT_8_8, 0x104) ENUM_VALUE(UINT_8_8_8_8, 0x10a) ENUM_VALUE(SNORM_8, 0x200) ENUM_VALUE(SNORM_8_8, 0x204) ENUM_VALUE(SNORM_8_8_8_8, 0x20a) ENUM_VALUE(SINT_8, 0x300) ENUM_VALUE(SINT_8_8, 0x304) ENUM_VALUE(SINT_8_8_8_8, 0x30a) ENUM_VALUE(FLOAT_32, 0x806) ENUM_VALUE(FLOAT_32_32, 0x80d) ENUM_VALUE(FLOAT_32_32_32, 0x811) ENUM_VALUE(FLOAT_32_32_32_32, 0x813) ENUM_END(GX2AttribFormat) ENUM_BEG(GX2AttribIndexType, uint32_t) ENUM_VALUE(PerVertex, 0) ENUM_VALUE(PerInstance, 1) ENUM_END(GX2AttribIndexType) ENUM_BEG(GX2AlphaToMaskMode, uint32_t) ENUM_VALUE(NonDithered, 0) ENUM_VALUE(Dither0, 1) ENUM_VALUE(Dither90, 2) ENUM_VALUE(Dither180, 3) ENUM_VALUE(Dither270, 4) ENUM_END(GX2AlphaToMaskMode) ENUM_BEG(GX2BlendMode, uint32_t) ENUM_VALUE(Zero, 0) ENUM_VALUE(One, 1) ENUM_VALUE(SrcColor, 2) ENUM_VALUE(InvSrcColor, 3) ENUM_VALUE(SrcAlpha, 4) ENUM_VALUE(InvSrcAlpha, 5) ENUM_VALUE(DestAlpha, 6) ENUM_VALUE(InvDestAlpha, 7) ENUM_VALUE(DestColor, 8) ENUM_VALUE(InvDestColor, 9) ENUM_VALUE(SrcAlphaSat, 10) ENUM_VALUE(BothSrcAlpha, 11) ENUM_VALUE(BothInvSrcAlpha, 12) ENUM_VALUE(BlendFactor, 13) ENUM_VALUE(InvBlendFactor, 14) ENUM_VALUE(Src1Color, 15) ENUM_VALUE(InvSrc1Color, 16) ENUM_VALUE(Src1Alpha, 17) ENUM_VALUE(InvSrc1Alpha, 18) ENUM_END(GX2BlendMode) ENUM_BEG(GX2BlendCombineMode, uint32_t) ENUM_VALUE(Add, 0) ENUM_VALUE(Subtract, 1) ENUM_VALUE(Min, 2) ENUM_VALUE(Max, 3) ENUM_VALUE(RevSubtract, 4) ENUM_END(GX2BlendCombineMode) ENUM_BEG(GX2BufferingMode, uint32_t) ENUM_VALUE(Single, 1) ENUM_VALUE(Double, 2) ENUM_VALUE(Triple, 3) ENUM_END(GX2BufferingMode) ENUM_BEG(GX2ChannelMask, uint8_t) ENUM_VALUE(R, 1) ENUM_VALUE(G, 2) ENUM_VALUE(RG, 3) ENUM_VALUE(B, 4) ENUM_VALUE(RB, 5) ENUM_VALUE(GB, 6) ENUM_VALUE(RGB, 7) ENUM_VALUE(A, 8) ENUM_VALUE(RA, 9) ENUM_VALUE(GA, 10) ENUM_VALUE(RGA, 11) ENUM_VALUE(BA, 12) ENUM_VALUE(RBA, 13) ENUM_VALUE(GBA, 14) ENUM_VALUE(RGBA, 15) ENUM_END(GX2ChannelMask) ENUM_BEG(GX2CompareFunction, uint32_t) ENUM_VALUE(Never, 0) ENUM_VALUE(Less, 1) ENUM_VALUE(Equal, 2) ENUM_VALUE(LessOrEqual, 3) ENUM_VALUE(Greater, 4) ENUM_VALUE(NotEqual, 5) ENUM_VALUE(GreaterOrEqual, 6) ENUM_VALUE(Always, 7) ENUM_END(GX2CompareFunction) ENUM_BEG(GX2Component, uint32_t) ENUM_VALUE(Mem0, 0) ENUM_VALUE(Mem1, 1) ENUM_VALUE(Mem2, 2) ENUM_VALUE(Mem3, 3) ENUM_VALUE(Zero, 4) ENUM_VALUE(One, 5) ENUM_END(GX2Component) FLAGS_BEG(GX2ContextStateFlags, uint32_t) FLAGS_VALUE(ProfilingEnabled, 1 << 0) FLAGS_VALUE(NoShadowDisplayList, 1 << 1) FLAGS_END(GX2ContextStateFlags) ENUM_BEG(GX2ClearFlags, uint32_t) ENUM_VALUE(Depth, 1) ENUM_VALUE(Stencil, 2) ENUM_END(GX2ClearFlags) ENUM_BEG(GX2DebugCaptureInterfaceVersion, uint32_t) ENUM_VALUE(Version1, 1) ENUM_END(GX2DebugCaptureInterfaceVersion) ENUM_BEG(GX2DebugTag, uint32_t) ENUM_VALUE(SetDefaultState, 1) ENUM_VALUE(ClearColor, 2) ENUM_VALUE(ClearDepthStencil, 3) ENUM_VALUE(ClearBuffers, 4) ENUM_VALUE(ResolveAAColorBuffer, 7) ENUM_VALUE(ExpandAAColorBuffer, 8) ENUM_VALUE(ExpandDepthBuffer, 9) ENUM_VALUE(ConvertDepthBufferToTexture, 10) ENUM_VALUE(CopyColorBufferToScanBuffer, 11) ENUM_VALUE(SwapScanBuffers, 12) ENUM_VALUE(PerfPassStart, 13) ENUM_VALUE(PerfPassEnd, 14) ENUM_VALUE(PerfTagStart, 15) ENUM_VALUE(PerfTagEnd, 16) ENUM_VALUE(User, 0xFEAD0000u) ENUM_VALUE(Group, 0xFEAE0000u) ENUM_END(GX2DebugTag) ENUM_BEG(GX2DrcRenderMode, uint32_t) ENUM_VALUE(Disabled, 0) ENUM_VALUE(Single, 1) ENUM_END(GX2DrcRenderMode) ENUM_BEG(GX2EndianSwapMode, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Swap8In16, 1) ENUM_VALUE(Swap8In32, 2) ENUM_VALUE(Default, 3) ENUM_END(GX2EndianSwapMode) ENUM_BEG(GX2EventType, uint32_t) ENUM_VALUE(StartOfPipeInterrupt, 0) ENUM_VALUE(EndOfPipeInterrupt, 1) ENUM_VALUE(Vsync, 2) ENUM_VALUE(Flip, 3) ENUM_VALUE(DisplayListOverrun, 4) ENUM_VALUE(Max, 5) ENUM_VALUE(StopAppIoThread, 0xFFFFFFFFu) ENUM_END(GX2EventType) ENUM_BEG(GX2FetchShaderType, uint32_t) ENUM_VALUE(NoTessellation, 0) ENUM_VALUE(LineTessellation, 1) ENUM_VALUE(TriangleTessellation, 2) ENUM_VALUE(QuadTessellation, 3) ENUM_END(GX2FetchShaderType) ENUM_BEG(GX2FrontFace, uint32_t) ENUM_VALUE(CounterClockwise, 0) ENUM_VALUE(Clockwise, 1) ENUM_END(GX2FrontFace) ENUM_BEG(GX2InitAttrib, uint32_t) ENUM_VALUE(End, 0) ENUM_VALUE(CommandBufferPoolBase, 1) ENUM_VALUE(CommandBufferPoolSize, 2) ENUM_VALUE(ArgC, 7) ENUM_VALUE(ArgV, 8) ENUM_VALUE(ProfileMode, 9) ENUM_VALUE(TossStage, 10) ENUM_VALUE(AppIoThreadStackSize, 11) ENUM_END(GX2InitAttrib) ENUM_BEG(GX2IndexType, uint32_t) ENUM_VALUE(U16_LE, 0x0) ENUM_VALUE(U32_LE, 0x1) ENUM_VALUE(U16, 0x4) ENUM_VALUE(U32, 0x9) ENUM_END(GX2IndexType) FLAGS_BEG(GX2InvalidateMode, uint32_t) FLAGS_VALUE(AttributeBuffer, 1 << 0) FLAGS_VALUE(Texture, 1 << 1) FLAGS_VALUE(UniformBlock, 1 << 2) FLAGS_VALUE(Shader, 1 << 3) FLAGS_VALUE(ColorBuffer, 1 << 4) FLAGS_VALUE(DepthBuffer, 1 << 5) FLAGS_VALUE(CPU, 1 << 6) FLAGS_VALUE(StreamOutBuffer, 1 << 7) FLAGS_VALUE(ExportBuffer, 1 << 8) FLAGS_END(GX2InvalidateMode) ENUM_BEG(GX2LogicOp, uint8_t) ENUM_VALUE(Clear, 0x00) ENUM_VALUE(Nor, 0x11) ENUM_VALUE(InvertedAnd, 0x22) ENUM_VALUE(InvertedCopy, 0x33) ENUM_VALUE(ReverseAnd, 0x44) ENUM_VALUE(Invert, 0x55) ENUM_VALUE(Xor, 0x66) ENUM_VALUE(NotAnd, 0x77) ENUM_VALUE(And, 0x88) ENUM_VALUE(Equiv, 0x99) ENUM_VALUE(NoOp, 0xAA) ENUM_VALUE(InvertedOr, 0xBB) ENUM_VALUE(Copy, 0xCC) ENUM_VALUE(ReverseOr, 0xDD) ENUM_VALUE(Or, 0xEE) ENUM_VALUE(Set, 0xFF) ENUM_END(GX2LogicOp) ENUM_BEG(GX2MiscParam, uint32_t) ENUM_VALUE(HangState, 0) ENUM_VALUE(HangResponse, 1) ENUM_VALUE(HangResetSwapTimeout, 2) ENUM_VALUE(HangResetSwapsOutstanding, 3) ENUM_END(GX2MiscParam) ENUM_BEG(GX2PatchShaderType, uint32_t) ENUM_VALUE(FetchShader, 0x1) ENUM_VALUE(VertexShader, 0x2) ENUM_VALUE(GeometryVertexShader, 0x3) ENUM_VALUE(GeometryShader, 0x4) ENUM_VALUE(PixelShader, 0x5) ENUM_END(GX2PatchShaderType) ENUM_BEG(GX2PerfType, uint32_t) ENUM_VALUE(GpuMetric, 0x1) ENUM_VALUE(GpuStat, 0x2) ENUM_VALUE(MemStat, 0x3) ENUM_END(GX2PerfType) FLAGS_BEG(GX2ProfileMode, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(SkipExecuteCommandBuffers, 1 << 0) FLAGS_END(GX2ProfileMode) ENUM_BEG(GX2PrimitiveMode, uint32_t) ENUM_VALUE(Triangles, 0x4) ENUM_VALUE(TriangleStrip, 0x6) ENUM_VALUE(Quads, 0x13) ENUM_VALUE(QuadStrip, 0x14) ENUM_END(GX2PrimitiveMode) FLAGS_BEG(GX2PrimitiveModeFlags, uint32_t) FLAGS_VALUE(ModeMask, 0x1F) FLAGS_VALUE(Tessellate, 0x80) FLAGS_END(GX2PrimitiveModeFlags) ENUM_BEG(GX2PolygonMode, uint32_t) ENUM_VALUE(Point, 0) ENUM_VALUE(Line, 1) ENUM_VALUE(Triangle, 2) ENUM_END(GX2PolygonMode) ENUM_BEG(GX2QueryType, uint32_t) ENUM_VALUE(OcclusionQuery, 0) ENUM_VALUE(StreamOutStats, 1) ENUM_VALUE(OcclusionQueryGpuMem, 2) ENUM_VALUE(StreamOutStatsGpuMem, 3) ENUM_END(GX2QueryType) ENUM_BEG(GX2RenderTarget, uint32_t) ENUM_VALUE(Target0, 0) ENUM_VALUE(Target1, 1) ENUM_VALUE(Target2, 2) ENUM_VALUE(Target3, 3) ENUM_VALUE(Target4, 4) ENUM_VALUE(Target5, 5) ENUM_VALUE(Target6, 6) ENUM_VALUE(Target7, 7) ENUM_END(GX2RenderTarget) FLAGS_BEG(GX2RResourceFlags, uint32_t) FLAGS_VALUE(BindTexture, 1 << 0) FLAGS_VALUE(BindColorBuffer, 1 << 1) FLAGS_VALUE(BindDepthBuffer, 1 << 2) FLAGS_VALUE(BindScanBuffer, 1 << 3) FLAGS_VALUE(BindVertexBuffer, 1 << 4) FLAGS_VALUE(BindIndexBuffer, 1 << 5) FLAGS_VALUE(BindUniformBlock, 1 << 6) FLAGS_VALUE(BindShaderProgram, 1 << 7) FLAGS_VALUE(BindStreamOutput, 1 << 8) FLAGS_VALUE(BindDisplayList, 1 << 9) FLAGS_VALUE(BindGSRing, 1 << 10) FLAGS_VALUE(UsageCpuRead, 1 << 11) FLAGS_VALUE(UsageCpuWrite, 1 << 12) FLAGS_VALUE(UsageCpuReadWrite, UsageCpuRead | UsageCpuWrite) FLAGS_VALUE(UsageGpuRead, 1 << 13) FLAGS_VALUE(UsageGpuWrite, 1 << 14) FLAGS_VALUE(UsageGpuReadWrite, UsageGpuRead | UsageGpuWrite) FLAGS_VALUE(UsageDmaRead, 1 << 15) FLAGS_VALUE(UsageDmaWrite, 1 << 16) FLAGS_VALUE(UsageForceMEM1, 1 << 17) FLAGS_VALUE(UsageForceMEM2, 1 << 18) FLAGS_VALUE(DisableCpuInvalidate, 1 << 20) FLAGS_VALUE(DisableGpuInvalidate, 1 << 21) FLAGS_VALUE(LockedReadOnly, 1 << 22) FLAGS_VALUE(DestroyNoFree, 1 << 23) FLAGS_VALUE(Gx2rAllocated, 1 << 29) FLAGS_VALUE(Locked, 1 << 30) FLAGS_END(GX2RResourceFlags) ENUM_BEG(GX2RoundingMode, uint32_t) ENUM_VALUE(RoundToEven, 0) ENUM_VALUE(Truncate, 1) ENUM_END(GX2RoundingMode) ENUM_BEG(GXSpecialState, uint32_t) ENUM_VALUE(Clear, 0) ENUM_VALUE(ClearHiZ, 1) ENUM_VALUE(Copy, 2) ENUM_VALUE(ExpandColor, 3) ENUM_VALUE(ExpandDepth, 4) ENUM_VALUE(ConvertDepth, 5) ENUM_VALUE(ConvertMsaaDepth, 6) ENUM_VALUE(ResolveColor, 7) ENUM_VALUE(ClearColorAsDepth, 8) ENUM_END(GXSpecialState) ENUM_BEG(GX2ScanTarget, uint32_t) ENUM_VALUE(TV, 1) ENUM_VALUE(DRC, 4) ENUM_END(GX2ScanTarget) ENUM_BEG(GX2StreamOutContextMode, uint32_t) ENUM_VALUE(Append, 0) ENUM_VALUE(FromStart, 1) ENUM_VALUE(FromOffset, 2) ENUM_END(GX2StreamOutContextMode) ENUM_BEG(GX2PipeEvent, uint32_t) ENUM_VALUE(Top, 0) ENUM_VALUE(Bottom, 1) ENUM_VALUE(BottomAfterFlush, 2) ENUM_END(GX2PipeEvent) ENUM_BEG(GX2SurfaceDim, uint32_t) ENUM_VALUE(Texture1D, 0) ENUM_VALUE(Texture2D, 1) ENUM_VALUE(Texture3D, 2) ENUM_VALUE(TextureCube, 3) ENUM_VALUE(Texture1DArray, 4) ENUM_VALUE(Texture2DArray, 5) ENUM_VALUE(Texture2DMSAA, 6) ENUM_VALUE(Texture2DMSAAArray, 7) ENUM_END(GX2SurfaceDim) ENUM_BEG(GX2SurfaceFormat, uint32_t) ENUM_VALUE(INVALID, 0x00) ENUM_VALUE(UNORM_R4_G4, 0x02) ENUM_VALUE(UNORM_R4_G4_B4_A4, 0x0b) ENUM_VALUE(UNORM_R8, 0x01) ENUM_VALUE(UNORM_R8_G8, 0x07) ENUM_VALUE(UNORM_R8_G8_B8_A8, 0x01a) ENUM_VALUE(UNORM_R16, 0x05) ENUM_VALUE(UNORM_R16_G16, 0x0f) ENUM_VALUE(UNORM_R16_G16_B16_A16, 0x01f) ENUM_VALUE(UNORM_R5_G6_B5, 0x08) ENUM_VALUE(UNORM_R5_G5_B5_A1, 0x0a) ENUM_VALUE(UNORM_A1_B5_G5_R5, 0x0c) ENUM_VALUE(UNORM_R24_X8, 0x011) ENUM_VALUE(UNORM_A2_B10_G10_R10, 0x01b) ENUM_VALUE(UNORM_R10_G10_B10_A2, 0x019) ENUM_VALUE(UNORM_BC1, 0x031) ENUM_VALUE(UNORM_BC2, 0x032) ENUM_VALUE(UNORM_BC3, 0x033) ENUM_VALUE(UNORM_BC4, 0x034) ENUM_VALUE(UNORM_BC5, 0x035) ENUM_VALUE(UNORM_NV12, 0x081) ENUM_VALUE(UINT_R8, 0x101) ENUM_VALUE(UINT_R8_G8, 0x107) ENUM_VALUE(UINT_R8_G8_B8_A8, 0x11a) ENUM_VALUE(UINT_R16, 0x105) ENUM_VALUE(UINT_R16_G16, 0x10f) ENUM_VALUE(UINT_R16_G16_B16_A16, 0x11f) ENUM_VALUE(UINT_R32, 0x10d) ENUM_VALUE(UINT_R32_G32, 0x11d) ENUM_VALUE(UINT_R32_G32_B32_A32, 0x122) ENUM_VALUE(UINT_A2_B10_G10_R10, 0x11b) ENUM_VALUE(UINT_R10_G10_B10_A2, 0x119) ENUM_VALUE(UINT_X24_G8, 0x111) ENUM_VALUE(UINT_G8_X24, 0x11c) ENUM_VALUE(SNORM_R8, 0x201) ENUM_VALUE(SNORM_R8_G8, 0x207) ENUM_VALUE(SNORM_R8_G8_B8_A8, 0x21a) ENUM_VALUE(SNORM_R16, 0x205) ENUM_VALUE(SNORM_R16_G16, 0x20f) ENUM_VALUE(SNORM_R16_G16_B16_A16, 0x21f) ENUM_VALUE(SNORM_R10_G10_B10_A2, 0x219) ENUM_VALUE(SNORM_BC4, 0x234) ENUM_VALUE(SNORM_BC5, 0x235) ENUM_VALUE(SINT_R8, 0x301) ENUM_VALUE(SINT_R8_G8, 0x307) ENUM_VALUE(SINT_R8_G8_B8_A8, 0x31a) ENUM_VALUE(SINT_R16, 0x305) ENUM_VALUE(SINT_R16_G16, 0x30f) ENUM_VALUE(SINT_R16_G16_B16_A16, 0x31f) ENUM_VALUE(SINT_R32, 0x30d) ENUM_VALUE(SINT_R32_G32, 0x31d) ENUM_VALUE(SINT_R32_G32_B32_A32, 0x322) ENUM_VALUE(SINT_R10_G10_B10_A2, 0x319) ENUM_VALUE(SRGB_R8_G8_B8_A8, 0x41a) ENUM_VALUE(SRGB_BC1, 0x431) ENUM_VALUE(SRGB_BC2, 0x432) ENUM_VALUE(SRGB_BC3, 0x433) ENUM_VALUE(FLOAT_R32, 0x80e) ENUM_VALUE(FLOAT_R32_G32, 0x81e) ENUM_VALUE(FLOAT_R32_G32_B32_A32, 0x823) ENUM_VALUE(FLOAT_R16, 0x806) ENUM_VALUE(FLOAT_R16_G16, 0x810) ENUM_VALUE(FLOAT_R16_G16_B16_A16, 0x820) ENUM_VALUE(FLOAT_R11_G11_B10, 0x816) ENUM_VALUE(FLOAT_D24_S8, 0x811) ENUM_VALUE(FLOAT_X8_X24, 0x81c) ENUM_END(GX2SurfaceFormat) ENUM_BEG(GX2SurfaceFormatType, uint32_t) ENUM_VALUE(UNORM, 0x0) ENUM_VALUE(UINT, 0x1) ENUM_VALUE(SNORM, 0x2) ENUM_VALUE(SINT, 0x3) ENUM_VALUE(SRGB, 0x4) ENUM_VALUE(FLOAT, 0x8) ENUM_END(GX2SurfaceFormatType) FLAGS_BEG(GX2SurfaceUse, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(Texture, 1 << 0) FLAGS_VALUE(ColorBuffer, 1 << 1) FLAGS_VALUE(DepthBuffer, 1 << 2) FLAGS_VALUE(ScanBuffer, 1 << 3) FLAGS_END(GX2SurfaceUse) ENUM_BEG(GX2StencilFunction, uint32_t) ENUM_VALUE(Keep, 0) ENUM_VALUE(Zero, 1) ENUM_VALUE(Replace, 2) ENUM_VALUE(IncrClamp, 3) ENUM_VALUE(DecrClamp, 4) ENUM_VALUE(Invert, 5) ENUM_VALUE(IncrWrap, 6) ENUM_VALUE(DecrWrap, 7) ENUM_END(GX2StencilFunction) ENUM_BEG(GX2TessellationMode, uint32_t) ENUM_VALUE(Discrete, 0) ENUM_VALUE(Continuous, 1) ENUM_VALUE(Adaptive, 2) ENUM_END(GX2TessellationMode) ENUM_BEG(GX2TexBorderType, uint32_t) ENUM_VALUE(TransparentBlack, 0) ENUM_VALUE(Black, 1) ENUM_VALUE(White, 2) ENUM_VALUE(Variable, 3) ENUM_END(GX2TexBorderType) ENUM_BEG(GX2TexClampMode, uint32_t) ENUM_VALUE(Wrap, 0) ENUM_VALUE(Mirror, 1) ENUM_VALUE(Clamp, 2) ENUM_VALUE(MirrorOnce, 3) ENUM_VALUE(ClampBorder, 6) ENUM_END(GX2TexClampMode) ENUM_BEG(GX2TexMipFilterMode, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Point, 1) ENUM_VALUE(Linear, 2) ENUM_END(GX2TexMipFilterMode) ENUM_BEG(GX2TexMipPerfMode, uint32_t) ENUM_VALUE(Disable, 0) ENUM_END(GX2TexMipPerfMode) ENUM_BEG(GX2TexXYFilterMode, uint32_t) ENUM_VALUE(Point, 0) ENUM_VALUE(Linear, 1) ENUM_END(GX2TexXYFilterMode) ENUM_BEG(GX2TexAnisoRatio, uint32_t) ENUM_VALUE(None, 0) ENUM_END(GX2TexAnisoRatio) ENUM_BEG(GX2TexZFilterMode, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Point, 1) ENUM_VALUE(Linear, 2) ENUM_END(GX2TexZFilterMode) ENUM_BEG(GX2TexZPerfMode, uint32_t) ENUM_VALUE(Disabled, 0) ENUM_END(GX2TexZPerfMode) ENUM_BEG(GX2TileMode, uint32_t) ENUM_VALUE(Default, 0) ENUM_VALUE(LinearAligned, 1) ENUM_VALUE(Tiled1DThin1, 2) ENUM_VALUE(Tiled1DThick, 3) ENUM_VALUE(Tiled2DThin1, 4) ENUM_VALUE(Tiled2DThin2, 5) ENUM_VALUE(Tiled2DThin4, 6) ENUM_VALUE(Tiled2DThick, 7) ENUM_VALUE(Tiled2BThin1, 8) ENUM_VALUE(Tiled2BThin2, 9) ENUM_VALUE(Tiled2BThin4, 10) ENUM_VALUE(Tiled2BThick, 11) ENUM_VALUE(Tiled3DThin1, 12) ENUM_VALUE(Tiled3DThick, 13) ENUM_VALUE(Tiled3BThin1, 14) ENUM_VALUE(Tiled3BThick, 15) ENUM_VALUE(LinearSpecial, 16) ENUM_VALUE(DefaultBadAlign, 0x20) ENUM_END(GX2TileMode) ENUM_BEG(GX2TossStage, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Unk1, 1) ENUM_VALUE(Unk2, 2) ENUM_VALUE(Unk7, 7) ENUM_VALUE(Unk8, 8) ENUM_END(GX2TossStage) ENUM_BEG(GX2TVRenderMode, uint32_t) ENUM_VALUE(Disabled, 0) ENUM_VALUE(Standard480p, 1) ENUM_VALUE(Wide480p, 2) ENUM_VALUE(Wide720p, 3) ENUM_VALUE(Unk720p, 4) ENUM_VALUE(Wide1080p, 5) ENUM_END(GX2TVRenderMode) ENUM_BEG(GX2TVScanMode, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(I576, 1) ENUM_VALUE(I480, 2) ENUM_VALUE(P480, 3) ENUM_VALUE(P720, 4) ENUM_VALUE(I1080, 6) ENUM_VALUE(P1080, 7) ENUM_END(GX2TVScanMode) ENUM_BEG(GX2SamplerVarType, uint32_t) ENUM_VALUE(Sampler1D, 0) ENUM_VALUE(Sampler2D, 1) ENUM_VALUE(Sampler3D, 3) ENUM_VALUE(SamplerCube, 4) ENUM_END(GX2SamplerVarType) ENUM_BEG(GX2ShaderMode, uint32_t) ENUM_VALUE(UniformRegister, 0) ENUM_VALUE(UniformBlock, 1) ENUM_VALUE(GeometryShader, 2) ENUM_VALUE(ComputeShader, 3) ENUM_END(GX2ShaderMode) ENUM_BEG(GX2ShaderVarType, uint32_t) ENUM_VALUE(Void, 0) ENUM_VALUE(Bool, 1) ENUM_VALUE(Int, 2) ENUM_VALUE(Uint, 3) ENUM_VALUE(Float, 4) ENUM_VALUE(Double, 5) ENUM_VALUE(Double2, 6) ENUM_VALUE(Double3, 7) ENUM_VALUE(Double4, 8) ENUM_VALUE(Float2, 9) ENUM_VALUE(Float3, 10) ENUM_VALUE(Float4, 11) ENUM_VALUE(Bool2, 12) ENUM_VALUE(Bool3, 13) ENUM_VALUE(Bool4, 14) ENUM_VALUE(Int2, 15) ENUM_VALUE(Int3, 16) ENUM_VALUE(Int4, 17) ENUM_VALUE(Uint2, 18) ENUM_VALUE(Uint3, 19) ENUM_VALUE(Uint4, 20) ENUM_VALUE(Float2x2, 21) ENUM_VALUE(Float2x3, 22) ENUM_VALUE(Float2x4, 23) ENUM_VALUE(Float3x2, 24) ENUM_VALUE(Float3x3, 25) ENUM_VALUE(Float3x4, 26) ENUM_VALUE(Float4x2, 27) ENUM_VALUE(Float4x3, 28) ENUM_VALUE(Float4x4, 29) ENUM_VALUE(Double2x2, 30) ENUM_VALUE(Double2x3, 31) ENUM_VALUE(Double2x4, 32) ENUM_VALUE(Double3x2, 33) ENUM_VALUE(Double3x3, 34) ENUM_VALUE(Double3x4, 35) ENUM_VALUE(Double4x2, 36) ENUM_VALUE(Double4x3, 37) ENUM_VALUE(Double4x4, 38) ENUM_END(GX2ShaderVarType) ENUM_NAMESPACE_EXIT(gx2) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_GX2_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_enum_string.cpp ================================================ #include "gx2_enum_string.h" #undef CAFE_GX2_ENUM_H #include #include "gx2_enum.h" #include ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_enum_string.h ================================================ #pragma once #include #include #include "gx2_enum.h" #undef CAFE_GX2_ENUM_H #include #include "gx2_enum.h" #include ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_event.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_event.h" #include "gx2_state.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_alarm.h" #include "cafe/libraries/coreinit/coreinit_memheap.h" #include "cafe/libraries/coreinit/coreinit_messagequeue.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/libraries/coreinit/coreinit_thread.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include "cafe/libraries/tcl/tcl_interrupthandler.h" #include #include #include namespace cafe::gx2 { using namespace cafe::coreinit; using namespace cafe::tcl; struct StaticEventData { struct EventCallbackData { GX2EventCallbackFunction func; virt_ptr data; }; be2_struct appIoThread; be2_array appIoMessageBuffer; be2_struct appIoMessageQueue; be2_struct vsyncThreadQueue; be2_struct flipThreadQueue; be2_struct vsyncAlarm; be2_array eventCallbacks; std::atomic lastVsync; std::atomic lastFlip; std::atomic swapCount; std::atomic flipCount; std::atomic framesReady; }; static virt_ptr sEventData = nullptr; static OSThreadEntryPointFn sAppIoThreadEntry = nullptr; static AlarmCallbackFn sVsyncAlarmHandler = nullptr; static TCLInterruptHandlerFn sTclEventCallback = nullptr; /** * Sleep the current thread until the last submitted command buffer * has been processed by the driver. */ BOOL GX2DrawDone() { GX2Flush(); return GX2WaitTimeStamp(GX2GetLastSubmittedTimeStamp()); } /** * Set the callback and data for a type of event. */ void GX2SetEventCallback(GX2EventType type, GX2EventCallbackFunction func, virt_ptr userData) { if (type == GX2EventType::EndOfPipeInterrupt) { if (func && !sEventData->eventCallbacks[type].func) { TCLIHEnableInterrupt(TCLInterruptType::CP_EOP_EVENT, TRUE); TCLIHRegister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback, virt_cast(static_cast(GX2EventType::EndOfPipeInterrupt))); } else if (!func && sEventData->eventCallbacks[type].func) { TCLIHEnableInterrupt(TCLInterruptType::CP_EOP_EVENT, FALSE); TCLIHUnregister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback, virt_cast(static_cast(GX2EventType::EndOfPipeInterrupt))); } } if (type < GX2EventType::Max) { sEventData->eventCallbacks[type].func = func; sEventData->eventCallbacks[type].data = userData; } } /** * Return the callback and data for a type of event. */ void GX2GetEventCallback(GX2EventType type, virt_ptr outFunc, virt_ptr> outUserData) { if (type < GX2EventType::Max) { *outFunc = sEventData->eventCallbacks[type].func; *outUserData = sEventData->eventCallbacks[type].data; } else { *outFunc = nullptr; *outUserData = nullptr; } } /** * Return the current swap status. * * \param outSwapCount * Outputs the number of swaps requested. * * \param outFlipCount * Outputs the number of swaps performed. * * \param outLastFlip * Outputs the time of the last flip. * * \param outLastVsync * Outputs the time of the last vsync. */ void GX2GetSwapStatus(virt_ptr outSwapCount, virt_ptr outFlipCount, virt_ptr outLastFlip, virt_ptr outLastVsync) { if (outSwapCount) { *outSwapCount = sEventData->swapCount.load(std::memory_order_acquire); } if (outFlipCount) { *outFlipCount = sEventData->flipCount.load(std::memory_order_acquire); } if (outLastFlip) { *outLastFlip = sEventData->lastFlip.load(std::memory_order_acquire); } if (outLastVsync) { *outLastVsync = sEventData->lastVsync.load(std::memory_order_acquire); } } /** * Sleep the current thread until the vsync alarm has triggered. */ void GX2WaitForVsync() { OSSleepThread(virt_addrof(sEventData->vsyncThreadQueue)); } /** * Sleep the current thread until a flip has been performed. */ void GX2WaitForFlip() { auto curFlipCount = sEventData->flipCount.load(std::memory_order_acquire); auto curSwapCount = sEventData->swapCount.load(std::memory_order_acquire); if (curFlipCount == curSwapCount) { // The user has no more pending flips, return immediately. return; } OSSleepThread(virt_addrof(sEventData->flipThreadQueue)); } namespace internal { /** * TCL interrupt handler which forwards a message to the AppIo thread to * perform the user callback. */ static void tclEventCallback(virt_ptr interruptEntry, virt_ptr userData) { auto eventType = static_cast(static_cast(virt_cast(userData))); if (sEventData->eventCallbacks[eventType].func) { auto message = StackObject { }; message->message = nullptr; message->args[0] = eventType; message->args[1] = 0u; message->args[2] = 0u; OSSendMessage(virt_addrof(sEventData->appIoMessageQueue), message, OSMessageFlags::Blocking); } } /** * Initialise GX2 events. */ void initEvents(virt_ptr appIoThreadStackBuffer, uint32_t appIoThreadStackSize) { OSInitThreadQueue(virt_addrof(sEventData->flipThreadQueue)); OSInitThreadQueue(virt_addrof(sEventData->vsyncThreadQueue)); // Setup 60hz alarm to perform vsync auto ticks = static_cast(OSGetSystemInfo()->busSpeed / 4) / 60; OSCreateAlarm(virt_addrof(sEventData->vsyncAlarm)); OSSetPeriodicAlarm(virt_addrof(sEventData->vsyncAlarm), OSGetTime(), ticks, sVsyncAlarmHandler); // Create AppIo thread OSInitMessageQueue(virt_addrof(sEventData->appIoMessageQueue), virt_addrof(sEventData->appIoMessageBuffer), sEventData->appIoMessageBuffer.size()); OSCreateThreadType(virt_addrof(sEventData->appIoThread), sAppIoThreadEntry, 0, nullptr, virt_cast(virt_cast(appIoThreadStackBuffer) + appIoThreadStackSize), appIoThreadStackSize, 16, OSThreadAttributes::Detached, OSThreadType::AppIo); OSResumeThread(virt_addrof(sEventData->appIoThread)); // Register TCL interrupt handlers. TCLIHRegister(TCLInterruptType::CP_IB1, sTclEventCallback, virt_cast(static_cast(GX2EventType::StartOfPipeInterrupt))); if (sEventData->eventCallbacks[GX2EventType::EndOfPipeInterrupt].func) { TCLIHRegister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback, virt_cast(static_cast(GX2EventType::EndOfPipeInterrupt))); } } /** * AppIo thread for GX2, receives messages and calls the appropriate callback. */ static uint32_t appIoThread(uint32_t argc, virt_ptr argv) { auto message = StackObject { }; while (true) { OSReceiveMessage(virt_addrof(sEventData->appIoMessageQueue), message, OSMessageFlags::Blocking); auto eventType = static_cast(message->args[0]); if (eventType == GX2EventType::StopAppIoThread) { break; } auto &callback = sEventData->eventCallbacks[eventType]; if (callback.func) { cafe::invoke(cpu::this_core::state(), callback.func, eventType, callback.data); } } return 0; } /** * Send a message to stop appIoThread. */ void stopAppIoThread() { // Unregister TCL interrupt handlers. TCLIHUnregister(TCLInterruptType::CP_IB1, sTclEventCallback, virt_cast(static_cast(GX2EventType::StartOfPipeInterrupt))); if (sEventData->eventCallbacks[GX2EventType::EndOfPipeInterrupt].func) { TCLIHUnregister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback, virt_cast(static_cast(GX2EventType::EndOfPipeInterrupt))); } // Send stop message auto message = StackObject { }; message->message = nullptr; message->args[0] = GX2EventType::StopAppIoThread; message->args[1] = 0u; message->args[2] = 0u; OSSendMessage(virt_addrof(sEventData->appIoMessageQueue), message, OSMessageFlags::Blocking); } /** * VSync alarm handler. * * Wakes up any threads waiting for vsync. * If a vsync callback has been set, trigger it. */ static void vsyncAlarmHandler(virt_ptr alarm, virt_ptr context) { auto vsyncTime = OSGetSystemTime(); auto curFramesReady = sEventData->framesReady.load(std::memory_order_acquire); auto curFlipCount = sEventData->flipCount.load(std::memory_order_acquire); if (curFramesReady > curFlipCount) { sEventData->flipCount.fetch_add(1, std::memory_order_release); sEventData->lastFlip.store(vsyncTime, std::memory_order_release); OSWakeupThread(virt_addrof(sEventData->flipThreadQueue)); auto callback = sEventData->eventCallbacks[GX2EventType::Flip]; if (callback.func) { cafe::invoke(cpu::this_core::state(), callback.func, GX2EventType::Flip, callback.data); } } sEventData->lastVsync.store(vsyncTime, std::memory_order_release); OSWakeupThread(virt_addrof(sEventData->vsyncThreadQueue)); auto callback = sEventData->eventCallbacks[GX2EventType::Vsync]; if (callback.func) { cafe::invoke(cpu::this_core::state(), callback.func, GX2EventType::Vsync, callback.data); } } /** * Called when a GX2 command requires more space in a display list. * * Will call the display list overrun callback to allocate a new command buffer. */ std::pair, uint32_t> displayListOverrun(virt_ptr list, uint32_t size, uint32_t neededSize) { auto callback = sEventData->eventCallbacks[GX2EventType::DisplayListOverrun]; if (callback.func && callback.data) { auto data = virt_cast(callback.data); data->oldList = list; data->oldSize = size; data->newList = nullptr; data->newSize = neededSize; // Call the user's function, it should set newList and newSize cafe::invoke(cpu::this_core::state(), callback.func, GX2EventType::DisplayListOverrun, callback.data); return { data->newList, data->newSize }; } gLog->error("Encountered DisplayListOverrun without a valid callback!"); return { nullptr, 0 }; } /** * Called when a swap is requested with GX2SwapBuffers. */ void onSwap() { sEventData->swapCount.fetch_add(1, std::memory_order_release); } /** * Called when a swap is performed by the driver. */ void onFlip() { sEventData->framesReady.fetch_add(1, std::memory_order_release); } } // namespace internal void Library::registerEventSymbols() { RegisterFunctionExport(GX2DrawDone); RegisterFunctionExport(GX2WaitForVsync); RegisterFunctionExport(GX2WaitForFlip); RegisterFunctionExport(GX2SetEventCallback); RegisterFunctionExport(GX2GetEventCallback); RegisterFunctionExport(GX2GetSwapStatus); RegisterDataInternal(sEventData); RegisterFunctionInternal(internal::vsyncAlarmHandler, sVsyncAlarmHandler); RegisterFunctionInternal(internal::appIoThread, sAppIoThreadEntry); RegisterFunctionInternal(internal::tclEventCallback, sTclEventCallback); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_event.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_alarm.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include "gx2_enum.h" #include #include namespace cafe::gx2 { /** * \defgroup gx2_event Event * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2DisplayListOverrunData { be2_virt_ptr oldList; be2_val oldSize; be2_virt_ptr newList; be2_val newSize; UNKNOWN(8); }; CHECK_OFFSET(GX2DisplayListOverrunData, 0x00, oldList); CHECK_OFFSET(GX2DisplayListOverrunData, 0x04, oldSize); CHECK_OFFSET(GX2DisplayListOverrunData, 0x08, newList); CHECK_OFFSET(GX2DisplayListOverrunData, 0x0C, newSize); CHECK_SIZE(GX2DisplayListOverrunData, 0x18); #pragma pack(pop) using GX2EventCallbackFunction = virt_func_ptr)>; BOOL GX2DrawDone(); void GX2WaitForVsync(); void GX2WaitForFlip(); void GX2SetEventCallback(GX2EventType type, GX2EventCallbackFunction func, virt_ptr userData); void GX2GetEventCallback(GX2EventType type, virt_ptr outFunc, virt_ptr> outUserData); void GX2GetSwapStatus(virt_ptr outSwapCount, virt_ptr outFlipCount, virt_ptr outLastFlip, virt_ptr outLastVsync); namespace internal { void initEvents(virt_ptr appIoThreadStackBuffer, uint32_t appIoThreadStackSize); void stopAppIoThread(); std::pair, uint32_t> displayListOverrun(virt_ptr list, uint32_t size, uint32_t neededSize); void onSwap(); void onFlip(); } // namespace internal /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_fence.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_fence.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include #include using namespace cafe::coreinit; namespace cafe::gx2 { void GX2SetGPUFence(virt_ptr memory, uint32_t mask, GX2CompareFunction op, uint32_t value) { using namespace latte; using namespace latte::pm4; if (op == GX2CompareFunction::Never) { return; } WRM_FUNCTION function; switch (op) { case GX2CompareFunction::Never: return; case GX2CompareFunction::Less: function = WRM_FUNCTION::FUNCTION_LESS_THAN; break; case GX2CompareFunction::Equal: function = WRM_FUNCTION::FUNCTION_EQUAL; break; case GX2CompareFunction::LessOrEqual: function = WRM_FUNCTION::FUNCTION_LESS_THAN_EQUAL; break; case GX2CompareFunction::Greater: function = WRM_FUNCTION::FUNCTION_GREATER_THAN; break; case GX2CompareFunction::NotEqual: function = WRM_FUNCTION::FUNCTION_NOT_EQUAL; break; case GX2CompareFunction::GreaterOrEqual: function = WRM_FUNCTION::FUNCTION_GREATER_THAN_EQUAL; break; case GX2CompareFunction::Always: function = WRM_FUNCTION::FUNCTION_ALWAYS; break; default: function = WRM_FUNCTION::FUNCTION_ALWAYS; } auto addr = OSEffectiveToPhysical(virt_cast(memory)); internal::writePM4(WaitMem { MEM_SPACE_FUNCTION::get(0) .MEM_SPACE(WRM_MEM_SPACE::MEM_SPACE_MEMORY) .FUNCTION(function) .ENGINE(WRM_ENGINE::ENGINE_ME), WRM_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN32), WRM_ADDR_HI::get(0), value, mask, 10, }); } void Library::registerFenceSymbols() { RegisterFunctionExport(GX2SetGPUFence); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_fence.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2_fence Fence * \ingroup gx2 * @{ */ void GX2SetGPUFence(virt_ptr memory, uint32_t mask, GX2CompareFunction op, uint32_t value); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_fetchshader.cpp ================================================ #include "gx2.h" #include "gx2_fetchshader.h" #include "gx2_shaders.h" #include "gx2_enum.h" #include "gx2_enum_string.h" #include "gx2_format.h" #include "gx2_memory.h" #include #include #include #include namespace cafe::gx2 { struct IndexMapEntry { uint32_t gpr; latte::SQ_CHAN chan; }; static const auto FetchesPerControlFlow = 16u; static const IndexMapEntry IndexMapNoTess[] = { { 0, latte::SQ_CHAN::X }, { 0, latte::SQ_CHAN::X }, { 0, latte::SQ_CHAN::X }, { 0, latte::SQ_CHAN::X }, }; static const IndexMapEntry IndexMapLineTess[] = { { 0, latte::SQ_CHAN::Y }, { 0, latte::SQ_CHAN::Z }, { 0, latte::SQ_CHAN::Y }, { 0, latte::SQ_CHAN::Y }, }; static const IndexMapEntry IndexMapLineTessAdaptive[] = { { 6, latte::SQ_CHAN::X }, { 6, latte::SQ_CHAN::Y }, { 6, latte::SQ_CHAN::Z }, { 6, latte::SQ_CHAN::X }, }; static const IndexMapEntry IndexMapTriTess[] = { { 1, latte::SQ_CHAN::X }, { 1, latte::SQ_CHAN::Y }, { 1, latte::SQ_CHAN::Z }, { 1, latte::SQ_CHAN::X }, }; static const IndexMapEntry IndexMapTriTessAdaptive[] = { { 6, latte::SQ_CHAN::X }, { 6, latte::SQ_CHAN::Y }, { 6, latte::SQ_CHAN::Z }, { 6, latte::SQ_CHAN::X }, }; static const IndexMapEntry IndexMapQuadTess[] = { { 0, latte::SQ_CHAN::Z }, { 1, latte::SQ_CHAN::X }, { 1, latte::SQ_CHAN::Y }, { 1, latte::SQ_CHAN::Z }, }; static const IndexMapEntry IndexMapQuadTessAdaptive[] = { { 6, latte::SQ_CHAN::X }, { 6, latte::SQ_CHAN::Y }, { 6, latte::SQ_CHAN::Z }, { 6, latte::SQ_CHAN::W }, }; static uint32_t fetchInstsPerAttrib(GX2FetchShaderType type) { switch (type) { case GX2FetchShaderType::NoTessellation: return 1; case GX2FetchShaderType::LineTessellation: return 2; case GX2FetchShaderType::TriangleTessellation: return 3; case GX2FetchShaderType::QuadTessellation: return 4; default: decaf_abort(fmt::format("Invalid GX2FetchShaderType {}", to_string(type))); } } static uint32_t calcNumFetchInsts(uint32_t attribs, GX2FetchShaderType type) { switch (type) { case GX2FetchShaderType::NoTessellation: return attribs; case GX2FetchShaderType::LineTessellation: case GX2FetchShaderType::TriangleTessellation: case GX2FetchShaderType::QuadTessellation: return fetchInstsPerAttrib(type) * (attribs - 2); default: decaf_abort(fmt::format("Invalid GX2FetchShaderType {}", to_string(type))); } } static uint32_t calcNumTessALUInsts(GX2FetchShaderType type, GX2TessellationMode mode) { if (mode == GX2TessellationMode::Adaptive) { switch (type) { case GX2FetchShaderType::NoTessellation: break; case GX2FetchShaderType::LineTessellation: return 11; case GX2FetchShaderType::TriangleTessellation: return 57; case GX2FetchShaderType::QuadTessellation: return 43; } } return 4; } static uint32_t calcNumAluInsts(GX2FetchShaderType type, GX2TessellationMode mode) { if (type == GX2FetchShaderType::NoTessellation) { return 0; } else { return calcNumTessALUInsts(type, mode) + 4; } } static uint32_t calcNumFetchCFInsts(uint32_t fetches) { return (fetches + (FetchesPerControlFlow - 1)) / FetchesPerControlFlow; } static uint32_t calcNumCFInsts(uint32_t fetches, GX2FetchShaderType type) { auto count = calcNumFetchCFInsts(fetches); if (type != GX2FetchShaderType::NoTessellation) { count += 2; } return count + 1; } static const IndexMapEntry * getIndexGprMap(GX2FetchShaderType type, GX2TessellationMode mode) { switch (type) { case GX2FetchShaderType::LineTessellation: if (mode == GX2TessellationMode::Adaptive) { return IndexMapLineTessAdaptive; } else { return IndexMapLineTess; } case GX2FetchShaderType::TriangleTessellation: if (mode == GX2TessellationMode::Adaptive) { return IndexMapTriTessAdaptive; } else { return IndexMapTriTess; } case GX2FetchShaderType::QuadTessellation: if (mode == GX2TessellationMode::Adaptive) { return IndexMapQuadTessAdaptive; } else { return IndexMapQuadTess; } case GX2FetchShaderType::NoTessellation: default: return IndexMapNoTess; } } uint32_t GX2CalcFetchShaderSizeEx(uint32_t attribs, GX2FetchShaderType type, GX2TessellationMode mode) { auto fetch = calcNumFetchInsts(attribs, type); auto aluBytes = sizeof(latte::AluInst) * calcNumAluInsts(type, mode); auto cfBytes = sizeof(latte::ControlFlowInst) * calcNumCFInsts(fetch, type); return static_cast(sizeof(latte::VertexFetchInst) * fetch + align_up(cfBytes + aluBytes, 16)); } void GX2InitFetchShaderEx(virt_ptr fetchShader, virt_ptr buffer, uint32_t attribCount, virt_ptr attribs, GX2FetchShaderType type, GX2TessellationMode tessMode) { if (type != GX2FetchShaderType::NoTessellation) { decaf_abort(fmt::format("Invalid GX2FetchShaderType {}", to_string(type))); } if (tessMode != GX2TessellationMode::Discrete) { decaf_abort(fmt::format("Invalid GX2TessellationMode {}", to_string(tessMode))); } auto someTessVar1 = 128u; auto someTessVar2 = 128u; auto numGPRs = 0u; auto barrier = false; // Calculate instruction pointers auto fetchCount = calcNumFetchInsts(attribCount, type); auto aluCount = 0; // calcNumAluInsts(type, tessMode); auto cfCount = calcNumCFInsts(fetchCount, type); auto cfSize = cfCount * sizeof(latte::ControlFlowInst); auto aluSize = aluCount * sizeof(latte::AluInst); auto cfOffset = 0u; auto aluOffset = cfSize; auto fetchOffset = align_up(cfSize + aluSize, 0x10u); auto cfPtr = virt_cast(buffer + cfOffset); auto aluPtr = virt_cast(buffer + aluOffset); auto fetchPtr = virt_cast(buffer + fetchOffset); // Setup fetch shader fetchShader->type = type; fetchShader->attribCount = attribCount; fetchShader->data = buffer; fetchShader->size = GX2CalcFetchShaderSizeEx(attribCount, type, tessMode); // Generate fetch instructions auto indexMap = getIndexGprMap(type, tessMode); for (auto i = 0u; i < attribCount; ++i) { latte::VertexFetchInst vfetch; auto &attrib = attribs[i]; std::memset(&vfetch, 0, sizeof(vfetch)); if (attrib.buffer == 16) { // TODO: Figure out what these vars are for if (attrib.offset) { if (attrib.offset == 1) { someTessVar1 = attrib.location; } } else { someTessVar2 = attrib.location; } } else { // Semantic vertex fetch vfetch.word0 = vfetch.word0 .VTX_INST(latte::SQ_VTX_INST_SEMANTIC) .BUFFER_ID(latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + attrib.buffer - latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0); vfetch.word2 = vfetch.word2 .OFFSET(attribs[i].offset); if (attrib.type) { auto selX = latte::SQ_SEL::SEL_X; auto fetchType = latte::SQ_VTX_FETCH_TYPE::VERTEX_DATA; if (attrib.type == GX2AttribIndexType::PerInstance) { fetchType = latte::SQ_VTX_FETCH_TYPE::INSTANCE_DATA; if (attrib.aluDivisor == 1) { selX = latte::SQ_SEL::SEL_W; } else if (attrib.aluDivisor == fetchShader->divisors[0]) { selX = latte::SQ_SEL::SEL_Y; } else if (attrib.aluDivisor == fetchShader->divisors[1]) { selX = latte::SQ_SEL::SEL_Z; } else { fetchShader->divisors[fetchShader->numDivisors] = attrib.aluDivisor; if (fetchShader->numDivisors == 0) { selX = latte::SQ_SEL::SEL_Y; } else if (fetchShader->numDivisors == 1) { selX = latte::SQ_SEL::SEL_Z; } fetchShader->numDivisors++; } } vfetch.word0 = vfetch.word0 .FETCH_TYPE(fetchType) .SRC_SEL_X(selX); } else { vfetch.word0 = vfetch.word0 .SRC_GPR(indexMap[0].gpr) .SRC_SEL_X(static_cast(indexMap[0].chan)); } // Setup dest vfetch.gpr = vfetch.gpr .DST_GPR(attrib.location); vfetch.word1 = vfetch.word1 .DST_SEL_W(static_cast(attrib.mask & 0x7)) .DST_SEL_Z(static_cast((attrib.mask >> 8) & 0x7)) .DST_SEL_Y(static_cast((attrib.mask >> 16) & 0x7)) .DST_SEL_X(static_cast((attrib.mask >> 24) & 0x7)); // Setup mega fetch vfetch.word2 = vfetch.word2 .MEGA_FETCH(1); vfetch.word0 = vfetch.word0 .MEGA_FETCH_COUNT(internal::getAttribFormatBytes(attrib.format) - 1); // Setup format auto dataFormat = internal::getAttribFormatDataFormat(attrib.format); auto numFormat = latte::SQ_NUM_FORMAT::NORM; auto formatComp = latte::SQ_FORMAT_COMP::UNSIGNED; if (attribs[i].format & GX2AttribFormatFlags::SCALED) { numFormat = latte::SQ_NUM_FORMAT::SCALED; } else if (attribs[i].format & GX2AttribFormatFlags::INTEGER) { numFormat = latte::SQ_NUM_FORMAT::INT; } if (attribs[i].format & GX2AttribFormatFlags::SIGNED) { formatComp = latte::SQ_FORMAT_COMP::SIGNED; } vfetch.word1 = vfetch.word1 .DATA_FORMAT(dataFormat) .NUM_FORMAT_ALL(numFormat) .FORMAT_COMP_ALL(formatComp); auto swapMode = internal::getSwapModeEndian(static_cast(attribs[i].endianSwap & 3)); if (attribs[i].endianSwap == GX2EndianSwapMode::Default) { swapMode = internal::getAttribFormatEndian(attribs[i].format); } vfetch.word2 = vfetch.word2 .ENDIAN_SWAP(swapMode); // Append to program *(fetchPtr++) = vfetch; // Add extra tesselation vertex fetches if (type != GX2FetchShaderType::NoTessellation && attrib.type != GX2AttribIndexType::PerInstance) { auto perAttrib = fetchInstsPerAttrib(type); for (auto j = 1u; j < perAttrib; ++j) { latte::VertexFetchInst vfetch2 = vfetch; // Update src/dst vfetch2.word0 = vfetch2.word0 .SRC_GPR(indexMap[j].gpr) .SRC_SEL_X(static_cast(indexMap[j].chan)); vfetch2.gpr = vfetch2.gpr .DST_GPR(j + attrib.location); // Append to program *(fetchPtr++) = vfetch; } } } } // Generate tessellation ALU ops if (type != GX2FetchShaderType::NoTessellation) { numGPRs = 2; if (tessMode == GX2TessellationMode::Adaptive) { switch (type) { case GX2FetchShaderType::LineTessellation: numGPRs = 3; break; case GX2FetchShaderType::TriangleTessellation: numGPRs = 7; break; case GX2FetchShaderType::QuadTessellation: numGPRs = 7; break; } } // TODO: GX2FSGenTessAluOps barrier = true; } // Generate a VTX CF per 16 VFETCH if (fetchCount) { for (auto i = 0u; i < cfCount - 1; ++i) { auto fetches = FetchesPerControlFlow; if (fetchCount < (i + 1) * FetchesPerControlFlow) { // Don't overrun our fetches! fetches = fetchCount % FetchesPerControlFlow; } latte::ControlFlowInst inst; std::memset(&inst, 0, sizeof(inst)); inst.word0 = inst.word0 .ADDR(static_cast((fetchOffset + sizeof(latte::VertexFetchInst) * i * FetchesPerControlFlow) / 8)); inst.word1 = inst.word1 .COUNT((fetches - 1) & 0x7) .COUNT_3(((fetches - 1) >> 3) & 0x1) .CF_INST(latte::SQ_CF_INST_VTX_TC) .BARRIER(barrier ? 1 : 0); *(cfPtr++) = inst; } } // Generate tessellation "post" ALU ops if (numGPRs) { // TODO: GX2FSGenPostAluOps } // Generate an EOP latte::ControlFlowInst eop; std::memset(&eop, 0, sizeof(eop)); eop.word1 = eop.word1 .BARRIER(1) .CF_INST(latte::SQ_CF_INST_RETURN); *(cfPtr++) = eop; // Set sq_pgm_resources_fs auto sq_pgm_resources_fs = fetchShader->regs.sq_pgm_resources_fs.value(); sq_pgm_resources_fs = sq_pgm_resources_fs .NUM_GPRS(numGPRs); fetchShader->regs.sq_pgm_resources_fs = sq_pgm_resources_fs; GX2Invalidate(GX2InvalidateMode::CPU, fetchShader->data, fetchShader->size); } void Library::registerFetchShadersSymbols() { RegisterFunctionExport(GX2CalcFetchShaderSizeEx); RegisterFunctionExport(GX2InitFetchShaderEx); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_fetchshader.h ================================================ #pragma once #include "gx2_enum.h" #include #include namespace cafe::gx2 { /** * \defgroup gx2_shaders Shaders * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2AttribStream; struct GX2FetchShader { be2_val type; struct { be2_val sq_pgm_resources_fs; } regs; be2_val size; be2_virt_ptr data; be2_val attribCount; be2_val numDivisors; be2_val divisors[2]; }; CHECK_OFFSET(GX2FetchShader, 0x0, type); CHECK_OFFSET(GX2FetchShader, 0x4, regs.sq_pgm_resources_fs); CHECK_OFFSET(GX2FetchShader, 0x8, size); CHECK_OFFSET(GX2FetchShader, 0xC, data); CHECK_OFFSET(GX2FetchShader, 0x10, attribCount); CHECK_OFFSET(GX2FetchShader, 0x14, numDivisors); CHECK_OFFSET(GX2FetchShader, 0x18, divisors); CHECK_SIZE(GX2FetchShader, 0x20); #pragma pack(pop) uint32_t GX2CalcFetchShaderSizeEx(uint32_t attribs, GX2FetchShaderType fetchShaderType, GX2TessellationMode tesellationMode); void GX2InitFetchShaderEx(virt_ptr fetchShader, virt_ptr buffer, uint32_t attribCount, virt_ptr attribs, GX2FetchShaderType type, GX2TessellationMode tessMode); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_format.cpp ================================================ #include "gx2.h" #include "gx2_enum_string.h" #include "gx2_format.h" #include #include #include namespace cafe::gx2 { struct GX2SurfaceFormatData { uint8_t bpp; GX2SurfaceUse use; uint8_t endian; uint8_t sourceFormat; // CB_SOURCE_FORMAT }; static GX2SurfaceFormatData sSurfaceFormatData[] = { { 0, GX2SurfaceUse::None, 0, 1 }, { 8, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 8, GX2SurfaceUse::Texture, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::DepthBuffer, 0, 0 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 }, { 16, GX2SurfaceUse::Texture, 0, 1 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::DepthBuffer, 0, 0 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::DepthBuffer, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 }, { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 }, { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::DepthBuffer, 0, 0 }, { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 128, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 128, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 1 }, { 16, GX2SurfaceUse::Texture, 0, 0 }, { 16, GX2SurfaceUse::Texture, 0, 0 }, { 32, GX2SurfaceUse::Texture, 0, 0 }, { 32, GX2SurfaceUse::Texture, 0, 0 }, { 32, GX2SurfaceUse::Texture, 0, 0 }, { 0, GX2SurfaceUse::Texture, 0, 1 }, { 0, GX2SurfaceUse::Texture, 0, 0 }, { 0, GX2SurfaceUse::Texture, 0, 0 }, { 96, GX2SurfaceUse::Texture, 0, 0 }, { 96, GX2SurfaceUse::Texture, 0, 0 }, { 64, GX2SurfaceUse::Texture, 0, 1 }, { 128, GX2SurfaceUse::Texture, 0, 1 }, { 128, GX2SurfaceUse::Texture, 0, 1 }, { 64, GX2SurfaceUse::Texture, 0, 1 }, { 128, GX2SurfaceUse::Texture, 0, 1 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, { 0, GX2SurfaceUse::None, 0, 0 }, }; BOOL GX2CheckSurfaceUseVsFormat(GX2SurfaceUse use, GX2SurfaceFormat format) { return internal::getSurfaceUse(format) & use ? TRUE : FALSE; } uint32_t GX2GetAttribFormatBits(GX2AttribFormat format) { switch (format & 0x1F) { case GX2AttribFormatType::TYPE_8: case GX2AttribFormatType::TYPE_4_4: return 8; case GX2AttribFormatType::TYPE_16: case GX2AttribFormatType::TYPE_16_FLOAT: case GX2AttribFormatType::TYPE_8_8: return 16; case GX2AttribFormatType::TYPE_32: case GX2AttribFormatType::TYPE_32_FLOAT: case GX2AttribFormatType::TYPE_16_16: case GX2AttribFormatType::TYPE_16_16_FLOAT: case GX2AttribFormatType::TYPE_10_11_11_FLOAT: case GX2AttribFormatType::TYPE_8_8_8_8: case GX2AttribFormatType::TYPE_10_10_10_2: return 32; case GX2AttribFormatType::TYPE_32_32: case GX2AttribFormatType::TYPE_32_32_FLOAT: case GX2AttribFormatType::TYPE_16_16_16_16: case GX2AttribFormatType::TYPE_16_16_16_16_FLOAT: return 64; case GX2AttribFormatType::TYPE_32_32_32: case GX2AttribFormatType::TYPE_32_32_32_FLOAT: return 96; case GX2AttribFormatType::TYPE_32_32_32_32: case GX2AttribFormatType::TYPE_32_32_32_32_FLOAT: return 128; default: decaf_abort(fmt::format("Invalid GX2AttribFormat {}", to_string(format))); } } uint32_t GX2GetSurfaceFormatBits(GX2SurfaceFormat format) { auto latteFormat = format & 0x3F; auto bpp = sSurfaceFormatData[latteFormat].bpp; if (latteFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && latteFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { bpp >>= 4; } return bpp; } uint32_t GX2GetSurfaceFormatBitsPerElement(GX2SurfaceFormat format) { return sSurfaceFormatData[format & 0x3F].bpp; } BOOL GX2SurfaceIsCompressed(GX2SurfaceFormat format) { auto latteFormat = format & 0x3F; if (latteFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && latteFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { return TRUE; } return FALSE; } namespace internal { uint32_t getAttribFormatBytes(GX2AttribFormat format) { return GX2GetAttribFormatBits(format) / 8; } latte::SQ_DATA_FORMAT getAttribFormatDataFormat(GX2AttribFormat format) { switch (format & 0x1F) { case GX2AttribFormatType::TYPE_8: return latte::SQ_DATA_FORMAT::FMT_8; case GX2AttribFormatType::TYPE_4_4: return latte::SQ_DATA_FORMAT::FMT_4_4; case GX2AttribFormatType::TYPE_16: return latte::SQ_DATA_FORMAT::FMT_16; case GX2AttribFormatType::TYPE_16_FLOAT: return latte::SQ_DATA_FORMAT::FMT_16_FLOAT; case GX2AttribFormatType::TYPE_8_8: return latte::SQ_DATA_FORMAT::FMT_8_8; case GX2AttribFormatType::TYPE_32: return latte::SQ_DATA_FORMAT::FMT_32; case GX2AttribFormatType::TYPE_32_FLOAT: return latte::SQ_DATA_FORMAT::FMT_32_FLOAT; case GX2AttribFormatType::TYPE_16_16: return latte::SQ_DATA_FORMAT::FMT_16_16; case GX2AttribFormatType::TYPE_16_16_FLOAT: return latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT; case GX2AttribFormatType::TYPE_10_11_11_FLOAT: return latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT; case GX2AttribFormatType::TYPE_8_8_8_8: return latte::SQ_DATA_FORMAT::FMT_8_8_8_8; case GX2AttribFormatType::TYPE_10_10_10_2: return latte::SQ_DATA_FORMAT::FMT_10_10_10_2; case GX2AttribFormatType::TYPE_32_32: return latte::SQ_DATA_FORMAT::FMT_32_32; case GX2AttribFormatType::TYPE_32_32_FLOAT: return latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT; case GX2AttribFormatType::TYPE_16_16_16_16: return latte::SQ_DATA_FORMAT::FMT_16_16_16_16; case GX2AttribFormatType::TYPE_16_16_16_16_FLOAT: return latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT; case GX2AttribFormatType::TYPE_32_32_32: return latte::SQ_DATA_FORMAT::FMT_32_32_32; case GX2AttribFormatType::TYPE_32_32_32_FLOAT: return latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT; case GX2AttribFormatType::TYPE_32_32_32_32: return latte::SQ_DATA_FORMAT::FMT_32_32_32_32; case GX2AttribFormatType::TYPE_32_32_32_32_FLOAT: return latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT; default: decaf_abort(fmt::format("Invalid GX2AttribFormat {}", to_string(format))); } } GX2EndianSwapMode getAttribFormatSwapMode(GX2AttribFormat format) { switch (format & 0x1F) { case GX2AttribFormatType::TYPE_8: case GX2AttribFormatType::TYPE_4_4: case GX2AttribFormatType::TYPE_8_8: case GX2AttribFormatType::TYPE_8_8_8_8: return GX2EndianSwapMode::None; case GX2AttribFormatType::TYPE_16: case GX2AttribFormatType::TYPE_16_FLOAT: case GX2AttribFormatType::TYPE_16_16: case GX2AttribFormatType::TYPE_16_16_FLOAT: case GX2AttribFormatType::TYPE_16_16_16_16: case GX2AttribFormatType::TYPE_16_16_16_16_FLOAT: return GX2EndianSwapMode::Swap8In16; case GX2AttribFormatType::TYPE_32: case GX2AttribFormatType::TYPE_32_FLOAT: case GX2AttribFormatType::TYPE_10_11_11_FLOAT: case GX2AttribFormatType::TYPE_10_10_10_2: case GX2AttribFormatType::TYPE_32_32: case GX2AttribFormatType::TYPE_32_32_FLOAT: case GX2AttribFormatType::TYPE_32_32_32: case GX2AttribFormatType::TYPE_32_32_32_FLOAT: case GX2AttribFormatType::TYPE_32_32_32_32: case GX2AttribFormatType::TYPE_32_32_32_32_FLOAT: return GX2EndianSwapMode::Swap8In32; default: decaf_abort(fmt::format("Invalid GX2AttribFormat {}", to_string(format))); } } latte::SQ_ENDIAN getAttribFormatEndian(GX2AttribFormat format) { return getSwapModeEndian(getAttribFormatSwapMode(format)); } uint32_t getSurfaceFormatBytesPerElement(GX2SurfaceFormat format) { return sSurfaceFormatData[format & 0x3F].bpp / 8; } GX2SurfaceUse getSurfaceUse(GX2SurfaceFormat format) { auto idx = format & 0x3F; if (idx == 17 || idx == 28) { return GX2SurfaceUse::DepthBuffer | GX2SurfaceUse::Texture; } return static_cast(sSurfaceFormatData[format & 0x3F].use); } GX2EndianSwapMode getSurfaceFormatSwapMode(GX2SurfaceFormat format) { return static_cast(sSurfaceFormatData[format & 0x3F].endian); } latte::SQ_ENDIAN getSurfaceFormatEndian(GX2SurfaceFormat format) { return getSwapModeEndian(getSurfaceFormatSwapMode(format)); } GX2SurfaceFormatType getSurfaceFormatType(GX2SurfaceFormat format) { return static_cast(format >> 8); } latte::CB_ENDIAN getSurfaceFormatColorEndian(GX2SurfaceFormat format) { return static_cast(sSurfaceFormatData[format & 0x3F].endian); } latte::CB_FORMAT getSurfaceFormatColorFormat(GX2SurfaceFormat format) { switch (format & 0x3F) { case latte::SQ_DATA_FORMAT::FMT_8: return latte::CB_FORMAT::COLOR_8; case latte::SQ_DATA_FORMAT::FMT_4_4: return latte::CB_FORMAT::COLOR_4_4; case latte::SQ_DATA_FORMAT::FMT_3_3_2: return latte::CB_FORMAT::COLOR_3_3_2; case latte::SQ_DATA_FORMAT::FMT_16: return latte::CB_FORMAT::COLOR_16; case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: return latte::CB_FORMAT::COLOR_16_FLOAT; case latte::SQ_DATA_FORMAT::FMT_8_8: return latte::CB_FORMAT::COLOR_8_8; case latte::SQ_DATA_FORMAT::FMT_5_6_5: return latte::CB_FORMAT::COLOR_5_6_5; case latte::SQ_DATA_FORMAT::FMT_6_5_5: return latte::CB_FORMAT::COLOR_6_5_5; case latte::SQ_DATA_FORMAT::FMT_1_5_5_5: return latte::CB_FORMAT::COLOR_1_5_5_5; case latte::SQ_DATA_FORMAT::FMT_4_4_4_4: return latte::CB_FORMAT::COLOR_4_4_4_4; case latte::SQ_DATA_FORMAT::FMT_5_5_5_1: return latte::CB_FORMAT::COLOR_5_5_5_1; case latte::SQ_DATA_FORMAT::FMT_32: return latte::CB_FORMAT::COLOR_32; case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: return latte::CB_FORMAT::COLOR_32_FLOAT; case latte::SQ_DATA_FORMAT::FMT_16_16: return latte::CB_FORMAT::COLOR_16_16; case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: return latte::CB_FORMAT::COLOR_16_16_FLOAT; case latte::SQ_DATA_FORMAT::FMT_8_24: return latte::CB_FORMAT::COLOR_8_24; case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT: return latte::CB_FORMAT::COLOR_8_24_FLOAT; case latte::SQ_DATA_FORMAT::FMT_24_8: return latte::CB_FORMAT::COLOR_24_8; case latte::SQ_DATA_FORMAT::FMT_24_8_FLOAT: return latte::CB_FORMAT::COLOR_24_8_FLOAT; case latte::SQ_DATA_FORMAT::FMT_10_11_11: return latte::CB_FORMAT::COLOR_10_11_11; case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT: return latte::CB_FORMAT::COLOR_10_11_11_FLOAT; case latte::SQ_DATA_FORMAT::FMT_11_11_10: return latte::CB_FORMAT::COLOR_11_11_10; case latte::SQ_DATA_FORMAT::FMT_11_11_10_FLOAT: return latte::CB_FORMAT::COLOR_11_11_10_FLOAT; case latte::SQ_DATA_FORMAT::FMT_2_10_10_10: return latte::CB_FORMAT::COLOR_2_10_10_10; case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: return latte::CB_FORMAT::COLOR_8_8_8_8; case latte::SQ_DATA_FORMAT::FMT_10_10_10_2: return latte::CB_FORMAT::COLOR_10_10_10_2; case latte::SQ_DATA_FORMAT::FMT_X24_8_32_FLOAT: return latte::CB_FORMAT::COLOR_X24_8_32_FLOAT; case latte::SQ_DATA_FORMAT::FMT_32_32: return latte::CB_FORMAT::COLOR_32_32; case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: return latte::CB_FORMAT::COLOR_32_32_FLOAT; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: return latte::CB_FORMAT::COLOR_16_16_16_16; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: return latte::CB_FORMAT::COLOR_16_16_16_16_FLOAT; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: return latte::CB_FORMAT::COLOR_32_32_32_32; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: return latte::CB_FORMAT::COLOR_32_32_32_32_FLOAT; default: decaf_abort(fmt::format("Invalid GX2SurfaceFormat {}", to_string(format))); } } latte::CB_NUMBER_TYPE getSurfaceFormatColorNumberType(GX2SurfaceFormat format) { switch (internal::getSurfaceFormatType(format)) { case GX2SurfaceFormatType::UNORM: return latte::CB_NUMBER_TYPE::UNORM; case GX2SurfaceFormatType::UINT: return latte::CB_NUMBER_TYPE::UINT; case GX2SurfaceFormatType::SNORM: return latte::CB_NUMBER_TYPE::SNORM; case GX2SurfaceFormatType::SINT: return latte::CB_NUMBER_TYPE::SINT; case GX2SurfaceFormatType::SRGB: return latte::CB_NUMBER_TYPE::SRGB; case GX2SurfaceFormatType::FLOAT: return latte::CB_NUMBER_TYPE::FLOAT; default: decaf_abort(fmt::format("Invalid GX2SurfaceFormat {}", to_string(format))); } } latte::CB_SOURCE_FORMAT getSurfaceFormatColorSourceFormat(GX2SurfaceFormat format) { return static_cast(sSurfaceFormatData[format & 0x3F].sourceFormat); } latte::SQ_ENDIAN getSwapModeEndian(GX2EndianSwapMode mode) { switch (mode) { case GX2EndianSwapMode::None: return latte::SQ_ENDIAN::NONE; case GX2EndianSwapMode::Swap8In16: return latte::SQ_ENDIAN::SWAP_8IN16; case GX2EndianSwapMode::Swap8In32: return latte::SQ_ENDIAN::SWAP_8IN32; case GX2EndianSwapMode::Default: return latte::SQ_ENDIAN::AUTO; default: decaf_abort(fmt::format("Invalid GX2EndianSwapMode {}", to_string(mode))); } } } // namespace internal void Library::registerFormatSymbols() { RegisterFunctionExport(GX2CheckSurfaceUseVsFormat); RegisterFunctionExport(GX2GetAttribFormatBits); RegisterFunctionExport(GX2GetSurfaceFormatBits); RegisterFunctionExport(GX2GetSurfaceFormatBitsPerElement); RegisterFunctionExport(GX2SurfaceIsCompressed); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_format.h ================================================ #pragma once #include "gx2_enum.h" #include #include #include #include #include #include namespace cafe::gx2 { BOOL GX2CheckSurfaceUseVsFormat(GX2SurfaceUse use, GX2SurfaceFormat format); uint32_t GX2GetAttribFormatBits(GX2AttribFormat format); uint32_t GX2GetSurfaceFormatBits(GX2SurfaceFormat format); uint32_t GX2GetSurfaceFormatBitsPerElement(GX2SurfaceFormat format); BOOL GX2SurfaceIsCompressed(GX2SurfaceFormat format); namespace internal { uint32_t getAttribFormatBytes(GX2AttribFormat format); latte::SQ_DATA_FORMAT getAttribFormatDataFormat(GX2AttribFormat type); GX2EndianSwapMode getAttribFormatSwapMode(GX2AttribFormat format); latte::SQ_ENDIAN getAttribFormatEndian(GX2AttribFormat format); uint32_t getSurfaceFormatBytesPerElement(GX2SurfaceFormat format); GX2SurfaceUse getSurfaceUse(GX2SurfaceFormat format); latte::CB_ENDIAN getSurfaceFormatColorEndian(GX2SurfaceFormat format); latte::CB_FORMAT getSurfaceFormatColorFormat(GX2SurfaceFormat format); latte::CB_NUMBER_TYPE getSurfaceFormatColorNumberType(GX2SurfaceFormat format); latte::CB_SOURCE_FORMAT getSurfaceFormatColorSourceFormat(GX2SurfaceFormat format); latte::SQ_ENDIAN getSurfaceFormatEndian(GX2SurfaceFormat format); GX2EndianSwapMode getSurfaceFormatSwapMode(GX2SurfaceFormat format); GX2SurfaceFormatType getSurfaceFormatType(GX2SurfaceFormat format); latte::SQ_ENDIAN getSwapModeEndian(GX2EndianSwapMode mode); } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_flush.cpp ================================================ #include "decaf_graphics.h" #include "gx2_internal_flush.h" #include "gx2_internal_pm4cap.h" namespace cafe::gx2::internal { void notifyCpuFlush(phys_addr address, uint32_t size) { captureCpuFlush(address, size); decaf::getGraphicsDriver()->notifyCpuFlush(address, size); } void notifyGpuFlush(phys_addr address, uint32_t size) { captureGpuFlush(address, size); decaf::getGraphicsDriver()->notifyGpuFlush(address, size); } } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_flush.h ================================================ #pragma once #include namespace cafe::gx2::internal { void notifyCpuFlush(phys_addr address, uint32_t size); void notifyGpuFlush(phys_addr address, uint32_t size); } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_gfd.cpp ================================================ #include "gx2_internal_gfd.h" namespace cafe::gx2::internal { void gfdToGX2Surface(const gfd::GFDSurface &src, GX2Surface *dst) { dst->dim = src.dim; dst->width = src.width; dst->height = src.height; dst->depth = src.depth; dst->mipLevels = src.mipLevels; dst->format = src.format; dst->aa = src.aa; dst->use = src.use; dst->imageSize = static_cast(src.image.size()); dst->mipmapSize = static_cast(src.mipmap.size()); dst->tileMode = src.tileMode; dst->swizzle = src.swizzle; dst->alignment = src.alignment; dst->pitch = src.pitch; for (auto i = 0u; i < src.mipLevelOffset.size(); ++i) { dst->mipLevelOffset[i] = src.mipLevelOffset[i]; } } void gx2ToGFDSurface(const GX2Surface *src, gfd::GFDSurface &dst) { dst.dim = src->dim; dst.width = src->width; dst.height = src->height; dst.depth = src->depth; dst.mipLevels = src->mipLevels; dst.format = src->format; dst.aa = src->aa; dst.use = src->use; dst.image.resize(src->imageSize); std::memcpy(dst.image.data(), src->image.get(), src->imageSize); dst.mipmap.resize(src->mipmapSize); std::memcpy(dst.mipmap.data(), src->mipmaps.get(), src->mipmapSize); dst.tileMode = src->tileMode; dst.swizzle = src->swizzle; dst.alignment = src->alignment; dst.pitch = src->pitch; for (auto i = 0u; i < src->mipLevelOffset.size(); ++i) { dst.mipLevelOffset[i] = src->mipLevelOffset[i]; } } void gfdToGX2Texture(const gfd::GFDTexture &src, GX2Texture *dst) { gfdToGX2Surface(src.surface, virt_addrof(dst->surface).get()); dst->viewFirstMip = src.viewFirstMip; dst->viewNumMips = src.viewNumMips; dst->viewFirstSlice = src.viewFirstSlice; dst->viewNumSlices = src.viewNumSlices; dst->compMap = src.compMap; dst->regs.word0 = src.regs.word0; dst->regs.word1 = src.regs.word1; dst->regs.word4 = src.regs.word4; dst->regs.word5 = src.regs.word5; dst->regs.word6 = src.regs.word6; } void gx2ToGFDTexture(const GX2Texture *src, gfd::GFDTexture &dst) { gx2ToGFDSurface(&src->surface, dst.surface); dst.viewFirstMip = src->viewFirstMip; dst.viewNumMips = src->viewNumMips; dst.viewFirstSlice = src->viewFirstSlice; dst.viewNumSlices = src->viewNumSlices; dst.compMap = src->compMap; dst.regs.word0 = src->regs.word0; dst.regs.word1 = src->regs.word1; dst.regs.word4 = src->regs.word4; dst.regs.word5 = src->regs.word5; dst.regs.word6 = src->regs.word6; } void gx2ToGFDVertexShader(const GX2VertexShader *src, gfd::GFDVertexShader &dst) { dst.regs.sq_pgm_resources_vs = src->regs.sq_pgm_resources_vs; dst.regs.vgt_primitiveid_en = src->regs.vgt_primitiveid_en; dst.regs.spi_vs_out_config = src->regs.spi_vs_out_config; dst.regs.num_spi_vs_out_id = src->regs.num_spi_vs_out_id; for (auto i = 0u; i < src->regs.spi_vs_out_id.size(); ++i) { dst.regs.spi_vs_out_id[i] = src->regs.spi_vs_out_id[i]; } dst.regs.pa_cl_vs_out_cntl = src->regs.pa_cl_vs_out_cntl; dst.regs.sq_vtx_semantic_clear = src->regs.sq_vtx_semantic_clear; dst.regs.num_sq_vtx_semantic = src->regs.num_sq_vtx_semantic; for (auto i = 0u; i < src->regs.sq_vtx_semantic.size(); ++i) { dst.regs.sq_vtx_semantic[i] = src->regs.sq_vtx_semantic[i]; } dst.regs.vgt_strmout_buffer_en = src->regs.vgt_strmout_buffer_en; dst.regs.vgt_vertex_reuse_block_cntl = src->regs.vgt_vertex_reuse_block_cntl; dst.regs.vgt_hos_reuse_depth = src->regs.vgt_hos_reuse_depth; dst.mode = src->mode; dst.uniformBlocks.resize(src->uniformBlockCount); for (auto i = 0u; i < src->uniformBlockCount; ++i) { dst.uniformBlocks[i].name = src->uniformBlocks[i].name.get(); dst.uniformBlocks[i].offset = src->uniformBlocks[i].offset; dst.uniformBlocks[i].size = src->uniformBlocks[i].size; } dst.uniformVars.resize(src->uniformVarCount); for (auto i = 0u; i < src->uniformBlockCount; ++i) { dst.uniformVars[i].name = src->uniformVars[i].name.get(); dst.uniformVars[i].type = src->uniformVars[i].type; dst.uniformVars[i].count = src->uniformVars[i].count; dst.uniformVars[i].offset = src->uniformVars[i].offset; dst.uniformVars[i].block = src->uniformVars[i].block; } dst.initialValues.resize(src->initialValueCount); for (auto i = 0u; i < src->initialValueCount; ++i) { for (auto j = 0u; j < dst.initialValues[i].value.size(); ++j) { dst.initialValues[i].value[j] = src->initialValues[i].value[j]; } dst.initialValues[i].offset = src->initialValues[i].offset; } dst.loopVars.resize(src->loopVarCount); for (auto i = 0u; i < src->loopVarCount; ++i) { dst.loopVars[i].offset = src->loopVars[i].offset; dst.loopVars[i].value = src->loopVars[i].value; } dst.samplerVars.resize(src->samplerVarCount); for (auto i = 0u; i < src->samplerVarCount; ++i) { dst.samplerVars[i].name = src->samplerVars[i].name.get(); dst.samplerVars[i].type = src->samplerVars[i].type; dst.samplerVars[i].location = src->samplerVars[i].location; } dst.attribVars.resize(src->attribVarCount); for (auto i = 0u; i < src->attribVarCount; ++i) { dst.attribVars[i].name = src->attribVars[i].name.get(); dst.attribVars[i].type = src->attribVars[i].type; dst.attribVars[i].count = src->attribVars[i].count; dst.attribVars[i].location = src->attribVars[i].location; } dst.ringItemSize = src->ringItemsize; dst.hasStreamOut = src->hasStreamOut; for (auto i = 0u; i < dst.streamOutStride.size(); ++i) { dst.streamOutStride[i] = src->streamOutStride[i]; } dst.gx2rData.elemCount = src->gx2rData.elemCount; dst.gx2rData.elemSize = src->gx2rData.elemSize; dst.gx2rData.flags = src->gx2rData.flags; if (src->gx2rData.buffer) { auto size = src->gx2rData.elemCount * src->gx2rData.elemSize; dst.gx2rData.buffer.resize(size); std::memcpy(dst.gx2rData.buffer.data(), src->gx2rData.buffer.get(), size); } } void gx2ToGFDPixelShader(const GX2PixelShader *src, gfd::GFDPixelShader &dst) { dst.regs.sq_pgm_resources_ps = src->regs.sq_pgm_resources_ps; dst.regs.sq_pgm_exports_ps = src->regs.sq_pgm_exports_ps; dst.regs.spi_ps_in_control_0 = src->regs.spi_ps_in_control_0; dst.regs.spi_ps_in_control_1 = src->regs.spi_ps_in_control_1; dst.regs.num_spi_ps_input_cntl = src->regs.num_spi_ps_input_cntl; for (auto i = 0u; i < src->regs.spi_ps_input_cntls.size(); ++i) { dst.regs.spi_ps_input_cntls[i] = src->regs.spi_ps_input_cntls[i]; } dst.regs.cb_shader_mask = src->regs.cb_shader_mask; dst.regs.cb_shader_control = src->regs.cb_shader_control; dst.regs.db_shader_control = src->regs.db_shader_control; dst.regs.spi_input_z = src->regs.spi_input_z; dst.data.resize(src->size); std::memcpy(dst.data.data(), src->data.get(), src->size); dst.mode = src->mode; dst.uniformBlocks.resize(src->uniformBlockCount); for (auto i = 0u; i < src->uniformBlockCount; ++i) { dst.uniformBlocks[i].name = src->uniformBlocks[i].name.get(); dst.uniformBlocks[i].offset = src->uniformBlocks[i].offset; dst.uniformBlocks[i].size = src->uniformBlocks[i].size; } dst.uniformVars.resize(src->uniformVarCount); for (auto i = 0u; i < src->uniformBlockCount; ++i) { dst.uniformVars[i].name = src->uniformVars[i].name.get(); dst.uniformVars[i].type = src->uniformVars[i].type; dst.uniformVars[i].count = src->uniformVars[i].count; dst.uniformVars[i].offset = src->uniformVars[i].offset; dst.uniformVars[i].block = src->uniformVars[i].block; } dst.initialValues.resize(src->initialValueCount); for (auto i = 0u; i < src->initialValueCount; ++i) { for (auto j = 0u; j < dst.initialValues[i].value.size(); ++j) { dst.initialValues[i].value[j] = src->initialValues[i].value[j]; } dst.initialValues[i].offset = src->initialValues[i].offset; } dst.loopVars.resize(src->loopVarCount); for (auto i = 0u; i < src->loopVarCount; ++i) { dst.loopVars[i].offset = src->loopVars[i].offset; dst.loopVars[i].value = src->loopVars[i].value; } dst.samplerVars.resize(src->samplerVarCount); for (auto i = 0u; i < src->samplerVarCount; ++i) { dst.samplerVars[i].name = src->samplerVars[i].name.get(); dst.samplerVars[i].type = src->samplerVars[i].type; dst.samplerVars[i].location = src->samplerVars[i].location; } dst.gx2rData.elemCount = src->gx2rData.elemCount; dst.gx2rData.elemSize = src->gx2rData.elemSize; dst.gx2rData.flags = src->gx2rData.flags; if (src->gx2rData.buffer) { auto size = src->gx2rData.elemCount * src->gx2rData.elemSize; dst.gx2rData.buffer.resize(size); std::memcpy(dst.gx2rData.buffer.data(), src->gx2rData.buffer.get(), size); } } void gx2ToGFDGeometryShader(const GX2GeometryShader *src, gfd::GFDGeometryShader &dst) { dst.regs.sq_pgm_resources_gs = src->regs.sq_pgm_resources_gs; dst.regs.vgt_gs_out_prim_type = src->regs.vgt_gs_out_prim_type; dst.regs.vgt_gs_mode = src->regs.vgt_gs_mode; dst.regs.pa_cl_vs_out_cntl = src->regs.pa_cl_vs_out_cntl; dst.regs.sq_pgm_resources_vs = src->regs.sq_pgm_resources_vs; dst.regs.sq_gs_vert_itemsize = src->regs.sq_gs_vert_itemsize; dst.regs.spi_vs_out_config = src->regs.spi_vs_out_config; dst.regs.num_spi_vs_out_id = src->regs.num_spi_vs_out_id; for (auto i = 0u; i < src->regs.spi_vs_out_id.size(); ++i) { dst.regs.spi_vs_out_id[i] = src->regs.spi_vs_out_id[i]; } dst.regs.vgt_strmout_buffer_en = src->regs.vgt_strmout_buffer_en; dst.data.resize(src->size); std::memcpy(dst.data.data(), src->data.get(), src->size); dst.vertexShaderData.resize(src->vertexShaderSize); std::memcpy(dst.vertexShaderData.data(), src->vertexShaderData.get(), src->vertexShaderSize); dst.mode = src->mode; dst.uniformBlocks.resize(src->uniformBlockCount); for (auto i = 0u; i < src->uniformBlockCount; ++i) { dst.uniformBlocks[i].name = src->uniformBlocks[i].name.get(); dst.uniformBlocks[i].offset = src->uniformBlocks[i].offset; dst.uniformBlocks[i].size = src->uniformBlocks[i].size; } dst.uniformVars.resize(src->uniformVarCount); for (auto i = 0u; i < src->uniformBlockCount; ++i) { dst.uniformVars[i].name = src->uniformVars[i].name.get(); dst.uniformVars[i].type = src->uniformVars[i].type; dst.uniformVars[i].count = src->uniformVars[i].count; dst.uniformVars[i].offset = src->uniformVars[i].offset; dst.uniformVars[i].block = src->uniformVars[i].block; } dst.initialValues.resize(src->initialValueCount); for (auto i = 0u; i < src->initialValueCount; ++i) { for (auto j = 0u; j < dst.initialValues[i].value.size(); ++j) { dst.initialValues[i].value[j] = src->initialValues[i].value[j]; } dst.initialValues[i].offset = src->initialValues[i].offset; } dst.loopVars.resize(src->loopVarCount); for (auto i = 0u; i < src->loopVarCount; ++i) { dst.loopVars[i].offset = src->loopVars[i].offset; dst.loopVars[i].value = src->loopVars[i].value; } dst.samplerVars.resize(src->samplerVarCount); for (auto i = 0u; i < src->samplerVarCount; ++i) { dst.samplerVars[i].name = src->samplerVars[i].name.get(); dst.samplerVars[i].type = src->samplerVars[i].type; dst.samplerVars[i].location = src->samplerVars[i].location; } dst.ringItemSize = src->ringItemSize; dst.hasStreamOut = src->hasStreamOut; for (auto i = 0u; i < dst.streamOutStride.size(); ++i) { dst.streamOutStride[i] = src->streamOutStride[i]; } dst.gx2rData.elemCount = src->gx2rData.elemCount; dst.gx2rData.elemSize = src->gx2rData.elemSize; dst.gx2rData.flags = src->gx2rData.flags; if (src->gx2rData.buffer) { auto size = src->gx2rData.elemCount * src->gx2rData.elemSize; dst.gx2rData.buffer.resize(size); std::memcpy(dst.gx2rData.buffer.data(), src->gx2rData.buffer.get(), size); } dst.gx2rVertexShaderData.elemCount = src->gx2rVertexShaderData.elemCount; dst.gx2rVertexShaderData.elemSize = src->gx2rVertexShaderData.elemSize; dst.gx2rVertexShaderData.flags = src->gx2rVertexShaderData.flags; if (src->gx2rVertexShaderData.buffer) { auto size = src->gx2rVertexShaderData.elemCount * src->gx2rVertexShaderData.elemSize; dst.gx2rVertexShaderData.buffer.resize(size); std::memcpy(dst.gx2rVertexShaderData.buffer.data(), src->gx2rVertexShaderData.buffer.get(), size); } } } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_gfd.h ================================================ #pragma once #include "gx2_shaders.h" #include "gx2_surface.h" #include "gx2_texture.h" #include namespace cafe::gx2::internal { void gfdToGX2Surface(const gfd::GFDSurface &src, GX2Surface *dst); void gx2ToGFDSurface(const GX2Surface *src, gfd::GFDSurface &dst); void gfdToGX2Texture(const gfd::GFDTexture &src, GX2Texture *dst); void gx2ToGFDTexture(const GX2Texture *src, gfd::GFDTexture &dst); void gx2ToGFDVertexShader(const GX2VertexShader *src, gfd::GFDVertexShader &dst); void gx2ToGFDPixelShader(const GX2PixelShader *src, gfd::GFDPixelShader &dst); void gx2ToGFDGeometryShader(const GX2GeometryShader *src, gfd::GFDGeometryShader &dst); } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_pm4cap.cpp ================================================ #include "decaf_pm4replay.h" #include "gx2_display.h" #include "gx2_event.h" #include "gx2_internal_pm4cap.h" #include "gx2_cbpool.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using decaf::pm4::CaptureMagic; using decaf::pm4::CapturePacket; using decaf::pm4::CaptureMemoryLoad; using decaf::pm4::CaptureSetBuffer; using namespace latte; using namespace latte::pm4; using namespace cafe::coreinit; static const auto HashAllMemory = true; static const auto HashShadowState = true; namespace cafe::gx2::internal { class Recorder { struct RecordedMemory { phys_addr start; phys_addr end; uint64_t hash[2]; }; public: Recorder() { mRegisters.fill(0); } bool requestStart(const std::string &path) { decaf_check(mState == CaptureState::Disabled); std::unique_lock lock { mMutex }; mOut.open(path, std::fstream::binary); if (!mOut.is_open()) { return false; } // Write magic header mOut.write(CaptureMagic.data(), CaptureMagic.size()); // Set intial state mRecordedMemory.clear(); mState = CaptureState::WaitStartNextFrame; return true; } void requestStop() { decaf_check(mState == CaptureState::Enabled); std::unique_lock lock { mMutex }; mState = CaptureState::WaitEndNextFrame; } void setCaptureNumFrames(size_t count) { mCaptureNumFrames = count; } CaptureState state() const { return mState; } void swap() { std::unique_lock lock { mMutex }; if (mState == CaptureState::WaitStartNextFrame) { start(); mCapturedFrames = 0; } else if (mState == CaptureState::WaitEndNextFrame) { stop(); mCaptureNumFrames = 0; } ++mCapturedFrames; if (mCapturedFrames == mCaptureNumFrames) { mState = CaptureState::WaitEndNextFrame; } } void commandBuffer(virt_ptr buffer, uint32_t numWords) { decaf_check(mState == CaptureState::Enabled || mState == CaptureState::WaitEndNextFrame); std::unique_lock lock { mMutex }; scanCommandBuffer( phys_cast(OSEffectiveToPhysical(virt_cast(buffer))), numWords); CapturePacket packet; packet.type = CapturePacket::CommandBuffer; packet.size = numWords * 4; writePacket(packet); writeData(buffer.get(), packet.size); } void cpuFlush(phys_addr address, uint32_t size) { decaf_check(mState == CaptureState::Enabled || mState == CaptureState::WaitEndNextFrame); std::unique_lock lock { mMutex }; trackMemory(CaptureMemoryLoad::CpuFlush, address, size); } void gpuFlush(phys_addr address, uint32_t size) { decaf_check(mState == CaptureState::Enabled || mState == CaptureState::WaitEndNextFrame); // Not sure if we need to do something here... } private: void start() { decaf_check(mState == CaptureState::Disabled || mState == CaptureState::WaitStartNextFrame); mState = CaptureState::Enabled; writeRegisterSnapshot(); writeDisplayInfo(); } void stop() { decaf_check(mState == CaptureState::Enabled || mState == CaptureState::WaitEndNextFrame); mOut.close(); mState = CaptureState::Disabled; } void writeRegisterSnapshot() { CapturePacket packet; packet.type = CapturePacket::RegisterSnapshot; packet.size = static_cast(mRegisters.size() * sizeof(uint32_t)); writePacket(packet); writeData(mRegisters.data(), packet.size); } void writeDisplayInfo() { auto tvScanBuffer = getTvScanBuffer(); if (tvScanBuffer->image) { CapturePacket packet; packet.type = CapturePacket::SetBuffer; packet.size = sizeof(CaptureSetBuffer); writePacket(packet); CaptureSetBuffer setBuffer; setBuffer.type = CaptureSetBuffer::TvBuffer; setBuffer.address = OSEffectiveToPhysical(virt_cast(tvScanBuffer->image)); setBuffer.size = tvScanBuffer->imageSize; setBuffer.renderMode = GX2TVRenderMode::Wide1080p; setBuffer.surfaceFormat = tvScanBuffer->format; setBuffer.bufferingMode = GX2BufferingMode::Double; setBuffer.width = tvScanBuffer->width; setBuffer.height = tvScanBuffer->height; writeData(&setBuffer, packet.size); } auto drcScanBuffer = getDrcScanBuffer(); if (drcScanBuffer->image) { CapturePacket packet; packet.type = CapturePacket::SetBuffer; packet.size = sizeof(CaptureSetBuffer); writePacket(packet); CaptureSetBuffer setBuffer; setBuffer.type = CaptureSetBuffer::DrcBuffer; setBuffer.address = OSEffectiveToPhysical(virt_cast(drcScanBuffer->image)); setBuffer.size = drcScanBuffer->imageSize; setBuffer.renderMode = GX2DrcRenderMode::Single; setBuffer.surfaceFormat = drcScanBuffer->format; setBuffer.bufferingMode = GX2BufferingMode::Double; setBuffer.width = drcScanBuffer->width; setBuffer.height = drcScanBuffer->height; writeData(&setBuffer, packet.size); } } void writePacket(CapturePacket &packet) { packet.timestamp = mPacketTimestamp++; mOut.write(reinterpret_cast(&packet), sizeof(CapturePacket)); } void writeData(void *data, uint32_t size) { mOut.write(reinterpret_cast(data), size); } void writeMemoryLoad(CaptureMemoryLoad::MemoryType type, phys_addr address, uint32_t size) { CaptureMemoryLoad load; load.type = type; load.address = address; CapturePacket packet; packet.type = CapturePacket::MemoryLoad; packet.size = size + sizeof(CaptureMemoryLoad); writePacket(packet); writeData(&load, sizeof(CaptureMemoryLoad)); writeData(phys_cast(address).get(), size); } void scanCommandBuffer(phys_ptr words, uint32_t numWords) { std::vector swapped; swapped.resize(numWords); for (auto i = 0u; i < swapped.size(); ++i) { swapped[i] = words[i]; } auto buffer = swapped.data(); auto bufferSize = swapped.size(); for (auto pos = size_t { 0u }; pos < bufferSize; ) { auto header = Header::get(buffer[pos]); auto size = size_t { 0u }; switch (header.type()) { case PacketType::Type0: { auto header0 = HeaderType0::get(header.value); size = header0.count() + 1; decaf_check(pos + size < bufferSize); scanType0(header0, gsl::make_span(&buffer[pos + 1], size)); break; } case PacketType::Type3: { auto header3 = HeaderType3::get(header.value); size = header3.size() + 1; decaf_check(pos + size < bufferSize); scanType3(header3, gsl::make_span(&buffer[pos + 1], size)); break; } case PacketType::Type2: { // This is a filler packet, like a "nop", ignore it break; } case PacketType::Type1: default: gLog->error("Invalid packet header type {}, header = 0x{:08X}", header.type(), header.value); size = bufferSize; break; } pos += size + 1; } } void scanType0(HeaderType0 header, const gsl::span &data) { } void trackSurface(phys_addr baseAddress, uint32_t pitch, uint32_t height, uint32_t depth, uint32_t aa, uint32_t level, bool isDepthBuffer, bool isScanBuffer, SQ_TEX_DIM dim, SQ_DATA_FORMAT format, SQ_TILE_MODE tileMode) { if (!baseAddress || !pitch || !height) { return; } // Adjust address for swizzling if (tileMode >= SQ_TILE_MODE::TILED_2D_THIN1) { baseAddress &= ~(0x800u - 1); } else { baseAddress &= ~(0x100u - 1); } auto desc = gpu7::tiling::SurfaceDescription{ }; desc.tileMode = static_cast(tileMode); desc.format = static_cast(format); desc.bpp = getDataFormatBitsPerElement(format); desc.numSamples = 1; // TODO: 1 << aa desc.width = pitch; desc.height = height; desc.numSlices = depth; desc.numLevels = 0; desc.bankSwizzle = 0; desc.pipeSwizzle = 0; if (isDepthBuffer) { desc.use |= gpu7::tiling::SurfaceUse::DepthBuffer; } if (isScanBuffer) { desc.use |= gpu7::tiling::SurfaceUse::ScanBuffer; } desc.dim = static_cast(dim); auto info = gpu7::tiling::computeSurfaceInfo(desc, 0); // TODO: Use align? info.baseAlign; // Track that badboy trackMemory(CaptureMemoryLoad::Surface, baseAddress, static_cast(info.surfSize)); } void trackColorBuffer(CB_COLORN_BASE cb_color_base, CB_COLORN_SIZE cb_color_size, CB_COLORN_INFO cb_color_info) { auto pitch_tile_max = cb_color_size.PITCH_TILE_MAX(); auto slice_tile_max = cb_color_size.SLICE_TILE_MAX(); auto pitch = static_cast((pitch_tile_max + 1) * MicroTileWidth); auto height = static_cast( ((slice_tile_max + 1) * (MicroTileWidth * MicroTileHeight)) / pitch); auto addr = (cb_color_base.BASE_256B() << 8); if (!addr || !pitch || !height) { return; } // Disabled for now, because it's a pointless upload // auto format = static_cast(cb_color_info.FORMAT()); // auto tileMode = getArrayModeTileMode(cb_color_info.ARRAY_MODE()); // trackSurface(addr, pitch, height, 1, SQ_TEX_DIM::DIM_2D, format, tileMode); } void trackDepthBuffer(DB_DEPTH_BASE db_depth_base, DB_DEPTH_SIZE db_depth_size, DB_DEPTH_INFO db_depth_info) { auto pitch_tile_max = db_depth_size.PITCH_TILE_MAX(); auto slice_tile_max = db_depth_size.SLICE_TILE_MAX(); auto pitch = static_cast((pitch_tile_max + 1) * MicroTileWidth); auto height = static_cast( ((slice_tile_max + 1) * (MicroTileWidth * MicroTileHeight)) / pitch); auto addr = (db_depth_base.BASE_256B() << 8); if (!addr || !pitch || !height) { return; } // Disabled for now, because it's a pointless upload // auto format = static_cast(db_depth_info.FORMAT()); // auto tileMode = getArrayModeTileMode(db_depth_info.ARRAY_MODE()); //trackSurface(addr, pitch, height, 1, SQ_TEX_DIM::DIM_2D, format, tileMode); } void trackActiveShaders() { auto pgm_start_fs = getRegister(Register::SQ_PGM_START_FS); auto pgm_size_fs = getRegister(Register::SQ_PGM_SIZE_FS); trackMemory(CaptureMemoryLoad::FetchShader, phys_addr { pgm_start_fs.PGM_START() << 8 }, pgm_size_fs.PGM_SIZE() << 3); auto pgm_start_vs = getRegister(Register::SQ_PGM_START_VS); auto pgm_size_vs = getRegister(Register::SQ_PGM_SIZE_VS); trackMemory(CaptureMemoryLoad::VertexShader, phys_addr { pgm_start_vs.PGM_START() << 8 }, pgm_size_vs.PGM_SIZE() << 3); auto pgm_start_ps = getRegister(Register::SQ_PGM_START_PS); auto pgm_size_ps = getRegister(Register::SQ_PGM_SIZE_PS); trackMemory(CaptureMemoryLoad::PixelShader, phys_addr { pgm_start_ps.PGM_START() << 8 }, pgm_size_ps.PGM_SIZE() << 3); auto pgm_start_gs = getRegister(Register::SQ_PGM_START_GS); auto pgm_size_gs = getRegister(Register::SQ_PGM_SIZE_GS); trackMemory(CaptureMemoryLoad::GeometryShader, phys_addr { pgm_start_gs.PGM_START() << 8 }, pgm_size_gs.PGM_SIZE() << 3); } void trackActiveAttribBuffers() { for (auto i = 0u; i < MaxAttribBuffers; ++i) { auto resourceOffset = (SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + i) * 7; auto sq_vtx_constant_word0 = getRegister(Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_vtx_constant_word1 = getRegister(Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset); trackMemory(CaptureMemoryLoad::AttributeBuffer, phys_addr { sq_vtx_constant_word0.BASE_ADDRESS() }, sq_vtx_constant_word1.SIZE() + 1); } } void trackActiveUniforms() { for (auto i = 0u; i < MaxUniformBlocks; ++i) { auto sq_alu_const_cache_vs = getRegister(Register::SQ_ALU_CONST_CACHE_VS_0 + 4 * i); auto sq_alu_const_buffer_size_vs = getRegister(Register::SQ_ALU_CONST_BUFFER_SIZE_VS_0 + 4 * i); trackMemory(CaptureMemoryLoad::UniformBuffer, phys_addr { sq_alu_const_cache_vs << 8 }, sq_alu_const_buffer_size_vs << 8); } } void trackActiveTextures() { for (auto i = 0u; i < MaxTextures; ++i) { auto resourceOffset = (SQ_RES_OFFSET::PS_TEX_RESOURCE_0 + i) * 7; auto sq_tex_resource_word0 = getRegister(Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_tex_resource_word1 = getRegister(Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset); auto sq_tex_resource_word2 = getRegister(Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset); auto sq_tex_resource_word3 = getRegister(Register::SQ_RESOURCE_WORD3_0 + 4 * resourceOffset); auto sq_tex_resource_word4 = getRegister(Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset); auto sq_tex_resource_word5 = getRegister(Register::SQ_RESOURCE_WORD5_0 + 4 * resourceOffset); auto sq_tex_resource_word6 = getRegister(Register::SQ_RESOURCE_WORD6_0 + 4 * resourceOffset); trackSurface(phys_addr { sq_tex_resource_word2.BASE_ADDRESS() << 8 }, (sq_tex_resource_word0.PITCH() + 1) * 8, sq_tex_resource_word1.TEX_HEIGHT() + 1, sq_tex_resource_word1.TEX_DEPTH() + 1, sq_tex_resource_word5.LAST_LEVEL(), 0, sq_tex_resource_word0.TILE_TYPE() == 1, false, sq_tex_resource_word0.DIM(), sq_tex_resource_word1.DATA_FORMAT(), sq_tex_resource_word0.TILE_MODE()); } } void trackActiveColorBuffer() { for (auto i = 0u; i < MaxRenderTargets; ++i) { auto cb_color_base = getRegister(Register::CB_COLOR0_BASE + i * 4); auto cb_color_size = getRegister(Register::CB_COLOR0_SIZE + i * 4); auto cb_color_info = getRegister(Register::CB_COLOR0_INFO + i * 4); trackColorBuffer(cb_color_base, cb_color_size, cb_color_info); } } void trackActiveDepthBuffer() { auto db_depth_base = getRegister(Register::DB_DEPTH_BASE); auto db_depth_size = getRegister(Register::DB_DEPTH_SIZE); auto db_depth_info = getRegister(Register::DB_DEPTH_INFO); trackDepthBuffer(db_depth_base, db_depth_size, db_depth_info); } void trackReadyDraw() { trackActiveShaders(); trackActiveAttribBuffers(); trackActiveUniforms(); trackActiveTextures(); trackActiveColorBuffer(); trackActiveDepthBuffer(); } void scanSetRegister(Register reg, uint32_t value) { mRegisters[reg / 4] = value; } void scanLoadRegisters(Register base, uint32_t *src, const gsl::span> ®isters) { for (auto &range : registers) { auto start = range.first; auto count = range.second; for (auto j = start; j < start + count; ++j) { scanSetRegister(static_cast(base + j * 4), src[j]); } } } template Type getRegister(uint32_t id) { return bit_cast(mRegisters[id / 4]); } void scanType3(HeaderType3 header, const gsl::span &rawData) { PacketReader reader { rawData }; switch (header.opcode()) { case IT_OPCODE::DECAF_COPY_COLOR_TO_SCAN: { auto data = read(reader); trackColorBuffer(data.cb_color_base, data.cb_color_size, data.cb_color_info); break; } case IT_OPCODE::DECAF_SWAP_BUFFERS: // Nothing to track! break; case IT_OPCODE::DECAF_CLEAR_COLOR: { auto data = read(reader); trackColorBuffer(data.cb_color_base, data.cb_color_size, data.cb_color_info); break; } case IT_OPCODE::DECAF_CLEAR_DEPTH_STENCIL: { auto data = read(reader); trackDepthBuffer(data.db_depth_base, data.db_depth_size, data.db_depth_info); break; } case IT_OPCODE::DECAF_SET_BUFFER: // Nothing to track! break; case IT_OPCODE::DECAF_OSSCREEN_FLIP: decaf_abort("pm4 capture not enabled for OSScreen api"); break; case IT_OPCODE::DECAF_COPY_SURFACE: { auto data = read(reader); trackSurface(phys_addr { data.srcImage }, data.srcPitch, data.srcHeight, data.srcDepth, 0, data.srcLevel, false, false, data.srcDim, data.srcFormat, data.srcTileMode); break; } case IT_OPCODE::DRAW_INDEX_AUTO: trackReadyDraw(); break; case IT_OPCODE::DRAW_INDEX_2: { auto data = read(reader); auto vgt_dma_index_type = getRegister( Register::VGT_DMA_INDEX_TYPE); auto indexByteSize = 4u; if (vgt_dma_index_type.INDEX_TYPE() == VGT_INDEX_TYPE::INDEX_16) { indexByteSize = 2u; } trackMemory(CaptureMemoryLoad::IndexBuffer, phys_addr { data.addr }, data.count * indexByteSize); trackReadyDraw(); break; } case IT_OPCODE::DRAW_INDEX_IMMD: trackReadyDraw(); break; case IT_OPCODE::INDEX_TYPE: { auto data = read(reader); mRegisters[Register::VGT_DMA_INDEX_TYPE / 4] = data.type.value; break; } case IT_OPCODE::NUM_INSTANCES: { auto data = read(reader); mRegisters[Register::VGT_DMA_NUM_INSTANCES / 4] = data.count; break; } case IT_OPCODE::SET_ALU_CONST: { auto data = read(reader); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(data.id + i * 4), data.values[i]); } break; } case IT_OPCODE::SET_CONFIG_REG: { auto data = read(reader); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(data.id + i * 4), data.values[i]); } break; } case IT_OPCODE::SET_CONTEXT_REG: { auto data = read(reader); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(data.id + i * 4), data.values[i]); } break; } case IT_OPCODE::SET_CTL_CONST: { auto data = read(reader); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(data.id + i * 4), data.values[i]); } break; } case IT_OPCODE::SET_LOOP_CONST: { auto data = read(reader); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(data.id + i * 4), data.values[i]); } break; } case IT_OPCODE::SET_SAMPLER: { auto data = read(reader); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(data.id + i * 4), data.values[i]); } break; } case IT_OPCODE::SET_RESOURCE: { auto data = read(reader); auto id = Register::ResourceRegisterBase + (4 * data.id); for (auto i = 0u; i < data.values.size(); ++i) { scanSetRegister(static_cast(id + i * 4), data.values[i]); } break; } case IT_OPCODE::LOAD_CONFIG_REG: { auto data = read(reader); scanLoadRegisters(Register::ConfigRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0xB00 * 4); break; } case IT_OPCODE::LOAD_CONTEXT_REG: { auto data = read(reader); scanLoadRegisters(Register::ContextRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0x400 * 4); break; } case IT_OPCODE::LOAD_ALU_CONST: { auto data = read(reader); scanLoadRegisters(Register::AluConstRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0x800 * 4); break; } case IT_OPCODE::LOAD_BOOL_CONST: { decaf_abort("Unsupported LOAD_BOOL_CONST"); auto data = read(reader); scanLoadRegisters(Register::BoolConstRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); break; } case IT_OPCODE::LOAD_LOOP_CONST: { auto data = read(reader); scanLoadRegisters(Register::LoopConstRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0x60 * 4); break; } case IT_OPCODE::LOAD_RESOURCE: { auto data = read(reader); scanLoadRegisters(Register::ResourceRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0xD9E * 4); break; } case IT_OPCODE::LOAD_SAMPLER: { auto data = read(reader); scanLoadRegisters(Register::SamplerRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0xA2 * 4); break; } case IT_OPCODE::LOAD_CTL_CONST: { decaf_abort("Unsupported LOAD_CTL_CONST"); auto data = read(reader); scanLoadRegisters(Register::ControlRegisterBase, phys_cast(data.addr).getRawPointer(), data.values); break; } case IT_OPCODE::INDIRECT_BUFFER: { auto data = read(reader); trackMemory(CaptureMemoryLoad::CommandBuffer, data.addr, data.size * 4u); scanCommandBuffer(phys_cast(data.addr), data.size); break; } case IT_OPCODE::INDIRECT_BUFFER_PRIV: { auto data = read(reader); trackMemory(CaptureMemoryLoad::CommandBuffer, data.addr, data.size * 4u); scanCommandBuffer(phys_cast(data.addr), data.size); break; } case IT_OPCODE::MEM_WRITE: break; case IT_OPCODE::EVENT_WRITE: break; case IT_OPCODE::EVENT_WRITE_EOP: break; case IT_OPCODE::PFP_SYNC_ME: break; case IT_OPCODE::STRMOUT_BASE_UPDATE: break; case IT_OPCODE::STRMOUT_BUFFER_UPDATE: break; case IT_OPCODE::NOP: break; case IT_OPCODE::SURFACE_SYNC: { auto data = read(reader); trackMemory(CaptureMemoryLoad::SurfaceSync, phys_addr { data.addr << 8 }, data.size << 8); break; } case IT_OPCODE::CONTEXT_CTL: break; default: gLog->debug("Unhandled pm4 packet type 3 opcode {}", header.opcode()); } } // Returns true if the memory was written into pm4 stream bool trackMemory(CaptureMemoryLoad::MemoryType type, phys_addr addr, uint32_t size) { auto trackStart = addr; auto trackEnd = trackStart + size; auto addNewEntry = true; uint64_t hash[2] = { 0, 0 }; auto useHash = HashAllMemory || (HashShadowState && type == CaptureMemoryLoad::ShadowState); if (!addr || size == 0) { return false; } if (useHash) { MurmurHash3_x64_128(phys_cast(addr).get(), size, 0, hash); } for (auto &mem : mRecordedMemory) { if (trackStart < mem.start || trackStart > mem.end) { // Not in this block! continue; } if (trackEnd <= mem.end) { // Current memory is completely contained within an already tracked block if (useHash) { // If hash is enabled, then we do not write if hash matches if (hash[0] == mem.hash[0] && hash[1] == mem.hash[1]) { return false; } } else if (type != CaptureMemoryLoad::CpuFlush) { // If hash is disabled, and this is NOT a flush, then do not write memory return false; } } mem.end = trackEnd; mem.hash[0] = hash[0]; mem.hash[1] = hash[1]; addNewEntry = false; break; } if (addNewEntry) { mRecordedMemory.emplace_back(RecordedMemory { trackStart, trackEnd, hash[0], hash[1] }); } writeMemoryLoad(type, addr, size); return true; } private: CaptureState mState = CaptureState::Disabled; std::mutex mMutex; std::ofstream mOut; std::vector mRecordedMemory; std::array mRegisters; size_t mCapturedFrames = 0; size_t mCaptureNumFrames = 0; uint64_t mPacketTimestamp = 0ull; }; static Recorder gRecorder; bool captureStartAtNextSwap() { decaf_check(gRecorder.state() == CaptureState::Disabled); auto filename = std::string {}; // Find an unused filename! for (auto i = 0u; i < 256u; ++i) { filename = fmt::format("trace{}.pm4", i); if (platform::fileExists(filename)) { continue; } break; } if (filename.empty()) { return false; } gLog->info("Starting pm4 trace as {}", filename); return gRecorder.requestStart(filename); } void captureStopAtNextSwap() { gRecorder.requestStop(); } bool captureNextFrame() { gRecorder.setCaptureNumFrames(1); return captureStartAtNextSwap(); } CaptureState captureState() { return gRecorder.state(); } void captureSwap() { if (captureState() != CaptureState::Disabled) { gx2::GX2DrawDone(); gRecorder.swap(); } } void captureCommandBuffer(virt_ptr buffer, uint32_t numWords) { if (captureState() == CaptureState::Enabled || captureState() == CaptureState::WaitEndNextFrame) { gRecorder.commandBuffer(buffer, numWords); } } void captureCpuFlush(phys_addr address, uint32_t size) { if (captureState() == CaptureState::Enabled || captureState() == CaptureState::WaitEndNextFrame) { gRecorder.cpuFlush(address, size); } } void captureGpuFlush(phys_addr address, uint32_t size) { if (captureState() == CaptureState::Enabled || captureState() == CaptureState::WaitEndNextFrame) { gRecorder.gpuFlush(address, size); } } } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_pm4cap.h ================================================ #pragma once #include #include namespace cafe::gx2::internal { enum class CaptureState { Disabled, WaitStartNextFrame, Enabled, WaitEndNextFrame, }; bool captureStartAtNextSwap(); void captureStopAtNextSwap(); bool captureNextFrame(); CaptureState captureState(); void captureSwap(); void captureCommandBuffer(virt_ptr buffer, uint32_t numWords); void captureCpuFlush(phys_addr buffer, uint32_t size); void captureGpuFlush(phys_addr buffer, uint32_t size); } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_internal_writegatherptr.h ================================================ #pragma once #include #include namespace cafe::gx2::internal { /** * A very simple class to emulate a write gather pointer. */ template class be2_write_gather_ptr { public: be2_val & operator *() { return *(mPointer++); } void write(Type value) { *(mPointer++) = value; } void write(virt_ptr buffer, uint32_t count) { std::memcpy(mPointer.get(), buffer.get(), sizeof(Type) * count); mPointer += count; } virt_ptr get() { return mPointer; } be2_write_gather_ptr & operator =(virt_ptr ptr) { mPointer = ptr; return *this; } private: be2_virt_ptr mPointer; }; } // namespace cafe::gx2::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_memory.cpp ================================================ #include "gx2.h" #include "gx2_aperture.h" #include "gx2_cbpool.h" #include "gx2_debugcapture.h" #include "gx2_memory.h" #include "gx2_state.h" #include "cafe/libraries/coreinit/coreinit_cache.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include using namespace cafe::coreinit; namespace cafe::gx2 { void GX2Invalidate(GX2InvalidateMode mode, virt_ptr buffer, uint32_t size) { if (!mode) { return; } auto addr = virt_cast(buffer); if (size != -1) { size = align_up(size, 0x100); } if (addr >= virt_addr { 0xE8000000 } && addr < virt_addr { 0xEA000000 }) { internal::translateAperture(addr, size); } if (mode & GX2InvalidateMode::CPU) { DCFlushRange(addr, size); if (internal::debugCaptureEnabled()) { internal::debugCaptureInvalidate(buffer, size); } } if (mode != GX2InvalidateMode::CPU) { auto cp_coher_cntl = latte::CP_COHER_CNTL::get(0) .ENGINE_ME(true); if (!addr && size == -1 && (mode & 0xF)) { cp_coher_cntl = cp_coher_cntl .FULL_CACHE_ENA(true); } if ((mode & GX2InvalidateMode::Texture) || (mode & GX2InvalidateMode::AttributeBuffer)) { cp_coher_cntl = cp_coher_cntl .TC_ACTION_ENA(true); } if (mode & GX2InvalidateMode::UniformBlock) { cp_coher_cntl = cp_coher_cntl .TC_ACTION_ENA(true) .SH_ACTION_ENA(true); } if (mode & GX2InvalidateMode::Shader) { cp_coher_cntl = cp_coher_cntl .SH_ACTION_ENA(true); } if (mode & GX2InvalidateMode::ColorBuffer) { cp_coher_cntl = cp_coher_cntl .CB0_DEST_BASE_ENA(true) .CB1_DEST_BASE_ENA(true) .CB2_DEST_BASE_ENA(true) .CB3_DEST_BASE_ENA(true) .CB4_DEST_BASE_ENA(true) .CB5_DEST_BASE_ENA(true) .CB6_DEST_BASE_ENA(true) .CB7_DEST_BASE_ENA(true) .CB_ACTION_ENA(true); } if (mode & GX2InvalidateMode::DepthBuffer) { cp_coher_cntl = cp_coher_cntl .DB_DEST_BASE_ENA(true) .DB_ACTION_ENA(true); } if (mode & GX2InvalidateMode::StreamOutBuffer) { cp_coher_cntl = cp_coher_cntl .SO0_DEST_BASE_ENA(true) .SO1_DEST_BASE_ENA(true) .SO2_DEST_BASE_ENA(true) .SO3_DEST_BASE_ENA(true) .SX_ACTION_ENA(true); } if (mode & GX2InvalidateMode::ExportBuffer) { cp_coher_cntl = cp_coher_cntl .DEST_BASE_0_ENA(true) .TC_ACTION_ENA(true) .CB_ACTION_ENA(true) .DB_ACTION_ENA(true) .SX_ACTION_ENA(true); } internal::writePM4(latte::pm4::SurfaceSync { cp_coher_cntl, size >> 8, OSEffectiveToPhysical(addr) >> 8, 4 }); } } void Library::registerMemorySymbols() { RegisterFunctionExport(GX2Invalidate); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_memory.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { void GX2Invalidate(GX2InvalidateMode mode, virt_ptr buffer, uint32_t size); } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_query.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_query.h" #include "gx2_memory.h" #include "cafe/libraries/coreinit/coreinit_cache.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include using namespace cafe::coreinit; using namespace latte::pm4; namespace cafe::gx2 { void GX2SampleTopGPUCycle(virt_ptr writeSamplePtr) { auto addr = OSEffectiveToPhysical(virt_cast(writeSamplePtr)); *writeSamplePtr = -1; auto addrLo = MW_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN64); auto addrHi = MW_ADDR_HI::get(0) .CNTR_SEL(MW_WRITE_CLOCK); internal::writePM4(MemWrite { addrLo, addrHi, 0, 0 }); } void GX2SampleBottomGPUCycle(virt_ptr writeSamplePtr) { auto addr = OSEffectiveToPhysical(virt_cast(writeSamplePtr)); *writeSamplePtr = -1; auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(latte::VGT_EVENT_TYPE::BOTTOM_OF_PIPE_TS) .EVENT_INDEX(latte::VGT_EVENT_INDEX::TS); auto addrLo = EW_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN64); auto addrHi = EWP_ADDR_HI::get(0) .DATA_SEL(EWP_DATA_CLOCK); internal::writePM4(EventWriteEOP { eventInitiator, addrLo, addrHi, 0, 0 }); } uint64_t GX2GPUTimeToCPUTime(uint64_t time) { return time; } static void beginOcclusionQuery(virt_ptr data, bool gpuMemoryWrite) { decaf_check(virt_cast(data) % 4 == 0); // There is some magic number read from their global gx2 state + 0xB0C, no // fucking clue what it is, so let's just set it to 0? auto magicNumber = 0u; auto addr = OSEffectiveToPhysical(virt_cast(data)); if (gpuMemoryWrite) { DCInvalidateRange(virt_cast(data), sizeof(GX2QueryData)); // Zero GX2QueryData from GPU for (auto i = 0u; i < 8; ++i) { auto addrLo = MW_ADDR_LO::get(0) .ADDR_LO((addr + 8 * i) >> 2); auto addrHi = MW_ADDR_HI::get(0) .WR_CONFIRM(true); auto dataHi = 0u; if (i < magicNumber) { dataHi = 0x80000000; } internal::writePM4(MemWrite { addrLo, addrHi, 0, dataHi }); } internal::writePM4(PfpSyncMe {}); } else { std::memset(data.get(), 0, sizeof(GX2QueryData)); auto dataWords = virt_cast(data); dataWords[magicNumber + 0] = 0x4F435055u; // "OCPU" dataWords[magicNumber + 1] = 0u; GX2Invalidate(GX2InvalidateMode::StreamOutBuffer, dataWords, sizeof(GX2QueryData)); } // DB_RENDER_CONTROL auto render_control = latte::DB_RENDER_CONTROL::get(0) .PERFECT_ZPASS_COUNTS(true); internal::writePM4(SetContextReg { latte::Register::DB_RENDER_CONTROL, render_control.value }); // EVENT_WRITE auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(latte::VGT_EVENT_TYPE::ZPASS_DONE) .EVENT_INDEX(latte::VGT_EVENT_INDEX::ZPASS_DONE); auto addrLo = EW_ADDR_LO::get(0) .ADDR_LO(addr >> 2); auto addrHi = EW_ADDR_HI::get(0); internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi }); } static void endOcclusionQuery(virt_ptr data, bool gpuMemoryWrite) { // EVENT_WRITE auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(latte::VGT_EVENT_TYPE::ZPASS_DONE) .EVENT_INDEX(latte::VGT_EVENT_INDEX::ZPASS_DONE); auto addr = OSEffectiveToPhysical(virt_cast(data)); auto addrLo = EW_ADDR_LO::get(0) .ADDR_LO((addr + 8) >> 2); auto addrHi = EW_ADDR_HI::get(0); internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi }); // DB_RENDER_CONTROL auto render_control = latte::DB_RENDER_CONTROL::get(0) .PERFECT_ZPASS_COUNTS(false); internal::writePM4(SetContextReg { latte::Register::DB_RENDER_CONTROL, render_control.value }); } static void beginStreamOutStatsQuery(virt_ptr data, bool gpuMemoryWrite) { auto addr = OSEffectiveToPhysical(virt_cast(data)); decaf_check(addr % 4 == 0); // There is some magic number read from their global gx2 state + 0xB0C, no // fucking clue what it is, so let's just set it to 0? auto magicNumber = 0u; auto endianSwap = latte::CB_ENDIAN::NONE; if (gpuMemoryWrite) { DCInvalidateRange(virt_cast(data), sizeof(GX2QueryData)); // Zero data first for (auto i = 0u; i < 4; ++i) { auto addrLo = MW_ADDR_LO::get(0) .ADDR_LO((addr + 8 * i) >> 2); auto addrHi = MW_ADDR_HI::get(0) .WR_CONFIRM(true); internal::writePM4(MemWrite { addrLo, addrHi, 0, 0 }); } internal::writePM4(PfpSyncMe {}); endianSwap = latte::CB_ENDIAN::SWAP_8IN32; } else { std::memset(data.get(), 0, sizeof(GX2QueryData)); auto dataWords = virt_cast(data); dataWords[magicNumber + 0] = 0x53435055u; // "SCPU" dataWords[magicNumber + 1] = 0u; GX2Invalidate(GX2InvalidateMode::StreamOutBuffer, dataWords, sizeof(GX2QueryData)); endianSwap = latte::CB_ENDIAN::SWAP_8IN64; } // EVENT_WRITE auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(latte::VGT_EVENT_TYPE::SAMPLE_STREAMOUTSTATS) .EVENT_INDEX(latte::VGT_EVENT_INDEX::SAMPLE_STREAMOUTSTAT); auto addrLo = EW_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(endianSwap); auto addrHi = EW_ADDR_HI::get(0); internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi }); } static void endStreamOutStatsQuery(virt_ptr data, bool gpuMemoryWrite) { auto addr = OSEffectiveToPhysical(virt_cast(data)); auto endianSwap = latte::CB_ENDIAN::NONE; if (gpuMemoryWrite) { endianSwap = latte::CB_ENDIAN::SWAP_8IN32; } else { endianSwap = latte::CB_ENDIAN::SWAP_8IN64; } // EVENT_WRITE auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(latte::VGT_EVENT_TYPE::SAMPLE_STREAMOUTSTATS) .EVENT_INDEX(latte::VGT_EVENT_INDEX::SAMPLE_STREAMOUTSTAT); auto addrLo = EW_ADDR_LO::get(0) .ADDR_LO(addr >> 2) .ENDIAN_SWAP(endianSwap); auto addrHi = EW_ADDR_HI::get(0); internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi }); } void GX2QueryBegin(GX2QueryType type, virt_ptr data) { switch (type) { case GX2QueryType::OcclusionQuery: beginOcclusionQuery(data, false); break; case GX2QueryType::OcclusionQueryGpuMem: beginOcclusionQuery(data, true); break; case GX2QueryType::StreamOutStats: beginStreamOutStatsQuery(data, false); break; case GX2QueryType::StreamOutStatsGpuMem: beginStreamOutStatsQuery(data, true); break; } } void GX2QueryEnd(GX2QueryType type, virt_ptr data) { switch (type) { case GX2QueryType::OcclusionQuery: endOcclusionQuery(data, false); break; case GX2QueryType::OcclusionQueryGpuMem: endOcclusionQuery(data, true); break; case GX2QueryType::StreamOutStats: endStreamOutStatsQuery(data, false); break; case GX2QueryType::StreamOutStatsGpuMem: endStreamOutStatsQuery(data, true); break; } } void GX2QueryGetOcclusionResult(virt_ptr data, virt_ptr outResult) { *outResult = data->_gpudata[1] - data->_gpudata[0]; } void GX2QueryBeginConditionalRender(GX2QueryType type, virt_ptr data, BOOL hint, BOOL predicate) { auto addr = OSEffectiveToPhysical(virt_cast(data)); auto op = SP_PRED_OP_PRIMCOUNT; if (type == 2) { op = SP_PRED_OP_ZPASS; } auto set_pred = SET_PRED::get(0) .PRED_OP(op) .HINT(!!hint) .PREDICATE(!!predicate); internal::writePM4(SetPredication { static_cast(addr), set_pred }); } void GX2QueryEndConditionalRender() { internal::writePM4(SetPredication { 0, SET_PRED::get(0) }); } bool GX2PerfMetricEnable(virt_ptr data, GX2PerfType type, uint32_t id) { return true; } void Library::registerQuerySymbols() { RegisterFunctionExport(GX2QueryBegin); RegisterFunctionExport(GX2QueryEnd); RegisterFunctionExport(GX2QueryGetOcclusionResult); RegisterFunctionExport(GX2QueryBeginConditionalRender); RegisterFunctionExport(GX2QueryEndConditionalRender); RegisterFunctionExport(GX2SampleTopGPUCycle); RegisterFunctionExport(GX2SampleBottomGPUCycle); RegisterFunctionExport(GX2GPUTimeToCPUTime); RegisterFunctionExport(GX2PerfMetricEnable); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_query.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { struct GX2PerfData { UNKNOWN(0x8a0); }; CHECK_SIZE(GX2PerfData, 0x8a0); struct GX2QueryData { // Note that these are intentionally host-endian as they // are GPU managed which is litte-endian, not big-endian (PPC). uint64_t _gpudata[8]; }; CHECK_SIZE(GX2QueryData, 0x40); void GX2SampleTopGPUCycle(virt_ptr writeSamplePtr); void GX2SampleBottomGPUCycle(virt_ptr writeSamplePtr); uint64_t GX2GPUTimeToCPUTime(uint64_t time); void GX2QueryBegin(GX2QueryType type, virt_ptr data); void GX2QueryEnd(GX2QueryType type, virt_ptr data); void GX2QueryGetOcclusionResult(virt_ptr data, virt_ptr outResult); void GX2QueryBeginConditionalRender(GX2QueryType type, virt_ptr data, BOOL hint, BOOL predicate); void GX2QueryEndConditionalRender(); bool GX2PerfMetricEnable(virt_ptr data, GX2PerfType type, uint32_t id); } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_registers.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_registers.h" #include "cafe/cafe_stackobject.h" using namespace latte::pm4; namespace cafe::gx2 { void GX2SetAAMask(uint8_t upperLeft, uint8_t upperRight, uint8_t lowerLeft, uint8_t lowerRight) { auto reg = StackObject { }; GX2InitAAMaskReg(reg, upperLeft, upperRight, lowerLeft, lowerRight); GX2SetAAMaskReg(reg); } void GX2InitAAMaskReg(virt_ptr reg, uint8_t upperLeft, uint8_t upperRight, uint8_t lowerLeft, uint8_t lowerRight) { auto pa_sc_aa_mask = latte::PA_SC_AA_MASK::get(0); pa_sc_aa_mask = pa_sc_aa_mask .AA_MASK_ULC(upperLeft) .AA_MASK_URC(upperRight) .AA_MASK_LLC(lowerLeft) .AA_MASK_LRC(lowerRight); reg->pa_sc_aa_mask = pa_sc_aa_mask; } void GX2GetAAMaskReg(virt_ptr reg, virt_ptr upperLeft, virt_ptr upperRight, virt_ptr lowerLeft, virt_ptr lowerRight) { auto pa_sc_aa_mask = reg->pa_sc_aa_mask.value(); *upperLeft = pa_sc_aa_mask.AA_MASK_ULC(); *upperRight = pa_sc_aa_mask.AA_MASK_URC(); *lowerLeft = pa_sc_aa_mask.AA_MASK_LLC(); *lowerRight = pa_sc_aa_mask.AA_MASK_LRC(); } void GX2SetAAMaskReg(virt_ptr reg) { auto pa_sc_aa_mask = reg->pa_sc_aa_mask.value(); internal::writePM4(SetContextReg { latte::Register::PA_SC_AA_MASK, pa_sc_aa_mask.value }); } void GX2SetAlphaTest(BOOL alphaTest, GX2CompareFunction func, float ref) { auto reg = StackObject { }; GX2InitAlphaTestReg(reg, alphaTest, func, ref); GX2SetAlphaTestReg(reg); } void GX2InitAlphaTestReg(virt_ptr reg, BOOL alphaTest, GX2CompareFunction func, float ref) { auto sx_alpha_ref = latte::SX_ALPHA_REF::get(0); auto sx_alpha_test_control = latte::SX_ALPHA_TEST_CONTROL::get(0); sx_alpha_test_control = sx_alpha_test_control .ALPHA_TEST_ENABLE(!!alphaTest) .ALPHA_FUNC(static_cast(func)); sx_alpha_ref = sx_alpha_ref .ALPHA_REF(ref); reg->sx_alpha_ref = sx_alpha_ref; reg->sx_alpha_test_control = sx_alpha_test_control; } void GX2GetAlphaTestReg(virt_ptr reg, virt_ptr alphaTest, virt_ptr func, virt_ptr ref) { auto sx_alpha_ref = reg->sx_alpha_ref.value(); auto sx_alpha_test_control = reg->sx_alpha_test_control.value(); *alphaTest = sx_alpha_test_control.ALPHA_TEST_ENABLE(); *func = static_cast(sx_alpha_test_control.ALPHA_FUNC()); *ref = sx_alpha_ref.ALPHA_REF(); } void GX2SetAlphaTestReg(virt_ptr reg) { auto sx_alpha_test_control = reg->sx_alpha_test_control.value(); internal::writePM4(SetContextReg { latte::Register::SX_ALPHA_TEST_CONTROL, sx_alpha_test_control.value }); auto sx_alpha_ref = reg->sx_alpha_ref.value(); internal::writePM4(SetContextReg { latte::Register::SX_ALPHA_REF, sx_alpha_ref.value }); } void GX2SetAlphaToMask(BOOL alphaToMask, GX2AlphaToMaskMode mode) { auto reg = StackObject { }; GX2InitAlphaToMaskReg(reg, alphaToMask, mode); GX2SetAlphaToMaskReg(reg); } void GX2InitAlphaToMaskReg(virt_ptr reg, BOOL alphaToMask, GX2AlphaToMaskMode mode) { auto db_alpha_to_mask = latte::DB_ALPHA_TO_MASK::get(0); db_alpha_to_mask = db_alpha_to_mask .ALPHA_TO_MASK_ENABLE(!!alphaToMask); switch (mode) { case GX2AlphaToMaskMode::NonDithered: // 0xAA = 10 10 10 10 db_alpha_to_mask = db_alpha_to_mask .ALPHA_TO_MASK_OFFSET0(2) .ALPHA_TO_MASK_OFFSET1(2) .ALPHA_TO_MASK_OFFSET2(2) .ALPHA_TO_MASK_OFFSET3(2); break; case GX2AlphaToMaskMode::Dither0: // 0x78 = 01 11 10 00 db_alpha_to_mask = db_alpha_to_mask .ALPHA_TO_MASK_OFFSET0(0) .ALPHA_TO_MASK_OFFSET1(2) .ALPHA_TO_MASK_OFFSET2(3) .ALPHA_TO_MASK_OFFSET3(1); break; case GX2AlphaToMaskMode::Dither90: // 0xC6 = 11 00 01 10 db_alpha_to_mask = db_alpha_to_mask .ALPHA_TO_MASK_OFFSET0(2) .ALPHA_TO_MASK_OFFSET1(1) .ALPHA_TO_MASK_OFFSET2(0) .ALPHA_TO_MASK_OFFSET3(3); break; case GX2AlphaToMaskMode::Dither180: // 0x2D = 00 10 11 01 db_alpha_to_mask = db_alpha_to_mask .ALPHA_TO_MASK_OFFSET0(1) .ALPHA_TO_MASK_OFFSET1(3) .ALPHA_TO_MASK_OFFSET2(2) .ALPHA_TO_MASK_OFFSET3(0); break; case GX2AlphaToMaskMode::Dither270: // 0x93 = 10 01 00 11 db_alpha_to_mask = db_alpha_to_mask .ALPHA_TO_MASK_OFFSET0(3) .ALPHA_TO_MASK_OFFSET1(0) .ALPHA_TO_MASK_OFFSET2(1) .ALPHA_TO_MASK_OFFSET3(2); break; } reg->db_alpha_to_mask = db_alpha_to_mask; } void GX2GetAlphaToMaskReg(virt_ptr reg, virt_ptr alphaToMask, virt_ptr mode) { auto db_alpha_to_mask = reg->db_alpha_to_mask.value(); auto value = (db_alpha_to_mask.value >> 8) & 0xff; *alphaToMask = db_alpha_to_mask.ALPHA_TO_MASK_ENABLE(); switch (value) { case 0x78: *mode = GX2AlphaToMaskMode::Dither0; break; case 0xC6: *mode = GX2AlphaToMaskMode::Dither90; break; case 0x2D: *mode = GX2AlphaToMaskMode::Dither180; break; case 0x93: *mode = GX2AlphaToMaskMode::Dither270; break; default: *mode = GX2AlphaToMaskMode::NonDithered; break; } } void GX2SetAlphaToMaskReg(virt_ptr reg) { auto db_alpha_to_mask = reg->db_alpha_to_mask.value(); internal::writePM4(SetContextReg { latte::Register::DB_ALPHA_TO_MASK, db_alpha_to_mask.value }); } void GX2SetBlendConstantColor(float red, float green, float blue, float alpha) { auto reg = StackObject { }; GX2InitBlendConstantColorReg(reg, red, green, blue, alpha); GX2SetBlendConstantColorReg(reg); } void GX2InitBlendConstantColorReg(virt_ptr reg, float red, float green, float blue, float alpha) { reg->red = red; reg->green = green; reg->blue = blue; reg->alpha = alpha; } void GX2GetBlendConstantColorReg(virt_ptr reg, virt_ptr red, virt_ptr green, virt_ptr blue, virt_ptr alpha) { *red = reg->red; *green = reg->green; *blue = reg->blue; *alpha = reg->alpha; } void GX2SetBlendConstantColorReg(virt_ptr reg) { float colors[] = { reg->red, reg->green, reg->blue, reg->alpha }; auto values = reinterpret_cast(colors); internal::writePM4(SetContextRegs { latte::Register::CB_BLEND_RED, gsl::make_span(values, 4) }); } void GX2SetBlendControl(GX2RenderTarget target, GX2BlendMode colorSrcBlend, GX2BlendMode colorDstBlend, GX2BlendCombineMode colorCombine, BOOL useAlphaBlend, GX2BlendMode alphaSrcBlend, GX2BlendMode alphaDstBlend, GX2BlendCombineMode alphaCombine) { auto reg = StackObject { }; GX2InitBlendControlReg(reg, target, colorSrcBlend, colorDstBlend, colorCombine, useAlphaBlend, alphaSrcBlend, alphaDstBlend, alphaCombine); GX2SetBlendControlReg(reg); } void GX2InitBlendControlReg(virt_ptr reg, GX2RenderTarget target, GX2BlendMode colorSrcBlend, GX2BlendMode colorDstBlend, GX2BlendCombineMode colorCombine, BOOL useAlphaBlend, GX2BlendMode alphaSrcBlend, GX2BlendMode alphaDstBlend, GX2BlendCombineMode alphaCombine) { auto cb_blend_control = latte::CB_BLENDN_CONTROL::get(0); reg->target = target; cb_blend_control = cb_blend_control .COLOR_SRCBLEND(static_cast(colorSrcBlend)) .COLOR_DESTBLEND(static_cast(colorDstBlend)) .COLOR_COMB_FCN(static_cast(colorCombine)) .SEPARATE_ALPHA_BLEND(useAlphaBlend) .ALPHA_SRCBLEND(static_cast(alphaSrcBlend)) .ALPHA_DESTBLEND(static_cast(alphaDstBlend)) .ALPHA_COMB_FCN(static_cast(alphaCombine)); reg->cb_blend_control = cb_blend_control; } void GX2GetBlendControlReg(virt_ptr reg, virt_ptr target, virt_ptr colorSrcBlend, virt_ptr colorDstBlend, virt_ptr colorCombine, virt_ptr useAlphaBlend, virt_ptr alphaSrcBlend, virt_ptr alphaDstBlend, virt_ptr alphaCombine) { auto cb_blend_control = reg->cb_blend_control.value(); *target = reg->target; *colorSrcBlend = static_cast(cb_blend_control.COLOR_SRCBLEND()); *colorDstBlend = static_cast(cb_blend_control.COLOR_DESTBLEND()); *colorCombine = static_cast(cb_blend_control.COLOR_COMB_FCN()); *useAlphaBlend = cb_blend_control.SEPARATE_ALPHA_BLEND() ? TRUE : FALSE; *alphaSrcBlend = static_cast(cb_blend_control.ALPHA_SRCBLEND()); *alphaDstBlend = static_cast(cb_blend_control.ALPHA_DESTBLEND()); *alphaCombine = static_cast(cb_blend_control.ALPHA_COMB_FCN()); } void GX2SetBlendControlReg(virt_ptr reg) { auto cb_blend_control = reg->cb_blend_control.value(); auto id = static_cast(latte::Register::CB_BLEND0_CONTROL + reg->target * 4); internal::writePM4(SetContextReg { id, cb_blend_control.value }); } void GX2SetColorControl(GX2LogicOp rop3, uint8_t targetBlendEnable, BOOL multiWriteEnable, BOOL colorWriteEnable) { auto reg = StackObject { }; GX2InitColorControlReg(reg, rop3, targetBlendEnable, multiWriteEnable, colorWriteEnable); GX2SetColorControlReg(reg); } void GX2InitColorControlReg(virt_ptr reg, GX2LogicOp rop3, uint8_t targetBlendEnable, BOOL multiWriteEnable, BOOL colorWriteEnable) { auto cb_color_control = latte::CB_COLOR_CONTROL::get(0); auto specialOp = latte::CB_SPECIAL_OP::DISABLE; if (colorWriteEnable) { specialOp = latte::CB_SPECIAL_OP::NORMAL; } cb_color_control = cb_color_control .ROP3(rop3) .TARGET_BLEND_ENABLE(targetBlendEnable) .MULTIWRITE_ENABLE(multiWriteEnable) .SPECIAL_OP(specialOp); reg->cb_color_control = cb_color_control; } void GX2GetColorControlReg(virt_ptr reg, virt_ptr rop3, virt_ptr targetBlendEnable, virt_ptr multiWriteEnable, virt_ptr colorWriteEnable) { auto cb_color_control = reg->cb_color_control.value(); *rop3 = static_cast(cb_color_control.ROP3()); *targetBlendEnable = cb_color_control.TARGET_BLEND_ENABLE(); *multiWriteEnable = cb_color_control.MULTIWRITE_ENABLE() ? TRUE : FALSE; if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::DISABLE) { *colorWriteEnable = FALSE; } else { *colorWriteEnable = TRUE; } } void GX2SetColorControlReg(virt_ptr reg) { auto cb_color_control = reg->cb_color_control.value(); internal::writePM4(SetContextReg { latte::Register::CB_COLOR_CONTROL, cb_color_control.value }); } void GX2SetDepthOnlyControl(BOOL depthTest, BOOL depthWrite, GX2CompareFunction depthCompare) { GX2SetDepthStencilControl(depthTest, depthWrite, depthCompare, FALSE, FALSE, GX2CompareFunction::Never, GX2StencilFunction::Keep, GX2StencilFunction::Keep, GX2StencilFunction::Keep, GX2CompareFunction::Never, GX2StencilFunction::Keep, GX2StencilFunction::Keep, GX2StencilFunction::Keep); } void GX2SetDepthStencilControl(BOOL depthTest, BOOL depthWrite, GX2CompareFunction depthCompare, BOOL stencilTest, BOOL backfaceStencil, GX2CompareFunction frontStencilFunc, GX2StencilFunction frontStencilZPass, GX2StencilFunction frontStencilZFail, GX2StencilFunction frontStencilFail, GX2CompareFunction backStencilFunc, GX2StencilFunction backStencilZPass, GX2StencilFunction backStencilZFail, GX2StencilFunction backStencilFail) { auto reg = StackObject { }; GX2InitDepthStencilControlReg(reg, depthTest, depthWrite, depthCompare, stencilTest, backfaceStencil, frontStencilFunc, frontStencilZPass, frontStencilZFail, frontStencilFail, backStencilFunc, backStencilZPass, backStencilZFail, backStencilFail); GX2SetDepthStencilControlReg(reg); } void GX2InitDepthStencilControlReg(virt_ptr reg, BOOL depthTest, BOOL depthWrite, GX2CompareFunction depthCompare, BOOL stencilTest, BOOL backfaceStencil, GX2CompareFunction frontStencilFunc, GX2StencilFunction frontStencilZPass, GX2StencilFunction frontStencilZFail, GX2StencilFunction frontStencilFail, GX2CompareFunction backStencilFunc, GX2StencilFunction backStencilZPass, GX2StencilFunction backStencilZFail, GX2StencilFunction backStencilFail) { auto db_depth_control = latte::DB_DEPTH_CONTROL::get(0); db_depth_control = db_depth_control .Z_ENABLE(!!depthTest) .Z_WRITE_ENABLE(!!depthWrite) .ZFUNC(static_cast(depthCompare)) .STENCIL_ENABLE(!!stencilTest) .BACKFACE_ENABLE(!!backfaceStencil) .STENCILFUNC(static_cast(frontStencilFunc)) .STENCILZPASS(static_cast(frontStencilZPass)) .STENCILZFAIL(static_cast(frontStencilZFail)) .STENCILFAIL(static_cast(frontStencilFail)) .STENCILFUNC_BF(static_cast(backStencilFunc)) .STENCILZPASS_BF(static_cast(backStencilZPass)) .STENCILZFAIL_BF(static_cast(backStencilZFail)) .STENCILFAIL_BF(static_cast(backStencilFail)); reg->db_depth_control = db_depth_control; } void GX2GetDepthStencilControlReg(virt_ptr reg, virt_ptr depthTest, virt_ptr depthWrite, virt_ptr depthCompare, virt_ptr stencilTest, virt_ptr backfaceStencil, virt_ptr frontStencilFunc, virt_ptr frontStencilZPass, virt_ptr frontStencilZFail, virt_ptr frontStencilFail, virt_ptr backStencilFunc, virt_ptr backStencilZPass, virt_ptr backStencilZFail, virt_ptr backStencilFail) { auto db_depth_control = reg->db_depth_control.value(); *depthTest = db_depth_control.Z_ENABLE(); *depthWrite = db_depth_control.Z_WRITE_ENABLE(); *depthCompare = static_cast(db_depth_control.ZFUNC()); *stencilTest = db_depth_control.STENCIL_ENABLE(); *backfaceStencil = db_depth_control.BACKFACE_ENABLE(); *frontStencilFunc = static_cast(db_depth_control.STENCILFUNC()); *frontStencilZPass = static_cast(db_depth_control.STENCILZPASS()); *frontStencilZFail = static_cast(db_depth_control.STENCILZFAIL()); *frontStencilFail = static_cast(db_depth_control.STENCILFAIL()); *backStencilFunc = static_cast(db_depth_control.STENCILFUNC_BF()); *backStencilZPass = static_cast(db_depth_control.STENCILZPASS_BF()); *backStencilZFail = static_cast(db_depth_control.STENCILZFAIL_BF()); *backStencilFail = static_cast(db_depth_control.STENCILFAIL_BF()); } void GX2SetDepthStencilControlReg(virt_ptr reg) { auto db_depth_control = reg->db_depth_control.value(); internal::writePM4(SetContextReg { latte::Register::DB_DEPTH_CONTROL, db_depth_control.value }); } void GX2SetStencilMask(uint8_t frontMask, uint8_t frontWriteMask, uint8_t frontRef, uint8_t backMask, uint8_t backWriteMask, uint8_t backRef) { auto reg = StackObject { }; GX2InitStencilMaskReg(reg, frontMask, frontWriteMask, frontRef, backMask, backWriteMask, backRef); GX2SetStencilMaskReg(reg); } void GX2InitStencilMaskReg(virt_ptr reg, uint8_t frontMask, uint8_t frontWriteMask, uint8_t frontRef, uint8_t backMask, uint8_t backWriteMask, uint8_t backRef) { auto db_stencilrefmask = latte::DB_STENCILREFMASK::get(0); auto db_stencilrefmask_bf = latte::DB_STENCILREFMASK_BF::get(0); db_stencilrefmask = db_stencilrefmask .STENCILREF(frontRef) .STENCILMASK(frontMask) .STENCILWRITEMASK(frontWriteMask); db_stencilrefmask_bf = db_stencilrefmask_bf .STENCILREF_BF(backRef) .STENCILMASK_BF(backMask) .STENCILWRITEMASK_BF(backWriteMask); reg->db_stencilrefmask = db_stencilrefmask; reg->db_stencilrefmask_bf = db_stencilrefmask_bf; } void GX2GetStencilMaskReg(virt_ptr reg, virt_ptr frontMask, virt_ptr frontWriteMask, virt_ptr frontRef, virt_ptr backMask, virt_ptr backWriteMask, virt_ptr backRef) { auto db_stencilrefmask = reg->db_stencilrefmask.value(); auto db_stencilrefmask_bf = reg->db_stencilrefmask_bf.value(); *frontRef = db_stencilrefmask.STENCILREF(); *frontMask = db_stencilrefmask.STENCILMASK(); *frontWriteMask = db_stencilrefmask.STENCILWRITEMASK(); *backRef = db_stencilrefmask_bf.STENCILREF_BF(); *backMask = db_stencilrefmask_bf.STENCILMASK_BF(); *backWriteMask = db_stencilrefmask_bf.STENCILWRITEMASK_BF(); } void GX2SetStencilMaskReg(virt_ptr reg) { auto db_stencilrefmask = reg->db_stencilrefmask.value(); auto db_stencilrefmask_bf = reg->db_stencilrefmask_bf.value(); internal::writePM4(SetContextReg { latte::Register::DB_STENCILREFMASK, db_stencilrefmask.value }); internal::writePM4(SetContextReg { latte::Register::DB_STENCILREFMASK_BF, db_stencilrefmask_bf.value }); } void GX2SetLineWidth(float width) { auto reg = StackObject { }; GX2InitLineWidthReg(reg, width); GX2SetLineWidthReg(reg); } void GX2InitLineWidthReg(virt_ptr reg, float width) { auto pa_su_line_cntl = latte::PA_SU_LINE_CNTL::get(0); pa_su_line_cntl = pa_su_line_cntl .WIDTH(gsl::narrow_cast(width * 8.0f)); reg->pa_su_line_cntl = pa_su_line_cntl; } void GX2GetLineWidthReg(virt_ptr reg, virt_ptr width) { auto pa_su_line_cntl = reg->pa_su_line_cntl.value(); *width = static_cast(pa_su_line_cntl.WIDTH()) / 8.0f; } void GX2SetLineWidthReg(virt_ptr reg) { auto pa_su_line_cntl = reg->pa_su_line_cntl.value(); internal::writePM4(SetContextReg { latte::Register::PA_SU_LINE_CNTL, pa_su_line_cntl.value }); } void GX2SetPointSize(float width, float height) { auto reg = StackObject { }; GX2InitPointSizeReg(reg, width, height); GX2SetPointSizeReg(reg); } void GX2InitPointSizeReg(virt_ptr reg, float width, float height) { auto pa_su_point_size = latte::PA_SU_POINT_SIZE::get(0); pa_su_point_size = pa_su_point_size .WIDTH(gsl::narrow_cast(width * 8.0f)) .HEIGHT(gsl::narrow_cast(height * 8.0f)); reg->pa_su_point_size = pa_su_point_size; } void GX2GetPointSizeReg(virt_ptr reg, virt_ptr width, virt_ptr height) { auto pa_su_point_size = reg->pa_su_point_size.value(); *width = static_cast(pa_su_point_size.WIDTH()) / 8.0f; *height = static_cast(pa_su_point_size.HEIGHT()) / 8.0f; } void GX2SetPointSizeReg(virt_ptr reg) { auto pa_su_point_size = reg->pa_su_point_size.value(); internal::writePM4(SetContextReg { latte::Register::PA_SU_POINT_SIZE, pa_su_point_size.value }); } void GX2SetPointLimits(float min, float max) { auto reg = StackObject { }; GX2InitPointLimitsReg(reg, min, max); GX2SetPointLimitsReg(reg); } void GX2InitPointLimitsReg(virt_ptr reg, float min, float max) { auto pa_su_point_minmax = latte::PA_SU_POINT_MINMAX::get(0); pa_su_point_minmax = pa_su_point_minmax .MIN_SIZE(gsl::narrow_cast(min * 8.0f)) .MAX_SIZE(gsl::narrow_cast(max * 8.0f)); reg->pa_su_point_minmax = pa_su_point_minmax; } void GX2GetPointLimitsReg(virt_ptr reg, virt_ptr min, virt_ptr max) { auto pa_su_point_minmax = reg->pa_su_point_minmax.value(); *min = static_cast(pa_su_point_minmax.MIN_SIZE()) / 8.0f; *max = static_cast(pa_su_point_minmax.MAX_SIZE()) / 8.0f; } void GX2SetPointLimitsReg(virt_ptr reg) { auto pa_su_point_minmax = reg->pa_su_point_minmax.value(); internal::writePM4(SetContextReg { latte::Register::PA_SU_POINT_MINMAX, pa_su_point_minmax.value }); } void GX2SetCullOnlyControl(GX2FrontFace frontFace, BOOL cullFront, BOOL cullBack) { GX2SetPolygonControl(frontFace, cullFront, cullBack, FALSE, GX2PolygonMode::Point, GX2PolygonMode::Point, FALSE, FALSE, FALSE); } void GX2SetPolygonControl(GX2FrontFace frontFace, BOOL cullFront, BOOL cullBack, BOOL polyMode, GX2PolygonMode polyModeFront, GX2PolygonMode polyModeBack, BOOL polyOffsetFrontEnable, BOOL polyOffsetBackEnable, BOOL polyOffsetParaEnable) { auto reg = StackObject { }; GX2InitPolygonControlReg(reg, frontFace, cullFront, cullBack, polyMode, polyModeFront, polyModeBack, polyOffsetFrontEnable, polyOffsetBackEnable, polyOffsetParaEnable); GX2SetPolygonControlReg(reg); } void GX2InitPolygonControlReg(virt_ptr reg, GX2FrontFace frontFace, BOOL cullFront, BOOL cullBack, uint32_t polyMode, GX2PolygonMode polyModeFront, GX2PolygonMode polyModeBack, BOOL polyOffsetFrontEnable, BOOL polyOffsetBackEnable, BOOL polyOffsetParaEnable) { auto pa_su_sc_mode_cntl = latte::PA_SU_SC_MODE_CNTL::get(0); pa_su_sc_mode_cntl = pa_su_sc_mode_cntl .FACE(static_cast(!!frontFace)) .CULL_FRONT(!!cullFront) .CULL_BACK(!!cullBack) .POLY_MODE(polyMode) .POLYMODE_FRONT_PTYPE(static_cast(polyModeFront)) .POLYMODE_BACK_PTYPE(static_cast(polyModeBack)) .POLY_OFFSET_FRONT_ENABLE(!!polyOffsetFrontEnable) .POLY_OFFSET_BACK_ENABLE(!!polyOffsetBackEnable) .POLY_OFFSET_PARA_ENABLE(!!polyOffsetParaEnable); reg->pa_su_sc_mode_cntl = pa_su_sc_mode_cntl; } void GX2GetPolygonControlReg(virt_ptr reg, virt_ptr frontFace, virt_ptr cullFront, virt_ptr cullBack, virt_ptr polyMode, virt_ptr polyModeFront, virt_ptr polyModeBack, virt_ptr polyOffsetFrontEnable, virt_ptr polyOffsetBackEnable, virt_ptr polyOffsetParaEnable) { auto pa_su_sc_mode_cntl = reg->pa_su_sc_mode_cntl.value(); *frontFace = static_cast(pa_su_sc_mode_cntl.FACE()); *cullFront = pa_su_sc_mode_cntl.CULL_FRONT(); *cullBack = pa_su_sc_mode_cntl.CULL_BACK(); *polyMode = pa_su_sc_mode_cntl.POLY_MODE(); *polyModeFront = static_cast(pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE()); *polyModeBack = static_cast(pa_su_sc_mode_cntl.POLYMODE_BACK_PTYPE()); *polyOffsetFrontEnable = pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE(); *polyOffsetBackEnable = pa_su_sc_mode_cntl.POLY_OFFSET_BACK_ENABLE(); *polyOffsetParaEnable = pa_su_sc_mode_cntl.POLY_OFFSET_PARA_ENABLE(); } void GX2SetPolygonControlReg(virt_ptr reg) { auto pa_su_sc_mode_cntl = reg->pa_su_sc_mode_cntl.value(); internal::writePM4(SetContextReg { latte::Register::PA_SU_SC_MODE_CNTL, pa_su_sc_mode_cntl.value }); } void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clamp) { auto reg = StackObject { }; GX2InitPolygonOffsetReg(reg, frontOffset, frontScale, backOffset, backScale, clamp); GX2SetPolygonOffsetReg(reg); } void GX2InitPolygonOffsetReg(virt_ptr reg, float frontOffset, float frontScale, float backOffset, float backScale, float clamp) { auto pa_su_poly_offset_front_offset = latte::PA_SU_POLY_OFFSET_FRONT_OFFSET::get(0); auto pa_su_poly_offset_front_scale = latte::PA_SU_POLY_OFFSET_FRONT_SCALE::get(0); auto pa_su_poly_offset_back_offset = latte::PA_SU_POLY_OFFSET_BACK_OFFSET::get(0); auto pa_su_poly_offset_back_scale = latte::PA_SU_POLY_OFFSET_BACK_SCALE::get(0); auto pa_su_poly_offset_clamp = latte::PA_SU_POLY_OFFSET_CLAMP::get(0); pa_su_poly_offset_front_offset = pa_su_poly_offset_front_offset .OFFSET(frontOffset); pa_su_poly_offset_front_scale = pa_su_poly_offset_front_scale .SCALE(frontScale * 16.0f); pa_su_poly_offset_back_offset = pa_su_poly_offset_back_offset .OFFSET(backOffset); pa_su_poly_offset_back_scale = pa_su_poly_offset_back_scale .SCALE(backScale * 16.0f); pa_su_poly_offset_clamp = pa_su_poly_offset_clamp .CLAMP(clamp); reg->pa_su_poly_offset_front_offset = pa_su_poly_offset_front_offset; reg->pa_su_poly_offset_front_scale = pa_su_poly_offset_front_scale; reg->pa_su_poly_offset_back_offset = pa_su_poly_offset_back_offset; reg->pa_su_poly_offset_back_scale = pa_su_poly_offset_back_scale; reg->pa_su_poly_offset_clamp = pa_su_poly_offset_clamp; } void GX2GetPolygonOffsetReg(virt_ptr reg, virt_ptr frontOffset, virt_ptr frontScale, virt_ptr backOffset, virt_ptr backScale, virt_ptr clamp) { auto pa_su_poly_offset_front_offset = reg->pa_su_poly_offset_front_offset.value(); auto pa_su_poly_offset_front_scale = reg->pa_su_poly_offset_front_scale.value(); auto pa_su_poly_offset_back_offset = reg->pa_su_poly_offset_back_offset.value(); auto pa_su_poly_offset_back_scale = reg->pa_su_poly_offset_back_scale.value(); auto pa_su_poly_offset_clamp = reg->pa_su_poly_offset_clamp.value(); *frontOffset = pa_su_poly_offset_front_offset.OFFSET(); *frontScale = pa_su_poly_offset_front_scale.SCALE() / 16.0f; *backOffset = pa_su_poly_offset_back_offset.OFFSET(); *backScale = pa_su_poly_offset_back_scale.SCALE() / 16.0f; *clamp = pa_su_poly_offset_clamp.CLAMP(); } void GX2SetPolygonOffsetReg(virt_ptr reg) { auto pa_su_poly_offset_front_offset = reg->pa_su_poly_offset_front_offset.value(); auto pa_su_poly_offset_front_scale = reg->pa_su_poly_offset_front_scale.value(); auto pa_su_poly_offset_back_offset = reg->pa_su_poly_offset_back_offset.value(); auto pa_su_poly_offset_back_scale = reg->pa_su_poly_offset_back_scale.value(); uint32_t values[] = { pa_su_poly_offset_front_scale.value, pa_su_poly_offset_front_offset.value, pa_su_poly_offset_back_scale.value, pa_su_poly_offset_back_offset.value, }; internal::writePM4(SetContextRegs { latte::Register::PA_SU_POLY_OFFSET_FRONT_SCALE, gsl::make_span(values) }); auto pa_su_poly_offset_clamp = reg->pa_su_poly_offset_clamp.value(); internal::writePM4(SetContextReg { latte::Register::PA_SU_POLY_OFFSET_CLAMP, pa_su_poly_offset_clamp.value }); } void GX2SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { auto reg = StackObject { }; GX2InitScissorReg(reg, x, y, width, height); GX2SetScissorReg(reg); } void GX2InitScissorReg(virt_ptr reg, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { auto pa_sc_generic_scissor_tl = latte::PA_SC_GENERIC_SCISSOR_TL::get(0); auto pa_sc_generic_scissor_br = latte::PA_SC_GENERIC_SCISSOR_BR::get(0); pa_sc_generic_scissor_tl = pa_sc_generic_scissor_tl .TL_X(x) .TL_Y(y); pa_sc_generic_scissor_br = pa_sc_generic_scissor_br .BR_X(x + width) .BR_Y(y + height); reg->pa_sc_generic_scissor_tl = pa_sc_generic_scissor_tl; reg->pa_sc_generic_scissor_br = pa_sc_generic_scissor_br; } void GX2GetScissorReg(virt_ptr reg, virt_ptr x, virt_ptr y, virt_ptr width, virt_ptr height) { auto pa_sc_generic_scissor_tl = reg->pa_sc_generic_scissor_tl.value(); auto pa_sc_generic_scissor_br = reg->pa_sc_generic_scissor_br.value(); *x = pa_sc_generic_scissor_tl.TL_X(); *y = pa_sc_generic_scissor_tl.TL_Y(); *width = pa_sc_generic_scissor_br.BR_X() - pa_sc_generic_scissor_tl.TL_X(); *height = pa_sc_generic_scissor_br.BR_Y() - pa_sc_generic_scissor_tl.TL_Y(); } void GX2SetScissorReg(virt_ptr reg) { auto pa_sc_generic_scissor_tl = reg->pa_sc_generic_scissor_tl.value(); auto pa_sc_generic_scissor_br = reg->pa_sc_generic_scissor_br.value(); uint32_t values[] = { pa_sc_generic_scissor_tl.value, pa_sc_generic_scissor_br.value, }; internal::writePM4(SetContextRegs { latte::Register::PA_SC_GENERIC_SCISSOR_TL, gsl::make_span(values) }); } void GX2SetTargetChannelMasks(GX2ChannelMask mask0, GX2ChannelMask mask1, GX2ChannelMask mask2, GX2ChannelMask mask3, GX2ChannelMask mask4, GX2ChannelMask mask5, GX2ChannelMask mask6, GX2ChannelMask mask7) { auto reg = StackObject { }; GX2InitTargetChannelMasksReg(reg, mask0, mask1, mask2, mask3, mask4, mask5, mask6, mask7); GX2SetTargetChannelMasksReg(reg); } void GX2InitTargetChannelMasksReg(virt_ptr reg, GX2ChannelMask mask0, GX2ChannelMask mask1, GX2ChannelMask mask2, GX2ChannelMask mask3, GX2ChannelMask mask4, GX2ChannelMask mask5, GX2ChannelMask mask6, GX2ChannelMask mask7) { auto cb_target_mask = latte::CB_TARGET_MASK::get(0); cb_target_mask = cb_target_mask .TARGET0_ENABLE(mask0) .TARGET1_ENABLE(mask1) .TARGET2_ENABLE(mask2) .TARGET3_ENABLE(mask3) .TARGET4_ENABLE(mask4) .TARGET5_ENABLE(mask5) .TARGET6_ENABLE(mask6) .TARGET7_ENABLE(mask7); reg->cb_target_mask = cb_target_mask; } void GX2GetTargetChannelMasksReg(virt_ptr reg, virt_ptr mask0, virt_ptr mask1, virt_ptr mask2, virt_ptr mask3, virt_ptr mask4, virt_ptr mask5, virt_ptr mask6, virt_ptr mask7) { auto cb_target_mask = reg->cb_target_mask.value(); *mask0 = static_cast(cb_target_mask.TARGET0_ENABLE()); *mask1 = static_cast(cb_target_mask.TARGET1_ENABLE()); *mask2 = static_cast(cb_target_mask.TARGET2_ENABLE()); *mask3 = static_cast(cb_target_mask.TARGET3_ENABLE()); *mask4 = static_cast(cb_target_mask.TARGET4_ENABLE()); *mask5 = static_cast(cb_target_mask.TARGET5_ENABLE()); *mask6 = static_cast(cb_target_mask.TARGET6_ENABLE()); *mask7 = static_cast(cb_target_mask.TARGET7_ENABLE()); } void GX2SetTargetChannelMasksReg(virt_ptr reg) { auto cb_target_mask = reg->cb_target_mask.value(); internal::writePM4(SetContextReg { latte::Register::CB_TARGET_MASK, cb_target_mask.value }); } void GX2SetViewport(float x, float y, float width, float height, float nearZ, float farZ) { auto reg = StackObject { }; GX2InitViewportReg(reg, x, y, width, height, nearZ, farZ); GX2SetViewportReg(reg); } void GX2InitViewportReg(virt_ptr reg, float x, float y, float width, float height, float nearZ, float farZ) { auto pa_cl_vport_xscale = latte::PA_CL_VPORT_XSCALE_N::get(0); auto pa_cl_vport_xoffset = latte::PA_CL_VPORT_XOFFSET_N::get(0); auto pa_cl_vport_yscale = latte::PA_CL_VPORT_YSCALE_N::get(0); auto pa_cl_vport_yoffset = latte::PA_CL_VPORT_YOFFSET_N::get(0); auto pa_cl_vport_zscale = latte::PA_CL_VPORT_ZSCALE_N::get(0); auto pa_cl_vport_zoffset = latte::PA_CL_VPORT_ZOFFSET_N::get(0); auto pa_cl_gb_vert_clip_adj = latte::PA_CL_GB_VERT_CLIP_ADJ::get(0); auto pa_cl_gb_vert_disc_adj = latte::PA_CL_GB_VERT_DISC_ADJ::get(0); auto pa_cl_gb_horz_clip_adj = latte::PA_CL_GB_HORZ_CLIP_ADJ::get(0); auto pa_cl_gb_horz_disc_adj = latte::PA_CL_GB_HORZ_DISC_ADJ::get(0); auto pa_sc_vport_zmin = reg->pa_sc_vport_zmin.value(); auto pa_sc_vport_zmax = reg->pa_sc_vport_zmax.value(); pa_cl_vport_xscale = pa_cl_vport_xscale .VPORT_XSCALE(width * 0.5f); pa_cl_vport_xoffset = pa_cl_vport_xoffset .VPORT_XOFFSET(x + (width * 0.5f)); pa_cl_vport_yscale = pa_cl_vport_yscale .VPORT_YSCALE(height * -0.5f); pa_cl_vport_yoffset = pa_cl_vport_yoffset .VPORT_YOFFSET(y + (height * 0.5f)); pa_cl_vport_zscale = pa_cl_vport_zscale .VPORT_ZSCALE((farZ - nearZ) * 0.5f); pa_cl_vport_zoffset = pa_cl_vport_zoffset .VPORT_ZOFFSET((farZ + nearZ) * 0.5f); if (width == 0.0f || height == 0.0f) { pa_cl_gb_vert_clip_adj = pa_cl_gb_vert_clip_adj .DATA_REGISTER(1.0f); pa_cl_gb_horz_clip_adj = pa_cl_gb_horz_clip_adj .DATA_REGISTER(1.0f); } else { if (height < 0.0f) { y += height; height = -height; } auto horz_clip_adj = x + 8192.0f; if (horz_clip_adj <= 0.0f) { horz_clip_adj = 8192.0f - (width + x); } pa_cl_gb_horz_clip_adj = pa_cl_gb_horz_clip_adj .DATA_REGISTER((horz_clip_adj + horz_clip_adj + width) / width); auto vert_clip_adj = y + 8192.0f; if (vert_clip_adj <= 0.0f) { vert_clip_adj = 8192.0f - (height + y); } pa_cl_gb_vert_clip_adj = pa_cl_gb_vert_clip_adj .DATA_REGISTER((vert_clip_adj + vert_clip_adj + height) / height); } pa_cl_gb_horz_disc_adj = pa_cl_gb_horz_disc_adj .DATA_REGISTER(1.0f); pa_cl_gb_vert_disc_adj = pa_cl_gb_vert_disc_adj .DATA_REGISTER(1.0f); pa_sc_vport_zmin = pa_sc_vport_zmin .VPORT_ZMIN(std::min(nearZ, farZ)); pa_sc_vport_zmax = pa_sc_vport_zmax .VPORT_ZMAX(std::max(nearZ, farZ)); reg->pa_cl_vport_xscale = pa_cl_vport_xscale; reg->pa_cl_vport_xoffset = pa_cl_vport_xoffset; reg->pa_cl_vport_yscale = pa_cl_vport_yscale; reg->pa_cl_vport_yoffset = pa_cl_vport_yoffset; reg->pa_cl_vport_zscale = pa_cl_vport_zscale; reg->pa_cl_vport_zoffset = pa_cl_vport_zoffset; reg->pa_cl_gb_vert_clip_adj = pa_cl_gb_vert_clip_adj; reg->pa_cl_gb_vert_disc_adj = pa_cl_gb_vert_disc_adj; reg->pa_cl_gb_horz_clip_adj = pa_cl_gb_horz_clip_adj; reg->pa_cl_gb_horz_disc_adj = pa_cl_gb_horz_disc_adj; reg->pa_sc_vport_zmin = pa_sc_vport_zmin; reg->pa_sc_vport_zmax = pa_sc_vport_zmax; } void GX2GetViewportReg(virt_ptr reg, virt_ptr x, virt_ptr y, virt_ptr width, virt_ptr height, virt_ptr nearZ, virt_ptr farZ) { auto pa_cl_vport_xscale = reg->pa_cl_vport_xscale.value(); auto pa_cl_vport_xoffset = reg->pa_cl_vport_xoffset.value(); auto pa_cl_vport_yscale = reg->pa_cl_vport_yscale.value(); auto pa_cl_vport_yoffset = reg->pa_cl_vport_yoffset.value(); auto pa_cl_vport_zscale = reg->pa_cl_vport_zscale.value(); auto pa_cl_vport_zoffset = reg->pa_cl_vport_zoffset.value(); *x = pa_cl_vport_xoffset.VPORT_XOFFSET() - pa_cl_vport_xscale.VPORT_XSCALE(); *y = pa_cl_vport_yoffset.VPORT_YOFFSET() + pa_cl_vport_yscale.VPORT_YSCALE(); *width = 2.0f * pa_cl_vport_xscale.VPORT_XSCALE(); *height = -2.0f * pa_cl_vport_yscale.VPORT_YSCALE(); *farZ = pa_cl_vport_zoffset.VPORT_ZOFFSET() + pa_cl_vport_zscale.VPORT_ZSCALE(); *nearZ = pa_cl_vport_zoffset.VPORT_ZOFFSET() - pa_cl_vport_zscale.VPORT_ZSCALE(); } void GX2SetViewportReg(virt_ptr reg) { auto pa_cl_vport_xscale = reg->pa_cl_vport_xscale.value(); auto pa_cl_vport_xoffset = reg->pa_cl_vport_xoffset.value(); auto pa_cl_vport_yscale = reg->pa_cl_vport_yscale.value(); auto pa_cl_vport_yoffset = reg->pa_cl_vport_yoffset.value(); auto pa_cl_vport_zscale = reg->pa_cl_vport_zscale.value(); auto pa_cl_vport_zoffset = reg->pa_cl_vport_zoffset.value(); uint32_t values1[] = { pa_cl_vport_xscale.value, pa_cl_vport_xoffset.value, pa_cl_vport_yscale.value, pa_cl_vport_yoffset.value, pa_cl_vport_zscale.value, pa_cl_vport_zoffset.value, }; internal::writePM4(SetContextRegs { latte::Register::PA_CL_VPORT_XSCALE_0, gsl::make_span(values1) }); auto pa_cl_gb_vert_clip_adj = reg->pa_cl_gb_vert_clip_adj.value(); auto pa_cl_gb_vert_disc_adj = reg->pa_cl_gb_vert_disc_adj.value(); auto pa_cl_gb_horz_clip_adj = reg->pa_cl_gb_horz_clip_adj.value(); auto pa_cl_gb_horz_disc_adj = reg->pa_cl_gb_horz_disc_adj.value(); uint32_t values2[] = { pa_cl_gb_vert_clip_adj.value, pa_cl_gb_vert_disc_adj.value, pa_cl_gb_horz_clip_adj.value,pa_cl_gb_horz_disc_adj.value, }; internal::writePM4(SetContextRegs { latte::Register::PA_CL_GB_VERT_CLIP_ADJ, gsl::make_span(values2) }); auto pa_sc_vport_zmin = reg->pa_sc_vport_zmin.value(); auto pa_sc_vport_zmax = reg->pa_sc_vport_zmax.value(); uint32_t values3[] = { pa_sc_vport_zmin.value, pa_sc_vport_zmax.value, }; internal::writePM4(SetContextRegs { latte::Register::PA_SC_VPORT_ZMIN_0, gsl::make_span(values3) }); } void GX2SetRasterizerClipControl(BOOL rasteriser, BOOL zclipEnable) { GX2SetRasterizerClipControlEx(rasteriser, zclipEnable, FALSE); } void GX2SetRasterizerClipControlEx(BOOL rasteriser, BOOL zclipEnable, BOOL halfZ) { auto pa_cl_clip_cntl = latte::PA_CL_CLIP_CNTL::get(0); pa_cl_clip_cntl = pa_cl_clip_cntl .RASTERISER_DISABLE(!rasteriser) .ZCLIP_NEAR_DISABLE(!zclipEnable) .ZCLIP_FAR_DISABLE(!zclipEnable) .DX_CLIP_SPACE_DEF(!!halfZ); internal::writePM4(SetContextReg { latte::Register::PA_CL_CLIP_CNTL, pa_cl_clip_cntl.value }); } void GX2SetRasterizerClipControlHalfZ(BOOL rasteriser, BOOL zclipEnable, BOOL halfZ) { GX2SetRasterizerClipControlEx(rasteriser, zclipEnable, halfZ); } void Library::registerRegistersSymbols() { RegisterFunctionExport(GX2SetAAMask); RegisterFunctionExport(GX2InitAAMaskReg); RegisterFunctionExport(GX2GetAAMaskReg); RegisterFunctionExport(GX2SetAAMaskReg); RegisterFunctionExport(GX2SetAlphaTest); RegisterFunctionExport(GX2InitAlphaTestReg); RegisterFunctionExport(GX2GetAlphaTestReg); RegisterFunctionExport(GX2SetAlphaTestReg); RegisterFunctionExport(GX2SetAlphaToMask); RegisterFunctionExport(GX2InitAlphaToMaskReg); RegisterFunctionExport(GX2GetAlphaToMaskReg); RegisterFunctionExport(GX2SetAlphaToMaskReg); RegisterFunctionExport(GX2SetBlendConstantColor); RegisterFunctionExport(GX2InitBlendConstantColorReg); RegisterFunctionExport(GX2GetBlendConstantColorReg); RegisterFunctionExport(GX2SetBlendConstantColorReg); RegisterFunctionExport(GX2SetBlendControl); RegisterFunctionExport(GX2InitBlendControlReg); RegisterFunctionExport(GX2GetBlendControlReg); RegisterFunctionExport(GX2SetBlendControlReg); RegisterFunctionExport(GX2SetColorControl); RegisterFunctionExport(GX2InitColorControlReg); RegisterFunctionExport(GX2GetColorControlReg); RegisterFunctionExport(GX2SetColorControlReg); RegisterFunctionExport(GX2SetDepthOnlyControl); RegisterFunctionExport(GX2SetDepthStencilControl); RegisterFunctionExport(GX2InitDepthStencilControlReg); RegisterFunctionExport(GX2GetDepthStencilControlReg); RegisterFunctionExport(GX2SetDepthStencilControlReg); RegisterFunctionExport(GX2SetStencilMask); RegisterFunctionExport(GX2InitStencilMaskReg); RegisterFunctionExport(GX2GetStencilMaskReg); RegisterFunctionExport(GX2SetStencilMaskReg); RegisterFunctionExport(GX2SetLineWidth); RegisterFunctionExport(GX2InitLineWidthReg); RegisterFunctionExport(GX2GetLineWidthReg); RegisterFunctionExport(GX2SetLineWidthReg); RegisterFunctionExport(GX2SetPointSize); RegisterFunctionExport(GX2InitPointSizeReg); RegisterFunctionExport(GX2GetPointSizeReg); RegisterFunctionExport(GX2SetPointSizeReg); RegisterFunctionExport(GX2SetPointLimits); RegisterFunctionExport(GX2InitPointLimitsReg); RegisterFunctionExport(GX2GetPointLimitsReg); RegisterFunctionExport(GX2SetPointLimitsReg); RegisterFunctionExport(GX2SetCullOnlyControl); RegisterFunctionExport(GX2SetPolygonControl); RegisterFunctionExport(GX2InitPolygonControlReg); RegisterFunctionExport(GX2GetPolygonControlReg); RegisterFunctionExport(GX2SetPolygonControlReg); RegisterFunctionExport(GX2SetPolygonOffset); RegisterFunctionExport(GX2InitPolygonOffsetReg); RegisterFunctionExport(GX2GetPolygonOffsetReg); RegisterFunctionExport(GX2SetPolygonOffsetReg); RegisterFunctionExport(GX2SetScissor); RegisterFunctionExport(GX2InitScissorReg); RegisterFunctionExport(GX2GetScissorReg); RegisterFunctionExport(GX2SetScissorReg); RegisterFunctionExport(GX2SetTargetChannelMasks); RegisterFunctionExport(GX2InitTargetChannelMasksReg); RegisterFunctionExport(GX2GetTargetChannelMasksReg); RegisterFunctionExport(GX2SetTargetChannelMasksReg); RegisterFunctionExport(GX2SetViewport); RegisterFunctionExport(GX2InitViewportReg); RegisterFunctionExport(GX2GetViewportReg); RegisterFunctionExport(GX2SetViewportReg); RegisterFunctionExport(GX2SetRasterizerClipControl); RegisterFunctionExport(GX2SetRasterizerClipControlEx); RegisterFunctionExport(GX2SetRasterizerClipControlHalfZ); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_registers.h ================================================ #pragma once #include "gx2_enum.h" #include #include namespace cafe::gx2 { #pragma pack(push, 1) struct GX2AAMaskReg { be2_val pa_sc_aa_mask; }; CHECK_SIZE(GX2AAMaskReg, 4); CHECK_OFFSET(GX2AAMaskReg, 0, pa_sc_aa_mask); struct GX2AlphaTestReg { be2_val sx_alpha_test_control; be2_val sx_alpha_ref; }; CHECK_SIZE(GX2AlphaTestReg, 8); CHECK_OFFSET(GX2AlphaTestReg, 0, sx_alpha_test_control); CHECK_OFFSET(GX2AlphaTestReg, 4, sx_alpha_ref); struct GX2AlphaToMaskReg { be2_val db_alpha_to_mask; }; CHECK_SIZE(GX2AlphaToMaskReg, 4); CHECK_OFFSET(GX2AlphaToMaskReg, 0, db_alpha_to_mask); struct GX2BlendControlReg { be2_val target; be2_val cb_blend_control; }; CHECK_SIZE(GX2BlendControlReg, 8); CHECK_OFFSET(GX2BlendControlReg, 0, target); CHECK_OFFSET(GX2BlendControlReg, 4, cb_blend_control); struct GX2BlendConstantColorReg { be2_val red; be2_val green; be2_val blue; be2_val alpha; }; CHECK_SIZE(GX2BlendConstantColorReg, 0x10); CHECK_OFFSET(GX2BlendConstantColorReg, 0x00, red); CHECK_OFFSET(GX2BlendConstantColorReg, 0x04, green); CHECK_OFFSET(GX2BlendConstantColorReg, 0x08, blue); CHECK_OFFSET(GX2BlendConstantColorReg, 0x0c, alpha); struct GX2ColorControlReg { be2_val cb_color_control; }; CHECK_SIZE(GX2ColorControlReg, 0x04); CHECK_OFFSET(GX2ColorControlReg, 0x00, cb_color_control); struct GX2DepthStencilControlReg { be2_val db_depth_control; }; CHECK_SIZE(GX2DepthStencilControlReg, 4); CHECK_OFFSET(GX2DepthStencilControlReg, 0, db_depth_control); struct GX2StencilMaskReg { be2_val db_stencilrefmask; be2_val db_stencilrefmask_bf; }; CHECK_SIZE(GX2StencilMaskReg, 8); CHECK_OFFSET(GX2StencilMaskReg, 0, db_stencilrefmask); CHECK_OFFSET(GX2StencilMaskReg, 4, db_stencilrefmask_bf); struct GX2LineWidthReg { be2_val pa_su_line_cntl; }; CHECK_SIZE(GX2LineWidthReg, 4); CHECK_OFFSET(GX2LineWidthReg, 0, pa_su_line_cntl); struct GX2PointSizeReg { be2_val pa_su_point_size; }; CHECK_SIZE(GX2PointSizeReg, 4); CHECK_OFFSET(GX2PointSizeReg, 0, pa_su_point_size); struct GX2PointLimitsReg { be2_val pa_su_point_minmax; }; CHECK_SIZE(GX2PointLimitsReg, 4); CHECK_OFFSET(GX2PointLimitsReg, 0, pa_su_point_minmax); struct GX2PolygonControlReg { be2_val pa_su_sc_mode_cntl; }; CHECK_SIZE(GX2PolygonControlReg, 4); CHECK_OFFSET(GX2PolygonControlReg, 0, pa_su_sc_mode_cntl); struct GX2PolygonOffsetReg { be2_val pa_su_poly_offset_front_scale; be2_val pa_su_poly_offset_front_offset; be2_val pa_su_poly_offset_back_scale; be2_val pa_su_poly_offset_back_offset; be2_val pa_su_poly_offset_clamp; }; CHECK_SIZE(GX2PolygonOffsetReg, 0x14); CHECK_OFFSET(GX2PolygonOffsetReg, 0x00, pa_su_poly_offset_front_scale); CHECK_OFFSET(GX2PolygonOffsetReg, 0x04, pa_su_poly_offset_front_offset); CHECK_OFFSET(GX2PolygonOffsetReg, 0x08, pa_su_poly_offset_back_scale); CHECK_OFFSET(GX2PolygonOffsetReg, 0x0C, pa_su_poly_offset_back_offset); CHECK_OFFSET(GX2PolygonOffsetReg, 0x10, pa_su_poly_offset_clamp); struct GX2ScissorReg { be2_val pa_sc_generic_scissor_tl; be2_val pa_sc_generic_scissor_br; }; CHECK_SIZE(GX2ScissorReg, 0x08); CHECK_OFFSET(GX2ScissorReg, 0x00, pa_sc_generic_scissor_tl); CHECK_OFFSET(GX2ScissorReg, 0x04, pa_sc_generic_scissor_br); struct GX2TargetChannelMaskReg { be2_val cb_target_mask; }; CHECK_SIZE(GX2TargetChannelMaskReg, 0x04); CHECK_OFFSET(GX2TargetChannelMaskReg, 0x00, cb_target_mask); struct GX2ViewportReg { be2_val pa_cl_vport_xscale; be2_val pa_cl_vport_xoffset; be2_val pa_cl_vport_yscale; be2_val pa_cl_vport_yoffset; be2_val pa_cl_vport_zscale; be2_val pa_cl_vport_zoffset; be2_val pa_cl_gb_vert_clip_adj; be2_val pa_cl_gb_vert_disc_adj; be2_val pa_cl_gb_horz_clip_adj; be2_val pa_cl_gb_horz_disc_adj; be2_val pa_sc_vport_zmin; be2_val pa_sc_vport_zmax; }; CHECK_SIZE(GX2ViewportReg, 0x30); CHECK_OFFSET(GX2ViewportReg, 0x00, pa_cl_vport_xscale); CHECK_OFFSET(GX2ViewportReg, 0x04, pa_cl_vport_xoffset); CHECK_OFFSET(GX2ViewportReg, 0x08, pa_cl_vport_yscale); CHECK_OFFSET(GX2ViewportReg, 0x0C, pa_cl_vport_yoffset); CHECK_OFFSET(GX2ViewportReg, 0x10, pa_cl_vport_zscale); CHECK_OFFSET(GX2ViewportReg, 0x14, pa_cl_vport_zoffset); CHECK_OFFSET(GX2ViewportReg, 0x18, pa_cl_gb_vert_clip_adj); CHECK_OFFSET(GX2ViewportReg, 0x1C, pa_cl_gb_vert_disc_adj); CHECK_OFFSET(GX2ViewportReg, 0x20, pa_cl_gb_horz_clip_adj); CHECK_OFFSET(GX2ViewportReg, 0x24, pa_cl_gb_horz_disc_adj); CHECK_OFFSET(GX2ViewportReg, 0x28, pa_sc_vport_zmin); CHECK_OFFSET(GX2ViewportReg, 0x2C, pa_sc_vport_zmax); #pragma pack(pop) void GX2SetAAMask(uint8_t upperLeft, uint8_t upperRight, uint8_t lowerLeft, uint8_t lowerRight); void GX2InitAAMaskReg(virt_ptr reg, uint8_t upperLeft, uint8_t upperRight, uint8_t lowerLeft, uint8_t lowerRight); void GX2GetAAMaskReg(virt_ptr reg, virt_ptr upperLeft, virt_ptr upperRight, virt_ptr lowerLeft, virt_ptr lowerRight); void GX2SetAAMaskReg(virt_ptr reg); void GX2SetAlphaTest(BOOL alphaTest, GX2CompareFunction func, float ref); void GX2InitAlphaTestReg(virt_ptr reg, BOOL alphaTest, GX2CompareFunction func, float ref); void GX2GetAlphaTestReg(virt_ptr reg, virt_ptr alphaTest, virt_ptr func, virt_ptr ref); void GX2SetAlphaTestReg(virt_ptr reg); void GX2SetAlphaToMask(BOOL alphaToMask, GX2AlphaToMaskMode mode); void GX2InitAlphaToMaskReg(virt_ptr reg, BOOL alphaToMask, GX2AlphaToMaskMode mode); void GX2GetAlphaToMaskReg(virt_ptr reg, virt_ptr alphaToMask, virt_ptr mode); void GX2SetAlphaToMaskReg(virt_ptr reg); void GX2SetBlendConstantColor(float red, float green, float blue, float alpha); void GX2InitBlendConstantColorReg(virt_ptr reg, float red, float green, float blue, float alpha); void GX2GetBlendConstantColorReg(virt_ptr reg, virt_ptr red, virt_ptr green, virt_ptr blue, virt_ptr alpha); void GX2SetBlendConstantColorReg(virt_ptr reg); void GX2SetBlendControl(GX2RenderTarget target, GX2BlendMode colorSrcBlend, GX2BlendMode colorDstBlend, GX2BlendCombineMode colorCombine, BOOL useAlphaBlend, GX2BlendMode alphaSrcBlend, GX2BlendMode alphaDstBlend, GX2BlendCombineMode alphaCombine); void GX2InitBlendControlReg(virt_ptr reg, GX2RenderTarget target, GX2BlendMode colorSrcBlend, GX2BlendMode colorDstBlend, GX2BlendCombineMode colorCombine, BOOL useAlphaBlend, GX2BlendMode alphaSrcBlend, GX2BlendMode alphaDstBlend, GX2BlendCombineMode alphaCombine); void GX2GetBlendControlReg(virt_ptr reg, virt_ptr target, virt_ptr colorSrcBlend, virt_ptr colorDstBlend, virt_ptr colorCombine, virt_ptr useAlphaBlend, virt_ptr alphaSrcBlend, virt_ptr alphaDstBlend, virt_ptr alphaCombine); void GX2SetBlendControlReg(virt_ptr reg); void GX2SetColorControl(GX2LogicOp rop3, uint8_t targetBlendEnable, BOOL multiWriteEnable, BOOL colorWriteEnable); void GX2InitColorControlReg(virt_ptr reg, GX2LogicOp rop3, uint8_t targetBlendEnable, BOOL multiWriteEnable, BOOL colorWriteEnable); void GX2GetColorControlReg(virt_ptr reg, virt_ptr rop3, virt_ptr targetBlendEnable, virt_ptr multiWriteEnable, virt_ptr colorWriteEnable); void GX2SetColorControlReg(virt_ptr reg); void GX2SetDepthOnlyControl(BOOL depthTest, BOOL depthWrite, GX2CompareFunction depthCompare); void GX2SetDepthStencilControl(BOOL depthTest, BOOL depthWrite, GX2CompareFunction depthCompare, BOOL stencilTest, BOOL backfaceStencil, GX2CompareFunction frontStencilFunc, GX2StencilFunction frontStencilZPass, GX2StencilFunction frontStencilZFail, GX2StencilFunction frontStencilFail, GX2CompareFunction backStencilFunc, GX2StencilFunction backStencilZPass, GX2StencilFunction backStencilZFail, GX2StencilFunction backStencilFail); void GX2InitDepthStencilControlReg(virt_ptr reg, BOOL depthTest, BOOL depthWrite, GX2CompareFunction depthCompare, BOOL stencilTest, BOOL backfaceStencil, GX2CompareFunction frontStencilFunc, GX2StencilFunction frontStencilZPass, GX2StencilFunction frontStencilZFail, GX2StencilFunction frontStencilFail, GX2CompareFunction backStencilFunc, GX2StencilFunction backStencilZPass, GX2StencilFunction backStencilZFail, GX2StencilFunction backStencilFail); void GX2GetDepthStencilControlReg(virt_ptr reg, virt_ptr depthTest, virt_ptr depthWrite, virt_ptr depthCompare, virt_ptr stencilTest, virt_ptr backfaceStencil, virt_ptr frontStencilFunc, virt_ptr frontStencilZPass, virt_ptr frontStencilZFail, virt_ptr frontStencilFail, virt_ptr backStencilFunc, virt_ptr backStencilZPass, virt_ptr backStencilZFail, virt_ptr backStencilFail); void GX2SetDepthStencilControlReg(virt_ptr reg); void GX2SetStencilMask(uint8_t frontMask, uint8_t frontWriteMask, uint8_t frontRef, uint8_t backMask, uint8_t backWriteMask, uint8_t backRef); void GX2InitStencilMaskReg(virt_ptr reg, uint8_t frontMask, uint8_t frontWriteMask, uint8_t frontRef, uint8_t backMask, uint8_t backWriteMask, uint8_t backRef); void GX2GetStencilMaskReg(virt_ptr reg, virt_ptr frontMask, virt_ptr frontWriteMask, virt_ptr frontRef, virt_ptr backMask, virt_ptr backWriteMask, virt_ptr backRef); void GX2SetStencilMaskReg(virt_ptr reg); void GX2SetLineWidth(float width); void GX2InitLineWidthReg(virt_ptr reg, float width); void GX2GetLineWidthReg(virt_ptr reg, virt_ptr width); void GX2SetLineWidthReg(virt_ptr reg); void GX2SetPointSize(float width, float height); void GX2InitPointSizeReg(virt_ptr reg, float width, float height); void GX2GetPointSizeReg(virt_ptr reg, virt_ptr width, virt_ptr height); void GX2SetPointSizeReg(virt_ptr reg); void GX2SetPointLimits(float min, float max); void GX2InitPointLimitsReg(virt_ptr reg, float min, float max); void GX2GetPointLimitsReg(virt_ptr reg, virt_ptr min, virt_ptr max); void GX2SetPointLimitsReg(virt_ptr reg); void GX2SetCullOnlyControl(GX2FrontFace frontFace, BOOL cullFront, BOOL cullBack); void GX2SetPolygonControl(GX2FrontFace frontFace, BOOL cullFront, BOOL cullBack, BOOL polyMode, GX2PolygonMode polyModeFront, GX2PolygonMode polyModeBack, BOOL polyOffsetFrontEnable, BOOL polyOffsetBackEnable, BOOL polyOffsetParaEnable); void GX2InitPolygonControlReg(virt_ptr reg, GX2FrontFace frontFace, BOOL cullFront, BOOL cullBack, uint32_t polyMode, GX2PolygonMode polyModeFront, GX2PolygonMode polyModeBack, BOOL polyOffsetFrontEnable, BOOL polyOffsetBackEnable, BOOL polyOffsetParaEnable); void GX2GetPolygonControlReg(virt_ptr reg, virt_ptr frontFace, virt_ptr cullFront, virt_ptr cullBack, virt_ptr polyMode, virt_ptr polyModeFront, virt_ptr polyModeBack, virt_ptr polyOffsetFrontEnable, virt_ptr polyOffsetBackEnable, virt_ptr polyOffsetParaEnable); void GX2SetPolygonControlReg(virt_ptr reg); void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clamp); void GX2InitPolygonOffsetReg(virt_ptr reg, float frontOffset, float frontScale, float backOffset, float backScale, float clamp); void GX2GetPolygonOffsetReg(virt_ptr reg, virt_ptr frontOffset, virt_ptr frontScale, virt_ptr backOffset, virt_ptr backScale, virt_ptr clamp); void GX2SetPolygonOffsetReg(virt_ptr reg); void GX2SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height); void GX2InitScissorReg(virt_ptr reg, uint32_t x, uint32_t y, uint32_t width, uint32_t height); void GX2GetScissorReg(virt_ptr reg, virt_ptr x, virt_ptr y, virt_ptr width, virt_ptr height); void GX2SetScissorReg(virt_ptr reg); void GX2SetTargetChannelMasks(GX2ChannelMask mask0, GX2ChannelMask mask1, GX2ChannelMask mask2, GX2ChannelMask mask3, GX2ChannelMask mask4, GX2ChannelMask mask5, GX2ChannelMask mask6, GX2ChannelMask mask7); void GX2InitTargetChannelMasksReg(virt_ptr reg, GX2ChannelMask mask0, GX2ChannelMask mask1, GX2ChannelMask mask2, GX2ChannelMask mask3, GX2ChannelMask mask4, GX2ChannelMask mask5, GX2ChannelMask mask6, GX2ChannelMask mask7); void GX2GetTargetChannelMasksReg(virt_ptr reg, virt_ptr mask0, virt_ptr mask1, virt_ptr mask2, virt_ptr mask3, virt_ptr mask4, virt_ptr mask5, virt_ptr mask6, virt_ptr mask7); void GX2SetTargetChannelMasksReg(virt_ptr reg); void GX2SetViewport(float x, float y, float width, float height, float nearZ, float farZ); void GX2InitViewportReg(virt_ptr reg, float x, float y, float width, float height, float nearZ, float farZ); void GX2GetViewportReg(virt_ptr reg, virt_ptr x, virt_ptr y, virt_ptr width, virt_ptr height, virt_ptr nearZ, virt_ptr farZ); void GX2SetViewportReg(virt_ptr reg); void GX2SetRasterizerClipControl(BOOL rasteriser, BOOL zclipEnable); void GX2SetRasterizerClipControlEx(BOOL rasteriser, BOOL zclipEnable, BOOL halfZ); void GX2SetRasterizerClipControlHalfZ(BOOL rasteriser, BOOL zclipEnable, BOOL halfZ); } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_sampler.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_sampler.h" #include namespace cafe::gx2 { void GX2InitSampler(virt_ptr sampler, GX2TexClampMode clampMode, GX2TexXYFilterMode minMagFilterMode) { sampler->regs.word0 = latte::SQ_TEX_SAMPLER_WORD0_N::get(0) .CLAMP_X(static_cast(clampMode)) .CLAMP_Y(static_cast(clampMode)) .CLAMP_Z(static_cast(clampMode)) .XY_MAG_FILTER(static_cast(minMagFilterMode)) .XY_MIN_FILTER(static_cast(minMagFilterMode)); sampler->regs.word1 = latte::SQ_TEX_SAMPLER_WORD1_N::get(0) .MAX_LOD(fixed_from_data(1023)); sampler->regs.word2 = latte::SQ_TEX_SAMPLER_WORD2_N::get(0) .TYPE(true); } void GX2InitSamplerBorderType(virt_ptr sampler, GX2TexBorderType borderType) { auto word0 = sampler->regs.word0.value(); word0 = word0 .BORDER_COLOR_TYPE(static_cast(borderType)); sampler->regs.word0 = word0; } void GX2InitSamplerClamping(virt_ptr sampler, GX2TexClampMode clampX, GX2TexClampMode clampY, GX2TexClampMode clampZ) { auto word0 = sampler->regs.word0.value(); word0 = word0 .CLAMP_X(static_cast(clampX)) .CLAMP_Y(static_cast(clampY)) .CLAMP_Z(static_cast(clampZ)); sampler->regs.word0 = word0; } void GX2InitSamplerDepthCompare(virt_ptr sampler, GX2CompareFunction depthCompare) { auto word0 = sampler->regs.word0.value(); word0 = word0 .DEPTH_COMPARE_FUNCTION(static_cast(depthCompare)); sampler->regs.word0 = word0; } void GX2InitSamplerFilterAdjust(virt_ptr sampler, BOOL highPrecision, GX2TexMipPerfMode perfMip, GX2TexZPerfMode perfZ) { auto word2 = sampler->regs.word2.value(); word2 = word2 .HIGH_PRECISION_FILTER(!!highPrecision) .PERF_MIP(perfMip) .PERF_Z(perfZ); sampler->regs.word2 = word2; } void GX2InitSamplerLOD(virt_ptr sampler, float lodMin, float lodMax, float lodBias) { auto word1 = sampler->regs.word1.value(); lodMin = std::min(std::max(lodMin, 0.0f), 16.0f); lodMax = std::min(std::max(lodMax, 0.0f), 16.0f); lodBias = std::min(std::max(lodBias, -32.0f), 32.0f); word1 = word1 .MIN_LOD(ufixed_4_6_t { lodMin }) .MAX_LOD(ufixed_4_6_t { lodMax }) .LOD_BIAS(sfixed_1_5_6_t { lodBias }); sampler->regs.word1 = word1; } void GX2InitSamplerLODAdjust(virt_ptr sampler, float anisoBias, BOOL lodUsesMinorAxis) { auto word0 = sampler->regs.word0.value(); auto word2 = sampler->regs.word2.value(); anisoBias = std::min(std::max(anisoBias, 0.0f), 2.0f); word2 = word2 .ANISO_BIAS(ufixed_1_5_t { anisoBias }); word0 = word0 .LOD_USES_MINOR_AXIS(!!lodUsesMinorAxis); sampler->regs.word0 = word0; sampler->regs.word2 = word2; } void GX2InitSamplerRoundingMode(virt_ptr sampler, GX2RoundingMode roundingMode) { auto word2 = sampler->regs.word2.value(); word2 = word2 .TRUNCATE_COORD(static_cast(roundingMode)); sampler->regs.word2 = word2; } void GX2InitSamplerXYFilter(virt_ptr sampler, GX2TexXYFilterMode filterMag, GX2TexXYFilterMode filterMin, GX2TexAnisoRatio maxAniso) { auto word0 = sampler->regs.word0.value(); word0 = word0 .XY_MAG_FILTER(static_cast(filterMag)) .XY_MIN_FILTER(static_cast(filterMin)) .MAX_ANISO_RATIO(static_cast(maxAniso)); sampler->regs.word0 = word0; } void GX2InitSamplerZMFilter(virt_ptr sampler, GX2TexZFilterMode filterZ, GX2TexMipFilterMode filterMip) { auto word0 = sampler->regs.word0.value(); word0 = word0 .Z_FILTER(static_cast(filterZ)) .MIP_FILTER(static_cast(filterMip)); sampler->regs.word0 = word0; } void GX2SetPixelSamplerBorderColor(uint32_t unit, float red, float green, float blue, float alpha) { uint32_t values[] = { bit_cast(red), bit_cast(green), bit_cast(blue), bit_cast(alpha), }; auto id = latte::Register::TD_PS_SAMPLER_BORDER0_RED + 4 * (unit * 4); internal::writePM4(latte::pm4::SetConfigRegs { static_cast(id), gsl::make_span(values) }); } void GX2SetVertexSamplerBorderColor(uint32_t unit, float red, float green, float blue, float alpha) { uint32_t values[] = { bit_cast(red), bit_cast(green), bit_cast(blue), bit_cast(alpha), }; auto id = latte::Register::TD_VS_SAMPLER_BORDER0_RED + 4 * (unit * 4); internal::writePM4(latte::pm4::SetConfigRegs { static_cast(id), gsl::make_span(values) }); } void GX2SetGeometrySamplerBorderColor(uint32_t unit, float red, float green, float blue, float alpha) { uint32_t values[] = { bit_cast(red), bit_cast(green), bit_cast(blue), bit_cast(alpha), }; auto id = latte::Register::TD_GS_SAMPLER_BORDER0_RED + 4 * (unit * 4); internal::writePM4(latte::pm4::SetConfigRegs { static_cast(id), gsl::make_span(values) }); } void Library::registerSamplerSymbols() { RegisterFunctionExport(GX2InitSampler); RegisterFunctionExport(GX2InitSamplerBorderType); RegisterFunctionExport(GX2InitSamplerClamping); RegisterFunctionExport(GX2InitSamplerDepthCompare); RegisterFunctionExport(GX2InitSamplerFilterAdjust); RegisterFunctionExport(GX2InitSamplerLOD); RegisterFunctionExport(GX2InitSamplerLODAdjust); RegisterFunctionExport(GX2InitSamplerRoundingMode); RegisterFunctionExport(GX2InitSamplerXYFilter); RegisterFunctionExport(GX2InitSamplerZMFilter); RegisterFunctionExport(GX2SetPixelSamplerBorderColor); RegisterFunctionExport(GX2SetVertexSamplerBorderColor); RegisterFunctionExport(GX2SetGeometrySamplerBorderColor); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_sampler.h ================================================ #pragma once #include "gx2_enum.h" #include #include namespace cafe::gx2 { /** * \defgroup gx2_sampler Sampler * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2Sampler { struct { be2_val word0; be2_val word1; be2_val word2; } regs; }; CHECK_OFFSET(GX2Sampler, 0x00, regs.word0); CHECK_OFFSET(GX2Sampler, 0x04, regs.word1); CHECK_OFFSET(GX2Sampler, 0x08, regs.word2); CHECK_SIZE(GX2Sampler, 0x0C); #pragma pack(pop) void GX2InitSampler(virt_ptr sampler, GX2TexClampMode clampMode, GX2TexXYFilterMode minMagFilterMode); void GX2InitSamplerBorderType(virt_ptr sampler, GX2TexBorderType borderType); void GX2InitSamplerClamping(virt_ptr sampler, GX2TexClampMode clampX, GX2TexClampMode clampY, GX2TexClampMode clampZ); void GX2InitSamplerDepthCompare(virt_ptr sampler, GX2CompareFunction depthCompare); void GX2InitSamplerFilterAdjust(virt_ptr sampler, BOOL highPrecision, GX2TexMipPerfMode perfMip, GX2TexZPerfMode perfZ); void GX2InitSamplerLOD(virt_ptr sampler, float lodMin, float lodMax, float lodBias); void GX2InitSamplerLODAdjust(virt_ptr sampler, float unk1, BOOL unk2); void GX2InitSamplerRoundingMode(virt_ptr sampler, GX2RoundingMode roundingMode); void GX2InitSamplerXYFilter(virt_ptr sampler, GX2TexXYFilterMode filterMag, GX2TexXYFilterMode filterMin, GX2TexAnisoRatio maxAniso); void GX2InitSamplerZMFilter(virt_ptr sampler, GX2TexZFilterMode filterZ, GX2TexMipFilterMode filterMip); void GX2SetPixelSamplerBorderColor(uint32_t unit, float red, float green, float blue, float alpha); void GX2SetVertexSamplerBorderColor(uint32_t unit, float red, float green, float blue, float alpha); void GX2SetGeometrySamplerBorderColor(uint32_t unit, float red, float green, float blue, float alpha); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_shaders.cpp ================================================ #include "gx2.h" #include "gx2_debug.h" #include "gx2_cbpool.h" #include "gx2_fetchshader.h" #include "gx2_shaders.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include using namespace latte::pm4; using latte::Register; namespace cafe::gx2 { using namespace cafe::coreinit; uint32_t GX2CalcGeometryShaderInputRingBufferSize(uint32_t ringItemSize) { return ringItemSize * 16384; } uint32_t GX2CalcGeometryShaderOutputRingBufferSize(uint32_t ringItemSize) { return ringItemSize * 16384; } void GX2SetFetchShader(virt_ptr shader) { auto sq_pgm_resources_fs = shader->regs.sq_pgm_resources_fs.value(); uint32_t shaderRegData[] = { OSEffectiveToPhysical(virt_cast(shader->data)) >> 8, shader->size >> 3, 0x100000, 0x100000, sq_pgm_resources_fs.value, }; internal::writePM4(SetContextRegs { Register::SQ_PGM_START_FS, gsl::make_span(shaderRegData) }); uint32_t vgt_instance_step_rates[] = { shader->divisors[0], shader->divisors[1], }; internal::writePM4(SetContextRegs { Register::VGT_INSTANCE_STEP_RATE_0, gsl::make_span(vgt_instance_step_rates) }); internal::debugDumpShader(shader); } void GX2SetVertexShader(virt_ptr shader) { auto ringItemsize = shader->ringItemsize.value(); auto pa_cl_vs_out_cntl = shader->regs.pa_cl_vs_out_cntl.value(); auto spi_vs_out_config = shader->regs.spi_vs_out_config.value(); auto num_spi_vs_out_id = shader->regs.num_spi_vs_out_id.value(); auto spi_vs_out_id = shader->regs.spi_vs_out_id.value(); auto sq_pgm_resources_vs = shader->regs.sq_pgm_resources_vs.value(); auto sq_vtx_semantic_clear = shader->regs.sq_vtx_semantic_clear.value(); auto num_sq_vtx_semantic = shader->regs.num_sq_vtx_semantic.value(); auto sq_vtx_semantic = shader->regs.sq_vtx_semantic.value(); auto vgt_hos_reuse_depth = shader->regs.vgt_hos_reuse_depth.value(); auto vgt_primitiveid_en = shader->regs.vgt_primitiveid_en.value(); auto vgt_strmout_buffer_en = shader->regs.vgt_strmout_buffer_en.value(); auto vgt_vertex_reuse_block_cntl = shader->regs.vgt_vertex_reuse_block_cntl.value(); auto shaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->data)); auto shaderProgSize = shader->size; if (!shaderProgAddr) { shaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->gx2rData.buffer)); shaderProgSize = shader->gx2rData.elemCount * shader->gx2rData.elemSize; } decaf_check(shaderProgAddr); decaf_check(shaderProgSize); uint32_t shaderRegData[] = { shaderProgAddr >> 8, shaderProgSize >> 3, 0x100000, 0x100000, sq_pgm_resources_vs.value, }; if (shader->mode != GX2ShaderMode::GeometryShader) { internal::writePM4(SetContextRegs { Register::SQ_PGM_START_VS, gsl::make_span(shaderRegData) }); internal::writePM4(SetContextReg { Register::VGT_PRIMITIVEID_EN, vgt_primitiveid_en.value }); internal::writePM4(SetContextReg { Register::SPI_VS_OUT_CONFIG, spi_vs_out_config.value }); internal::writePM4(SetContextReg { Register::PA_CL_VS_OUT_CNTL, pa_cl_vs_out_cntl.value }); if (num_spi_vs_out_id > 0) { internal::writePM4(SetContextRegs { Register::SPI_VS_OUT_ID_0, gsl::make_span(&spi_vs_out_id[0].value, num_spi_vs_out_id) }); } internal::writePM4(SetContextReg { Register::SQ_PGM_CF_OFFSET_VS, 0 }); if (shader->hasStreamOut) { internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_0, shader->streamOutStride[0] >> 2 }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_1, shader->streamOutStride[1] >> 2 }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_2, shader->streamOutStride[2] >> 2 }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_3, shader->streamOutStride[3] >> 2 }); } internal::writePM4(SetContextReg { Register::VGT_STRMOUT_BUFFER_EN, vgt_strmout_buffer_en.value }); } else { internal::writePM4(SetContextRegs { Register::SQ_PGM_START_ES, gsl::make_span(shaderRegData) }); internal::writePM4(SetContextReg { Register::SQ_ESGS_RING_ITEMSIZE, ringItemsize }); } internal::writePM4(SetContextReg { Register::SQ_VTX_SEMANTIC_CLEAR, sq_vtx_semantic_clear.value }); if (num_sq_vtx_semantic > 0) { internal::writePM4(SetContextRegs { Register::SQ_VTX_SEMANTIC_0, gsl::make_span(&sq_vtx_semantic[0].value, num_sq_vtx_semantic) }); } internal::writePM4(SetContextReg { Register::VGT_VERTEX_REUSE_BLOCK_CNTL, vgt_vertex_reuse_block_cntl.value }); internal::writePM4(SetContextReg { Register::VGT_HOS_REUSE_DEPTH, vgt_hos_reuse_depth.value }); for (auto i = 0u; i < shader->loopVarCount; ++i) { auto id = static_cast(Register::SQ_LOOP_CONST_VS_0 + shader->loopVars[i].offset); internal::writePM4(SetLoopConst { id, shader->loopVars[i].value }); } internal::debugDumpShader(shader); } void GX2SetPixelShader(virt_ptr shader) { auto cb_shader_control = shader->regs.cb_shader_control.value(); auto cb_shader_mask = shader->regs.cb_shader_mask.value(); auto db_shader_control = shader->regs.db_shader_control.value(); auto spi_input_z = shader->regs.spi_input_z.value(); auto spi_ps_in_control_0 = shader->regs.spi_ps_in_control_0.value(); auto spi_ps_in_control_1 = shader->regs.spi_ps_in_control_1.value(); auto spi_ps_input_cntls = shader->regs.spi_ps_input_cntls.value(); auto num_spi_ps_input_cntl = shader->regs.num_spi_ps_input_cntl.value(); auto sq_pgm_resources_ps = shader->regs.sq_pgm_resources_ps.value(); auto sq_pgm_exports_ps = shader->regs.sq_pgm_exports_ps.value(); auto shaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->data)); auto shaderProgSize = shader->size; if (!shaderProgAddr) { shaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->gx2rData.buffer)); shaderProgSize = shader->gx2rData.elemCount * shader->gx2rData.elemSize; } decaf_check(shaderProgAddr); decaf_check(shaderProgSize); uint32_t shaderRegData[] = { shaderProgAddr >> 8, shaderProgSize >> 3, 0x100000, 0x100000, sq_pgm_resources_ps.value, sq_pgm_exports_ps.value, }; internal::writePM4(SetContextRegs { Register::SQ_PGM_START_PS, gsl::make_span(shaderRegData) }); uint32_t spi_ps_in_control[] = { spi_ps_in_control_0.value, spi_ps_in_control_1.value, }; internal::writePM4(SetContextRegs { Register::SPI_PS_IN_CONTROL_0, gsl::make_span(spi_ps_in_control) }); if (num_spi_ps_input_cntl > 0) { internal::writePM4(SetContextRegs { Register::SPI_PS_INPUT_CNTL_0, gsl::make_span(&spi_ps_input_cntls[0].value, num_spi_ps_input_cntl), }); } db_shader_control = db_shader_control .DUAL_EXPORT_ENABLE(1); internal::writePM4(SetContextReg { Register::CB_SHADER_MASK, cb_shader_mask.value }); internal::writePM4(SetContextReg { Register::CB_SHADER_CONTROL, cb_shader_control.value }); internal::writePM4(SetContextReg { Register::DB_SHADER_CONTROL, db_shader_control.value }); internal::writePM4(SetContextReg { Register::SPI_INPUT_Z, spi_input_z.value }); for (auto i = 0u; i < shader->loopVarCount; ++i) { auto id = static_cast(Register::SQ_LOOP_CONST_PS_0 + shader->loopVars[i].offset); internal::writePM4(SetLoopConst { id, shader->loopVars[i].value }); } internal::debugDumpShader(shader); } void GX2SetGeometryShader(virt_ptr shader) { // Setup geometry shader data auto sq_pgm_resources_gs = shader->regs.sq_pgm_resources_gs.value(); auto sq_gs_vert_itemsize = shader->regs.sq_gs_vert_itemsize.value(); auto vgt_gs_out_prim_type = shader->regs.vgt_gs_out_prim_type.value(); auto vgt_gs_mode = shader->regs.vgt_gs_mode.value(); auto vgt_strmout_buffer_en = shader->regs.vgt_strmout_buffer_en.value(); auto shaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->data)); auto shaderProgSize = shader->size; if (!shaderProgAddr) { shaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->gx2rData.buffer)); shaderProgSize = shader->gx2rData.elemCount * shader->gx2rData.elemSize; } decaf_check(shaderProgAddr); decaf_check(shaderProgSize); uint32_t shaderRegData[] = { shaderProgAddr >> 8, shaderProgSize >> 3, 0, 0, sq_pgm_resources_gs.value, }; internal::writePM4(SetContextRegs { Register::SQ_PGM_START_GS, gsl::make_span(shaderRegData) }); internal::writePM4(SetContextReg { Register::VGT_GS_OUT_PRIM_TYPE, vgt_gs_out_prim_type.value }); internal::writePM4(SetContextReg { Register::VGT_GS_MODE, vgt_gs_mode.value }); internal::writePM4(SetContextReg { Register::SQ_GS_VERT_ITEMSIZE, sq_gs_vert_itemsize.value }); if (shader->hasStreamOut) { internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_0, shader->streamOutStride[0] >> 2 }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_1, shader->streamOutStride[1] >> 2 }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_2, shader->streamOutStride[2] >> 2 }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_3, shader->streamOutStride[3] >> 2 }); } internal::writePM4(SetContextReg { Register::VGT_STRMOUT_BUFFER_EN, vgt_strmout_buffer_en.value }); // Setup vertex shader data auto sq_pgm_resources_vs = shader->regs.sq_pgm_resources_vs.value(); auto num_spi_vs_out_id = shader->regs.num_spi_vs_out_id.value(); auto spi_vs_out_id = shader->regs.spi_vs_out_id.value(); auto spi_vs_out_config = shader->regs.spi_vs_out_config.value(); auto pa_cl_vs_out_cntl = shader->regs.pa_cl_vs_out_cntl.value(); auto vertexShaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->vertexShaderData)); auto vertexShaderProgSize = shader->vertexShaderSize; if (!vertexShaderProgAddr) { vertexShaderProgAddr = OSEffectiveToPhysical(virt_cast(shader->gx2rVertexShaderData.buffer)); vertexShaderProgSize = shader->gx2rVertexShaderData.elemCount * shader->gx2rVertexShaderData.elemSize; } decaf_check(vertexShaderProgAddr); decaf_check(vertexShaderProgSize); uint32_t vertexShaderRegData[] = { vertexShaderProgAddr >> 8, vertexShaderProgSize >> 3, 0, 0, sq_pgm_resources_vs.value, }; internal::writePM4(SetContextRegs { Register::SQ_PGM_START_VS, gsl::make_span(vertexShaderRegData) }); internal::writePM4(SetContextReg { Register::PA_CL_VS_OUT_CNTL, pa_cl_vs_out_cntl.value }); if (num_spi_vs_out_id > 0) { internal::writePM4(SetContextRegs { Register::SPI_VS_OUT_ID_0, gsl::make_span(&spi_vs_out_id[0].value, num_spi_vs_out_id) }); } internal::writePM4(SetContextReg { Register::SPI_VS_OUT_CONFIG, spi_vs_out_config.value }); internal::writePM4(SetContextReg { Register::SQ_GSVS_RING_ITEMSIZE, shader->ringItemSize }); for (auto i = 0u; i < shader->loopVarCount; ++i) { auto id = static_cast(Register::SQ_LOOP_CONST_GS_0 + shader->loopVars[i].offset); internal::writePM4(SetLoopConst { id, shader->loopVars[i].value }); } } void _GX2SetSampler(virt_ptr sampler, uint32_t id) { internal::writePM4(SetSamplerAttrib { id * 3, sampler->regs.word0, sampler->regs.word1, sampler->regs.word2 }); } void GX2SetPixelSampler(virt_ptr sampler, uint32_t id) { decaf_check(id < 0x12); _GX2SetSampler(sampler, 0 + id); } void GX2SetVertexSampler(virt_ptr sampler, uint32_t id) { decaf_check(id < 0x12); _GX2SetSampler(sampler, 18 + id); } void GX2SetGeometrySampler(virt_ptr sampler, uint32_t id) { decaf_check(id < 0x12); _GX2SetSampler(sampler, 36 + id); } void GX2SetVertexUniformReg(uint32_t offset, uint32_t count, virt_ptr data) { auto loop = offset >> 16; if (loop) { auto id = static_cast(Register::SQ_LOOP_CONST_VS_0 + 4 * loop); internal::writePM4(SetLoopConst { id, data[0] }); offset &= 0x7fff; } auto id = static_cast(Register::SQ_ALU_CONSTANT0_256 + 4 * offset); internal::writePM4(SetAluConstsBE { id, { data.get(), count } }); } void GX2SetPixelUniformReg(uint32_t offset, uint32_t count, virt_ptr data) { auto loop = offset >> 16; if (loop) { auto id = static_cast(Register::SQ_LOOP_CONST_PS_0 + 4 * loop); internal::writePM4(SetLoopConst { id, data[0] }); offset &= 0x7fff; } auto id = static_cast(Register::SQ_ALU_CONSTANT0_0 + 4 * offset); internal::writePM4(SetAluConstsBE { id, { data.get(), count } }); } void GX2SetVertexUniformBlock(uint32_t location, uint32_t size, virt_ptr data) { decaf_check(!(virt_cast(data) & 0xFF)); SetVtxResource res; std::memset(&res, 0, sizeof(SetVtxResource)); res.id = (latte::SQ_RES_OFFSET::VS_BUF_RESOURCE_0 + location) * 7; res.baseAddress = OSEffectiveToPhysical(virt_cast(data)); res.word1 = res.word1 .SIZE(size - 1); res.word2 = res.word2 .STRIDE(16) .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32) .FORMAT_COMP_ALL(latte::SQ_FORMAT_COMP::SIGNED); res.word3 = res.word3 .MEM_REQUEST_SIZE(1); res.word6 = res.word6 .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER); internal::writePM4(res); auto addrId = static_cast(Register::SQ_ALU_CONST_CACHE_VS_0 + location * 4); auto addr256 = OSEffectiveToPhysical(virt_cast(data)) >> 8; internal::writePM4(SetContextReg { addrId, addr256 }); auto sizeId = static_cast(Register::SQ_ALU_CONST_BUFFER_SIZE_VS_0 + location * 4); auto size256 = ((size + 255) >> 8) & 0x1FF; internal::writePM4(SetContextReg { sizeId, size256 }); } void GX2SetPixelUniformBlock(uint32_t location, uint32_t size, virt_ptr data) { decaf_check(!(virt_cast(data) & 0xFF)); SetVtxResource res; std::memset(&res, 0, sizeof(SetVtxResource)); res.id = (latte::SQ_RES_OFFSET::PS_BUF_RESOURCE_0 + location) * 7; res.baseAddress = OSEffectiveToPhysical(virt_cast(data)); res.word1 = res.word1 .SIZE(size - 1); res.word2 = res.word2 .STRIDE(16) .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32) .FORMAT_COMP_ALL(latte::SQ_FORMAT_COMP::SIGNED); res.word3 = res.word3 .MEM_REQUEST_SIZE(1); res.word6 = res.word6 .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER); internal::writePM4(res); auto addrId = static_cast(Register::SQ_ALU_CONST_CACHE_PS_0 + location * 4); auto addr256 = OSEffectiveToPhysical(virt_cast(data)) >> 8; internal::writePM4(SetContextReg { addrId, addr256 }); auto sizeId = static_cast(Register::SQ_ALU_CONST_BUFFER_SIZE_PS_0 + location * 4); auto size256 = ((size + 255) >> 8) & 0x1FF; internal::writePM4(SetContextReg { sizeId, size256 }); } void GX2SetGeometryUniformBlock(uint32_t location, uint32_t size, virt_ptr data) { decaf_check(!(virt_cast(data) & 0xFF)); SetVtxResource res; std::memset(&res, 0, sizeof(SetVtxResource)); res.id = (latte::SQ_RES_OFFSET::GS_BUF_RESOURCE_0 + location) * 7; res.baseAddress = OSEffectiveToPhysical(virt_cast(data)); res.word1 = res.word1 .SIZE(size - 1); res.word2 = res.word2 .STRIDE(16) .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32) .FORMAT_COMP_ALL(latte::SQ_FORMAT_COMP::SIGNED); res.word3 = res.word3 .MEM_REQUEST_SIZE(1); res.word6 = res.word6 .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER); internal::writePM4(res); auto addrId = static_cast(Register::SQ_ALU_CONST_CACHE_GS_0 + location * 4); auto addr256 = OSEffectiveToPhysical(virt_cast(data)) >> 8; internal::writePM4(SetContextReg { addrId, addr256 }); auto sizeId = static_cast(Register::SQ_ALU_CONST_BUFFER_SIZE_GS_0 + location * 4); auto size256 = ((size + 255) >> 8) & 0x1FF; internal::writePM4(SetContextReg { sizeId, size256 }); } void GX2SetShaderModeEx(GX2ShaderMode mode, uint32_t numVsGpr, uint32_t numVsStackEntries, uint32_t numGsGpr, uint32_t numGsStackEntries, uint32_t numPsGpr, uint32_t numPsStackEntries) { auto sq_config = latte::SQ_CONFIG::get(0); auto sq_gpr_resource_mgmt_1 = latte::SQ_GPR_RESOURCE_MGMT_1::get(0); auto sq_gpr_resource_mgmt_2 = latte::SQ_GPR_RESOURCE_MGMT_2::get(0); auto sq_thread_resource_mgmt = latte::SQ_THREAD_RESOURCE_MGMT::get(0); auto sq_stack_resource_mgmt_1 = latte::SQ_STACK_RESOURCE_MGMT_1::get(0); auto sq_stack_resource_mgmt_2 = latte::SQ_STACK_RESOURCE_MGMT_2::get(0); if (mode != GX2ShaderMode::GeometryShader) { auto vgt_gs_mode = latte::VGT_GS_MODE::get(0); if (mode == GX2ShaderMode::ComputeShader) { vgt_gs_mode = vgt_gs_mode .MODE(latte::VGT_GS_ENABLE_MODE::SCENARIO_G) .COMPUTE_MODE(1) .FAST_COMPUTE_MODE(1) .PARTIAL_THD_AT_EOI(1); } else { vgt_gs_mode = vgt_gs_mode .MODE(latte::VGT_GS_ENABLE_MODE::OFF); } internal::writePM4(SetContextReg { Register::VGT_GS_MODE, vgt_gs_mode.value }); } if (mode == GX2ShaderMode::ComputeShader) { sq_config = sq_config .ALU_INST_PREFER_VECTOR(1) .PS_PRIO(0) .VS_PRIO(1) .GS_PRIO(2) .ES_PRIO(3); } else { sq_config = sq_config .ALU_INST_PREFER_VECTOR(1) .PS_PRIO(3) .VS_PRIO(2) .GS_PRIO(1) .ES_PRIO(0); } if (mode == GX2ShaderMode::UniformRegister) { sq_config = sq_config .DX9_CONSTS(1); } if (mode == GX2ShaderMode::GeometryShader) { sq_gpr_resource_mgmt_1 = sq_gpr_resource_mgmt_1 .NUM_PS_GPRS(numPsGpr) .NUM_VS_GPRS(64) .NUM_CLAUSE_TEMP_GPRS(4); sq_gpr_resource_mgmt_2 = sq_gpr_resource_mgmt_2 .NUM_ES_GPRS(numVsGpr) .NUM_GS_GPRS(numGsGpr); sq_stack_resource_mgmt_1 = sq_stack_resource_mgmt_1 .NUM_PS_STACK_ENTRIES(numPsStackEntries); sq_stack_resource_mgmt_2 .NUM_ES_STACK_ENTRIES(numVsStackEntries); sq_stack_resource_mgmt_2 .NUM_GS_STACK_ENTRIES(numGsStackEntries); sq_thread_resource_mgmt = sq_thread_resource_mgmt .NUM_PS_THREADS(124) .NUM_VS_THREADS(32) .NUM_GS_THREADS(8) .NUM_ES_THREADS(28); } else if (mode == GX2ShaderMode::ComputeShader) { sq_gpr_resource_mgmt_1 = sq_gpr_resource_mgmt_1 .NUM_CLAUSE_TEMP_GPRS(4); sq_gpr_resource_mgmt_2 = sq_gpr_resource_mgmt_2 .NUM_ES_GPRS(248); sq_stack_resource_mgmt_2 = sq_stack_resource_mgmt_2 .NUM_ES_STACK_ENTRIES(256); sq_thread_resource_mgmt = sq_thread_resource_mgmt .NUM_PS_THREADS(1) .NUM_VS_THREADS(1) .NUM_GS_THREADS(1) .NUM_ES_THREADS(189); } else { sq_gpr_resource_mgmt_1 = sq_gpr_resource_mgmt_1 .NUM_PS_GPRS(numPsGpr) .NUM_VS_GPRS(numVsGpr); sq_stack_resource_mgmt_1 = sq_stack_resource_mgmt_1 .NUM_PS_STACK_ENTRIES(numPsStackEntries) .NUM_VS_STACK_ENTRIES(numVsStackEntries); sq_thread_resource_mgmt = sq_thread_resource_mgmt .NUM_PS_THREADS(136) .NUM_VS_THREADS(48) .NUM_GS_THREADS(4) .NUM_ES_THREADS(4); } uint32_t regData[] = { sq_config.value, sq_gpr_resource_mgmt_1.value, sq_gpr_resource_mgmt_2.value, sq_thread_resource_mgmt.value, sq_stack_resource_mgmt_1.value, sq_stack_resource_mgmt_2.value, }; internal::writePM4(SetConfigRegs { Register::SQ_CONFIG, gsl::make_span(regData) }); if (mode == GX2ShaderMode::ComputeShader) { uint32_t ringBaseData[] = { 0, 0xFFFFFF, 0, 0xFFFFFF }; internal::writePM4(SetConfigRegs { Register::SQ_ESGS_RING_BASE, gsl::make_span(ringBaseData) }); uint32_t ringItemSizes[] = { 0, 1 }; internal::writePM4(SetContextRegs { Register::SQ_ESGS_RING_ITEMSIZE, gsl::make_span(ringItemSizes) }); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_EN, 0 }); } } void GX2SetStreamOutBuffer(uint32_t index, virt_ptr stream) { decaf_check(index <= 3); decaf_check(virt_cast(stream->buffer) % 256 == 0); decaf_check(stream->stride % 4 == 0); decaf_check(stream->size % 4 == 0); auto addr = OSEffectiveToPhysical(virt_cast(stream->buffer)); auto size = stream->size; if (!addr) { addr = OSEffectiveToPhysical(virt_cast(stream->gx2rData.buffer)); size = stream->gx2rData.elemCount * stream->gx2rData.elemSize; } internal::writePM4(SetContextReg { static_cast(Register::VGT_STRMOUT_BUFFER_SIZE_0 + 16 * index), size >> 2 }); internal::writePM4(SetContextReg { static_cast(Register::VGT_STRMOUT_BUFFER_BASE_0 + 16 * index), addr >> 8 }); internal::writePM4(StreamOutBaseUpdate { index, addr >> 8 }); } void GX2SetStreamOutEnable(BOOL enable) { auto vgt_strmout_en = latte::VGT_STRMOUT_EN::get(0); vgt_strmout_en = vgt_strmout_en .STREAMOUT(!!enable); internal::writePM4(SetContextReg { Register::VGT_STRMOUT_EN, vgt_strmout_en.value }); } void GX2SetStreamOutContext(uint32_t index, virt_ptr stream, GX2StreamOutContextMode mode) { decaf_check(index <= 3); auto srcLo = phys_addr { 0 }; // In the case of an explicit offset, the stream pointer is actually // a uint32_t specifying the offset into the buffer to be using. uint32_t explicitOffset = virt_cast(stream).getAddress(); auto control = SBU_CONTROL::get(0) .STORE_BUFFER_FILLED_SIZE(false) .SELECT_BUFFER(static_cast(index)); switch (mode) { case GX2StreamOutContextMode::Append: control = control .OFFSET_SOURCE(STRMOUT_OFFSET_FROM_MEM); srcLo = OSEffectiveToPhysical(virt_cast(stream->context)); break; case GX2StreamOutContextMode::FromStart: control = control .OFFSET_SOURCE(STRMOUT_OFFSET_FROM_PACKET); srcLo = phys_addr { 0 }; break; case GX2StreamOutContextMode::FromOffset: control = control .OFFSET_SOURCE(STRMOUT_OFFSET_FROM_PACKET); srcLo = phys_addr { explicitOffset }; break; } internal::writePM4(StreamOutBufferUpdate { control, phys_addr { 0 }, 0, srcLo, 0 }); } void GX2SaveStreamOutContext(uint32_t index, virt_ptr stream) { decaf_check(index <= 3); auto dstLo = OSEffectiveToPhysical(virt_cast(stream->context)); auto control = SBU_CONTROL::get(0) .STORE_BUFFER_FILLED_SIZE(true) .OFFSET_SOURCE(STRMOUT_OFFSET_NONE) .SELECT_BUFFER(static_cast(index)); internal::writePM4(StreamOutBufferUpdate { control, dstLo, 0, phys_addr { 0 }, 0 }); } void GX2SetGeometryShaderInputRingBuffer(virt_ptr buffer, uint32_t size) { internal::writePM4(SetConfigReg { Register::SQ_ESGS_RING_BASE, OSEffectiveToPhysical(virt_cast(buffer)) >> 8 }); internal::writePM4(SetConfigReg { Register::SQ_ESGS_RING_SIZE, size >> 8 }); SetVtxResource res; std::memset(&res, 0, sizeof(SetVtxResource)); res.id = latte::SQ_RES_OFFSET::GS_GSIN_RESOURCE * 7; res.baseAddress = OSEffectiveToPhysical(virt_cast(buffer)); res.word1 = res.word1 .SIZE(size - 1); res.word2 = res.word2 .STRIDE(4) .CLAMP_X(latte::SQ_VTX_CLAMP::TO_NAN) .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT); res.word3 = res.word3 .UNCACHED(1); res.word6 = res.word6 .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER); internal::writePM4(res); } void GX2SetGeometryShaderOutputRingBuffer(virt_ptr buffer, uint32_t size) { internal::writePM4(SetConfigReg { Register::SQ_GSVS_RING_BASE, OSEffectiveToPhysical(virt_cast(buffer)) >> 8 }); internal::writePM4(SetConfigReg { Register::SQ_GSVS_RING_SIZE, size >> 8 }); SetVtxResource res; std::memset(&res, 0, sizeof(SetVtxResource)); res.id = latte::SQ_RES_OFFSET::VS_GSOUT_RESOURCE * 7; res.baseAddress = OSEffectiveToPhysical(virt_cast(buffer)); res.word1 = res.word1 .SIZE(size - 1); res.word2 = res.word2 .STRIDE(4) .CLAMP_X(latte::SQ_VTX_CLAMP::TO_NAN) .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT); res.word3 = res.word3 .UNCACHED(1); res.word6 = res.word6 .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER); internal::writePM4(res); } uint32_t GX2GetPixelShaderGPRs(virt_ptr shader) { return shader->regs.sq_pgm_resources_ps.value().NUM_GPRS(); } uint32_t GX2GetPixelShaderStackEntries(virt_ptr shader) { return shader->regs.sq_pgm_resources_ps.value().STACK_SIZE(); } uint32_t GX2GetVertexShaderGPRs(virt_ptr shader) { return shader->regs.sq_pgm_resources_vs.value().NUM_GPRS(); } uint32_t GX2GetVertexShaderStackEntries(virt_ptr shader) { return shader->regs.sq_pgm_resources_vs.value().STACK_SIZE(); } uint32_t GX2GetGeometryShaderGPRs(virt_ptr shader) { return shader->regs.sq_pgm_resources_gs.value().NUM_GPRS(); } uint32_t GX2GetGeometryShaderStackEntries(virt_ptr shader) { return shader->regs.sq_pgm_resources_gs.value().STACK_SIZE(); } void Library::registerShadersSymbols() { RegisterFunctionExport(GX2CalcGeometryShaderInputRingBufferSize); RegisterFunctionExport(GX2CalcGeometryShaderOutputRingBufferSize); RegisterFunctionExport(GX2SetFetchShader); RegisterFunctionExport(GX2SetVertexShader); RegisterFunctionExport(GX2SetPixelShader); RegisterFunctionExport(GX2SetGeometryShader); RegisterFunctionExport(GX2SetVertexSampler); RegisterFunctionExport(GX2SetPixelSampler); RegisterFunctionExport(GX2SetGeometrySampler); RegisterFunctionExport(GX2SetVertexUniformReg); RegisterFunctionExport(GX2SetPixelUniformReg); RegisterFunctionExport(GX2SetVertexUniformBlock); RegisterFunctionExport(GX2SetPixelUniformBlock); RegisterFunctionExport(GX2SetGeometryUniformBlock); RegisterFunctionExport(GX2SetShaderModeEx); RegisterFunctionExport(GX2SetStreamOutBuffer); RegisterFunctionExport(GX2SetStreamOutEnable); RegisterFunctionExport(GX2SetStreamOutContext); RegisterFunctionExport(GX2SaveStreamOutContext); RegisterFunctionExport(GX2SetGeometryShaderInputRingBuffer); RegisterFunctionExport(GX2SetGeometryShaderOutputRingBuffer); RegisterFunctionExport(GX2GetPixelShaderGPRs); RegisterFunctionExport(GX2GetPixelShaderStackEntries); RegisterFunctionExport(GX2GetVertexShaderGPRs); RegisterFunctionExport(GX2GetVertexShaderStackEntries); RegisterFunctionExport(GX2GetGeometryShaderGPRs); RegisterFunctionExport(GX2GetGeometryShaderStackEntries); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_shaders.h ================================================ #pragma once #include "gx2_enum.h" #include "gx2_sampler.h" #include "gx2r_buffer.h" #include #include namespace cafe::gx2 { /** * \defgroup gx2_shaders Shaders * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2FetchShader; struct GX2UniformVar { be2_virt_ptr name; be2_val type; be2_val count; be2_val offset; be2_val block; }; CHECK_OFFSET(GX2UniformVar, 0x00, name); CHECK_OFFSET(GX2UniformVar, 0x04, type); CHECK_OFFSET(GX2UniformVar, 0x08, count); CHECK_OFFSET(GX2UniformVar, 0x0C, offset); CHECK_OFFSET(GX2UniformVar, 0x10, block); CHECK_SIZE(GX2UniformVar, 0x14); struct GX2UniformInitialValue { be2_array value; be2_val offset; }; CHECK_OFFSET(GX2UniformInitialValue, 0x00, value); CHECK_OFFSET(GX2UniformInitialValue, 0x10, offset); CHECK_SIZE(GX2UniformInitialValue, 0x14); struct GX2UniformBlock { be2_virt_ptr name; be2_val offset; be2_val size; }; CHECK_OFFSET(GX2UniformBlock, 0x00, name); CHECK_OFFSET(GX2UniformBlock, 0x04, offset); CHECK_OFFSET(GX2UniformBlock, 0x08, size); CHECK_SIZE(GX2UniformBlock, 0x0C); struct GX2AttribVar { be2_virt_ptr name; be2_val type; be2_val count; be2_val location; }; CHECK_OFFSET(GX2AttribVar, 0x00, name); CHECK_OFFSET(GX2AttribVar, 0x04, type); CHECK_OFFSET(GX2AttribVar, 0x08, count); CHECK_OFFSET(GX2AttribVar, 0x0C, location); CHECK_SIZE(GX2AttribVar, 0x10); struct GX2SamplerVar { be2_virt_ptr name; be2_val type; be2_val location; }; CHECK_OFFSET(GX2SamplerVar, 0x00, name); CHECK_OFFSET(GX2SamplerVar, 0x04, type); CHECK_OFFSET(GX2SamplerVar, 0x08, location); CHECK_SIZE(GX2SamplerVar, 0x0C); struct GX2LoopVar { be2_val offset; be2_val value; }; CHECK_OFFSET(GX2LoopVar, 0x00, offset); CHECK_OFFSET(GX2LoopVar, 0x04, value); CHECK_SIZE(GX2LoopVar, 0x08); struct GX2VertexShader { struct { be2_val sq_pgm_resources_vs; be2_val vgt_primitiveid_en; be2_val spi_vs_out_config; be2_val num_spi_vs_out_id; be2_array spi_vs_out_id; be2_val pa_cl_vs_out_cntl; be2_val sq_vtx_semantic_clear; be2_val num_sq_vtx_semantic; be2_array sq_vtx_semantic; be2_val vgt_strmout_buffer_en; be2_val vgt_vertex_reuse_block_cntl; be2_val vgt_hos_reuse_depth; } regs; be2_val size; be2_virt_ptr data; be2_val mode; be2_val uniformBlockCount; be2_virt_ptr uniformBlocks; be2_val uniformVarCount; be2_virt_ptr uniformVars; be2_val initialValueCount; be2_virt_ptr initialValues; be2_val loopVarCount; be2_virt_ptr loopVars; be2_val samplerVarCount; be2_virt_ptr samplerVars; be2_val attribVarCount; be2_virt_ptr attribVars; be2_val ringItemsize; be2_val hasStreamOut; be2_array streamOutStride; GX2RBuffer gx2rData; }; CHECK_OFFSET(GX2VertexShader, 0x00, regs.sq_pgm_resources_vs); CHECK_OFFSET(GX2VertexShader, 0x04, regs.vgt_primitiveid_en); CHECK_OFFSET(GX2VertexShader, 0x08, regs.spi_vs_out_config); CHECK_OFFSET(GX2VertexShader, 0x0C, regs.num_spi_vs_out_id); CHECK_OFFSET(GX2VertexShader, 0x10, regs.spi_vs_out_id); CHECK_OFFSET(GX2VertexShader, 0x38, regs.pa_cl_vs_out_cntl); CHECK_OFFSET(GX2VertexShader, 0x3C, regs.sq_vtx_semantic_clear); CHECK_OFFSET(GX2VertexShader, 0x40, regs.num_sq_vtx_semantic); CHECK_OFFSET(GX2VertexShader, 0x44, regs.sq_vtx_semantic); CHECK_OFFSET(GX2VertexShader, 0xC4, regs.vgt_strmout_buffer_en); CHECK_OFFSET(GX2VertexShader, 0xC8, regs.vgt_vertex_reuse_block_cntl); CHECK_OFFSET(GX2VertexShader, 0xCC, regs.vgt_hos_reuse_depth); CHECK_OFFSET(GX2VertexShader, 0xD0, size); CHECK_OFFSET(GX2VertexShader, 0xD4, data); CHECK_OFFSET(GX2VertexShader, 0xD8, mode); CHECK_OFFSET(GX2VertexShader, 0xDC, uniformBlockCount); CHECK_OFFSET(GX2VertexShader, 0xE0, uniformBlocks); CHECK_OFFSET(GX2VertexShader, 0xE4, uniformVarCount); CHECK_OFFSET(GX2VertexShader, 0xE8, uniformVars); CHECK_OFFSET(GX2VertexShader, 0xEC, initialValueCount); CHECK_OFFSET(GX2VertexShader, 0xF0, initialValues); CHECK_OFFSET(GX2VertexShader, 0xF4, loopVarCount); CHECK_OFFSET(GX2VertexShader, 0xF8, loopVars); CHECK_OFFSET(GX2VertexShader, 0xFC, samplerVarCount); CHECK_OFFSET(GX2VertexShader, 0x100, samplerVars); CHECK_OFFSET(GX2VertexShader, 0x104, attribVarCount); CHECK_OFFSET(GX2VertexShader, 0x108, attribVars); CHECK_OFFSET(GX2VertexShader, 0x10C, ringItemsize); CHECK_OFFSET(GX2VertexShader, 0x110, hasStreamOut); CHECK_OFFSET(GX2VertexShader, 0x114, streamOutStride); CHECK_OFFSET(GX2VertexShader, 0x124, gx2rData); CHECK_SIZE(GX2VertexShader, 0x134); struct GX2PixelShader { struct { be2_val sq_pgm_resources_ps; be2_val sq_pgm_exports_ps; be2_val spi_ps_in_control_0; be2_val spi_ps_in_control_1; be2_val num_spi_ps_input_cntl; be2_array spi_ps_input_cntls; be2_val cb_shader_mask; be2_val cb_shader_control; be2_val db_shader_control; be2_val spi_input_z; } regs; be2_val size; be2_virt_ptr data; be2_val mode; be2_val uniformBlockCount; be2_virt_ptr uniformBlocks; be2_val uniformVarCount; be2_virt_ptr uniformVars; be2_val initialValueCount; be2_virt_ptr initialValues; be2_val loopVarCount; be2_virt_ptr loopVars; be2_val samplerVarCount; be2_virt_ptr samplerVars; GX2RBuffer gx2rData; }; CHECK_OFFSET(GX2PixelShader, 0x00, regs.sq_pgm_resources_ps); CHECK_OFFSET(GX2PixelShader, 0x04, regs.sq_pgm_exports_ps); CHECK_OFFSET(GX2PixelShader, 0x08, regs.spi_ps_in_control_0); CHECK_OFFSET(GX2PixelShader, 0x0C, regs.spi_ps_in_control_1); CHECK_OFFSET(GX2PixelShader, 0x10, regs.num_spi_ps_input_cntl); CHECK_OFFSET(GX2PixelShader, 0x14, regs.spi_ps_input_cntls); CHECK_OFFSET(GX2PixelShader, 0x94, regs.cb_shader_mask); CHECK_OFFSET(GX2PixelShader, 0x98, regs.cb_shader_control); CHECK_OFFSET(GX2PixelShader, 0x9C, regs.db_shader_control); CHECK_OFFSET(GX2PixelShader, 0xA0, regs.spi_input_z); CHECK_OFFSET(GX2PixelShader, 0xA4, size); CHECK_OFFSET(GX2PixelShader, 0xA8, data); CHECK_OFFSET(GX2PixelShader, 0xAC, mode); CHECK_OFFSET(GX2PixelShader, 0xB0, uniformBlockCount); CHECK_OFFSET(GX2PixelShader, 0xB4, uniformBlocks); CHECK_OFFSET(GX2PixelShader, 0xB8, uniformVarCount); CHECK_OFFSET(GX2PixelShader, 0xBC, uniformVars); CHECK_OFFSET(GX2PixelShader, 0xC0, initialValueCount); CHECK_OFFSET(GX2PixelShader, 0xC4, initialValues); CHECK_OFFSET(GX2PixelShader, 0xC8, loopVarCount); CHECK_OFFSET(GX2PixelShader, 0xCC, loopVars); CHECK_OFFSET(GX2PixelShader, 0xD0, samplerVarCount); CHECK_OFFSET(GX2PixelShader, 0xD4, samplerVars); CHECK_OFFSET(GX2PixelShader, 0xD8, gx2rData); CHECK_SIZE(GX2PixelShader, 0xe8); struct GX2GeometryShader { struct { be2_val sq_pgm_resources_gs; be2_val vgt_gs_out_prim_type; be2_val vgt_gs_mode; be2_val pa_cl_vs_out_cntl; be2_val sq_pgm_resources_vs; be2_val sq_gs_vert_itemsize; be2_val spi_vs_out_config; be2_val num_spi_vs_out_id; be2_array spi_vs_out_id; be2_val vgt_strmout_buffer_en; } regs; be2_val size; be2_virt_ptr data; be2_val vertexShaderSize; be2_virt_ptr vertexShaderData; be2_val mode; be2_val uniformBlockCount; be2_virt_ptr uniformBlocks; be2_val uniformVarCount; be2_virt_ptr uniformVars; be2_val initialValueCount; be2_virt_ptr initialValues; be2_val loopVarCount; be2_virt_ptr loopVars; be2_val samplerVarCount; be2_virt_ptr samplerVars; be2_val ringItemSize; be2_val hasStreamOut; be2_array streamOutStride; GX2RBuffer gx2rData; GX2RBuffer gx2rVertexShaderData; }; CHECK_OFFSET(GX2GeometryShader, 0x00, regs.sq_pgm_resources_gs); CHECK_OFFSET(GX2GeometryShader, 0x04, regs.vgt_gs_out_prim_type); CHECK_OFFSET(GX2GeometryShader, 0x08, regs.vgt_gs_mode); CHECK_OFFSET(GX2GeometryShader, 0x0C, regs.pa_cl_vs_out_cntl); CHECK_OFFSET(GX2GeometryShader, 0x10, regs.sq_pgm_resources_vs); CHECK_OFFSET(GX2GeometryShader, 0x14, regs.sq_gs_vert_itemsize); CHECK_OFFSET(GX2GeometryShader, 0x18, regs.spi_vs_out_config); CHECK_OFFSET(GX2GeometryShader, 0x1C, regs.num_spi_vs_out_id); CHECK_OFFSET(GX2GeometryShader, 0x20, regs.spi_vs_out_id); CHECK_OFFSET(GX2GeometryShader, 0x48, regs.vgt_strmout_buffer_en); CHECK_OFFSET(GX2GeometryShader, 0x4C, size); CHECK_OFFSET(GX2GeometryShader, 0x50, data); CHECK_OFFSET(GX2GeometryShader, 0x54, vertexShaderSize); CHECK_OFFSET(GX2GeometryShader, 0x58, vertexShaderData); CHECK_OFFSET(GX2GeometryShader, 0x5C, mode); CHECK_OFFSET(GX2GeometryShader, 0x60, uniformBlockCount); CHECK_OFFSET(GX2GeometryShader, 0x64, uniformBlocks); CHECK_OFFSET(GX2GeometryShader, 0x68, uniformVarCount); CHECK_OFFSET(GX2GeometryShader, 0x6C, uniformVars); CHECK_OFFSET(GX2GeometryShader, 0x70, initialValueCount); CHECK_OFFSET(GX2GeometryShader, 0x74, initialValues); CHECK_OFFSET(GX2GeometryShader, 0x78, loopVarCount); CHECK_OFFSET(GX2GeometryShader, 0x7C, loopVars); CHECK_OFFSET(GX2GeometryShader, 0x80, samplerVarCount); CHECK_OFFSET(GX2GeometryShader, 0x84, samplerVars); CHECK_OFFSET(GX2GeometryShader, 0x88, ringItemSize); CHECK_OFFSET(GX2GeometryShader, 0x8C, hasStreamOut); CHECK_OFFSET(GX2GeometryShader, 0x90, streamOutStride); CHECK_OFFSET(GX2GeometryShader, 0xA0, gx2rData); CHECK_OFFSET(GX2GeometryShader, 0xB0, gx2rVertexShaderData); CHECK_SIZE(GX2GeometryShader, 0xC0); struct GX2AttribStream { be2_val location; be2_val buffer; be2_val offset; be2_val format; be2_val type; be2_val aluDivisor; be2_val mask; be2_val endianSwap; }; CHECK_OFFSET(GX2AttribStream, 0x0, location); CHECK_OFFSET(GX2AttribStream, 0x4, buffer); CHECK_OFFSET(GX2AttribStream, 0x8, offset); CHECK_OFFSET(GX2AttribStream, 0xC, format); CHECK_OFFSET(GX2AttribStream, 0x10, type); CHECK_OFFSET(GX2AttribStream, 0x14, aluDivisor); CHECK_OFFSET(GX2AttribStream, 0x18, mask); CHECK_OFFSET(GX2AttribStream, 0x1C, endianSwap); CHECK_SIZE(GX2AttribStream, 0x20); struct GX2StreamContext { be2_val currentOffset; // Total size unknown (but <= 256) }; CHECK_OFFSET(GX2StreamContext, 0x00, currentOffset); struct GX2OutputStream { be2_val size; be2_virt_ptr buffer; be2_val stride; GX2RBuffer gx2rData; be2_virt_ptr context; }; CHECK_OFFSET(GX2OutputStream, 0x00, size); CHECK_OFFSET(GX2OutputStream, 0x04, buffer); CHECK_OFFSET(GX2OutputStream, 0x08, stride); CHECK_OFFSET(GX2OutputStream, 0x0C, gx2rData); CHECK_OFFSET(GX2OutputStream, 0x1C, context); CHECK_SIZE(GX2OutputStream, 0x20); #pragma pack(pop) uint32_t GX2CalcGeometryShaderInputRingBufferSize(uint32_t ringItemSize); uint32_t GX2CalcGeometryShaderOutputRingBufferSize(uint32_t ringItemSize); void GX2SetFetchShader(virt_ptr shader); void GX2SetVertexShader(virt_ptr shader); void GX2SetPixelShader(virt_ptr shader); void GX2SetGeometryShader(virt_ptr shader); void GX2SetVertexSampler(virt_ptr sampler, uint32_t id); void GX2SetPixelSampler(virt_ptr sampler, uint32_t id); void GX2SetGeometrySampler(virt_ptr sampler, uint32_t id); void GX2SetVertexUniformReg(uint32_t offset, uint32_t count, virt_ptr data); void GX2SetPixelUniformReg(uint32_t offset, uint32_t count, virt_ptr data); void GX2SetVertexUniformBlock(uint32_t location, uint32_t size, virt_ptr data); void GX2SetPixelUniformBlock(uint32_t location, uint32_t size, virt_ptr data); void GX2SetGeometryUniformBlock(uint32_t location, uint32_t size, virt_ptr data); void GX2SetShaderModeEx(GX2ShaderMode mode, uint32_t numVsGpr, uint32_t numVsStackEntries, uint32_t numGsGpr, uint32_t numGsStackEntries, uint32_t numPsGpr, uint32_t numPsStackEntries); void GX2SetStreamOutBuffer(uint32_t index, virt_ptr stream); void GX2SetStreamOutEnable(BOOL enable); void GX2SetStreamOutContext(uint32_t index, virt_ptr stream, GX2StreamOutContextMode mode); void GX2SaveStreamOutContext(uint32_t index, virt_ptr stream); void GX2SetGeometryShaderInputRingBuffer(virt_ptr buffer, uint32_t size); void GX2SetGeometryShaderOutputRingBuffer(virt_ptr buffer, uint32_t size); uint32_t GX2GetPixelShaderGPRs(virt_ptr shader); uint32_t GX2GetPixelShaderStackEntries(virt_ptr shader); uint32_t GX2GetVertexShaderGPRs(virt_ptr shader); uint32_t GX2GetVertexShaderStackEntries(virt_ptr shader); uint32_t GX2GetGeometryShaderGPRs(virt_ptr shader); uint32_t GX2GetGeometryShaderStackEntries(virt_ptr shader); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_state.cpp ================================================ #include "gx2.h" #include "gx2_contextstate.h" #include "gx2_debugcapture.h" #include "gx2_displaylist.h" #include "gx2_event.h" #include "gx2_cbpool.h" #include "gx2_internal_pm4cap.h" #include "gx2_registers.h" #include "gx2_state.h" #include "cafe/libraries/coreinit/coreinit_core.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include "cafe/libraries/tcl/tcl_driver.h" #include "decaf_config.h" #include #include #include #include #include namespace cafe::gx2 { using namespace coreinit; struct StaticStateData { be2_val initialized; be2_val mainCoreId; be2_array profilingEnabled; be2_val profileMode; be2_val tossStage; be2_val timeoutMS; be2_val hangState; be2_val hangResponse; be2_val hangResetSwapTimeout; be2_val hangResetSwapsOutstanding; }; static virt_ptr sStateData = nullptr; void GX2Init(virt_ptr attributes) { auto cbPoolBase = virt_ptr { nullptr }; auto argv = virt_ptr { nullptr }; auto cbPoolSize = 0x400000u; auto argc = 0u; auto profileMode = GX2ProfileMode::None; auto tossStage = GX2TossStage::None; auto appIoThreadStackSize = 4096u; // Set main gx2 core sStateData->initialized = true; sStateData->mainCoreId = OSGetCoreId(); // Set default GPU timeout to 10 seconds sStateData->timeoutMS = 10u * 1000; // Setup hang params GX2SetMiscParam(GX2MiscParam::HangResponse, 1); GX2SetMiscParam(GX2MiscParam::HangResetSwapTimeout, 1000); GX2SetMiscParam(GX2MiscParam::HangResetSwapsOutstanding, 3); // Parse attributes while (attributes && *attributes != GX2InitAttrib::End) { auto id = *(attributes++); auto value = static_cast(*(attributes++)); switch (id) { case GX2InitAttrib::CommandBufferPoolBase: cbPoolBase = virt_cast(virt_addr { value }); break; case GX2InitAttrib::CommandBufferPoolSize: cbPoolSize = value; break; case GX2InitAttrib::ArgC: argc = value; break; case GX2InitAttrib::ArgV: argv = virt_cast(virt_addr { value }); break; case GX2InitAttrib::ProfileMode: profileMode = static_cast(value); break; case GX2InitAttrib::TossStage: tossStage = static_cast(value); break; case GX2InitAttrib::AppIoThreadStackSize: appIoThreadStackSize = value; break; default: gLog->warn("Unknown GX2InitAttrib {} = {}", id, value); } } // Ensure minimum size if (cbPoolSize < 0x2000) { cbPoolSize = 0x2000; } // Allocate command buffer pool if (!cbPoolBase) { cbPoolBase = virt_cast(MEMAllocFromDefaultHeapEx(cbPoolSize, 0x100)); } // Allocate AppIo stack from end of cbPool auto appIoStackBuffer = align_up(virt_cast(cbPoolBase) + cbPoolSize - appIoThreadStackSize, 64); appIoThreadStackSize = static_cast(virt_cast(cbPoolBase) + cbPoolSize - appIoStackBuffer); cbPoolSize = static_cast(appIoStackBuffer - virt_cast(cbPoolBase)); // Init event handler stuff (vsync, flips, etc) internal::initEvents(virt_cast(appIoStackBuffer), appIoThreadStackSize); // Initialise GPU callbacks gpu::setFlipCallback(&internal::onFlip); // Initialise command buffer pools internal::initialiseCommandBufferPool(cbPoolBase, cbPoolSize); // Initialise profiling settings internal::initialiseProfiling(profileMode, tossStage); // Setup default gx2 state internal::disableStateShadowing(); internal::initialiseRegisters(); GX2SetDefaultState(); GX2Flush(); } void GX2Shutdown() { if (internal::debugCaptureEnabled()) { internal::debugCaptureShutdown(); } } int32_t GX2GetMainCoreId() { if (sStateData->initialized) { return sStateData->mainCoreId; } else { return -1; } } void GX2Flush() { if (GX2GetDisplayListWriteStatus()) { gLog->error("GX2Flush called from within a display list"); } internal::flushCommandBuffer(0x100, TRUE); } uint32_t GX2GetGPUTimeout() { return sStateData->timeoutMS; } void GX2SetGPUTimeout(uint32_t timeout) { sStateData->timeoutMS = timeout; } uint32_t GX2GetMiscParam(GX2MiscParam param) { switch (param) { case GX2MiscParam::HangState: return sStateData->hangState; case GX2MiscParam::HangResponse: return sStateData->hangResponse; break; case GX2MiscParam::HangResetSwapTimeout: return sStateData->hangResetSwapTimeout; break; case GX2MiscParam::HangResetSwapsOutstanding: return sStateData->hangResetSwapsOutstanding; break; default: return static_cast(-1); } } BOOL GX2SetMiscParam(GX2MiscParam param, uint32_t value) { switch (param) { case GX2MiscParam::HangState: sStateData->hangState = value; break; case GX2MiscParam::HangResponse: if (value > 2) { return FALSE; } tcl::TCLSetHangWait(value == 1 ? TRUE : FALSE); sStateData->hangResponse = value; break; case GX2MiscParam::HangResetSwapTimeout: sStateData->hangResetSwapTimeout = value; break; case GX2MiscParam::HangResetSwapsOutstanding: sStateData->hangResetSwapsOutstanding = value; break; default: return FALSE; } return TRUE; } void GX2SetSpecialState(GXSpecialState state, BOOL enabled) { if (state == GXSpecialState::Clear) { if (enabled) { internal::writePM4(latte::pm4::SetContextReg { latte::Register::PA_CL_VTE_CNTL, latte::PA_CL_VTE_CNTL::get(0) .VTX_XY_FMT(true) .VTX_Z_FMT(true) .value }); internal::writePM4(latte::pm4::SetContextReg { latte::Register::PA_CL_CLIP_CNTL, latte::PA_CL_CLIP_CNTL::get(0) .CLIP_DISABLE(true) .DX_CLIP_SPACE_DEF(true) .value }); } else { internal::writePM4(latte::pm4::SetContextReg { latte::Register::PA_CL_VTE_CNTL, latte::PA_CL_VTE_CNTL::get(0) .VPORT_X_SCALE_ENA(true) .VPORT_X_OFFSET_ENA(true) .VPORT_Y_SCALE_ENA(true) .VPORT_Y_OFFSET_ENA(true) .VPORT_Z_SCALE_ENA(true) .VPORT_Z_OFFSET_ENA(true) .VTX_W0_FMT(true) .value }); GX2SetRasterizerClipControl(true, true); } } else if (state == GXSpecialState::Copy) { // There are no registers set for this special state. } else { gLog->warn("Unexpected GX2SetSpecialState state {}", state); } } namespace internal { void enableStateShadowing() { auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0) .ENABLE_CONFIG_REG(true) .ENABLE_CONTEXT_REG(true) .ENABLE_ALU_CONST(true) .ENABLE_BOOL_CONST(true) .ENABLE_LOOP_CONST(true) .ENABLE_RESOURCE(true) .ENABLE_SAMPLER(true) .ENABLE_CTL_CONST(true) .ENABLE_ORDINAL(true); auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0) .ENABLE_CONFIG_REG(true) .ENABLE_CONTEXT_REG(true) .ENABLE_ALU_CONST(true) .ENABLE_BOOL_CONST(true) .ENABLE_LOOP_CONST(true) .ENABLE_RESOURCE(true) .ENABLE_SAMPLER(true) .ENABLE_CTL_CONST(true) .ENABLE_ORDINAL(true); internal::writePM4(latte::pm4::ContextControl { LOAD_CONTROL, SHADOW_ENABLE }); } void disableStateShadowing() { auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0) .ENABLE_ORDINAL(true); auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0) .ENABLE_ORDINAL(true); internal::writePM4(latte::pm4::ContextControl { LOAD_CONTROL, SHADOW_ENABLE }); } bool isInitialised() { return sStateData->mainCoreId != 0xFF; } uint32_t getMainCoreId() { return sStateData->mainCoreId; } void setMainCore() { sStateData->mainCoreId = OSGetCoreId(); } void initialiseProfiling(GX2ProfileMode profileMode, GX2TossStage tossStage) { sStateData->profileMode = profileMode; sStateData->tossStage = tossStage; // TODO: Update these GX2ProfileMode values with enum named values switch (tossStage) { case GX2TossStage::Unk1: sStateData->profileMode |= 0x60; break; case GX2TossStage::Unk2: sStateData->profileMode |= 0x40; break; case GX2TossStage::Unk7: sStateData->profileMode |= 0x90; break; case GX2TossStage::Unk8: sStateData->profileMode |= 0x10; break; } sStateData->profilingEnabled[0] = true; sStateData->profilingEnabled[1] = true; sStateData->profilingEnabled[2] = true; } GX2ProfileMode getProfileMode() { return sStateData->profileMode; } GX2TossStage getTossStage() { return sStateData->tossStage; } BOOL getProfilingEnabled() { return sStateData->profilingEnabled[cpu::this_core::id()]; } void setProfilingEnabled(BOOL enabled) { sStateData->profilingEnabled[cpu::this_core::id()] = enabled; } } // namespace internal void Library::registerStateSymbols() { RegisterFunctionExport(GX2Init); RegisterFunctionExport(GX2Shutdown); RegisterFunctionExport(GX2GetMainCoreId); RegisterFunctionExport(GX2Flush); RegisterFunctionExport(GX2GetGPUTimeout); RegisterFunctionExport(GX2SetGPUTimeout); RegisterFunctionExport(GX2GetMiscParam); RegisterFunctionExport(GX2SetMiscParam); RegisterFunctionExport(GX2SetSpecialState); RegisterDataInternal(sStateData); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_state.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { void GX2Init(virt_ptr attributes); void GX2Shutdown(); int32_t GX2GetMainCoreId(); void GX2Flush(); uint32_t GX2GetGPUTimeout(); void GX2SetGPUTimeout(uint32_t timeout); uint32_t GX2GetMiscParam(GX2MiscParam param); BOOL GX2SetMiscParam(GX2MiscParam param, uint32_t value); void GX2SetSpecialState(GXSpecialState state, BOOL enabled); namespace internal { void enableStateShadowing(); void disableStateShadowing(); bool isInitialised(); uint32_t getMainCoreId(); void setMainCore(); void initialiseProfiling(GX2ProfileMode profileMode, GX2TossStage tossStage); GX2ProfileMode getProfileMode(); GX2TossStage getTossStage(); BOOL getProfilingEnabled(); void setProfilingEnabled(BOOL enabled); } // namespace internal } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_surface.cpp ================================================ #include "gx2.h" #include "gx2_addrlib.h" #include "gx2_enum_string.h" #include "gx2_format.h" #include "gx2_cbpool.h" #include "gx2_surface.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include #include #include #include #include namespace cafe::gx2 { using namespace cafe::coreinit; static uint32_t calcNumLevelsForSize(uint32_t size) { return 32 - clz(size); } static uint32_t calcNumLevels(virt_ptr surface) { if (surface->mipLevels <= 1) { return 1; } auto levels = std::max( calcNumLevelsForSize(surface->width), calcNumLevelsForSize(surface->height)); if (surface->dim == GX2SurfaceDim::Texture3D) { levels = std::max(levels, calcNumLevelsForSize(surface->depth)); } return levels; } static void computeAuxInfo(virt_ptr colorBuffer, virt_ptr outSize, virt_ptr outAlignment, bool updateRegisters) { // TODO: Implement once we have AddrComputeCmaskInfo, AddrComputeFmaskInfo if (outSize) { *outSize = 2048u; } if (outAlignment) { *outAlignment = 2048u; } } void GX2CalcSurfaceSizeAndAlignment(virt_ptr surface) { ADDR_COMPUTE_SURFACE_INFO_OUTPUT output; auto isDepthBuffer = !!(surface->use & GX2SurfaceUse::DepthBuffer); auto isColorBuffer = !!(surface->use & GX2SurfaceUse::ColorBuffer); auto tileModeChanged = false; auto isBuggedTilemode = (surface->tileMode == GX2TileMode::DefaultBadAlign); if (surface->tileMode == GX2TileMode::Default || surface->tileMode == GX2TileMode::DefaultBadAlign) { if (surface->dim || surface->aa || isDepthBuffer) { if (surface->dim != GX2SurfaceDim::Texture3D || isColorBuffer) { surface->tileMode = GX2TileMode::Tiled2DThin1; } else { surface->tileMode = GX2TileMode::Tiled2DThick; } tileModeChanged = true; } else { surface->tileMode = GX2TileMode::LinearAligned; } } surface->mipLevels = std::max(surface->mipLevels, 1u); surface->mipLevels = std::min(surface->mipLevels, calcNumLevels(surface)); surface->mipLevelOffset[0] = 0u; surface->swizzle &= 0xFF00FFFF; if (surface->tileMode >= GX2TileMode::Tiled2DThin1 && surface->tileMode != GX2TileMode::LinearSpecial) { surface->swizzle |= 0xD0000; } auto buggedAlignShiftFix = 0u; if (isBuggedTilemode && GX2SurfaceIsCompressed(surface->format)) { buggedAlignShiftFix = 2u; } auto lastTileMode = static_cast(surface->tileMode); auto prevSize = 0u; auto offset0 = 0u; for (auto level = 0u; level < surface->mipLevels; ++level) { internal::getSurfaceInfo(surface.get(), level, &output); if (level) { auto pad = 0u; if (lastTileMode >= GX2TileMode::Tiled2DThin1 && lastTileMode != GX2TileMode::LinearSpecial) { if (output.tileMode < ADDR_TM_2D_TILED_THIN1) { surface->swizzle = (level << 16) | (surface->swizzle & 0xFF00FFFF); lastTileMode = static_cast(output.tileMode); if (level > 1) { pad = surface->swizzle & 0xFFFF; } } } pad += (output.baseAlign - (prevSize % output.baseAlign)) % output.baseAlign; if (level == 1) { offset0 = pad + prevSize; } else { surface->mipLevelOffset[level - 1] = pad + prevSize + surface->mipLevelOffset[level - 2]; } } else { if (tileModeChanged) { if (surface->tileMode != output.tileMode) { surface->tileMode = static_cast(output.tileMode); internal::getSurfaceInfo(surface.get(), level, &output); if (surface->tileMode < GX2TileMode::Tiled2DThin1 || surface->tileMode == GX2TileMode::LinearSpecial) { surface->swizzle &= 0xFF00FFFF; } lastTileMode = surface->tileMode; } if (surface->width < (output.pitchAlign << buggedAlignShiftFix) && surface->height < (output.heightAlign << buggedAlignShiftFix)) { if (surface->tileMode == GX2TileMode::Tiled2DThick) { surface->tileMode = GX2TileMode::Tiled1DThick; } else { surface->tileMode = GX2TileMode::Tiled1DThin1; } internal::getSurfaceInfo(surface.get(), level, &output); surface->swizzle &= 0xFF00FFFF; lastTileMode = surface->tileMode; } } surface->imageSize = static_cast(output.surfSize); surface->alignment = output.baseAlign; surface->pitch = output.pitch; } prevSize = static_cast(output.surfSize); } if (surface->mipLevels <= 1) { surface->mipmapSize = 0u; } else { surface->mipmapSize = prevSize + surface->mipLevelOffset[surface->mipLevels - 2]; } surface->mipLevelOffset[0] = offset0; if (surface->format == GX2SurfaceFormat::UNORM_NV12) { auto pad = (surface->alignment - surface->imageSize % surface->alignment) % surface->alignment; surface->mipLevelOffset[0] = pad + surface->imageSize; surface->imageSize = surface->mipLevelOffset[0] + (surface->imageSize >> 1); } } void GX2CalcDepthBufferHiZInfo(virt_ptr depthBuffer, virt_ptr outSize, virt_ptr outAlignment) { ADDR_COMPUTE_HTILE_INFO_INPUT input; ADDR_COMPUTE_HTILE_INFO_OUTPUT output; std::memset(&input, 0, sizeof(ADDR_COMPUTE_HTILE_INFO_INPUT)); std::memset(&output, 0, sizeof(ADDR_COMPUTE_HTILE_INFO_OUTPUT)); input.size = sizeof(ADDR_COMPUTE_HTILE_INFO_INPUT); output.size = sizeof(ADDR_COMPUTE_HTILE_INFO_OUTPUT); input.pitch = depthBuffer->surface.pitch; input.height = depthBuffer->surface.height; input.numSlices = depthBuffer->surface.depth; input.blockWidth = ADDR_HTILE_BLOCKSIZE_8; input.blockHeight = ADDR_HTILE_BLOCKSIZE_8; AddrComputeHtileInfo(gpu::getAddrLibHandle(), &input, &output); depthBuffer->hiZSize = gsl::narrow_cast(output.htileBytes); if (outSize) { *outSize = depthBuffer->hiZSize; } if (outAlignment) { *outAlignment = output.baseAlign; } } void GX2CalcColorBufferAuxInfo(virt_ptr colorBuffer, virt_ptr outSize, virt_ptr outAlignment) { decaf_warn_stub(); computeAuxInfo(colorBuffer, outSize, outAlignment, false); colorBuffer->aaSize = *outSize; } void GX2SetColorBuffer(virt_ptr colorBuffer, GX2RenderTarget target) { using latte::Register; auto reg = [](unsigned id) { return static_cast(id); }; auto cb_color_info = colorBuffer->regs.cb_color_info.value(); auto cb_color_mask = colorBuffer->regs.cb_color_mask.value(); auto cb_color_size = colorBuffer->regs.cb_color_size.value(); auto cb_color_view = colorBuffer->regs.cb_color_view.value(); auto addr = static_cast( OSEffectiveToPhysical(virt_cast(colorBuffer->surface.image))); auto addrTile = 0u; auto addrFrag = 0u; if (colorBuffer->viewMip) { addr = static_cast( OSEffectiveToPhysical(virt_cast(colorBuffer->surface.mipmaps))); if (colorBuffer->viewMip > 1) { addr += colorBuffer->surface.mipLevelOffset[colorBuffer->viewMip - 1]; } } if (colorBuffer->surface.tileMode >= GX2TileMode::Tiled2DThin1 && colorBuffer->surface.tileMode != GX2TileMode::LinearSpecial) { if (colorBuffer->viewMip < ((colorBuffer->surface.swizzle >> 16) & 0xFF)) { addr ^= colorBuffer->surface.swizzle & 0xFFFF; } } if (colorBuffer->surface.aa) { addrFrag = static_cast( OSEffectiveToPhysical(virt_cast(colorBuffer->aaBuffer))); addrTile = addrFrag + colorBuffer->regs.cmask_offset; } internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_BASE + target * 4), addr >> 8 }); internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_SIZE + target * 4), cb_color_size.value }); internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_INFO + target * 4), cb_color_info.value }); internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_TILE + target * 4), addrTile >> 8 }); internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_FRAG + target * 4), addrFrag >> 8 }); internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_VIEW + target * 4), cb_color_view.value }); internal::writePM4(latte::pm4::SetContextReg { reg(Register::CB_COLOR0_MASK + target * 4), cb_color_mask.value }); } void GX2SetDepthBuffer(virt_ptr depthBuffer) { auto db_depth_info = depthBuffer->regs.db_depth_info.value(); auto db_depth_size = depthBuffer->regs.db_depth_size.value(); auto db_depth_view = depthBuffer->regs.db_depth_view.value(); auto db_htile_surface = depthBuffer->regs.db_htile_surface.value(); auto db_prefetch_limit = depthBuffer->regs.db_prefetch_limit.value(); auto db_preload_control = depthBuffer->regs.db_preload_control.value(); auto pa_poly_offset_cntl = depthBuffer->regs.pa_poly_offset_cntl.value(); uint32_t values1[] = { db_depth_size.value, db_depth_view.value, }; internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_DEPTH_SIZE, gsl::make_span(values1) }); auto addr = OSEffectiveToPhysical(virt_cast(depthBuffer->surface.image)); auto addrHiZ = OSEffectiveToPhysical(virt_cast(depthBuffer->hiZPtr)); if (depthBuffer->viewMip) { addr = OSEffectiveToPhysical(virt_cast(depthBuffer->surface.mipmaps)); if (depthBuffer->viewMip > 1) { addr += depthBuffer->surface.mipLevelOffset[depthBuffer->viewMip - 1]; } } if (depthBuffer->surface.tileMode >= GX2TileMode::Tiled2DThin1 && depthBuffer->surface.tileMode != GX2TileMode::LinearSpecial) { if (depthBuffer->viewMip < ((depthBuffer->surface.swizzle >> 16) & 0xFF)) { addr ^= depthBuffer->surface.swizzle & 0xFFFF; } } uint32_t values2[] = { addr >> 8, db_depth_info.value, addrHiZ >> 8, }; internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_DEPTH_BASE, gsl::make_span(values2) }); internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_HTILE_SURFACE, db_htile_surface.value }); internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_PREFETCH_LIMIT, db_prefetch_limit.value }); internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_PRELOAD_CONTROL, db_preload_control.value }); internal::writePM4(latte::pm4::SetContextReg { latte::Register::PA_SU_POLY_OFFSET_DB_FMT_CNTL, pa_poly_offset_cntl.value }); uint32_t values3[] = { depthBuffer->stencilClear, bit_cast(depthBuffer->depthClear), }; internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_STENCIL_CLEAR, gsl::make_span(values3) }); } void GX2InitColorBufferRegs(virt_ptr colorBuffer) { auto surfaceFormat = colorBuffer->surface.format; auto surfaceFormatType = internal::getSurfaceFormatType(surfaceFormat); auto output = ADDR_COMPUTE_SURFACE_INFO_OUTPUT { }; internal::getSurfaceInfo(virt_addrof(colorBuffer->surface).get(), colorBuffer->viewMip, &output); // cb_color_size auto pitchTileMax = (output.pitch / latte::MicroTileWidth) - 1; auto sliceTileMax = ((output.pitch * output.height) / (latte::MicroTileWidth * latte::MicroTileHeight)) - 1; colorBuffer->regs.cb_color_size = latte::CB_COLORN_SIZE::get(0) .PITCH_TILE_MAX(pitchTileMax) .SLICE_TILE_MAX(sliceTileMax); // cb_color_info auto cbFormat = internal::getSurfaceFormatColorFormat(colorBuffer->surface.format); auto cbCompSwap = latte::CB_COMP_SWAP::STD; if (cbFormat == latte::CB_FORMAT::COLOR_5_5_5_1 || cbFormat == latte::CB_FORMAT::COLOR_10_10_10_2) { cbCompSwap = latte::CB_COMP_SWAP::STD_REV; } auto cbNumberType = internal::getSurfaceFormatColorNumberType(surfaceFormat); auto cbTileMode = latte::CB_TILE_MODE::DISABLE; if (colorBuffer->surface.aa) { cbTileMode = latte::CB_TILE_MODE::FRAG_ENABLE; } auto blendBypass = false; if (surfaceFormatType == GX2SurfaceFormatType::UINT || surfaceFormat == GX2SurfaceFormat::UNORM_R24_X8 || surfaceFormat == GX2SurfaceFormat::FLOAT_D24_S8 || surfaceFormat == GX2SurfaceFormat::FLOAT_X8_X24) { blendBypass = true; } auto cbRoundMode = latte::CB_ROUND_MODE::BY_HALF; if (surfaceFormatType == GX2SurfaceFormatType::FLOAT) { cbRoundMode = latte::CB_ROUND_MODE::TRUNCATE; } auto cbSourceFormat = internal::getSurfaceFormatColorSourceFormat(surfaceFormat); if (surfaceFormatType == GX2SurfaceFormatType::UINT) { cbSourceFormat = latte::CB_SOURCE_FORMAT::EXPORT_NORM; } colorBuffer->regs.cb_color_info = latte::CB_COLORN_INFO::get(0) .ENDIAN(internal::getSurfaceFormatColorEndian(surfaceFormat)) .FORMAT(cbFormat) .NUMBER_TYPE(cbNumberType) .ARRAY_MODE(static_cast(output.tileMode)) .COMP_SWAP(cbCompSwap) .TILE_MODE(cbTileMode) .BLEND_BYPASS(blendBypass) .ROUND_MODE(cbRoundMode) .SOURCE_FORMAT(cbSourceFormat); colorBuffer->regs.cb_color_mask = latte::CB_COLORN_MASK::get(0); if (colorBuffer->surface.tileMode == GX2TileMode::LinearSpecial) { colorBuffer->regs.cb_color_view = latte::CB_COLORN_VIEW::get(0); } else { colorBuffer->regs.cb_color_view = latte::CB_COLORN_VIEW::get(0) .SLICE_START(colorBuffer->viewFirstSlice) .SLICE_MAX(colorBuffer->viewFirstSlice + colorBuffer->viewNumSlices - 1); } if (colorBuffer->surface.aa) { computeAuxInfo(colorBuffer, nullptr, nullptr, true); } } void GX2InitDepthBufferRegs(virt_ptr depthBuffer) { ADDR_COMPUTE_SURFACE_INFO_OUTPUT surfaceInfo; internal::getSurfaceInfo(virt_addrof(depthBuffer->surface).get(), depthBuffer->viewMip, &surfaceInfo); // db_depth_size auto pitchTileMax = (surfaceInfo.pitch / latte::MicroTileWidth) - 1; auto sliceTileMax = ((surfaceInfo.pitch * surfaceInfo.height) / (latte::MicroTileWidth * latte::MicroTileHeight)) - 1; depthBuffer->regs.db_depth_size = latte::DB_DEPTH_SIZE::get(0) .PITCH_TILE_MAX(pitchTileMax) .SLICE_TILE_MAX(sliceTileMax); // db_depth_view depthBuffer->regs.db_depth_view = latte::DB_DEPTH_VIEW::get(0) .SLICE_START(depthBuffer->viewFirstSlice) .SLICE_MAX(depthBuffer->viewFirstSlice + depthBuffer->viewNumSlices - 1); depthBuffer->regs.db_htile_surface = latte::DB_HTILE_SURFACE::get(0) .HTILE_WIDTH(true) .HTILE_HEIGHT(true) .FULL_CACHE(true); depthBuffer->regs.db_prefetch_limit = latte::DB_PREFETCH_LIMIT::get(0) .DEPTH_HEIGHT_TILE_MAX(((depthBuffer->surface.height / 8) - 1) & 0x3FF); depthBuffer->regs.db_preload_control = latte::DB_PRELOAD_CONTROL::get(0) .MAX_X(static_cast(depthBuffer->surface.width / 32)) .MAX_Y(static_cast(depthBuffer->surface.height / 32)); auto db_depth_info = latte::DB_DEPTH_INFO::get(0) .READ_SIZE(latte::BUFFER_READ_SIZE::READ_512_BITS) .ARRAY_MODE(static_cast(surfaceInfo.tileMode)) .TILE_SURFACE_ENABLE(depthBuffer->hiZPtr != nullptr); auto pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0); switch (depthBuffer->surface.format) { case GX2SurfaceFormat::UNORM_R16: db_depth_info = db_depth_info .FORMAT(latte::DB_FORMAT::DEPTH_16); pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0) .POLY_OFFSET_NEG_NUM_DB_BITS(240); break; case GX2SurfaceFormat::UNORM_R24_X8: db_depth_info = db_depth_info .FORMAT(latte::DB_FORMAT::DEPTH_8_24); pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0) .POLY_OFFSET_NEG_NUM_DB_BITS(232); break; case GX2SurfaceFormat::FLOAT_R32: db_depth_info = db_depth_info .FORMAT(latte::DB_FORMAT::DEPTH_32_FLOAT); pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0) .POLY_OFFSET_NEG_NUM_DB_BITS(233) .POLY_OFFSET_DB_IS_FLOAT_FMT(true); break; case GX2SurfaceFormat::FLOAT_D24_S8: db_depth_info = db_depth_info .FORMAT(latte::DB_FORMAT::DEPTH_8_24_FLOAT); pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0) .POLY_OFFSET_NEG_NUM_DB_BITS(236) .POLY_OFFSET_DB_IS_FLOAT_FMT(true); break; case GX2SurfaceFormat::FLOAT_X8_X24: db_depth_info = db_depth_info .FORMAT(latte::DB_FORMAT::DEPTH_X24_8_32_FLOAT); pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0) .POLY_OFFSET_NEG_NUM_DB_BITS(233) .POLY_OFFSET_DB_IS_FLOAT_FMT(true); break; default: db_depth_info = db_depth_info .FORMAT(latte::DB_FORMAT::DEPTH_INVALID); } depthBuffer->regs.db_depth_info = db_depth_info; depthBuffer->regs.pa_poly_offset_cntl = pa_poly_offset_cntl; } void GX2InitDepthBufferHiZEnable(virt_ptr depthBuffer, BOOL enable) { auto db_depth_info = depthBuffer->regs.db_depth_info.value(); db_depth_info = db_depth_info .TILE_SURFACE_ENABLE(!!enable); depthBuffer->regs.db_depth_info = db_depth_info; } uint32_t GX2GetSurfaceSwizzle(virt_ptr surface) { return (surface->swizzle >> 8) & 0xff; } uint32_t GX2GetSurfaceSwizzleOffset(virt_ptr surface, uint32_t level) { if (surface->tileMode < GX2TileMode::Tiled2DThin1 || surface->tileMode == GX2TileMode::LinearSpecial) { return 0; } if (level < ((surface->swizzle >> 16) & 0xFF)) { return 0; } return surface->swizzle & 0xFFFF; } void GX2SetSurfaceSwizzle(virt_ptr surface, uint32_t swizzle) { surface->swizzle &= 0xFFFF00FF; surface->swizzle |= swizzle << 8; } uint32_t GX2GetSurfaceMipPitch(virt_ptr surface, uint32_t level) { ADDR_COMPUTE_SURFACE_INFO_OUTPUT info; internal::getSurfaceInfo(surface.get(), level, &info); return info.pitch; } uint32_t GX2GetSurfaceMipSliceSize(virt_ptr surface, uint32_t level) { ADDR_COMPUTE_SURFACE_INFO_OUTPUT info; internal::getSurfaceInfo(surface.get(), level, &info); return internal::calcSliceSize(surface.get(), &info); } void GX2CopySurface(virt_ptr src, uint32_t srcLevel, uint32_t srcSlice, virt_ptr dst, uint32_t dstLevel, uint32_t dstSlice) { if (src->format == GX2SurfaceFormat::INVALID || src->width == 0 || src->height == 0) { return; } if (dst->format == GX2SurfaceFormat::INVALID) { return; } if (src->tileMode == GX2TileMode::LinearSpecial || dst->tileMode == GX2TileMode::LinearSpecial) { // LinearSpecial surfaces cause the copy to occur on the CPU. This code // assumes that if the texture was previously written by the GPU, that it // has since been invalidated into CPU memory. gx2::internal::copySurface(src.get(), srcLevel, srcSlice, dst.get(), dstLevel, dstSlice); return; } auto dstTileType = latte::SQ_TILE_TYPE::DEFAULT; if (dst->use & GX2SurfaceUse::DepthBuffer) { dstTileType = latte::SQ_TILE_TYPE::DEPTH; } auto dstDim = static_cast(dst->dim.value()); auto dstFormat = static_cast(dst->format & 0x3f); auto dstTileMode = static_cast(dst->tileMode.value()); auto dstFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED; auto dstNumFormat = latte::SQ_NUM_FORMAT::NORM; auto dstForceDegamma = false; auto dstPitch = dst->pitch; auto dstDepth = dst->depth; auto dstSamples = 0u; if (dst->format & GX2AttribFormatFlags::SIGNED) { dstFormatComp = latte::SQ_FORMAT_COMP::SIGNED; } if (dst->format & GX2AttribFormatFlags::SCALED) { dstNumFormat = latte::SQ_NUM_FORMAT::SCALED; } else if (dst->format & GX2AttribFormatFlags::INTEGER) { dstNumFormat = latte::SQ_NUM_FORMAT::INT; } if (dst->format & GX2AttribFormatFlags::DEGAMMA) { dstForceDegamma = true; } if (dstFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dstFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { dstPitch *= 4; } if (dstDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) { dstDepth /= 6; } if (dst->aa == GX2AAMode::Mode2X) { dstSamples = 2; } else if (dst->aa == GX2AAMode::Mode4X) { dstSamples = 4; } else if (dst->aa == GX2AAMode::Mode8X) { dstSamples = 8; } auto srcTileType = latte::SQ_TILE_TYPE::DEFAULT; if (src->use & GX2SurfaceUse::DepthBuffer) { srcTileType = latte::SQ_TILE_TYPE::DEPTH; } auto srcDim = static_cast(src->dim.value()); auto srcFormat = static_cast(src->format & 0x3f); auto srcTileMode = static_cast(src->tileMode.value()); auto srcFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED; auto srcNumFormat = latte::SQ_NUM_FORMAT::NORM; auto srcForceDegamma = false; auto srcPitch = src->pitch; auto srcDepth = src->depth; auto srcSamples = 0u; if (src->format & GX2AttribFormatFlags::SIGNED) { srcFormatComp = latte::SQ_FORMAT_COMP::SIGNED; } if (src->format & GX2AttribFormatFlags::SCALED) { srcNumFormat = latte::SQ_NUM_FORMAT::SCALED; } else if (src->format & GX2AttribFormatFlags::INTEGER) { srcNumFormat = latte::SQ_NUM_FORMAT::INT; } if (src->format & GX2AttribFormatFlags::DEGAMMA) { srcForceDegamma = true; } if (srcFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && srcFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { srcPitch *= 4; } if (srcDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) { srcDepth /= 6; } if (src->aa == GX2AAMode::Mode2X) { srcSamples = 2; } else if (src->aa == GX2AAMode::Mode4X) { srcSamples = 4; } else if (src->aa == GX2AAMode::Mode8X) { srcSamples = 8; } internal::writePM4(latte::pm4::DecafCopySurface { OSEffectiveToPhysical(virt_cast(dst->image)), OSEffectiveToPhysical(virt_cast(dst->mipmaps)), dstLevel, dstSlice, dstPitch, dst->width, dst->height, dstDepth, dstSamples, dstDim, dstFormat, dstNumFormat, dstFormatComp, dstForceDegamma ? 1u : 0u, dstTileType, dstTileMode, OSEffectiveToPhysical(virt_cast(src->image)), OSEffectiveToPhysical(virt_cast(src->mipmaps)), srcLevel, srcSlice, srcPitch, src->width, src->height, srcDepth, srcSamples, srcDim, srcFormat, srcNumFormat, srcFormatComp, srcForceDegamma ? 1u : 0u, srcTileType, srcTileMode }); } void GX2ExpandColorBuffer(virt_ptr buffer) { // TODO: GX2ExpandColorBuffer decaf_warn_stub(); } void GX2ExpandDepthBuffer(virt_ptr buffer) { // TODO: GX2ExpandDepthBuffer decaf_warn_stub(); } void GX2ResolveAAColorBuffer(virt_ptr src, virt_ptr dst, uint32_t dstLevel, uint32_t dstSlice) { if (src->surface.format == GX2SurfaceFormat::INVALID || src->surface.width == 0 || src->surface.height == 0) { return; } if (dst->format == GX2SurfaceFormat::INVALID) { return; } auto dstTileType = latte::SQ_TILE_TYPE::DEFAULT; auto dstDim = static_cast(dst->dim.value()); auto dstFormat = static_cast(dst->format & 0x3f); auto dstTileMode = static_cast(dst->tileMode.value()); auto dstFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED; auto dstNumFormat = latte::SQ_NUM_FORMAT::NORM; auto dstForceDegamma = false; auto dstPitch = dst->pitch; auto dstDepth = dst->depth; auto dstSamples = 0u; if (dst->format & GX2AttribFormatFlags::SIGNED) { dstFormatComp = latte::SQ_FORMAT_COMP::SIGNED; } if (dst->format & GX2AttribFormatFlags::SCALED) { dstNumFormat = latte::SQ_NUM_FORMAT::SCALED; } else if (dst->format & GX2AttribFormatFlags::INTEGER) { dstNumFormat = latte::SQ_NUM_FORMAT::INT; } if (dst->format & GX2AttribFormatFlags::DEGAMMA) { dstForceDegamma = true; } if (dstFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dstFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { dstPitch *= 4; } if (dstDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) { dstDepth /= 6; } if (dst->aa == GX2AAMode::Mode2X) { dstSamples = 2; } else if (dst->aa == GX2AAMode::Mode4X) { dstSamples = 4; } else if (dst->aa == GX2AAMode::Mode8X) { dstSamples = 8; } auto srcTileType = latte::SQ_TILE_TYPE::DEFAULT; auto srcDim = static_cast(src->surface.dim.value()); auto srcFormat = static_cast(src->surface.format & 0x3f); auto srcTileMode = static_cast(src->surface.tileMode.value()); auto srcFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED; auto srcNumFormat = latte::SQ_NUM_FORMAT::NORM; auto srcForceDegamma = false; auto srcPitch = src->surface.pitch; auto srcDepth = src->surface.depth; auto srcSamples = 0u; if (src->surface.format & GX2AttribFormatFlags::SIGNED) { srcFormatComp = latte::SQ_FORMAT_COMP::SIGNED; } if (src->surface.format & GX2AttribFormatFlags::SCALED) { srcNumFormat = latte::SQ_NUM_FORMAT::SCALED; } else if (src->surface.format & GX2AttribFormatFlags::INTEGER) { srcNumFormat = latte::SQ_NUM_FORMAT::INT; } if (src->surface.format & GX2AttribFormatFlags::DEGAMMA) { srcForceDegamma = true; } if (srcFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && srcFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { srcPitch *= 4; } if (srcDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) { srcDepth /= 6; } if (src->surface.aa == GX2AAMode::Mode2X) { srcSamples = 2; } else if (src->surface.aa == GX2AAMode::Mode4X) { srcSamples = 4; } else if (src->surface.aa == GX2AAMode::Mode8X) { srcSamples = 8; } internal::writePM4(latte::pm4::DecafExpandColorBuffer { OSEffectiveToPhysical(virt_cast(dst->image)), OSEffectiveToPhysical(virt_cast(dst->mipmaps)), dstLevel, dstSlice, dstPitch, dst->width, dst->height, dstDepth, dstSamples, dstDim, dstFormat, dstNumFormat, dstFormatComp, dstForceDegamma ? 1u : 0u, dstTileType, dstTileMode, OSEffectiveToPhysical(virt_cast(src->surface.image)), OSEffectiveToPhysical(virt_cast(src->aaBuffer)), OSEffectiveToPhysical(virt_cast(src->surface.mipmaps)), src->viewMip, src->viewFirstSlice, srcPitch, src->surface.width, src->surface.height, srcDepth, srcSamples, srcDim, srcFormat, srcNumFormat, srcFormatComp, srcForceDegamma ? 1u : 0u, srcTileType, srcTileMode, src->viewNumSlices }); } void Library::registerSurfaceSymbols() { RegisterFunctionExport(GX2InitDepthBufferHiZEnable); RegisterFunctionExport(GX2InitDepthBufferRegs); RegisterFunctionExport(GX2InitColorBufferRegs); RegisterFunctionExport(GX2SetDepthBuffer); RegisterFunctionExport(GX2SetColorBuffer); RegisterFunctionExport(GX2CalcColorBufferAuxInfo); RegisterFunctionExport(GX2CalcDepthBufferHiZInfo); RegisterFunctionExport(GX2CalcSurfaceSizeAndAlignment); RegisterFunctionExport(GX2GetSurfaceSwizzle); RegisterFunctionExport(GX2GetSurfaceSwizzleOffset); RegisterFunctionExport(GX2SetSurfaceSwizzle); RegisterFunctionExport(GX2GetSurfaceMipPitch); RegisterFunctionExport(GX2GetSurfaceMipSliceSize); RegisterFunctionExport(GX2CopySurface); RegisterFunctionExport(GX2ExpandColorBuffer); RegisterFunctionExport(GX2ExpandDepthBuffer); RegisterFunctionExport(GX2ResolveAAColorBuffer); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_surface.h ================================================ #pragma once #include "gx2_enum.h" #include #include namespace cafe::gx2 { /** * \defgroup gx2_surface Surface * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2Surface { be2_val dim; be2_val width; be2_val height; be2_val depth; be2_val mipLevels; be2_val format; be2_val aa; union { be2_val use; be2_val resourceFlags; }; be2_val imageSize; be2_virt_ptr image; be2_val mipmapSize; be2_virt_ptr mipmaps; be2_val tileMode; be2_val swizzle; be2_val alignment; be2_val pitch; be2_array mipLevelOffset; }; CHECK_OFFSET(GX2Surface, 0x00, dim); CHECK_OFFSET(GX2Surface, 0x04, width); CHECK_OFFSET(GX2Surface, 0x08, height); CHECK_OFFSET(GX2Surface, 0x0C, depth); CHECK_OFFSET(GX2Surface, 0x10, mipLevels); CHECK_OFFSET(GX2Surface, 0x14, format); CHECK_OFFSET(GX2Surface, 0x18, aa); CHECK_OFFSET(GX2Surface, 0x1C, use); CHECK_OFFSET(GX2Surface, 0x1C, resourceFlags); CHECK_OFFSET(GX2Surface, 0x20, imageSize); CHECK_OFFSET(GX2Surface, 0x24, image); CHECK_OFFSET(GX2Surface, 0x28, mipmapSize); CHECK_OFFSET(GX2Surface, 0x2C, mipmaps); CHECK_OFFSET(GX2Surface, 0x30, tileMode); CHECK_OFFSET(GX2Surface, 0x34, swizzle); CHECK_OFFSET(GX2Surface, 0x38, alignment); CHECK_OFFSET(GX2Surface, 0x3C, pitch); CHECK_OFFSET(GX2Surface, 0x40, mipLevelOffset); CHECK_SIZE(GX2Surface, 0x74); struct GX2DepthBuffer { be2_struct surface; be2_val viewMip; be2_val viewFirstSlice; be2_val viewNumSlices; be2_virt_ptr hiZPtr; be2_val hiZSize; be2_val depthClear; be2_val stencilClear; struct { be2_val db_depth_size; be2_val db_depth_view; be2_val db_depth_info; be2_val db_htile_surface; be2_val db_prefetch_limit; be2_val db_preload_control; be2_val pa_poly_offset_cntl; } regs; }; CHECK_OFFSET(GX2DepthBuffer, 0x00, surface); CHECK_OFFSET(GX2DepthBuffer, 0x74, viewMip); CHECK_OFFSET(GX2DepthBuffer, 0x78, viewFirstSlice); CHECK_OFFSET(GX2DepthBuffer, 0x7C, viewNumSlices); CHECK_OFFSET(GX2DepthBuffer, 0x80, hiZPtr); CHECK_OFFSET(GX2DepthBuffer, 0x84, hiZSize); CHECK_OFFSET(GX2DepthBuffer, 0x88, depthClear); CHECK_OFFSET(GX2DepthBuffer, 0x8C, stencilClear); CHECK_OFFSET(GX2DepthBuffer, 0x90, regs.db_depth_size); CHECK_OFFSET(GX2DepthBuffer, 0x94, regs.db_depth_view); CHECK_OFFSET(GX2DepthBuffer, 0x98, regs.db_depth_info); CHECK_OFFSET(GX2DepthBuffer, 0x9C, regs.db_htile_surface); CHECK_OFFSET(GX2DepthBuffer, 0xA0, regs.db_prefetch_limit); CHECK_OFFSET(GX2DepthBuffer, 0xA4, regs.db_preload_control); CHECK_OFFSET(GX2DepthBuffer, 0xA8, regs.pa_poly_offset_cntl); CHECK_SIZE(GX2DepthBuffer, 0xAC); struct GX2ColorBuffer { be2_struct surface; be2_val viewMip; be2_val viewFirstSlice; be2_val viewNumSlices; be2_virt_ptr aaBuffer; be2_val aaSize; struct { be2_val cb_color_size; be2_val cb_color_info; be2_val cb_color_view; be2_val cb_color_mask; be2_val cmask_offset; } regs; }; CHECK_OFFSET(GX2ColorBuffer, 0x00, surface); CHECK_OFFSET(GX2ColorBuffer, 0x74, viewMip); CHECK_OFFSET(GX2ColorBuffer, 0x78, viewFirstSlice); CHECK_OFFSET(GX2ColorBuffer, 0x7C, viewNumSlices); CHECK_OFFSET(GX2ColorBuffer, 0x80, aaBuffer); CHECK_OFFSET(GX2ColorBuffer, 0x84, aaSize); CHECK_OFFSET(GX2ColorBuffer, 0x88, regs.cb_color_size); CHECK_OFFSET(GX2ColorBuffer, 0x8C, regs.cb_color_info); CHECK_OFFSET(GX2ColorBuffer, 0x90, regs.cb_color_view); CHECK_OFFSET(GX2ColorBuffer, 0x94, regs.cb_color_mask); CHECK_OFFSET(GX2ColorBuffer, 0x98, regs.cmask_offset); CHECK_SIZE(GX2ColorBuffer, 0x9C); #pragma pack(pop) void GX2CalcSurfaceSizeAndAlignment(virt_ptr surface); void GX2CalcDepthBufferHiZInfo(virt_ptr depthBuffer, virt_ptr outSize, virt_ptr outAlignment); void GX2CalcColorBufferAuxInfo(virt_ptr colorBuffer, virt_ptr outSize, virt_ptr outAlignment); void GX2SetColorBuffer(virt_ptr colorBuffer, GX2RenderTarget target); void GX2SetDepthBuffer(virt_ptr depthBuffer); void GX2InitColorBufferRegs(virt_ptr colorBuffer); void GX2InitDepthBufferRegs(virt_ptr depthBuffer); void GX2InitDepthBufferHiZEnable(virt_ptr depthBuffer, BOOL enable); uint32_t GX2GetSurfaceSwizzle(virt_ptr surface); uint32_t GX2GetSurfaceSwizzleOffset(virt_ptr surface, uint32_t level); void GX2SetSurfaceSwizzle(virt_ptr surface, uint32_t swizzle); uint32_t GX2GetSurfaceMipPitch(virt_ptr surface, uint32_t level); uint32_t GX2GetSurfaceMipSliceSize(virt_ptr surface, uint32_t level); void GX2CopySurface(virt_ptr src, uint32_t srcLevel, uint32_t srcSlice, virt_ptr dst, uint32_t dstLevel, uint32_t dstSlice); void GX2ExpandDepthBuffer(virt_ptr buffer); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_temp.cpp ================================================ #include "gx2.h" #include "gx2_temp.h" namespace cafe::gx2 { uint32_t GX2TempGetGPUVersion() { return 2; } void Library::registerTempSymbols() { RegisterFunctionExport(GX2TempGetGPUVersion); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_temp.h ================================================ #pragma once #include namespace cafe::gx2 { uint32_t GX2TempGetGPUVersion(); } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_tessellation.cpp ================================================ #include "gx2.h" #include "gx2_cbpool.h" #include "gx2_tessellation.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::gx2 { void GX2SetTessellation(GX2TessellationMode tessellationMode, GX2PrimitiveMode primitiveMode, GX2IndexType indexType) { decaf_warn_stub(); // TODO: Set registers 0xA285, 0xA289, 0xA28A, 0xA28B, 0xA28C, 0xA28E, 0xA28D, 0xA28F } void GX2SetMinTessellationLevel(float min) { auto vgt_hos_min_tess_level = latte::VGT_HOS_MIN_TESS_LEVEL::get(0); vgt_hos_min_tess_level = vgt_hos_min_tess_level .MIN_TESS(min); internal::writePM4(latte::pm4::SetContextReg { latte::Register::VGT_HOS_MIN_TESS_LEVEL, vgt_hos_min_tess_level.value }); } void GX2SetMaxTessellationLevel(float max) { auto vgt_hos_max_tess_level = latte::VGT_HOS_MAX_TESS_LEVEL::get(0); vgt_hos_max_tess_level = vgt_hos_max_tess_level .MAX_TESS(max); internal::writePM4(latte::pm4::SetContextReg { latte::Register::VGT_HOS_MAX_TESS_LEVEL, vgt_hos_max_tess_level.value }); } void Library::registerTessellationSymbols() { RegisterFunctionExport(GX2SetTessellation); RegisterFunctionExport(GX2SetMinTessellationLevel); RegisterFunctionExport(GX2SetMaxTessellationLevel); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_tessellation.h ================================================ #pragma once #include "gx2_enum.h" namespace cafe::gx2 { void GX2SetTessellation(GX2TessellationMode tessellationMode, GX2PrimitiveMode primitiveMode, GX2IndexType indexType); void GX2SetMinTessellationLevel(float min); void GX2SetMaxTessellationLevel(float max); } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_texture.cpp ================================================ #include "gx2.h" #include "gx2_debug.h" #include "gx2_format.h" #include "gx2_cbpool.h" #include "gx2_texture.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include #include namespace cafe::gx2 { using namespace cafe::coreinit; void GX2InitTextureRegs(virt_ptr texture) { auto word0 = latte::SQ_TEX_RESOURCE_WORD0_N::get(0); auto word1 = latte::SQ_TEX_RESOURCE_WORD1_N::get(0); auto word4 = latte::SQ_TEX_RESOURCE_WORD4_N::get(0); auto word5 = texture->regs.word5.value(); auto word6 = texture->regs.word6.value(); // Minimum values if (!texture->viewNumMips) { texture->viewNumMips = 1u; } if (!texture->viewNumSlices) { texture->viewNumSlices = 1u; } if (!texture->surface.width) { texture->surface.width = 1u; } if (!texture->surface.height) { texture->surface.height = 1u; } if (!texture->surface.depth) { texture->surface.depth = 1u; } if (!texture->surface.mipLevels) { texture->surface.mipLevels = 1u; } // Word 0 auto tileType = 0; auto format = static_cast(texture->surface.format & 0x3F); auto pitch = texture->surface.pitch; if (texture->surface.use & GX2SurfaceUse::DepthBuffer) { tileType = 1; } if (format >= latte::SQ_DATA_FORMAT::FMT_BC1 && format <= latte::SQ_DATA_FORMAT::FMT_BC5) { pitch *= 4; } pitch = std::max(pitch, 8u); word0 = word0 .DIM(static_cast(texture->surface.dim & 0x7)) .TILE_MODE(static_cast(texture->surface.tileMode.value())) .TILE_TYPE(static_cast(tileType)) .PITCH((pitch / 8) - 1) .TEX_WIDTH(texture->surface.width - 1); // Word 1 auto depth = 0u; if (texture->surface.dim == GX2SurfaceDim::TextureCube) { depth = (texture->surface.depth / 6) - 1; } else if (texture->surface.dim == GX2SurfaceDim::Texture3D || texture->surface.dim == GX2SurfaceDim::Texture2DMSAAArray || texture->surface.dim == GX2SurfaceDim::Texture2DArray || texture->surface.dim == GX2SurfaceDim::Texture1DArray) { depth = texture->surface.depth - 1; } word1 = word1 .TEX_HEIGHT(texture->surface.height - 1) .TEX_DEPTH(depth) .DATA_FORMAT(format); // Word 4 auto formatComp = latte::SQ_FORMAT_COMP::UNSIGNED; auto numFormat = latte::SQ_NUM_FORMAT::NORM; auto forceDegamma = false; if (texture->surface.format & GX2AttribFormatFlags::SIGNED) { formatComp = latte::SQ_FORMAT_COMP::SIGNED; } if (texture->surface.format & GX2AttribFormatFlags::SCALED) { numFormat = latte::SQ_NUM_FORMAT::SCALED; } else if (texture->surface.format & GX2AttribFormatFlags::INTEGER) { numFormat = latte::SQ_NUM_FORMAT::INT; } if (texture->surface.format & GX2AttribFormatFlags::DEGAMMA) { forceDegamma = true; } auto endian = internal::getSurfaceFormatEndian(texture->surface.format); auto dstSelX = static_cast((texture->compMap >> 24) & 0x7); auto dstSelY = static_cast((texture->compMap >> 16) & 0x7); auto dstSelZ = static_cast((texture->compMap >> 8) & 0x7); auto dstSelW = static_cast(texture->compMap & 0x7); word4 = word4 .FORMAT_COMP_X(formatComp) .FORMAT_COMP_Y(formatComp) .FORMAT_COMP_Z(formatComp) .FORMAT_COMP_W(formatComp) .NUM_FORMAT_ALL(numFormat) .FORCE_DEGAMMA(forceDegamma) .ENDIAN_SWAP(endian) .REQUEST_SIZE(2) .DST_SEL_X(dstSelX) .DST_SEL_Y(dstSelY) .DST_SEL_Z(dstSelZ) .DST_SEL_W(dstSelW) .BASE_LEVEL(texture->viewFirstMip); // Word 5 auto yuvConv = 0u; if (texture->surface.dim == GX2SurfaceDim::TextureCube && word1.TEX_DEPTH()) { yuvConv = 1; } word5 = word5 .LAST_LEVEL(texture->viewFirstMip + texture->viewNumMips - 1) .BASE_ARRAY(texture->viewFirstSlice) .LAST_ARRAY(texture->viewFirstSlice + texture->viewNumSlices - 1) .YUV_CONV(yuvConv); // For MSAA textures, we overwrite the LAST_LEVEL field if (texture->surface.aa) { decaf_check(texture->surface.dim == GX2SurfaceDim::Texture2DMSAA || texture->surface.dim == GX2SurfaceDim::Texture2DMSAAArray); if (texture->surface.aa == GX2AAMode::Mode2X) { word5 = word5 .LAST_LEVEL(1); } else if (texture->surface.aa == GX2AAMode::Mode4X) { word5 = word5 .LAST_LEVEL(2); } else if (texture->surface.aa == GX2AAMode::Mode8X) { word5 = word5 .LAST_LEVEL(3); } } // Word 6 word6 = word6 .MAX_ANISO_RATIO(4) .PERF_MODULATION(7) .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_TEXTURE); // Update big endian register in texture texture->regs.word0 = word0; texture->regs.word1 = word1; texture->regs.word4 = word4; texture->regs.word5 = word5; texture->regs.word6 = word6; } static void setTexture(virt_ptr texture, latte::SQ_RES_OFFSET offset, uint32_t unit) { auto imageAddress = static_cast( OSEffectiveToPhysical(virt_cast(texture->surface.image))); auto mipAddress = static_cast( OSEffectiveToPhysical(virt_cast(texture->surface.mipmaps))); if (mipAddress & 0xff) { decaf_check_warn_once(!"Game used unaligned mip address"); } if (imageAddress & 0xff) { decaf_check_warn_once(!"Game used unaligned image address"); } if (texture->surface.tileMode >= GX2TileMode::Tiled2DThin1 && texture->surface.tileMode != GX2TileMode::LinearSpecial) { if ((texture->surface.swizzle >> 16) & 0xFF) { imageAddress ^= (texture->surface.swizzle & 0xFFFF); mipAddress ^= (texture->surface.swizzle & 0xFFFF); } } auto word2 = latte::SQ_TEX_RESOURCE_WORD2_N::get(imageAddress >> 8); auto word3 = latte::SQ_TEX_RESOURCE_WORD3_N::get(mipAddress >> 8); internal::writePM4(latte::pm4::SetTexResource { (offset + unit) * 7, texture->regs.word0, texture->regs.word1, word2, word3, texture->regs.word4, texture->regs.word5, texture->regs.word6, }); internal::debugDumpTexture(texture); } void GX2SetPixelTexture(virt_ptr texture, uint32_t unit) { setTexture(texture, latte::SQ_RES_OFFSET::PS_TEX_RESOURCE_0, unit); } void GX2SetVertexTexture(virt_ptr texture, uint32_t unit) { setTexture(texture, latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0, unit); } void GX2SetGeometryTexture(virt_ptr texture, uint32_t unit) { setTexture(texture, latte::SQ_RES_OFFSET::GS_TEX_RESOURCE_0, unit); } void Library::registerTextureSymbols() { RegisterFunctionExport(GX2InitTextureRegs); RegisterFunctionExport(GX2SetPixelTexture); RegisterFunctionExport(GX2SetVertexTexture); RegisterFunctionExport(GX2SetGeometryTexture); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2_texture.h ================================================ #pragma once #include "gx2_enum.h" #include "gx2_surface.h" #include #include namespace cafe::gx2 { /** * \defgroup gx2_texture Texture * \ingroup gx2 * @{ */ struct GX2Sampler; #pragma pack(push, 1) struct GX2Texture { be2_struct surface; be2_val viewFirstMip; be2_val viewNumMips; be2_val viewFirstSlice; be2_val viewNumSlices; be2_val compMap; struct { be2_val word0; be2_val word1; be2_val word4; be2_val word5; be2_val word6; } regs; }; CHECK_OFFSET(GX2Texture, 0x00, surface); CHECK_OFFSET(GX2Texture, 0x74, viewFirstMip); CHECK_OFFSET(GX2Texture, 0x78, viewNumMips); CHECK_OFFSET(GX2Texture, 0x7C, viewFirstSlice); CHECK_OFFSET(GX2Texture, 0x80, viewNumSlices); CHECK_OFFSET(GX2Texture, 0x84, compMap); CHECK_OFFSET(GX2Texture, 0x88, regs.word0); CHECK_OFFSET(GX2Texture, 0x8C, regs.word1); CHECK_OFFSET(GX2Texture, 0x90, regs.word4); CHECK_OFFSET(GX2Texture, 0x94, regs.word5); CHECK_OFFSET(GX2Texture, 0x98, regs.word6); CHECK_SIZE(GX2Texture, 0x9c); #pragma pack(pop) void GX2InitTextureRegs(virt_ptr texture); void GX2SetPixelTexture(virt_ptr texture, uint32_t unit); void GX2SetVertexTexture(virt_ptr texture, uint32_t unit); void GX2SetGeometryTexture(virt_ptr texture, uint32_t unit); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_buffer.cpp ================================================ #include "gx2.h" #include "gx2_debugcapture.h" #include "gx2_memory.h" #include "gx2_shaders.h" #include "gx2r_buffer.h" #include "gx2r_memory.h" #include "gx2r_resource.h" #include "cafe/libraries/coreinit/coreinit_cache.h" #include "cafe/libraries/cafe_hle_stub.h" #include #include namespace cafe::gx2 { using namespace coreinit; uint32_t GX2RGetBufferAlignment(GX2RResourceFlags flags) { return 256; } uint32_t GX2RGetBufferAllocationSize(virt_ptr buffer) { return align_up(buffer->elemCount * buffer->elemSize, 64); } void GX2RSetBufferName(virt_ptr buffer, virt_ptr name) { decaf_warn_stub(); } BOOL GX2RBufferExists(virt_ptr buffer) { return (buffer && buffer->buffer) ? TRUE : FALSE; } BOOL GX2RCreateBuffer(virt_ptr buffer) { decaf_check(!buffer->buffer); auto align = GX2RGetBufferAlignment(buffer->flags); auto size = GX2RGetBufferAllocationSize(buffer); buffer->flags &= ~GX2RResourceFlags::Locked; buffer->flags |= GX2RResourceFlags::Gx2rAllocated; buffer->buffer = gx2::internal::gx2rAlloc(buffer->flags, size, align); if (!buffer->buffer) { return FALSE; } GX2NotifyMemAlloc(buffer->buffer, size, align); // Check if we need to invalidate the buffer if ((buffer->flags & GX2RResourceFlags::UsageGpuWrite) || (buffer->flags & GX2RResourceFlags::UsageDmaWrite)) { DCInvalidateRange(virt_cast(buffer->buffer), size); } return TRUE; } BOOL GX2RCreateBufferUserMemory(virt_ptr buffer, virt_ptr memory, uint32_t size) { decaf_check(buffer); decaf_check(memory); buffer->buffer = memory; buffer->flags &= ~GX2RResourceFlags::Locked; buffer->flags &= ~GX2RResourceFlags::Gx2rAllocated; // Check if we need to invalidate the buffer if ((buffer->flags & GX2RResourceFlags::UsageGpuWrite) || (buffer->flags & GX2RResourceFlags::UsageDmaWrite)) { DCInvalidateRange(virt_cast(buffer->buffer), buffer->elemCount * buffer->elemSize); } GX2NotifyMemAlloc(buffer->buffer, size, GX2RGetBufferAlignment(buffer->flags)); return TRUE; } void GX2RDestroyBufferEx(virt_ptr buffer, GX2RResourceFlags flags) { if (!buffer || !buffer->buffer) { return; } flags = internal::getOptionFlags(flags); if (!GX2RIsUserMemory(buffer->flags) && !(flags & GX2RResourceFlags::DestroyNoFree)) { gx2::internal::gx2rFree(flags, buffer->buffer); buffer->buffer = nullptr; } GX2NotifyMemFree(buffer->buffer); } void GX2RInvalidateBuffer(virt_ptr buffer, GX2RResourceFlags flags) { flags = internal::getOptionFlags(flags); flags = static_cast(flags | (buffer->flags & ~0xF80000)); GX2RInvalidateMemory(flags, buffer->buffer, buffer->elemSize * buffer->elemCount); } virt_ptr GX2RLockBufferEx(virt_ptr buffer, GX2RResourceFlags flags) { flags = buffer->flags | internal::getOptionFlags(flags); // Update buffer flags buffer->flags |= GX2RResourceFlags::Locked; buffer->flags |= flags & GX2RResourceFlags::LockedReadOnly; // Check if we need to invalidate the buffer if ((flags & GX2RResourceFlags::UsageGpuWrite) || (flags & GX2RResourceFlags::UsageDmaWrite)) { if (!(flags & GX2RResourceFlags::DisableCpuInvalidate) && !(flags & GX2RResourceFlags::LockedReadOnly)) { DCInvalidateRange(virt_cast(buffer->buffer), buffer->elemCount * buffer->elemSize); } } // Return buffer pointer return buffer->buffer; } void GX2RUnlockBufferEx(virt_ptr buffer, GX2RResourceFlags flags) { flags = internal::getOptionFlags(flags); // Invalidate the GPU buffer only if it was not read only locked if (!(buffer->flags & GX2RResourceFlags::LockedReadOnly)) { GX2RInvalidateBuffer(buffer, flags); } // Update buffer flags buffer->flags &= ~GX2RResourceFlags::Locked; buffer->flags &= ~GX2RResourceFlags::LockedReadOnly; } void GX2RSetStreamOutBuffer(uint32_t index, virt_ptr stream) { GX2SetStreamOutBuffer(index, stream); } void Library::registerGx2rBufferSymbols() { RegisterFunctionExport(GX2RGetBufferAlignment); RegisterFunctionExport(GX2RGetBufferAllocationSize); RegisterFunctionExport(GX2RSetBufferName); RegisterFunctionExport(GX2RBufferExists); RegisterFunctionExport(GX2RCreateBuffer); RegisterFunctionExport(GX2RCreateBufferUserMemory); RegisterFunctionExport(GX2RDestroyBufferEx); RegisterFunctionExport(GX2RInvalidateBuffer); RegisterFunctionExport(GX2RLockBufferEx); RegisterFunctionExport(GX2RUnlockBufferEx); RegisterFunctionExport(GX2RSetStreamOutBuffer); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_buffer.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2r_buffer GX2R Buffer * \ingroup gx2 * @{ */ #pragma pack(push, 1) struct GX2OutputStream; struct GX2RBuffer { be2_val flags; be2_val elemSize; be2_val elemCount; be2_virt_ptr buffer; }; CHECK_SIZE(GX2RBuffer, 0x10); CHECK_OFFSET(GX2RBuffer, 0x00, flags); CHECK_OFFSET(GX2RBuffer, 0x04, elemSize); CHECK_OFFSET(GX2RBuffer, 0x08, elemCount); CHECK_OFFSET(GX2RBuffer, 0x0C, buffer); #pragma pack(pop) uint32_t GX2RGetBufferAlignment(GX2RResourceFlags flags); uint32_t GX2RGetBufferAllocationSize(virt_ptr buffer); void GX2RSetBufferName(virt_ptr buffer, virt_ptr name); BOOL GX2RBufferExists(virt_ptr buffer); BOOL GX2RCreateBuffer(virt_ptr buffer); BOOL GX2RCreateBufferUserMemory(virt_ptr buffer, virt_ptr memory, uint32_t size); void GX2RDestroyBufferEx(virt_ptr buffer, GX2RResourceFlags flags); void GX2RInvalidateBuffer(virt_ptr buffer, GX2RResourceFlags flags); virt_ptr GX2RLockBufferEx(virt_ptr buffer, GX2RResourceFlags flags); void GX2RUnlockBufferEx(virt_ptr buffer, GX2RResourceFlags flags); void GX2RSetStreamOutBuffer(uint32_t index, virt_ptr stream); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_displaylist.cpp ================================================ #include "gx2.h" #include "gx2_displaylist.h" #include "gx2r_buffer.h" #include "gx2r_displaylist.h" #include namespace cafe::gx2 { void GX2RBeginDisplayListEx(virt_ptr displayList, uint32_t unused, GX2RResourceFlags flags) { if (!displayList || !displayList->buffer) { return; } GX2BeginDisplayListEx(displayList->buffer, displayList->elemCount * displayList->elemSize, TRUE); } uint32_t GX2REndDisplayList(virt_ptr displayList) { auto size = GX2EndDisplayList(displayList->buffer); decaf_check(size < (displayList->elemCount * displayList->elemSize)); return size; } void GX2RCallDisplayList(virt_ptr displayList, uint32_t size) { if (!displayList || !displayList->buffer) { return; } GX2CallDisplayList(displayList->buffer, size); } void GX2RDirectCallDisplayList(virt_ptr displayList, uint32_t size) { if (!displayList || !displayList->buffer) { return; } GX2DirectCallDisplayList(displayList->buffer, size); } void Library::registerGx2rDisplayListSymbols() { RegisterFunctionExport(GX2RBeginDisplayListEx); RegisterFunctionExport(GX2REndDisplayList); RegisterFunctionExport(GX2RCallDisplayList); RegisterFunctionExport(GX2RDirectCallDisplayList); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_displaylist.h ================================================ #pragma once #include "gx2_enum.h" #include "gx2r_buffer.h" #include namespace cafe::gx2 { /** * \defgroup gx2r_aperture GX2R Display List * \ingroup gx2 * @{ */ void GX2RBeginDisplayListEx(virt_ptr displayList, uint32_t unused, GX2RResourceFlags flags); uint32_t GX2REndDisplayList(virt_ptr displayList); void GX2RCallDisplayList(virt_ptr displayList, uint32_t size); void GX2RDirectCallDisplayList(virt_ptr displayList, uint32_t size); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_draw.cpp ================================================ #include "gx2.h" #include "gx2_draw.h" #include "gx2r_buffer.h" #include "gx2r_draw.h" namespace cafe::gx2 { void GX2RSetAttributeBuffer(virt_ptr buffer, uint32_t index, uint32_t stride, uint32_t offset) { GX2SetAttribBuffer(index, buffer->elemCount * buffer->elemSize, stride, virt_cast(buffer->buffer) + offset); } void GX2RDrawIndexed(GX2PrimitiveMode mode, virt_ptr buffer, GX2IndexType indexType, uint32_t count, uint32_t indexOffset, uint32_t vertexOffset, uint32_t numInstances) { GX2DrawIndexedEx(mode, count, indexType, virt_cast(buffer->buffer) + indexOffset * buffer->elemSize, vertexOffset, numInstances); } void Library::registerGx2rDrawSymbols() { RegisterFunctionExport(GX2RSetAttributeBuffer); RegisterFunctionExport(GX2RDrawIndexed); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_draw.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2r_draw GX2R Draw * \ingroup gx2 * @{ */ struct GX2RBuffer; void GX2RSetAttributeBuffer(virt_ptr buffer, uint32_t index, uint32_t stride, uint32_t offset); void GX2RDrawIndexed(GX2PrimitiveMode mode, virt_ptr buffer, GX2IndexType indexType, uint32_t count, uint32_t indexOffset, uint32_t vertexOffset, uint32_t numInstances); /** @{ */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_memory.cpp ================================================ #include "gx2.h" #include "gx2_memory.h" #include "gx2r_memory.h" namespace cafe::gx2 { static GX2InvalidateMode getInvalidateMode(GX2RResourceFlags flags) { auto mode = 0u; if (flags & GX2RResourceFlags::BindTexture) { mode |= GX2InvalidateMode::Texture; } if (flags & GX2RResourceFlags::BindColorBuffer) { mode |= GX2InvalidateMode::ColorBuffer; } if (flags & GX2RResourceFlags::BindDepthBuffer) { mode |= GX2InvalidateMode::DepthBuffer; } if (flags & GX2RResourceFlags::BindScanBuffer) { mode |= GX2InvalidateMode::ColorBuffer; mode |= GX2InvalidateMode::DepthBuffer; } if (flags & GX2RResourceFlags::BindVertexBuffer) { mode |= GX2InvalidateMode::AttributeBuffer; } if (flags & GX2RResourceFlags::BindIndexBuffer) { mode |= GX2InvalidateMode::AttributeBuffer; } if (flags & GX2RResourceFlags::BindUniformBlock) { mode |= GX2InvalidateMode::UniformBlock; } if (flags & GX2RResourceFlags::BindShaderProgram) { mode |= GX2InvalidateMode::Shader; } if (flags & GX2RResourceFlags::BindStreamOutput) { mode |= GX2InvalidateMode::StreamOutBuffer; } if (flags & GX2RResourceFlags::UsageCpuReadWrite) { mode |= GX2InvalidateMode::CPU; } if (flags & GX2RResourceFlags::DisableCpuInvalidate) { // Clear only the CPU bit mode &= ~GX2InvalidateMode::CPU; } if (flags & GX2RResourceFlags::DisableGpuInvalidate) { // Clear every bit except CPU mode &= GX2InvalidateMode::CPU; } return static_cast(mode); } void GX2RInvalidateMemory(GX2RResourceFlags flags, virt_ptr buffer, uint32_t size) { GX2Invalidate(getInvalidateMode(flags), buffer, size); } void Library::registerGx2rMemorySymbols() { RegisterFunctionExport(GX2RInvalidateMemory); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_memory.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2r_memory GX2R Memory * \ingroup gx2 * @{ */ void GX2RInvalidateMemory(GX2RResourceFlags flags, virt_ptr buffer, uint32_t size); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_resource.cpp ================================================ #include "gx2.h" #include "gx2r_resource.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include #include namespace cafe::gx2 { using namespace coreinit; struct StaticGx2rResourceData { be2_val alloc; be2_val free; }; static virt_ptr sGx2rResourceData = nullptr; static GX2RAllocFuncPtr GX2RDefaultAlloc = nullptr; static GX2RFreeFuncPtr GX2RDefaultFree = nullptr; void GX2RSetAllocator(GX2RAllocFuncPtr allocFn, GX2RFreeFuncPtr freeFn) { sGx2rResourceData->alloc = allocFn; sGx2rResourceData->free = freeFn; } BOOL GX2RIsUserMemory(GX2RResourceFlags flags) { return (flags & GX2RResourceFlags::Gx2rAllocated) ? FALSE : TRUE; } static virt_ptr defaultAlloc(GX2RResourceFlags flags, uint32_t size, uint32_t align) { return MEMAllocFromDefaultHeapEx(size, align); } static void defaultFree(GX2RResourceFlags flags, virt_ptr buffer) { MEMFreeToDefaultHeap(buffer); } namespace internal { GX2RResourceFlags getOptionFlags(GX2RResourceFlags flags) { // Allow flags in bits 19 to 23 return static_cast(flags & 0xF80000); } virt_ptr gx2rAlloc(GX2RResourceFlags flags, uint32_t size, uint32_t align) { return cafe::invoke(cpu::this_core::state(), sGx2rResourceData->alloc, flags, size, align); } void gx2rFree(GX2RResourceFlags flags, virt_ptr buffer) { cafe::invoke(cpu::this_core::state(), sGx2rResourceData->free, flags, buffer); } GX2RAllocFuncPtr getDefaultGx2rAlloc() { return GX2RDefaultAlloc; } GX2RFreeFuncPtr getDefaultGx2rFree() { return GX2RDefaultFree; } void initialiseGx2rAllocator() { GX2RSetAllocator(GX2RDefaultAlloc, GX2RDefaultFree); } } // namespace internal void Library::registerGx2rResourceSymbols() { RegisterFunctionExport(GX2RSetAllocator); RegisterFunctionExport(GX2RIsUserMemory); RegisterDataInternal(sGx2rResourceData); RegisterFunctionInternal(defaultAlloc, GX2RDefaultAlloc); RegisterFunctionInternal(defaultFree, GX2RDefaultFree); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_resource.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2r_resource GX2R Resource * \ingroup gx2 * @{ */ using GX2RAllocFuncPtr = virt_func_ptr< virt_ptr(GX2RResourceFlags, uint32_t, uint32_t) >; using GX2RFreeFuncPtr = virt_func_ptr< void(GX2RResourceFlags, virt_ptr) >; void GX2RSetAllocator(GX2RAllocFuncPtr allocFn, GX2RFreeFuncPtr freeFn); BOOL GX2RIsUserMemory(GX2RResourceFlags flags); namespace internal { void initialiseGx2rAllocator(); GX2RResourceFlags getOptionFlags(GX2RResourceFlags flags); virt_ptr gx2rAlloc(GX2RResourceFlags flags, uint32_t size, uint32_t align); void gx2rFree(GX2RResourceFlags flags, virt_ptr buffer); GX2RAllocFuncPtr getDefaultGx2rAlloc(); GX2RFreeFuncPtr getDefaultGx2rFree(); } // namespace internal /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_shaders.cpp ================================================ #include "gx2.h" #include "gx2r_shaders.h" #include "gx2_shaders.h" namespace cafe::gx2 { void GX2RSetVertexUniformBlock(virt_ptr buffer, uint32_t location, uint32_t offset) { GX2SetVertexUniformBlock(location, (buffer->elemSize * buffer->elemCount) - offset, virt_cast(buffer->buffer) + offset); } void GX2RSetPixelUniformBlock(virt_ptr buffer, uint32_t location, uint32_t offset) { GX2SetPixelUniformBlock(location, (buffer->elemSize * buffer->elemCount) - offset, virt_cast(buffer->buffer) + offset); } void GX2RSetGeometryUniformBlock(virt_ptr buffer, uint32_t location, uint32_t offset) { GX2SetGeometryUniformBlock(location, (buffer->elemSize * buffer->elemCount) - offset, virt_cast(buffer->buffer) + offset); } void Library::registerGx2rShadersSymbols() { RegisterFunctionExport(GX2RSetVertexUniformBlock); RegisterFunctionExport(GX2RSetPixelUniformBlock); RegisterFunctionExport(GX2RSetGeometryUniformBlock); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_shaders.h ================================================ #pragma once #include namespace cafe::gx2 { /** * \defgroup gx2r_shaders GX2R Shaders * \ingroup gx2 * @{ */ struct GX2RBuffer; void GX2RSetVertexUniformBlock(virt_ptr buffer, uint32_t location, uint32_t offset); void GX2RSetPixelUniformBlock(virt_ptr buffer, uint32_t location, uint32_t offset); void GX2RSetGeometryUniformBlock(virt_ptr buffer, uint32_t location, uint32_t offset); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_surface.cpp ================================================ #include "gx2.h" #include "gx2_surface.h" #include "gx2r_memory.h" #include "gx2r_resource.h" #include "gx2r_surface.h" #include "cafe/libraries/coreinit/coreinit_cache.h" namespace cafe::gx2 { using namespace coreinit; namespace internal { static void getSurfaceData(virt_ptr surface, int32_t level, virt_ptr *addr, uint32_t *size) { if (level == 0) { *addr = surface->image; *size = surface->imageSize; } else if (level == -1) { *addr = surface->mipmaps; *size = surface->mipmapSize; } else { decaf_check(level > 0); auto curLevelOffset = 0u; auto nextLevelOffset = 0u; if (level > 1) { curLevelOffset = surface->mipLevelOffset[level - 1]; } if (static_cast(level + 1) >= surface->mipLevels) { nextLevelOffset = surface->mipmapSize; } else { nextLevelOffset = surface->mipLevelOffset[level]; } *addr = surface->mipmaps + curLevelOffset; *size = nextLevelOffset - curLevelOffset; } } } // namespace internal BOOL GX2RCreateSurface(virt_ptr surface, GX2RResourceFlags flags) { surface->resourceFlags = flags; surface->resourceFlags &= ~GX2RResourceFlags::Locked; surface->resourceFlags |= GX2RResourceFlags::Gx2rAllocated; GX2CalcSurfaceSizeAndAlignment(surface); auto buffer = internal::gx2rAlloc(surface->resourceFlags, surface->imageSize + surface->mipmapSize, surface->alignment); surface->image = virt_cast(buffer); if (!surface->image) { return FALSE; } surface->mipmaps = nullptr; if (surface->mipmapSize) { surface->mipmaps = surface->image + surface->imageSize; } if ((surface->resourceFlags & GX2RResourceFlags::UsageGpuWrite) || (surface->resourceFlags & GX2RResourceFlags::UsageDmaWrite)) { DCInvalidateRange(virt_cast(surface->image), surface->imageSize); } return TRUE; } BOOL GX2RCreateSurfaceUserMemory(virt_ptr surface, virt_ptr image, virt_ptr mipmap, GX2RResourceFlags flags) { GX2CalcSurfaceSizeAndAlignment(surface); surface->resourceFlags = flags; surface->resourceFlags &= ~GX2RResourceFlags::Locked; surface->resourceFlags &= ~GX2RResourceFlags::Gx2rAllocated; surface->image = image; surface->mipmaps = mipmap; if ((surface->resourceFlags & GX2RResourceFlags::UsageGpuWrite) || (surface->resourceFlags & GX2RResourceFlags::UsageDmaWrite)) { DCInvalidateRange(virt_cast(surface->image), surface->imageSize); if (surface->mipmaps) { DCInvalidateRange(virt_cast(surface->mipmaps), surface->mipmapSize); } } return true; } void GX2RDestroySurfaceEx(virt_ptr surface, GX2RResourceFlags flags) { if (!surface || !surface->image) { return; } flags = surface->resourceFlags | internal::getOptionFlags(flags); if (!GX2RIsUserMemory(surface->resourceFlags)) { gx2::internal::gx2rFree(flags, surface->image); } surface->image = nullptr; } virt_ptr GX2RLockSurfaceEx(virt_ptr surface, int32_t level, GX2RResourceFlags flags) { decaf_check(surface); decaf_check(surface->resourceFlags & ~GX2RResourceFlags::Locked); flags = surface->resourceFlags | internal::getOptionFlags(flags); // Set Locked flag surface->resourceFlags |= GX2RResourceFlags::Locked; surface->resourceFlags |= flags & GX2RResourceFlags::LockedReadOnly; // Check if we need to invalidate the surface. if ((flags & GX2RResourceFlags::UsageGpuWrite) || (flags & GX2RResourceFlags::UsageDmaWrite)) { if (!(flags & GX2RResourceFlags::DisableCpuInvalidate)) { auto ptr = virt_ptr { nullptr }; auto size = uint32_t { 0 }; internal::getSurfaceData(surface, level, &ptr, &size); DCInvalidateRange(virt_cast(ptr), size); } } return surface->image; } void GX2RUnlockSurfaceEx(virt_ptr surface, int32_t level, GX2RResourceFlags flags) { decaf_check(surface); decaf_check(surface->resourceFlags & GX2RResourceFlags::Locked); // Invalidate surface GX2RInvalidateSurface(surface, level, flags); // Clear locked flags. surface->resourceFlags &= ~GX2RResourceFlags::LockedReadOnly; surface->resourceFlags &= ~GX2RResourceFlags::Locked; } BOOL GX2RIsGX2RSurface(GX2RResourceFlags flags) { return (flags & (GX2RResourceFlags::UsageCpuReadWrite | GX2RResourceFlags::UsageGpuReadWrite)) ? TRUE : FALSE; } void GX2RInvalidateSurface(virt_ptr surface, int32_t level, GX2RResourceFlags flags) { flags = internal::getOptionFlags(flags); // Get surface ptr & size auto ptr = virt_ptr { nullptr }; auto size = uint32_t { 0 }; internal::getSurfaceData(surface, level, &ptr, &size); // Invalidate! GX2RInvalidateMemory(surface->resourceFlags | flags, ptr, size); } void Library::registerGx2rSurfaceSymbols() { RegisterFunctionExport(GX2RCreateSurface); RegisterFunctionExport(GX2RCreateSurfaceUserMemory); RegisterFunctionExport(GX2RDestroySurfaceEx); RegisterFunctionExport(GX2RLockSurfaceEx); RegisterFunctionExport(GX2RUnlockSurfaceEx); RegisterFunctionExport(GX2RIsGX2RSurface); RegisterFunctionExport(GX2RInvalidateSurface); } } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/gx2/gx2r_surface.h ================================================ #pragma once #include "gx2_enum.h" #include namespace cafe::gx2 { /** * \defgroup gx2r_surface GX2R Surface * \ingroup gx2 * @{ */ struct GX2Surface; BOOL GX2RCreateSurface(virt_ptr surface, GX2RResourceFlags flags); BOOL GX2RCreateSurfaceUserMemory(virt_ptr surface, virt_ptr image, virt_ptr mipmap, GX2RResourceFlags flags); void GX2RDestroySurfaceEx(virt_ptr surface, GX2RResourceFlags flags); virt_ptr GX2RLockSurfaceEx(virt_ptr surface, int32_t level, GX2RResourceFlags flags); void GX2RUnlockSurfaceEx(virt_ptr surface, int32_t level, GX2RResourceFlags flags); BOOL GX2RIsGX2RSurface(GX2RResourceFlags flags); void GX2RInvalidateSurface(virt_ptr surface, int32_t level, GX2RResourceFlags flags); /** @} */ } // namespace cafe::gx2 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264.cpp ================================================ #include "h264.h" namespace cafe::h264 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerDecodeSymbols(); registerStreamSymbols(); } } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::h264 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::h264, "h264.rpl") { } protected: virtual void registerSymbols() override; private: void registerDecodeSymbols(); void registerStreamSymbols(); }; } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_bitstream.h ================================================ #pragma once #include namespace cafe::h264 { class BitStream { public: BitStream(const uint8_t *buffer, size_t size) : mBuffer(buffer), mSize(size), mBytePosition(0), mBitPosition(0) { } uint8_t peekU1() { if (eof()) { return 0; } return static_cast((mBuffer[mBytePosition] >> (7 - mBitPosition)) & 1); } uint8_t readU1() { if (eof()) { return 0; } auto value = static_cast((mBuffer[mBytePosition] >> (7 - mBitPosition)) & 1); mBitPosition++; if (mBitPosition == 8) { mBitPosition = 0; mBytePosition++; } return value; } uint32_t readU(size_t n) { auto value = uint32_t { 0 }; for (auto i = 0u; i < n; ++i) { value |= readU1() << (n - i - 1); } return value; } uint8_t readU2() { return static_cast(readU(2)); } uint8_t readU3() { return static_cast(readU(3)); } uint8_t readU4() { return static_cast(readU(4)); } uint8_t readU5() { return static_cast(readU(5)); } uint8_t readU8() { if (eof()) { return 0; } if (mBitPosition == 0) { return mBuffer[mBytePosition++]; } return static_cast(readU(8)); } uint16_t readU16() { return static_cast(readU(16)); } uint32_t readUE() { auto r = uint32_t { 0 }; auto i = 0; while (readU1() == 0 && i < 32 && !eof()) { i++; } r = readU(i); r += (1 << i) - 1; return r; } int32_t readSE() { auto r = static_cast(readUE()); if (r & 0x01) { r = (r + 1) / 2; } else { r = -(r / 2); } return r; } bool byteAligned() { return mBitPosition == 0; } bool eof() { return mBytePosition >= mSize; } size_t bytesRead() { return mBytePosition; } private: const uint8_t *mBuffer; size_t mSize; size_t mBytePosition; size_t mBitPosition; }; } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_decode.cpp ================================================ #include "h264.h" #include "h264_decode.h" #include "h264_decode_ffmpeg.h" #include "h264_decode_null.h" #include "h264_stream.h" #include "cafe/libraries/cafe_hle_stub.h" #include #include #include namespace cafe::h264 { constexpr auto BaseMemoryRequirement = 0x480000u; constexpr auto WorkMemoryAlign = 0x100u; constexpr auto MinimumWidth = 64u; constexpr auto MinimumHeight = 64u; constexpr auto MaximumWidth = 2800u; constexpr auto MaximumHeight = 1408u; namespace internal { virt_ptr getWorkMemory(virt_ptr memory) { auto workMemory = byte_swap(*virt_cast(memory)); auto addrDiff = workMemory - virt_cast(memory); if (addrDiff > WorkMemoryAlign || addrDiff < 0) { return nullptr; } return virt_cast(workMemory); } static void initialiseWorkMemory(virt_ptr workMemory, virt_addr alignedMemoryEnd) { auto baseAddress = virt_cast(workMemory); workMemory->streamMemory = virt_cast(baseAddress + 0x100u); workMemory->unk0x10 = virt_cast(baseAddress + 0x439C00); workMemory->bitStream = virt_cast(baseAddress + 0x439D00); workMemory->database = virt_cast(baseAddress + 0x439E00); workMemory->codecMemory = virt_cast(baseAddress + 0x442300); workMemory->l1Memory = virt_cast(baseAddress + 0x445F00); workMemory->unk0x24 = virt_cast(baseAddress + 0x447F00); workMemory->internalFrameMemory = virt_cast(baseAddress + 0x44FC00); workMemory->streamMemory->workMemoryEnd = virt_cast(alignedMemoryEnd); workMemory->streamMemory->unkMemory = virt_cast(alignedMemoryEnd - 0x450000 + 0x300); } static uint32_t getLevelMemoryRequirement(int32_t level) { switch (level) { case 10: return 0x63000; case 11: return 0xE1000; case 12: case 13: case 20: return 0x252000; case 21: return 0x4A4000; case 22: case 30: return 0x7E9000; case 31: return 0x1194000; case 32: return 0x1400000; case 40: case 41: return 0x2000000; case 42: return 0x2200000; case 50: return 0x6BD0000; case 51: return 0xB400000; default: decaf_abort(fmt::format("Unexpected H264 level {}", level)); } } } // namespace internal /** * Calculate the amount of memory required for the specified parameters. */ H264Error H264DECMemoryRequirement(int32_t profile, int32_t level, int32_t maxWidth, int32_t maxHeight, virt_ptr outMemoryRequirement) { if (maxWidth < MinimumWidth || maxHeight < MinimumHeight || maxWidth > MaximumWidth || maxHeight > MaximumHeight) { return H264Error::InvalidParameter; } if (!outMemoryRequirement) { return H264Error::InvalidParameter; } if (level > 51) { return H264Error::InvalidParameter; } if (profile != 66 && profile != 77 && profile != 100) { return H264Error::InvalidParameter; } *outMemoryRequirement = BaseMemoryRequirement + internal::getLevelMemoryRequirement(level) + 1023u; return H264Error::OK; } /** * Initialise a H264 decoder in the given memory. */ H264Error H264DECInitParam(int32_t memorySize, virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } if (memorySize < 0x44F900) { return H264Error::OutOfMemory; } std::memset(memory.get(), 0, memorySize); // Calculate aligned memory auto alignedMemory = align_up(memory, WorkMemoryAlign); auto alignOffset = virt_cast(memory) - virt_cast(alignedMemory); auto alignedMemoryStart = virt_cast(alignedMemory); auto alignedMemoryEnd = alignedMemoryStart + memorySize - alignOffset; // Write aligned memory start reversed to memory *virt_cast(memory) = byte_swap(alignedMemoryStart); // Initialise our memory auto workMemory = virt_cast(alignedMemoryStart); internal::initialiseWorkMemory(workMemory, alignedMemoryEnd); // TODO: More things. return H264Error::OK; } /** * Set H264 decoder parameter. */ H264Error H264DECSetParam(virt_ptr memory, H264Parameter parameter, virt_ptr value) { if (!memory || !value) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } auto streamMemory = workMemory->streamMemory; switch (parameter) { case H264Parameter::FramePointerOutput: streamMemory->paramFramePointerOutput = virt_func_cast(virt_cast(value)); break; case H264Parameter::OutputPerFrame: streamMemory->paramOutputPerFrame = *virt_cast(value); break; case H264Parameter::UserMemory: streamMemory->paramUserMemory = value; break; case H264Parameter::Unknown0x20000030: streamMemory->param_0x20000030 = *virt_cast(value); break; case H264Parameter::Unknown0x20000040: streamMemory->param_0x20000040 = *virt_cast(value); break; case H264Parameter::Unknown0x20000010: // TODO default: return H264Error::InvalidParameter; } return H264Error::OK; } /** * Set the callback which is called when a frame is output from the decoder. */ H264Error H264DECSetParam_FPTR_OUTPUT(virt_ptr memory, H264DECFptrOutputFn value) { if (!memory || !value) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } workMemory->streamMemory->paramFramePointerOutput = value; return H264Error::OK; } /** * Set whether the decoder should internally buffer frames or call the callback * immediately as soon as a frame is emitted. */ H264Error H264DECSetParam_OUTPUT_PER_FRAME(virt_ptr memory, uint32_t value) { if (!memory || !value) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } if (value) { workMemory->streamMemory->paramOutputPerFrame = uint8_t { 1 }; } else { workMemory->streamMemory->paramOutputPerFrame = uint8_t { 0 }; } return H264Error::OK; } /** * Set a user memory pointer which is passed to the frame output callback. */ H264Error H264DECSetParam_USER_MEMORY(virt_ptr memory, virt_ptr value) { if (!memory || !value) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } workMemory->streamMemory->paramUserMemory = value; return H264Error::OK; } H264Error H264DECCheckMemSegmentation(virt_ptr memory, uint32_t size) { if (!memory || !size) { return H264Error::InvalidParameter; } return H264Error::OK; } H264Error H264DECOpen(virt_ptr memory) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECOpen(memory); #else return null::H264DECOpen(memory); #endif } H264Error H264DECBegin(virt_ptr memory) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECBegin(memory); #else return null::H264DECBegin(memory); #endif } H264Error H264DECSetBitstream(virt_ptr memory, virt_ptr buffer, uint32_t bufferLength, double timestamp) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECSetBitstream(memory, buffer, bufferLength, timestamp); #else return null::H264DECSetBitstream(memory, buffer, bufferLength, timestamp); #endif } H264Error H264DECExecute(virt_ptr memory, virt_ptr frameBuffer) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECExecute(memory, frameBuffer); #else return null::H264DECExecute(memory, frameBuffer); #endif } H264Error H264DECFlush(virt_ptr memory) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECFlush(memory); #else return null::H264DECFlush(memory); #endif } H264Error H264DECEnd(virt_ptr memory) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECEnd(memory); #else return null::H264DECEnd(memory); #endif } H264Error H264DECClose(virt_ptr memory) { #ifdef DECAF_FFMPEG return ffmpeg::H264DECClose(memory); #else return null::H264DECClose(memory); #endif } void Library::registerDecodeSymbols() { RegisterFunctionExport(H264DECMemoryRequirement); RegisterFunctionExport(H264DECInitParam); RegisterFunctionExport(H264DECSetParam); RegisterFunctionExport(H264DECSetParam_FPTR_OUTPUT); RegisterFunctionExport(H264DECSetParam_OUTPUT_PER_FRAME); RegisterFunctionExport(H264DECSetParam_USER_MEMORY); RegisterFunctionExport(H264DECCheckMemSegmentation); RegisterFunctionExport(H264DECOpen); RegisterFunctionExport(H264DECBegin); RegisterFunctionExport(H264DECSetBitstream); RegisterFunctionExport(H264DECExecute); RegisterFunctionExport(H264DECFlush); RegisterFunctionExport(H264DECClose); RegisterFunctionExport(H264DECEnd); } } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_decode.h ================================================ #pragma once #include "h264_enum.h" #include "h264_stream.h" #include namespace cafe::h264 { struct H264Bitstream; struct H264CodecMemory; struct H264StreamMemory; struct H264WorkMemory { //! Space for pointer to aligned memory in case the original memory was //! already aligned. PADDING(4); //! End of the h264 work memory be2_virt_ptr workMemoryEnd; be2_virt_ptr unk0x08; be2_virt_ptr streamMemory; be2_virt_ptr unk0x10; be2_virt_ptr bitStream; be2_virt_ptr database; be2_virt_ptr codecMemory; be2_virt_ptr l1Memory; be2_virt_ptr unk0x24; be2_virt_ptr internalFrameMemory; }; CHECK_OFFSET(H264WorkMemory, 0x04, workMemoryEnd); CHECK_OFFSET(H264WorkMemory, 0x08, unk0x08); CHECK_OFFSET(H264WorkMemory, 0x0C, streamMemory); CHECK_OFFSET(H264WorkMemory, 0x10, unk0x10); CHECK_OFFSET(H264WorkMemory, 0x14, bitStream); CHECK_OFFSET(H264WorkMemory, 0x18, database); CHECK_OFFSET(H264WorkMemory, 0x1C, codecMemory); CHECK_OFFSET(H264WorkMemory, 0x20, l1Memory); CHECK_OFFSET(H264WorkMemory, 0x24, unk0x24); CHECK_OFFSET(H264WorkMemory, 0x28, internalFrameMemory); CHECK_SIZE(H264WorkMemory, 0x2C); H264Error H264DECMemoryRequirement(int32_t profile, int32_t level, int32_t maxWidth, int32_t maxHeight, virt_ptr outMemoryRequirement); H264Error H264DECInitParam(int32_t memorySize, virt_ptr memory); H264Error H264DECSetParam(virt_ptr memory, H264Parameter parameter, virt_ptr value); H264Error H264DECSetParam_FPTR_OUTPUT(virt_ptr memory, H264DECFptrOutputFn value); H264Error H264DECSetParam_OUTPUT_PER_FRAME(virt_ptr memory, uint32_t value); H264Error H264DECSetParam_USER_MEMORY(virt_ptr memory, virt_ptr value); H264Error H264DECCheckMemSegmentation(virt_ptr memory, uint32_t size); H264Error H264DECOpen(virt_ptr memory); H264Error H264DECBegin(virt_ptr memory); H264Error H264DECSetBitstream(virt_ptr memory, virt_ptr buffer, uint32_t bufferLength, double timestamp); H264Error H264DECExecute(virt_ptr memory, virt_ptr frameBuffer); H264Error H264DECFlush(virt_ptr memory); H264Error H264DECEnd(virt_ptr memory); H264Error H264DECClose(virt_ptr memory); namespace internal { virt_ptr getWorkMemory(virt_ptr memory); } // namespace internal } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_decode_ffmpeg.cpp ================================================ #ifdef DECAF_FFMPEG #include "h264.h" #include "h264_decode.h" #include "h264_stream.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" #include #include #include #include #include // ffmpeg unfortunately does not validate with high warning levels #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4244) #endif extern "C" { #include #include #include } #ifdef _MSC_VER # pragma warning(pop) #endif namespace cafe::h264 { // This is decaf specific stuff - does not match structure in h264.rpl struct H264CodecMemory { AVCodecContext *context; AVFrame *frame; AVCodecParserContext *parser; SwsContext *sws; int swsWidth; int swsHeight; int inputFrameIndex; int outputFrameIndex; //! HACK: This is just a copy of the most recently seen vui_parameters in //! the stream, technically it should probably be the ones that are in the //! SPS for the given PPS in given frame's slice headers. uint8_t vui_parameters_present_flag; H264DecodedVuiParameters vui_parameters; }; } // namespace cafe::h264 namespace cafe::h264::ffmpeg { static int receiveFrames(virt_ptr workMemory) { auto codecMemory = workMemory->codecMemory; auto streamMemory = workMemory->streamMemory; auto frame = codecMemory->frame; auto result = 0; while (result == 0) { result = avcodec_receive_frame(codecMemory->context, frame); if (result != 0) { break; } // Get the decoded frame info auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->outputFrameIndex]; codecMemory->outputFrameIndex = (codecMemory->outputFrameIndex + 1) % streamMemory->decodedFrameInfos.size(); auto decodeResult = StackObject { }; decodeResult->status = 100; decodeResult->timestamp = decodedFrameInfo.timestamp; const auto pitch = align_up(frame->width, 256); // Destroy previously created SWS if there is different width/height if (codecMemory->sws && (codecMemory->swsWidth != frame->width || codecMemory->swsHeight != frame->height)) { sws_freeContext(codecMemory->sws); codecMemory->sws = nullptr; } // Create SWS context if needed if (!codecMemory->sws) { codecMemory->sws = sws_getContext(frame->width, frame->height, static_cast(frame->format), frame->width, frame->height, AV_PIX_FMT_NV12, 0, nullptr, nullptr, nullptr); } // Use SWS to convert frame output to NV12 format decaf_check(codecMemory->sws); auto frameBuffer = virt_cast(decodedFrameInfo.buffer); uint8_t *dstBuffers[] = { frameBuffer.get(), frameBuffer.get() + frame->height * pitch, }; int dstStride[] = { pitch, pitch }; sws_scale(codecMemory->sws, frame->data, frame->linesize, 0, frame->height, dstBuffers, dstStride); decodeResult->framebuffer = frameBuffer; decodeResult->width = frame->width; decodeResult->height = frame->height; decodeResult->nextLine = pitch; // Copy crop if (frame->crop_top || frame->crop_bottom || frame->crop_left || frame->crop_right) { decodeResult->cropEnableFlag = uint8_t { 1 }; } else { decodeResult->cropEnableFlag = uint8_t { 0 }; } decodeResult->cropTop = static_cast(frame->crop_top); decodeResult->cropBottom = static_cast(frame->crop_bottom); decodeResult->cropLeft = static_cast(frame->crop_left); decodeResult->cropRight = static_cast(frame->crop_right); // Copy pan scan decodeResult->panScanEnableFlag = uint8_t { 0 }; decodeResult->panScanTop = 0; decodeResult->panScanBottom = 0; decodeResult->panScanLeft = 0; decodeResult->panScanRight = 0; for (auto i = 0; i < frame->nb_side_data; ++i) { auto sideData = frame->side_data[i]; if (sideData->type == AV_FRAME_DATA_PANSCAN) { auto panScan = reinterpret_cast(sideData->data); decodeResult->panScanEnableFlag = uint8_t { 1 }; decodeResult->panScanTop = panScan->position[0][0]; decodeResult->panScanLeft = panScan->position[0][1]; decodeResult->panScanRight = decodeResult->panScanLeft + panScan->width; decodeResult->panScanBottom = decodeResult->panScanTop + panScan->height; } } // Copy vui_parameters from decoded frame info decodeResult->vui_parameters_present_flag = decodedFrameInfo.vui_parameters_present_flag; if (decodeResult->vui_parameters_present_flag) { decodeResult->vui_parameters = virt_addrof(decodedFrameInfo.vui_parameters); } else { decodeResult->vui_parameters = nullptr; } // Invoke the frame output callback, right now this is 1 frame at a time // in future we may want to hoist this outside of the loop and emit N frames auto results = StackArray, 5> { }; auto output = StackObject { }; output->frameCount = 1; output->decodeResults = results; output->userMemory = streamMemory->paramUserMemory; results[0] = decodeResult; cafe::invoke(cpu::this_core::state(), streamMemory->paramFramePointerOutput, output); } if (result == AVERROR_EOF || result == AVERROR(EAGAIN)) { // Expected return values are not an error! result = 0; } else { char buffer[255]; av_strerror(result, buffer, 255); gLog->error("avcodec_receive_frame error: {}", buffer); } return result; } /** * Open a H264 decoder. */ H264Error H264DECOpen(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } auto codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { return H264Error::GenericError; } auto context = avcodec_alloc_context3(codec); if (!context) { return H264Error::GenericError; } context->flags |= AV_CODEC_FLAG_LOW_DELAY; context->thread_type = FF_THREAD_SLICE; context->pix_fmt = AV_PIX_FMT_NV12; if (avcodec_open2(context, codec, NULL) < 0) { return H264Error::GenericError; } workMemory->codecMemory->context = context; workMemory->codecMemory->frame = av_frame_alloc(); return H264Error::OK; } /** * Prepare for decoding. */ H264Error H264DECBegin(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } std::memset(virt_addrof(workMemory->streamMemory->decodedFrameInfos).get(), 0, sizeof(workMemory->streamMemory->decodedFrameInfos)); // Open a new parser, because there is no reset function for it and I don't // know if it has internal state which is important :). workMemory->codecMemory->parser = av_parser_init(AV_CODEC_ID_H264); workMemory->codecMemory->inputFrameIndex = 0; workMemory->codecMemory->outputFrameIndex = 0; return H264Error::OK; } /** * Set the bit stream to be read for decoding. */ H264Error H264DECSetBitstream(virt_ptr memory, virt_ptr buffer, uint32_t bufferLength, double timestamp) { if (!memory || !buffer || bufferLength < 4) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } workMemory->bitStream->buffer = buffer; workMemory->bitStream->buffer_length = bufferLength; workMemory->bitStream->bit_position = 0u; workMemory->bitStream->timestamp = timestamp; return H264Error::OK; } /** * Perform decoding of the bitstream and put the output frame into frameBuffer. */ H264Error H264DECExecute(virt_ptr memory, virt_ptr frameBuffer) { if (!memory || !frameBuffer) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } auto bitStream = workMemory->bitStream; auto codecMemory = workMemory->codecMemory; auto streamMemory = workMemory->streamMemory; if (!bitStream->buffer_length) { return H264Error::GenericError; } // Parse the bitstream looking for any SPS to grab latest vui parameters auto sps = StackObject { }; if (internal::decodeNaluSps(bitStream->buffer.get(), bitStream->buffer_length, 0, sps) == H264Error::OK) { // Copy VUI parameters from the SPS codecMemory->vui_parameters_present_flag = sps->vui_parameters_present_flag; if (sps->vui_parameters_present_flag) { auto &vui = codecMemory->vui_parameters; vui.aspect_ratio_info_present_flag = sps->vui_aspect_ratio_info_present_flag; vui.aspect_ratio_idc = sps->vui_aspect_ratio_idc; vui.sar_width = sps->vui_sar_width; vui.sar_height = sps->vui_sar_height; vui.overscan_info_present_flag = sps->vui_overscan_info_present_flag; vui.overscan_appropriate_flag = sps->vui_overscan_appropriate_flag; vui.video_signal_type_present_flag = sps->vui_video_signal_type_present_flag; vui.video_format = sps->vui_video_format; vui.video_full_range_flag = sps->vui_video_full_range_flag; vui.colour_description_present_flag = sps->vui_colour_description_present_flag; vui.colour_primaries = sps->vui_colour_primaries; vui.transfer_characteristics = sps->vui_transfer_characteristics; vui.matrix_coefficients = sps->vui_matrix_coefficients; vui.chroma_loc_info_present_flag = sps->vui_chroma_loc_info_present_flag; vui.chroma_sample_loc_type_top_field = sps->vui_chroma_sample_loc_type_top_field; vui.chroma_sample_loc_type_bottom_field = sps->vui_chroma_sample_loc_type_bottom_field; vui.timing_info_present_flag = sps->vui_timing_info_present_flag; vui.num_units_in_tick = sps->vui_num_units_in_tick; vui.time_scale = sps->vui_time_scale; vui.fixed_frame_rate_flag = sps->vui_fixed_frame_rate_flag; vui.nal_hrd_parameters_present_flag = sps->vui_nal_hrd_parameters_present_flag; vui.vcl_hrd_parameters_present_flag = sps->vui_vcl_hrd_parameters_present_flag; vui.low_delay_hrd_flag = sps->vui_low_delay_hrd_flag; vui.pic_struct_present_flag = sps->vui_pic_struct_present_flag; vui.bitstream_restriction_flag = sps->vui_bitstream_restriction_flag; vui.motion_vectors_over_pic_boundaries_flag = sps->vui_motion_vectors_over_pic_boundaries_flag; vui.max_bytes_per_pic_denom = sps->vui_max_bytes_per_pic_denom; vui.max_bits_per_mb_denom = sps->vui_max_bits_per_mb_denom; vui.log2_max_mv_length_horizontal = sps->vui_log2_max_mv_length_horizontal; vui.log2_max_mv_length_vertical = sps->vui_log2_max_mv_length_vertical; vui.num_reorder_frames = sps->vui_num_reorder_frames; vui.max_dec_frame_buffering = sps->vui_max_dec_frame_buffering; } } // Update the decoded frame info for this frame auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->inputFrameIndex]; codecMemory->inputFrameIndex = (codecMemory->inputFrameIndex + 1) % streamMemory->decodedFrameInfos.size(); decodedFrameInfo.buffer = frameBuffer; decodedFrameInfo.timestamp = bitStream->timestamp; // Copy the latest VUI parameters // HACK: This is not technically correct and we should probably parse the // slice headers to see which SPS they are referencing. decodedFrameInfo.vui_parameters_present_flag = codecMemory->vui_parameters_present_flag; if (decodedFrameInfo.vui_parameters_present_flag) { std::memcpy(virt_addrof(decodedFrameInfo.vui_parameters).get(), &codecMemory->vui_parameters, sizeof(decodedFrameInfo.vui_parameters)); } // Submit packet to ffmpeg auto* packet = av_packet_alloc(); packet->data = bitStream->buffer.get(); packet->size = bitStream->buffer_length; auto result = avcodec_send_packet(codecMemory->context, packet); if (result != 0) { char buffer[255]; av_strerror(result, buffer, 255); gLog->error("H264DECExecute avcodec_send_packet error: {}", buffer); av_packet_free(&packet); return static_cast(result); } av_packet_free(&packet); bitStream->buffer_length = 0u; // Read any completed frames back from ffmpeg result = receiveFrames(workMemory); if (result != 0) { return static_cast(result); } // Return 100% decoded frame return static_cast(0x80 | 100); } /** * Flush any internally buffered frames. */ H264Error H264DECFlush(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } // Discard internal state and buffered frames if (workMemory->codecMemory->context) { avcodec_flush_buffers(workMemory->codecMemory->context); } return H264Error::OK; } /** * End decoding of the current stream. */ H264Error H264DECEnd(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } // Flush the stream H264DECFlush(memory); if (workMemory->codecMemory->parser) { av_parser_close(workMemory->codecMemory->parser); workMemory->codecMemory->parser = nullptr; } return H264Error::OK; } /** * Cleanup the decoder. */ H264Error H264DECClose(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } av_frame_free(&workMemory->codecMemory->frame); avcodec_free_context(&workMemory->codecMemory->context); sws_freeContext(workMemory->codecMemory->sws); workMemory->codecMemory->sws = nullptr; // Just in case someone did not call H264DECEnd if (workMemory->codecMemory->parser) { av_parser_close(workMemory->codecMemory->parser); workMemory->codecMemory->parser = nullptr; } return H264Error::OK; } #if 0 // ffmpeg based H264DECCheckDecunitLength H264Error H264DECCheckDecunitLength(virt_ptr memory, virt_ptr buffer, int32_t bufferLength, int32_t offset, virt_ptr outLength) { auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } uint8_t *outBuf = nullptr; int outBufSize = 0; auto ret = av_parser_parse2(workMemory->codecMemory->parser, workMemory->codecMemory->context, &outBuf, &outBufSize, buffer.get() + offset, bufferLength - offset, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (ret < 0) { return H264Error::GenericError; } *outLength = outBufSize; return H264Error::OK; } #endif // if 0 } // namespace cafe::h264::ffmpeg #endif // ifdef DECAF_FFMPEG ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_decode_ffmpeg.h ================================================ #pragma once #include namespace cafe::h264::ffmpeg { H264Error H264DECOpen(virt_ptr memory); H264Error H264DECBegin(virt_ptr memory); H264Error H264DECSetBitstream(virt_ptr memory, virt_ptr buffer, uint32_t bufferLength, double timestamp); H264Error H264DECExecute(virt_ptr memory, virt_ptr frameBuffer); H264Error H264DECFlush(virt_ptr memory); H264Error H264DECEnd(virt_ptr memory); H264Error H264DECClose(virt_ptr memory); } // namespace cafe::h264::ffmpeg ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_decode_null.cpp ================================================ #include "h264.h" #include "h264_decode.h" #include "h264_stream.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" #include namespace cafe::h264 { // This is decaf specific stuff - does not match structure in h264.rpl struct H264CodecMemory { int inputFrameIndex; int outputFrameIndex; int width; int height; //! HACK: This is just a copy of the most recently seen vui_parameters in //! the stream, technically it should probably be the ones that are in the //! SPS for the given PPS in given frame's slice headers. uint8_t vui_parameters_present_flag; H264DecodedVuiParameters vui_parameters; }; } // namespace cafe::h264 namespace cafe::h264::null { static constexpr uint8_t NullImage[6][20] = { // H264 { 1,0,0,1, 0, 1,1,1,0, 0, 0,1,1,0, 0, 1,0,0,1, 0, }, { 1,0,0,1, 0, 0,0,0,1, 0, 1,0,0,0, 0, 1,0,0,1, 0, }, { 1,1,1,1, 0, 0,1,1,0, 0, 1,1,1,0, 0, 1,1,1,1, 0, }, { 1,0,0,1, 0, 1,0,0,0, 0, 1,0,0,1, 0, 0,0,0,1, 0, }, { 1,0,0,1, 0, 1,1,1,1, 0, 0,1,1,0, 0, 0,0,0,1, 0, }, { 0,0,0,0, 0, 0,0,0,0, 0, 0,0,0,0, 0, 0,0,0,0, 0, }, }; static int receiveFrames(virt_ptr workMemory) { auto codecMemory = workMemory->codecMemory; auto streamMemory = workMemory->streamMemory; const auto pitch = align_up(codecMemory->width, 256); while (codecMemory->outputFrameIndex != codecMemory->inputFrameIndex) { // Get the decoded frame info auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->outputFrameIndex]; codecMemory->outputFrameIndex = (codecMemory->outputFrameIndex + 1) % streamMemory->decodedFrameInfos.size(); // Fill framebuffer with our fake image auto frameBuffer = virt_cast(decodedFrameInfo.buffer); auto dst = frameBuffer.get(); // Y for (auto y = 0; y < codecMemory->height; ++y) { for (auto x = 0; x < codecMemory->width; ++x) { dst[x] = NullImage[(y / 2) % 6][(x / 2) % 20] ? 0xFF : 0x00; } dst += pitch; } // Interleaved UV for (auto y = 0; y < codecMemory->height / 2; ++y) { for (auto x = 0; x < codecMemory->width / 2; ++x) { dst[x * 2 + 0] = x % 255; dst[x * 2 + 1] = y % 255; } dst += pitch; } auto decodeResult = StackObject { }; decodeResult->status = 100; decodeResult->timestamp = decodedFrameInfo.timestamp; decodeResult->framebuffer = frameBuffer; decodeResult->width = codecMemory->width; decodeResult->height = codecMemory->height; decodeResult->nextLine = pitch; // Crop decodeResult->cropEnableFlag = uint8_t { 0 }; decodeResult->cropTop = 0; decodeResult->cropBottom = 0; decodeResult->cropLeft = 0; decodeResult->cropRight = 0; // Copy pan scan decodeResult->panScanEnableFlag = uint8_t { 0 }; decodeResult->panScanTop = 0; decodeResult->panScanBottom = 0; decodeResult->panScanLeft = 0; decodeResult->panScanRight = 0; // Copy vui_parameters from decoded frame info decodeResult->vui_parameters_present_flag = decodedFrameInfo.vui_parameters_present_flag; if (decodeResult->vui_parameters_present_flag) { decodeResult->vui_parameters = virt_addrof(decodedFrameInfo.vui_parameters); } else { decodeResult->vui_parameters = nullptr; } // Invoke the frame output callback auto results = StackArray, 5> { }; auto output = StackObject { }; output->frameCount = 1; output->decodeResults = results; output->userMemory = streamMemory->paramUserMemory; results[0] = decodeResult; cafe::invoke(cpu::this_core::state(), streamMemory->paramFramePointerOutput, output); } return H264Error::OK; } /** * Open a H264 decoder. */ H264Error H264DECOpen(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } return H264Error::OK; } /** * Prepare for decoding. */ H264Error H264DECBegin(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } std::memset(virt_addrof(workMemory->streamMemory->decodedFrameInfos).get(), 0, sizeof(workMemory->streamMemory->decodedFrameInfos)); workMemory->codecMemory->inputFrameIndex = 0; workMemory->codecMemory->outputFrameIndex = 0; return H264Error::OK; } /** * Set the bit stream to be read for decoding. */ H264Error H264DECSetBitstream(virt_ptr memory, virt_ptr buffer, uint32_t bufferLength, double timestamp) { if (!memory || !buffer || bufferLength < 4) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } workMemory->bitStream->buffer = buffer; workMemory->bitStream->buffer_length = bufferLength; workMemory->bitStream->bit_position = 0u; workMemory->bitStream->timestamp = timestamp; return H264Error::OK; } /** * Perform decoding of the bitstream and put the output frame into frameBuffer. */ H264Error H264DECExecute(virt_ptr memory, virt_ptr frameBuffer) { if (!memory || !frameBuffer) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } auto bitStream = workMemory->bitStream; auto codecMemory = workMemory->codecMemory; auto streamMemory = workMemory->streamMemory; if (!bitStream->buffer_length) { return H264Error::GenericError; } // Parse the bitstream looking for any SPS auto sps = StackObject { }; if (internal::decodeNaluSps(bitStream->buffer.get(), bitStream->buffer_length, 0, sps) == H264Error::OK) { codecMemory->width = sps->pic_width; codecMemory->height = sps->pic_height; if (!sps->frame_mbs_only_flag) { codecMemory->height = 2 * sps->pic_height; } // Copy VUI parameters from the SPS codecMemory->vui_parameters_present_flag = sps->vui_parameters_present_flag; if (sps->vui_parameters_present_flag) { auto &vui = codecMemory->vui_parameters; vui.aspect_ratio_info_present_flag = sps->vui_aspect_ratio_info_present_flag; vui.aspect_ratio_idc = sps->vui_aspect_ratio_idc; vui.sar_width = sps->vui_sar_width; vui.sar_height = sps->vui_sar_height; vui.overscan_info_present_flag = sps->vui_overscan_info_present_flag; vui.overscan_appropriate_flag = sps->vui_overscan_appropriate_flag; vui.video_signal_type_present_flag = sps->vui_video_signal_type_present_flag; vui.video_format = sps->vui_video_format; vui.video_full_range_flag = sps->vui_video_full_range_flag; vui.colour_description_present_flag = sps->vui_colour_description_present_flag; vui.colour_primaries = sps->vui_colour_primaries; vui.transfer_characteristics = sps->vui_transfer_characteristics; vui.matrix_coefficients = sps->vui_matrix_coefficients; vui.chroma_loc_info_present_flag = sps->vui_chroma_loc_info_present_flag; vui.chroma_sample_loc_type_top_field = sps->vui_chroma_sample_loc_type_top_field; vui.chroma_sample_loc_type_bottom_field = sps->vui_chroma_sample_loc_type_bottom_field; vui.timing_info_present_flag = sps->vui_timing_info_present_flag; vui.num_units_in_tick = sps->vui_num_units_in_tick; vui.time_scale = sps->vui_time_scale; vui.fixed_frame_rate_flag = sps->vui_fixed_frame_rate_flag; vui.nal_hrd_parameters_present_flag = sps->vui_nal_hrd_parameters_present_flag; vui.vcl_hrd_parameters_present_flag = sps->vui_vcl_hrd_parameters_present_flag; vui.low_delay_hrd_flag = sps->vui_low_delay_hrd_flag; vui.pic_struct_present_flag = sps->vui_pic_struct_present_flag; vui.bitstream_restriction_flag = sps->vui_bitstream_restriction_flag; vui.motion_vectors_over_pic_boundaries_flag = sps->vui_motion_vectors_over_pic_boundaries_flag; vui.max_bytes_per_pic_denom = sps->vui_max_bytes_per_pic_denom; vui.max_bits_per_mb_denom = sps->vui_max_bits_per_mb_denom; vui.log2_max_mv_length_horizontal = sps->vui_log2_max_mv_length_horizontal; vui.log2_max_mv_length_vertical = sps->vui_log2_max_mv_length_vertical; vui.num_reorder_frames = sps->vui_num_reorder_frames; vui.max_dec_frame_buffering = sps->vui_max_dec_frame_buffering; } } bitStream->buffer_length = 0u; // Update the decoded frame info for this frame auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->inputFrameIndex]; codecMemory->inputFrameIndex = (codecMemory->inputFrameIndex + 1) % streamMemory->decodedFrameInfos.size(); decodedFrameInfo.buffer = frameBuffer; decodedFrameInfo.timestamp = bitStream->timestamp; decodedFrameInfo.vui_parameters_present_flag = codecMemory->vui_parameters_present_flag; if (decodedFrameInfo.vui_parameters_present_flag) { std::memcpy(virt_addrof(decodedFrameInfo.vui_parameters).get(), &codecMemory->vui_parameters, sizeof(decodedFrameInfo.vui_parameters)); } auto result = receiveFrames(workMemory); if (result == 0) { return static_cast(0x80 | 100); } return static_cast(result); } /** * Flush any internally buffered frames. */ H264Error H264DECFlush(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } // We do not buffer frames, so nothing to flush. return H264Error::OK; } /** * End decoding of the current stream. */ H264Error H264DECEnd(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } H264DECFlush(memory); return H264Error::OK; } /** * Cleanup the decoder. */ H264Error H264DECClose(virt_ptr memory) { if (!memory) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); if (!workMemory) { return H264Error::InvalidParameter; } return H264Error::OK; } } // namespace cafe::h264::null ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_decode_null.h ================================================ #pragma once #include namespace cafe::h264::null { H264Error H264DECOpen(virt_ptr memory); H264Error H264DECBegin(virt_ptr memory); H264Error H264DECSetBitstream(virt_ptr memory, virt_ptr buffer, uint32_t bufferLength, double timestamp); H264Error H264DECExecute(virt_ptr memory, virt_ptr frameBuffer); H264Error H264DECFlush(virt_ptr memory); H264Error H264DECEnd(virt_ptr memory); H264Error H264DECClose(virt_ptr memory); } // namespace cafe::h264::null ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_enum.h ================================================ #ifndef H264_ENUM_H #define H264_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(h264) ENUM_BEG(EntryPoint, int8_t) ENUM_VALUE(InitParam, 4) ENUM_VALUE(SetParam, 5) ENUM_VALUE(Open, 6) ENUM_VALUE(Begin, 7) ENUM_VALUE(SetBitStream, 8) ENUM_VALUE(Execute, 9) ENUM_VALUE(End, 10) ENUM_VALUE(Close, 11) ENUM_END(EntryPoint) ENUM_BEG(H264Error, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(InvalidPps, 24) ENUM_VALUE(InvalidSps, 26) ENUM_VALUE(InvalidSliceHeader, 61) ENUM_VALUE(GenericError, 0x1000000) ENUM_VALUE(InvalidParameter, 0x1010000) ENUM_VALUE(OutOfMemory, 0x1020000) ENUM_VALUE(InvalidProfile, 0x1080000) ENUM_END(H264Error) ENUM_BEG(H264Parameter, int32_t) ENUM_VALUE(FramePointerOutput, 1) ENUM_VALUE(OutputPerFrame, 0x20000002) ENUM_VALUE(Unknown0x20000010, 0x20000010) ENUM_VALUE(Unknown0x20000030, 0x20000030) ENUM_VALUE(Unknown0x20000040, 0x20000040) ENUM_VALUE(UserMemory, 0x70000001) ENUM_END(H264Parameter) ENUM_BEG(SliceType, int32_t) ENUM_VALUE(P, 0) ENUM_VALUE(B, 1) ENUM_VALUE(I, 2) ENUM_VALUE(EP, 0) ENUM_VALUE(EB, 1) ENUM_VALUE(EI, 2) ENUM_VALUE(SP, 3) ENUM_VALUE(SI, 4) ENUM_VALUE(OnlyP, 5) ENUM_VALUE(OnlyB, 6) ENUM_VALUE(OnlyI, 7) ENUM_VALUE(OnlyEP, 5) ENUM_VALUE(OnlyEB, 6) ENUM_VALUE(OnlyEI, 7) ENUM_VALUE(OnlySP, 8) ENUM_VALUE(OnlySI, 9) ENUM_END(SliceType) ENUM_BEG(NaluType, int32_t) ENUM_VALUE(NonIdr, 1) ENUM_VALUE(DataPartitionA, 2) ENUM_VALUE(DataPartitionB, 3) ENUM_VALUE(DataPartitionC, 4) ENUM_VALUE(Idr, 5) ENUM_VALUE(Sei, 6) ENUM_VALUE(Sps, 7) ENUM_VALUE(Pps, 8) ENUM_VALUE(Aud, 9) ENUM_VALUE(EndOfSequence, 10) ENUM_VALUE(EndOfStream, 11) ENUM_VALUE(Filler, 12) ENUM_VALUE(SpsExt, 13) ENUM_VALUE(PrefixNal, 14) ENUM_VALUE(SubsetSps, 15) ENUM_VALUE(Dps, 16) ENUM_VALUE(CodedSliceAux, 19) ENUM_VALUE(CodedSliceSvcExt, 20) ENUM_END(NaluType) ENUM_NAMESPACE_EXIT(h264) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef SWKBD_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_stream.cpp ================================================ #include "h264.h" #include "h264_bitstream.h" #include "h264_decode.h" #include "h264_enum.h" #include "h264_stream.h" #include "cafe/cafe_stackobject.h" #include namespace cafe::h264 { namespace internal { static void clearPictureParameterSet(virt_ptr pps) { std::memset(pps.get(), 0, sizeof(H264PictureParameterSet)); pps->scalingList4x4.fill(16u); pps->scalingList8x8.fill(16u); } static void clearSequenceParameterSet(virt_ptr sps) { std::memset(sps.get(), 0, sizeof(H264SequenceParameterSet)); sps->scalingList4x4.fill(16u); sps->scalingList8x8.fill(16u); } static bool rbspHasMoreData(BitStream &bs) { if (bs.eof()) { return false; } // No rbsp_stop_bit yet if (bs.peekU1() == 0) { return true; } // Next bit is 1, is it the rsbp_stop_bit? only if the rest of bits are 0 auto bsTmp = BitStream { bs }; bsTmp.readU1(); while (!bsTmp.eof()) { // A later bit was 1, it wasn't the rsbp_stop_bit if (bsTmp.readU1() == 1) { return true; } } // All following bits were 0, it was the rsbp_stop_bit return false; } static void rbspReadTrailingBits(BitStream &bs) { // Read rbsp_stop_one_bit bs.readU1(); // Read rbsp_alignment_zero_bit until we are aligned while (!bs.byteAligned()) { bs.readU1(); } } static void readScalingList(BitStream &bs, uint8_t *scalingList, int sizeOfScalingList) { uint8_t lastScale = 8; uint8_t nextScale = 8; for (int i = 0; i < sizeOfScalingList; ++i) { if (nextScale != 0) { nextScale = (lastScale + bs.readSE() + 256) % 256; } scalingList[i] = (nextScale == 0) ? lastScale : nextScale; lastScale = scalingList[i]; } } static void readHrdParameters(BitStream &bs, virt_ptr sps) { sps->hrd_cpb_cnt_minus1 = static_cast(bs.readUE()); if (sps->hrd_cpb_cnt_minus1 < 32) { sps->hrd_bit_rate_scale = bs.readU4(); sps->hrd_cpb_size_scale = bs.readU4(); for (auto i = 0; i <= sps->hrd_cpb_cnt_minus1; ++i) { sps->hrd_bit_rate_value_minus1[i] = static_cast(bs.readUE()); sps->hrd_cpb_size_value_minus1[i] = static_cast(bs.readUE()); sps->hrd_cbr_flag[i] = bs.readU1(); } sps->hrd_initial_cpb_removal_delay_length_minus1 = bs.readU5(); sps->hrd_cpb_removal_delay_length_minus1 = bs.readU5(); sps->hrd_dpb_output_delay_length_minus1 = bs.readU5(); sps->hrd_time_offset_length = bs.readU5(); } } static void readVuiParameters(BitStream &bs, virt_ptr sps) { sps->vui_aspect_ratio_info_present_flag = bs.readU1(); if (sps->vui_aspect_ratio_info_present_flag) { sps->vui_aspect_ratio_idc = bs.readU8(); if (sps->vui_aspect_ratio_idc == SarExtended) { sps->vui_sar_width = bs.readU16(); sps->vui_sar_height = bs.readU16(); } } sps->vui_overscan_info_present_flag = bs.readU1(); if (sps->vui_overscan_info_present_flag) { sps->vui_overscan_appropriate_flag = bs.readU1(); } sps->vui_video_signal_type_present_flag = bs.readU1(); if (sps->vui_video_signal_type_present_flag) { sps->vui_video_format = bs.readU3(); sps->vui_video_full_range_flag = bs.readU1(); sps->vui_colour_description_present_flag = bs.readU1(); if (sps->vui_colour_description_present_flag) { sps->vui_colour_primaries = bs.readU8(); sps->vui_transfer_characteristics = bs.readU8(); sps->vui_matrix_coefficients = bs.readU8(); } } sps->vui_chroma_loc_info_present_flag = bs.readU1(); if (sps->vui_chroma_loc_info_present_flag) { sps->vui_chroma_sample_loc_type_top_field = static_cast(bs.readUE()); sps->vui_chroma_sample_loc_type_bottom_field = static_cast(bs.readUE()); } sps->vui_timing_info_present_flag = bs.readU1(); if (sps->vui_timing_info_present_flag) { sps->vui_num_units_in_tick = bs.readU(32); sps->vui_time_scale = bs.readU(32); sps->vui_fixed_frame_rate_flag = bs.readU1(); } sps->vui_nal_hrd_parameters_present_flag = bs.readU1(); if (sps->vui_nal_hrd_parameters_present_flag) { sps->unk0x95E = uint8_t { 1 }; readHrdParameters(bs, sps); } sps->vui_vcl_hrd_parameters_present_flag = bs.readU1(); if (sps->vui_vcl_hrd_parameters_present_flag) { sps->unk0x95F = uint8_t { 1 }; readHrdParameters(bs, sps); } if (sps->vui_nal_hrd_parameters_present_flag || sps->vui_vcl_hrd_parameters_present_flag) { sps->unk0x960 = uint8_t { 1 }; sps->vui_low_delay_hrd_flag = bs.readU1(); } sps->vui_pic_struct_present_flag = bs.readU1(); sps->vui_bitstream_restriction_flag = bs.readU1(); if (sps->vui_bitstream_restriction_flag) { sps->vui_motion_vectors_over_pic_boundaries_flag = bs.readU1(); sps->vui_max_bytes_per_pic_denom = static_cast(bs.readUE()); sps->vui_max_bits_per_mb_denom = static_cast(bs.readUE()); sps->vui_log2_max_mv_length_horizontal = static_cast(bs.readUE()); sps->vui_log2_max_mv_length_vertical = static_cast(bs.readUE()); sps->vui_num_reorder_frames = static_cast(bs.readUE()); sps->unk0x7B0 = uint16_t { 5 }; sps->vui_max_dec_frame_buffering = static_cast(bs.readUE() + 1); } } static H264Error readSequenceParameterSet(virt_ptr streamMemory, const uint8_t *buffer, uint32_t bufferSize, virt_ptr outSps, size_t *outBytesRead) { auto sps = StackObject { }; clearSequenceParameterSet(sps); auto bs = BitStream { buffer, bufferSize }; sps->profile_idc = bs.readU8(); if (sps->profile_idc != 66 && sps->profile_idc != 77 && sps->profile_idc != 100) { return H264Error::InvalidProfile; } sps->constraint_set = bs.readU8(); sps->level_idc = bs.readU8(); sps->seq_parameter_set_id = static_cast(bs.readUE()); if (sps->seq_parameter_set_id >= MaxSeqParameterSets) { return H264Error::InvalidSps; } if (sps->profile_idc == 100 || sps->profile_idc == 110 || sps->profile_idc == 122 || sps->profile_idc == 144) { sps->chroma_format_idc = static_cast(bs.readUE()); if (sps->chroma_format_idc == 3) { sps->residual_colour_transform_flag = bs.readU1(); } sps->bit_depth_luma_minus8 = static_cast(bs.readUE()); sps->bit_depth_chroma_minus8 = static_cast(bs.readUE()); sps->qpprime_y_zero_transform_bypass_flag = bs.readU1(); sps->seq_scaling_matrix_present_flag = bs.readU1(); if (sps->seq_scaling_matrix_present_flag) { for (auto i = 0u; i < 8; ++i) { auto seq_scaling_list_present_flag = bs.readU1(); if (seq_scaling_list_present_flag) { if (i < 6) { readScalingList(bs, (virt_addrof(sps->scalingList4x4) + 16 * i).get(), 16); } else { readScalingList(bs, (virt_addrof(sps->scalingList8x8) + 64 * i).get(), 64); } } } } } sps->log2_max_frame_num_minus4 = static_cast(bs.readUE()); if (sps->log2_max_frame_num_minus4 > 12) { return H264Error::InvalidSps; } sps->log2_max_frame_num = static_cast(sps->log2_max_frame_num_minus4 + 4); sps->max_frame_num = 1u << sps->log2_max_frame_num; sps->pic_order_cnt_type = static_cast(bs.readUE()); if (sps->pic_order_cnt_type > 2) { return H264Error::InvalidSps; } if (sps->pic_order_cnt_type == 0) { sps->log2_max_pic_order_cnt_lsb_minus4 = static_cast(bs.readUE()); sps->max_pic_order_cnt_lsb = 1u << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4); } else if (sps->pic_order_cnt_type == 1) { sps->delta_pic_order_always_zero_flag = bs.readU1(); sps->offset_for_non_ref_pic = bs.readSE(); sps->offset_for_top_to_bottom_field = bs.readSE(); sps->num_ref_frames_in_pic_order_cnt_cycle = static_cast(bs.readUE()); for (int i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; ++i) { sps->offset_for_ref_frame[i] = bs.readSE(); } } sps->num_ref_frames = static_cast(bs.readUE()); if (sps->num_ref_frames > 16) { return H264Error::InvalidSps; } sps->gaps_in_frame_num_value_allowed_flag = bs.readU1(); sps->pic_width_in_mbs = static_cast(bs.readUE() + 1); sps->pic_width = static_cast(16 * sps->pic_width_in_mbs); sps->pic_height_in_map_units = static_cast(bs.readUE() + 1); sps->pic_height = static_cast(16 * sps->pic_height_in_map_units); sps->pic_size_in_mbs = static_cast(sps->pic_width_in_mbs * sps->pic_height_in_map_units); sps->frame_mbs_only_flag = bs.readU1(); if (!sps->frame_mbs_only_flag) { sps->mb_adaptive_frame_field_flag = bs.readU1(); } sps->unk0x7B8 = static_cast((2 - sps->frame_mbs_only_flag) * sps->pic_height_in_map_units); sps->direct_8x8_inference_flag = bs.readU1(); sps->frame_cropping_flag = bs.readU1(); if (sps->frame_cropping_flag) { sps->frame_crop_left_offset = static_cast(bs.readUE()); sps->frame_crop_right_offset = static_cast(bs.readUE()); sps->frame_crop_top_offset = static_cast(bs.readUE()); sps->frame_crop_bottom_offset = static_cast(bs.readUE()); } if (sps->level_idc > 51) { sps->level_idc = uint16_t { 51 }; } sps->unk0x7B0 = uint16_t { 5 }; sps->vui_parameters_present_flag = bs.readU1(); if (sps->vui_parameters_present_flag) { readVuiParameters(bs, sps); } if (sps->unk0x7B0 > 5) { sps->unk0x7B0 = uint16_t { 5 }; } rbspReadTrailingBits(bs); sps->valid = uint8_t { 1 }; sps->unk0x7CC = static_cast(sps->pic_width_in_mbs * sps->unk0x7B8); sps->unk0x7C6 = static_cast(sps->pic_width_in_mbs * sps->pic_height_in_map_units); if (outSps) { *outSps = *sps; } else { streamMemory->spsTable[sps->seq_parameter_set_id] = *sps; } if (outBytesRead) { *outBytesRead = bs.bytesRead(); } return H264Error::OK; } static H264Error readPictureParameterSet(virt_ptr streamMemory, const uint8_t *buffer, uint32_t bufferSize, size_t *outBytesRead) { auto bs = BitStream { buffer, bufferSize }; auto pic_parameter_set_id = bs.readUE(); if (pic_parameter_set_id >= MaxPicParameterSets) { return H264Error::InvalidPps; } auto pps = virt_addrof(streamMemory->ppsTable[pic_parameter_set_id]); clearPictureParameterSet(pps); pps->seq_parameter_set_id = static_cast(bs.readUE()); if (pps->seq_parameter_set_id >= MaxSeqParameterSets) { return H264Error::InvalidPps; } pps->entropy_coding_mode_flag = bs.readU1(); pps->pic_order_present_flag = bs.readU1(); pps->num_slice_groups_minus1 = static_cast(bs.readUE()); if (pps->num_slice_groups_minus1 > 7) { return H264Error::InvalidPps; } if (pps->num_slice_groups_minus1 > 0) { pps->slice_group_map_type = static_cast(bs.readUE()); if (pps->slice_group_map_type == 0) { for (int i = 0u; i <= pps->num_slice_groups_minus1; ++i) { pps->run_length_minus1[i] = static_cast(bs.readUE()); } } else if (pps->slice_group_map_type == 2) { for (auto i = 0u; i < pps->num_slice_groups_minus1; i++) { pps->top_left[i] = static_cast(bs.readUE()); pps->bottom_right[i] = static_cast(bs.readUE()); } } else if (pps->slice_group_map_type == 3 || pps->slice_group_map_type == 4 || pps->slice_group_map_type == 5) { pps->slice_group_change_direction_flag = bs.readU1(); pps->slice_group_change_rate_minus1 = static_cast(bs.readUE()); } else if (pps->slice_group_map_type == 6) { pps->pic_size_in_map_units_minus1 = static_cast(bs.readUE()); if (pps->pic_size_in_map_units_minus1 > 30800) { return H264Error::InvalidPps; } auto sliceGroupIdNumBits = static_cast(std::log2(pps->num_slice_groups_minus1 + 1)); for (int i = 0; i <= pps->pic_size_in_map_units_minus1; i++) { // pps->slice_group_id bs.readU(sliceGroupIdNumBits); } } } pps->num_ref_idx_l0_active = static_cast(bs.readUE() + 1); if (pps->num_ref_idx_l0_active > 31) { return H264Error::InvalidPps; } pps->num_ref_idx_l1_active = static_cast(bs.readUE() + 1); if (pps->num_ref_idx_l1_active > 31) { return H264Error::InvalidPps; } pps->weighted_pred_flag = bs.readU1(); pps->weighted_bipred_idc = bs.readU2(); pps->pic_init_qp_minus26 = static_cast(bs.readSE()); pps->pic_init_qs_minus26 = static_cast(bs.readSE()); pps->chroma_qp_index_offset = static_cast(bs.readSE()); pps->deblocking_filter_control_present_flag = bs.readU1(); pps->constrained_intra_pred_flag = bs.readU1(); pps->redundant_pic_cnt_present_flag = bs.readU1(); auto &sps = streamMemory->spsTable[pps->seq_parameter_set_id]; if ((sps.profile_idc == 100 || sps.profile_idc == 110 || sps.profile_idc == 122 || sps.profile_idc == 144) && rbspHasMoreData(bs)) { pps->transform_8x8_mode_flag = bs.readU1(); pps->pic_scaling_matrix_present_flag = bs.readU1(); if (pps->pic_scaling_matrix_present_flag) { for (auto i = 0; i < 6 + 2 * pps->transform_8x8_mode_flag; i++) { pps->pic_scaling_list_present_flag[i] = bs.readU1(); if (pps->pic_scaling_list_present_flag[i]) { if (i < 6) { readScalingList(bs, (virt_addrof(pps->scalingList4x4) + 16 * i).get(), 16); } else { readScalingList(bs, (virt_addrof(pps->scalingList8x8) + 64 * i).get(), 64); } } } } pps->second_chroma_qp_index_offset = static_cast(bs.readSE()); } else { pps->second_chroma_qp_index_offset = pps->chroma_qp_index_offset; } pps->valid = uint8_t { 1 }; rbspReadTrailingBits(bs); streamMemory->ppsTable[pic_parameter_set_id] = *pps; if (outBytesRead) { *outBytesRead = bs.bytesRead(); } return H264Error::OK; } static uint8_t decodeSliceType(uint32_t sliceType) { if (sliceType <= 2u) { return static_cast(sliceType); } else if (sliceType >= 5u && sliceType <= 7u) { return static_cast(sliceType - 5); } return 0; } static H264Error readSliceHeader(virt_ptr streamMemory, NaluType naluType, const uint8_t *buffer, uint32_t bufferSize, virt_ptr slice, size_t *outBytesRead) { auto bs = BitStream { buffer, bufferSize }; slice->field_02 = uint8_t { 1 }; slice->first_mb_in_slice = static_cast(bs.readUE()); slice->slice_type = decodeSliceType(bs.readUE()); slice->pic_parameter_set_id = static_cast(bs.readUE()); if (slice->pic_parameter_set_id >= MaxPicParameterSets) { return H264Error::InvalidSliceHeader; } auto &pps = streamMemory->ppsTable[slice->pic_parameter_set_id]; if (!pps.valid) { return H264Error::InvalidSliceHeader; } auto &sps = streamMemory->spsTable[pps.seq_parameter_set_id]; if (!sps.valid) { return H264Error::InvalidSliceHeader; } streamMemory->currentSps = sps; streamMemory->currentPps = pps; // TODO: More error checking... slice->frame_num = static_cast(bs.readU(sps.log2_max_frame_num)); if (!sps.frame_mbs_only_flag) { slice->field_pic_flag = bs.readU1(); if (slice->field_pic_flag) { slice->bottom_field_flag = bs.readU1(); } } if (naluType == NaluType::Idr) { slice->idr_pic_id = bs.readUE(); } if (sps.pic_order_cnt_type == 0) { slice->pic_order_cnt_lsb = bs.readU(sps.log2_max_pic_order_cnt_lsb_minus4 + 4); if (pps.pic_order_present_flag && !slice->field_pic_flag) { slice->delta_pic_order_cnt_bottom = bs.readSE(); } else { slice->delta_pic_order_cnt_bottom = 0; } } if (sps.pic_order_cnt_type == 1 && !sps.delta_pic_order_always_zero_flag) { slice->delta_pic_order_cnt[0] = bs.readSE(); if (pps.pic_order_present_flag && !slice->field_pic_flag) { slice->delta_pic_order_cnt[1] = bs.readSE(); } } if (pps.redundant_pic_cnt_present_flag) { slice->redundant_pic_cnt = static_cast(bs.readUE()); if (slice->redundant_pic_cnt > 137) { return H264Error::InvalidSliceHeader; } } if (slice->slice_type == SliceType::B) { slice->direct_spatial_mv_pred_flag = bs.readU1(); } if (slice->slice_type == SliceType::P || slice->slice_type == SliceType::B) { slice->num_ref_idx_active_override_flag = bs.readU1(); if (slice->num_ref_idx_active_override_flag) { slice->num_ref_idx_l0_active = static_cast(bs.readUE() + 1); if (slice->num_ref_idx_l0_active > 31) { return H264Error::InvalidSliceHeader; } if (slice->num_ref_idx_l0_active) { slice->num_ref_idx_l1_active = static_cast(bs.readUE() + 1); if (slice->num_ref_idx_l1_active > 31) { return H264Error::InvalidSliceHeader; } } } } *outBytesRead = bs.bytesRead(); return H264Error::OK; } /** * Find and decode SPS from a NALU stream. */ H264Error decodeNaluSps(const uint8_t *buffer, int bufferLength, int offset, virt_ptr sps) { // Find SPS while (offset + 4 < bufferLength) { if (buffer[offset + 0] == 0 && buffer[offset + 1] == 0 && buffer[offset + 2] == 1 && H264NaluHeader::get(buffer[offset + 3]).type() == NaluType::Sps) { break; } ++offset; } offset += 4; if (offset >= bufferLength) { return H264Error::GenericError; } // Decode SPS internal::readSequenceParameterSet(nullptr, buffer + offset, bufferLength - offset, sps, nullptr); return H264Error::OK; } } // namespace internal /** * Check that the stream contains sufficient data to decode an entire frame. */ H264Error H264DECCheckDecunitLength(virt_ptr memory, virt_ptr buffer, int32_t bufferLength, int32_t offset, virt_ptr outLength) { if (!memory || !buffer || !outLength || bufferLength < 4 || offset < 0 || bufferLength <= offset) { return H264Error::InvalidParameter; } auto workMemory = internal::getWorkMemory(memory); auto readSlice = false; auto readField = false; auto readFrameStart = false; auto readFrameEnd = false; auto start = offset; while (offset < bufferLength - 4) { // Search for NALU header if (buffer[offset + 0] != 0 || buffer[offset + 1] != 0 || buffer[offset + 2] != 1) { offset++; continue; } auto naluType = H264NaluHeader::get(buffer[offset + 3]).type(); auto readBuffer = buffer.get() + (offset + 4); auto readLength = bufferLength - (offset + 4); if (naluType == NaluType::Idr || naluType == NaluType::NonIdr) { auto bytesRead = size_t { 0 }; auto slice = StackObject { }; if (internal::readSliceHeader(workMemory->streamMemory, naluType, readBuffer, readLength, slice, &bytesRead)) { return H264Error::InvalidParameter; } if (readSlice && slice->first_mb_in_slice == 0) { // If we have read a slice and first_mb_in_slice == 0 then this // slice is part of the next frame. readFrameEnd = true; } else if (slice->field_pic_flag == 0) { // If this is not a field, then we have a frame, but we still need // to find the end. readFrameStart = true; } else if (readField) { // If this is the second field, then we have a frame, but we still // need to find the end. readFrameStart = true; } else { // First field, we need to read next field. readField = true; } readSlice = true; if (readFrameEnd) { break; } offset += static_cast(bytesRead); } else if (readFrameStart && naluType >= NaluType::Sps) { // If we are looking for the frame end, and this is a non-slice NALU // then we have found it! readFrameEnd = true; break; } if (naluType == NaluType::Sps) { auto bytesRead = size_t { 0 }; if (internal::readSequenceParameterSet(workMemory->streamMemory, readBuffer, readLength, nullptr, &bytesRead)) { return H264Error::InvalidParameter; } offset += static_cast(bytesRead); } else if (naluType == NaluType::Pps) { auto bytesRead = size_t { 0 }; if (internal::readPictureParameterSet(workMemory->streamMemory, readBuffer, readLength, &bytesRead)) { return H264Error::InvalidParameter; } offset += static_cast(bytesRead); } offset += 4; } if (!readFrameEnd) { return H264Error::GenericError; } if (offset >= bufferLength) { return H264Error::GenericError; } // We scan for 0 0 1, but it could be 0 0 0 1 if (offset > 0 && buffer[offset] == 0) { --offset; } *outLength = offset - start; auto bs = BitStream { buffer.get() + offset, static_cast(bufferLength - offset) }; bs.readUE(); if (bs.eof()) { return H264Error::InvalidParameter; } bs.readUE(); if (bs.eof()) { return H264Error::InvalidParameter; } auto unkValue = bs.readUE(); if (bs.eof()) { return H264Error::InvalidParameter; } if (unkValue >= 0x100) { return H264Error::GenericError; } return H264Error::OK; } /** * Check if the next NALU can be skipped without breaking decoding. */ H264Error H264DECCheckSkipableFrame(virt_ptr buffer, int32_t bufferLength, virt_ptr outSkippable) { if (!buffer || bufferLength < 4 || !outSkippable) { return H264Error::InvalidParameter; } *outSkippable = TRUE; for (auto i = 0; i < bufferLength - 4; ++i) { if (buffer[i + 0] == 0 && buffer[i + 1] == 0 && buffer[i + 2] == 1 && H264NaluHeader::get(buffer[i + 3]).refIdc()) { *outSkippable = FALSE; return H264Error::OK; } } return H264Error::OK; } /** * Find the first SPS in the stream. */ H264Error H264DECFindDecstartpoint(virt_ptr buffer, int32_t bufferLength, virt_ptr outOffset) { if (!buffer || bufferLength < 4 || !outOffset) { return H264Error::InvalidParameter; } for (auto i = 0; i < bufferLength - 4; i++) { if (buffer[i + 0] == 0 && buffer[i + 1] == 0 && buffer[i + 2] == 1 && H264NaluHeader::get(buffer[i + 3]).type() == NaluType::Sps) { *outOffset = std::max(i - 1, 0); return H264Error::OK; } } return H264Error::GenericError; } /** * Find the first "IDR point" in the stream. * * An IDR point is either: * - If an SPS or PPS header is found before the IDR and there are no non-IDR * inbetween the SPS/PPS and IDR then return the first of the SPS/PPS. * - The first found IDR. */ int32_t H264DECFindIdrpoint(virt_ptr buffer, int32_t bufferLength, virt_ptr outOffset) { if (!buffer || bufferLength < 4 || !outOffset) { return H264Error::InvalidParameter; } auto ppsOffset = int32_t { -1 }; auto spsOffset = int32_t { -1 }; for (auto i = 0; i < bufferLength - 4; i++) { if (buffer[i + 0] != 0 || buffer[i + 1] != 0 || buffer[i + 2] != 1) { continue; } auto type = H264NaluHeader::get(buffer[i + 3]).type(); if (type == NaluType::NonIdr) { // When we encounter a non-idr frame reset the pps / sps offset ppsOffset = -1; spsOffset = -1; } else if (type == NaluType::Pps) { ppsOffset = i - 1; } else if (type == NaluType::Sps) { spsOffset = i - 1; } else if (type == NaluType::Idr) { // Found an IDR frame! auto offset = std::max(i - 1, 0); if (ppsOffset >= 0 && spsOffset >= 0) { offset = std::min(ppsOffset, spsOffset); } else if (ppsOffset >= 0) { offset = ppsOffset; } else if (spsOffset >= 0) { offset = spsOffset; } return H264Error::OK; } } return H264Error::GenericError; } /** * Parse the H264 stream and read the width & height from the first found SPS. */ H264Error H264DECGetImageSize(virt_ptr buffer, int32_t bufferLength, int32_t offset, virt_ptr outWidth, virt_ptr outHeight) { // Check parameters if (!buffer || !outWidth || !outHeight || bufferLength < 4 || offset < 0 || bufferLength <= offset) { return H264Error::InvalidParameter; } // Find and parse SPS auto sps = StackObject { }; auto result = internal::decodeNaluSps(buffer.get(), bufferLength, offset, sps); if (result != H264Error::OK) { return result; } // Set output *outWidth = sps->pic_width; *outHeight = sps->pic_height; if (!sps->frame_mbs_only_flag) { *outHeight = 2 * sps->pic_height; } if (!*outWidth || !*outHeight) { return H264Error::GenericError; } return H264Error::OK; } void Library::registerStreamSymbols() { RegisterFunctionExport(H264DECCheckDecunitLength); RegisterFunctionExport(H264DECCheckSkipableFrame); RegisterFunctionExport(H264DECFindDecstartpoint); RegisterFunctionExport(H264DECFindIdrpoint); RegisterFunctionExport(H264DECGetImageSize); } } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/h264/h264_stream.h ================================================ #pragma once #include "h264_enum.h" #include #include namespace cafe::h264 { static constexpr auto MaxPicParameterSets = 256; static constexpr auto MaxSeqParameterSets = 32; static constexpr auto MaxBufferedFrames = 5; static constexpr auto SarExtended = uint8_t { 255 }; #pragma pack(push, 1) struct H264Bitstream; struct H264DecodeOutput; struct H264DecodedFrameInfo; struct H264DecodedVuiParameters; struct H264PictureParameterSet; struct H264SequenceParameterSet; struct H264SliceHeader; struct H264StreamMemory; using H264DECFptrOutputFn = virt_func_ptr< void (virt_ptr output)>; struct H264Bitstream { be2_virt_ptr buffer; be2_val buffer_length; be2_val bit_position; //! Actually stored at +0x8F0 but that would go over into other structs //! memory..? be2_val timestamp; }; CHECK_OFFSET(H264Bitstream, 0x00, buffer); CHECK_OFFSET(H264Bitstream, 0x04, buffer_length); CHECK_OFFSET(H264Bitstream, 0x08, bit_position); BITFIELD_BEG(H264NaluHeader, uint8_t) BITFIELD_ENTRY(7, 1, bool, forbiddenZeroBit) BITFIELD_ENTRY(5, 2, uint8_t, refIdc) BITFIELD_ENTRY(0, 5, NaluType, type) BITFIELD_END struct H264PictureParameterSet { be2_val valid; UNKNOWN(1); be2_val seq_parameter_set_id; be2_val entropy_coding_mode_flag; be2_val pic_order_present_flag; be2_val num_slice_groups_minus1; be2_val slice_group_map_type; be2_val slice_group_change_direction_flag; be2_val slice_group_change_rate_minus1; be2_val pic_size_in_map_units_minus1; be2_virt_ptr ptr_to_slice_group_id; be2_val num_ref_idx_l0_active; be2_val num_ref_idx_l1_active; be2_val weighted_pred_flag; be2_val weighted_bipred_idc; be2_val pic_init_qp_minus26; be2_val pic_init_qs_minus26; be2_val chroma_qp_index_offset; be2_val deblocking_filter_control_present_flag; be2_val constrained_intra_pred_flag; be2_val redundant_pic_cnt_present_flag; be2_val transform_8x8_mode_flag; be2_val pic_scaling_matrix_present_flag; be2_array pic_scaling_list_present_flag; be2_array scalingList4x4; be2_array scalingList8x8; UNKNOWN(1); be2_val second_chroma_qp_index_offset; UNKNOWN(4); be2_array run_length_minus1; be2_array top_left; be2_array bottom_right; UNKNOWN(2); }; CHECK_OFFSET(H264PictureParameterSet, 0x000, valid); CHECK_OFFSET(H264PictureParameterSet, 0x002, seq_parameter_set_id); CHECK_OFFSET(H264PictureParameterSet, 0x004, entropy_coding_mode_flag); CHECK_OFFSET(H264PictureParameterSet, 0x005, pic_order_present_flag); CHECK_OFFSET(H264PictureParameterSet, 0x006, num_slice_groups_minus1); CHECK_OFFSET(H264PictureParameterSet, 0x008, slice_group_map_type); CHECK_OFFSET(H264PictureParameterSet, 0x00A, slice_group_change_direction_flag); CHECK_OFFSET(H264PictureParameterSet, 0x00C, slice_group_change_rate_minus1); CHECK_OFFSET(H264PictureParameterSet, 0x00E, pic_size_in_map_units_minus1); CHECK_OFFSET(H264PictureParameterSet, 0x010, ptr_to_slice_group_id); CHECK_OFFSET(H264PictureParameterSet, 0x014, num_ref_idx_l0_active); CHECK_OFFSET(H264PictureParameterSet, 0x015, num_ref_idx_l1_active); CHECK_OFFSET(H264PictureParameterSet, 0x016, weighted_pred_flag); CHECK_OFFSET(H264PictureParameterSet, 0x017, weighted_bipred_idc); CHECK_OFFSET(H264PictureParameterSet, 0x018, pic_init_qp_minus26); CHECK_OFFSET(H264PictureParameterSet, 0x01A, pic_init_qs_minus26); CHECK_OFFSET(H264PictureParameterSet, 0x01C, chroma_qp_index_offset); CHECK_OFFSET(H264PictureParameterSet, 0x01E, deblocking_filter_control_present_flag); CHECK_OFFSET(H264PictureParameterSet, 0x01F, constrained_intra_pred_flag); CHECK_OFFSET(H264PictureParameterSet, 0x020, redundant_pic_cnt_present_flag); CHECK_OFFSET(H264PictureParameterSet, 0x021, transform_8x8_mode_flag); CHECK_OFFSET(H264PictureParameterSet, 0x022, pic_scaling_matrix_present_flag); CHECK_OFFSET(H264PictureParameterSet, 0x023, pic_scaling_list_present_flag); CHECK_OFFSET(H264PictureParameterSet, 0x02B, scalingList4x4); CHECK_OFFSET(H264PictureParameterSet, 0x08B, scalingList8x8); CHECK_OFFSET(H264PictureParameterSet, 0x10C, second_chroma_qp_index_offset); CHECK_OFFSET(H264PictureParameterSet, 0x112, run_length_minus1); CHECK_OFFSET(H264PictureParameterSet, 0x126, top_left); CHECK_OFFSET(H264PictureParameterSet, 0x13A, bottom_right); CHECK_SIZE(H264PictureParameterSet, 0x150); struct H264SequenceParameterSet { be2_val valid; UNKNOWN(0x1); be2_val profile_idc; be2_val level_idc; be2_val constraint_set; be2_val seq_parameter_set_id; be2_val chroma_format_idc; be2_val residual_colour_transform_flag; be2_val bit_depth_luma_minus8; be2_val bit_depth_chroma_minus8; be2_val qpprime_y_zero_transform_bypass_flag; be2_val seq_scaling_matrix_present_flag; be2_array scalingList4x4; be2_array scalingList8x8; be2_val log2_max_frame_num_minus4; be2_val pic_order_cnt_type; be2_val log2_max_pic_order_cnt_lsb_minus4; be2_val delta_pic_order_always_zero_flag; PADDING(0x2); be2_val offset_for_non_ref_pic; be2_val offset_for_top_to_bottom_field; be2_val num_ref_frames_in_pic_order_cnt_cycle; PADDING(0x2); be2_array offset_for_ref_frame; be2_val num_ref_frames; be2_val gaps_in_frame_num_value_allowed_flag; PADDING(0x1); be2_val pic_width_in_mbs; be2_val pic_height_in_map_units; be2_val frame_mbs_only_flag; be2_val mb_adaptive_frame_field_flag; be2_val direct_8x8_inference_flag; be2_val frame_cropping_flag; be2_val frame_crop_left_offset; be2_val frame_crop_right_offset; be2_val frame_crop_top_offset; be2_val frame_crop_bottom_offset; be2_val vui_parameters_present_flag; be2_val vui_aspect_ratio_info_present_flag; be2_val vui_aspect_ratio_idc; PADDING(0x1); be2_val vui_sar_width; be2_val vui_sar_height; be2_val vui_overscan_info_present_flag; be2_val vui_overscan_appropriate_flag; be2_val vui_video_signal_type_present_flag; be2_val vui_video_format; be2_val vui_video_full_range_flag; be2_val vui_colour_description_present_flag; be2_val vui_colour_primaries; be2_val vui_transfer_characteristics; be2_val vui_matrix_coefficients; be2_val vui_chroma_loc_info_present_flag; be2_val vui_chroma_sample_loc_type_top_field; be2_val vui_chroma_sample_loc_type_bottom_field; be2_val vui_timing_info_present_flag; PADDING(0x3); be2_val vui_num_units_in_tick; be2_val vui_time_scale; be2_val vui_fixed_frame_rate_flag; be2_val vui_nal_hrd_parameters_present_flag; be2_val vui_vcl_hrd_parameters_present_flag; PADDING(0x1); be2_val hrd_cpb_cnt_minus1; be2_val hrd_bit_rate_scale; be2_val hrd_cpb_size_scale; be2_array hrd_bit_rate_value_minus1; be2_array hrd_cpb_size_value_minus1; be2_array hrd_cbr_flag; be2_val hrd_initial_cpb_removal_delay_length_minus1; be2_val hrd_cpb_removal_delay_length_minus1; be2_val hrd_dpb_output_delay_length_minus1; be2_val hrd_time_offset_length; be2_val vui_low_delay_hrd_flag; be2_val vui_pic_struct_present_flag; be2_val vui_bitstream_restriction_flag; be2_val vui_motion_vectors_over_pic_boundaries_flag; be2_val vui_max_bytes_per_pic_denom; be2_val vui_max_bits_per_mb_denom; be2_val vui_log2_max_mv_length_horizontal; be2_val vui_log2_max_mv_length_vertical; be2_val vui_num_reorder_frames; be2_val unk0x7B0; be2_val vui_max_dec_frame_buffering; be2_val max_pic_order_cnt_lsb; be2_val unk0x7B8; UNKNOWN(0x2); be2_val pic_size_in_mbs; UNKNOWN(0x2); be2_val max_frame_num; be2_val log2_max_frame_num; be2_val unk0x7C5; be2_val unk0x7C6; be2_val pic_width; be2_val pic_height; be2_val unk0x7CC; UNKNOWN(0x95E - 0x7CE); be2_val unk0x95E; be2_val unk0x95F; be2_val unk0x960; UNKNOWN(0x978 - 0x961); }; CHECK_OFFSET(H264SequenceParameterSet, 0x000, valid); CHECK_OFFSET(H264SequenceParameterSet, 0x002, profile_idc); CHECK_OFFSET(H264SequenceParameterSet, 0x004, level_idc); CHECK_OFFSET(H264SequenceParameterSet, 0x006, constraint_set); CHECK_OFFSET(H264SequenceParameterSet, 0x007, seq_parameter_set_id); CHECK_OFFSET(H264SequenceParameterSet, 0x008, chroma_format_idc); CHECK_OFFSET(H264SequenceParameterSet, 0x00A, bit_depth_luma_minus8); CHECK_OFFSET(H264SequenceParameterSet, 0x00B, bit_depth_chroma_minus8); CHECK_OFFSET(H264SequenceParameterSet, 0x00C, qpprime_y_zero_transform_bypass_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x00D, seq_scaling_matrix_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x00E, scalingList4x4); CHECK_OFFSET(H264SequenceParameterSet, 0x06E, scalingList8x8); CHECK_OFFSET(H264SequenceParameterSet, 0x0EE, log2_max_frame_num_minus4); CHECK_OFFSET(H264SequenceParameterSet, 0x0F0, pic_order_cnt_type); CHECK_OFFSET(H264SequenceParameterSet, 0x0F2, log2_max_pic_order_cnt_lsb_minus4); CHECK_OFFSET(H264SequenceParameterSet, 0x0F4, delta_pic_order_always_zero_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x0F8, offset_for_non_ref_pic); CHECK_OFFSET(H264SequenceParameterSet, 0x0FC, offset_for_top_to_bottom_field); CHECK_OFFSET(H264SequenceParameterSet, 0x100, num_ref_frames_in_pic_order_cnt_cycle); CHECK_OFFSET(H264SequenceParameterSet, 0x104, offset_for_ref_frame); CHECK_OFFSET(H264SequenceParameterSet, 0x504, num_ref_frames); CHECK_OFFSET(H264SequenceParameterSet, 0x506, gaps_in_frame_num_value_allowed_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x508, pic_width_in_mbs); CHECK_OFFSET(H264SequenceParameterSet, 0x50A, pic_height_in_map_units); CHECK_OFFSET(H264SequenceParameterSet, 0x50C, frame_mbs_only_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x50D, mb_adaptive_frame_field_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x50E, direct_8x8_inference_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x50F, frame_cropping_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x510, frame_crop_left_offset); CHECK_OFFSET(H264SequenceParameterSet, 0x512, frame_crop_right_offset); CHECK_OFFSET(H264SequenceParameterSet, 0x514, frame_crop_top_offset); CHECK_OFFSET(H264SequenceParameterSet, 0x516, frame_crop_bottom_offset); CHECK_OFFSET(H264SequenceParameterSet, 0x518, vui_parameters_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x519, vui_aspect_ratio_info_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x51A, vui_aspect_ratio_idc); CHECK_OFFSET(H264SequenceParameterSet, 0x51C, vui_sar_width); CHECK_OFFSET(H264SequenceParameterSet, 0x51E, vui_sar_height); CHECK_OFFSET(H264SequenceParameterSet, 0x520, vui_overscan_info_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x521, vui_overscan_appropriate_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x522, vui_video_signal_type_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x523, vui_video_format); CHECK_OFFSET(H264SequenceParameterSet, 0x524, vui_video_full_range_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x525, vui_colour_description_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x526, vui_colour_primaries); CHECK_OFFSET(H264SequenceParameterSet, 0x527, vui_transfer_characteristics); CHECK_OFFSET(H264SequenceParameterSet, 0x528, vui_matrix_coefficients); CHECK_OFFSET(H264SequenceParameterSet, 0x529, vui_chroma_loc_info_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x52A, vui_chroma_sample_loc_type_top_field); CHECK_OFFSET(H264SequenceParameterSet, 0x52B, vui_chroma_sample_loc_type_bottom_field); CHECK_OFFSET(H264SequenceParameterSet, 0x52C, vui_timing_info_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x530, vui_num_units_in_tick); CHECK_OFFSET(H264SequenceParameterSet, 0x534, vui_time_scale); CHECK_OFFSET(H264SequenceParameterSet, 0x538, vui_fixed_frame_rate_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x539, vui_nal_hrd_parameters_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x53A, vui_vcl_hrd_parameters_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x53C, hrd_cpb_cnt_minus1); CHECK_OFFSET(H264SequenceParameterSet, 0x53E, hrd_bit_rate_scale); CHECK_OFFSET(H264SequenceParameterSet, 0x540, hrd_cpb_size_scale); CHECK_OFFSET(H264SequenceParameterSet, 0x542, hrd_bit_rate_value_minus1); CHECK_OFFSET(H264SequenceParameterSet, 0x60A, hrd_cpb_size_value_minus1); CHECK_OFFSET(H264SequenceParameterSet, 0x6D2, hrd_cbr_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x79A, hrd_initial_cpb_removal_delay_length_minus1); CHECK_OFFSET(H264SequenceParameterSet, 0x79C, hrd_cpb_removal_delay_length_minus1); CHECK_OFFSET(H264SequenceParameterSet, 0x79E, hrd_dpb_output_delay_length_minus1); CHECK_OFFSET(H264SequenceParameterSet, 0x7A0, hrd_time_offset_length); CHECK_OFFSET(H264SequenceParameterSet, 0x7A2, vui_low_delay_hrd_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x7A3, vui_pic_struct_present_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x7A4, vui_bitstream_restriction_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x7A5, vui_motion_vectors_over_pic_boundaries_flag); CHECK_OFFSET(H264SequenceParameterSet, 0x7A6, vui_max_bytes_per_pic_denom); CHECK_OFFSET(H264SequenceParameterSet, 0x7A8, vui_max_bits_per_mb_denom); CHECK_OFFSET(H264SequenceParameterSet, 0x7AA, vui_log2_max_mv_length_horizontal); CHECK_OFFSET(H264SequenceParameterSet, 0x7AC, vui_log2_max_mv_length_vertical); CHECK_OFFSET(H264SequenceParameterSet, 0x7AE, vui_num_reorder_frames); CHECK_OFFSET(H264SequenceParameterSet, 0x7B2, vui_max_dec_frame_buffering); CHECK_OFFSET(H264SequenceParameterSet, 0x7B4, max_pic_order_cnt_lsb); CHECK_OFFSET(H264SequenceParameterSet, 0x7BC, pic_size_in_mbs); CHECK_OFFSET(H264SequenceParameterSet, 0x7C0, max_frame_num); CHECK_OFFSET(H264SequenceParameterSet, 0x7C4, log2_max_frame_num); CHECK_OFFSET(H264SequenceParameterSet, 0x7C8, pic_width); CHECK_OFFSET(H264SequenceParameterSet, 0x7CA, pic_height); CHECK_SIZE(H264SequenceParameterSet, 0x978); struct H264SliceHeader { UNKNOWN(2); be2_val field_02; UNKNOWN(1); be2_val pic_parameter_set_id; be2_val frame_num; be2_val idr_pic_id; be2_val pic_order_cnt_lsb; be2_val delta_pic_order_cnt_bottom; be2_val field_14; be2_val field_18; be2_val field_1C; be2_val field_1D; be2_val field_pic_flag; be2_val bottom_field_flag; be2_val field_20; be2_val first_mb_in_slice; be2_val slice_type; be2_val field_25; be2_val field_26; be2_val field_27; be2_array delta_pic_order_cnt; be2_val redundant_pic_cnt; be2_val direct_spatial_mv_pred_flag; be2_val num_ref_idx_active_override_flag; be2_val num_ref_idx_l0_active; be2_val num_ref_idx_l1_active; }; CHECK_OFFSET(H264SliceHeader, 0x02, field_02); CHECK_OFFSET(H264SliceHeader, 0x04, pic_parameter_set_id); CHECK_OFFSET(H264SliceHeader, 0x06, frame_num); CHECK_OFFSET(H264SliceHeader, 0x08, idr_pic_id); CHECK_OFFSET(H264SliceHeader, 0x0C, pic_order_cnt_lsb); CHECK_OFFSET(H264SliceHeader, 0x10, delta_pic_order_cnt_bottom); CHECK_OFFSET(H264SliceHeader, 0x14, field_14); CHECK_OFFSET(H264SliceHeader, 0x18, field_18); CHECK_OFFSET(H264SliceHeader, 0x1C, field_1C); CHECK_OFFSET(H264SliceHeader, 0x1D, field_1D); CHECK_OFFSET(H264SliceHeader, 0x1E, field_pic_flag); CHECK_OFFSET(H264SliceHeader, 0x1F, bottom_field_flag); CHECK_OFFSET(H264SliceHeader, 0x20, field_20); CHECK_OFFSET(H264SliceHeader, 0x22, first_mb_in_slice); CHECK_OFFSET(H264SliceHeader, 0x24, slice_type); CHECK_OFFSET(H264SliceHeader, 0x25, field_25); CHECK_OFFSET(H264SliceHeader, 0x26, field_26); CHECK_OFFSET(H264SliceHeader, 0x27, field_27); CHECK_OFFSET(H264SliceHeader, 0x28, delta_pic_order_cnt); CHECK_OFFSET(H264SliceHeader, 0x30, redundant_pic_cnt); CHECK_OFFSET(H264SliceHeader, 0x32, direct_spatial_mv_pred_flag); CHECK_OFFSET(H264SliceHeader, 0x33, num_ref_idx_active_override_flag); CHECK_OFFSET(H264SliceHeader, 0x34, num_ref_idx_l0_active); CHECK_OFFSET(H264SliceHeader, 0x35, num_ref_idx_l1_active); CHECK_SIZE(H264SliceHeader, 0x36); struct H264DecodedVuiParameters { be2_val aspect_ratio_info_present_flag; be2_val aspect_ratio_idc; be2_val sar_width; be2_val sar_height; be2_val overscan_info_present_flag; be2_val overscan_appropriate_flag; be2_val video_signal_type_present_flag; be2_val video_format; be2_val video_full_range_flag; be2_val colour_description_present_flag; be2_val colour_primaries; be2_val transfer_characteristics; be2_val matrix_coefficients; be2_val chroma_loc_info_present_flag; be2_val chroma_sample_loc_type_top_field; be2_val chroma_sample_loc_type_bottom_field; be2_val timing_info_present_flag; PADDING(1); be2_val num_units_in_tick; be2_val time_scale; be2_val fixed_frame_rate_flag; be2_val nal_hrd_parameters_present_flag; be2_val vcl_hrd_parameters_present_flag; be2_val low_delay_hrd_flag; be2_val pic_struct_present_flag; be2_val bitstream_restriction_flag; be2_val motion_vectors_over_pic_boundaries_flag; PADDING(1); be2_val max_bytes_per_pic_denom; be2_val max_bits_per_mb_denom; be2_val log2_max_mv_length_horizontal; be2_val log2_max_mv_length_vertical; be2_val num_reorder_frames; be2_val max_dec_frame_buffering; }; CHECK_OFFSET(H264DecodedVuiParameters, 0x00, aspect_ratio_info_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x01, aspect_ratio_idc); CHECK_OFFSET(H264DecodedVuiParameters, 0x02, sar_width); CHECK_OFFSET(H264DecodedVuiParameters, 0x04, sar_height); CHECK_OFFSET(H264DecodedVuiParameters, 0x06, overscan_info_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x07, overscan_appropriate_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x08, video_signal_type_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x09, video_format); CHECK_OFFSET(H264DecodedVuiParameters, 0x0A, video_full_range_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x0B, colour_description_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x0C, colour_primaries); CHECK_OFFSET(H264DecodedVuiParameters, 0x0D, transfer_characteristics); CHECK_OFFSET(H264DecodedVuiParameters, 0x0E, matrix_coefficients); CHECK_OFFSET(H264DecodedVuiParameters, 0x0F, chroma_loc_info_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x10, chroma_sample_loc_type_top_field); CHECK_OFFSET(H264DecodedVuiParameters, 0x11, chroma_sample_loc_type_bottom_field); CHECK_OFFSET(H264DecodedVuiParameters, 0x12, timing_info_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x14, num_units_in_tick); CHECK_OFFSET(H264DecodedVuiParameters, 0x18, time_scale); CHECK_OFFSET(H264DecodedVuiParameters, 0x1C, fixed_frame_rate_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x1D, nal_hrd_parameters_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x1E, vcl_hrd_parameters_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x1F, low_delay_hrd_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x20, pic_struct_present_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x21, bitstream_restriction_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x22, motion_vectors_over_pic_boundaries_flag); CHECK_OFFSET(H264DecodedVuiParameters, 0x24, max_bytes_per_pic_denom); CHECK_OFFSET(H264DecodedVuiParameters, 0x26, max_bits_per_mb_denom); CHECK_OFFSET(H264DecodedVuiParameters, 0x28, log2_max_mv_length_horizontal); CHECK_OFFSET(H264DecodedVuiParameters, 0x2A, log2_max_mv_length_vertical); CHECK_OFFSET(H264DecodedVuiParameters, 0x2C, num_reorder_frames); CHECK_OFFSET(H264DecodedVuiParameters, 0x2E, max_dec_frame_buffering); CHECK_SIZE(H264DecodedVuiParameters, 0x30); struct H264DecodedFrameInfo { // Timestamp is actually stored in dpb but we are lazy and don't have dpb be2_val timestamp; UNKNOWN(0x30 - 8); be2_virt_ptr buffer; be2_val pan_scan_enable_flag; PADDING(1); be2_val left_pan_scan; be2_val right_pan_scan; be2_val top_pan_scan; be2_val bottom_pan_scan; UNKNOWN(1); be2_val vui_parameters_present_flag; be2_struct vui_parameters; }; CHECK_OFFSET(H264DecodedFrameInfo, 0x30, buffer); CHECK_OFFSET(H264DecodedFrameInfo, 0x34, pan_scan_enable_flag); CHECK_OFFSET(H264DecodedFrameInfo, 0x36, left_pan_scan); CHECK_OFFSET(H264DecodedFrameInfo, 0x38, right_pan_scan); CHECK_OFFSET(H264DecodedFrameInfo, 0x3A, top_pan_scan); CHECK_OFFSET(H264DecodedFrameInfo, 0x3C, bottom_pan_scan); CHECK_OFFSET(H264DecodedFrameInfo, 0x3F, vui_parameters_present_flag); CHECK_OFFSET(H264DecodedFrameInfo, 0x40, vui_parameters); CHECK_SIZE(H264DecodedFrameInfo, 0x70); struct H264StreamMemory { be2_virt_ptr workMemoryEnd; be2_virt_ptr unkMemory; UNKNOWN(0x7C4 - 0x8); be2_array decodedFrameInfos; be2_struct currentSps; be2_virt_ptr currentSpsPtr; be2_struct currentPps; UNKNOWN(0x107BC - 0x1530); be2_val paramFramePointerOutput; be2_array spsTable; be2_array ppsTable; UNKNOWN(0x38AD8 - 0x386C0); be2_val param_0x20000030; be2_val param_0x20000040; UNKNOWN(0x8); be2_val frameBufferIndex; be2_virt_ptr frameBufferPtr; be2_virt_ptr paramUserMemory; be2_val paramOutputPerFrame; UNKNOWN(0x10E6B4 - (0x38AF4 + 1)); }; CHECK_OFFSET(H264StreamMemory, 0x0, workMemoryEnd); CHECK_OFFSET(H264StreamMemory, 0x4, unkMemory); CHECK_OFFSET(H264StreamMemory, 0x7C4, decodedFrameInfos); CHECK_OFFSET(H264StreamMemory, 0xA64, currentSps); CHECK_OFFSET(H264StreamMemory, 0x13DC, currentSpsPtr); CHECK_OFFSET(H264StreamMemory, 0x13E0, currentPps); CHECK_OFFSET(H264StreamMemory, 0x107BC, paramFramePointerOutput); CHECK_OFFSET(H264StreamMemory, 0x107C0, spsTable); CHECK_OFFSET(H264StreamMemory, 0x236C0, ppsTable); CHECK_OFFSET(H264StreamMemory, 0x38AD8, param_0x20000030); CHECK_OFFSET(H264StreamMemory, 0x38ADC, param_0x20000040); CHECK_OFFSET(H264StreamMemory, 0x38AE8, frameBufferIndex); CHECK_OFFSET(H264StreamMemory, 0x38AEC, frameBufferPtr); CHECK_OFFSET(H264StreamMemory, 0x38AF0, paramUserMemory); CHECK_OFFSET(H264StreamMemory, 0x38AF4, paramOutputPerFrame); CHECK_SIZE(H264StreamMemory, 0x10E6B4); struct H264DecodeResult { be2_val status; PADDING(4); be2_val timestamp; be2_val width; be2_val height; be2_val nextLine; be2_val cropEnableFlag; PADDING(3); be2_val cropTop; be2_val cropBottom; be2_val cropLeft; be2_val cropRight; be2_val panScanEnableFlag; PADDING(3); be2_val panScanTop; be2_val panScanBottom; be2_val panScanLeft; be2_val panScanRight; be2_virt_ptr framebuffer; be2_val vui_parameters_present_flag; PADDING(3); be2_virt_ptr vui_parameters; UNKNOWN(40); }; CHECK_OFFSET(H264DecodeResult, 0x00, status); CHECK_OFFSET(H264DecodeResult, 0x08, timestamp); CHECK_OFFSET(H264DecodeResult, 0x10, width); CHECK_OFFSET(H264DecodeResult, 0x14, height); CHECK_OFFSET(H264DecodeResult, 0x18, nextLine); CHECK_OFFSET(H264DecodeResult, 0x1C, cropEnableFlag); CHECK_OFFSET(H264DecodeResult, 0x20, cropTop); CHECK_OFFSET(H264DecodeResult, 0x24, cropBottom); CHECK_OFFSET(H264DecodeResult, 0x28, cropLeft); CHECK_OFFSET(H264DecodeResult, 0x2C, cropRight); CHECK_OFFSET(H264DecodeResult, 0x30, panScanEnableFlag); CHECK_OFFSET(H264DecodeResult, 0x34, panScanTop); CHECK_OFFSET(H264DecodeResult, 0x38, panScanBottom); CHECK_OFFSET(H264DecodeResult, 0x3C, panScanLeft); CHECK_OFFSET(H264DecodeResult, 0x40, panScanRight); CHECK_OFFSET(H264DecodeResult, 0x44, framebuffer); CHECK_OFFSET(H264DecodeResult, 0x48, vui_parameters_present_flag); CHECK_OFFSET(H264DecodeResult, 0x4C, vui_parameters); CHECK_SIZE(H264DecodeResult, 0x78); struct H264DecodeOutput { //! Number of frames output be2_val frameCount; //! Frames be2_virt_ptr> decodeResults; //! User memory pointer passed into SetParam be2_virt_ptr userMemory; }; CHECK_OFFSET(H264DecodeOutput, 0x00, frameCount); CHECK_OFFSET(H264DecodeOutput, 0x04, decodeResults); CHECK_OFFSET(H264DecodeOutput, 0x08, userMemory); CHECK_SIZE(H264DecodeOutput, 0x0C); #pragma pack(pop) H264Error H264DECCheckDecunitLength(virt_ptr memory, virt_ptr buffer, int32_t bufferLength, int32_t offset, virt_ptr outLength); H264Error H264DECCheckSkipableFrame(virt_ptr buffer, int32_t bufferLength, virt_ptr outSkippable); H264Error H264DECFindDecstartpoint(virt_ptr buffer, int32_t bufferLength, virt_ptr outOffset); int32_t H264DECFindIdrpoint(virt_ptr buffer, int32_t bufferLength, virt_ptr outOffset); H264Error H264DECGetImageSize(virt_ptr buffer, int32_t bufferLength, int32_t offset, virt_ptr outWidth, virt_ptr outHeight); namespace internal { H264Error decodeNaluSps(const uint8_t *buffer, int bufferLength, int offset, virt_ptr sps); } // namespace internal } // namespace cafe::h264 ================================================ FILE: src/libdecaf/src/cafe/libraries/lzma920/lzma920.cpp ================================================ #include "lzma920.h" namespace cafe::lzma920 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::lzma920 ================================================ FILE: src/libdecaf/src/cafe/libraries/lzma920/lzma920.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::lzma920 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::lzma920, "lzma920.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::lzma920 ================================================ FILE: src/libdecaf/src/cafe/libraries/mic/mic.cpp ================================================ #include "mic.h" namespace cafe::mic { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerMicSymbols(); } } // namespace cafe::mic ================================================ FILE: src/libdecaf/src/cafe/libraries/mic/mic.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::mic { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::mic, "mic.rpl") { } protected: virtual void registerSymbols() override; private: void registerMicSymbols(); }; } // namespace cafe::mic ================================================ FILE: src/libdecaf/src/cafe/libraries/mic/mic_mic.cpp ================================================ #include "mic.h" #include "mic_mic.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::mic { MICHandle MICInit(uint32_t a1, virt_ptr a2, virt_ptr a3, virt_ptr outError) { decaf_warn_stub(); if (outError) { *outError = -1; } return 0; } MICError MICUninit(MICHandle handle) { return -1; } MICError MICOpen(MICHandle handle) { return -1; } MICError MICClose(MICHandle handle) { return -1; } MICError MICGetStatus(MICHandle handle, virt_ptr status) { return -1; } void Library::registerMicSymbols() { RegisterFunctionExport(MICInit); RegisterFunctionExport(MICUninit); RegisterFunctionExport(MICOpen); RegisterFunctionExport(MICClose); RegisterFunctionExport(MICGetStatus); } } // namespace cafe::mic ================================================ FILE: src/libdecaf/src/cafe/libraries/mic/mic_mic.h ================================================ #pragma once #include namespace cafe::mic { using MICHandle = uint32_t; using MICError = int32_t; struct MICStatus; MICHandle MICInit(uint32_t a1, virt_ptr a2, virt_ptr a3, virt_ptr outError); MICError MICUninit(MICHandle handle); MICError MICOpen(MICHandle handle); MICError MICClose(MICHandle handle); MICError MICGetStatus(MICHandle handle, virt_ptr status); } // namespace cafe::mic ================================================ FILE: src/libdecaf/src/cafe/libraries/nfc/nfc.cpp ================================================ #include "nfc.h" namespace cafe::nfc { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nfc ================================================ FILE: src/libdecaf/src/cafe/libraries/nfc/nfc.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nfc { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nfc, "nfc.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nfc ================================================ FILE: src/libdecaf/src/cafe/libraries/nio_prof/nio_prof.cpp ================================================ #include "nio_prof.h" namespace cafe::nio_prof { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nio_prof ================================================ FILE: src/libdecaf/src/cafe/libraries/nio_prof/nio_prof.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nio_prof { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nio_prof, "nio_prof.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nio_prof ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl.cpp ================================================ #include "nlibcurl.h" namespace cafe::nlibcurl { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerCurlSymbols(); registerEasySymbols(); } } // namespace cafe::nlibcurl ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nlibcurl { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nlibcurl, "nlibcurl.rpl") { } protected: virtual void registerSymbols() override; private: void registerCurlSymbols(); void registerEasySymbols(); }; } // namespace cafe::nlibcurl ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_curl.cpp ================================================ #include "nlibcurl.h" #include "nlibcurl_curl.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nlibcurl { CURLcode curl_global_init(int32_t flags) { // Host curl_global_init is called during decaf initialisation return ::CURLE_OK; } CURLcode curl_global_init_mem(int32_t flags, virt_ptr mallocCallback, virt_ptr freeCallback, virt_ptr reallocCallback, virt_ptr strdupCallback, virt_ptr callocCallback) { decaf_warn_stub(); return ::CURLE_OK; } void curl_global_cleanup() { // Host curl_global_cleanup is called during decaf exit } void Library::registerCurlSymbols() { RegisterFunctionExport(curl_global_init); RegisterFunctionExport(curl_global_init_mem); RegisterFunctionExport(curl_global_cleanup); } } // namespace cafe::nlibcurl ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_curl.h ================================================ #pragma once #include #include namespace cafe::nlibcurl { struct CURL { ::CURL *hostHandle; }; using CURLcode = int32_t; CURLcode curl_global_init(int32_t flags); CURLcode curl_global_init_mem(int32_t flags, virt_ptr mallocCallback, virt_ptr freeCallback, virt_ptr reallocCallback, virt_ptr strdupCallback, virt_ptr callocCallback); void curl_global_cleanup(); } // namespace cafe::nlibcurl ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_easy.cpp ================================================ #include "nlibcurl.h" #include "nlibcurl_easy.h" #include "cafe/cafe_ppc_interface_varargs.h" #include "cafe/libraries/cafe_hle_stub.h" #include namespace cafe::nlibcurl { struct StaticEasyData { be2_array handles; }; static virt_ptr sEasyData = nullptr; virt_ptr curl_easy_init() { auto handle = ::curl_easy_init(); if (!handle) { return nullptr; } for (auto i = 0u; i < sEasyData->handles.size(); ++i) { if (!sEasyData->handles[i].hostHandle) { sEasyData->handles[i].hostHandle = handle; return virt_addrof(sEasyData->handles[i]); } } ::curl_easy_cleanup(handle); return nullptr; } void curl_easy_cleanup(virt_ptr handle) { ::curl_easy_cleanup(handle->hostHandle); handle->hostHandle = nullptr; } CURLcode curl_easy_setopt(virt_ptr handle, CURLoption option, var_args args) { auto vaList = make_va_list(args); //auto curl = handle->hostHandle; // TODO: Translate to ::curl_easy_setopt decaf_warn_stub(); free_va_list(vaList); return ::CURLE_OK; } void Library::registerEasySymbols() { RegisterFunctionExport(curl_easy_init); RegisterFunctionExport(curl_easy_cleanup); RegisterFunctionExport(curl_easy_setopt); RegisterDataInternal(sEasyData); } } // namespace cafe::nlibcurl ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_easy.h ================================================ #pragma once #include #include #include "nlibcurl_curl.h" namespace cafe::nlibcurl { using CURLoption = int32_t; virt_ptr curl_easy_init(); void curl_easy_cleanup(virt_ptr handle); CURLcode curl_easy_setopt(virt_ptr handle, CURLoption option, var_args args); } // namespace cafe::nlibcurl ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibnss/nlibnss.cpp ================================================ #include "nlibnss.h" namespace cafe::nlibnss { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nlibnss ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibnss/nlibnss.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nlibnss { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nlibnss, "nlibnss.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nlibnss ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibnss2/nlibnss2.cpp ================================================ #include "nlibnss2.h" namespace cafe::nlibnss2 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nlibnss2 ================================================ FILE: src/libdecaf/src/cafe/libraries/nlibnss2/nlibnss2.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nlibnss2 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nlibnss2, "nlibnss2.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nlibnss2 ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac.cpp ================================================ #include "nn_ac.h" namespace cafe::nn_ac { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerCApiFunctions(); registerClientSymbols(); registerServiceSymbols(); } } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_ac { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_ac, "nn_ac.rpl") { } protected: virtual void registerSymbols() override; private: void registerCApiFunctions(); void registerClientSymbols(); void registerServiceSymbols(); }; } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_capi.cpp ================================================ #include "nn_ac.h" #include "nn_ac_capi.h" #include "nn_ac_client.h" #include "nn_ac_service.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/cafe_stackobject.h" namespace cafe::nn_ac { nn::Result ACInitialize() { return Initialize(); } void ACFinalize() { return Finalize(); } nn::Result ACConnect() { return Connect(); } nn::Result ACConnectAsync() { return ConnectAsync(); } nn::Result ACIsApplicationConnected(virt_ptr connected) { auto isConnected = StackObject { }; *isConnected = *connected ? true : false; auto result = IsApplicationConnected(isConnected); *connected = *isConnected ? TRUE : FALSE; return result; } nn::Result ACGetAssignedAddress(virt_ptr outAddress) { return GetAssignedAddress(outAddress); } nn::Result ACGetConnectStatus(virt_ptr outStatus) { return GetConnectStatus(outStatus); } nn::Result ACGetLastErrorCode(virt_ptr outError) { return GetLastErrorCode(outError); } nn::Result ACGetStatus(virt_ptr outStatus) { return GetStatus(outStatus); } nn::Result ACGetStartupId(virt_ptr outStartupId) { return GetStartupId(outStartupId); } nn::Result ACReadConfig(ConfigId id, virt_ptr config) { return ReadConfig(id, config); } void Library::registerCApiFunctions() { RegisterFunctionExport(ACInitialize); RegisterFunctionExport(ACFinalize); RegisterFunctionExport(ACConnect); RegisterFunctionExport(ACConnectAsync); RegisterFunctionExport(ACIsApplicationConnected); RegisterFunctionExport(ACGetAssignedAddress); RegisterFunctionExport(ACGetConnectStatus); RegisterFunctionExport(ACGetLastErrorCode); RegisterFunctionExport(ACGetStatus); RegisterFunctionExport(ACGetStartupId); RegisterFunctionExport(ACReadConfig); } } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_capi.h ================================================ #pragma once #include "nn_ac_enum.h" #include "nn_ac_service.h" #include "nn/nn_result.h" #include namespace cafe::nn_ac { nn::Result ACInitialize(); void ACFinalize(); nn::Result ACConnect(); nn::Result ACConnectAsync(); nn::Result ACIsApplicationConnected(virt_ptr connected); nn::Result ACGetAssignedAddress(virt_ptr outAddress); nn::Result ACGetConnectStatus(virt_ptr outStatus); nn::Result ACGetLastErrorCode(virt_ptr outError); nn::Result ACGetStatus(virt_ptr outStatus); nn::Result ACGetStartupId(virt_ptr outStartupId); nn::Result ACReadConfig(ConfigId id, virt_ptr config); } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_client.cpp ================================================ #include "nn_ac.h" #include "nn_ac_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/ac/nn_ac_service.h" using namespace cafe::coreinit; using namespace nn::ac; using namespace nn::ipc; namespace cafe::nn_ac { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(256) be2_array allocatorMemory; be2_struct mutex; be2_val refCount = 0u; be2_struct client; be2_struct allocator; }; static virt_ptr sClientData = nullptr; nn::Result Initialize() { auto result = nn::ResultSuccess; OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { result = sClientData->client.initialise(make_stack_string("/dev/ac_main")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); if (result) { internal::getClient()->sendSyncRequest( ClientCommand { internal::getAllocator() }); } } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return result; } void Finalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { internal::getClient()->sendSyncRequest( ClientCommand { internal::getAllocator() }); sClientData->client.close(); } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } namespace internal { virt_ptr getClient() { return virt_addrof(sClientData->client); } virt_ptr getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn2acFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn2acFv", Finalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_client.h ================================================ #pragma once #include "nn/nn_result.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" #include namespace cafe::nn_ac { nn::Result Initialize(); void Finalize(); namespace internal { virt_ptr getClient(); virt_ptr getAllocator(); } // namespace internal } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_enum.h ================================================ #ifndef CAFE_NN_AC_ENUM_H #define CAFE_NN_AC_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_ac) ENUM_BEG(Status, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Error, -1) ENUM_END(Status) ENUM_NAMESPACE_EXIT(nn_ac) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_NN_AC_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_service.cpp ================================================ #include "nn_ac.h" #include "nn_ac_client.h" #include "nn_ac_service.h" #include "cafe/libraries/cafe_hle_stub.h" #include "nn/ac/nn_ac_result.h" #include "nn/ac/nn_ac_service.h" #include "nn/ipc/nn_ipc_command.h" using namespace nn::ac; using namespace nn::ipc; namespace cafe::nn_ac { nn::Result Connect() { decaf_warn_stub(); return ResultSuccess; } nn::Result ConnectAsync() { decaf_warn_stub(); return ResultSuccess; } nn::Result IsApplicationConnected(virt_ptr connected) { decaf_warn_stub(); *connected = false; return ResultSuccess; } nn::Result GetAssignedAddress(virt_ptr outAddress) { if (!internal::getClient()->isInitialised()) { return ResultLibraryNotInitialiased; } if (!outAddress) { return ResultInvalidArgument; } auto command = ClientCommand { internal::getAllocator() }; command.setParameters(0); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { auto address = uint32_t{ 0 }; result = command.readResponse(address); if (result.ok()) { *outAddress = address; } } return result; } nn::Result GetConnectStatus(virt_ptr outStatus) { decaf_warn_stub(); *outStatus = Status::Error; return ResultSuccess; } nn::Result GetLastErrorCode(virt_ptr outError) { decaf_warn_stub(); *outError = -1; return ResultSuccess; } nn::Result GetStatus(virt_ptr outStatus) { decaf_warn_stub(); *outStatus = Status::Error; return ResultSuccess; } nn::Result GetStartupId(virt_ptr outStartupId) { decaf_warn_stub(); *outStartupId = 0; return ResultSuccess; } nn::Result ReadConfig(ConfigId id, virt_ptr config) { decaf_warn_stub(); std::memset(config.get(), 0, sizeof(Config)); return ResultSuccess; } void Library::registerServiceSymbols() { RegisterFunctionExportName("Connect__Q2_2nn2acFv", Connect); RegisterFunctionExportName("ConnectAsync__Q2_2nn2acFv", ConnectAsync); RegisterFunctionExportName("IsApplicationConnected__Q2_2nn2acFPb", IsApplicationConnected); RegisterFunctionExportName("GetAssignedAddress__Q2_2nn2acFPUl", GetAssignedAddress); RegisterFunctionExportName("GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", GetConnectStatus); RegisterFunctionExportName("GetLastErrorCode__Q2_2nn2acFPUi", GetLastErrorCode); RegisterFunctionExportName("GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", GetStatus); RegisterFunctionExportName("GetStartupId__Q2_2nn2acFPQ3_2nn2ac11ConfigIdNum", GetStartupId); RegisterFunctionExportName("ReadConfig__Q2_2nn2acFQ3_2nn2ac11ConfigIdNumP16netconf_profile_", ReadConfig); } } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_service.h ================================================ #pragma once #include "nn_ac_enum.h" #include "nn/nn_result.h" #include namespace cafe::nn_ac { using ConfigId = int32_t; struct Config { UNKNOWN(0x280); }; CHECK_SIZE(Config, 0x280); nn::Result Connect(); nn::Result ConnectAsync(); nn::Result IsApplicationConnected(virt_ptr connected); nn::Result GetAssignedAddress(virt_ptr outAddress); nn::Result GetConnectStatus(virt_ptr outStatus); nn::Result GetLastErrorCode(virt_ptr outError); nn::Result GetStatus(virt_ptr outStatus); nn::Result GetStartupId(virt_ptr outStartupId); nn::Result ReadConfig(ConfigId id, virt_ptr config); } // namespace cafe::nn_ac ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp.cpp ================================================ #include "nn_acp.h" #include "nn_acp_internal_driver.h" #include "cafe/libraries/cafe_hle.h" using namespace cafe::coreinit; namespace cafe::nn_acp { static int32_t rpl_entry(OSDynLoad_ModuleHandle moduleHandle, OSDynLoad_EntryReason reason) { if (reason == OSDynLoad_EntryReason::Loaded) { internal::startDriver(moduleHandle); } else if (reason == OSDynLoad_EntryReason::Unloaded) { internal::stopDriver(moduleHandle); } return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); registerDeviceSymbols(); registerDriverSymbols(); registerMiscServiceSymbols(); registerSaveServiceSymbols(); } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_acp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_acp, "nn_acp.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); void registerDeviceSymbols(); void registerDriverSymbols(); void registerMiscServiceSymbols(); void registerSaveServiceSymbols(); }; } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_acpresult.cpp ================================================ #include "nn_acp_acpresult.h" #include "nn/acp/nn_acp_result.h" #include "nn/ipc/nn_ipc_result.h" #include "cafe/libraries/coreinit/coreinit_cosreport.h" #include "cafe/libraries/coreinit/coreinit_osreport.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/cafe_stackobject.h" using namespace cafe::coreinit; using namespace nn::acp; namespace cafe::nn_acp { static void ACPSendCOSFatalError(coreinit::OSFatalErrorMessageType type, uint32_t errorCode, nn::Result result, const char *funcName, int32_t lineNo) { internal::COSWarn(COSReportModule::Unknown1, fmt::format("ACP: ##### FATAL ERROR at {} ({})#####\n", funcName, result.code())); if (OSGetUPID() == cafe::kernel::UniqueProcessId::ErrorDisplay) { internal::COSWarn(COSReportModule::Unknown1, fmt::format("ACP: Skip to call OSSendFatalError from {} (UPID={}).\n", funcName, static_cast(cafe::kernel::UniqueProcessId::ErrorDisplay))); return; } StackObject fatalError; fatalError->messageType = type; fatalError->errorCode = errorCode; fatalError->internalErrorCode = static_cast(result); if (funcName) { OSSendFatalError(fatalError, make_stack_string(funcName), lineNo); } else { OSSendFatalError(fatalError, make_stack_string("ACPSendCOSFatalError"), 103); } } ACPResult ACPConvertToACPResult(nn::Result result, const char *funcName, int32_t lineNo) { if (result.ok()) { return ACPResult::Success; } if (result == ResultInvalidParameter) { return ACPResult::InvalidParameter; } else if (result == ResultInvalidFile) { return ACPResult::InvalidFile; } else if (result == ResultInvalidXmlFile) { return ACPResult::InvalidXmlFile; } else if (result == ResultFileAccessMode) { return ACPResult::FileAccessMode; } else if (result == ResultInvalidNetworkTime) { return ACPResult::InvalidNetworkTime; } else if (result == ResultInvalid) { return ACPResult::Invalid; } if (result == ResultFileNotFound) { return ACPResult::FileNotFound; } else if (result == ResultDirNotFound) { return ACPResult::DirNotFound; } else if (result == ResultDeviceNotFound) { return ACPResult::DeviceNotFound; } else if (result == ResultTitleNotFound) { return ACPResult::TitleNotFound; } else if (result == ResultApplicationNotFound) { return ACPResult::ApplicationNotFound; } else if (result == ResultSystemConfigNotFound) { return ACPResult::SystemConfigNotFound; } else if (result == ResultXmlItemNotFound) { return ACPResult::XmlItemNotFound; } else if (result == ResultNotFound) { return ACPResult::NotFound; } if (result == ResultFileAlreadyExists) { return ACPResult::FileAlreadyExists; } else if (result == ResultDirAlreadyExists) { return ACPResult::DirAlreadyExists; } else if (result == ResultAlreadyExists) { return ACPResult::AlreadyExists; } if (result == ResultAlreadyDone) { return ACPResult::AlreadyDone; } if (result == ResultInvalidRegion) { return ACPResult::InvalidRegion; } else if (result == ResultRestrictedRating) { return ACPResult::RestrictedRating; } else if (result == ResultNotPresentRating) { return ACPResult::NotPresentRating; } else if (result == ResultPendingRating) { return ACPResult::PendingRating; } else if (result == ResultNetSettingRequired) { return ACPResult::NetSettingRequired; } else if (result == ResultNetAccountRequired) { return ACPResult::NetAccountRequired; } else if (result == ResultNetAccountError) { return ACPResult::NetAccountError; } else if (result == ResultBrowserRequired) { return ACPResult::BrowserRequired; } else if (result == ResultOlvRequired) { return ACPResult::OlvRequired; } else if (result == ResultPincodeRequired) { return ACPResult::PincodeRequired; } else if (result == ResultIncorrectPincode) { return ACPResult::IncorrectPincode; } else if (result == ResultInvalidLogo) { return ACPResult::InvalidLogo; } else if (result == ResultDemoExpiredNumber) { return ACPResult::DemoExpiredNumber; } else if (result == ResultDrcRequired) { return ACPResult::DrcRequired; } else if (result == ResultAuthentication) { return ACPResult::Authentication; } if (result == ResultNoFilePermission) { return ACPResult::NoFilePermission; } else if (result == ResultNoDirPermission) { return ACPResult::NoDirPermission; } else if (result == ResultNoPermission) { return ACPResult::NoPermission; } if (result == ResultUsbStorageNotReady) { return ACPResult::UsbStorageNotReady; } else if (result == ResultBusy) { return ACPResult::Busy; } if (result == ResultCancelled) { return ACPResult::Cancelled; } if (result == ResultDeviceFull) { return ACPResult::DeviceFull; } else if (result == ResultJournalFull) { return ACPResult::JournalFull; } else if (result == ResultSystemMemory) { return ACPResult::SystemMemory; } else if (result == ResultFsResource) { return ACPResult::FsResource; } else if (result == ResultIpcResource) { return ACPResult::IpcResource; } else if (result == ResultResource) { return ACPResult::Resource; } if (result == ResultNotInitialised) { return ACPResult::NotInitialised; } if (result == ResultAccountError) { return ACPResult::AccountError; } if (result == ResultUnsupported) { return ACPResult::Unsupported; } if (result == ResultSlcDataCorrupted) { ACPSendCOSFatalError(4, 0x1870AD, result, funcName, lineNo); return ACPResult::SlcDataCorrupted; } else if (result == ResultMlcDataCorrupted) { ACPSendCOSFatalError(2, 0x1870ae, result, funcName, lineNo); return ACPResult::MlcDataCorrupted; } else if (result == ResultUsbDataCorrupted) { ACPSendCOSFatalError(5, 0x187c67, result, funcName, lineNo); return ACPResult::UsbDataCorrupted; } else if (result == ResultDataCorrupted) { ACPSendCOSFatalError(3, 0x187c68, result, funcName, lineNo); return ACPResult::DataCorrupted; } else if (result == ResultDevice) { return ACPResult::Device; } if (result == ResultOddMediaNotReady) { return ACPResult::OddMediaNotReady; } else if (result == ResultOddMediaBroken) { return ACPResult::OddMediaBroken; } else if (result == ResultUsbMediaNotReady) { ACPSendCOSFatalError(6, 0x187499, result, funcName, lineNo); return ACPResult::UsbMediaNotReady; } else if (result == ResultUsbMediaBroken) { ACPSendCOSFatalError(5, 0x187c6a, result, funcName, lineNo); return ACPResult::UsbMediaBroken; } else if (result == ResultMediaNotReady) { return ACPResult::MediaNotReady; } else if (result == ResultMediaBroken) { return ACPResult::MediaBroken; } else if (result == ResultMediaWriteProtected) { return ACPResult::MediaWriteProtected; } else if (result == ResultUsbWriteProtected) { return ACPResult::UsbWriteProtected; } else if (result == ResultUsbWriteProtected) { return ACPResult::UsbWriteProtected; } else if (result == ResultMedia) { return ACPResult::Media; } if (result == ResultEncryptionError) { return ACPResult::EncryptionError; } else if (result == ResultMii) { return ACPResult::Mii; } if (result == ResultFsaFatal) { ACPSendCOSFatalError(1, 0x18748d, result, funcName, lineNo); } else if (result == ResultFsaAddClientFatal) { ACPSendCOSFatalError(1, 0x18748e, result, funcName, lineNo); } else if (result == ResultMcpTitleFatal) { ACPSendCOSFatalError(1, 0x18748f, result, funcName, lineNo); } else if (result == ResultMcpPatchFatal) { ACPSendCOSFatalError(1, 0x187490, result, funcName, lineNo); } else if (result == ResultMcpFatal) { ACPSendCOSFatalError(1, 0x187491, result, funcName, lineNo); } else if (result == ResultSaveFatal) { ACPSendCOSFatalError(1, 0x187492, result, funcName, lineNo); } else if (result == ResultUcFatal) { ACPSendCOSFatalError(1, 0x187493, result, funcName, lineNo); } else if (result == nn::ipc::ResultCapabilityFailed) { ACPSendCOSFatalError(1, 0x187494, result, funcName, lineNo); } else if (result == ResultFatal) { ACPSendCOSFatalError(1, 0x18748c, result, funcName, lineNo); } else { ACPSendCOSFatalError(1, 0x18749f, result, funcName, lineNo); } return ACPResult::GenericError; } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_acpresult.h ================================================ #pragma once #include "nn/nn_result.h" #include namespace cafe::nn_acp { enum class ACPResult : int32_t { Success = 0, Invalid = -200, InvalidParameter = -201, InvalidFile = -202, InvalidXmlFile = -203, FileAccessMode = -204, InvalidNetworkTime = -205, NotFound = -500, FileNotFound = -501, DirNotFound = -502, DeviceNotFound = -503, TitleNotFound = -504, ApplicationNotFound = -505, SystemConfigNotFound = -506, XmlItemNotFound = -507, AlreadyExists = -600, FileAlreadyExists = -601, DirAlreadyExists = -602, AlreadyDone = -700, Authentication = -1000, InvalidRegion = -1001, RestrictedRating = -1002, NotPresentRating = -1003, PendingRating = -1004, NetSettingRequired = -1005, NetAccountRequired = -1006, NetAccountError = -1007, BrowserRequired = -1008, OlvRequired = -1009, PincodeRequired = -1010, IncorrectPincode = -1011, InvalidLogo = -1012, DemoExpiredNumber = -1013, DrcRequired = -1014, NoPermission = -1100, NoFilePermission = -1101, NoDirPermission = -1102, Busy = -1300, UsbStorageNotReady = -1301, Cancelled = -1400, Resource = -1500, DeviceFull = -1501, JournalFull = -1502, SystemMemory = -1503, FsResource = -1504, IpcResource = -1505, NotInitialised = -1600, AccountError = -1700, Unsupported = -1800, DataCorrupted = -2000, Device = -2001, SlcDataCorrupted = -2002, MlcDataCorrupted = -2003, UsbDataCorrupted = -2004, Media = -2100, MediaNotReady = -2101, MediaBroken = -2102, OddMediaNotReady = -2103, OddMediaBroken = -2104, UsbMediaNotReady = -2105, UsbMediaBroken = -2106, MediaWriteProtected = -2107, UsbWriteProtected = -2108, Mii = -2200, EncryptionError = -2201, GenericError = -4096, }; ACPResult ACPConvertToACPResult(nn::Result result, const char *funcName, int32_t lineNo); } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_client.cpp ================================================ #include "nn_acp.h" #include "nn_acp_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/acp/nn_acp_result.h" using namespace cafe::coreinit; using namespace nn::acp; namespace cafe::nn_acp { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(64) be2_array allocatorMemory; be2_struct mutex; be2_val refCount = 0u; be2_struct client; be2_struct allocator; }; static virt_ptr sClientData = nullptr; ACPResult ACPInitialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->client.initialise(make_stack_string("/dev/acp_main")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); // TODO: MCP_Open // TODO: nn::spm::Initialize // TODO: nn::spm::StartFatalDetection auto upid = OSGetUPID(); if (upid == kernel::UniqueProcessId::HomeMenu || upid == kernel::UniqueProcessId::Game) { // TODO: PrepareToSetOwnApplicationTitleId // TODO: ACPSetOwnApplicationTitleId } } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return ACPResult::Success; } void ACPFinalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); // TODO: Cleanup the above TODO things in initialize! } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } namespace internal { virt_ptr getClient() { return virt_addrof(sClientData->client); } virt_ptr getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExport(ACPInitialize); RegisterFunctionExport(ACPFinalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_client.h ================================================ #pragma once #include "nn_acp_acpresult.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" namespace cafe::nn_acp { ACPResult ACPInitialize(); void ACPFinalize(); namespace internal { virt_ptr getClient(); virt_ptr getAllocator(); } // namespace internal } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_device.cpp ================================================ #include "nn_acp.h" #include "nn_acp_device.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nn_acp { ACPResult ACPCheckApplicationDeviceEmulation(virt_ptr outValue) { decaf_warn_stub(); *outValue = FALSE; return ACPResult::Success; } void Library::registerDeviceSymbols() { RegisterFunctionExportName("ACPCheckApplicationDeviceEmulation", ACPCheckApplicationDeviceEmulation); } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_device.h ================================================ #pragma once #include "nn_acp_acpresult.h" #include namespace cafe::nn_acp { ACPResult ACPCheckApplicationDeviceEmulation(virt_ptr outValue); } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_internal_driver.cpp ================================================ #include "nn_acp.h" #include "nn_acp_client.h" #include "nn_acp_internal_driver.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_driver.h" #include "cafe/libraries/coreinit/coreinit_cosreport.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" using namespace cafe::coreinit; namespace cafe::nn_acp::internal { struct StaticDriverData { be2_val registered = FALSE; be2_val initialised = FALSE; be2_array name = "ACP"; be2_struct driverInterface; }; static virt_ptr sDriverData = nullptr; static OSDriver_GetNameFn sDriverGetName = nullptr; static OSDriver_OnInitFn sDriverOnInit = nullptr; static OSDriver_OnAcquiredForegroundFn sDriverOnAcquiredForeground = nullptr; static OSDriver_OnReleasedForegroundFn sDriverOnReleasedForeground = nullptr; static OSDriver_OnDoneFn sDriverOnDone = nullptr; static virt_ptr getName(OSDriver_UserDriverId id) { return virt_addrof(sDriverData->name); } static void onInit(OSDriver_UserDriverId id) { coreinit::internal::COSWarn(COSReportModule::Unknown1, " ACP_AutoInit: start\n"); ACPInitialize(); coreinit::internal::COSWarn(COSReportModule::Unknown1, " ACP_AutoInit: ACPInitialize complete\n"); // TODO: ACPSaveDataInit() coreinit::internal::COSWarn(COSReportModule::Unknown1, " ACP_AutoInit: ACPSaveDataInit complete\n"); // TODO: ACPNotifyPlayEvent(1) coreinit::internal::COSWarn(COSReportModule::Unknown1, " ACP_AutoInit: ACPNotifyPlayEvent complete\n"); // TODO: NDMInitialize coreinit::internal::COSWarn(COSReportModule::Unknown1, " ACP_AutoInit: NDMInitialize complete\n"); auto upid = OSGetUPID(); if (upid == kernel::UniqueProcessId::Game || upid == kernel::UniqueProcessId::HomeMenu) { // Check for bg daemon enable } sDriverData->initialised = TRUE; } static void onAcquiredForeground(OSDriver_UserDriverId id) { } static void onReleasedForeground(OSDriver_UserDriverId id) { } static void onDone(OSDriver_UserDriverId id) { } void startDriver(OSDynLoad_ModuleHandle moduleHandle) { if (sDriverData->registered) { return; } auto driversAlreadyInitialised = StackObject { }; sDriverData->driverInterface.getName = sDriverGetName; sDriverData->driverInterface.onInit = sDriverOnInit; sDriverData->driverInterface.onAcquiredForeground = sDriverOnAcquiredForeground; sDriverData->driverInterface.onReleasedForeground = sDriverOnReleasedForeground; sDriverData->driverInterface.onDone = sDriverOnDone; OSDriver_Register(moduleHandle, 910, virt_addrof(sDriverData->driverInterface), 0, nullptr, nullptr, driversAlreadyInitialised); if (*driversAlreadyInitialised) { onInit(0); } sDriverData->registered = TRUE; } void stopDriver(OSDynLoad_ModuleHandle moduleHandle) { if (sDriverData->registered) { OSDriver_Deregister(moduleHandle, 0); sDriverData->registered = FALSE; } } } // namespace cafe::nn_acp::internal namespace cafe::nn_acp { void Library::registerDriverSymbols() { RegisterFunctionInternal(internal::getName, internal::sDriverGetName); RegisterFunctionInternal(internal::onInit, internal::sDriverOnInit); RegisterFunctionInternal(internal::onAcquiredForeground, internal::sDriverOnAcquiredForeground); RegisterFunctionInternal(internal::onReleasedForeground, internal::sDriverOnReleasedForeground); RegisterFunctionInternal(internal::onDone, internal::sDriverOnDone); RegisterDataInternal(internal::sDriverData); } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_internal_driver.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_driver.h" namespace cafe::nn_acp::internal { void startDriver(coreinit::OSDynLoad_ModuleHandle moduleHandle); void stopDriver(coreinit::OSDynLoad_ModuleHandle moduleHandle); } // namespace cafe::nn_acp::internal ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_miscservice.cpp ================================================ #include "nn_acp.h" #include "nn_acp_client.h" #include "nn_acp_miscservice.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/acp/nn_acp_result.h" #include "nn/acp/nn_acp_miscservice.h" #include #include using namespace nn::acp; using namespace nn::ipc; namespace cafe::nn_acp { //! Microseconds for Unix-epoch timestamp 01/01/2000 @ 12:00am static constexpr auto NetworkTimeEpoch = 946684800000000; /** * Convert network time to calendar time. * * networkTime is microseconds since NetworkTimeEpoch. */ void ACPConvertNetworkTimeToOSCalendarTime(int64_t networkTime, virt_ptr calendarTime) { auto time = std::chrono::microseconds(networkTime) + std::chrono::microseconds(NetworkTimeEpoch); auto tm = platform::localtime(std::chrono::duration_cast(time).count()); calendarTime->tm_sec = tm.tm_sec; calendarTime->tm_min = tm.tm_min; calendarTime->tm_hour = tm.tm_hour; calendarTime->tm_mday = tm.tm_mday; calendarTime->tm_mon = tm.tm_mon; calendarTime->tm_year = tm.tm_year + 1900; // posix tm_year is year - 1900 calendarTime->tm_wday = tm.tm_wday; calendarTime->tm_yday = tm.tm_yday; auto timeOffset = time - std::chrono::duration_cast(time); auto msOffset = std::chrono::duration_cast(timeOffset); auto uOffset = std::chrono::duration_cast(timeOffset - msOffset); calendarTime->tm_msec = static_cast(msOffset.count()); calendarTime->tm_usec = static_cast(uOffset.count()); } /** * Sets outTime to microseconds since NetworkTimeEpoch */ ACPResult ACPGetNetworkTime(virt_ptr outTime, virt_ptr outUnknown) { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { auto time = int64_t { 0 }; auto unk = uint32_t { 0 }; result = command.readResponse(time, unk); if (result.ok()) { *outTime = time - NetworkTimeEpoch; *outUnknown = unk; } } return ACPConvertToACPResult(result, "ACPGetNetworkTime", 79); } ACPResult ACPGetTitleIdOfMainApplication(virt_ptr outTitleId) { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { auto titleId = ACPTitleId{ 0 }; result = command.readResponse(titleId); if (result.ok()) { *outTitleId = titleId; } } return ACPConvertToACPResult(result, "GetTitleIdOfMainApplication", 126); } ACPResult ACPGetTitleMetaXml(ACPTitleId titleId, virt_ptr outData) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(outData, titleId); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "GetTitleMetaXml", 40); } void Library::registerMiscServiceSymbols() { RegisterFunctionExport(ACPConvertNetworkTimeToOSCalendarTime); RegisterFunctionExport(ACPGetNetworkTime); RegisterFunctionExport(ACPGetTitleIdOfMainApplication); RegisterFunctionExport(ACPGetTitleMetaXml); RegisterFunctionExportName("GetTitleMetaXml__Q2_2nn3acpFULP11_ACPMetaXml", ACPGetTitleMetaXml); } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_miscservice.h ================================================ #pragma once #include "nn_acp_acpresult.h" #include "nn/acp/nn_acp_miscservice.h" #include namespace cafe::coreinit { struct OSCalendarTime; } // namespace cafe::coreinit namespace cafe::nn_acp { using ACPMetaXml = nn::acp::ACPMetaXml; using ACPTitleId = nn::acp::ACPTitleId; ACPResult ACPGetNetworkTime(virt_ptr outTime, virt_ptr outUnknown); void ACPConvertNetworkTimeToOSCalendarTime(int64_t networkTime, virt_ptr calendarTime); ACPResult ACPGetTitleIdOfMainApplication(virt_ptr outTitleId); ACPResult ACPGetTitleMetaXml(ACPTitleId titleId, virt_ptr outData); } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_saveservice.cpp ================================================ #include "nn_acp.h" #include "nn_acp_client.h" #include "nn_acp_saveservice.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/acp/nn_acp_result.h" #include "nn/acp/nn_acp_saveservice.h" using namespace nn::acp; using namespace nn::ipc; namespace cafe::nn_acp { ACPResult ACPCreateSaveDir(uint32_t persistentId, ACPDeviceType deviceType) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(persistentId, deviceType); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPCreateSaveDir", 771); } ACPResult ACPCreateSaveDirEx(uint32_t persistentId, ACPTitleId titleId, ACPDeviceType deviceType) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(persistentId, titleId, deviceType); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPCreateSaveDirEx", 783); } ACPResult ACPIsExternalStorageRequired(virt_ptr outRequired) { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { auto required = int32_t{ 0 }; result = command.readResponse(required); if (result.ok()) { *outRequired = required; } } return ACPConvertToACPResult(result, "ACPIsExternalStorageRequired", 1464); } ACPResult ACPMountExternalStorage() { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPMountExternalStorage", 1452); } ACPResult ACPMountSaveDir() { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPMountSaveDir", 96); } ACPResult ACPRepairSaveMetaDir() { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPRepairSaveMetaDir", 1538); } ACPResult ACPUnmountExternalStorage() { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPUnmountExternalStorage", 1458); } ACPResult ACPUnmountSaveDir() { auto command = ClientCommand { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return ACPConvertToACPResult(result, "ACPUnmountSaveDir", 105); } void Library::registerSaveServiceSymbols() { RegisterFunctionExport(ACPCreateSaveDir); RegisterFunctionExport(ACPCreateSaveDirEx); RegisterFunctionExport(ACPIsExternalStorageRequired); RegisterFunctionExport(ACPMountExternalStorage); RegisterFunctionExport(ACPMountSaveDir); RegisterFunctionExport(ACPRepairSaveMetaDir); RegisterFunctionExport(ACPUnmountExternalStorage); RegisterFunctionExport(ACPUnmountSaveDir); RegisterFunctionExportName("CreateSaveDir__Q2_2nn3acpFUi13ACPDeviceType", ACPCreateSaveDir); RegisterFunctionExportName("RepairSaveMetaDir__Q2_2nn3acpFv", ACPRepairSaveMetaDir); } } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_saveservice.h ================================================ #pragma once #include "nn_acp_acpresult.h" #include "nn/acp/nn_acp_saveservice.h" #include namespace cafe::nn_acp { using ACPDeviceType = nn::acp::ACPDeviceType; ACPResult ACPCreateSaveDir(uint32_t persistentId, ACPDeviceType deviceType); ACPResult ACPIsExternalStorageRequired(virt_ptr outRequired); ACPResult ACPMountExternalStorage(); ACPResult ACPMountSaveDir(); ACPResult ACPRepairSaveMetaDir(); ACPResult ACPUnmountExternalStorage(); ACPResult ACPUnmountSaveDir(); } // namespace cafe::nn_acp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act.cpp ================================================ #include "nn_act.h" namespace cafe::nn_act { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); registerAccountLoaderServiceSymbols(); registerAccountManagerServiceSymbols(); registerClientStandardServiceSymbols(); registerServerStandardServiceSymbols(); } } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_act { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_act, "nn_act.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); void registerAccountLoaderServiceSymbols(); void registerAccountManagerServiceSymbols(); void registerClientStandardServiceSymbols(); void registerServerStandardServiceSymbols(); }; } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountloaderservice.cpp ================================================ #include "nn_act.h" #include "nn_act_client.h" #include "nn_act_accountloaderservice.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/act/nn_act_accountloaderservice.h" #include "nn/act/nn_act_result.h" #include #include using namespace nn::act; using namespace nn::ipc; namespace cafe::nn_act { nn::Result LoadConsoleAccount(SlotNo slot, ACTLoadOption loadOption, virt_ptr arg3, bool arg4) { auto command = ClientCommand { internal::getAllocator() }; if (arg3) { auto arg3Size = static_cast(strnlen(arg3.get(), 16) + 1); if (arg3Size > 0x11) { return ResultInvalidSize; } command.setParameters(slot, loadOption, { arg3, arg3Size }, !!arg4); } else { command.setParameters(slot, loadOption, { nullptr, 0 }, !!arg4); } return internal::getClient()->sendSyncRequest(command); } void Library::registerAccountLoaderServiceSymbols() { RegisterFunctionExportName("LoadConsoleAccount__Q2_2nn3actFUc13ACTLoadOptionPCcb", LoadConsoleAccount); } } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountloaderservice.h ================================================ #pragma once #include "nn/nn_result.h" #include "nn/act/nn_act_enum.h" #include "nn/act/nn_act_types.h" namespace cafe::nn_act { nn::Result LoadConsoleAccount(nn::act::SlotNo slot, nn::act::ACTLoadOption option, virt_ptr arg3, bool arg4); } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountmanagerservice.cpp ================================================ #include "nn_act.h" #include "nn_act_client.h" #include "nn_act_accountmanagerservice.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/act/nn_act_accountmanagerservice.h" #include using namespace nn::act; using namespace nn::ipc; namespace cafe::nn_act { nn::Result CreateConsoleAccount() { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(); return internal::getClient()->sendSyncRequest(command); } void Library::registerAccountManagerServiceSymbols() { RegisterFunctionExportName("CreateConsoleAccount__Q2_2nn3actFv", CreateConsoleAccount); } } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountmanagerservice.h ================================================ #pragma once #include "nn/nn_result.h" namespace cafe::nn_act { nn::Result CreateConsoleAccount(); } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_client.cpp ================================================ #include "nn_act.h" #include "nn_act_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/act/nn_act_result.h" using namespace cafe::coreinit; using namespace nn::act; namespace cafe::nn_act { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(64) be2_array allocatorMemory; be2_struct mutex; be2_val refCount = 0u; be2_struct client; be2_struct allocator; }; static virt_ptr sClientData = nullptr; nn::Result Initialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->client.initialise(make_stack_string("/dev/act")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return nn::ResultSuccess; } nn::Result Finalize() { auto result = nn::ResultSuccess; OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); } } else { result = 0xC070FA80; } OSUnlockMutex(virt_addrof(sClientData->mutex)); return result; } namespace internal { virt_ptr getClient() { return virt_addrof(sClientData->client); } virt_ptr getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn3actFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn3actFv", Finalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_client.h ================================================ #pragma once #include "nn/nn_result.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" namespace cafe::nn_act { nn::Result Initialize(); nn::Result Finalize(); namespace internal { virt_ptr getClient(); virt_ptr getAllocator(); } // namespace internal } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_clientstandardservice.cpp ================================================ #include "nn_act.h" #include "nn_act_client.h" #include "nn_act_clientstandardservice.h" #include "cafe/cafe_stackobject.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/act/nn_act_result.h" #include "nn/act/nn_act_clientstandardservice.h" #include "nn/ffl/nn_ffl_miidata.h" #include using namespace nn::act; using namespace nn::ipc; namespace cafe::nn_act { nn::Result GetAccountId(virt_ptr accountId) { if (!accountId) { return ResultInvalidPointer; } return internal::GetAccountInfo(CurrentUserSlot, accountId, AccountIdSize, InfoType::AccountId); } nn::Result GetAccountIdEx(virt_ptr accountId, SlotNo slotNo) { if (!accountId) { return ResultInvalidPointer; } return internal::GetAccountInfo(slotNo, accountId, AccountIdSize, InfoType::AccountId); } nn::Result GetBirthday(virt_ptr year, virt_ptr month, virt_ptr day) { return GetBirthdayEx(year, month, day, CurrentUserSlot); } nn::Result GetBirthdayEx(virt_ptr year, virt_ptr month, virt_ptr day, SlotNo slotNo) { StackObject birthday; if (!year || !month || !day) { return ResultInvalidPointer; } auto result = internal::GetAccountInfo(CurrentUserSlot, birthday, sizeof(Birthday), InfoType::Birthday); if (result) { *year = birthday->year; *month = birthday->month; *day = birthday->day; } return result; } SlotNo GetDefaultAccount() { StackObject slotNo; if (!internal::GetAccountInfo(CurrentUserSlot, slotNo, sizeof(SlotNo), InfoType::DefaultAccount)) { return 0; } return *slotNo; } nn::Result GetDeviceHash(virt_ptr hash) { if (!hash) { return ResultInvalidPointer; } return internal::GetCommonInfo(hash, sizeof(uint64_t), InfoType::DeviceHash); } nn::Result GetMii(virt_ptr mii) { if (!mii) { return ResultInvalidPointer; } return internal::GetAccountInfo(CurrentUserSlot, mii, sizeof(nn::ffl::FFLStoreData), InfoType::Mii); } nn::Result GetMiiEx(virt_ptr mii, SlotNo slotNo) { if (!mii) { return ResultInvalidPointer; } return internal::GetAccountInfo(slotNo, mii, sizeof(nn::ffl::FFLStoreData), InfoType::Mii); } nn::Result GetMiiImageEx(virt_ptr outImageSize, virt_ptr buffer, uint32_t bufferSize, MiiImageType miiImageType, SlotNo slot) { auto command = ClientCommand{ internal::getAllocator() }; command.setParameters(slot, { buffer, bufferSize }, miiImageType); auto result = internal::getClient()->sendSyncRequest(command); if (result) { auto imageSize = uint32_t { 0 }; result = command.readResponse(imageSize); if (result) { *outImageSize = imageSize; } } return result; } nn::Result GetMiiName(virt_ptr name) { return GetMiiNameEx(name, CurrentUserSlot); } nn::Result GetMiiNameEx(virt_ptr name, SlotNo slotNo) { if (!name) { return ResultInvalidPointer; } return internal::GetAccountInfo(slotNo, name, MiiNameSize * sizeof(char16_t), InfoType::MiiName); } nn::Result GetNfsPassword(virt_ptr password) { return GetNfsPasswordEx(password, CurrentUserSlot); } nn::Result GetNfsPasswordEx(virt_ptr password, SlotNo slotNo) { if (!password) { return ResultInvalidPointer; } return internal::GetAccountInfo(slotNo, password, 17, InfoType::NfsPassword); } uint8_t GetNumOfAccounts() { StackObject num; if (!internal::GetCommonInfo(num, sizeof(uint8_t), InfoType::NumOfAccounts)) { return 0; } return *num; } SlotNo GetParentalControlSlotNo() { StackObject slotNo; if (!internal::GetAccountInfo(CurrentUserSlot, slotNo, sizeof(SlotNo), InfoType::ParentalControlSlot)) { return 0; } return *slotNo; } nn::Result GetParentalControlSlotNoEx(virt_ptr parentalSlotNo, SlotNo slotNo) { if (!parentalSlotNo) { return ResultInvalidPointer; } return internal::GetAccountInfo(slotNo, parentalSlotNo, sizeof(SlotNo), InfoType::ParentalControlSlot); } PersistentId GetPersistentId() { StackObject id; if (!internal::GetAccountInfo(CurrentUserSlot, id, sizeof(PersistentId), InfoType::PersistentId)) { return 0; } return *id; } PersistentId GetPersistentIdEx(SlotNo slotNo) { StackObject id; if (!internal::GetAccountInfo(slotNo, id, sizeof(PersistentId), InfoType::PersistentId)) { return 0; } return *id; } PrincipalId GetPrincipalId() { StackObject id; if (!internal::GetAccountInfo(CurrentUserSlot, id, sizeof(PrincipalId), InfoType::PrincipalId)) { return 0; } return *id; } nn::Result GetPrincipalIdEx(virt_ptr id, SlotNo slot) { if (!id) { return ResultInvalidPointer; } return internal::GetAccountInfo(slot, id, sizeof(PrincipalId), InfoType::PrincipalId); } SimpleAddressId GetSimpleAddressId() { StackObject id; if (!internal::GetAccountInfo(CurrentUserSlot, id, sizeof(SimpleAddressId), InfoType::SimpleAddressId)) { return 0; } return *id; } nn::Result GetSimpleAddressIdEx(virt_ptr id, SlotNo slot) { if (!id) { return ResultInvalidPointer; } return internal::GetAccountInfo(slot, id, sizeof(SimpleAddressId), InfoType::SimpleAddressId); } SlotNo GetSlotNo() { StackObject slotNo; if (!internal::GetCommonInfo(slotNo, sizeof(SlotNo), InfoType::SlotNo)) { return 0; } return *slotNo; } uint64_t GetTransferableId(uint32_t unk1) { StackObject id; GetTransferableIdEx(id, unk1, CurrentUserSlot); return *id; } nn::Result GetTransferableIdEx(virt_ptr id, uint32_t unk1, SlotNo slotNo) { if (!id) { return ResultInvalidPointer; } auto command = ClientCommand { internal::getAllocator() }; command.setParameters(slotNo, unk1); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { auto value = TransferrableId { }; result = command.readResponse(value); if (result.ok()) { *id = value; } } return result; } nn::Result GetUuidEx(virt_ptr uuid, SlotNo slotNo, int32_t arg3) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(slotNo, uuid, arg3); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return result; } nn::Result GetUuidEx(virt_ptr uuid, SlotNo slot) { return GetUuidEx(uuid, slot, -2); } nn::Result GetUuid(virt_ptr uuid, int32_t arg3) { return GetUuidEx(uuid, CurrentUserSlot, arg3); } nn::Result GetUuid(virt_ptr uuid) { return GetUuidEx(uuid, CurrentUserSlot, -2); } bool HasNfsAccount() { StackArray nfsPassword; if (!GetNfsPassword(nfsPassword)) { return false; } return nfsPassword[0] != '\0'; } bool IsCommitted() { return IsCommittedEx(CurrentUserSlot); } bool IsCommittedEx(SlotNo slotNo) { StackObject value; if (!internal::GetAccountInfo(slotNo, value, sizeof(uint8_t), InfoType::IsCommitted)) { return 0; } return *value != 0; } bool IsPasswordCacheEnabled() { return IsPasswordCacheEnabledEx(GetSlotNo()); } bool IsPasswordCacheEnabledEx(SlotNo slotNo) { StackObject value; if (!internal::GetAccountInfo(slotNo, value, sizeof(uint8_t), InfoType::IsPasswordCacheEnabled)) { return 0; } return *value != 0; } bool IsNetworkAccount() { StackArray accountId; if (!GetAccountId(accountId)) { return false; } return accountId[0] != 0; } bool IsNetworkAccountEx(SlotNo slotNo) { StackArray accountId; if (!GetAccountIdEx(accountId, slotNo)) { return false; } return accountId[0] != 0; } bool IsServerAccountActive() { return IsServerAccountActiveEx(CurrentUserSlot); } bool IsServerAccountActiveEx(SlotNo slotNo) { StackObject value; if (!internal::GetAccountInfo(slotNo, value, sizeof(uint32_t), InfoType::ServerAccountStatus)) { return 0; } return *value == 0; } bool IsServerAccountDeleted() { return IsServerAccountDeletedEx(CurrentUserSlot); } bool IsServerAccountDeletedEx(SlotNo slotNo) { StackObject value; if (!internal::GetAccountInfo(slotNo, value, sizeof(uint8_t), InfoType::IsServerAccountDeleted)) { return 0; } return !!*value; } bool IsSlotOccupied(SlotNo slot) { return GetPersistentIdEx(slot) != 0; } namespace internal { nn::Result GetAccountInfo(SlotNo slotNo, virt_ptr buffer, uint32_t bufferSize, InfoType type) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(slotNo, { buffer, bufferSize }, type); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return result; } nn::Result GetCommonInfo(virt_ptr buffer, uint32_t bufferSize, InfoType type) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters({ buffer, bufferSize }, type); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return result; } } // namespace internal void Library::registerClientStandardServiceSymbols() { RegisterFunctionExportName("GetAccountId__Q2_2nn3actFPc", GetAccountId); RegisterFunctionExportName("GetAccountIdEx__Q2_2nn3actFPcUc", GetAccountIdEx); RegisterFunctionExportName("GetBirthday__Q2_2nn3actFPUsPUcT2", GetBirthday); RegisterFunctionExportName("GetBirthdayEx__Q2_2nn3actFPUsPUcT2Uc", GetBirthdayEx); RegisterFunctionExportName("GetDefaultAccount__Q2_2nn3actFv", GetDefaultAccount); RegisterFunctionExportName("GetDeviceHash__Q2_2nn3actFPUL", GetDeviceHash); RegisterFunctionExportName("GetMii__Q2_2nn3actFP12FFLStoreData", GetMii); RegisterFunctionExportName("GetMiiEx__Q2_2nn3actFP12FFLStoreDataUc", GetMiiEx); RegisterFunctionExportName("GetMiiImageEx__Q2_2nn3actFPUiPvUi15ACTMiiImageTypeUc", GetMiiImageEx); RegisterFunctionExportName("GetMiiName__Q2_2nn3actFPw", GetMiiName); RegisterFunctionExportName("GetMiiNameEx__Q2_2nn3actFPwUc", GetMiiNameEx); RegisterFunctionExportName("GetNfsPassword__Q2_2nn3actFPc", GetNfsPassword); RegisterFunctionExportName("GetNfsPasswordEx__Q2_2nn3actFPcUc", GetNfsPasswordEx); RegisterFunctionExportName("GetNumOfAccounts__Q2_2nn3actFv", GetNumOfAccounts); RegisterFunctionExportName("GetParentalControlSlotNo__Q2_2nn3actFv", GetParentalControlSlotNo); RegisterFunctionExportName("GetParentalControlSlotNoEx__Q2_2nn3actFPUcUc", GetParentalControlSlotNoEx); RegisterFunctionExportName("GetPersistentId__Q2_2nn3actFv", GetPersistentId); RegisterFunctionExportName("GetPersistentIdEx__Q2_2nn3actFUc", GetPersistentIdEx); RegisterFunctionExportName("GetPrincipalId__Q2_2nn3actFv", GetPrincipalId); RegisterFunctionExportName("GetPrincipalIdEx__Q2_2nn3actFPUiUc", GetPrincipalIdEx); RegisterFunctionExportName("GetSimpleAddressId__Q2_2nn3actFv", GetSimpleAddressId); RegisterFunctionExportName("GetSimpleAddressIdEx__Q2_2nn3actFPUiUc", GetSimpleAddressIdEx); RegisterFunctionExportName("GetSlotNo__Q2_2nn3actFv", GetSlotNo); RegisterFunctionExportName("GetTransferableId__Q2_2nn3actFUi", GetTransferableId); RegisterFunctionExportName("GetTransferableIdEx__Q2_2nn3actFPULUiUc", GetTransferableIdEx); RegisterFunctionExportName("GetUuid__Q2_2nn3actFP7ACTUuid", static_cast)>(GetUuid)); RegisterFunctionExportName("GetUuid__Q2_2nn3actFP7ACTUuidUi", static_cast, int32_t)>(GetUuid)); RegisterFunctionExportName("GetUuidEx__Q2_2nn3actFP7ACTUuidUc", static_cast, SlotNo)>(GetUuidEx)); RegisterFunctionExportName("GetUuidEx__Q2_2nn3actFP7ACTUuidUcUi", static_cast, SlotNo, int32_t)>(GetUuidEx)); RegisterFunctionExportName("HasNfsAccount__Q2_2nn3actFv", HasNfsAccount); RegisterFunctionExportName("IsCommitted__Q2_2nn3actFv", IsCommitted); RegisterFunctionExportName("IsCommittedEx__Q2_2nn3actFUc", IsCommittedEx); RegisterFunctionExportName("IsPasswordCacheEnabled__Q2_2nn3actFv", IsPasswordCacheEnabled); RegisterFunctionExportName("IsPasswordCacheEnabledEx__Q2_2nn3actFUc", IsPasswordCacheEnabledEx); RegisterFunctionExportName("IsNetworkAccount__Q2_2nn3actFv", IsNetworkAccount); RegisterFunctionExportName("IsNetworkAccountEx__Q2_2nn3actFUc", IsNetworkAccountEx); RegisterFunctionExportName("IsServerAccountActive__Q2_2nn3actFv", IsServerAccountActive); RegisterFunctionExportName("IsServerAccountActiveEx__Q2_2nn3actFUc", IsServerAccountActiveEx); RegisterFunctionExportName("IsServerAccountDeleted__Q2_2nn3actFv", IsServerAccountDeleted); RegisterFunctionExportName("IsServerAccountDeletedEx__Q2_2nn3actFUc", IsServerAccountDeletedEx); RegisterFunctionExportName("IsSlotOccupied__Q2_2nn3actFUc", IsSlotOccupied); } } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_clientstandardservice.h ================================================ #pragma once #include "nn/nn_result.h" #include "nn/act/nn_act_enum.h" #include "nn/act/nn_act_types.h" #include "nn/ffl/nn_ffl_miidata.h" #include namespace cafe::nn_act { using nn::act::MiiImageType; using nn::act::PersistentId; using nn::act::PrincipalId; using nn::act::SlotNo; using nn::act::SimpleAddressId; using nn::act::TransferrableId; using nn::act::Uuid; nn::Result GetAccountId(virt_ptr accountId); nn::Result GetAccountIdEx(virt_ptr accountId, SlotNo slotNo); nn::Result GetBirthday(virt_ptr year, virt_ptr month, virt_ptr day); nn::Result GetBirthdayEx(virt_ptr year, virt_ptr month, virt_ptr day, SlotNo slotNo); SlotNo GetDefaultAccount(); nn::Result GetDeviceHash(virt_ptr hash); nn::Result GetMii(virt_ptr mii); nn::Result GetMiiEx(virt_ptr mii, SlotNo slotNo); nn::Result GetMiiImageEx(virt_ptr outImageSize, virt_ptr buffer, uint32_t bufferSize, MiiImageType miiImageType, SlotNo slot); nn::Result GetMiiName(virt_ptr name); nn::Result GetMiiNameEx(virt_ptr name, SlotNo slotNo); nn::Result GetNfsPassword(virt_ptr password); nn::Result GetNfsPasswordEx(virt_ptr password, SlotNo slotNo); uint8_t GetNumOfAccounts(); SlotNo GetParentalControlSlotNo(); nn::Result GetParentalControlSlotNoEx(virt_ptr parentalSlotNo, SlotNo slotNo); PersistentId GetPersistentId(); PersistentId GetPersistentIdEx(SlotNo slotNo); PrincipalId GetPrincipalId(); nn::Result GetPrincipalIdEx(virt_ptr id, SlotNo slot); SimpleAddressId GetSimpleAddressId(); nn::Result GetSimpleAddressIdEx(virt_ptr id, SlotNo slot); SlotNo GetSlotNo(); uint64_t GetTransferableId(uint32_t unk1); nn::Result GetTransferableIdEx(virt_ptr id, uint32_t unk1, SlotNo slotNo); nn::Result GetUuidEx(virt_ptr uuid, SlotNo slotNo, int32_t arg3); nn::Result GetUuidEx(virt_ptr uuid, SlotNo slot); nn::Result GetUuid(virt_ptr uuid, int32_t arg3); nn::Result GetUuid(virt_ptr uuid); bool HasNfsAccount(); bool IsCommitted(); bool IsCommittedEx(SlotNo slotNo); bool IsPasswordCacheEnabled(); bool IsPasswordCacheEnabledEx(SlotNo slotNo); bool IsNetworkAccount(); bool IsNetworkAccountEx(SlotNo slotNo); bool IsServerAccountActive(); bool IsServerAccountActiveEx(SlotNo slotNo); bool IsServerAccountDeleted(); bool IsServerAccountDeletedEx(SlotNo slotNo); bool IsSlotOccupied(SlotNo slot); namespace internal { nn::Result GetAccountInfo(nn::act::SlotNo slotNo, virt_ptr buffer, uint32_t bufferSize, nn::act::InfoType type); nn::Result GetCommonInfo(virt_ptr buffer, uint32_t bufferSize, nn::act::InfoType type); } // namespace internal } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_serverstandardservice.cpp ================================================ #include "nn_act.h" #include "nn_act_client.h" #include "nn_act_serverstandardservice.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/act/nn_act_serverstandardservice.h" #include using namespace nn::act; using namespace nn::ipc; namespace cafe::nn_act { using services::ServerStandardService; struct StaticServerData { be2_val parentalControlCheckEnabled = false; }; static virt_ptr sStaticServerData = nullptr; nn::Result AcquireNexServiceToken(virt_ptr result, uint32_t gameId) { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(CurrentUserSlot, result, gameId, sStaticServerData->parentalControlCheckEnabled); return internal::getClient()->sendSyncRequest(command); } nn::Result Cancel() { auto command = ClientCommand { internal::getAllocator() }; command.setParameters(); return internal::getClient()->sendSyncRequest(command); } void EnableParentalControlCheck(bool enable) { sStaticServerData->parentalControlCheckEnabled = enable; } bool IsParentalControlCheckEnabled() { return sStaticServerData->parentalControlCheckEnabled; } void Library::registerServerStandardServiceSymbols() { RegisterFunctionExportName("AcquireNexServiceToken__Q2_2nn3actFP26ACTNexAuthenticationResultUi", AcquireNexServiceToken); RegisterFunctionExportName("Cancel__Q2_2nn3actFv", Cancel); RegisterFunctionExportName("IsParentalControlCheckEnabled__Q2_2nn3actFv", IsParentalControlCheckEnabled); RegisterFunctionExportName("EnableParentalControlCheck__Q2_2nn3actFb", EnableParentalControlCheck); RegisterDataInternal(sStaticServerData); } } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_act/nn_act_serverstandardservice.h ================================================ #pragma once #include "nn/nn_result.h" #include "nn/act/nn_act_types.h" namespace cafe::nn_act { using ACTNexAuthenticationResult = nn::act::NexAuthenticationResult; nn::Result AcquireNexServiceToken(virt_ptr result, uint32_t unk); nn::Result Cancel(); bool IsParentalControlCheckEnabled(); void EnableParentalControlCheck(bool enable); } // namespace cafe::nn_act ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc.cpp ================================================ #include "nn_aoc.h" namespace cafe::nn_aoc { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerLibSymbols(); } } // namespace cafe::nn_aoc ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_aoc { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_aoc, "nn_aoc.rpl") { } protected: virtual void registerSymbols() override; private: void registerLibSymbols(); }; } // namespace cafe::nn_aoc ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc_enum.h ================================================ #ifndef CAFE_NN_AOC_ENUM_H #define CAFE_NN_AOC_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_aoc) ENUM_BEG(AOCError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(GenericError, -1) ENUM_END(AOCError) ENUM_NAMESPACE_EXIT(nn_aoc) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_NN_AOC_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc_lib.cpp ================================================ #include "nn_aoc.h" #include "nn_aoc_lib.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nn_aoc { AOCError AOC_Initialize() { decaf_warn_stub(); return AOCError::OK; } AOCError AOC_Finalize() { decaf_warn_stub(); return AOCError::OK; } uint32_t AOC_CalculateWorkBufferSize(uint32_t maxTitles) { if (maxTitles > 256) { maxTitles = 256; } return (0x61 * maxTitles) + 0x80; } AOCError AOC_ListTitle(virt_ptr outTitleCount, virt_ptr titles, uint32_t maxTitles, virt_ptr workBuffer, uint32_t workBufferSize) { decaf_warn_stub(); *outTitleCount = 0u; return AOCError::OK; } void Library::registerLibSymbols() { RegisterFunctionExportName("AOC_Initialize", AOC_Initialize); RegisterFunctionExportName("AOC_Finalize", AOC_Finalize); RegisterFunctionExportName("AOC_CalculateWorkBufferSize", AOC_CalculateWorkBufferSize); RegisterFunctionExportName("AOC_ListTitle", AOC_ListTitle); } } // namespace cafe::nn_aoc ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc_lib.h ================================================ #pragma once #include "nn_aoc_enum.h" #include namespace cafe::nn_aoc { #pragma pack(push, 1) struct AOCTitle { UNKNOWN(0x61); }; CHECK_SIZE(AOCTitle, 0x61); #pragma pack(pop) AOCError AOC_Initialize(); AOCError AOC_Finalize(); uint32_t AOC_CalculateWorkBufferSize(uint32_t maxTitles); AOCError AOC_ListTitle(virt_ptr outTitleCount, virt_ptr titles, uint32_t maxTitles, virt_ptr workBuffer, uint32_t workBufferSize); } // namespace cafe::nn_aoc ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss.cpp ================================================ #include "nn_boss.h" namespace cafe::nn_boss { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerAccountSymbols(); registerAlmightyStorageSymbols(); registerAlmightyTaskSymbols(); registerClientSymbols(); registerDataNameSymbols(); registerNbdlTaskSettingSymbols(); registerNetTaskSettingSymbols(); registerPlayLogUploadTaskSettingSymbols(); registerPlayReportSettingSymbols(); registerPrivilegedTaskSymbols(); registerRawUlTaskSettingSymbols(); registerStorageSymbols(); registerTaskSymbols(); registerTaskIdSymbols(); registerTaskSettingSymbols(); registerTitleSymbols(); registerTitleIdSymbols(); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_boss { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_boss, "nn_boss.rpl") { } protected: virtual void registerSymbols() override; private: void registerAccountSymbols(); void registerAlmightyStorageSymbols(); void registerAlmightyTaskSymbols(); void registerClientSymbols(); void registerDataNameSymbols(); void registerNbdlTaskSettingSymbols(); void registerNetTaskSettingSymbols(); void registerPlayLogUploadTaskSettingSymbols(); void registerPlayReportSettingSymbols(); void registerPrivilegedTaskSymbols(); void registerRawUlTaskSettingSymbols(); void registerStorageSymbols(); void registerTaskSymbols(); void registerTaskIdSymbols(); void registerTaskSettingSymbols(); void registerTitleSymbols(); void registerTitleIdSymbols(); }; } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_account.cpp ================================================ #include "nn_boss.h" #include "nn_boss_account.h" #include "nn_boss_client.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/boss/nn_boss_result.h" #include "nn/boss/nn_boss_privileged_service.h" using namespace nn::boss; using namespace nn::boss::services; using namespace nn::ipc; namespace cafe::nn_boss { virt_ptr Account::VirtualTable = nullptr; virt_ptr Account::TypeDescriptor = nullptr; virt_ptr Account_Constructor(virt_ptr self, uint32_t a1) { if (!self) { self = virt_cast(ghs::malloc(sizeof(Account))); if (!self) { return nullptr; } } self->virtualTable = Account::VirtualTable; self->unk0x00 = a1; return self; } void Account_Destructor(virt_ptr self, ghs::DestructorFlags flags) { if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result Account_AddAccount(PersistentId persistentId) { if (!IsInitialized()) { return ResultLibraryNotInitialiased; } auto command = ClientCommand { internal::getAllocator() }; command.setParameters(persistentId); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return result; } void Library::registerAccountSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss7AccountFUi", Account_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss7AccountFv", Account_Destructor); RegisterFunctionExportName("AddAccount__Q3_2nn4boss7AccountSFUi", Account_AddAccount); RegisterTypeInfo( Account, "nn::boss::Account", { "__dt__Q3_2nn4boss7AccountFv", }, {}); } } // namespace namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_account.h ================================================ #pragma once #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include "nn/boss/nn_boss_types.h" #include namespace cafe::nn_boss { using PersistentId = nn::boss::PersistentId; #pragma pack(push, 1) struct Account { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; be2_val unk0x00; be2_virt_ptr virtualTable; }; CHECK_OFFSET(Account, 0x00, unk0x00); CHECK_OFFSET(Account, 0x04, virtualTable); CHECK_SIZE(Account, 0x8); #pragma pack(pop) virt_ptr Account_Constructor(virt_ptr self, uint32_t a1); void Account_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result Account_AddAccount(PersistentId persistentId); } // namespace namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightystorage.cpp ================================================ #include "nn_boss.h" #include "nn_boss_almightystorage.h" #include "nn_boss_enum.h" #include "nn_boss_storage.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/boss/nn_boss_result.h" #include using namespace nn::boss; namespace cafe::nn_boss { virt_ptr AlmightyStorage::VirtualTable = nullptr; virt_ptr AlmightyStorage::TypeDescriptor = nullptr; virt_ptr AlmightyStorage_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(AlmightyStorage))); if (!self) { return nullptr; } } Storage_Constructor(virt_cast(self)); self->virtualTable = AlmightyStorage::VirtualTable; return self; } void AlmightyStorage_Destructor(virt_ptr self, ghs::DestructorFlags flags) { Storage_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result AlmightyStorage_Initialize(virt_ptr self, virt_ptr titleId, virt_ptr directory, nn::act::PersistentId persistentId, StorageKind storageKind) { if (!directory) { return ResultInvalidParameter; } self->titleID = *titleId; self->persistentId = persistentId; self->storageKind = storageKind; self->directoryName = directory.get(); self->directoryName[7] = '\0'; return ResultSuccess; } void Library::registerAlmightyStorageSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss15AlmightyStorageFv", static_cast (*)(virt_ptr)>(AlmightyStorage_Constructor)); RegisterFunctionExportName("__dt__Q3_2nn4boss15AlmightyStorageFv", AlmightyStorage_Destructor); RegisterFunctionExportName("Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", AlmightyStorage_Initialize); RegisterTypeInfo( AlmightyStorage, "nn::boss::AlmightyStorage", { "__dt__Q3_2nn4boss15AlmightyStorageFv", }, { "nn::boss::Storage", }); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightystorage.h ================================================ #pragma once #include "nn_boss_storage.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::AlmightyStorage::GetReadFlagFromNsDatas(const nn::boss::DataName *, unsigned int, bool *) const nn::boss::AlmightyStorage::GetBossStorageDirectoryList(unsigned int, nn::boss::TitleID, nn::boss::StorageKind, nn::boss::DataName *, unsigned int, unsigned int *, unsigned int) nn::boss::AlmightyStorage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool *) nn::boss::AlmightyStorage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool) */ namespace cafe::nn_boss { struct AlmightyStorage : public Storage { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; }; CHECK_SIZE(AlmightyStorage, 0x28); virt_ptr AlmightyStorage_Constructor(virt_ptr self); void AlmightyStorage_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result AlmightyStorage_Initialize(virt_ptr self, virt_ptr titleId, virt_ptr directory, nn::act::PersistentId persistentId, StorageKind storageKind); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightytask.cpp ================================================ #include "nn_boss.h" #include "nn_boss_almightytask.h" #include "nn_boss_privilegedtask.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/boss/nn_boss_result.h" namespace cafe::nn_boss { virt_ptr AlmightyTask::VirtualTable = nullptr; virt_ptr AlmightyTask::TypeDescriptor = nullptr; virt_ptr AlmightyTask_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(AlmightyTask))); if (!self) { return nullptr; } } PrivilegedTask_Constructor(virt_cast(self)); self->virtualTable = AlmightyTask::VirtualTable; return self; } void AlmightyTask_Destructor(virt_ptr self, ghs::DestructorFlags flags) { if (!self) { return; } self->virtualTable = AlmightyTask::VirtualTable; PrivilegedTask_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result AlmightyTask_Initialize(virt_ptr self, virt_ptr titleId, virt_ptr taskId, uint32_t accountId) { if (!taskId) { return nn::boss::ResultInvalidParameter; } self->titleId = *titleId; TaskID_OperatorAssign(virt_addrof(self->taskId), taskId); self->accountId = accountId; return nn::boss::ResultSuccess; } void Library::registerAlmightyTaskSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss12AlmightyTaskFv", AlmightyTask_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss12AlmightyTaskFv", AlmightyTask_Destructor); RegisterFunctionExportName("Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", AlmightyTask_Initialize); RegisterTypeInfo( AlmightyTask, "nn::boss::AlmightyTask", { "__dt__Q3_2nn4boss12AlmightyTaskFv", }, {}); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightytask.h ================================================ #pragma once #include "nn_boss_privilegedtask.h" #include "nn_boss_titleid.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::AlmightyTask::GetTaskRecord((nn::boss::TaskRecord *,uint *)) nn::boss::AlmightyTask::SetHttpOption((ushort)) nn::boss::AlmightyTask::SetPermission((uchar)) */ namespace cafe::nn_boss { struct AlmightyTask : PrivilegedTask { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; }; CHECK_SIZE(AlmightyTask, 0x20); virt_ptr AlmightyTask_Constructor(virt_ptr self); void AlmightyTask_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result AlmightyTask_Initialize(virt_ptr self, virt_ptr titleId, virt_ptr taskId, uint32_t accountId); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_client.cpp ================================================ #include "nn_boss.h" #include "nn_boss_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/nn/cafe_nn_ipc_client.h" using namespace cafe::coreinit; using namespace nn::ipc; namespace cafe::nn_boss { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(256) be2_array allocatorMemory; be2_struct mutex; be2_val refCount = 0u; be2_struct client; be2_struct allocator; }; static virt_ptr sClientData = nullptr; nn::Result Initialize() { auto result = nn::ResultSuccess; OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { result = sClientData->client.initialise(make_stack_string("/dev/boss")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return result; } void Finalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } bool IsInitialized() { return sClientData->client.isInitialised(); } BossState GetBossState() { decaf_warn_stub(); return BossState::Unknown0; } namespace internal { virt_ptr getClient() { return virt_addrof(sClientData->client); } virt_ptr getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn4bossFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn4bossFv", Finalize); RegisterFunctionExportName("IsInitialized__Q2_2nn4bossFv", IsInitialized); RegisterFunctionExportName("GetBossState__Q2_2nn4bossFv", GetBossState); RegisterDataInternal(sClientData); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_client.h ================================================ #pragma once #include "nn_boss_enum.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/nn_result.h" namespace cafe::nn_boss { nn::Result Initialize(); void Finalize(); bool IsInitialized(); BossState GetBossState(); namespace internal { virt_ptr getClient(); virt_ptr getAllocator(); } // namespace internal } // namespace namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_dataname.cpp ================================================ #include "nn_boss.h" #include "nn_boss_dataname.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "common/strutils.h" namespace cafe::nn_boss { virt_ptr DataName_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(DataName))); if (!self) { return nullptr; } } self->name.fill('\0'); return self; } virt_ptr DataName_Constructor(virt_ptr self, virt_ptr name) { if (!self) { self = virt_cast(ghs::malloc(sizeof(DataName))); if (!self) { return nullptr; } } string_copy(virt_addrof(self->name).getRawPointer(), name.getRawPointer(), self->name.size()); self->name[31] = '\0'; return self; } virt_ptr DataName_Constructor(virt_ptr self, virt_ptr other) { if (!self) { self = virt_cast(ghs::malloc(sizeof(DataName))); if (!self) { return nullptr; } } memcpy(self.get(), other.get(), sizeof(DataName)); return self; } virt_ptr DataName_OperatorCastConstCharPtr(virt_ptr self) { return virt_addrof(self->name); } virt_ptr DataName_OperatorAssign(virt_ptr self, virt_ptr name) { string_copy(virt_addrof(self->name).getRawPointer(), name.getRawPointer(), self->name.size()); self->name[31] = '\0'; return self; } bool DataName_OperatorEqual(virt_ptr self, virt_ptr other) { return std::strncmp(virt_addrof(self->name).getRawPointer(), virt_addrof(other->name).getRawPointer(), 31) == 0; } bool DataName_OperatorEqual(virt_ptr self, virt_ptr name) { return std::strncmp(virt_addrof(self->name).getRawPointer(), name.getRawPointer(), 31) == 0; } bool DataName_OperatorNotEqual(virt_ptr self, virt_ptr other) { return !DataName_OperatorEqual(self, other); } bool DataName_OperatorNotEqual(virt_ptr self, virt_ptr name) { return !DataName_OperatorEqual(self, name); } void Library::registerDataNameSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss8DataNameFv", static_cast (*)(virt_ptr)>(DataName_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss8DataNameFPCc", static_cast (*)(virt_ptr, virt_ptr)>(DataName_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss8DataNameFRCQ3_2nn4boss8DataName", static_cast (*)(virt_ptr, virt_ptr)>(DataName_Constructor)); RegisterFunctionExportName("__opPCc__Q3_2nn4boss8DataNameCFv", DataName_OperatorCastConstCharPtr); RegisterFunctionExportName("__as__Q3_2nn4boss8DataNameFPCc", DataName_OperatorAssign); RegisterFunctionExportName("__eq__Q3_2nn4boss8DataNameCFRCQ3_2nn4boss8DataName", static_cast, virt_ptr)>(DataName_OperatorEqual)); RegisterFunctionExportName("__eq__Q3_2nn4boss8DataNameCFPCc", static_cast, virt_ptr)>(DataName_OperatorEqual)); RegisterFunctionExportName("__ne__Q3_2nn4boss8DataNameCFRCQ3_2nn4boss8DataName", static_cast, virt_ptr)>(DataName_OperatorNotEqual)); RegisterFunctionExportName("__ne__Q3_2nn4boss8DataNameCFPCc", static_cast, virt_ptr)>(DataName_OperatorNotEqual)); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_dataname.h ================================================ #pragma once #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include namespace cafe::nn_boss { struct DataName { be2_array name; }; CHECK_SIZE(DataName, 0x20); virt_ptr DataName_Constructor(virt_ptr self); virt_ptr DataName_Constructor(virt_ptr self, virt_ptr name); virt_ptr DataName_Constructor(virt_ptr self, virt_ptr other); virt_ptr DataName_OperatorCastConstCharPtr(virt_ptr self); virt_ptr DataName_OperatorAssign(virt_ptr self, virt_ptr name); bool DataName_OperatorEqual(virt_ptr self, virt_ptr other); bool DataName_OperatorEqual(virt_ptr self, virt_ptr name); bool DataName_OperatorNotEqual(virt_ptr self, virt_ptr other); bool DataName_OperatorNotEqual(virt_ptr self, virt_ptr name); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_enum.h ================================================ #ifndef CAFE_NN_BOSS_ENUM_H #define CAFE_NN_BOSS_ENUM_H #include ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_boss) ENUM_BEG(BossState, uint32_t) ENUM_VALUE(Unknown0, 0) ENUM_END(BossState) ENUM_BEG(StorageKind, uint32_t) ENUM_END(StorageKind) ENUM_NAMESPACE_EXIT(nn_boss) ENUM_NAMESPACE_EXIT(cafe) #include #endif // ifdef CAFE_NN_BOSS_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nbdltasksetting.cpp ================================================ #include "nn_boss.h" #include "nn_boss_nbdltasksetting.h" #include "nn_boss_nettasksetting.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/boss/nn_boss_result.h" #include namespace cafe::nn_boss { using namespace ::nn::boss; virt_ptr NbdlTaskSetting::VirtualTable = nullptr; virt_ptr NbdlTaskSetting::TypeDescriptor = nullptr; virt_ptr NbdlTaskSetting_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(NbdlTaskSetting))); if (!self) { return nullptr; } } NetTaskSetting_Constructor(virt_cast(self)); self->virtualTable = NbdlTaskSetting::VirtualTable; return self; } void NbdlTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags) { NetTaskSetting_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result NbdlTaskSetting_Initialize(virt_ptr self, virt_ptr bossCode, int64_t a3, virt_ptr directoryName) { if (!bossCode || strnlen(bossCode.get(), 32) == 32) { return ResultInvalidParameter; } if (directoryName && strnlen(directoryName.get(), 8) == 8) { return ResultInvalidParameter; } // TODO: Add appropriate fields to TaskSetting structure string_copy(reinterpret_cast(self.get()) + 0x7C0, 32, bossCode.get(), 32); *(reinterpret_cast(self.get()) + 0x7C0 + 32 - 1) = 0; if (directoryName) { string_copy(reinterpret_cast(self.get()) + 0x7E0, 8, directoryName.get(), 8); *(reinterpret_cast(self.get()) + 0x7E0 + 8 - 1) = 0; } *virt_cast(virt_cast(self) + 0x7F0) = a3; return ResultSuccess; } nn::Result NbdlTaskSetting_SetFileName(virt_ptr self, virt_ptr fileName) { if (!fileName || strnlen(fileName.get(), 8) == 8) { return ResultInvalidParameter; } // TODO: Add appropriate fields to TaskSetting structure string_copy(reinterpret_cast(self.get()) + 0x7F8, 32, fileName.get(), 32); *(reinterpret_cast(self.get()) + 0x7F8 + 32 - 1) = 0; return ResultSuccess; } nn::Result PrivateNbdlTaskSetting_SetCountryCode(virt_ptr self, virt_ptr countryCode) { if (!countryCode || strnlen(countryCode.get(), 3) != 2) { return ResultInvalidParameter; } // TODO: Add appropriate fields to TaskSetting structure string_copy(reinterpret_cast(self.get()) + 0x81C, 3, countryCode.get(), 3); *(reinterpret_cast(self.get()) + 0x81C + 3 - 1) = 0; return ResultSuccess; } nn::Result PrivateNbdlTaskSetting_SetLanguageCode(virt_ptr self, virt_ptr languageCode) { if (!languageCode || strnlen(languageCode.get(), 3) != 2) { return ResultInvalidParameter; } // TODO: Add appropriate fields to TaskSetting structure string_copy(reinterpret_cast(self.get()) + 0x818, 3, languageCode.get(), 3); *(reinterpret_cast(self.get()) + 0x818 + 3 - 1) = 0; return ResultSuccess; } nn::Result PrivateNbdlTaskSetting_SetOption(virt_ptr self, uint8_t option) { // TODO: Add appropriate fields to TaskSetting structure *(virt_cast(self) + 0x81F) = option; return ResultSuccess; } void Library::registerNbdlTaskSettingSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss15NbdlTaskSettingFv", NbdlTaskSetting_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss15NbdlTaskSettingFv", NbdlTaskSetting_Destructor); RegisterFunctionExportName("Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", NbdlTaskSetting_Initialize); RegisterFunctionExportName("SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", NbdlTaskSetting_SetFileName); RegisterFunctionExportName( "SetCountryCodeA2__Q3_2nn4boss22PrivateNbdlTaskSettingSFRQ3_2nn4boss15NbdlTaskSettingPCc", PrivateNbdlTaskSetting_SetCountryCode); RegisterFunctionExportName( "SetLanguageCodeA2__Q3_2nn4boss22PrivateNbdlTaskSettingSFRQ3_2nn4boss15NbdlTaskSettingPCc", PrivateNbdlTaskSetting_SetLanguageCode); RegisterFunctionExportName( "SetOption__Q3_2nn4boss22PrivateNbdlTaskSettingSFRQ3_2nn4boss15NbdlTaskSettingUc", PrivateNbdlTaskSetting_SetOption); RegisterTypeInfo( NbdlTaskSetting, "nn::boss::NbdlTaskSetting", { "__dt__Q3_2nn4boss15NbdlTaskSettingFv", "RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc", "RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", }, { "nn::boss::NetTaskSetting", }); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nbdltasksetting.h ================================================ #pragma once #include "nn_boss_nettasksetting.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include namespace cafe::nn_boss { struct NbdlTaskSetting : NetTaskSetting { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; }; CHECK_SIZE(NbdlTaskSetting, 0x1004); virt_ptr NbdlTaskSetting_Constructor(virt_ptr self); void NbdlTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result NbdlTaskSetting_Initialize(virt_ptr self, virt_ptr bossCode, int64_t a3, virt_ptr directoryName); nn::Result NbdlTaskSetting_SetFileName(virt_ptr self, virt_ptr fileName); nn::Result PrivateNbdlTaskSetting_SetCountryCode(virt_ptr self, virt_ptr countryCode); nn::Result PrivateNbdlTaskSetting_SetLanguageCode(virt_ptr self, virt_ptr languageCode); nn::Result PrivateNbdlTaskSetting_SetOption(virt_ptr self, uint8_t option); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nettasksetting.cpp ================================================ #include "nn_boss.h" #include "nn_boss_nettasksetting.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_boss { virt_ptr NetTaskSetting::VirtualTable = nullptr; virt_ptr NetTaskSetting::TypeDescriptor = nullptr; virt_ptr NetTaskSetting_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(NetTaskSetting))); if (!self) { return nullptr; } } TaskSetting_Constructor(virt_cast(self)); self->virtualTable = NetTaskSetting::VirtualTable; self->unk0x18C = 120u; return self; } void NetTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags) { TaskSetting_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } void Library::registerNetTaskSettingSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss14NetTaskSettingFv", NetTaskSetting_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss14NetTaskSettingFv", NetTaskSetting_Destructor); RegisterTypeInfo( NetTaskSetting, "nn::boss::NetTaskSetting", { "__dt__Q3_2nn4boss14NetTaskSettingFv", "RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc", "RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", }, { "nn::boss::TaskSetting", }); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nettasksetting.h ================================================ #pragma once #include "nn_boss_tasksetting.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::NetTaskSetting::AddCaCert(char const *) nn::boss::NetTaskSetting::AddHttpHeader(char const *, char const *) nn::boss::NetTaskSetting::AddHttpQueryString(char const *, nn::boss::QueryKind) nn::boss::NetTaskSetting::AddInternalCaCert(signed char) nn::boss::NetTaskSetting::ClearCaCertSetting(void) nn::boss::NetTaskSetting::ClearClientCertSetting(void) nn::boss::NetTaskSetting::ClearHttpHeaders(void) nn::boss::NetTaskSetting::ClearHttpQueryStrings(void) nn::boss::NetTaskSetting::SetClientCert(char const *, char const *) nn::boss::NetTaskSetting::SetConnectionSetting(nn::boss::HttpProtocol, char const *, unsigned short) nn::boss::NetTaskSetting::SetFirstLastModifiedTime(char const *) nn::boss::NetTaskSetting::SetHttpOption(unsigned short) nn::boss::NetTaskSetting::SetHttpTimeout(unsigned int) nn::boss::NetTaskSetting::SetInternalClientCert(signed char) nn::boss::NetTaskSetting::SetServiceToken(unsigned char const *) */ namespace cafe::nn_boss { struct NetTaskSetting : public TaskSetting { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; }; CHECK_SIZE(NetTaskSetting, 0x1004); virt_ptr NetTaskSetting_Constructor(virt_ptr self); void NetTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playloguploadtasksetting.cpp ================================================ #include "nn_boss.h" #include "nn_boss_playloguploadtasksetting.h" #include "cafe/libraries/cafe_hle_stub.h" #include "nn/boss/nn_boss_result.h" using namespace nn::boss; namespace cafe::nn_boss { virt_ptr PlayLogUploadTaskSetting::VirtualTable = nullptr; virt_ptr PlayLogUploadTaskSetting::TypeDescriptor = nullptr; virt_ptr PlayLogUploadTaskSetting_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(PlayLogUploadTaskSetting))); if (!self) { return nullptr; } } RawUlTaskSetting_Constructor(virt_cast(self)); self->virtualTable = PlayLogUploadTaskSetting::VirtualTable; return self; } void PlayLogUploadTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags) { RawUlTaskSetting_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result PlayLogUploadTaskSetting_Initialize(virt_ptr self) { PlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord(self); return ResultSuccess; } nn::Result PlayLogUploadTaskSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3) { return ResultSuccess; } void PlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord(virt_ptr self) { self->unk0x28 = uint16_t { 5 }; self->permission |= 0x1A; } void Library::registerPlayLogUploadTaskSettingSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss24PlayLogUploadTaskSettingFv", PlayLogUploadTaskSetting_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss24PlayLogUploadTaskSettingFv", PlayLogUploadTaskSetting_Destructor); RegisterFunctionExportName("Initialize__Q3_2nn4boss24PlayLogUploadTaskSettingFv", PlayLogUploadTaskSetting_Initialize); RegisterFunctionExportName("RegisterPreprocess__Q3_2nn4boss24PlayLogUploadTaskSettingFUiQ3_2nn4boss7TitleIDPCc", PlayLogUploadTaskSetting_RegisterPreprocess); RegisterFunctionExportName("SetPlayLogUploadTaskSettingToRecord__Q3_2nn4boss24PlayLogUploadTaskSettingFv", PlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord); RegisterTypeInfo( PlayLogUploadTaskSetting, "nn::boss::PlayLogUploadTaskSetting", { "__dt__Q3_2nn4boss24PlayLogUploadTaskSettingFv", "RegisterPreprocess__Q3_2nn4boss24PlayLogUploadTaskSettingFUiQ3_2nn4boss7TitleIDPCc", "RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", }, { "nn::boss::PlayLogUploadTaskSetting", }); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playloguploadtasksetting.h ================================================ #pragma once #include "nn_boss_rawultasksetting.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include namespace cafe::nn_boss { struct PlayLogUploadTaskSetting : public RawUlTaskSetting { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; }; CHECK_SIZE(PlayLogUploadTaskSetting, 0x1210); virt_ptr PlayLogUploadTaskSetting_Constructor(virt_ptr self); void PlayLogUploadTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result PlayLogUploadTaskSetting_Initialize(virt_ptr self); nn::Result PlayLogUploadTaskSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3); void PlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord(virt_ptr self); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playreportsetting.cpp ================================================ #include "nn_boss.h" #include "nn_boss_playreportsetting.h" #include "cafe/libraries/cafe_hle_stub.h" #include "nn/boss/nn_boss_result.h" using namespace nn::boss; namespace cafe::nn_boss { virt_ptr PlayReportSetting::VirtualTable = nullptr; virt_ptr PlayReportSetting::TypeDescriptor = nullptr; virt_ptr PlayReportSetting_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(PlayReportSetting))); if (!self) { return nullptr; } } RawUlTaskSetting_Constructor(virt_cast(self)); self->virtualTable = PlayReportSetting::VirtualTable; self->playReportUnk1 = 0u; self->playReportUnk2 = 0u; self->playReportUnk3 = 0u; self->playReportUnk4 = 0u; return self; } void PlayReportSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags) { RawUlTaskSetting_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } void PlayReportSetting_Initialize(virt_ptr self, virt_ptr a1, uint32_t a2) { decaf_warn_stub(); } nn::Result PlayReportSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3) { decaf_warn_stub(); return ResultInvalidParameter; } bool PlayReportSetting_Set(virt_ptr self, virt_ptr key, uint32_t value) { decaf_warn_stub(); return true; } bool PlayReportSetting_Set(virt_ptr self, uint32_t key, uint32_t value) { decaf_warn_stub(); return true; } void Library::registerPlayReportSettingSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss17PlayReportSettingFv", PlayReportSetting_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss17PlayReportSettingFv", PlayReportSetting_Destructor); RegisterFunctionExportName("RegisterPreprocess__Q3_2nn4boss17PlayReportSettingFUiQ3_2nn4boss7TitleIDPCc", PlayReportSetting_RegisterPreprocess); RegisterFunctionExportName("Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", PlayReportSetting_Initialize); RegisterFunctionExportName("Set__Q3_2nn4boss17PlayReportSettingFPCcUi", static_cast, virt_ptr, uint32_t)>(PlayReportSetting_Set)); RegisterFunctionExportName("Set__Q3_2nn4boss17PlayReportSettingFUiT1", static_cast, uint32_t, uint32_t)>(PlayReportSetting_Set)); RegisterTypeInfo( PlayReportSetting, "nn::boss::PlayReportSetting", { "__dt__Q3_2nn4boss17PlayReportSettingFv", "RegisterPreprocess__Q3_2nn4boss17PlayReportSettingFUiQ3_2nn4boss7TitleIDPCc", "RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", }, { "nn::boss::RawUlTaskSetting", }); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playreportsetting.h ================================================ #pragma once #include "nn_boss_rawultasksetting.h" #include "cafe/libraries/cafe_hle_library_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::PlayReportSetting::FindValueHead(char const *) const nn::boss::PlayReportSetting::GetValue(char const *, unsigned int *) const nn::boss::PlayReportSetting::GetValue(unsigned int, unsigned int *) const nn::boss::PlayReportSetting::RegisterPreprocess(unsigned int, nn::boss::TitleID, char const *) */ namespace cafe::nn_boss { struct PlayReportSetting : public RawUlTaskSetting { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; be2_val playReportUnk1; be2_val playReportUnk2; be2_val playReportUnk3; be2_val playReportUnk4; }; CHECK_OFFSET(PlayReportSetting, 0x1210, playReportUnk1); CHECK_OFFSET(PlayReportSetting, 0x1214, playReportUnk2); CHECK_OFFSET(PlayReportSetting, 0x1218, playReportUnk3); CHECK_OFFSET(PlayReportSetting, 0x121C, playReportUnk4); CHECK_SIZE(PlayReportSetting, 0x1220); virt_ptr PlayReportSetting_Constructor(virt_ptr self); void PlayReportSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags); void PlayReportSetting_Initialize(virt_ptr self, virt_ptr a1, uint32_t a2); nn::Result PlayReportSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3); bool PlayReportSetting_Set(virt_ptr self, virt_ptr key, uint32_t value); bool PlayReportSetting_Set(virt_ptr self, uint32_t key, uint32_t value); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_privilegedtask.cpp ================================================ #include "nn_boss.h" #include "nn_boss_privilegedtask.h" #include "nn_boss_task.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_boss { virt_ptr PrivilegedTask::VirtualTable = nullptr; virt_ptr PrivilegedTask::TypeDescriptor = nullptr; virt_ptr PrivilegedTask_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(PrivilegedTask))); if (!self) { return nullptr; } } Task_Constructor(virt_cast(self)); self->virtualTable = PrivilegedTask::VirtualTable; return self; } virt_ptr PrivilegedTask_Constructor(virt_ptr self, virt_ptr taskId, uint32_t accountId) { if (!self) { self = virt_cast(ghs::malloc(sizeof(PrivilegedTask))); if (!self) { return nullptr; } } Task_Constructor(virt_cast(self), taskId, accountId); self->virtualTable = PrivilegedTask::VirtualTable; return self; } void PrivilegedTask_Destructor(virt_ptr self, ghs::DestructorFlags flags) { if (!self) { return; } self->virtualTable = PrivilegedTask::VirtualTable; Task_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } void Library::registerPrivilegedTaskSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss14PrivilegedTaskFv", static_cast (*)(virt_ptr)>(PrivilegedTask_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss14PrivilegedTaskFPCcUi", static_cast(*)(virt_ptr, virt_ptr, uint32_t)>(PrivilegedTask_Constructor)); RegisterFunctionExportName("__dt__Q3_2nn4boss14PrivilegedTaskFv", PrivilegedTask_Destructor); RegisterTypeInfo( PrivilegedTask, "nn::boss::PrivilegedTask", { "__dt__Q3_2nn4boss14PrivilegedTaskFv", }, {}); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_privilegedtask.h ================================================ #pragma once #include "nn_boss_task.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::PrivilegedTask::GetAction(const(void)) nn::boss::PrivilegedTask::SetHttpOption((ushort)) nn::boss::PrivilegedTask::SetPermission((uchar)) nn::boss::PrivilegedTask::SetUserAgentMode((nn::boss::UserAgentMode)) */ namespace cafe::nn_boss { struct PrivilegedTask : Task { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; }; CHECK_SIZE(PrivilegedTask, 0x20); virt_ptr PrivilegedTask_Constructor(virt_ptr self); virt_ptr PrivilegedTask_Constructor(virt_ptr self, virt_ptr taskId, uint32_t accountId); void PrivilegedTask_Destructor(virt_ptr self, ghs::DestructorFlags flags); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_rawultasksetting.cpp ================================================ #include "nn_boss.h" #include "nn_boss_rawultasksetting.h" #include "cafe/libraries/cafe_hle_stub.h" #include "nn/boss/nn_boss_result.h" using namespace nn::boss; namespace cafe::nn_boss { virt_ptr RawUlTaskSetting::VirtualTable = nullptr; virt_ptr RawUlTaskSetting::TypeDescriptor = nullptr; virt_ptr RawUlTaskSetting_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(RawUlTaskSetting))); if (!self) { return nullptr; } } NetTaskSetting_Constructor(virt_cast(self)); self->virtualTable = RawUlTaskSetting::VirtualTable; self->rawUlUnk1 = 0u; self->rawUlUnk2 = 0u; self->rawUlUnk3 = 0u; std::memset(virt_addrof(self->rawUlData).get(), 0, self->rawUlData.size()); return self; } void RawUlTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags) { NetTaskSetting_Destructor(virt_cast(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result RawUlTaskSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3) { decaf_warn_stub(); return ResultInvalidParameter; } void RawUlTaskSetting_RegisterPostprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3, virt_ptr a4) { decaf_warn_stub(); } void Library::registerRawUlTaskSettingSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss16RawUlTaskSettingFv", RawUlTaskSetting_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss16RawUlTaskSettingFv", RawUlTaskSetting_Destructor); RegisterFunctionExportName("RegisterPreprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCc", RawUlTaskSetting_RegisterPreprocess); RegisterFunctionExportName("RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", RawUlTaskSetting_RegisterPostprocess); RegisterTypeInfo( RawUlTaskSetting, "nn::boss::RawUlTaskSetting", { "__dt__Q3_2nn4boss16RawUlTaskSettingFv", "RegisterPreprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCc", "RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", }, { "nn::boss::NetTaskSetting", }); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_rawultasksetting.h ================================================ #pragma once #include "nn_boss_nettasksetting.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::RawUlTaskSetting::AddLargeHttpHeader(char const *, char const *) nn::boss::RawUlTaskSetting::ClearLargeHttpHeader(void) nn::boss::RawUlTaskSetting::CopyUploadFileToBossStorage(unsigned int, nn::boss::TitleID, char const *) const nn::boss::RawUlTaskSetting::Initialize(char const *, unsigned char const *, long long) nn::boss::RawUlTaskSetting::Initialize(char const *, char const *) nn::boss::RawUlTaskSetting::Initialize(char const *, char const *, unsigned char *, long long) nn::boss::RawUlTaskSetting::RegisterPreprocess(unsigned int, nn::boss::TitleID, char const *) nn::boss::RawUlTaskSetting::SetOption(unsigned int) nn::boss::RawUlTaskSetting::SetRawUlTaskSettingToRecord(char const *) */ namespace cafe::nn_boss { struct RawUlTaskSetting : public NetTaskSetting { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; be2_val rawUlUnk1; be2_val rawUlUnk2; be2_val rawUlUnk3; be2_array rawUlData; }; CHECK_SIZE(RawUlTaskSetting, 0x1210); virt_ptr RawUlTaskSetting_Constructor(virt_ptr self); void RawUlTaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result RawUlTaskSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3); void RawUlTaskSetting_RegisterPostprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3, virt_ptr a4); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_storage.cpp ================================================ #include "nn_boss.h" #include "nn_boss_storage.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/boss/nn_boss_result.h" using namespace nn::boss; namespace cafe::nn_boss { virt_ptr Storage::VirtualTable = nullptr; virt_ptr Storage::TypeDescriptor = nullptr; virt_ptr Storage_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(Storage))); if (!self) { return nullptr; } } self->virtualTable = Storage::VirtualTable; TitleID_Constructor(virt_addrof(self->titleID), 0ull); return self; } void Storage_Destructor(virt_ptr self, ghs::DestructorFlags flags) { self->virtualTable = Storage::VirtualTable; // TODO: Call Storage_Finalize(self); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } void Library::registerStorageSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss7StorageFv", static_cast (*)(virt_ptr)>(Storage_Constructor)); RegisterFunctionExportName("__dt__Q3_2nn4boss7StorageFv", Storage_Destructor); RegisterTypeInfo( Storage, "nn::boss::Storage", { "__dt__Q3_2nn4boss7StorageFv", }, {}); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_storage.h ================================================ #pragma once #include "nn_boss_enum.h" #include "nn_boss_titleid.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/act/nn_act_types.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::Storage::Storage(const char *, nn::boss::StorageKind) nn::boss::Storage::Storage(const char *, unsigned int, nn::boss::StorageKind) nn::boss::Storage::Storage(const nn::boss::Storage &) nn::boss::Storage::Storage(unsigned char, const char *, nn::boss::StorageKind) nn::boss::Storage::ClearAllHistories() nn::boss::Storage::ClearHistory() nn::boss::Storage::Exist() const nn::boss::Storage::Finalize() nn::boss::Storage::GetDataList(nn::boss::DataName *, unsigned int, unsigned int *, unsigned int) const nn::boss::Storage::GetReadFlagFromNsDatas(const nn::boss::DataName *, unsigned int, bool *) const nn::boss::Storage::GetUnreadDataList(nn::boss::DataName *, unsigned int, unsigned int *, unsigned int) const nn::boss::Storage::Initialize(const char *, nn::boss::StorageKind) nn::boss::Storage::Initialize(const char *, unsigned int, nn::boss::StorageKind) nn::boss::Storage::Initialize(unsigned char, const char *, nn::boss::StorageKind) nn::boss::Storage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool *) nn::boss::Storage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool) */ namespace cafe::nn_boss { struct Storage { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; be2_val persistentId; be2_val storageKind; UNKNOWN(3); be2_array directoryName; UNKNOWN(5); be2_val titleID; be2_virt_ptr virtualTable; UNKNOWN(4); }; CHECK_OFFSET(Storage, 0x00, persistentId); CHECK_OFFSET(Storage, 0x04, storageKind); CHECK_OFFSET(Storage, 0x0B, directoryName); CHECK_OFFSET(Storage, 0x18, titleID); CHECK_OFFSET(Storage, 0x20, virtualTable); CHECK_SIZE(Storage, 0x28); virt_ptr Storage_Constructor(virt_ptr self); void Storage_Destructor(virt_ptr self, ghs::DestructorFlags flags); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_task.cpp ================================================ #include "nn_boss.h" #include "nn_boss_task.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "cafe/libraries/nn_act/nn_act_clientstandardservice.h" #include "nn/boss/nn_boss_result.h" using namespace nn::boss; namespace cafe::nn_boss { virt_ptr Task::VirtualTable = nullptr; virt_ptr Task::TypeDescriptor = nullptr; virt_ptr Task_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(Task))); if (!self) { return nullptr; } } self->virtualTable = Task::VirtualTable; TaskID_Constructor(virt_addrof(self->taskId)); TitleID_Constructor(virt_addrof(self->titleId)); std::memset(virt_addrof(self->taskId).get(), 0, sizeof(TaskID)); return self; } virt_ptr Task_Constructor(virt_ptr self, virt_ptr taskId) { self = Task_Constructor(self); if (self) { Task_Initialize(self, taskId); } return self; } virt_ptr Task_Constructor(virt_ptr self, virt_ptr taskId, uint32_t accountId) { self = Task_Constructor(self); if (self) { Task_Initialize(self, taskId, accountId); } return self; } virt_ptr Task_Constructor(virt_ptr self, uint8_t slot, virt_ptr taskId) { self = Task_Constructor(self); if (self) { Task_Initialize(self, slot, taskId); } return self; } void Task_Destructor(virt_ptr self, ghs::DestructorFlags flags) { if (!self) { return; } self->virtualTable = Task::VirtualTable; Task_Finalize(self); if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result Task_Initialize(virt_ptr self, virt_ptr taskId) { return Task_Initialize(self, taskId, 0u); } nn::Result Task_Initialize(virt_ptr self, virt_ptr taskId, uint32_t accountId) { if (!taskId || strnlen(taskId.get(), 8) == 8) { return ResultInvalidParameter; } self->accountId = accountId; TaskID_OperatorAssign(virt_addrof(self->taskId), taskId); return ResultSuccess; } nn::Result Task_Initialize(virt_ptr self, uint8_t slot, virt_ptr taskId) { if (!slot) { return Task_Initialize(self, taskId, 0u); } else if (auto accountId = nn_act::GetPersistentIdEx(slot)) { return Task_Initialize(self, taskId, accountId); } return ResultInvalidParameter; } void Task_Finalize(virt_ptr self) { decaf_warn_stub(); TitleID_Constructor(virt_addrof(self->titleId)); } bool Task_IsRegistered(virt_ptr self) { decaf_warn_stub(); return false; } nn::Result Task_Register(virt_ptr self, virt_ptr taskSetting) { decaf_warn_stub(); return ResultSuccess; } uint32_t Task_GetAccountID(virt_ptr self) { return self->accountId; } void Task_GetTaskID(virt_ptr self, virt_ptr id) { *id = self->taskId; } void Task_GetTitleID(virt_ptr self, virt_ptr id) { *id = self->titleId; } void Library::registerTaskSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss4TaskFv", static_cast (*)(virt_ptr)>(Task_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss4TaskFPCc", static_cast(*)(virt_ptr, virt_ptr)>(Task_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss4TaskFPCcUi", static_cast(*)(virt_ptr, virt_ptr, uint32_t)>(Task_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss4TaskFUcPCc", static_cast(*)(virt_ptr, uint8_t, virt_ptr)>(Task_Constructor)); RegisterFunctionExportName("__dt__Q3_2nn4boss4TaskFv", Task_Destructor); RegisterFunctionExportName("Initialize__Q3_2nn4boss4TaskFPCc", static_cast, virt_ptr)>(Task_Initialize)); RegisterFunctionExportName("Initialize__Q3_2nn4boss4TaskFPCcUi", static_cast, virt_ptr, uint32_t)>(Task_Initialize)); RegisterFunctionExportName("Initialize__Q3_2nn4boss4TaskFUcPCc", static_cast, uint8_t, virt_ptr)>(Task_Initialize)); RegisterFunctionExportName("Finalize__Q3_2nn4boss4TaskFv", Task_Finalize); RegisterFunctionExportName("IsRegistered__Q3_2nn4boss4TaskCFv", Task_IsRegistered); RegisterFunctionExportName("Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", Task_Register); RegisterFunctionExportName("GetAccountID__Q3_2nn4boss4TaskCFv", Task_GetAccountID); RegisterFunctionExportName("GetTaskID__Q3_2nn4boss4TaskCFv", Task_GetTaskID); RegisterFunctionExportName("GetTitleID__Q3_2nn4boss4TaskCFv", Task_GetTitleID); RegisterTypeInfo( Task, "nn::boss::Task", { "__dt__Q3_2nn4boss4TaskFv", }, {}); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_task.h ================================================ #pragma once #include "nn_boss_taskid.h" #include "nn_boss_tasksetting.h" #include "nn_boss_titleid.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include /* Unimplemented functions: nn::boss::Task::CancelSync(nn::boss::CancelMode) nn::boss::Task::CancelSync(unsigned int, nn::boss::CancelMode) nn::boss::Task::Cancel(nn::boss::CancelMode) nn::boss::Task::ClearTurnState(void) nn::boss::Task::GetContentLength(unsigned int *) const nn::boss::Task::GetExecCount(void) const nn::boss::Task::GetHttpStatusCode(unsigned int *) const nn::boss::Task::GetIntervalSec(void) const nn::boss::Task::GetLifeTimeSec(void) const nn::boss::Task::GetOptionResult(unsigned int, unsigned int *) const nn::boss::Task::GetPriority(void) const nn::boss::Task::GetProcessedLength(unsigned int *) const nn::boss::Task::GetRemainingLifeTimeSec(void) const nn::boss::Task::GetResult(unsigned int *) const nn::boss::Task::GetRunningState(unsigned int *) const nn::boss::Task::GetServiceStatus(void) const nn::boss::Task::GetState(unsigned int *) const nn::boss::Task::GetTaskID(void) const nn::boss::Task::GetTitleID(void) const nn::boss::Task::GetTurnState(unsigned int *) const nn::boss::Task::GetUrl(char *, unsigned int) const nn::boss::Task::IsFinished(void) const nn::boss::Task::Reconfigure(nn::boss::TaskSetting const &) nn::boss::Task::RegisterForImmediateRun(nn::boss::TaskSetting const &) nn::boss::Task::RestoreLifeTime(void) nn::boss::Task::Run(bool) nn::boss::Task::StartScheduling(bool) nn::boss::Task::StopScheduling(void) nn::boss::Task::Unregister(void) nn::boss::Task::UpdateIntervalSec(unsigned int) nn::boss::Task::UpdateLifeTimeSec(long long) nn::boss::Task::Wait(nn::boss::TaskWaitState) nn::boss::Task::Wait(unsigned int, nn::boss::TaskWaitState) */ namespace cafe::nn_boss { struct Task { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; be2_val accountId; UNKNOWN(4); be2_struct taskId; be2_struct titleId; be2_virt_ptr virtualTable; UNKNOWN(4); }; CHECK_OFFSET(Task, 0x00, accountId); CHECK_OFFSET(Task, 0x08, taskId); CHECK_OFFSET(Task, 0x10, titleId); CHECK_OFFSET(Task, 0x18, virtualTable); CHECK_SIZE(Task, 0x20); virt_ptr Task_Constructor(virt_ptr self); virt_ptr Task_Constructor(virt_ptr self, virt_ptr taskId); virt_ptr Task_Constructor(virt_ptr self, virt_ptr taskId, uint32_t accountId); virt_ptr Task_Constructor(virt_ptr self, uint8_t slot, virt_ptr taskId); void Task_Destructor(virt_ptr self, ghs::DestructorFlags flags); nn::Result Task_Initialize(virt_ptr self, virt_ptr taskId); nn::Result Task_Initialize(virt_ptr self, virt_ptr taskId, uint32_t accountId); nn::Result Task_Initialize(virt_ptr self, uint8_t slot, virt_ptr taskId); void Task_Finalize(virt_ptr self); bool Task_IsRegistered(virt_ptr self); nn::Result Task_Register(virt_ptr self, virt_ptr taskSetting); uint32_t Task_GetAccountID(virt_ptr self); void Task_GetTaskID(virt_ptr self, virt_ptr id); void Task_GetTitleID(virt_ptr self, virt_ptr id); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_taskid.cpp ================================================ #include "nn_boss.h" #include "nn_boss_taskid.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include namespace cafe::nn_boss { virt_ptr TaskID_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(TaskID))); if (!self) { return nullptr; } } self->value[0] = char { 0 }; return self; } virt_ptr TaskID_Constructor(virt_ptr self, virt_ptr id) { if (!self) { self = virt_cast(ghs::malloc(sizeof(TaskID))); if (!self) { return nullptr; } } string_copy(virt_addrof(self->value).get(), self->value.size(), id.get(), 8); self->value[7] = char { 0 }; return self; } virt_ptr TaskID_Constructor(virt_ptr self, virt_ptr other) { if (!self) { self = virt_cast(ghs::malloc(sizeof(TaskID))); if (!self) { return nullptr; } } std::memcpy(virt_addrof(self->value).get(), virt_addrof(other->value).get(), 8); return self; } virt_ptr TaskID_OperatorAssign(virt_ptr self, virt_ptr id) { string_copy(virt_addrof(self->value).get(), self->value.size(), id.get(), 8); self->value[7] = char { 0 }; return self; } bool TaskID_OperatorEqual(virt_ptr self, virt_ptr id) { return std::strncmp(virt_addrof(self->value).get(), id.get(), 8) == 0; } bool TaskID_OperatorEqual(virt_ptr self, virt_ptr other) { return std::strncmp(virt_addrof(self->value).get(), virt_addrof(other->value).get(), 8) == 0; } bool TaskID_OperatorNotEqual(virt_ptr self, virt_ptr id) { return !TaskID_OperatorEqual(self, id); } bool TaskID_OperatorNotEqual(virt_ptr self, virt_ptr other) { return !TaskID_OperatorEqual(self, other); } virt_ptr TaskID_OperatorCastConstCharPtr(virt_ptr self) { return virt_addrof(self->value); } void Library::registerTaskIdSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss6TaskIDFv", static_cast (*)(virt_ptr)>(TaskID_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss6TaskIDFPCc", static_cast(*)(virt_ptr, virt_ptr)>(TaskID_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss6TaskIDFRCQ3_2nn4boss6TaskID", static_cast(*)(virt_ptr, virt_ptr)>(TaskID_Constructor)); RegisterFunctionExportName("__as__Q3_2nn4boss6TaskIDFPCc", static_cast (*)(virt_ptr, virt_ptr)>(TaskID_OperatorAssign)); RegisterFunctionExportName("__eq__Q3_2nn4boss6TaskIDCFPCc", static_cast, virt_ptr)>(TaskID_OperatorEqual)); RegisterFunctionExportName("__eq__Q3_2nn4boss6TaskIDCFRCQ3_2nn4boss6TaskID", static_cast, virt_ptr)>(TaskID_OperatorEqual)); RegisterFunctionExportName("__ne__Q3_2nn4boss6TaskIDCFPCc", static_cast, virt_ptr)>(TaskID_OperatorNotEqual)); RegisterFunctionExportName("__ne__Q3_2nn4boss6TaskIDCFRCQ3_2nn4boss6TaskID", static_cast, virt_ptr)>(TaskID_OperatorNotEqual)); RegisterFunctionExportName("__opPCc__Q3_2nn4boss6TaskIDCFv", TaskID_OperatorCastConstCharPtr); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_taskid.h ================================================ #pragma once #include namespace cafe::nn_boss { struct TaskID { be2_array value; }; CHECK_OFFSET(TaskID, 0, value); CHECK_SIZE(TaskID, 8); virt_ptr TaskID_Constructor(virt_ptr self); virt_ptr TaskID_Constructor(virt_ptr self, virt_ptr id); virt_ptr TaskID_Constructor(virt_ptr self, virt_ptr other); virt_ptr TaskID_OperatorAssign(virt_ptr self, virt_ptr id); bool TaskID_OperatorEqual(virt_ptr self, virt_ptr id); bool TaskID_OperatorEqual(virt_ptr self, virt_ptr other); bool TaskID_OperatorNotEqual(virt_ptr self, virt_ptr id); bool TaskID_OperatorNotEqual(virt_ptr self, virt_ptr other); virt_ptr TaskID_OperatorCastConstCharPtr(virt_ptr self); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_tasksetting.cpp ================================================ #include "nn_boss.h" #include "nn_boss_tasksetting.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/boss/nn_boss_result.h" namespace cafe::nn_boss { using namespace nn::boss; virt_ptr TaskSetting::VirtualTable = nullptr; virt_ptr TaskSetting::TypeDescriptor = nullptr; virt_ptr TaskSetting_Constructor(virt_ptr self) { if (!self) { self = virt_cast(ghs::malloc(sizeof(TaskSetting))); if (!self) { return nullptr; } } self->virtualTable = TaskSetting::VirtualTable; TaskSetting_InitializeSetting(self); return self; } void TaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags) { if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } void TaskSetting_InitializeSetting(virt_ptr self) { std::memset(self.get(), 0, 0x1000); self->unk0x00 = 0u; self->unk0x08 = 0u; self->unk0x0C = 0u; self->priority = uint8_t { 125 }; self->intervalSec = 28800u; self->lifeTimeSec = 7776000u; } void TaskSetting_SetRunPermissionInParentalControlRestriction(virt_ptr self, bool value) { if (value) { self->permission |= 2u; } else { self->permission &= ~2u; } } nn::Result TaskSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3) { return ResultSuccess; } void TaskSetting_RegisterPostprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3, virt_ptr a4) { } nn::Result PrivateTaskSetting_SetIntervalSecForShortSpanRetry(virt_ptr self, uint16_t sec) { self->intervalSecForShortSpanRetry = sec; return ResultSuccess; } nn::Result PrivateTaskSetting_SetIntervalSec(virt_ptr self, uint32_t sec) { self->intervalSec = sec; return ResultSuccess; } nn::Result PrivateTaskSetting_SetLifeTimeSec(virt_ptr self, uint64_t lifeTimeSec) { self->lifeTimeSec = lifeTimeSec; return ResultSuccess; } nn::Result PrivateTaskSetting_SetPermission(virt_ptr self, uint8_t permission) { self->permission = permission; return ResultSuccess; } nn::Result PrivateTaskSetting_SetPriority(virt_ptr self, TaskPriority priority) { self->priority = priority; return ResultSuccess; } void Library::registerTaskSettingSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss11TaskSettingFv", TaskSetting_Constructor); RegisterFunctionExportName("__dt__Q3_2nn4boss11TaskSettingFv", TaskSetting_Destructor); RegisterFunctionExportName("InitializeSetting__Q3_2nn4boss11TaskSettingFv", TaskSetting_InitializeSetting); RegisterFunctionExportName("RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc", TaskSetting_RegisterPreprocess); RegisterFunctionExportName("RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", TaskSetting_RegisterPostprocess); RegisterFunctionExportName("SetRunPermissionInParentalControlRestriction__Q3_2nn4boss11TaskSettingFb", TaskSetting_SetRunPermissionInParentalControlRestriction); RegisterFunctionExportName("SetIntervalSecForShortSpanRetry__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUs", PrivateTaskSetting_SetIntervalSecForShortSpanRetry); RegisterFunctionExportName("SetIntervalSec__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUi", PrivateTaskSetting_SetIntervalSec); RegisterFunctionExportName("SetLifeTimeSec__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUL", PrivateTaskSetting_SetLifeTimeSec); RegisterFunctionExportName("SetPermission__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUc", PrivateTaskSetting_SetPermission); RegisterFunctionExportName("SetPriority__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingQ3_2nn4boss12TaskPriority", PrivateTaskSetting_SetPriority); RegisterTypeInfo( TaskSetting, "nn::boss::TaskSetting", { "__dt__Q3_2nn4boss11TaskSettingFv", "RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc", "RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result", }, {}); } } // namespace namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_tasksetting.h ================================================ #pragma once #include "nn_boss_titleid.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include namespace cafe::nn_boss { #pragma pack(push, 1) using TaskPriority = uint8_t; struct TaskSetting { static virt_ptr VirtualTable; static virt_ptr TypeDescriptor; be2_val unk0x00; UNKNOWN(4); be2_val unk0x08; be2_val unk0x0C; UNKNOWN(0x28 - 0x10); be2_val unk0x28; be2_val priority; UNKNOWN(1); be2_val permission; UNKNOWN(1); be2_val intervalSecForShortSpanRetry; be2_val intervalSec; UNKNOWN(4); be2_val lifeTimeSec; UNKNOWN(0x18C - 0x40); be2_val unk0x18C; UNKNOWN(0x1000 - 0x190); be2_virt_ptr virtualTable; }; CHECK_OFFSET(TaskSetting, 0x0, unk0x00); CHECK_OFFSET(TaskSetting, 0x8, unk0x08); CHECK_OFFSET(TaskSetting, 0xC, unk0x0C); CHECK_OFFSET(TaskSetting, 0x28, unk0x28); CHECK_OFFSET(TaskSetting, 0x2A, priority); CHECK_OFFSET(TaskSetting, 0x2C, permission); CHECK_OFFSET(TaskSetting, 0x2E, intervalSecForShortSpanRetry); CHECK_OFFSET(TaskSetting, 0x30, intervalSec); CHECK_OFFSET(TaskSetting, 0x38, lifeTimeSec); CHECK_OFFSET(TaskSetting, 0x18C, unk0x18C); CHECK_OFFSET(TaskSetting, 0x1000, virtualTable); CHECK_SIZE(TaskSetting, 0x1004); #pragma pack(pop) virt_ptr TaskSetting_Constructor(virt_ptr self); void TaskSetting_Destructor(virt_ptr self, ghs::DestructorFlags flags); void TaskSetting_InitializeSetting(virt_ptr self); void TaskSetting_SetRunPermissionInParentalControlRestriction(virt_ptr self, bool value); nn::Result TaskSetting_RegisterPreprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3); void TaskSetting_RegisterPostprocess(virt_ptr self, uint32_t a1, virt_ptr a2, virt_ptr a3, virt_ptr a4); nn::Result PrivateTaskSetting_SetIntervalSecForShortSpanRetry(virt_ptr self, uint16_t sec); nn::Result PrivateTaskSetting_SetIntervalSec(virt_ptr self, uint32_t sec); nn::Result PrivateTaskSetting_SetLifeTimeSec(virt_ptr self, uint64_t lifeTimeSec); nn::Result PrivateTaskSetting_SetPermission(virt_ptr self, uint8_t permission); nn::Result PrivateTaskSetting_SetPriority(virt_ptr self, TaskPriority priority); } // namespace namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_title.cpp ================================================ #include "nn_boss.h" #include "nn_boss_title.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "cafe/libraries/nn_act/nn_act_clientstandardservice.h" #include "nn/boss/nn_boss_result.h" using namespace nn::boss; namespace cafe::nn_boss { virt_ptr Title::VirtualTable = nullptr; virt_ptr Title::TypeDescriptor = nullptr; virt_ptr Title_Constructor(virt_ptr<Title> self) { if (!self) { self = virt_cast<Title *>(ghs::malloc(sizeof(Title))); if (!self) { return nullptr; } } self->virtualTable = Title::VirtualTable; self->accountID = 0u; TitleID_Constructor(virt_addrof(self->titleID), 0ull); return self; } virt_ptr<Title> Title_Constructor(virt_ptr<Title> self, uint32_t accountId, virt_ptr<TitleID> titleId) { if (!self) { self = virt_cast<Title *>(ghs::malloc(sizeof(Title))); if (!self) { return nullptr; } } self->virtualTable = Title::VirtualTable; self->accountID = accountId; TitleID_Constructor(virt_addrof(self->titleID), titleId); return self; } void Title_Destructor(virt_ptr<Title> self, ghs::DestructorFlags flags) { if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result Title_ChangeAccount(virt_ptr<Title> self, uint8_t slot) { if (!slot) { self->accountID = slot; } else if (auto accountId = nn_act::GetPersistentIdEx(slot)) { self->accountID = accountId; } else { return ResultInvalidParameter; } return ResultSuccess; } void Library::registerTitleSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss5TitleFv", static_cast<virt_ptr<Title> (*)(virt_ptr<Title>)>(Title_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss5TitleFUiQ3_2nn4boss7TitleID", static_cast<virt_ptr<Title>(*)(virt_ptr<Title>, uint32_t, virt_ptr<TitleID>)>(Title_Constructor)); RegisterFunctionExportName("__dt__Q3_2nn4boss5TitleFv", Title_Destructor); RegisterFunctionExportName("ChangeAccount__Q3_2nn4boss5TitleFUc", Title_ChangeAccount); RegisterTypeInfo( Title, "nn::boss::Title", { "__dt__Q3_2nn4boss5TitleFv", }, {}); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_title.h ================================================ #pragma once #include "nn_boss_titleid.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_boss { struct Title { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; be2_val<uint32_t> accountID; UNKNOWN(4); be2_val<TitleID> titleID; be2_virt_ptr<ghs::VirtualTable> virtualTable; UNKNOWN(4); }; CHECK_OFFSET(Title, 0x00, accountID); CHECK_OFFSET(Title, 0x08, titleID); CHECK_OFFSET(Title, 0x10, virtualTable); CHECK_SIZE(Title, 0x18); virt_ptr<Title> Title_Constructor(virt_ptr<Title> self); virt_ptr<Title> Title_Constructor(virt_ptr<Title> self, uint32_t accountId, virt_ptr<TitleID> titleId); void Title_Destructor(virt_ptr<Title> self, ghs::DestructorFlags flags); nn::Result Title_ChangeAccount(virt_ptr<Title> self, uint8_t slot); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_titleid.cpp ================================================ #include "nn_boss.h" #include "nn_boss_titleid.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_boss { virt_ptr<TitleID> TitleID_Constructor(virt_ptr<TitleID> self) { if (!self) { self = virt_cast<TitleID *>(ghs::malloc(sizeof(TitleID))); if (!self) { return nullptr; } } self->value = 0ull; return self; } virt_ptr<TitleID> TitleID_Constructor(virt_ptr<TitleID> self, virt_ptr<TitleID> other) { if (!self) { self = virt_cast<TitleID *>(ghs::malloc(sizeof(TitleID))); if (!self) { return nullptr; } } self->value = other->value; return self; } virt_ptr<TitleID> TitleID_Constructor(virt_ptr<TitleID> self, uint64_t id) { if (!self) { self = virt_cast<TitleID *>(ghs::malloc(sizeof(TitleID))); if (!self) { return nullptr; } } self->value = id; return self; } bool TitleID_IsValid(virt_ptr<TitleID> self) { return self->value != 0ull; } uint64_t TitleID_GetValue(virt_ptr<TitleID> self) { return self->value; } uint32_t TitleID_GetTitleID(virt_ptr<TitleID> self) { return static_cast<uint32_t>(self->value & 0xFFFFFFFF); } uint32_t TitleID_GetTitleCode(virt_ptr<TitleID> self) { return TitleID_GetTitleID(self); } uint32_t TitleID_GetUniqueId(virt_ptr<TitleID> self) { return (TitleID_GetTitleID(self) >> 8) & 0xFFFF; } bool TitleID_OperatorEqual(virt_ptr<TitleID> self, virt_ptr<TitleID> other) { if (self->value & 0x2000000000ull) { return (self->value & 0xFFFFFF00FFFFFFFFull) == (other->value & 0xFFFFFF00FFFFFFFFull); } return self->value == other->value; } bool TitleID_OperatorNotEqual(virt_ptr<TitleID> self, virt_ptr<TitleID> other) { return !TitleID_OperatorEqual(self, other); } void Library::registerTitleIdSymbols() { RegisterFunctionExportName("__ct__Q3_2nn4boss7TitleIDFv", static_cast<virt_ptr<TitleID> (*)(virt_ptr<TitleID>)>(TitleID_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", static_cast<virt_ptr<TitleID>(*)(virt_ptr<TitleID>, virt_ptr<TitleID>)>(TitleID_Constructor)); RegisterFunctionExportName("__ct__Q3_2nn4boss7TitleIDFUL", static_cast<virt_ptr<TitleID>(*)(virt_ptr<TitleID>, uint64_t)>(TitleID_Constructor)); RegisterFunctionExportName("IsValid__Q3_2nn4boss7TitleIDCFv", TitleID_IsValid); RegisterFunctionExportName("GetValue__Q3_2nn4boss7TitleIDCFv", TitleID_GetValue); RegisterFunctionExportName("GetTitleId__Q3_2nn4boss7TitleIDCFv", TitleID_GetTitleID); RegisterFunctionExportName("GetTitleCode__Q3_2nn4boss7TitleIDCFv", TitleID_GetTitleCode); RegisterFunctionExportName("GetUniqueId__Q3_2nn4boss7TitleIDCFv", TitleID_GetUniqueId); RegisterFunctionExportName("__eq__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", TitleID_OperatorEqual); RegisterFunctionExportName("__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", TitleID_OperatorNotEqual); } } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_titleid.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::nn_boss { struct TitleID { be2_val<uint64_t> value; }; CHECK_OFFSET(TitleID, 0, value); CHECK_SIZE(TitleID, 8); virt_ptr<TitleID> TitleID_Constructor(virt_ptr<TitleID> self); virt_ptr<TitleID> TitleID_Constructor(virt_ptr<TitleID> self, virt_ptr<TitleID> other); virt_ptr<TitleID> TitleID_Constructor(virt_ptr<TitleID> self, uint64_t id); bool TitleID_IsValid(virt_ptr<TitleID> self); uint64_t TitleID_GetValue(virt_ptr<TitleID> self); uint32_t TitleID_GetTitleID(virt_ptr<TitleID> self); uint32_t TitleID_GetTitleCode(virt_ptr<TitleID> self); uint32_t TitleID_GetUniqueId(virt_ptr<TitleID> self); bool TitleID_OperatorEqual(virt_ptr<TitleID> self, virt_ptr<TitleID> other); bool TitleID_OperatorNotEqual(virt_ptr<TitleID> self, virt_ptr<TitleID> other); } // namespace cafe::nn_boss ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ccr/nn_ccr.cpp ================================================ #include "nn_ccr.h" namespace cafe::nn_ccr { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nn_ccr ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ccr/nn_ccr.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_ccr { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_ccr, "nn_ccr.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nn_ccr ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt.cpp ================================================ #include "nn_cmpt.h" namespace cafe::nn_cmpt { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerLibSymbols(); } } // namespace cafe::nn_cmpt ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_cmpt { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_cmpt, "nn_cmpt.rpl") { } protected: virtual void registerSymbols() override; private: void registerLibSymbols(); }; } // namespace cafe::nn_cmpt ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt_enum.h ================================================ #ifndef CAFE_NN_CMPT_ENUM_H #define CAFE_NN_CMPT_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_cmpt) ENUM_BEG(CMPTError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(UserConfigError, -512) ENUM_END(CMPTError) FLAGS_BEG(CMPTPcConfFlags, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(RstInternetCh, 1 << 0) FLAGS_VALUE(RstNwAccess, 1 << 1) FLAGS_VALUE(RstPtOrder, 1 << 2) FLAGS_END(CMPTPcConfFlags) ENUM_NAMESPACE_EXIT(nn_cmpt) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef CAFE_NN_CMPT_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt_lib.cpp ================================================ #include "nn_cmpt.h" #include "nn_cmpt_lib.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_userconfig.h" using namespace cafe::coreinit; namespace cafe::nn_cmpt { CMPTError CMPTGetDataSize(virt_ptr<uint32_t> outDataSize) { *outDataSize = 12u * 1024u * 1024u; return CMPTError::OK; } CMPTError CMPTAcctGetPcConf(virt_ptr<CMPTPcConf> outConf) { StackObject<uint32_t> rating { 0 }; StackObject<uint32_t> organisation { 0 }; StackObject<uint8_t> rst_internet_ch { 0 }; StackObject<uint8_t> rst_nw_access { 0 }; StackObject<uint8_t> rst_pt_order { 0 }; StackArray<UCSysConfig, 5> config { { { "wii_acct.pc.rating", 0u, UCDataType::UnsignedInt, UCError::OK, 4u, rating }, { "wii_acct.pc.organization", 0u, UCDataType::UnsignedInt, UCError::OK, 4u, organisation }, { "wii_acct.pc.rst_internet_ch", 0u, UCDataType::UnsignedByte, UCError::OK, 1u, rst_internet_ch }, { "wii_acct.pc.rst_nw_access", 0u, UCDataType::UnsignedByte, UCError::OK, 1u, rst_nw_access }, { "wii_acct.pc.rst_pt_order", 0u, UCDataType::UnsignedByte, UCError::OK, 1u, rst_pt_order }, } }; auto error = UCOpen(); if (error < 0) { return CMPTError::UserConfigError; } auto handle = static_cast<IOSHandle>(error); error = UCReadSysConfig(handle, 5, config); UCClose(handle); if (error != UCError::OK) { return CMPTError::UserConfigError; } outConf->rating = *rating; outConf->organisation = *organisation; outConf->flags = CMPTPcConfFlags::None; if (*rst_internet_ch) { outConf->flags = CMPTPcConfFlags::RstInternetCh; } if (*rst_nw_access) { outConf->flags = CMPTPcConfFlags::RstNwAccess; } if (*rst_pt_order) { outConf->flags = CMPTPcConfFlags::RstPtOrder; } return CMPTError::OK; } void Library::registerLibSymbols() { RegisterFunctionExport(CMPTAcctGetPcConf); RegisterFunctionExport(CMPTGetDataSize); } } // namespace cafe::nn_cmpt ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt_lib.h ================================================ #pragma once #include "nn_cmpt_enum.h" #include <libcpu/be2_struct.h> namespace cafe::nn_cmpt { struct CMPTPcConf { be2_val<uint32_t> rating; be2_val<uint32_t> organisation; be2_val<CMPTPcConfFlags> flags; }; CHECK_OFFSET(CMPTPcConf, 0x00, rating); CHECK_OFFSET(CMPTPcConf, 0x04, organisation); CHECK_OFFSET(CMPTPcConf, 0x08, flags); CHECK_SIZE(CMPTPcConf, 0x0C); CMPTError CMPTGetDataSize(virt_ptr<uint32_t> outDataSize); CMPTError CMPTAcctGetPcConf(virt_ptr<CMPTPcConf> outConf); } // namespace cafe::nn_cmpt ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_dlp/nn_dlp.cpp ================================================ #include "nn_dlp.h" namespace cafe::nn_dlp { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nn_dlp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_dlp/nn_dlp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_dlp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_dlp, "nn_dlp.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nn_dlp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec.cpp ================================================ #include "nn_ec.h" namespace cafe::nn_ec { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerCatalogSymbols(); registerItemListSymbols(); registerLibSymbols(); registerMemoryManagerSymbols(); registerMoneySymbols(); registerQuerySymbols(); registerRootObjectSymbols(); registerShoppingCatalogSymbols(); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_ec { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_ec, "nn_ec.rpl") { } protected: virtual void registerSymbols() override; void registerCatalogSymbols(); void registerItemListSymbols(); void registerLibSymbols(); void registerMemoryManagerSymbols(); void registerMoneySymbols(); void registerQuerySymbols(); void registerRootObjectSymbols(); void registerShoppingCatalogSymbols(); private: }; } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_catalog.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_catalog.h" #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" namespace cafe::nn_ec { virt_ptr<ghs::VirtualTable> Catalog::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> Catalog::TypeDescriptor = nullptr; virt_ptr<Catalog> Catalog_Constructor(virt_ptr<Catalog> self) { if (!self) { self = virt_cast<Catalog *>(RootObject_New(sizeof(Catalog))); if (!self) { return nullptr; } } ItemList_Constructor(virt_cast<ItemList *>(self)); self->impl = nullptr; self->virtualTable = Catalog::VirtualTable; return self; } void Catalog_Destructor(virt_ptr<Catalog> self, ghs::DestructorFlags flags) { self->virtualTable = Catalog::VirtualTable; ItemList_Destructor(virt_cast<ItemList *>(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { RootObject_Free(self); } } void Library::registerCatalogSymbols() { RegisterFunctionExportName("__ct__Q3_2nn2ec7CatalogFv", Catalog_Constructor); RegisterFunctionExportName("__dt__Q3_2nn2ec7CatalogFv", Catalog_Destructor); RegisterTypeInfo( Catalog, "nn::ec::Catalog", { "__dt__Q3_2nn2ec7CatalogFv", }, { "nn::ec::ItemList", }); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_catalog.h ================================================ #pragma once #include "cafe/libraries/nn_ec/nn_ec_itemlist.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct Catalog : ItemList { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; }; virt_ptr<Catalog> Catalog_Constructor(virt_ptr<Catalog> self); void Catalog_Destructor(virt_ptr<Catalog> self, ghs::DestructorFlags flags); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_itemlist.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_itemlist.h" #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" namespace cafe::nn_ec { template<> virt_ptr<ghs::TypeDescriptor> NonCopyable<ItemList>::TypeDescriptor = nullptr; virt_ptr<ghs::VirtualTable> ItemList::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> ItemList::TypeDescriptor = nullptr; virt_ptr<ItemList> ItemList_Constructor(virt_ptr<ItemList> self) { if (!self) { self = virt_cast<ItemList *>(RootObject_New(sizeof(ItemList))); if (!self) { return nullptr; } } self->impl = nullptr; self->virtualTable = ItemList::VirtualTable; return self; } void ItemList_Destructor(virt_ptr<ItemList> self, ghs::DestructorFlags flags) { self->virtualTable = ItemList::VirtualTable; if (flags & ghs::DestructorFlags::FreeMemory) { RootObject_Free(self); } } void Library::registerItemListSymbols() { RegisterFunctionExportName("__ct__Q3_2nn2ec8ItemListFv", ItemList_Constructor); RegisterFunctionExportName("__dt__Q3_2nn2ec8ItemListFv", ItemList_Destructor); RegisterTypeInfo( NonCopyable<ItemList>, "nn::ec::NonCopyable<nn::ec::ItemList>", { }, { }); RegisterTypeInfo( ItemList, "nn::ec::ItemList", { "__dt__Q3_2nn2ec8ItemListFv", }, { "nn::ec::RootObject", "nn::ec::NonCopyable<nn::ec::ItemList>", }); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_itemlist.h ================================================ #pragma once #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" #include "cafe/libraries/nn_ec/nn_ec_noncopyable.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct ItemList : RootObject, NonCopyable<ItemList> { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; be2_virt_ptr<void> impl; be2_virt_ptr<ghs::VirtualTable> virtualTable; }; virt_ptr<ItemList> ItemList_Constructor(virt_ptr<ItemList> self); void ItemList_Destructor(virt_ptr<ItemList> self, ghs::DestructorFlags flags); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_lib.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_lib.h" #include "cafe/libraries/nn_ec/nn_ec_memorymanager.h" #include "cafe/libraries/nn_ec/nn_ec_result.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nn_ec { nn::Result Initialize(uint32_t unk0) { decaf_warn_stub(); return nn::ResultSuccess; } nn::Result Finalize() { decaf_warn_stub(); return nn::ResultSuccess; } nn::Result SetAllocator(MemoryManager::AllocFn allocFn, MemoryManager::FreeFn freeFn) { auto test = nn::Result(0xC1603C80); if (!allocFn || !freeFn) { return ResultInvalidArgument; } internal::MemoryManager_SetAllocator(allocFn, freeFn); return nn::ResultSuccess; } void Library::registerLibSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn2ecFUi", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn2ecFv", Finalize); RegisterFunctionExportName("SetAllocator__Q2_2nn2ecFPFUii_PvPFPv_v", SetAllocator); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_lib.h ================================================ #pragma once #include "nn_ec_memorymanager.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { nn::Result Initialize(uint32_t unk0); nn::Result Finalize(); nn::Result SetAllocator(MemoryManager::AllocFn allocate, MemoryManager::FreeFn free); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_memorymanager.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_memorymanager.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include <libcpu/be2_struct.h> #include <mutex> namespace cafe::nn_ec { struct StaticMemoryManagerData { StaticMemoryManagerData() { MemoryManager_Constructor(virt_addrof(memoryManager)); } be2_struct<MemoryManager> memoryManager; }; static virt_ptr<StaticMemoryManagerData> sMemoryManagerData = nullptr; static MemoryManager::AllocFn sDefaultAllocFn; static MemoryManager::FreeFn sDefaultFreeFn; virt_ptr<MemoryManager> MemoryManager_GetSingleton() { return virt_addrof(sMemoryManagerData->memoryManager); } virt_ptr<MemoryManager> MemoryManager_Constructor(virt_ptr<MemoryManager> self) { self->allocFn = sDefaultAllocFn; self->freeFn = sDefaultFreeFn; return self; } virt_ptr<void> MemoryManager_Allocate(virt_ptr<MemoryManager> self, uint32_t size, uint32_t align) { std::unique_lock<nn::os::CriticalSection> lock { self->mutex }; return cafe::invoke(cpu::this_core::state(), self->allocFn, size, align); } void MemoryManager_Free(virt_ptr<MemoryManager> self, virt_ptr<void> ptr) { std::unique_lock<nn::os::CriticalSection> lock { self->mutex }; return cafe::invoke(cpu::this_core::state(), self->freeFn, ptr); } namespace internal { void MemoryManager_SetAllocator(MemoryManager::AllocFn allocFn, MemoryManager::FreeFn freeFn) { auto memoryManager = MemoryManager_GetSingleton(); std::unique_lock<nn::os::CriticalSection> lock { memoryManager->mutex }; memoryManager->allocFn = allocFn; memoryManager->freeFn = freeFn; } static virt_ptr<void> defaultAllocFn(uint32_t size, uint32_t align) { return coreinit::MEMAllocFromDefaultHeapEx(size, align); } static void defaultFreeFn(virt_ptr<void> ptr) { coreinit::MEMFreeToDefaultHeap(ptr); } } // namespace internal void Library::registerMemoryManagerSymbols() { RegisterDataInternal(sMemoryManagerData); RegisterFunctionInternal(internal::defaultAllocFn, sDefaultAllocFn); RegisterFunctionInternal(internal::defaultFreeFn, sDefaultFreeFn); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_memorymanager.h ================================================ #pragma once #include "cafe/nn/cafe_nn_os_criticalsection.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct MemoryManager { using AllocFn = virt_func_ptr<virt_ptr<void>(uint32_t size, uint32_t align)>; using FreeFn = virt_func_ptr<void(virt_ptr<void> ptr)>; be2_struct<nn::os::CriticalSection> mutex; be2_val<AllocFn> allocFn; be2_val<FreeFn> freeFn; UNKNOWN(0x48 - 0x34); }; CHECK_OFFSET(MemoryManager, 0x00, mutex); CHECK_OFFSET(MemoryManager, 0x2C, allocFn); CHECK_OFFSET(MemoryManager, 0x30, freeFn); CHECK_SIZE(MemoryManager, 0x48); virt_ptr<MemoryManager> MemoryManager_GetSingleton(); virt_ptr<MemoryManager> MemoryManager_Constructor(virt_ptr<MemoryManager> self); virt_ptr<void> MemoryManager_Allocate(virt_ptr<MemoryManager> self, uint32_t size, uint32_t align); void MemoryManager_Free(virt_ptr<MemoryManager> self, virt_ptr<void> ptr); namespace internal { void MemoryManager_SetAllocator(MemoryManager::AllocFn allocFn, MemoryManager::FreeFn freeFn); } // internal } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_money.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_money.h" #include "common/strutils.h" namespace cafe::nn_ec { virt_ptr<Money> Money_Constructor(virt_ptr<Money> self, virt_ptr<const char> value, virt_ptr<const char> currency, virt_ptr<const char> amount) { if (!self) { self = virt_cast<Money *>(RootObject_New(sizeof(Money))); if (!self) { return nullptr; } } if (value) { string_copy(virt_addrof(self->value).get(), value.get(), self->value.size() - 1); } else { self->value[0] = '\0'; } if (currency && strnlen(currency.get(), 4) == 3) { memcpy(virt_addrof(self->currency).get(), currency.get(), 4); } else { self->currency[0] = '\0'; } if (amount) { string_copy(virt_addrof(self->amount).get(), amount.get(), self->amount.size() - 1); } else { // TODO: Does something with currency and value to generate an amount string self->amount = "0"; } return self; } void Library::registerMoneySymbols() { RegisterFunctionExportName("__ct__Q3_2nn2ec5MoneyFPCcN21", Money_Constructor); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_money.h ================================================ #pragma once #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" #include "cafe/nn/cafe_nn_os_criticalsection.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct Money : RootObject { be2_array<char, 44> amount; be2_array<char, 16> value; be2_array<char, 4> currency; }; CHECK_OFFSET(Money, 0x00, amount); CHECK_OFFSET(Money, 0x2C, value); CHECK_OFFSET(Money, 0x3C, currency); CHECK_SIZE(Money, 0x40); virt_ptr<Money> Money_Constructor(virt_ptr<Money> self, virt_ptr<const char> value, virt_ptr<const char> currency, virt_ptr<const char> amount); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_noncopyable.h ================================================ #pragma once #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" #include "cafe/nn/cafe_nn_os_criticalsection.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { template<typename T> struct NonCopyable { static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; NonCopyable(const NonCopyable &) = delete; NonCopyable &operator =(const NonCopyable &) = delete; }; } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_query.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_query.h" #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nn_ec { virt_ptr<Query> Query_Constructor(virt_ptr<Query> self) { if (!self) { self = virt_cast<Query *>(RootObject_New(sizeof(Query))); if (!self) { return nullptr; } } self->impl = nullptr; return self; } void Query_Destructor(virt_ptr<Query> self, ghs::DestructorFlags flags) { Query_Clear(self); if (flags & ghs::DestructorFlags::FreeMemory) { RootObject_Free(self); } } void Query_Clear(virt_ptr<Query> self) { if (self->impl) { decaf_warn_stub(); } self->impl = nullptr; } void Library::registerQuerySymbols() { RegisterFunctionExportName("__ct__Q3_2nn2ec5QueryFv", Query_Constructor); RegisterFunctionExportName("__dt__Q3_2nn2ec5QueryFv", Query_Destructor); RegisterFunctionExportName("Clear__Q3_2nn2ec5QueryFv", Query_Clear); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_query.h ================================================ #pragma once #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" #include "cafe/libraries/nn_ec/nn_ec_noncopyable.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct Query : RootObject { be2_virt_ptr<void> impl; }; virt_ptr<Query> Query_Constructor(virt_ptr<Query> self); void Query_Destructor(virt_ptr<Query> self, ghs::DestructorFlags flags); void Query_Clear(virt_ptr<Query> self); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace cafe::nn_ec { static constexpr nn::Result ResultInvalidArgument { nn::Result::MODULE_NN_EC, nn::Result::LEVEL_USAGE, 0x3C80 }; } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_rootobject.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_memorymanager.h" #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" namespace cafe::nn_ec { virt_ptr<ghs::TypeDescriptor> RootObject::TypeDescriptor = nullptr; virt_ptr<void> RootObject_New(uint32_t size) { return MemoryManager_Allocate(MemoryManager_GetSingleton(), size, 8); } virt_ptr<void> RootObject_PlacementNew(uint32_t size, virt_ptr<void> ptr) { return ptr; } void RootObject_Free(virt_ptr<void> ptr) { MemoryManager_Free(MemoryManager_GetSingleton(), ptr); } void Library::registerRootObjectSymbols() { RegisterFunctionExportName("__nw__Q3_2nn2ec10RootObjectSFUi", RootObject_New); RegisterFunctionExportName("__nwa__Q3_2nn2ec10RootObjectSFUi", RootObject_New); RegisterFunctionExportName("__nw__Q3_2nn2ec10RootObjectSFUiPv", RootObject_PlacementNew); RegisterFunctionExportName("__nwa__Q3_2nn2ec10RootObjectSFUiPv", RootObject_PlacementNew); RegisterFunctionExportName("__dl__Q3_2nn2ec10RootObjectSFPv", RootObject_Free); RegisterFunctionExportName("__dla__Q3_2nn2ec10RootObjectSFPv", RootObject_Free); RegisterTypeInfo( RootObject, "nn::ec::RootObject", { }, { }); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_rootobject.h ================================================ #pragma once #include "cafe/nn/cafe_nn_os_criticalsection.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct RootObject { static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; }; virt_ptr<void> RootObject_New(uint32_t size); virt_ptr<void> RootObject_PlacementNew(uint32_t size, virt_ptr<void> ptr); void RootObject_Free(virt_ptr<void> ptr); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_shoppingcatalog.cpp ================================================ #include "cafe/libraries/nn_ec/nn_ec.h" #include "cafe/libraries/nn_ec/nn_ec_shoppingcatalog.h" #include "cafe/libraries/nn_ec/nn_ec_rootobject.h" namespace cafe::nn_ec { virt_ptr<ghs::VirtualTable> ShoppingCatalog::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> ShoppingCatalog::TypeDescriptor = nullptr; virt_ptr<ShoppingCatalog> ShoppingCatalog_Constructor(virt_ptr<ShoppingCatalog> self) { if (!self) { self = virt_cast<ShoppingCatalog *>(RootObject_New(sizeof(ShoppingCatalog))); if (!self) { return nullptr; } } Catalog_Constructor(virt_cast<Catalog *>(self)); self->impl = nullptr; self->virtualTable = ShoppingCatalog::VirtualTable; return self; } void ShoppingCatalog_Destructor(virt_ptr<ShoppingCatalog> self, ghs::DestructorFlags flags) { self->virtualTable = ShoppingCatalog::VirtualTable; Catalog_Destructor(virt_cast<Catalog *>(self), ghs::DestructorFlags::None); if (flags & ghs::DestructorFlags::FreeMemory) { RootObject_Free(self); } } void Library::registerShoppingCatalogSymbols() { RegisterFunctionExportName("__ct__Q3_2nn2ec15ShoppingCatalogFv", ShoppingCatalog_Constructor); RegisterFunctionExportName("__dt__Q3_2nn2ec15ShoppingCatalogFv", ShoppingCatalog_Destructor); RegisterTypeInfo( ShoppingCatalog, "nn::ec::ShoppingCatalog", { "__dt__Q3_2nn2ec15ShoppingCatalogFv", }, { "nn::ec::Catalog", }); } } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_shoppingcatalog.h ================================================ #pragma once #include "cafe/libraries/nn_ec/nn_ec_catalog.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include <libcpu/be2_struct.h> namespace cafe::nn_ec { struct ShoppingCatalog : Catalog { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; }; virt_ptr<ShoppingCatalog> ShoppingCatalog_Constructor(virt_ptr<ShoppingCatalog> self); void ShoppingCatalog_Destructor(virt_ptr<ShoppingCatalog> self, ghs::DestructorFlags flags); } // namespace cafe::nn_ec ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_fp/nn_fp.cpp ================================================ #include "nn_fp.h" namespace cafe::nn_fp { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerLibSymbols(); } } // namespace cafe::nn_fp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_fp/nn_fp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_fp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_fp, "nn_fp.rpl") { } protected: virtual void registerSymbols() override; private: void registerLibSymbols(); }; } // namespace cafe::nn_fp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_fp/nn_fp_lib.cpp ================================================ #include "nn_fp.h" #include "nn_fp_lib.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nn_fp { struct StaticLibData { be2_val<uint32_t> initialiseCount; }; static virt_ptr<StaticLibData> sLibData = nullptr; nn::Result Initialize() { decaf_warn_stub(); sLibData->initialiseCount++; return nn::ResultSuccess; } nn::Result Finalize() { decaf_warn_stub(); if (sLibData->initialiseCount > 0) { sLibData->initialiseCount--; } return nn::ResultSuccess; } bool IsInitialized() { decaf_warn_stub(); return sLibData->initialiseCount > 0; } bool IsOnline() { decaf_warn_stub(); return false; } nn::Result GetFriendList(virt_ptr<void> list, virt_ptr<uint32_t> outLength, uint32_t index, uint32_t listSize) { decaf_warn_stub(); if (outLength) { *outLength = 0u; } return nn::ResultSuccess; } void Library::registerLibSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn2fpFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn2fpFv", Finalize); RegisterFunctionExportName("IsInitialized__Q2_2nn2fpFv", IsInitialized); RegisterFunctionExportName("IsOnline__Q2_2nn2fpFv", IsOnline); RegisterFunctionExportName("GetFriendList__Q2_2nn2fpFPUiT1UiT3", GetFriendList); RegisterDataInternal(sLibData); } } // namespace cafe::nn_fp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_fp/nn_fp_lib.h ================================================ #pragma once #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_fp { nn::Result Initialize(); nn::Result Finalize(); bool IsInitialized(); bool IsOnline(); nn::Result GetFriendList(virt_ptr<void> list, virt_ptr<uint32_t> outLength, uint32_t index, uint32_t listSize); } // namespace cafe::nn_fp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_hai/nn_hai.cpp ================================================ #include "nn_hai.h" namespace cafe::nn_hai { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nn_hai ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_hai/nn_hai.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_hai { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_hai, "nn_hai.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nn_hai ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_hpad/nn_hpad.cpp ================================================ #include "nn_hpad.h" namespace cafe::nn_hpad { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nn_hpad ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_hpad/nn_hpad.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_hpad { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_hpad, "nn_hpad.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nn_hpad ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_idbe/nn_idbe.cpp ================================================ #include "nn_idbe.h" namespace cafe::nn_idbe { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nn_idbe ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_idbe/nn_idbe.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_idbe { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_idbe, "nn_idbe.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nn_idbe ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm.cpp ================================================ #include "nn_ndm.h" namespace cafe::nn_ndm { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); } } // namespace cafe::nn_ndm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_ndm { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_ndm, "nn_ndm.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); }; } // namespace cafe::nn_ndm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm_client.cpp ================================================ #include "nn_ndm.h" #include "nn_ndm_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/nn/cafe_nn_ipc_client.h" using namespace cafe::coreinit; namespace cafe::nn_ndm { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(64) be2_array<uint8_t, 0x1000> allocatorMemory; be2_struct<OSMutex> mutex; be2_val<uint32_t> refCount = 0u; be2_struct<nn::ipc::Client> client; be2_struct<nn::ipc::BufferAllocator> allocator; }; static virt_ptr<StaticClientData> sClientData = nullptr; nn::Result Initialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->client.initialise(make_stack_string("/dev/ndm")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return nn::ResultSuccess; } void Finalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } bool IsInitialized() { return sClientData->client.isInitialised(); } nn::Result EnableResumeDaemons() { decaf_warn_stub(); return nn::ResultSuccess; } nn::Result GetDaemonStatus(virt_ptr<uint32_t> status, // nn::ndm::IDaemon::Status * uint32_t unknown) // nn::ndm::Cafe::DaemonName { decaf_warn_stub(); *status = 3u; return nn::ResultSuccess; } void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn3ndmFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn3ndmFv", Finalize); RegisterFunctionExportName("IsInitialized__Q2_2nn3ndmFv", IsInitialized); RegisterFunctionExportName("EnableResumeDaemons__Q2_2nn3ndmFv", EnableResumeDaemons); RegisterFunctionExportName("GetDaemonStatus__Q2_2nn3ndmFPQ4_2nn3ndm7IDaemon6StatusQ4_2nn3ndm4Cafe10DaemonName", GetDaemonStatus); RegisterFunctionExportName("NDMInitialize", Initialize); RegisterFunctionExportName("NDMFinalize", Finalize); RegisterFunctionExportName("NDMEnableResumeDaemons", EnableResumeDaemons); RegisterDataInternal(sClientData); } } // namespace cafe::nn_ndm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm_client.h ================================================ #pragma once #include "nn/nn_result.h" namespace cafe::nn_ndm { nn::Result Initialize(); void Finalize(); bool IsInitialized(); nn::Result EnableResumeDaemons(); } // namespace cafe::nn_ndm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nets2/nn_nets2.cpp ================================================ #include "nn_nets2.h" namespace cafe::nn_nets2 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nn_nets2 ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nets2/nn_nets2.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_nets2 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_nets2, "nn_nets2.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nn_nets2 ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp.cpp ================================================ #include "nn_nfp.h" namespace cafe::nn_nfp { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerLibSymbols(); } } // namespace cafe::nn_nfp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_nfp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_nfp, "nn_nfp.rpl") { } protected: virtual void registerSymbols() override; private: void registerLibSymbols(); }; } // namespace cafe::nn_nfp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp_enum.h ================================================ #ifndef NN_NFP_ENUM_H #define NN_NFP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_nfp) ENUM_BEG(State, uint32_t) ENUM_VALUE(Uninitialised, 0) ENUM_VALUE(Initialised, 1) ENUM_VALUE(Detecting, 2) ENUM_END(State) ENUM_NAMESPACE_EXIT(nn_nfp) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef NN_NFP_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp_lib.cpp ================================================ #include "nn_nfp.h" #include "nn_nfp_lib.h" #include "cafe/libraries/cafe_hle_stub.h" #include "nn/nfp/nn_nfp_result.h" using namespace nn::nfp; namespace cafe::nn_nfp { struct StaticLibData { be2_val<uint32_t> initialiseCount; }; static virt_ptr<StaticLibData> sLibData = nullptr; nn::Result Initialize() { decaf_warn_stub(); sLibData->initialiseCount++; return ResultSuccess; } nn::Result Finalize() { decaf_warn_stub(); if (sLibData->initialiseCount > 0) { sLibData->initialiseCount--; } return ResultSuccess; } nn::Result GetAmiiboSettingsArgs(virt_ptr<AmiiboSettingsArgs> args) { decaf_warn_stub(); std::memset(args.get(), 0, sizeof(AmiiboSettingsArgs)); return ResultSuccess; } State GetNfpState() { decaf_warn_stub(); return State::Uninitialised; } nn::Result SetActivateEvent(uint32_t a1) { decaf_warn_stub(); return ResultSuccess; } nn::Result SetDeactivateEvent(uint32_t a1) { decaf_warn_stub(); return ResultSuccess; } nn::Result StartDetection() { decaf_warn_stub(); return ResultSuccess; } nn::Result StopDetection() { decaf_warn_stub(); return ResultSuccess; } void Library::registerLibSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn3nfpFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn3nfpFv", Finalize); RegisterFunctionExportName("GetAmiiboSettingsArgs__Q2_2nn3nfpFPQ3_2nn3nfp18AmiiboSettingsArgs", GetAmiiboSettingsArgs); RegisterFunctionExportName("GetNfpState__Q2_2nn3nfpFv", GetNfpState); RegisterFunctionExportName("SetActivateEvent__Q2_2nn3nfpFP7OSEvent", SetActivateEvent); RegisterFunctionExportName("SetDeactivateEvent__Q2_2nn3nfpFP7OSEvent", SetDeactivateEvent); RegisterFunctionExportName("StartDetection__Q2_2nn3nfpFv", StartDetection); RegisterFunctionExportName("StopDetection__Q2_2nn3nfpFv", StopDetection); RegisterDataInternal(sLibData); } } // namespace cafe::nn_nfp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp_lib.h ================================================ #pragma once #include "nn_nfp_enum.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_nfp { struct AmiiboSettingsArgs { UNKNOWN(0x5D); }; CHECK_SIZE(AmiiboSettingsArgs, 0x5D); nn::Result Initialize(); nn::Result Finalize(); nn::Result GetAmiiboSettingsArgs(virt_ptr<AmiiboSettingsArgs> args); State GetNfpState(); nn::Result SetActivateEvent(uint32_t a1); nn::Result SetDeactivateEvent(uint32_t a1); nn::Result StartDetection(); nn::Result StopDetection(); } // namespace cafe::nn_nfp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nim/nn_nim.cpp ================================================ #include "nn_nim.h" namespace cafe::nn_nim { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); } } // namespace cafe::nn_nim ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nim/nn_nim.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_nim { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_nim, "nn_nim.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); }; } // namespace cafe::nn_nim ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nim/nn_nim_client.cpp ================================================ #include "nn_nim.h" #include "nn_nim_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/nn/cafe_nn_ipc_client.h" using namespace cafe::coreinit; namespace cafe::nn_nim { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(64) be2_array<uint8_t, 0x10000> allocatorMemory; be2_struct<OSMutex> mutex; be2_val<uint32_t> refCount = 0u; be2_struct<nn::ipc::Client> client; be2_struct<nn::ipc::BufferAllocator> allocator; }; static virt_ptr<StaticClientData> sClientData = nullptr; nn::Result Initialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->client.initialise(make_stack_string("/dev/nim")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return nn::ResultSuccess; } void Finalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } namespace internal { virt_ptr<nn::ipc::Client> getClient() { return virt_addrof(sClientData->client); } virt_ptr<nn::ipc::BufferAllocator> getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn3nimFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn3nimFv", Finalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_nim ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_nim/nn_nim_client.h ================================================ #pragma once #include "nn/nn_result.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" namespace cafe::nn_nim { nn::Result Initialize(); void Finalize(); namespace internal { virt_ptr<nn::ipc::Client> getClient(); virt_ptr<nn::ipc::BufferAllocator> getAllocator(); } // namespace internal } // namespace cafe::nn_nim ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv.cpp ================================================ #include "nn_olv.h" #include "cafe/libraries/coreinit/coreinit_ghs.h" #include "cafe/libraries/coreinit/coreinit_osreport.h" namespace cafe::nn_olv { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerDownloadedCommunityDataSymbols(); registerDownloadedDataBaseSymbols(); registerDownloadedPostDataSymbols(); registerDownloadedTopicDataSymbols(); registerInitSymbols(); registerInitializeParamSymbols(); registerUploadedDataBaseSymbols(); registerUploadedPostDataSymbols(); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_olv { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_olv, "nn_olv.rpl") { } protected: virtual void registerSymbols() override; private: void registerDownloadedCommunityDataSymbols(); void registerDownloadedDataBaseSymbols(); void registerDownloadedPostDataSymbols(); void registerDownloadedTopicDataSymbols(); void registerInitSymbols(); void registerInitializeParamSymbols(); void registerUploadedDataBaseSymbols(); void registerUploadedPostDataSymbols(); }; } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedcommunitydata.cpp ================================================ #include "nn_olv.h" #include "nn_olv_downloadedcommunitydata.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/olv/nn_olv_result.h" using namespace nn::olv; using nn::ffl::FFLStoreData; namespace cafe::nn_olv { virt_ptr<DownloadedCommunityData> DownloadedCommunityData_Constructor(virt_ptr<DownloadedCommunityData> self) { if (!self) { self = virt_cast<DownloadedCommunityData *>(ghs::malloc(sizeof(DownloadedCommunityData))); if (!self) { return nullptr; } } std::memset(self.get(), 0, sizeof(DownloadedCommunityData)); return self; } uint32_t DownloadedCommunityData_GetAppDataSize(virt_ptr<DownloadedCommunityData> self) { if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasAppData)) { return 0; } return self->appDataLength; } nn::Result DownloadedCommunityData_GetAppData(virt_ptr<DownloadedCommunityData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize) { if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasAppData)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->appDataLength); std::memcpy(buffer.get(), virt_addrof(self->appData).get(), length); if (outDataSize) { *outDataSize = length; } return ResultSuccess; } uint32_t DownloadedCommunityData_GetCommunityId(virt_ptr<DownloadedCommunityData> self) { return self->communityId; } nn::Result DownloadedCommunityData_GetDescriptionText(virt_ptr<DownloadedCommunityData> self, virt_ptr<char16_t> buffer, uint32_t bufferSize) { if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasDescriptionText)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->descriptionTextLength); std::memcpy(buffer.get(), virt_addrof(self->descriptionText).get(), length * sizeof(char16_t)); if (length < bufferSize) { buffer[length] = char16_t { 0 }; } return ResultSuccess; } nn::Result DownloadedCommunityData_GetIconData(virt_ptr<DownloadedCommunityData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outIconSize, uint32_t bufferSize) { if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasIconData)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->iconDataLength); std::memcpy(buffer.get(), virt_addrof(self->iconData).get(), length); if (outIconSize) { *outIconSize = length; } return ResultSuccess; } nn::Result DownloadedCommunityData_GetOwnerMiiData(virt_ptr<DownloadedCommunityData> self, virt_ptr<nn::ffl::FFLStoreData> data) { if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasOwnerMiiData)) { return ResultNoData; } if (!data) { return ResultInvalidPointer; } std::memcpy(data.get(), virt_addrof(self->ownerMiiData).get(), sizeof(FFLStoreData)); return ResultSuccess; } virt_ptr<char16_t> DownloadedCommunityData_GetOwnerMiiNickname(virt_ptr<DownloadedCommunityData> self) { if (!self->ownerMiiNickname[0]) { return nullptr; } return virt_addrof(self->ownerMiiNickname); } uint32_t DownloadedCommunityData_GetOwnerPid(virt_ptr<DownloadedCommunityData> self) { return self->ownerPid; } nn::Result DownloadedCommunityData_GetTitleText(virt_ptr<DownloadedCommunityData> self, virt_ptr<char16_t> buffer, uint32_t bufferSize) { if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasTitleText)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->titleTextLength); std::memcpy(buffer.get(), virt_addrof(self->titleText).get(), length * sizeof(char16_t)); if (length < bufferSize) { buffer[length] = char16_t { 0 }; } return ResultSuccess; } bool DownloadedCommunityData_TestFlags(virt_ptr<DownloadedCommunityData> self, uint32_t flags) { return !!(self->flags & flags); } void Library::registerDownloadedCommunityDataSymbols() { RegisterFunctionExportName("__ct__Q3_2nn3olv23DownloadedCommunityDataFv", DownloadedCommunityData_Constructor); RegisterFunctionExportName("GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv", DownloadedCommunityData_GetAppDataSize); RegisterFunctionExportName("GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", DownloadedCommunityData_GetAppData); RegisterFunctionExportName("GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv", DownloadedCommunityData_GetCommunityId); RegisterFunctionExportName("GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", DownloadedCommunityData_GetDescriptionText); RegisterFunctionExportName("GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", DownloadedCommunityData_GetIconData); RegisterFunctionExportName("GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData", DownloadedCommunityData_GetOwnerMiiData); RegisterFunctionExportName("GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv", DownloadedCommunityData_GetOwnerMiiNickname); RegisterFunctionExportName("GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv", DownloadedCommunityData_GetOwnerPid); RegisterFunctionExportName("GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", DownloadedCommunityData_GetTitleText); RegisterFunctionExportName("TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi", DownloadedCommunityData_TestFlags); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedcommunitydata.h ================================================ #pragma once #include "nn/nn_result.h" #include "nn/ffl/nn_ffl_miidata.h" #include <libcpu/be2_struct.h> /* Unimplemented functions: nn::olv::DownloadedCommunityData::GetCommunityCode(char *, unsigned int) const GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi */ namespace cafe::nn_olv { struct DownloadedCommunityData { enum Flags { Empty = 0, HasTitleText = 1 << 0, HasDescriptionText = 1 << 1, HasAppData = 1 << 2, HasIconData = 1 << 3, HasOwnerMiiData = 1 << 4, }; be2_val<uint32_t> flags; be2_val<uint32_t> communityId; be2_val<uint32_t> ownerPid; be2_array<char16_t, 128> titleText; be2_val<uint32_t> titleTextLength; be2_array<char16_t, 256> descriptionText; be2_val<uint32_t> descriptionTextLength; be2_array<uint8_t, 1024> appData; be2_val<uint32_t> appDataLength; be2_array<uint8_t, 0x1002C> iconData; be2_val<uint32_t> iconDataLength; be2_array<uint8_t, 96> ownerMiiData; be2_array<char16_t, 32> ownerMiiNickname; UNKNOWN(0x1818); }; CHECK_OFFSET(DownloadedCommunityData, 0x00, flags); CHECK_OFFSET(DownloadedCommunityData, 0x04, communityId); CHECK_OFFSET(DownloadedCommunityData, 0x08, ownerPid); CHECK_OFFSET(DownloadedCommunityData, 0x0C, titleText); CHECK_OFFSET(DownloadedCommunityData, 0x10C, titleTextLength); CHECK_OFFSET(DownloadedCommunityData, 0x110, descriptionText); CHECK_OFFSET(DownloadedCommunityData, 0x310, descriptionTextLength); CHECK_OFFSET(DownloadedCommunityData, 0x314, appData); CHECK_OFFSET(DownloadedCommunityData, 0x714, appDataLength); CHECK_OFFSET(DownloadedCommunityData, 0x718, iconData); CHECK_OFFSET(DownloadedCommunityData, 0x10744, iconDataLength); CHECK_OFFSET(DownloadedCommunityData, 0x10748, ownerMiiData); CHECK_OFFSET(DownloadedCommunityData, 0x107A8, ownerMiiNickname); CHECK_SIZE(DownloadedCommunityData, 0x12000); virt_ptr<DownloadedCommunityData> DownloadedCommunityData_Constructor(virt_ptr<DownloadedCommunityData> self); uint32_t DownloadedCommunityData_GetAppDataSize(virt_ptr<DownloadedCommunityData> self); nn::Result DownloadedCommunityData_GetAppData(virt_ptr<DownloadedCommunityData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize); uint32_t DownloadedCommunityData_GetCommunityId(virt_ptr<DownloadedCommunityData> self); nn::Result DownloadedCommunityData_GetDescriptionText(virt_ptr<DownloadedCommunityData> self, virt_ptr<char16_t> buffer, uint32_t bufferSize); nn::Result DownloadedCommunityData_GetIconData(virt_ptr<DownloadedCommunityData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outIconSize, uint32_t bufferSize); nn::Result DownloadedCommunityData_GetOwnerMiiData(virt_ptr<DownloadedCommunityData> self, virt_ptr<nn::ffl::FFLStoreData> data); virt_ptr<char16_t> DownloadedCommunityData_GetOwnerMiiNickname(virt_ptr<DownloadedCommunityData> self); uint32_t DownloadedCommunityData_GetOwnerPid(virt_ptr<DownloadedCommunityData> self); nn::Result DownloadedCommunityData_GetTitleText(virt_ptr<DownloadedCommunityData> self, virt_ptr<char16_t> buffer, uint32_t bufferSize); bool DownloadedCommunityData_TestFlags(virt_ptr<DownloadedCommunityData> self, uint32_t flags); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadeddatabase.cpp ================================================ #include "nn_olv.h" #include "nn_olv_downloadeddatabase.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/olv/nn_olv_result.h" #include <common/strutils.h> using namespace nn::olv; using nn::ffl::FFLStoreData; namespace cafe::nn_olv { constexpr auto MinMemoBufferSize = 0x2582Cu; virt_ptr<ghs::VirtualTable> DownloadedDataBase::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> DownloadedDataBase::TypeDescriptor = nullptr; virt_ptr<DownloadedDataBase> DownloadedDataBase_Constructor(virt_ptr<DownloadedDataBase> self) { if (!self) { self = virt_cast<DownloadedDataBase *>(ghs::malloc(sizeof(DownloadedDataBase))); if (!self) { return nullptr; } } std::memset(self.get(), 0, sizeof(DownloadedDataBase)); self->virtualTable = DownloadedDataBase::VirtualTable; return self; } void DownloadedDataBase_Destructor(virt_ptr<DownloadedDataBase> self, ghs::DestructorFlags flags) { if (!self) { return; } if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } nn::Result DownloadedDataBase_DownloadExternalBinaryData(virt_ptr<const DownloadedDataBase> self, virt_ptr<void> dataBuffer, virt_ptr<uint32_t> outDataSize, uint32_t dataBufferSize) { return ResultNotOnline; } nn::Result DownloadedDataBase_DownloadExternalImageData(virt_ptr<const DownloadedDataBase> self, virt_ptr<void> dataBuffer, virt_ptr<uint32_t> outDataSize, uint32_t dataBufferSize) { return ResultNotOnline; } nn::Result DownloadedDataBase_GetAppData(virt_ptr<const DownloadedDataBase> self, virt_ptr<uint32_t> dataBuffer, virt_ptr<uint32_t> outSize, uint32_t dataBufferSize) { if (!dataBuffer) { return ResultInvalidPointer; } if (!dataBufferSize) { return ResultInvalidSize; } if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasAppData)) { return ResultNoData; } auto copySize = std::min<uint32_t>(self->appDataSize, dataBufferSize); std::memcpy(dataBuffer.get(), virt_addrof(self->appData).get(), copySize); if (outSize) { *outSize = copySize; } return ResultSuccess; } uint32_t DownloadedDataBase_GetAppDataSize(virt_ptr<const DownloadedDataBase> self) { if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasAppData)) { return self->appDataSize; } return 0; } nn::Result DownloadedDataBase_GetBodyMemo(virt_ptr<const DownloadedDataBase> self, virt_ptr<uint8_t> memoBuffer, virt_ptr<uint32_t> outSize, uint32_t memoBufferSize) { if (!memoBuffer) { return ResultInvalidPointer; } if (memoBufferSize < MinMemoBufferSize) { return ResultInvalidSize; } if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasBodyMemo)) { return ResultNoData; } // TODO: TGA uncompress mBodyMemo return ResultNoData; } nn::Result DownloadedDataBase_GetBodyText(virt_ptr<const DownloadedDataBase> self, virt_ptr<char16_t> textBuffer, uint32_t textBufferSize) { if (!textBuffer) { return ResultInvalidPointer; } if (!textBufferSize) { return ResultInvalidSize; } if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasBodyText)) { return ResultNoData; } string_copy<char16_t>(textBuffer.get(), textBufferSize, virt_addrof(self->bodyText).get(), static_cast<size_t>(self->bodyTextLength)); return ResultSuccess; } uint8_t DownloadedDataBase_GetCountryId(virt_ptr<const DownloadedDataBase> self) { return self->countryId; } uint32_t DownloadedDataBase_GetExternalBinaryDataSize(virt_ptr<const DownloadedDataBase> self) { if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasExternalBinaryData)) { return self->externalBinaryDataSize; } return 0; } uint32_t DownloadedDataBase_GetExternalImageDataSize(virt_ptr<const DownloadedDataBase> self) { if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasExternalImageData)) { return self->externalImageDataSize; } return 0; } virt_ptr<const char> DownloadedDataBase_GetExternalUrl(virt_ptr<const DownloadedDataBase> self) { if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasExternalUrl)) { return virt_addrof(self->externalUrl); } return nullptr; } uint8_t DownloadedDataBase_GetFeeling(virt_ptr<const DownloadedDataBase> self) { return self->feeling; } uint8_t DownloadedDataBase_GetLanguageId(virt_ptr<const DownloadedDataBase> self) { return self->languageId; } nn::Result DownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self, virt_ptr<FFLStoreData> outData) { if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasMiiData)) { return ResultNoData; } if (!outData) { return ResultInvalidPointer; } std::memcpy(outData.get(), virt_addrof(self->miiData).get(), sizeof(FFLStoreData)); return ResultSuccess; } virt_ptr<nn::ffl::FFLStoreData> DownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self) { if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasMiiData)) { return virt_addrof(self->miiData); } return nullptr; } virt_ptr<const char16_t> DownloadedDataBase_GetMiiNickname(virt_ptr<const DownloadedDataBase> self) { if (self->miiNickname[0]) { return virt_addrof(self->miiNickname); } return nullptr; } uint8_t DownloadedDataBase_GetPlatformId(virt_ptr<const DownloadedDataBase> self) { return self->platformId; } uint64_t DownloadedDataBase_GetPostDate(virt_ptr<const DownloadedDataBase> self) { return self->postDate; } virt_ptr<const uint8_t> DownloadedDataBase_GetPostId(virt_ptr<const DownloadedDataBase> self) { return virt_addrof(self->postId); } uint32_t DownloadedDataBase_GetRegionId(virt_ptr<const DownloadedDataBase> self) { return self->regionId; } virt_ptr<const uint8_t> DownloadedDataBase_GetTopicTag(virt_ptr<const DownloadedDataBase> self) { return virt_addrof(self->topicTag); } uint32_t DownloadedDataBase_GetUserPid(virt_ptr<const DownloadedDataBase> self) { return self->userPid; } bool DownloadedDataBase_TestFlags(virt_ptr<const DownloadedDataBase> self, uint32_t flagMask) { return (self->flags & flagMask) != 0; } void Library::registerDownloadedDataBaseSymbols() { RegisterFunctionExportName("__ct__Q3_2nn3olv18DownloadedDataBaseFv", DownloadedDataBase_Constructor); RegisterFunctionExportName("__dt__Q3_2nn3olv18DownloadedDataBaseFv", DownloadedDataBase_Destructor); RegisterFunctionExportName("DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", DownloadedDataBase_DownloadExternalBinaryData); RegisterFunctionExportName("DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", DownloadedDataBase_DownloadExternalImageData); RegisterFunctionExportName("GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetAppDataSize); RegisterFunctionExportName("GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", DownloadedDataBase_GetAppData); RegisterFunctionExportName("GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", DownloadedDataBase_GetBodyMemo); RegisterFunctionExportName("GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", DownloadedDataBase_GetBodyText); RegisterFunctionExportName("GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetCountryId); RegisterFunctionExportName("GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetExternalBinaryDataSize); RegisterFunctionExportName("GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetExternalImageDataSize); RegisterFunctionExportName("GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetExternalUrl); RegisterFunctionExportName("GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetFeeling); RegisterFunctionExportName("GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetLanguageId); RegisterFunctionExportName("GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData", static_cast<nn::Result (*)(virt_ptr<const DownloadedDataBase>, virt_ptr<FFLStoreData>)>(DownloadedDataBase_GetMiiData)); RegisterFunctionExportName("GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", static_cast<virt_ptr<FFLStoreData> (*)(virt_ptr<const DownloadedDataBase>)>(DownloadedDataBase_GetMiiData)); RegisterFunctionExportName("GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetMiiNickname); RegisterFunctionExportName("GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetPlatformId); RegisterFunctionExportName("GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetPostDate); RegisterFunctionExportName("GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetPostId); RegisterFunctionExportName("GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetRegionId); RegisterFunctionExportName("GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetTopicTag); RegisterFunctionExportName("GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv", DownloadedDataBase_GetUserPid); RegisterFunctionExportName("TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", DownloadedDataBase_TestFlags); RegisterTypeInfo( DownloadedDataBase, "nn::olv::DownloadedDataBase", { "__pure_virtual_called", }, {}); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadeddatabase.h ================================================ #pragma once #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include "nn/ffl/nn_ffl_miidata.h" #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct DownloadedDataBase { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; enum Flags { Empty = 0, HasBodyText = 1 << 0, HasBodyMemo = 1 << 1, HasExternalImageData = 1 << 2, HasExternalBinaryData = 1 << 3, HasMiiData = 1 << 4, HasExternalUrl = 1 << 5, HasAppData = 1 << 6, }; be2_val<uint32_t> flags; be2_val<uint32_t> userPid; be2_array<uint8_t, 32> postId; be2_val<uint64_t> postDate; be2_val<uint8_t> feeling; PADDING(3); be2_val<uint32_t> regionId; be2_val<uint8_t> platformId; be2_val<uint8_t> languageId; be2_val<uint8_t> countryId; PADDING(1); be2_array<char16_t, 256> bodyText; be2_val<uint32_t> bodyTextLength; be2_array<uint8_t, 40960> bodyMemo; be2_val<uint32_t> bodyMemoSize; be2_array<uint8_t, 304> topicTag; be2_array<uint8_t, 1024> appData; be2_val<uint32_t> appDataSize; be2_array<char, 256> externalBinaryUrl; be2_val<uint32_t> externalBinaryDataSize; be2_array<char, 256> externalImageUrl; be2_val<uint32_t> externalImageDataSize; be2_array<char, 256> externalUrl; be2_struct<nn::ffl::FFLStoreData> miiData; be2_array<char16_t, 128> miiNickname; // Actual size unknown UNKNOWN(0xC000 - 0xABE0); be2_virt_ptr<ghs::VirtualTable> virtualTable; UNKNOWN(4); }; CHECK_OFFSET(DownloadedDataBase, 0x00, flags); CHECK_OFFSET(DownloadedDataBase, 0x04, userPid); CHECK_OFFSET(DownloadedDataBase, 0x08, postId); CHECK_OFFSET(DownloadedDataBase, 0x28, postDate); CHECK_OFFSET(DownloadedDataBase, 0x30, feeling); CHECK_OFFSET(DownloadedDataBase, 0x34, regionId); CHECK_OFFSET(DownloadedDataBase, 0x38, platformId); CHECK_OFFSET(DownloadedDataBase, 0x39, languageId); CHECK_OFFSET(DownloadedDataBase, 0x3A, countryId); CHECK_OFFSET(DownloadedDataBase, 0x3C, bodyText); CHECK_OFFSET(DownloadedDataBase, 0x23C, bodyTextLength); CHECK_OFFSET(DownloadedDataBase, 0x240, bodyMemo); CHECK_OFFSET(DownloadedDataBase, 0xA240, bodyMemoSize); CHECK_OFFSET(DownloadedDataBase, 0xA244, topicTag); CHECK_OFFSET(DownloadedDataBase, 0xA374, appData); CHECK_OFFSET(DownloadedDataBase, 0xA774, appDataSize); CHECK_OFFSET(DownloadedDataBase, 0xA778, externalBinaryUrl); CHECK_OFFSET(DownloadedDataBase, 0xA878, externalBinaryDataSize); CHECK_OFFSET(DownloadedDataBase, 0xA87C, externalImageUrl); CHECK_OFFSET(DownloadedDataBase, 0xA97C, externalImageDataSize); CHECK_OFFSET(DownloadedDataBase, 0xA980, externalUrl); CHECK_OFFSET(DownloadedDataBase, 0xAA80, miiData); CHECK_OFFSET(DownloadedDataBase, 0xAAE0, miiNickname); CHECK_OFFSET(DownloadedDataBase, 0xC000, virtualTable); CHECK_SIZE(DownloadedDataBase, 0xC008); virt_ptr<DownloadedDataBase> DownloadedDataBase_Constructor(virt_ptr<DownloadedDataBase> self); void DownloadedDataBase_Destructor(virt_ptr<DownloadedDataBase> self, ghs::DestructorFlags flags); nn::Result DownloadedDataBase_DownloadExternalBinaryData(virt_ptr<const DownloadedDataBase> self, virt_ptr<void> dataBuffer, virt_ptr<uint32_t> outDataSize, uint32_t dataBufferSize); nn::Result DownloadedDataBase_DownloadExternalImageData(virt_ptr<const DownloadedDataBase> self, virt_ptr<void> dataBuffer, virt_ptr<uint32_t> outDataSize, uint32_t dataBufferSize); nn::Result DownloadedDataBase_GetAppData(virt_ptr<const DownloadedDataBase> self, virt_ptr<uint32_t> dataBuffer, virt_ptr<uint32_t> outSize, uint32_t dataBufferSize); uint32_t DownloadedDataBase_GetAppDataSize(virt_ptr<const DownloadedDataBase> self); nn::Result DownloadedDataBase_GetBodyMemo(virt_ptr<const DownloadedDataBase> self, virt_ptr<uint8_t> memoBuffer, virt_ptr<uint32_t> outSize, uint32_t memoBufferSize); nn::Result DownloadedDataBase_GetBodyText(virt_ptr<const DownloadedDataBase> self, virt_ptr<char16_t> textBuffer, uint32_t textBufferSize); uint8_t DownloadedDataBase_GetCountryId(virt_ptr<const DownloadedDataBase> self); uint32_t DownloadedDataBase_GetExternalBinaryDataSize(virt_ptr<const DownloadedDataBase> self); uint32_t DownloadedDataBase_GetExternalImageDataSize(virt_ptr<const DownloadedDataBase> self); virt_ptr<const char> DownloadedDataBase_GetExternalUrl(virt_ptr<const DownloadedDataBase> self); uint8_t DownloadedDataBase_GetFeeling(virt_ptr<const DownloadedDataBase> self); uint8_t DownloadedDataBase_GetLanguageId(virt_ptr<const DownloadedDataBase> self); nn::Result DownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self, virt_ptr<nn::ffl::FFLStoreData> outData); virt_ptr<nn::ffl::FFLStoreData> DownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self); virt_ptr<const char16_t> DownloadedDataBase_GetMiiNickname(virt_ptr<const DownloadedDataBase> self); uint8_t DownloadedDataBase_GetPlatformId(virt_ptr<const DownloadedDataBase> self); uint64_t DownloadedDataBase_GetPostDate(virt_ptr<const DownloadedDataBase> self); virt_ptr<const uint8_t> DownloadedDataBase_GetPostId(virt_ptr<const DownloadedDataBase> self); uint32_t DownloadedDataBase_GetRegionId(virt_ptr<const DownloadedDataBase> self); virt_ptr<const uint8_t> DownloadedDataBase_GetTopicTag(virt_ptr<const DownloadedDataBase> self); uint32_t DownloadedDataBase_GetUserPid(virt_ptr<const DownloadedDataBase> self); bool DownloadedDataBase_TestFlags(virt_ptr<const DownloadedDataBase> self, uint32_t flagMask); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedpostdata.cpp ================================================ #include "nn_olv.h" #include "nn_olv_downloadeddatabase.h" #include "nn_olv_downloadedpostdata.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_olv { virt_ptr<ghs::VirtualTable> DownloadedPostData::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> DownloadedPostData::TypeDescriptor = nullptr; virt_ptr<DownloadedPostData> DownloadedPostData_Constructor(virt_ptr<DownloadedPostData> self) { if (!self) { self = virt_cast<DownloadedPostData *>(ghs::malloc(sizeof(DownloadedPostData))); if (!self) { return nullptr; } } DownloadedDataBase_Constructor(virt_cast<DownloadedDataBase *>(self)); self->virtualTable = DownloadedPostData::VirtualTable; self->communityId = 0u; self->empathyCount = 0u; self->commentCount = 0u; return self; } void DownloadedPostData_Destructor(virt_ptr<DownloadedPostData> self, ghs::DestructorFlags flags) { if (!self) { return; } if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } uint32_t DownloadedPostData_GetCommentCount(virt_ptr<const DownloadedPostData> self) { return self->commentCount; } uint32_t DownloadedPostData_GetCommunityId(virt_ptr<const DownloadedPostData> self) { return self->communityId; } uint32_t DownloadedPostData_GetEmpathyCount(virt_ptr<const DownloadedPostData> self) { return self->empathyCount; } virt_ptr<const uint8_t> DownloadedPostData_GetPostId(virt_ptr<const DownloadedPostData> self) { return DownloadedDataBase_GetPostId(virt_cast<const DownloadedDataBase *>(self)); } void Library::registerDownloadedPostDataSymbols() { RegisterFunctionExportName("__ct__Q3_2nn3olv18DownloadedPostDataFv", DownloadedPostData_Constructor); RegisterFunctionExportName("__dt__Q3_2nn3olv18DownloadedPostDataFv", DownloadedPostData_Destructor); RegisterFunctionExportName("GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv", DownloadedPostData_GetCommentCount); RegisterFunctionExportName("GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", DownloadedPostData_GetCommunityId); RegisterFunctionExportName("GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv", DownloadedPostData_GetEmpathyCount); RegisterFunctionExportName("GetPostId__Q3_2nn3olv18DownloadedPostDataCFv", DownloadedPostData_GetPostId); RegisterTypeInfo( DownloadedPostData, "nn::olv::DownloadedPostData", { "__dt__Q3_2nn3olv18DownloadedPostDataFv", }, {}); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedpostdata.h ================================================ #pragma once #include "nn_olv_downloadeddatabase.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct DownloadedPostData : public DownloadedDataBase { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; be2_val<uint32_t> communityId; be2_val<uint32_t> empathyCount; be2_val<uint32_t> commentCount; UNKNOWN(0x1F4); }; CHECK_OFFSET(DownloadedPostData, 0xC008, communityId); CHECK_OFFSET(DownloadedPostData, 0xC00C, empathyCount); CHECK_OFFSET(DownloadedPostData, 0xC010, commentCount); CHECK_SIZE(DownloadedPostData, 0xC208); virt_ptr<DownloadedPostData> DownloadedPostData_Constructor(virt_ptr<DownloadedPostData> self); void DownloadedPostData_Destructor(virt_ptr<DownloadedPostData> self, ghs::DestructorFlags flags); uint32_t DownloadedPostData_GetCommentCount(virt_ptr<const DownloadedPostData> self); uint32_t DownloadedPostData_GetCommunityId(virt_ptr<const DownloadedPostData> self); uint32_t DownloadedPostData_GetEmpathyCount(virt_ptr<const DownloadedPostData> self); virt_ptr<const uint8_t> DownloadedPostData_GetPostId(virt_ptr<const DownloadedPostData> self); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedtopicdata.cpp ================================================ #include "nn_olv.h" #include "nn_olv_downloadedtopicdata.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_olv { virt_ptr<DownloadedTopicData> DownloadedTopicData_Constructor(virt_ptr<DownloadedTopicData> self) { if (!self) { self = virt_cast<DownloadedTopicData *>(ghs::malloc(sizeof(DownloadedTopicData))); if (!self) { return nullptr; } } self->unk1 = 0u; self->communityId = 0u; return self; } uint32_t DownloadedTopicData_GetCommunityId(virt_ptr<const DownloadedTopicData> self) { return self->communityId; } uint32_t DownloadedTopicData_GetUserCount(virt_ptr<const DownloadedTopicData> self) { return 0; } void Library::registerDownloadedTopicDataSymbols() { RegisterFunctionExportName("__ct__Q3_2nn3olv19DownloadedTopicDataFv", DownloadedTopicData_Constructor); RegisterFunctionExportName("GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv", DownloadedTopicData_GetCommunityId); RegisterFunctionExportName("GetUserCount__Q3_2nn3olv19DownloadedTopicDataCFv", DownloadedTopicData_GetUserCount); } } // namespace namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedtopicdata.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct DownloadedTopicData { be2_val<uint32_t> unk1; be2_val<uint32_t> communityId; UNKNOWN(0xFF8); }; CHECK_OFFSET(DownloadedTopicData, 0x00, unk1); CHECK_OFFSET(DownloadedTopicData, 0x04, communityId); CHECK_SIZE(DownloadedTopicData, 0x1000); virt_ptr<DownloadedTopicData> DownloadedTopicData_Constructor(virt_ptr<DownloadedTopicData> self); uint32_t DownloadedTopicData_GetCommunityId(virt_ptr<const DownloadedTopicData> self); uint32_t DownloadedTopicData_GetUserCount(virt_ptr<const DownloadedTopicData> self); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_init.cpp ================================================ #include "nn_olv.h" #include "nn_olv_init.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nn_olv { static bool gInitialised = false; nn::Result Initialize(virt_ptr<InitializeParam> initParam) { decaf_warn_stub(); gInitialised = true; return nn::ResultSuccess; } nn::Result Initialize(virt_ptr<MainAppParam> mainAppParam, virt_ptr<InitializeParam> initParam) { decaf_warn_stub(); gInitialised = true; return nn::ResultSuccess; } nn::Result Finalize() { decaf_warn_stub(); gInitialised = false; return nn::ResultSuccess; } bool IsInitialized() { decaf_warn_stub(); return gInitialised; } void Library::registerInitSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam", static_cast<nn::Result (*)(virt_ptr<InitializeParam>)>(Initialize)); RegisterFunctionExportName("Initialize__Q2_2nn3olvFPQ3_2nn3olv12MainAppParamPCQ3_2nn3olv15InitializeParam", static_cast<nn::Result (*)(virt_ptr<MainAppParam>, virt_ptr<InitializeParam>)>(Initialize)); RegisterFunctionExportName("Finalize__Q2_2nn4bossFv", Finalize); RegisterFunctionExportName("IsInitialized__Q2_2nn3olvFv", IsInitialized); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_init.h ================================================ #pragma once #include "nn_olv_initializeparam.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct MainAppParam { }; UNKNOWN_SIZE(MainAppParam); nn::Result Initialize(virt_ptr<InitializeParam> initParam); nn::Result Initialize(virt_ptr<MainAppParam> mainAppParam, virt_ptr<InitializeParam> initParam); nn::Result Finalize(); bool IsInitialized(); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_initializeparam.cpp ================================================ #include "nn_olv.h" #include "nn_olv_initializeparam.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/olv/nn_olv_result.h" using namespace nn::olv; namespace cafe::nn_olv { constexpr auto MinWorkBufferSize = 0x10000u; virt_ptr<InitializeParam> InitializeParam_Constructor(virt_ptr<InitializeParam> self) { if (!self) { self = virt_cast<InitializeParam *>(ghs::malloc(sizeof(InitializeParam))); if (!self) { return nullptr; } } self->flags = 0u; self->reportTypes = 0x1B7Fu; self->workBuffer = nullptr; self->workBufferSize = 0u; self->sysArgs = nullptr; self->sysArgsSize = 0u; return self; } nn::Result InitializeParam_SetFlags(virt_ptr<InitializeParam> self, uint32_t flags) { self->flags = flags; return ResultSuccess; } nn::Result InitializeParam_SetWork(virt_ptr<InitializeParam> self, virt_ptr<uint8_t> workBuffer, uint32_t workBufferSize) { if (!workBuffer) { return ResultInvalidPointer; } if (workBufferSize < MinWorkBufferSize) { return ResultInvalidSize; } self->workBuffer = workBuffer; self->workBufferSize = workBufferSize; return ResultSuccess; } nn::Result InitializeParam_SetReportTypes(virt_ptr<InitializeParam> self, uint32_t types) { self->reportTypes = types; return ResultSuccess; } nn::Result InitializeParam_SetSysArgs(virt_ptr<InitializeParam> self, virt_ptr<uint8_t> sysArgs, uint32_t sysArgsSize) { if (!sysArgs) { return ResultInvalidPointer; } if (!sysArgsSize) { return ResultInvalidSize; } self->sysArgs = sysArgs; self->sysArgsSize = sysArgsSize; return ResultSuccess; } void Library::registerInitializeParamSymbols() { RegisterFunctionExportName("__ct__Q3_2nn3olv15InitializeParamFv", InitializeParam_Constructor); RegisterFunctionExportName("SetFlags__Q3_2nn3olv15InitializeParamFUi", InitializeParam_SetFlags); RegisterFunctionExportName("SetWork__Q3_2nn3olv15InitializeParamFPUcUi", InitializeParam_SetWork); RegisterFunctionExportName("SetReportTypes__Q3_2nn3olv15InitializeParamFUi", InitializeParam_SetReportTypes); RegisterFunctionExportName("SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", InitializeParam_SetSysArgs); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_initializeparam.h ================================================ #pragma once #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct InitializeParam { be2_val<uint32_t> flags; be2_val<uint32_t> reportTypes; be2_virt_ptr<uint8_t> workBuffer; be2_val<uint32_t> workBufferSize; be2_virt_ptr<uint8_t> sysArgs; be2_val<uint32_t> sysArgsSize; UNKNOWN(0x28); }; CHECK_OFFSET(InitializeParam, 0x00, flags); CHECK_OFFSET(InitializeParam, 0x04, reportTypes); CHECK_OFFSET(InitializeParam, 0x08, workBuffer); CHECK_OFFSET(InitializeParam, 0x0C, workBufferSize); CHECK_OFFSET(InitializeParam, 0x10, sysArgs); CHECK_OFFSET(InitializeParam, 0x14, sysArgsSize); CHECK_SIZE(InitializeParam, 0x40); virt_ptr<InitializeParam> InitializeParam_Constructor(virt_ptr<InitializeParam> self); nn::Result InitializeParam_SetFlags(virt_ptr<InitializeParam> self, uint32_t flags); nn::Result InitializeParam_SetWork(virt_ptr<InitializeParam> self, virt_ptr<uint8_t> workBuffer, uint32_t workBufferSize); nn::Result InitializeParam_SetReportTypes(virt_ptr<InitializeParam> self, uint32_t types); nn::Result InitializeParam_SetSysArgs(virt_ptr<InitializeParam> self, virt_ptr<uint8_t> sysArgs, uint32_t sysArgsSize); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadeddatabase.cpp ================================================ #include "nn_olv.h" #include "nn_olv_uploadeddatabase.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" #include "nn/olv/nn_olv_result.h" using namespace nn::olv; namespace cafe::nn_olv { virt_ptr<ghs::VirtualTable> UploadedDataBase::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> UploadedDataBase::TypeDescriptor = nullptr; virt_ptr<UploadedDataBase> UploadedDataBase_Constructor(virt_ptr<UploadedDataBase> self) { if (!self) { self = virt_cast<UploadedDataBase *>(ghs::malloc(sizeof(UploadedDataBase))); if (!self) { return nullptr; } } self->virtualTable = UploadedDataBase::VirtualTable; self->flags = 0u; self->bodyTextLength = 0u; self->bodyMemoLength = 0u; self->appDataLength = 0u; self->feeling = int8_t { 0 }; self->commonDataUnknown = 0u; self->commonDataLength = 0u; self->postID[0] = char { 0 }; return self; } void UploadedDataBase_Destructor(virt_ptr<UploadedDataBase> self, ghs::DestructorFlags flags) { if (!self) { return; } if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } uint32_t UploadedDataBase_GetAppDataSize(virt_ptr<const UploadedDataBase> self) { if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasAppData)) { return 0; } return self->appDataLength; } nn::Result UploadedDataBase_GetAppData(virt_ptr<const UploadedDataBase> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize) { if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasAppData)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->appDataLength); std::memcpy(buffer.get(), virt_addrof(self->appData).get(), length); if (outDataSize) { *outDataSize = length; } return ResultSuccess; } nn::Result UploadedDataBase_GetBodyText(virt_ptr<const UploadedDataBase> self, virt_ptr<char16_t> buffer, uint32_t bufferSize) { if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasBodyText)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->bodyTextLength); std::memcpy(buffer.get(), virt_addrof(self->bodyText).get(), length * sizeof(char16_t)); if (length < bufferSize) { buffer[length] = char16_t { 0 }; } return ResultSuccess; } nn::Result UploadedDataBase_GetBodyMemo(virt_ptr<const UploadedDataBase> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outMemoSize, uint32_t bufferSize) { if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasBodyMemo)) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->bodyMemoLength); std::memcpy(buffer.get(), virt_addrof(self->bodyMemo).get(), length); if (outMemoSize) { *outMemoSize = length; } return ResultSuccess; } nn::Result UploadedDataBase_GetCommonData(virt_ptr<const UploadedDataBase> self, virt_ptr<uint32_t> unk, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize) { if (!self->commonDataLength) { return ResultNoData; } if (!buffer) { return ResultInvalidPointer; } if (!bufferSize) { return ResultInvalidSize; } auto length = std::min<uint32_t>(bufferSize, self->commonDataLength); std::memcpy(buffer.get(), virt_addrof(self->commonData).get(), length); if (unk) { *unk = self->commonDataUnknown; } if (outDataSize) { *outDataSize = length; } return ResultSuccess; } int32_t UploadedDataBase_GetFeeling(virt_ptr<const UploadedDataBase> self) { return self->feeling; } virt_ptr<const char> UploadedDataBase_GetPostId(virt_ptr<const UploadedDataBase> self) { return virt_addrof(self->postID); } bool UploadedDataBase_TestFlags(virt_ptr<const UploadedDataBase> self, uint32_t flag) { return !!(self->flags & flag); } void Library::registerUploadedDataBaseSymbols() { RegisterFunctionExportName("__dt__Q3_2nn3olv16UploadedDataBaseFv", UploadedDataBase_Destructor); RegisterFunctionExportName("GetCommonData__Q3_2nn3olv16UploadedDataBaseFPUiPUcT1Ui", UploadedDataBase_GetCommonData); RegisterTypeInfo( UploadedDataBase, "nn::olv::UploadedDataBase", { "__pure_virtual_called", }, {}); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadeddatabase.h ================================================ #pragma once #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct UploadedDataBase { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; enum Flags { Empty = 0, HasBodyText = 1 << 0, HasBodyMemo = 1 << 1, HasAppData = 1 << 2, }; be2_val<uint32_t> flags; be2_array<char, 32> postID; be2_array<char16_t, 256> bodyText; be2_val<uint32_t> bodyTextLength; be2_array<uint8_t, 0xA000> bodyMemo; be2_val<uint32_t> bodyMemoLength; be2_array<uint8_t, 1024> appData; be2_val<uint32_t> appDataLength; be2_val<int8_t> feeling; UNKNOWN(3); be2_val<uint32_t> commonDataUnknown; be2_val<uint32_t> commonDataLength; be2_array<uint8_t, 0x1000> commonData; UNKNOWN(0x9C8); be2_virt_ptr<ghs::VirtualTable> virtualTable; }; CHECK_OFFSET(UploadedDataBase, 0x00, flags); CHECK_OFFSET(UploadedDataBase, 0x04, postID); CHECK_OFFSET(UploadedDataBase, 0x24, bodyText); CHECK_OFFSET(UploadedDataBase, 0x224, bodyTextLength); CHECK_OFFSET(UploadedDataBase, 0x228, bodyMemo); CHECK_OFFSET(UploadedDataBase, 0xA228, bodyMemoLength); CHECK_OFFSET(UploadedDataBase, 0xA22C, appData); CHECK_OFFSET(UploadedDataBase, 0xA62C, appDataLength); CHECK_OFFSET(UploadedDataBase, 0xA630, feeling); CHECK_OFFSET(UploadedDataBase, 0xA634, commonDataUnknown); CHECK_OFFSET(UploadedDataBase, 0xA638, commonDataLength); CHECK_OFFSET(UploadedDataBase, 0xA63C, commonData); CHECK_OFFSET(UploadedDataBase, 0xC004, virtualTable); CHECK_SIZE(UploadedDataBase, 0xC008); virt_ptr<UploadedDataBase> UploadedDataBase_Constructor(virt_ptr<UploadedDataBase> self); void UploadedDataBase_Destructor(virt_ptr<UploadedDataBase> self, ghs::DestructorFlags flags); uint32_t UploadedDataBase_GetAppDataSize(virt_ptr<const UploadedDataBase> self); nn::Result UploadedDataBase_GetAppData(virt_ptr<const UploadedDataBase> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize); nn::Result UploadedDataBase_GetBodyText(virt_ptr<const UploadedDataBase> self, virt_ptr<char16_t> buffer, uint32_t bufferSize); nn::Result UploadedDataBase_GetBodyMemo(virt_ptr<const UploadedDataBase> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outMemoSize, uint32_t bufferSize); nn::Result UploadedDataBase_GetCommonData(virt_ptr<const UploadedDataBase> self, virt_ptr<uint32_t> unk, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize); int32_t UploadedDataBase_GetFeeling(virt_ptr<const UploadedDataBase> self); virt_ptr<const char> UploadedDataBase_GetPostId(virt_ptr<const UploadedDataBase> self); bool UploadedDataBase_TestFlags(virt_ptr<const UploadedDataBase> self, uint32_t flag); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadedpostdata.cpp ================================================ #include "nn_olv.h" #include "nn_olv_uploadedpostdata.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_olv { virt_ptr<ghs::VirtualTable> UploadedPostData::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> UploadedPostData::TypeDescriptor = nullptr; virt_ptr<UploadedPostData> UploadedPostData_Constructor(virt_ptr<UploadedPostData> self) { if (!self) { self = virt_cast<UploadedPostData *>(ghs::malloc(sizeof(UploadedPostData))); if (!self) { return nullptr; } } UploadedDataBase_Constructor(virt_cast<UploadedDataBase *>(self)); self->virtualTable = UploadedPostData::VirtualTable; return self; } void UploadedPostData_Destructor(virt_ptr<UploadedPostData> self, ghs::DestructorFlags flags) { if (!self) { return; } if (flags & ghs::DestructorFlags::FreeMemory) { ghs::free(self); } } uint32_t UploadedPostData_GetAppDataSize(virt_ptr<const UploadedPostData> self) { return UploadedDataBase_GetAppDataSize(virt_cast<const UploadedDataBase *>(self)); } nn::Result UploadedPostData_GetAppData(virt_ptr<const UploadedPostData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize) { return UploadedDataBase_GetAppData(virt_cast<const UploadedDataBase *>(self), buffer, outDataSize, bufferSize); } nn::Result UploadedPostData_GetBodyText(virt_ptr<const UploadedPostData> self, virt_ptr<char16_t> buffer, uint32_t bufferSize) { return UploadedDataBase_GetBodyText(virt_cast<const UploadedDataBase *>(self), buffer, bufferSize); } nn::Result UploadedPostData_GetBodyMemo(virt_ptr<const UploadedPostData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outMemoSize, uint32_t bufferSize) { return UploadedDataBase_GetBodyMemo(virt_cast<const UploadedDataBase *>(self), buffer, outMemoSize, bufferSize); } int32_t UploadedPostData_GetFeeling(virt_ptr<const UploadedPostData> self) { return UploadedDataBase_GetFeeling(virt_cast<const UploadedDataBase *>(self)); } virt_ptr<const char> UploadedPostData_GetPostId(virt_ptr<const UploadedPostData> self) { return UploadedDataBase_GetPostId(virt_cast<const UploadedDataBase *>(self)); } bool UploadedPostData_TestFlags(virt_ptr<const UploadedPostData> self, uint32_t flag) { return UploadedDataBase_TestFlags(virt_cast<const UploadedDataBase *>(self), flag); } void Library::registerUploadedPostDataSymbols() { RegisterFunctionExportName("__ct__Q3_2nn3olv16UploadedPostDataFv", UploadedPostData_Constructor); RegisterFunctionExportName("__dt__Q3_2nn3olv16UploadedPostDataFv", UploadedPostData_Destructor); RegisterFunctionExportName("GetAppDataSize__Q3_2nn3olv16UploadedPostDataCFv", UploadedPostData_GetAppDataSize); RegisterFunctionExportName("GetAppData__Q3_2nn3olv16UploadedPostDataCFPUcPUiUi", UploadedPostData_GetAppData); RegisterFunctionExportName("GetBodyMemo__Q3_2nn3olv16UploadedPostDataCFPUcPUiUi", UploadedPostData_GetBodyMemo); RegisterFunctionExportName("GetBodyText__Q3_2nn3olv16UploadedPostDataCFPwUi", UploadedPostData_GetBodyText); RegisterFunctionExportName("GetFeeling__Q3_2nn3olv16UploadedPostDataCFv", UploadedPostData_GetFeeling); RegisterFunctionExportName("GetPostId__Q3_2nn3olv16UploadedPostDataCFv", UploadedPostData_GetPostId); RegisterFunctionExportName("TestFlags__Q3_2nn3olv16UploadedPostDataCFUi", UploadedPostData_TestFlags); RegisterTypeInfo( UploadedPostData, "nn::olv::UploadedPostData", { "__dt__Q3_2nn3olv16UploadedPostDataFv", }, { "nn::olv::UploadedDataBase", }); } } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadedpostdata.h ================================================ #pragma once #include "nn_olv_uploadeddatabase.h" #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_olv { struct UploadedPostData : public UploadedDataBase { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; UNKNOWN(0x200); }; CHECK_SIZE(UploadedPostData, 0xC208); virt_ptr<UploadedPostData> UploadedPostData_Constructor(virt_ptr<UploadedPostData> self); void UploadedPostData_Destructor(virt_ptr<UploadedPostData> self, ghs::DestructorFlags flags); uint32_t UploadedPostData_GetAppDataSize(virt_ptr<const UploadedPostData> self); nn::Result UploadedPostData_GetAppData(virt_ptr<const UploadedPostData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outDataSize, uint32_t bufferSize); nn::Result UploadedPostData_GetBodyText(virt_ptr<const UploadedPostData> self, virt_ptr<char16_t> buffer, uint32_t bufferSize); nn::Result UploadedPostData_GetBodyMemo(virt_ptr<const UploadedPostData> self, virt_ptr<uint8_t> buffer, virt_ptr<uint32_t> outMemoSize, uint32_t bufferSize); int32_t UploadedPostData_GetFeeling(virt_ptr<const UploadedPostData> self); virt_ptr<const char> UploadedPostData_GetPostId(virt_ptr<const UploadedPostData> self); bool UploadedPostData_TestFlags(virt_ptr<const UploadedPostData> self, uint32_t flag); } // namespace cafe::nn_olv ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm.cpp ================================================ #include "nn_pdm.h" namespace cafe::nn_pdm { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); registerCosServiceSymbols(); } } // namespace cafe::nn_pdm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_pdm { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_pdm, "nn_pdm.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); void registerCosServiceSymbols(); }; } // namespace cafe::nn_pdm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_client.cpp ================================================ #include "nn_pdm.h" #include "nn_pdm_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/pdm/nn_pdm_result.h" using namespace cafe::coreinit; using namespace nn::pdm; namespace cafe::nn_pdm { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(64) be2_array<uint8_t, 4096> allocatorMemory; be2_struct<OSMutex> mutex; be2_val<uint32_t> refCount = 0u; be2_struct<nn::ipc::Client> client; be2_struct<nn::ipc::BufferAllocator> allocator; }; static virt_ptr<StaticClientData> sClientData = nullptr; nn::Result PDMInitialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->client.initialise(make_stack_string("/dev/pdm")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return ResultSuccess; } void PDMFinalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } namespace internal { virt_ptr<nn::ipc::Client> getClient() { return virt_addrof(sClientData->client); } virt_ptr<nn::ipc::BufferAllocator> getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExport(PDMInitialize); RegisterFunctionExport(PDMFinalize); RegisterFunctionExportName("Initialize__Q2_2nn3pdmFv", PDMInitialize); RegisterFunctionExportName("Finalize__Q2_2nn3pdmFv", PDMFinalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_pdm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_client.h ================================================ #pragma once #include "cafe/nn/cafe_nn_ipc_client.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" #include "nn/nn_result.h" namespace cafe::nn_pdm { nn::Result PDMInitialize(); void PDMFinalize(); namespace internal { virt_ptr<nn::ipc::Client> getClient(); virt_ptr<nn::ipc::BufferAllocator> getAllocator(); } // namespace internal } // namespace cafe::nn_pdm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_cosservice.cpp ================================================ #include "nn_pdm.h" #include "nn_pdm_client.h" #include "nn_pdm_cosservice.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/pdm/nn_pdm_result.h" #include "nn/pdm/nn_pdm_cosservice.h" using namespace nn::ipc; using namespace nn::pdm; namespace cafe::nn_pdm { nn::Result PDMGetPlayDiaryMaxLength(virt_ptr<uint32_t> outMaxLength) { auto command = ClientCommand<services::CosService::GetPlayDiaryMaxLength> { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.failed()) { return result; } auto maxLength = uint32_t { 0 }; result = command.readResponse(maxLength); if (result.ok()) { *outMaxLength = maxLength; } return result; } nn::Result PDMGetPlayStatsMaxLength(virt_ptr<uint32_t> outMaxLength) { auto command = ClientCommand<services::CosService::GetPlayStatsMaxLength> { internal::getAllocator() }; auto result = internal::getClient()->sendSyncRequest(command); if (result.failed()) { return result; } auto maxLength = uint32_t { 0 }; result = command.readResponse(maxLength); if (result.ok()) { *outMaxLength = maxLength; } return result; } void Library::registerCosServiceSymbols() { RegisterFunctionExportName("GetPlayDiaryMaxLength__Q2_2nn3pdmFPi", PDMGetPlayDiaryMaxLength); RegisterFunctionExportName("GetPlayStatsMaxLength__Q2_2nn3pdmFPi", PDMGetPlayStatsMaxLength); } } // namespace cafe::nn_pdm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_cosservice.h ================================================ #pragma once #include "nn/nn_result.h" #include "nn/pdm/nn_pdm_cosservice.h" #include <libcpu/be2_struct.h> namespace cafe::nn_pdm { nn::Result PDMGetPlayDiaryMaxLength(virt_ptr<uint32_t> outMaxLength); nn::Result PDMGetPlayStatsMaxLength(virt_ptr<uint32_t> outMaxLength); } // namespace cafe::nn_pdm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_save/nn_save.cpp ================================================ #include "nn_save.h" namespace cafe::nn_save { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerLibraryDependency("coreinit"); registerLibraryDependency("nn_act"); registerLibraryDependency("nn_acp"); registerCmdSymbols(); registerPathSymbols(); } } // namespace cafe::nn_save ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_save/nn_save.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_save { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_save, "nn_save.rpl") { } protected: virtual void registerSymbols() override; private: void registerCmdSymbols(); void registerPathSymbols(); }; } // namespace cafe::nn_save ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_save/nn_save_cmd.cpp ================================================ #include "nn_save.h" #include "nn_save_cmd.h" #include "nn_save_path.h" #include "cafe/libraries/coreinit/coreinit_fs_cmd.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/cafe_stackobject.h" namespace cafe::nn_save { SaveStatus SAVEChangeGroupAndOthersMode(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, uint32_t mode, FSErrorFlag errorMask) { // TODO: SAVEChangeGroupAndOthersMode decaf_warn_stub(); return SaveStatus::OK; } SaveStatus SAVEFlushQuota(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, FSErrorFlag errorMask) { auto fsPath = internal::getSaveDirectory(account); return FSFlushQuota(client, block, make_stack_string(fsPath.path()), errorMask); } SaveStatus SAVEFlushQuotaAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSaveDirectory(account); return FSFlushQuotaAsync(client, block, make_stack_string(fsPath.path()), errorMask, asyncData); } SaveStatus SAVEGetFreeSpaceSize(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<uint64_t> freeSpace, FSErrorFlag errorMask) { auto fsPath = internal::getSaveDirectory(account); return FSGetFreeSpaceSize(client, block, make_stack_string(fsPath.path()), freeSpace, errorMask); } SaveStatus SAVEGetFreeSpaceSizeAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<uint64_t> freeSpace, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSaveDirectory(account); return FSGetFreeSpaceSizeAsync(client, block, make_stack_string(fsPath.path()), freeSpace, errorMask, asyncData); } SaveStatus SAVEGetStat(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask) { auto fsPath = internal::getSavePath(account, path.get()); return FSGetStat(client, block, make_stack_string(fsPath.path()), stat, errorMask); } SaveStatus SAVEGetStatAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSavePath(account, path.get()); return FSGetStatAsync(client, block, make_stack_string(fsPath.path()), stat, errorMask, asyncData); } SaveStatus SAVEGetStatOtherApplication(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint64_t titleId, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask) { auto fsPath = internal::getTitleSavePath(titleId, account, path.get()); return FSGetStat(client, block, make_stack_string(fsPath.path()), stat, errorMask); } SaveStatus SAVEGetStatOtherApplicationAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint64_t titleId, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getTitleSavePath(titleId, account, path.get()); return FSGetStatAsync(client, block, make_stack_string(fsPath.path()), stat, errorMask, asyncData); } SaveStatus SAVEMakeDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask) { auto fsPath = internal::getSavePath(account, path.get()); return FSMakeDir(client, block, make_stack_string(fsPath.path()), errorMask); } SaveStatus SAVEMakeDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSavePath(account, path.get()); return FSMakeDirAsync(client, block, make_stack_string(fsPath.path()), errorMask, asyncData); } SaveStatus SAVEOpenDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSDirHandle> handle, FSErrorFlag errorMask) { auto fsPath = internal::getSavePath(account, path.get()); return FSOpenDir(client, block, make_stack_string(fsPath.path()), handle, errorMask); } SaveStatus SAVEOpenDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSDirHandle> handle, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSavePath(account, path.get()); return FSOpenDirAsync(client, block, make_stack_string(fsPath.path()), handle, errorMask, asyncData); } SaveStatus SAVEOpenFile(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> handle, FSErrorFlag errorMask) { auto fsPath = internal::getSavePath(account, path.get()); return FSOpenFile(client, block, make_stack_string(fsPath.path()), mode, handle, errorMask); } SaveStatus SAVEOpenFileAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> handle, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSavePath(account, path.get()); return FSOpenFileAsync(client, block, make_stack_string(fsPath.path()), mode, handle, errorMask, asyncData); } SaveStatus SAVERemove(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask) { auto fsPath = internal::getSavePath(account, path.get()); return FSRemove(client, block, make_stack_string(fsPath.path()), errorMask); } SaveStatus SAVERemoveAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto fsPath = internal::getSavePath(account, path.get()); return FSRemoveAsync(client, block, make_stack_string(fsPath.path()), errorMask, asyncData); } SaveStatus SAVERename(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> src, virt_ptr<const char> dst, FSErrorFlag errorMask) { auto srcPath = internal::getSavePath(account, src.get()); auto dstPath = internal::getSavePath(account, dst.get()); return FSRename(client, block, make_stack_string(srcPath.path()), make_stack_string(dstPath.path()), errorMask); } SaveStatus SAVERenameAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> src, virt_ptr<const char> dst, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData) { auto srcPath = internal::getSavePath(account, src.get()); auto dstPath = internal::getSavePath(account, dst.get()); return FSRenameAsync(client, block, make_stack_string(srcPath.path()), make_stack_string(dstPath.path()), errorMask, asyncData); } void Library::registerCmdSymbols() { RegisterFunctionExport(SAVEChangeGroupAndOthersMode); RegisterFunctionExport(SAVEFlushQuotaAsync); RegisterFunctionExport(SAVEFlushQuota); RegisterFunctionExport(SAVEGetFreeSpaceSizeAsync); RegisterFunctionExport(SAVEGetFreeSpaceSize); RegisterFunctionExport(SAVEGetStatAsync); RegisterFunctionExport(SAVEGetStat); RegisterFunctionExport(SAVEGetStatOtherApplication); RegisterFunctionExport(SAVEGetStatOtherApplicationAsync); RegisterFunctionExport(SAVEMakeDir); RegisterFunctionExport(SAVEMakeDirAsync); RegisterFunctionExport(SAVEOpenDir); RegisterFunctionExport(SAVEOpenDirAsync); RegisterFunctionExport(SAVEOpenFile); RegisterFunctionExport(SAVEOpenFileAsync); RegisterFunctionExport(SAVERemoveAsync); RegisterFunctionExport(SAVERemove); RegisterFunctionExport(SAVERenameAsync); RegisterFunctionExport(SAVERename); } } // namespace cafe::nn_save ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_save/nn_save_cmd.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_fs.h" #include "nn_save_path.h" namespace cafe::nn_save { using coreinit::FSAsyncData; using coreinit::FSClient; using coreinit::FSCmdBlock; using coreinit::FSDirHandle; using coreinit::FSErrorFlag; using coreinit::FSFileHandle; using coreinit::FSStat; SaveStatus SAVEChangeGroupAndOthersMode(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, uint32_t mode, FSErrorFlag errorMask); SaveStatus SAVEFlushQuota(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, FSErrorFlag errorMask); SaveStatus SAVEFlushQuotaAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVEGetFreeSpaceSize(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<uint64_t> freeSpace, FSErrorFlag errorMask); SaveStatus SAVEGetFreeSpaceSizeAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<uint64_t> freeSpace, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVEGetStat(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask); SaveStatus SAVEGetStatAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVEGetStatOtherApplication(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint64_t titleId, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask); SaveStatus SAVEGetStatOtherApplicationAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint64_t titleId, uint8_t account, virt_ptr<const char> path, virt_ptr<FSStat> stat, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVEMakeDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask); SaveStatus SAVEMakeDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVEOpenDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSDirHandle> handle, FSErrorFlag errorMask); SaveStatus SAVEOpenDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<FSDirHandle> handle, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVEOpenFile(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> handle, FSErrorFlag errorMask); SaveStatus SAVEOpenFileAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> handle, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVERemove(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask); SaveStatus SAVERemoveAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); SaveStatus SAVERename(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> src, virt_ptr<const char> dst, FSErrorFlag errorMask); SaveStatus SAVERenameAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, uint8_t account, virt_ptr<const char> src, virt_ptr<const char> dst, FSErrorFlag errorMask, virt_ptr<FSAsyncData> asyncData); } // namespace cafe::nn_save ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_save/nn_save_path.cpp ================================================ #include "nn_save.h" #include "nn_save_path.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_fs_client.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/libraries/coreinit/coreinit_osreport.h" #include "cafe/libraries/nn_act/nn_act_client.h" #include "cafe/libraries/nn_act/nn_act_clientstandardservice.h" #include "cafe/libraries/nn_acp/nn_acp_client.h" #include "cafe/libraries/nn_acp/nn_acp_saveservice.h" #include <fmt/core.h> using namespace cafe::coreinit; using namespace nn::act; using namespace nn::acp; namespace cafe::nn_save { struct StaticSaveData { be2_val<bool> initialised; be2_struct<OSMutex> mutex; be2_struct<FSClient> fsClient; be2_struct<FSCmdBlock> fsCmdBlock; be2_array<uint32_t, NumSlots> persistentIdCache; }; static virt_ptr<StaticSaveData> sSaveData = nullptr; SaveStatus SAVEInit() { if (sSaveData->initialised) { return SaveStatus::OK; } OSInitMutex(virt_addrof(sSaveData->mutex)); nn_act::Initialize(); nn_acp::ACPInitialize(); FSAddClient(virt_addrof(sSaveData->fsClient), FSErrorFlag::None); FSInitCmdBlock(virt_addrof(sSaveData->fsCmdBlock)); for (auto i = SlotNo { 1 }; i <= NumSlots; ++i) { sSaveData->persistentIdCache[i - 1] = nn_act::GetPersistentIdEx(i); } // Mount external storage if it is required if (OSGetUPID() == kernel::UniqueProcessId::Game) { auto externalStorageRequired = StackObject<int32_t> { }; if (nn_acp::ACPIsExternalStorageRequired(externalStorageRequired) == nn_acp::ACPResult::Success && *externalStorageRequired) { nn_acp::ACPMountExternalStorage(); } } nn_acp::ACPMountSaveDir(); nn_acp::ACPRepairSaveMetaDir(); sSaveData->initialised = true; return SaveStatus::OK; } void SAVEShutdown() { if (sSaveData->initialised) { FSDelClient(virt_addrof(sSaveData->fsClient), FSErrorFlag::None); nn_acp::ACPFinalize(); nn_act::Finalize(); sSaveData->initialised = false; } } SaveStatus SAVEInitSaveDir(uint8_t slotNo) { if (!sSaveData->initialised) { coreinit::internal::OSPanic("save.cpp", 630, "SAVE library is not initialized. Call SAVEInit() prior to this function.\n"); } auto persistentId = uint32_t { 0 }; if (!internal::getPersistentId(slotNo, persistentId)) { return SaveStatus::NotFound; } auto result = nn_acp::ACPCreateSaveDir(persistentId, ACPDeviceType::Unknown1); // TODO: Update ACPGetApplicationBox return internal::translateResult(result); } SaveStatus SAVEGetSharedDataTitlePath(uint64_t titleID, virt_ptr<const char> dir, virt_ptr<char> buffer, uint32_t bufferSize) { if (!sSaveData->initialised) { coreinit::internal::OSPanic("save.cpp", 2543, "SAVE library is not initialized. Call SAVEInit() prior to this function.\n"); } auto externalStorageRequired = StackObject<int32_t> { }; auto storage = "storage_mlc01"; if (nn_acp::ACPIsExternalStorageRequired(externalStorageRequired) == nn_acp::ACPResult::Success) { if (*externalStorageRequired) { storage = "storage_hfiomlc01"; } } else { return SaveStatus::FatalError; } auto titleLo = static_cast<uint32_t>(titleID & 0xffffffff); auto titleHi = static_cast<uint32_t>(titleID >> 32); auto result = std::snprintf(buffer.get(), bufferSize, "/vol/%s/sys/title/%08x/%08x/content/%s", storage, titleHi, titleLo, dir.get()); if (result < 0 || static_cast<uint32_t>(result) >= bufferSize) { return SaveStatus::FatalError; } return SaveStatus::OK; } SaveStatus SAVEGetSharedSaveDataPath(uint64_t titleID, virt_ptr<const char> dir, virt_ptr<char> buffer, uint32_t bufferSize) { if (!sSaveData->initialised) { coreinit::internal::OSPanic("save.cpp", 2543, "SAVE library is not initialized. Call SAVEInit() prior to this function.\n"); } auto externalStorageRequired = StackObject<int32_t> { }; auto storage = "storage_mlc01"; if (nn_acp::ACPIsExternalStorageRequired(externalStorageRequired) == nn_acp::ACPResult::Success) { if (*externalStorageRequired) { storage = "storage_hfiomlc01"; } } else { return SaveStatus::FatalError; } auto titleLo = static_cast<uint32_t>(titleID & 0xffffffff); auto titleHi = static_cast<uint32_t>(titleID >> 32); auto result = std::snprintf(buffer.get(), bufferSize, "/vol/%s/usr/save/%08x/%08x/user/common/%s", storage, titleHi, titleLo, dir.get()); if (result < 0 || static_cast<uint32_t>(result) >= bufferSize) { return SaveStatus::FatalError; } return SaveStatus::OK; } namespace internal { SaveStatus translateResult(nn_acp::ACPResult result) { // TODO: Reverse this completely if (result == nn_acp::ACPResult::Success) { return SaveStatus::OK; } if (result == nn_acp::ACPResult::NotFound || result == nn_acp::ACPResult::DirNotFound || result == nn_acp::ACPResult::FileNotFound){ return SaveStatus::NotFound; } if (result == nn_acp::ACPResult::DeviceFull) { return SaveStatus::StorageFull; } return SaveStatus::MediaError; } bool getPersistentId(SlotNo slot, uint32_t &outPersistentId) { if (slot == SystemSlot) { outPersistentId = 0; return true; } else if (slot >= 1 && slot <= NumSlots) { outPersistentId = sSaveData->persistentIdCache[slot - 1]; return true; } return false; } vfs::Path getSaveDirectory(SlotNo slot) { auto persistentId = uint32_t { 0 }; getPersistentId(slot, persistentId); if (persistentId == 0) { return fmt::format("/vol/save/common"); } else { return fmt::format("/vol/save/{:08X}", persistentId); } } vfs::Path getSavePath(SlotNo slot, std::string_view path) { return getSaveDirectory(slot) / path; } vfs::Path getTitleSaveDirectory(uint64_t title, SlotNo slot) { auto titleLo = static_cast<uint32_t>(title & 0xffffffff); auto titleHi = static_cast<uint32_t>(title >> 32); auto persistentId = uint32_t { 0 }; getPersistentId(slot, persistentId); if (persistentId == 0) { return fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/common", titleHi, titleLo); } else { return fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/{:08X}", titleHi, titleLo, persistentId); } } vfs::Path getTitleSavePath(uint64_t title, nn_act::SlotNo slot, std::string_view path) { return getTitleSaveDirectory(title, slot) / path; } } // namespace internal void Library::registerPathSymbols() { RegisterFunctionExport(SAVEInit); RegisterFunctionExport(SAVEShutdown); RegisterFunctionExport(SAVEInitSaveDir); RegisterFunctionExport(SAVEGetSharedDataTitlePath); RegisterFunctionExport(SAVEGetSharedSaveDataPath); RegisterDataInternal(sSaveData); } } // namespace cafe::nn_save ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_save/nn_save_path.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_fs.h" #include "cafe/libraries/nn_acp/nn_acp_acpresult.h" #include "nn/nn_result.h" #include "nn/act/nn_act_types.h" #include "vfs/vfs_path.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::nn_save { using SaveStatus = coreinit::FSStatus; SaveStatus SAVEInit(); void SAVEShutdown(); SaveStatus SAVEInitSaveDir(uint8_t userID); SaveStatus SAVEGetSharedDataTitlePath(uint64_t titleID, virt_ptr<const char> dir, virt_ptr<char> buffer, uint32_t bufferSize); SaveStatus SAVEGetSharedSaveDataPath(uint64_t titleID, virt_ptr<const char> dir, virt_ptr<char> buffer, uint32_t bufferSize); namespace internal { SaveStatus translateResult(nn_acp::ACPResult result); bool getPersistentId(nn::act::SlotNo slot, uint32_t &outPersistentId); vfs::Path getSaveDirectory(nn::act::SlotNo slot); vfs::Path getSavePath(nn::act::SlotNo slot, std::string_view path); vfs::Path getTitleSaveDirectory(uint64_t title, nn::act::SlotNo slot); vfs::Path getTitleSavePath(uint64_t title, nn::act::SlotNo slot, std::string_view path); } // namespace internal } // namespace cafe::nn_save ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_sl/nn_sl.cpp ================================================ #include "nn_sl.h" namespace cafe::nn_sl { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerDrcTransferrerSymbols(); registerLibSymbols(); } } // namespace cafe::nn_sl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_sl/nn_sl.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_sl { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_sl, "nn_sl.rpl") { } protected: virtual void registerSymbols() override; private: void registerDrcTransferrerSymbols(); void registerLibSymbols(); }; } // namespace cafe::nn_sl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_drctransferrer.cpp ================================================ #include "nn_sl.h" #include "nn_sl_drctransferrer.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/ghs/cafe_ghs_malloc.h" namespace cafe::nn_sl { virt_ptr<ghs::TypeIDStorage> ITransferrer::TypeID = nullptr; virt_ptr<ghs::TypeDescriptor> ITransferrer::TypeDescriptor = nullptr; virt_ptr<ghs::VirtualTable> DrcTransferrer::VirtualTable = nullptr; virt_ptr<ghs::TypeDescriptor> DrcTransferrer::TypeDescriptor = nullptr; virt_ptr<DrcTransferrer> DrcTransferrer_Constructor(virt_ptr<DrcTransferrer> self) { if (!self) { self = virt_cast<DrcTransferrer *>(ghs::malloc(sizeof(DrcTransferrer))); if (!self) { return nullptr; } } self->virtualTable = DrcTransferrer::VirtualTable; return self; } void DrcTransferrer_Destructor(virt_ptr<DrcTransferrer> self, ghs::DestructorFlags flags) { if (self && (flags & ghs::DestructorFlags::FreeMemory)) { ghs::free(self); } } nn::Result DrcTransferrer_Transfer1(virt_ptr<DrcTransferrer> self, virt_ptr<void> arg1, uint32_t arg2, uint32_t arg3, bool arg4) { return DrcTransferrer_Transfer2(self, arg1, arg2, arg3, arg4 ? 3 : 1); } nn::Result DrcTransferrer_Transfer2(virt_ptr<DrcTransferrer> self, virt_ptr<void> arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4) { decaf_warn_stub(); return { 0xA183ED80 }; } nn::Result DrcTransferrer_AbortTransfer(virt_ptr<DrcTransferrer> self) { decaf_warn_stub(); return { 0xA183ED80 }; } nn::Result DrcTransferrer_UnkFunc4(virt_ptr<DrcTransferrer> self) { // Not a stub, this is actual implementation! return { 0xA183E800 }; } nn::Result DrcTransferrer_DisplayNotification(virt_ptr<DrcTransferrer> self, uint32_t arg2, uint32_t arg3, uint32_t arg4) { decaf_warn_stub(); return { 0xA183E800 }; } void Library::registerDrcTransferrerSymbols() { RegisterFunctionInternalName("__dt__Q5_2nn2sl4core6detail14DrcTransferrerFv", DrcTransferrer_Destructor); RegisterFunctionInternalName("DrcTransferrer_Transfer1", DrcTransferrer_Transfer1); RegisterFunctionInternalName("DrcTransferrer_Transfer2", DrcTransferrer_Transfer2); RegisterFunctionInternalName("DrcTransferrer_AbortTransfer", DrcTransferrer_AbortTransfer); RegisterFunctionInternalName("DrcTransferrer_UnkFunc4", DrcTransferrer_UnkFunc4); RegisterFunctionInternalName("DrcTransferrer_DisplayNotification", DrcTransferrer_DisplayNotification); RegisterDataExportName("__TID_Q3_2nn2sl12ITransferrer", ITransferrer::TypeID); RegisterTypeInfo( ITransferrer, "nn::sl::ITransferrer", {}, {}, "__TID_Q3_2nn2sl12ITransferrer"); RegisterTypeInfo( DrcTransferrer, "nn::sl::core::detail::DrcTransferrer", { "__dt__Q5_2nn2sl4core6detail14DrcTransferrerFv", "DrcTransferrer_Transfer1", "DrcTransferrer_Transfer2", "DrcTransferrer_AbortTransfer", "DrcTransferrer_UnkFunc4", "DrcTransferrer_DisplayNotification", }, { "nn::sl::ITransferrer", }); } } // namespace cafe::nn_sl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_drctransferrer.h ================================================ #pragma once #include "cafe/libraries/ghs/cafe_ghs_typeinfo.h" #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_sl { struct ITransferrer { static virt_ptr<ghs::TypeIDStorage> TypeID; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; be2_virt_ptr<ghs::VirtualTable> virtualTable; }; CHECK_OFFSET(ITransferrer, 0x00, virtualTable); CHECK_SIZE(ITransferrer, 0x4); struct DrcTransferrer : public ITransferrer { static virt_ptr<ghs::VirtualTable> VirtualTable; static virt_ptr<ghs::TypeDescriptor> TypeDescriptor; }; CHECK_SIZE(DrcTransferrer, 0x4); virt_ptr<DrcTransferrer> DrcTransferrer_Constructor(virt_ptr<DrcTransferrer> self); void DrcTransferrer_Destructor(virt_ptr<DrcTransferrer> self, ghs::DestructorFlags flags); nn::Result DrcTransferrer_Transfer1(virt_ptr<DrcTransferrer> self, virt_ptr<void> arg1, uint32_t arg2, uint32_t arg3, bool arg4); nn::Result DrcTransferrer_Transfer2(virt_ptr<DrcTransferrer> self, virt_ptr<void> arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4); nn::Result DrcTransferrer_AbortTransfer(virt_ptr<DrcTransferrer> self); nn::Result DrcTransferrer_UnkFunc4(virt_ptr<DrcTransferrer> self); } // namespace cafe::nn_sl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_lib.cpp ================================================ #include "nn_sl.h" #include "nn_sl_lib.h" #include "nn_sl_drctransferrer.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/coreinit/coreinit_cosreport.h" namespace cafe::nn_sl { struct StaticLibData { StaticLibData() { DrcTransferrer_Constructor(virt_addrof(drcTransferrer)); } be2_val<BOOL> initialised; be2_struct<DrcTransferrer> drcTransferrer; be2_val<AllocFn> allocFn; be2_val<FreeFn> freeFn; be2_virt_ptr<ITransferrer> transferrer; }; static virt_ptr<StaticLibData> sLibData = nullptr; nn::Result Initialize(AllocFn allocFn, FreeFn freeFn) { return Initialize(allocFn, freeFn, virt_cast<ITransferrer *>(GetDrcTransferrer())); } nn::Result Initialize(AllocFn allocFn, FreeFn freeFn, virt_ptr<ITransferrer> transferrer) { if (!sLibData->initialised) { sLibData->allocFn = allocFn; sLibData->freeFn = freeFn; sLibData->transferrer = transferrer; if (!transferrer) { coreinit::internal::COSWarn(coreinit::COSReportModule::Unknown1, "NULL transferrer is set!"); } // nn::sl::FileSystemAccessor::Initialize(nn::sl::GetFileSystemAccessor()) sLibData->initialised = TRUE; } return nn::ResultSuccess; } nn::Result Finalize() { if (sLibData->initialised) { // nn::sl::FileSystemAccessor::Finalize(nn::sl::GetFileSystemAccessor()) sLibData->initialised = FALSE; } return nn::ResultSuccess; } virt_ptr<DrcTransferrer> GetDrcTransferrer() { return virt_addrof(sLibData->drcTransferrer); } void Library::registerLibSymbols() { RegisterFunctionExportName( "Initialize__Q2_2nn2slFPFUiT1_PvPFPv_v", static_cast<nn::Result(*)(AllocFn,FreeFn)>(Initialize)); RegisterFunctionExportName( "Initialize__Q4_2nn2sl4core6detailFPFUiT1_PvPFPv_vRQ3_2nn2sl12ITransferrer", static_cast<nn::Result(*)(AllocFn, FreeFn, virt_ptr<ITransferrer>)>(Initialize)); RegisterFunctionExportName("Finalize__Q2_2nn2slFv", Finalize); RegisterFunctionExportName("GetDrcTransferrer__Q2_2nn2slFv", GetDrcTransferrer); RegisterDataInternal(sLibData); } } // namespace namespace cafe::nn_sl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_lib.h ================================================ #pragma once #include "nn/nn_result.h" #include <libcpu/be2_struct.h> namespace cafe::nn_sl { using AllocFn = virt_func_ptr<virt_ptr<void> (uint32_t size, uint32_t align)>; using FreeFn = virt_func_ptr<void (virt_ptr<void> ptr)>; struct ITransferrer; struct DrcTransferrer; nn::Result Initialize(AllocFn alloc, FreeFn free); nn::Result Initialize(AllocFn alloc, FreeFn free, virt_ptr<ITransferrer> transferrer); nn::Result Finalize(); virt_ptr<DrcTransferrer> GetDrcTransferrer(); } // namespace namespace cafe::nn_sl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_spm/nn_spm.cpp ================================================ #include "nn_spm.h" namespace cafe::nn_spm { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); registerExtendedStorageServiceSymbols(); } } // namespace cafe::nn_spm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_spm/nn_spm.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_spm { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_spm, "nn_spm.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); void registerExtendedStorageServiceSymbols(); }; } // namespace cafe::nn_spm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_client.cpp ================================================ #include "nn_spm.h" #include "nn_spm_client.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/nn/cafe_nn_ipc_client.h" using namespace cafe::coreinit; namespace cafe::nn_spm { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } alignas(64) be2_array<uint8_t, 0x4000> allocatorMemory; be2_struct<OSMutex> mutex; be2_val<uint32_t> refCount = 0u; be2_struct<nn::ipc::Client> client; be2_struct<nn::ipc::BufferAllocator> allocator; }; static virt_ptr<StaticClientData> sClientData = nullptr; nn::Result Initialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->client.initialise(make_stack_string("/dev/acp_main")); sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory), sClientData->allocatorMemory.size()); } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return nn::ResultSuccess; } void Finalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->client.close(); } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } namespace internal { virt_ptr<nn::ipc::Client> getClient() { return virt_addrof(sClientData->client); } virt_ptr<nn::ipc::BufferAllocator> getAllocator() { return virt_addrof(sClientData->allocator); } } // namespace internal void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn3spmFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn3spmFv", Finalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_spm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_client.h ================================================ #pragma once #include "nn/nn_result.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "cafe/nn/cafe_nn_ipc_bufferallocator.h" namespace cafe::nn_spm { nn::Result Initialize(); void Finalize(); namespace internal { virt_ptr<nn::ipc::Client> getClient(); virt_ptr<nn::ipc::BufferAllocator> getAllocator(); } // namespace internal } // namespace cafe::nn_spm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_extendedstorageservice.cpp ================================================ #include "nn_spm.h" #include "nn_spm_client.h" #include "nn_spm_extendedstorageservice.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/nn/cafe_nn_ipc_client.h" #include "nn/spm/nn_spm_extendedstorageservice.h" using namespace nn::spm; using namespace nn::ipc; namespace cafe::nn_spm { using nn::spm::services::ExtendedStorageService; nn::Result SetAutoFatal(uint8_t autoFatal) { auto command = ClientCommand<ExtendedStorageService::SetAutoFatal> { internal::getAllocator() }; command.setParameters(autoFatal); auto result = internal::getClient()->sendSyncRequest(command); if (result.ok()) { result = command.readResponse(); } return result; } void Library::registerExtendedStorageServiceSymbols() { RegisterFunctionExportName("SetAutoFatal__Q2_2nn3spmFb", SetAutoFatal); } } // namespace cafe::nn_spm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_extendedstorageservice.h ================================================ #pragma once #include "nn/nn_result.h" #include <cstdint> namespace cafe::nn_spm { nn::Result SetAutoFatal(uint8_t autoFatal); } // namespace cafe::nn_spm ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_temp/nn_temp.cpp ================================================ #include "nn_temp.h" namespace cafe::nn_temp { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerTempDirSymbols(); } } // namespace cafe::nn_temp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_temp/nn_temp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_temp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_temp, "nn_temp.rpl") { } protected: virtual void registerSymbols() override; private: void registerTempDirSymbols(); }; } // namespace cafe::nn_temp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_temp/nn_temp_enum.h ================================================ #ifndef CAFE_TEMP_ENUM_H #define CAFE_TEMP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nn_temp) ENUM_BEG(TEMPDevicePreference, uint32_t) //! Largest free space between USB and MLC ENUM_VALUE(LargestFreeSpace, 0) //! Prefer USB instead of MLC ENUM_VALUE(USB, 1) ENUM_END(TEMPDevicePreference) ENUM_BEG(TEMPDeviceType, uint32_t) ENUM_VALUE(Invalid, 0) ENUM_VALUE(MLC, 1) ENUM_VALUE(USB, 2) ENUM_END(TEMPDeviceType) ENUM_BEG(TEMPStatus, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Cancelled, -1) ENUM_VALUE(End, -2) ENUM_VALUE(Max, -3) ENUM_VALUE(AlreadyOpen, -4) ENUM_VALUE(Exists, -5) ENUM_VALUE(NotFound, -6) ENUM_VALUE(NotFile, -7) ENUM_VALUE(NotDirectory, -8) ENUM_VALUE(AccessError, -9) ENUM_VALUE(PermissionError, -10) ENUM_VALUE(FileTooBig, -11) ENUM_VALUE(StorageFull, -12) ENUM_VALUE(JournalFull, -13) ENUM_VALUE(UnsupportedCmd, -14) ENUM_VALUE(MediaNotReady, -15) ENUM_VALUE(MediaError, -17) ENUM_VALUE(Corrupted, -18) ENUM_VALUE(FatalError, -0x400) ENUM_VALUE(InvalidParam, -0x401) ENUM_END(TEMPStatus) ENUM_NAMESPACE_EXIT(nn_temp) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef CAFE_TEMP_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_temp/nn_temp_tempdir.cpp ================================================ #include "nn_temp.h" #include "nn_temp_tempdir.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_cosreport.h" #include "cafe/libraries/coreinit/coreinit_event.h" #include "cafe/libraries/coreinit/coreinit_fs_client.h" #include "cafe/libraries/coreinit/coreinit_fs_cmdblock.h" #include "cafe/libraries/coreinit/coreinit_fs_cmd.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/libraries/coreinit/coreinit_osreport.h" #include <cinttypes> #include <common/strutils.h> #include <cstring> #include <fmt/core.h> #include <libcpu/cpu_formatters.h> using namespace cafe::coreinit; template<typename... Args> void tempLogInfo(const char *file, unsigned line, const char *msg, const Args &... args) { auto str = fmt::format(msg, args...); cafe::coreinit::internal::COSInfo( cafe::coreinit::COSReportModule::Unknown1, fmt::format("TEMP: [INFO]:{}({}):{}", file, line, str.c_str())); } template<typename... Args> void tempLogWarn(const char *file, unsigned line, const char *msg, const Args &... args) { auto str = fmt::format(msg, args...); cafe::coreinit::internal::COSInfo( cafe::coreinit::COSReportModule::Unknown1, fmt::format("TEMP: [WARN]:{}({}):{}", file, line, str.c_str())); } template<typename... Args> void tempLogError(const char *file, unsigned line, const char *msg, const Args &... args) { auto str = fmt::format(msg, args...); cafe::coreinit::internal::COSInfo( cafe::coreinit::COSReportModule::Unknown1, fmt::format("TEMP: [ERROR]:{}({}):{}", file, line, str.c_str())); } namespace cafe::nn_temp { constexpr uint32_t NumSyncEvents = 1u; struct TEMPSyncEventContext { be2_virt_ptr<OSEvent> event; be2_val<TEMPStatus> result; }; struct TempDirData { TempDirData() { OSInitCond(virt_addrof(syncEventListCond)); OSInitMutex(virt_addrof(syncEventListMutex)); } be2_val<int32_t> initCount; be2_struct<OSMutex> mutex; be2_struct<FSClient> fsClient; be2_struct<FSCmdBlock> fsCmdBlock; be2_array<char, FSMaxPathLength> globalDirPath; be2_array<char, FSMaxPathLength> dirPath; be2_array<OSEvent, NumSyncEvents> syncEventList; be2_struct<OSCondition> syncEventListCond; be2_struct<OSMutex> syncEventListMutex; be2_val<uint32_t> syncEventListFreeBitmask = static_cast<uint32_t>((1 << syncEventList.size()) - 1); }; static virt_ptr<TempDirData> sTempDirData = nullptr; static FSAsyncCallbackFn sSyncEventCallbackFn = nullptr; namespace internal { bool checkIsInitialised() { if (sTempDirData->initCount > 0) { return true; } coreinit::internal::OSPanic( "temp.cpp", 181, "TEMP: [PANIC]:TEMP library is not initialized. Call TEMPInit() prior to " "this function."); return false; } static virt_ptr<OSEvent> acquireSyncEvent() { auto event = virt_ptr<OSEvent> { nullptr }; OSLockMutex(virt_addrof(sTempDirData->syncEventListMutex)); while (!event) { for (auto i = 0u; i < sTempDirData->syncEventList.size(); ++i) { if (sTempDirData->syncEventListFreeBitmask & (1 << i)) { event = virt_addrof(sTempDirData->syncEventList[i]); sTempDirData->syncEventListFreeBitmask &= ~(1 << i); break; } } if (!event) { // No free events, wait for one to become free OSWaitCond(virt_addrof(sTempDirData->syncEventListCond), virt_addrof(sTempDirData->syncEventListMutex)); } } OSUnlockMutex(virt_addrof(sTempDirData->syncEventListMutex)); OSInitEvent(event, FALSE, OSEventMode::AutoReset); return event; } static void releaseSyncEvent(virt_ptr<OSEvent> event) { OSLockMutex(virt_addrof(sTempDirData->syncEventListMutex)); for (auto i = 0u; i < sTempDirData->syncEventList.size(); ++i) { if (virt_addrof(sTempDirData->syncEventList[i]) == event) { sTempDirData->syncEventListFreeBitmask |= (1 << i); } } OSUnlockMutex(virt_addrof(sTempDirData->syncEventListMutex)); OSSignalCond(virt_addrof(sTempDirData->syncEventListCond)); } static void syncEventCallback(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> cmd, FSStatus status, virt_ptr<void> context) { auto syncEventContext = virt_cast<TEMPSyncEventContext *>(context); syncEventContext->result = static_cast<TEMPStatus>(status); OSSignalEvent(syncEventContext->event); } static TEMPDirId makeDirId(uint32_t deviceIndex, TEMPDeviceType deviceType, uint32_t upid) { return deviceIndex | (deviceType << 8) | static_cast<uint64_t>(OSGetUPID()) << 32; } static void setDeviceInfo(virt_ptr<TEMPDeviceInfo> deviceInfo, TEMPDeviceType deviceType, uint8_t deviceIndex, virt_ptr<char> devicePath) { deviceInfo->dirId = makeDirId(deviceIndex, deviceType, static_cast<uint32_t>(OSGetUPID())); std::snprintf(virt_addrof(deviceInfo->targetPath).get(), deviceInfo->targetPath.size(), "%s/usr/tmp/app", devicePath.get()); } static TEMPStatus updatePreferentialDeviceInfo(virt_ptr<TEMPDeviceInfo> deviceInfo, uint32_t maxSize, TEMPDevicePreference devicePreference) { auto freeMlcSize = StackObject<uint64_t> { }; auto freeUsbSize = StackObject<uint64_t> { }; FSGetFreeSpaceSize(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), make_stack_string("/vol/storage_mlc01"), freeMlcSize, FSErrorFlag::None); tempLogInfo("UpdatePreferentialDeviceInfo", 544, "MLC freeSpaceSize={}", *freeMlcSize); // TODO: Use nn::spm to find device index for USB if (FSGetFreeSpaceSize(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), make_stack_string("/vol/storage_usb01"), freeUsbSize, FSErrorFlag::All) != FSStatus::OK) { *freeUsbSize = 0ull; } tempLogInfo("UpdatePreferentialDeviceInfo", 562, "USB freeSpaceSize={}", *freeUsbSize); if ((devicePreference == TEMPDevicePreference::USB || *freeUsbSize >= *freeMlcSize) && (*freeUsbSize >= maxSize)) { setDeviceInfo(deviceInfo, TEMPDeviceType::USB, 1, make_stack_string("/vol/storage_usb01")); } else if (*freeMlcSize >= maxSize) { setDeviceInfo(deviceInfo, TEMPDeviceType::MLC, 1, make_stack_string("/vol/storage_mlc01")); } else { setDeviceInfo(deviceInfo, TEMPDeviceType::Invalid, 0, make_stack_string("")); tempLogInfo("UpdatePreferentialDeviceInfo", 648, "Cannot create temp dir: ({})", -12); return static_cast<TEMPStatus>(TEMPStatus::StorageFull); } tempLogInfo("UpdatePreferentialDeviceInfo", 644, "Preferential Device Path for temp dir: {}", virt_addrof(deviceInfo->targetPath)); return TEMPStatus::OK; } static TEMPStatus createAndMountTempDir(virt_ptr<TEMPDeviceInfo> deviceInfo) { tempLogInfo("CreateAndMountTempDir", 297, "(ENTR): dirID={}, targetPath={}", deviceInfo->dirId, virt_addrof(deviceInfo->targetPath)); auto error = static_cast<TEMPStatus>( FSMakeDir(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(deviceInfo->targetPath), FSErrorFlag::All)); tempLogInfo("CreateAndMountTempDir", 305, "Make dir done at {}, returned {}", virt_addrof(deviceInfo->targetPath), error); if (error == TEMPStatus::StorageFull) { goto out; } if (error != TEMPStatus::Exists) { error = static_cast<TEMPStatus>( FSChangeMode(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(deviceInfo->targetPath), 0x666, 0x666, FSErrorFlag::None)); tempLogInfo("CreateAndMountTempDir", 316, "Change mode done at {}, returned {}", virt_addrof(deviceInfo->targetPath), error); } error = TEMPGetDirGlobalPath(deviceInfo->dirId, virt_addrof(sTempDirData->globalDirPath), GlobalPathMaxLength); if (error != TEMPStatus::OK) { goto out; } tempLogInfo("CreateAndMountTempDir", 325, "Global Path={}", virt_addrof(sTempDirData->globalDirPath)); error = static_cast<TEMPStatus>( FSMakeDir(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), FSErrorFlag::All)); tempLogInfo("CreateAndMountTempDir", 333, "Make dir done at {}, returned {}", virt_addrof(sTempDirData->globalDirPath), error); if (error == TEMPStatus::StorageFull) { goto out; } error = TEMPGetDirPath(deviceInfo->dirId, virt_addrof(sTempDirData->dirPath), sTempDirData->dirPath.size()); if (error != TEMPStatus::OK) { goto out; } tempLogInfo("CreateAndMountTempDir", 346, "Dir Path={}", virt_addrof(sTempDirData->dirPath)); error = static_cast<TEMPStatus>( FSBindMount(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), virt_addrof(sTempDirData->dirPath), FSErrorFlag::None)); tempLogInfo("CreateAndMountTempDir", 353, "Bind mount done to {}, returned {}", virt_addrof(sTempDirData->dirPath), error); out: tempLogInfo("CreateAndMountTempDir", 356, "(EXIT): return {}", error); return error; } static TEMPDeviceType getDeviceType(TEMPDirId dirId) { return static_cast<TEMPDeviceType>((dirId >> 8) & 0xFF); } static TEMPDeviceType getDeviceIndex(TEMPDirId dirId) { return static_cast<TEMPDeviceType>(dirId & 0xFF); } static TEMPStatus getDeviceInfo(virt_ptr<TEMPDeviceInfo> deviceInfo, TEMPDirId dirId) { auto deviceType = getDeviceType(dirId); auto deviceIndex = getDeviceIndex(dirId); deviceInfo->dirId = dirId; if (deviceType == TEMPDeviceType::MLC) { std::snprintf(virt_addrof(deviceInfo->targetPath).get(), deviceInfo->targetPath.size(), "/vol/storage_mlc%02d/usr/tmp/app", deviceIndex); } else if (deviceType == TEMPDeviceType::USB) { std::snprintf(virt_addrof(deviceInfo->targetPath).get(), deviceInfo->targetPath.size(), "/vol/storage_usb%02d/usr/tmp/app", deviceIndex); } else { return static_cast<TEMPStatus>(TEMPStatus::NotFound); } return TEMPStatus::OK; } static TEMPStatus getTempAbsolutePath(TEMPDirId dirId, virt_ptr<const char> relativePath, virt_ptr<char> pathBuffer, uint32_t pathBufferSize) { auto error = TEMPGetDirPath(dirId, pathBuffer, pathBufferSize); if (error >= TEMPStatus::OK) { auto tempDirLength = std::strlen(pathBuffer.get()); auto relPathLength = std::strlen(relativePath.get()); if (tempDirLength + relPathLength + 2 > pathBufferSize) { return TEMPStatus::FatalError; } pathBuffer[tempDirLength++] = '/'; string_copy(pathBuffer.get() + tempDirLength, relativePath.get(), pathBufferSize - tempDirLength); } return error; } static TEMPStatus removeRecursiveBody(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, virt_ptr<char> path, uint32_t pathBufferSize, FSStatFlags statFlags); static TEMPStatus removeDirectoryEntry(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, virt_ptr<char> path, uint32_t pathBufferSize) { auto handle = StackObject<FSDirHandle> { }; auto entry = StackObject<FSDirEntry> { }; auto error = static_cast<TEMPStatus>( FSOpenDir(client, block, path, handle, FSErrorFlag::All)); if (error) { tempLogError("RemoveDirectoryEntry", 264, "FSOpenDir failed with {}", error); return error; } while (FSReadDir(client, block, *handle, entry, FSErrorFlag::All) == 0) { // Append directory entry name to path auto pathLen = strnlen(path.get(), pathBufferSize); std::strncat(path.get(), "/", pathBufferSize - pathLen - 1); std::strncat(path.get(), virt_addrof(entry->name).get(), pathBufferSize - pathLen - 2); error = removeRecursiveBody(client, block, path, pathBufferSize, entry->stat.flags); // Reset path path[pathLen] = char { 0 }; if (error) { break; } } FSCloseDir(client, block, *handle, FSErrorFlag::None); return error; } static TEMPStatus removeRecursiveBody(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, virt_ptr<char> path, uint32_t pathBufferSize, FSStatFlags statFlags) { auto error = TEMPStatus::OK; if (statFlags & FSStatFlags::Quota) { tempLogWarn("RemoveRecursiveBody", 219, "Quota is found inside temp directory!"); // TODO: FSRemoveQuota } else if (statFlags & FSStatFlags::Directory) { error = removeDirectoryEntry(client, block, path, pathBufferSize); if (error == TEMPStatus::OK) { tempLogInfo("RemoveRecursiveBody", 242, "Removing {}", path); error = static_cast<TEMPStatus>( FSRemove(client, block, path, FSErrorFlag::All)); if (error) { tempLogError("RemoveRecursiveBody", 236, "FSRemove failed with {}", error); } } } else { tempLogInfo("RemoveRecursiveBody", 242, "Removing {}", path); error = static_cast<TEMPStatus>( FSRemove(client, block, path, FSErrorFlag::All)); if (error) { tempLogError("RemoveRecursiveBody", 236, "FSRemove failed with {}", error); } } return error; } static TEMPStatus removeRecursive(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, virt_ptr<char> path, uint32_t pathBufferSize) { auto stat = StackObject<FSStat> { }; auto error = static_cast<TEMPStatus>( FSGetStat(client, block, path, stat, FSErrorFlag::All)); if (error) { tempLogError("RemoveRecursive", 202, "FSGetStat failed with {}", error); return error; } return removeRecursiveBody(client, block, path, pathBufferSize, stat->flags); } static TEMPStatus teardownTempDir(TEMPDirId id) { TEMPGetDirPath(id, virt_addrof(sTempDirData->globalDirPath), GlobalPathMaxLength); tempLogInfo("TeardownTempDir", 365, "(ENTR): path={}", virt_addrof(sTempDirData->globalDirPath)); auto error = internal::removeRecursive( virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size() - 1); if (error) { tempLogError("TeardownTempDir", 373, "Failed to clean up temp dir, status={}", error); } else { FSBindUnmount(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), FSErrorFlag::None); tempLogInfo("TeardownTempDir", 379, "TEMP: [INFO]:%s(%d):Bind unmount done at {}", virt_addrof(sTempDirData->globalDirPath)); } tempLogInfo("TeardownTempDir", 382, "(EXIT): return {}", error); return error; } static TEMPStatus parseDirId(virt_ptr<const char> path, TEMPDirId *outDirId) { if (!strstr(path.get(), "/vol/storage_")) { return TEMPStatus::InvalidParam; } if (strstr(path.get() + 13, "mlc")) { auto deviceIndex = strtoul(path.get() + 16, nullptr, 10); auto deviceType = TEMPDeviceType::MLC; auto upid = 0u; if (auto pos = strstr(path.get() + 16, "/usr/tmp/app")) { upid = strtoul(pos + 14, nullptr, 16); } *outDirId = makeDirId(deviceIndex, deviceType, upid); } else if (strstr(path.get() + 13, "usb")) { auto deviceIndex = strtoul(path.get() + 16, nullptr, 10); auto deviceType = TEMPDeviceType::USB; auto upid = 0u; if (auto pos = strstr(path.get() + 16, "/usr/tmp/app")) { upid = strtoul(pos + 14, nullptr, 16); } *outDirId = makeDirId(deviceIndex, deviceType, upid); } else { return TEMPStatus::InvalidParam; } return TEMPStatus::OK; } static TEMPStatus forceRemoveTempDir(virt_ptr<const char> rootPath) { if (std::snprintf(virt_addrof(sTempDirData->globalDirPath).get(), GlobalPathMaxLength, "%s/%08x", rootPath.get(), static_cast<uint32_t>(OSGetUPID())) >= GlobalPathMaxLength) { coreinit::internal::OSPanic( "temp.cpp", 442, fmt::format("The specified path is too long: {}", virt_addrof(sTempDirData->globalDirPath).get())); return TEMPStatus::FatalError; } auto handle = StackObject<FSDirHandle> { }; auto error = static_cast<TEMPStatus>( FSOpenDir(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), rootPath, handle, FSErrorFlag::NotFound)); if (error) { return error; } FSCloseDir(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), *handle, FSErrorFlag::None); auto dirId = TEMPDirId { }; error = parseDirId(virt_addrof(sTempDirData->globalDirPath), &dirId); if (error) { return error; } error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->dirPath), DirPathMaxLength); if (error) { return error; } FSBindUnmount(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->dirPath), FSErrorFlag::None); error = internal::removeRecursive(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size() - 1); if (error) { tempLogError("ForceRemoveTempDir", 478, "Failed to clean up temp dir, status={}", error); } return error; } static void tempShutdownBody(bool isDriverDone) { tempLogInfo("_TEMPShutdownBody", 695, "(ENTRY)"); OSLockMutex(virt_addrof(sTempDirData->mutex)); if (sTempDirData->initCount <= 0) { if (!isDriverDone) { tempLogWarn("_TEMPShutdownBody", 749, "Library is not initialized."); } } else if (sTempDirData->initCount == 1) { auto error = forceRemoveTempDir(make_stack_string("/vol/storage_mlc01/usr/tmp/app")); if (error && error != TEMPStatus::NotFound) { tempLogError("_TEMPShutdownBody", 708, "Failed to delete temp dir in MLC."); } // TODO: Use nn::spm to find device index for USB error = forceRemoveTempDir(make_stack_string("/vol/storage_usb01/usr/tmp/app")); if (error && error != TEMPStatus::NotFound) { tempLogError("_TEMPShutdownBody", 729, "Failed to delete temp dir in USB."); } FSDelClient(virt_addrof(sTempDirData->fsClient), FSErrorFlag::None); sTempDirData->initCount = 0; tempLogInfo("_TEMPShutdownBody", 737, "Deleted client"); } else { sTempDirData->initCount--; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); tempLogInfo("_TEMPShutdownBody", 754, "(EXIT)"); } } // namespace internal TEMPStatus TEMPInit() { tempLogInfo("TEMPInit", 668, "(ENTR)"); OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!sTempDirData->initCount) { FSInit(); FSAddClient(virt_addrof(sTempDirData->fsClient), FSErrorFlag::None); FSInitCmdBlock(virt_addrof(sTempDirData->fsCmdBlock)); } else { tempLogInfo("TEMPInit", 661, "Library is already initialized."); } sTempDirData->initCount++; OSUnlockMutex(virt_addrof(sTempDirData->mutex)); tempLogInfo("TEMPInit", 668, "(EXIT): return {}", 0); return TEMPStatus::OK; } void TEMPShutdown() { internal::tempShutdownBody(false); } TEMPStatus TEMPChangeDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPChangeDirAsync(client, block, dirId, path, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPChangeDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, path, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSChangeDirAsync(client, block, virt_addrof(sTempDirData->globalDirPath), errorMask, asyncData)); } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPCreateAndInitTempDir(uint32_t maxSize, TEMPDevicePreference devicePreference, virt_ptr<TEMPDirId> outDirId) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; auto error = TEMPStatus::OK; tempLogInfo("TEMPCreateAndInitTempDir", 779, "(ENTR): maxSize={}, pref={}", maxSize, devicePreference); OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { error = TEMPStatus::FatalError; goto out; } if (!outDirId) { tempLogError("TEMPCreateAndInitTempDir", 793, "pDirID was NULL."); error = TEMPStatus::InvalidParam; goto out; } if (devicePreference != TEMPDevicePreference::LargestFreeSpace && devicePreference != TEMPDevicePreference::USB) { tempLogError("TEMPCreateAndInitTempDir", 800, "Invalid value was specified to pref."); error = TEMPStatus::InvalidParam; goto out; } error = internal::updatePreferentialDeviceInfo(deviceInfo, maxSize, devicePreference); if (error < TEMPStatus::OK) { goto out; } error = internal::createAndMountTempDir(deviceInfo); if (error < TEMPStatus::OK) { goto out; } *outDirId = deviceInfo->dirId; out: OSUnlockMutex(virt_addrof(sTempDirData->mutex)); tempLogInfo("TEMPCreateAndInitTempDir", 826, "(EXIT): return {}", error); return error; } TEMPStatus TEMPGetDirPath(TEMPDirId dirId, virt_ptr<char> pathBuffer, uint32_t pathBufferSize) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; if (!internal::checkIsInitialised()) { return TEMPStatus::FatalError; } if (!pathBuffer) { tempLogError("TEMPGetDirPath", 865, "path was NULL"); return TEMPStatus::InvalidParam; } if (pathBufferSize < DirPathMaxLength) { tempLogError("TEMPGetDirPath", 870, "pathLen(={}) was too short. Must be equal or bigger than TEMP_DIR_PATH_LENGTH_MAX(={})", pathBufferSize, DirPathMaxLength); return TEMPStatus::InvalidParam; } if (std::snprintf(pathBuffer.get(), pathBufferSize, "/vol/temp/%016" PRIx64, dirId) >= DirPathMaxLength) { tempLogError("TEMPGetDirPath", 881, "Failed to generate path"); return TEMPStatus::FatalError; } return TEMPStatus::OK; } TEMPStatus TEMPGetDirGlobalPath(TEMPDirId dirId, virt_ptr<char> pathBuffer, uint32_t pathBufferSize) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; if (!internal::checkIsInitialised()) { return static_cast<TEMPStatus>(TEMPStatus::FatalError); } if (!pathBuffer) { tempLogError("TEMPGetDirGlobalPath", 898, "path was NULL"); return TEMPStatus::InvalidParam; } if (pathBufferSize < GlobalPathMaxLength) { tempLogError("TEMPGetDirGlobalPath", 903, "pathLen(={}) was too short. Must be equal or bigger than TEMP_DIR_PATH_LENGTH_MAX(={})", pathBufferSize, DirPathMaxLength); return TEMPStatus::InvalidParam; } if (auto error = internal::getDeviceInfo(deviceInfo, dirId)) { return error; } if (std::snprintf(pathBuffer.get(), pathBufferSize, "%s/%08x", virt_addrof(deviceInfo->targetPath).get(), static_cast<uint32_t>(deviceInfo->dirId & 0xFFFFFFFF)) >= GlobalPathMaxLength) { tempLogError("TEMPGetDirGlobalPath", 922, "Failed to generate path"); return TEMPStatus::FatalError; } return TEMPStatus::OK; } TEMPStatus TEMPGetFreeSpaceSize(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<uint64_t> outFreeSize, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPGetFreeSpaceSizeAsync(client, block, dirId, outFreeSize, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPGetFreeSpaceSizeAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<uint64_t> outFreeSize, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->dirPath), sTempDirData->dirPath.size()); if (error >= TEMPStatus::OK) { tempLogInfo("TEMPGetFreeSpaceSizeAsync", 1730, "Path = {}", virt_addrof(sTempDirData->dirPath).get()); error = static_cast<TEMPStatus>( FSGetFreeSpaceSizeAsync(client, block, virt_addrof(sTempDirData->dirPath), outFreeSize, errorMask, asyncData)); } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPGetStat(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<FSStat> outStat, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPGetStatAsync(client, block, dirId, path, outStat, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPGetStatAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<FSStat> outStat, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, path, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSGetStatAsync(client, block, virt_addrof(sTempDirData->globalDirPath), outStat, errorMask, asyncData)); } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPMakeDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPMakeDirAsync(client, block, dirId, path, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPMakeDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, path, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSMakeDirAsync(client, block, virt_addrof(sTempDirData->globalDirPath), errorMask, asyncData)); } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPMountTempDir(TEMPDirId dirId) { OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = TEMPGetDirGlobalPath(dirId, virt_addrof(sTempDirData->globalDirPath), GlobalPathMaxLength); if (error >= TEMPStatus::OK) { tempLogInfo("TEMPMountTempDir", 1619, "Global Path={}", virt_addrof(sTempDirData->globalDirPath).get()); error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->dirPath), DirPathMaxLength); if (error >= TEMPStatus::OK) { tempLogInfo("TEMPMountTempDir", 1629, "Dir Path={}", virt_addrof(sTempDirData->dirPath).get()); error = static_cast<TEMPStatus>( FSBindMount(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), virt_addrof(sTempDirData->dirPath), FSErrorFlag::None)); tempLogInfo("TEMPMountTempDir", 1636, "Bind mount done to {}, returned {}", virt_addrof(sTempDirData->dirPath).get(), error); } } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPOpenDir(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<FSDirHandle> outDirHandle, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPOpenDirAsync(client, block, dirId, path, outDirHandle, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPOpenDirAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<FSDirHandle> outDirHandle, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, path, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSOpenDirAsync(client, block, virt_addrof(sTempDirData->globalDirPath), outDirHandle, errorMask, asyncData)); } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPOpenFile(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> outFileHandle, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPOpenFileAsync(client, block, dirId, path, mode, outFileHandle, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPOpenFileAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> outFileHandle, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, path, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSOpenFileAsync(client, block, virt_addrof(sTempDirData->globalDirPath), mode, outFileHandle, errorMask, asyncData)); } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPOpenNewFile(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> outFileHandle, FSErrorFlag errorMask) { // Yes, this is the real implementation. return TEMPStatus::UnsupportedCmd; } TEMPStatus TEMPOpenNewFileAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<FSFileHandle> outFileHandle, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { // Yes, this is the real implementation. return TEMPStatus::UnsupportedCmd; } TEMPStatus TEMPRemove(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPRemoveAsync(client, block, dirId, path, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPRemoveAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, path, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSRemoveAsync(client, block, virt_addrof(sTempDirData->globalDirPath), errorMask, asyncData)); } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPRename(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> src, virt_ptr<const char> dst, FSErrorFlag errorMask) { auto syncEventContext = StackObject<TEMPSyncEventContext> { }; syncEventContext->event = internal::acquireSyncEvent(); auto asyncData = StackObject<FSAsyncData> { }; asyncData->ioMsgQueue = nullptr; asyncData->userCallback = sSyncEventCallbackFn; asyncData->userContext = syncEventContext; auto error = TEMPRenameAsync(client, block, dirId, src, dst, errorMask, asyncData); if (error >= TEMPStatus::OK) { OSWaitEvent(syncEventContext->event); error = syncEventContext->result; } internal::releaseSyncEvent(syncEventContext->event); return error; } TEMPStatus TEMPRenameAsync(virt_ptr<FSClient> client, virt_ptr<FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> src, virt_ptr<const char> dst, FSErrorFlag errorMask, virt_ptr<const FSAsyncData> asyncData) { auto deviceInfo = StackObject<TEMPDeviceInfo> { }; OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::getTempAbsolutePath(dirId, src, virt_addrof(sTempDirData->globalDirPath), sTempDirData->globalDirPath.size()); if (error >= TEMPStatus::OK) { error = internal::getTempAbsolutePath(dirId, dst, virt_addrof(sTempDirData->dirPath), sTempDirData->dirPath.size()); if (error >= TEMPStatus::OK) { error = static_cast<TEMPStatus>( FSRenameAsync(client, block, virt_addrof(sTempDirData->globalDirPath), virt_addrof(sTempDirData->dirPath), errorMask, asyncData)); } } else { error = TEMPStatus::NotFound; } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } TEMPStatus TEMPShutdownTempDir(TEMPDirId id) { tempLogInfo("TEMPShutdownTempDir", 834, "(ENTR): dirID={}", id); OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = internal::teardownTempDir(id); if (error && error != TEMPStatus::NotFound) { tempLogError("TEMPShutdownTempDir", 848, "Failed to delete temp dir ({}).", id); } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); tempLogInfo("TEMPShutdownTempDir", 853, "(EXIT): return {}", error); return error; } TEMPStatus TEMPUnmountTempDir(TEMPDirId dirId) { OSLockMutex(virt_addrof(sTempDirData->mutex)); if (!internal::checkIsInitialised()) { OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return TEMPStatus::FatalError; } auto error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->globalDirPath), DirPathMaxLength); if (error >= TEMPStatus::OK) { tempLogInfo("TEMPUnmountTempDir", 1661, "Global Path={}", virt_addrof(sTempDirData->globalDirPath).get()); error = static_cast<TEMPStatus>( FSBindUnmount(virt_addrof(sTempDirData->fsClient), virt_addrof(sTempDirData->fsCmdBlock), virt_addrof(sTempDirData->globalDirPath), FSErrorFlag::None)); tempLogInfo("TEMPUnmountTempDir", 1669, "Bind unmount done at {}", virt_addrof(sTempDirData->globalDirPath).get()); } OSUnlockMutex(virt_addrof(sTempDirData->mutex)); return error; } void Library::registerTempDirSymbols() { RegisterFunctionExport(TEMPChangeDir); RegisterFunctionExport(TEMPChangeDirAsync); RegisterFunctionExport(TEMPCreateAndInitTempDir); RegisterFunctionExport(TEMPGetDirGlobalPath); RegisterFunctionExport(TEMPGetDirPath); RegisterFunctionExport(TEMPGetFreeSpaceSize); RegisterFunctionExport(TEMPGetFreeSpaceSizeAsync); RegisterFunctionExport(TEMPGetStat); RegisterFunctionExport(TEMPGetStatAsync); RegisterFunctionExport(TEMPInit); RegisterFunctionExport(TEMPMakeDir); RegisterFunctionExport(TEMPMakeDirAsync); RegisterFunctionExport(TEMPMountTempDir); RegisterFunctionExport(TEMPOpenDir); RegisterFunctionExport(TEMPOpenDirAsync); RegisterFunctionExport(TEMPOpenFile); RegisterFunctionExport(TEMPOpenFileAsync); RegisterFunctionExport(TEMPOpenNewFile); RegisterFunctionExport(TEMPOpenNewFileAsync); RegisterFunctionExport(TEMPRemove); RegisterFunctionExport(TEMPRemoveAsync); RegisterFunctionExport(TEMPRename); RegisterFunctionExport(TEMPRenameAsync); RegisterFunctionExport(TEMPShutdown); RegisterFunctionExport(TEMPShutdownTempDir); RegisterFunctionExport(TEMPUnmountTempDir); RegisterDataInternal(sTempDirData); RegisterFunctionInternal(internal::syncEventCallback, sSyncEventCallbackFn); } } // namespace cafe::nn_temp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_temp/nn_temp_tempdir.h ================================================ #pragma once #include "nn_temp_enum.h" #include "cafe/libraries/coreinit/coreinit_fs.h" #include <libcpu/be2_struct.h> #include <string> namespace cafe::nn_temp { #pragma pack(push, 1) using TEMPDirId = uint64_t; constexpr auto DirPathMaxLength = 0x20u; constexpr auto GlobalPathMaxLength = 0x40u; struct TEMPDeviceInfo { be2_val<uint64_t> dirId; be2_array<char, GlobalPathMaxLength> targetPath; }; CHECK_OFFSET(TEMPDeviceInfo, 0x00, dirId); CHECK_OFFSET(TEMPDeviceInfo, 0x08, targetPath); CHECK_SIZE(TEMPDeviceInfo, 0x48); #pragma pack(pop) TEMPStatus TEMPInit(); void TEMPShutdown(); TEMPStatus TEMPChangeDir(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPChangeDirAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPCreateAndInitTempDir(uint32_t maxSize, TEMPDevicePreference pref, virt_ptr<TEMPDirId> outDirId); TEMPStatus TEMPGetDirPath(TEMPDirId dirId, virt_ptr<char> pathBuffer, uint32_t pathBufferSize); TEMPStatus TEMPGetDirGlobalPath(TEMPDirId dirId, virt_ptr<char> pathBuffer, uint32_t pathBufferSize); TEMPStatus TEMPGetFreeSpaceSize(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<uint64_t> outFreeSize, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPGetFreeSpaceSizeAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<uint64_t> outFreeSize, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPGetStat(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<coreinit::FSStat> outStat, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPGetStatAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<coreinit::FSStat> outStat, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPMakeDir(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPMakeDirAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPMountTempDir(TEMPDirId dirId); TEMPStatus TEMPOpenDir(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<coreinit::FSDirHandle> outDirHandle, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPOpenDirAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<coreinit::FSDirHandle> outDirHandle, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPOpenFile(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<coreinit::FSFileHandle> outFileHandle, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPOpenFileAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<coreinit::FSFileHandle> outFileHandle, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPOpenNewFile(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<coreinit::FSFileHandle> outFileHandle, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPOpenNewFileAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, virt_ptr<const char> mode, virt_ptr<coreinit::FSFileHandle> outFileHandle, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPRemove(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPRemoveAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> path, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPRename(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> src, virt_ptr<const char> dst, coreinit::FSErrorFlag errorMask); TEMPStatus TEMPRenameAsync(virt_ptr<coreinit::FSClient> client, virt_ptr<coreinit::FSCmdBlock> block, TEMPDirId dirId, virt_ptr<const char> src, virt_ptr<const char> dst, coreinit::FSErrorFlag errorMask, virt_ptr<const coreinit::FSAsyncData> asyncData); TEMPStatus TEMPShutdownTempDir(TEMPDirId id); TEMPStatus TEMPUnmountTempDir(TEMPDirId dirId); } // namespace cafe::nn_temp ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_uds/nn_uds.cpp ================================================ #include "nn_uds.h" namespace cafe::nn_uds { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerApiSymbols(); } } // namespace cafe::nn_uds ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_uds/nn_uds.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_uds { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_uds, "nn_uds.rpl") { } protected: virtual void registerSymbols() override; private: void registerApiSymbols(); }; } // namespace cafe::nn_uds ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_uds/nn_uds_api.cpp ================================================ #include "nn_uds.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_ios.h" using namespace cafe::coreinit; namespace cafe::nn_uds { static virt_ptr<OSMutex> sLock = nullptr; static virt_ptr<IOSHandle> sUdsIpcHandle = nullptr; void udsApiCppGlobalConstructor() { *sUdsIpcHandle = -1; OSInitMutex(sLock); } void Library::registerApiSymbols() { RegisterFunctionExportName("__sti___11_uds_Api_cpp_f5d9abb2", udsApiCppGlobalConstructor); RegisterDataExportName("s_Lock__Q3_2nn3uds4Cafe", sLock); RegisterDataExportName("s_UdsIpc__Q3_2nn3uds4Cafe", sUdsIpcHandle); } } // namespace cafe::nn_uds ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl.cpp ================================================ #include "nn_vctl.h" namespace cafe::nn_vctl { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerClientSymbols(); } } // namespace cafe::nn_vctl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nn_vctl { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nn_vctl, "nn_vctl.rpl") { } protected: virtual void registerSymbols() override; private: void registerClientSymbols(); }; } // namespace cafe::nn_vctl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl_client.cpp ================================================ #include "nn_vctl.h" #include "nn_vctl_client.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" using namespace cafe::coreinit; namespace cafe::nn_vctl { struct StaticClientData { StaticClientData() { OSInitMutex(virt_addrof(mutex)); } be2_struct<OSMutex> mutex; be2_val<uint32_t> refCount = 0u; be2_val<BOOL> initialised = FALSE; }; static virt_ptr<StaticClientData> sClientData = nullptr; nn::Result Initialize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount == 0) { sClientData->initialised = TRUE; } sClientData->refCount++; OSUnlockMutex(virt_addrof(sClientData->mutex)); return nn::ResultSuccess; } void Finalize() { OSLockMutex(virt_addrof(sClientData->mutex)); if (sClientData->refCount > 0) { sClientData->refCount--; if (sClientData->refCount == 0) { sClientData->initialised = FALSE; } } OSUnlockMutex(virt_addrof(sClientData->mutex)); } void Library::registerClientSymbols() { RegisterFunctionExportName("Initialize__Q2_2nn4vctlFv", Initialize); RegisterFunctionExportName("Finalize__Q2_2nn4vctlFv", Finalize); RegisterDataInternal(sClientData); } } // namespace cafe::nn_vctl ================================================ FILE: src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl_client.h ================================================ #pragma once #include "nn/nn_result.h" namespace cafe::nn_vctl { nn::Result Initialize(); void Finalize(); } // namespace cafe::nn_vctl ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysccr/nsysccr.cpp ================================================ #include "nsysccr.h" namespace cafe::nsysccr { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nsysccr ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysccr/nsysccr.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nsysccr { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nsysccr, "nsysccr.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nsysccr ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyshid/nsyshid.cpp ================================================ #include "nsyshid.h" namespace cafe::nsyshid { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nsyshid ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyshid/nsyshid.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nsyshid { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nsyshid, "nsyshid.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nsyshid ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd.cpp ================================================ #include "nsyskbd.h" namespace cafe::nsyskbd { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerKprSymbols(); registerSkbdSymbols(); } } // namespace cafe::nsyskbd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nsyskbd { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nsyskbd, "nsyskbd.rpl") { } protected: virtual void registerSymbols() override; private: void registerKprSymbols(); void registerSkbdSymbols(); }; } // namespace cafe::nsyskbd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_enum.h ================================================ #ifndef CAFE_NSYSKBD_ENUM_H #define CAFE_NSYSKBD_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nsyskbd) ENUM_BEG(KPRMode, uint32_t) ENUM_VALUE(AltCode, 1 << 0) ENUM_END(KPRMode) ENUM_BEG(SKBDChannelStatus, uint32_t) ENUM_VALUE(Connected, 0) ENUM_VALUE(Disconnected, 1) ENUM_END(SKBDChannelStatus) ENUM_BEG(SKBDCountry, uint32_t) ENUM_VALUE(Max, 0x13) ENUM_END(SKBDCountry) ENUM_BEG(SKBDError, uint32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(InvalidCountry, 4) ENUM_END(SKBDError) ENUM_BEG(SKBDModState, uint32_t) ENUM_VALUE(NoModifiers, 0) ENUM_END(SKBDModState) ENUM_NAMESPACE_EXIT(nsyskbd) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef CAFE_NSYSKBD_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_kpr.cpp ================================================ #include "nsyskbd.h" #include "nsyskbd_kpr.h" namespace cafe::nsyskbd { void KPRInitQueue(virt_ptr<KPRQueue> queue) { KPRSetMode(queue, KPRMode::AltCode); } KPRMode KPRGetMode(virt_ptr<KPRQueue> queue) { return queue->mode; } void KPRSetMode(virt_ptr<KPRQueue> queue, KPRMode mode) { queue->mode = mode; KPRClearQueue(queue); } void KPRClearQueue(virt_ptr<KPRQueue> queue) { queue->numCharsOut = uint8_t { 0 }; queue->numCharsIn = uint8_t { 0 }; queue->unk0x14 = 0u; } kpr_char_t KPRGetChar(virt_ptr<KPRQueue> queue) { auto result = kpr_char_t { 0 }; // TODO: Once we implement processing of input -> ouput, use numCharsOut if (queue->numCharsIn > 0) { // numCharsOut > 0 result = queue->buffer[0]; queue->numCharsIn -= 1; } return result; } uint8_t KPRPutChar(virt_ptr<KPRQueue> queue, kpr_char_t chr) { decaf_check(queue->numCharsOut + queue->numCharsIn < 5); queue->buffer[queue->numCharsOut + queue->numCharsIn] = chr; queue->numCharsIn += 1; // TODO: Process characters from out -> in return queue->numCharsIn; // return queue->numCharsOut; } kpr_char_t KPRRemoveChar(virt_ptr<KPRQueue> queue) { if (queue->numCharsIn == 0) { return 0; } auto result = queue->buffer[queue->numCharsOut + queue->numCharsIn - 1]; queue->numCharsIn -= 1; return result; } uint8_t KPRLookAhead(virt_ptr<KPRQueue> queue, virt_ptr<kpr_char_t> buffer, uint32_t size) { if (!buffer || !size) { return 0; } auto length = static_cast<uint8_t>(queue->numCharsOut + queue->numCharsIn); for (auto i = 0u; i < length && i < size; ++i) { buffer[i] = queue->buffer[i]; } if (length < size) { buffer[length] = kpr_char_t { 0 }; } return length; } void Library::registerKprSymbols() { RegisterFunctionExport(KPRInitQueue); RegisterFunctionExport(KPRSetMode); RegisterFunctionExport(KPRGetMode); RegisterFunctionExport(KPRClearQueue); RegisterFunctionExport(KPRPutChar); RegisterFunctionExport(KPRGetChar); RegisterFunctionExport(KPRRemoveChar); RegisterFunctionExport(KPRLookAhead); } } // namespace cafe::nsyskbd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_kpr.h ================================================ #pragma once #include "nsyskbd_enum.h" #include <libcpu/be2_struct.h> namespace cafe::nsyskbd { /** * \defgroup nsyskbd_kbr KBR * \ingroup nsyskbd * * This is used for combining characters. Which is useful for ALT+Num unicode * characters and for typing japanese things where you input multiple characters * which combine together into one character. * * @{ */ #pragma pack(push, 1) using kpr_char_t = int16_t; struct KPRQueue { be2_array<kpr_char_t, 6> buffer; be2_val<KPRMode> mode; be2_val<uint8_t> numCharsOut; be2_val<uint8_t> numCharsIn; PADDING(2); be2_val<uint32_t> unk0x14; }; CHECK_OFFSET(KPRQueue, 0x00, buffer); CHECK_OFFSET(KPRQueue, 0x0C, mode); CHECK_OFFSET(KPRQueue, 0x10, numCharsOut); CHECK_OFFSET(KPRQueue, 0x11, numCharsIn); CHECK_OFFSET(KPRQueue, 0x14, unk0x14); CHECK_SIZE(KPRQueue, 0x18); #pragma pack(pop) void KPRInitQueue(virt_ptr<KPRQueue> queue); void KPRSetMode(virt_ptr<KPRQueue> queue, KPRMode mode); KPRMode KPRGetMode(virt_ptr<KPRQueue> queue); void KPRClearQueue(virt_ptr<KPRQueue> queue); uint8_t KPRPutChar(virt_ptr<KPRQueue> queue, kpr_char_t chr); kpr_char_t KPRGetChar(virt_ptr<KPRQueue> queue); kpr_char_t KPRRemoveChar(virt_ptr<KPRQueue> queue); uint8_t KPRLookAhead(virt_ptr<KPRQueue> queue, virt_ptr<kpr_char_t> buffer, uint32_t size); /** @} */ } // namespace cafe::nsyskbd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_skbd.cpp ================================================ #include "nsyskbd.h" #include "nsyskbd_skbd.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::nsyskbd { SKBDError SKBDSetup(uint32_t unk_r3) { decaf_warn_stub(); return SKBDError::OK; } SKBDError SKBDTeardown() { decaf_warn_stub(); return SKBDError::OK; } SKBDError SKBDGetChannelStatus(SKBDChannel channel, virt_ptr<SKBDChannelStatus> outStatus) { decaf_warn_stub(); if (channel == 0) { *outStatus = SKBDChannelStatus::Connected; } else { *outStatus = SKBDChannelStatus::Disconnected; } return SKBDError::OK; } SKBDError SKBDGetKey(SKBDChannel channel, virt_ptr<SKBDKeyData> keyData) { decaf_warn_stub(); std::memset(keyData.get(), 0, sizeof(SKBDKeyData)); keyData->channel = channel; return SKBDError::OK; } SKBDError SKBDGetModState(SKBDChannel channel, virt_ptr<SKBDModState> outModState) { decaf_warn_stub(); *outModState = SKBDModState::NoModifiers; return SKBDError::OK; } SKBDError SKBDResetChannel(SKBDChannel channel) { decaf_warn_stub(); return SKBDError::OK; } SKBDError SKBDSetCountry(SKBDChannel channel, SKBDCountry country) { decaf_warn_stub(); if (country >= SKBDCountry::Max) { return SKBDError::InvalidCountry; } return SKBDError::OK; } SKBDError SKBDSetMode(uint32_t mode) { decaf_warn_stub(); return SKBDError::OK; } void Library::registerSkbdSymbols() { RegisterFunctionExport(SKBDSetup); RegisterFunctionExport(SKBDTeardown); RegisterFunctionExport(SKBDGetChannelStatus); RegisterFunctionExport(SKBDGetKey); RegisterFunctionExport(SKBDGetModState); RegisterFunctionExport(SKBDResetChannel); RegisterFunctionExport(SKBDSetCountry); RegisterFunctionExport(SKBDSetMode); } } // namespace cafe::nsyskbd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_skbd.h ================================================ #pragma once #include "nsyskbd_enum.h" #include <libcpu/be2_struct.h> namespace cafe::nsyskbd { /** * \defgroup nsyskbd_skbd SKBD * \ingroup nsyskbd * @{ */ #pragma pack(push, 1) using SKBDChannel = uint8_t; struct SKBDKeyData { be2_val<SKBDChannel> channel; UNKNOWN(0x0F); }; CHECK_OFFSET(SKBDKeyData, 0x00, channel); CHECK_SIZE(SKBDKeyData, 0x10); #pragma pack(pop) SKBDError SKBDSetup(uint32_t unk_r3); SKBDError SKBDTeardown(); SKBDError SKBDGetChannelStatus(SKBDChannel channel, virt_ptr<SKBDChannelStatus> outStatus); SKBDError SKBDGetKey(SKBDChannel channel, virt_ptr<SKBDKeyData> keyData); SKBDError SKBDGetModState(SKBDChannel channel, virt_ptr<SKBDModState> outModState); SKBDError SKBDResetChannel(SKBDChannel channel); SKBDError SKBDSetCountry(SKBDChannel channel, SKBDCountry country); SKBDError SKBDSetMode(uint32_t mode); /** @} */ } // namespace cafe::nsyskbd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet.cpp ================================================ #include "nsysnet.h" #include "nsysnet_socket_lib.h" #include "nsysnet_nssl.h" namespace cafe::nsysnet { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerEndianSymbols(); registerSocketLibSymbols(); registerSslSymbols(); } } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nsysnet { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nsysnet, "nsysnet.rpl") { } protected: virtual void registerSymbols() override; private: void registerEndianSymbols(); void registerSocketLibSymbols(); void registerSslSymbols(); }; } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_endian.cpp ================================================ #include "nsysnet.h" namespace cafe::nsysnet { /* * These are all no-op because the Wii U's host byte order is net order. */ static uint16_t nsysnet_htons(uint16_t value) { return value; } static uint32_t nsysnet_htonl(uint32_t value) { return value; } static uint16_t nsysnet_ntohs(uint16_t value) { return value; } static uint32_t nsysnet_ntohl(uint32_t value) { return value; } void Library::registerEndianSymbols() { RegisterFunctionExportName("htons", nsysnet_htons); RegisterFunctionExportName("htonl", nsysnet_htonl); RegisterFunctionExportName("ntohs", nsysnet_ntohs); RegisterFunctionExportName("ntohl", nsysnet_ntohl); } } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_enum.h ================================================ #ifndef NSYSNET_ENUM_H #define NSYSNET_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(nsysnet) FLAGS_BEG(GetHostError, int32_t) FLAGS_VALUE(OK, 0) FLAGS_VALUE(HostNotFound, 1) FLAGS_VALUE(TryAgain, 2) FLAGS_VALUE(NoRecovery, 3) FLAGS_VALUE(NoData, 4) FLAGS_END(GetHostError) FLAGS_BEG(Error, int32_t) FLAGS_VALUE(OK, 0) FLAGS_END(Error) ENUM_NAMESPACE_EXIT(nsysnet) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef NSYSNET_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_nssl.cpp ================================================ #include "nsysnet.h" #include "nsysnet_nssl.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_ios.h" #include "cafe/libraries/coreinit/coreinit_ipcbufpool.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "ios/nsec/ios_nsec_nssl.h" namespace cafe::nsysnet { using namespace coreinit; using ios::nsec::NSSLCertType; using ios::nsec::NSSLCommand; using ios::nsec::NSSLCreateContextRequest; using ios::nsec::NSSLDestroyContextRequest; using ios::nsec::NSSLAddServerPKIRequest; using ios::nsec::NSSLAddServerPKIExternalRequest; using ios::nsec::NSSLExportInternalClientCertificateRequest; using ios::nsec::NSSLExportInternalClientCertificateResponse; using ios::nsec::NSSLExportInternalServerCertificateRequest; using ios::nsec::NSSLExportInternalServerCertificateResponse; struct SslData { static constexpr uint32_t MessageCount = 0x40; static constexpr uint32_t MessageSize = 0x100; SslData() { OSInitMutex(virt_addrof(lock)); } be2_struct<OSMutex> lock; be2_val<IOSHandle> handle = IOSHandle { -1 }; be2_virt_ptr<IPCBufPool> messagePool; be2_array<uint8_t, MessageCount * MessageSize> messageBuffer; be2_val<uint32_t> messageCount; }; static virt_ptr<SslData> sSslData; namespace internal { static bool isInitialised(); static virt_ptr<void> allocateIpcBuffer(uint32_t size); static void freeIpcBuffer(virt_ptr<void> buf); } // namespace internal NSSLError NSSLInit() { OSLockMutex(virt_addrof(sSslData->lock)); if (sSslData->handle < 0) { auto iosError = IOS_Open(make_stack_string("/dev/nsec/nssl"), IOSOpenMode::None); if (iosError < 0) { OSUnlockMutex(virt_addrof(sSslData->lock)); return NSSLError::IpcError; } sSslData->handle = static_cast<IOSHandle>(iosError); } if (!sSslData->messagePool) { sSslData->messagePool = IPCBufPoolCreate(virt_addrof(sSslData->messageBuffer), static_cast<uint32_t>(sSslData->messageBuffer.size()), SslData::MessageSize, virt_addrof(sSslData->messageCount), 1); if (!sSslData->messagePool) { IOS_Close(sSslData->handle); sSslData->handle = -1; OSUnlockMutex(virt_addrof(sSslData->lock)); return NSSLError::NsslLibError; } } OSUnlockMutex(virt_addrof(sSslData->lock)); return NSSLError::OK; } NSSLError NSSLFinish() { OSLockMutex(virt_addrof(sSslData->lock)); if (sSslData->handle != -1) { IOS_Close(sSslData->handle); sSslData->handle = -1; } OSUnlockMutex(virt_addrof(sSslData->lock)); return NSSLError::OK; } NSSLError NSSLCreateContext(ios::nsec::NSSLVersion version) { if (!internal::isInitialised()) { return NSSLError::LibNotReady; } auto buf = internal::allocateIpcBuffer(sizeof(NSSLCreateContextRequest)); if (!buf) { return NSSLError::OutOfMemory; } auto request = virt_cast<NSSLCreateContextRequest *>(buf); request->version = version; auto error = IOS_Ioctl(sSslData->handle, NSSLCommand::CreateContext, request, sizeof(NSSLCreateContextRequest), NULL, 0); internal::freeIpcBuffer(buf); return static_cast<NSSLError>(error); } NSSLError NSSLDestroyContext(NSSLContextHandle context) { if (!internal::isInitialised()) { return NSSLError::LibNotReady; } auto buf = internal::allocateIpcBuffer(sizeof(NSSLDestroyContextRequest)); if (!buf) { return NSSLError::OutOfMemory; } auto request = virt_cast<NSSLDestroyContextRequest *>(buf); request->context = context; auto error = IOS_Ioctl(sSslData->handle, NSSLCommand::DestroyContext, request, sizeof(NSSLDestroyContextRequest), NULL, 0); internal::freeIpcBuffer(buf); return static_cast<NSSLError>(error); } NSSLError NSSLAddServerPKI(NSSLContextHandle context, NSSLCertID certId) { if (!internal::isInitialised()) { return NSSLError::LibNotReady; } auto buf = internal::allocateIpcBuffer(sizeof(NSSLAddServerPKIRequest)); if (!buf) { return NSSLError::OutOfMemory; } auto request = virt_cast<NSSLAddServerPKIRequest *>(buf); request->context = context; request->cert = certId; auto error = IOS_Ioctl(sSslData->handle, NSSLCommand::AddServerPKI, request, sizeof(NSSLAddServerPKIRequest), NULL, 0); internal::freeIpcBuffer(buf); return static_cast<NSSLError>(error); } NSSLError NSSLAddServerPKIExternal(NSSLContextHandle context, virt_ptr<uint8_t> cert, uint32_t certSize, NSSLCertType certType) { if (!internal::isInitialised()) { return NSSLError::LibNotReady; } if (!cert || !certSize) { return NSSLError::InvalidArg; } if (certType != NSSLCertType::Unknown0) { return NSSLError::InvalidCertType; } auto buf = internal::allocateIpcBuffer(0x88); if (!buf) { return NSSLError::OutOfMemory; } auto vec = virt_cast<IOSVec *>(buf); vec[0].vaddr = virt_cast<virt_addr>(cert); vec[0].len = certSize; auto request = virt_cast<NSSLAddServerPKIExternalRequest *>(virt_cast<virt_addr>(buf) + 0x80); vec[1].vaddr = virt_cast<virt_addr>(request); vec[1].len = static_cast<uint32_t>(sizeof(NSSLAddServerPKIExternalRequest)); request->context = context; request->certType = certType; auto error = IOS_Ioctlv(sSslData->handle, NSSLCommand::AddServerPKIExternal, 2, 0, vec); internal::freeIpcBuffer(buf); return static_cast<NSSLError>(error); } NSSLError NSSLExportInternalClientCertificate(NSSLCertID certId, virt_ptr<uint8_t> certBuffer, virt_ptr<uint32_t> certBufferSize, virt_ptr<NSSLCertType> certType, virt_ptr<uint8_t> privateKeyBuffer, virt_ptr<uint32_t> privateKeyBufferSize, virt_ptr<NSSLPrivateKeyType> privateKeyType) { if (!internal::isInitialised()) { return NSSLError::LibNotReady; } if (!certBufferSize || !certType || !privateKeyBufferSize || !privateKeyType) { return NSSLError::InvalidArg; } auto buf = internal::allocateIpcBuffer(0x80 + sizeof(NSSLExportInternalClientCertificateRequest)); if (!buf) { return NSSLError::OutOfMemory; } auto bufResponse = internal::allocateIpcBuffer(0x8); if (!bufResponse) { internal::freeIpcBuffer(buf); return NSSLError::OutOfMemory; } auto request = virt_cast<NSSLExportInternalClientCertificateRequest *>(virt_cast<virt_addr>(buf) + 0x80); request->certId = certId; auto vec = virt_cast<IOSVec *>(buf); vec[0].vaddr = virt_cast<virt_addr>(request); vec[0].len = static_cast<uint32_t>(sizeof(NSSLExportInternalClientCertificateRequest)); vec[1].vaddr = virt_cast<virt_addr>(certBuffer); vec[1].len = *certBufferSize; vec[2].vaddr = virt_cast<virt_addr>(privateKeyBuffer); vec[2].len = *privateKeyBufferSize; auto response = virt_cast<NSSLExportInternalClientCertificateResponse *>(bufResponse); vec[3].vaddr = virt_cast<virt_addr>(response); vec[3].len = static_cast<uint32_t>(sizeof(NSSLExportInternalClientCertificateResponse)); auto error = IOS_Ioctlv(sSslData->handle, NSSLCommand::ExportInternalClientCertificate, 1, 3, vec); if (error >= IOSError::OK) { *certType = response->certType; *certBufferSize = response->certSize; *privateKeyType = response->privateKeyType; *privateKeyBufferSize = response->privateKeySize; } internal::freeIpcBuffer(buf); internal::freeIpcBuffer(bufResponse); return static_cast<NSSLError>(error); } NSSLError NSSLExportInternalServerCertificate(NSSLCertID certId, virt_ptr<uint8_t> certBuffer, virt_ptr<uint32_t> certBufferSize, virt_ptr<NSSLCertType> certType) { if (!internal::isInitialised()) { return NSSLError::LibNotReady; } if (!certBufferSize || !certType) { return NSSLError::InvalidArg; } auto buf = internal::allocateIpcBuffer(0x80 + sizeof(NSSLExportInternalServerCertificateRequest)); if (!buf) { return NSSLError::OutOfMemory; } auto bufResponse = internal::allocateIpcBuffer(0x8); if (!bufResponse) { internal::freeIpcBuffer(buf); return NSSLError::OutOfMemory; } auto request = virt_cast<NSSLExportInternalServerCertificateRequest *>(virt_cast<virt_addr>(buf) + 0x80); request->certId = certId; auto vec = virt_cast<IOSVec *>(buf); vec[0].vaddr = virt_cast<virt_addr>(request); vec[0].len = static_cast<uint32_t>(sizeof(NSSLExportInternalServerCertificateRequest)); vec[1].vaddr = virt_cast<virt_addr>(certBuffer); vec[1].len = *certBufferSize; auto response = virt_cast<NSSLExportInternalServerCertificateResponse *>(bufResponse); vec[2].vaddr = virt_cast<virt_addr>(response); vec[2].len = static_cast<uint32_t>(sizeof(NSSLExportInternalServerCertificateResponse)); auto error = IOS_Ioctlv(sSslData->handle, NSSLCommand::ExportInternalServerCertificate, 1, 2, vec); if (error >= IOSError::OK) { *certType = response->certType; *certBufferSize = response->certSize; } internal::freeIpcBuffer(buf); internal::freeIpcBuffer(bufResponse); return static_cast<NSSLError>(error); } namespace internal { static bool isInitialised() { auto initialised = true; OSLockMutex(virt_addrof(sSslData->lock)); if (sSslData->handle < 0 || !sSslData->messagePool) { initialised = false; } OSUnlockMutex(virt_addrof(sSslData->lock)); return initialised; } static virt_ptr<void> allocateIpcBuffer(uint32_t size) { auto buf = IPCBufPoolAllocate(sSslData->messagePool, size); if (buf) { std::memset(buf.get(), 0, size); } return buf; } static void freeIpcBuffer(virt_ptr<void> buf) { IPCBufPoolFree(sSslData->messagePool, buf); } } // namespace internal void Library::registerSslSymbols() { RegisterFunctionExport(NSSLInit); RegisterFunctionExport(NSSLFinish); RegisterFunctionExport(NSSLCreateContext); RegisterFunctionExport(NSSLDestroyContext); RegisterFunctionExport(NSSLAddServerPKI); RegisterFunctionExport(NSSLAddServerPKIExternal); RegisterFunctionExport(NSSLExportInternalClientCertificate); RegisterFunctionExport(NSSLExportInternalServerCertificate); RegisterDataInternal(sSslData); } } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_nssl.h ================================================ #pragma once #include "ios/nsec/ios_nsec_nssl.h" namespace cafe::nsysnet { using NSSLContextHandle = ios::nsec::NSSLContextHandle; using NSSLError = ios::nsec::NSSLError; using NSSLVersion = ios::nsec::NSSLVersion; using NSSLCertID = ios::nsec::NSSLCertID; using NSSLCertType = ios::nsec::NSSLCertType; using NSSLPrivateKeyType = ios::nsec::NSSLPrivateKeyType; NSSLError NSSLInit(); NSSLError NSSLFinish(); NSSLError NSSLCreateContext(NSSLVersion version); NSSLError NSSLDestroyContext(NSSLContextHandle context); NSSLError NSSLAddServerPKI(NSSLContextHandle context, NSSLCertID certId); NSSLError NSSLAddServerPKIExternal(NSSLContextHandle context, virt_ptr<uint8_t> cert, uint32_t certSize, NSSLCertType certType); NSSLError NSSLExportInternalClientCertificate(NSSLCertID certId, virt_ptr<uint8_t> certBuffer, virt_ptr<uint32_t> certBufferSize, virt_ptr<NSSLCertType> certType, virt_ptr<uint8_t> privateKeyBuffer, virt_ptr<uint32_t> privateKeyBufferSize, virt_ptr<NSSLPrivateKeyType> privateKeyType); NSSLError NSSLExportInternalServerCertificate(NSSLCertID certId, virt_ptr<uint8_t> certBuffer, virt_ptr<uint32_t> certBufferSize, virt_ptr<NSSLCertType> certType); } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_socket_lib.cpp ================================================ #include "nsysnet.h" #include "nsysnet_enum.h" #include "nsysnet_socket_lib.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_ghs.h" #include "cafe/libraries/coreinit/coreinit_ios.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include "cafe/libraries/coreinit/coreinit_ipcbufpool.h" #include "ios/net/ios_net_socket.h" #include "ios/ios_error.h" #include <charconv> #include <common/strutils.h> #include <optional> #include <string_view> namespace cafe::nsysnet { using namespace coreinit; using ios::net::SocketAddr; using ios::net::SocketAddrIn; using ios::net::SocketCommand; using ios::net::SocketDnsQueryType; using ios::net::SocketError; using ios::net::SocketFamily; using ios::net::SocketAcceptRequest; using ios::net::SocketBindRequest; using ios::net::SocketCloseRequest; using ios::net::SocketConnectRequest; using ios::net::SocketDnsQueryRequest; using ios::net::SocketGetPeerNameRequest; using ios::net::SocketGetSockNameRequest; using ios::net::SocketListenRequest; using ios::net::SocketRecvRequest; using ios::net::SocketSendRequest; using ios::net::SocketSetSockOptRequest; using ios::net::SocketSelectRequest; using ios::net::SocketSocketRequest; using ios::net::SocketAcceptResponse; using ios::net::SocketGetPeerNameResponse; using ios::net::SocketGetSockNameResponse; using ios::net::SocketSelectResponse; // We do not get this from ios::net because that uses phys_ptr, and here we // want virt_ptr. struct SocketDnsQueryResponse { be2_val<uint32_t> unk0x00; be2_val<uint32_t> unk0x04; be2_val<uint32_t> sendTime; be2_val<uint32_t> expireTime; be2_val<uint16_t> tries; be2_val<uint16_t> lport; be2_val<uint16_t> id; UNKNOWN(0x2); be2_val<uint32_t> unk0x18; be2_val<uint32_t> replies; be2_val<uint32_t> ipaddrs; be2_array<uint32_t, 10> ipaddrList; be2_array<virt_ptr<uint32_t>, 10> hostentIpaddrList; be2_val<uint32_t> err; be2_val<uint32_t> rcode; be2_array<char, 256> dnsNames; be2_array<char, 129> unk0x17C; UNKNOWN(0x27C - 0x1FD); be2_val<uint32_t> authsIp; be2_array<virt_ptr<char>, 2> aliases; UNKNOWN(0x290 - 0x288); be2_struct<SocketHostEnt> hostent; be2_val<SocketDnsQueryType> queryType; be2_array<uint8_t, 2> unk0x2A5; UNKNOWN(0x2B0 - 0x2A7); be2_val<uint32_t> dnsReq; be2_val<uint32_t> next; //! Used to adjust pointers in hostent be2_val<virt_addr> selfPointerOffset; }; CHECK_OFFSET(SocketDnsQueryResponse, 0x00, unk0x00); CHECK_OFFSET(SocketDnsQueryResponse, 0x04, unk0x04); CHECK_OFFSET(SocketDnsQueryResponse, 0x08, sendTime); CHECK_OFFSET(SocketDnsQueryResponse, 0x0C, expireTime); CHECK_OFFSET(SocketDnsQueryResponse, 0x10, tries); CHECK_OFFSET(SocketDnsQueryResponse, 0x12, lport); CHECK_OFFSET(SocketDnsQueryResponse, 0x14, id); CHECK_OFFSET(SocketDnsQueryResponse, 0x18, unk0x18); CHECK_OFFSET(SocketDnsQueryResponse, 0x1C, replies); CHECK_OFFSET(SocketDnsQueryResponse, 0x20, ipaddrs); CHECK_OFFSET(SocketDnsQueryResponse, 0x24, ipaddrList); CHECK_OFFSET(SocketDnsQueryResponse, 0x4C, hostentIpaddrList); CHECK_OFFSET(SocketDnsQueryResponse, 0x74, err); CHECK_OFFSET(SocketDnsQueryResponse, 0x78, rcode); CHECK_OFFSET(SocketDnsQueryResponse, 0x7C, dnsNames); CHECK_OFFSET(SocketDnsQueryResponse, 0x17C, unk0x17C); CHECK_OFFSET(SocketDnsQueryResponse, 0x27C, authsIp); CHECK_OFFSET(SocketDnsQueryResponse, 0x280, aliases); CHECK_OFFSET(SocketDnsQueryResponse, 0x290, hostent); CHECK_OFFSET(SocketDnsQueryResponse, 0x2A4, queryType); CHECK_OFFSET(SocketDnsQueryResponse, 0x2A5, unk0x2A5); CHECK_OFFSET(SocketDnsQueryResponse, 0x2B0, dnsReq); CHECK_OFFSET(SocketDnsQueryResponse, 0x2B4, next); CHECK_OFFSET(SocketDnsQueryResponse, 0x2B8, selfPointerOffset); CHECK_SIZE(SocketDnsQueryResponse, 0x2BC); struct SocketLibData { static constexpr uint32_t MessageCount = 0x20; static constexpr uint32_t MessageSize = 0x100; SocketLibData() { OSInitMutex(virt_addrof(lock)); } be2_struct<OSMutex> lock; be2_val<IOSHandle> handle = IOSHandle { -1 }; be2_virt_ptr<IPCBufPool> messagePool; be2_array<uint8_t, MessageCount * MessageSize> messageBuffer; be2_val<uint32_t> messageCount; be2_val<ResolverAlloc> userResolverAlloc; be2_val<ResolverFree> userResolverFree; be2_val<GetHostError> getHostError; be2_struct<SocketHostEnt> getHostByNameHostEnt; be2_array<char, 32> getHostByNameName; be2_val<uint32_t> getHostByNameIpAddr; be2_array<virt_ptr<uint32_t>, 2> getHostByNameAddrList; be2_struct<SocketDnsQueryResponse> getHostByNameQuery; }; static virt_ptr<SocketLibData> sSocketLibData; namespace internal { static bool isInitialised(); static virt_ptr<void> allocateIpcBuffer(uint32_t size); static void freeIpcBuffer(virt_ptr<void> buf); static int32_t decodeIosError(IOSError err); static int32_t performDnsQuery(virt_ptr<const char> name, SocketDnsQueryType queryType, uint32_t a3, uint32_t a4, virt_ptr<SocketDnsQueryResponse> outResponse, bool isAsync); } // namespace internal int32_t socket_lib_init() { auto error = 0; OSLockMutex(virt_addrof(sSocketLibData->lock)); if (sSocketLibData->handle < 0) { auto iosError = IOS_Open(make_stack_string("/dev/socket"), IOSOpenMode::None); if (iosError < 0) { sSocketLibData->handle = -1; error = SocketError::NoLibRm; goto out; } sSocketLibData->handle = static_cast<IOSHandle>(iosError); } if (!sSocketLibData->messagePool) { sSocketLibData->messagePool = IPCBufPoolCreate(virt_addrof(sSocketLibData->messageBuffer), static_cast<uint32_t>(sSocketLibData->messageBuffer.size()), SocketLibData::MessageSize, virt_addrof(sSocketLibData->messageCount), 1); if (!sSocketLibData->messagePool) { error = SocketError::NoMem; IOS_Close(sSocketLibData->handle); sSocketLibData->handle = -1; goto out; } } out: OSUnlockMutex(virt_addrof(sSocketLibData->lock)); gh_set_errno(error); return error == 0 ? 0 : -1; } int32_t socket_lib_finish() { auto error = 0; OSLockMutex(virt_addrof(sSocketLibData->lock)); if (sSocketLibData->handle >= 0) { IOS_Close(sSocketLibData->handle); sSocketLibData->handle = -1; } else { error = SocketError::NoLibRm; } OSUnlockMutex(virt_addrof(sSocketLibData->lock)); gh_set_errno(error); return error == 0 ? 0 : -1; } int32_t set_resolver_allocator(ResolverAlloc allocFn, ResolverFree freeFn) { if (!allocFn || !freeFn) { return -1; } sSocketLibData->userResolverAlloc = allocFn; sSocketLibData->userResolverFree = freeFn; return 0; } int32_t accept(int32_t sockfd, virt_ptr<SocketAddr> addr, virt_ptr<int32_t> addrlen) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (addr && (!addrlen || *addrlen != sizeof(SocketAddrIn))) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketAcceptRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketAcceptRequest *>(buf); request->fd = sockfd; if (addrlen) { request->addrlen = *addrlen; } else { request->addrlen = static_cast<int32_t>(sizeof(SocketAddrIn)); } auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Accept, request, sizeof(SocketAcceptRequest), request, sizeof(SocketAcceptResponse)); if (error >= IOSError::OK) { auto response = virt_cast<SocketAcceptRequest *>(buf); if (addr) { std::memcpy(addr.get(), std::addressof(response->addr), response->addrlen); *addrlen = response->addrlen; } } auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t bind(int32_t sockfd, virt_ptr<SocketAddr> addr, int32_t addrlen) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (!addr || addr->sa_family != SocketFamily::Inet || addrlen != sizeof(SocketAddrIn)) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketBindRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketBindRequest *>(buf); request->fd = sockfd; request->addr = *virt_cast<SocketAddrIn *>(addr); request->addrlen = addrlen; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Bind, request, sizeof(SocketBindRequest), NULL, 0); auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t connect(int32_t sockfd, virt_ptr<SocketAddr> addr, int32_t addrlen) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (!addr || addr->sa_family != SocketFamily::Inet || addrlen != sizeof(SocketAddrIn)) { gh_set_errno(SocketError::Inval); return -1; } // TODO: if set_multicast_state(TRUE) auto buf = internal::allocateIpcBuffer(sizeof(SocketConnectRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketConnectRequest *>(buf); request->fd = sockfd; request->addr = *virt_cast<SocketAddrIn *>(addr); request->addrlen = addrlen; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Connect, request, sizeof(SocketConnectRequest), NULL, 0); auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } /** * Parse IP address from a string. * * Similar to inet_aton but different: * - Requires 1-3 dots * - Only supports 0-255 for each number * * Which means unlike inet_aton: * - a = invalid * - a.b = a.0.0.b * - a.b.c = a.0.b.c * - a.b.c.d = a.b.c.d */ static std::optional<uint32_t> parseIpAddress(const char *host) { if (!host) { return {}; } auto len = strnlen(host, 256); if (!len || len == 256) { return {}; } auto sv = std::string_view { host, len }; // Count dots and ensure no invalid characters for an IP address auto numDots = 0; for (auto c : sv) { if (c == '.') { numDots++; } else if (c < '0' || c > '9') { return {}; } } if (numDots < 1 || numDots > 3) { return {}; } auto start = std::string_view::size_type { 0 }; auto ip = std::array<uint8_t, 4> { 0, 0, 0, 0 }; for (auto idx = 0; idx < numDots + 1; ++idx) { auto component = 0; auto end = std::min(sv.find_first_of('.', start), sv.size()); auto result = std::from_chars(sv.data() + start, sv.data() + end, component); if (result.ec != std::errc() || component < 0 || component > 255) { return {}; } if (idx == 0) { ip[idx] = static_cast<uint8_t>(component); } else { ip[idx + (3 - numDots)] = static_cast<uint8_t>(component); } start = end + 1; } auto output = uint32_t { 0 }; std::memcpy(&output, ip.data(), 4); return output; } virt_ptr<GetHostError> get_h_errno() { return virt_addrof(sSocketLibData->getHostError); } virt_ptr<SocketHostEnt> gethostbyname(virt_ptr<const char> name) { if (!name || !name[0]) { sSocketLibData->getHostError = GetHostError::NoRecovery; return nullptr; } // If the host name is just an ip address we can return immediately. if (auto ipaddr = parseIpAddress(name.get()); ipaddr.has_value()) { auto result = virt_addrof(sSocketLibData->getHostByNameHostEnt); result->h_aliases = nullptr; result->h_length = 4; result->h_addrtype = 2; result->h_name = virt_addrof(sSocketLibData->getHostByNameName); string_copy(result->h_name.get(), name.get(), 32); result->h_addr_list = virt_addrof(sSocketLibData->getHostByNameAddrList); result->h_addr_list[0] = virt_addrof(sSocketLibData->getHostByNameIpAddr); result->h_addr_list[1] = nullptr; sSocketLibData->getHostByNameIpAddr = ipaddr.value(); return result; } auto error = internal::performDnsQuery( name, SocketDnsQueryType::GetHostByName, 0u, 0u, virt_addrof(sSocketLibData->getHostByNameQuery), false); if (error < 0 || !sSocketLibData->getHostByNameQuery.ipaddrs) { sSocketLibData->getHostError = GetHostError::HostNotFound; return nullptr; } // Update ip address list sSocketLibData->getHostByNameQuery.hostent.h_addrtype = 2; sSocketLibData->getHostByNameQuery.hostent.h_length = 4; sSocketLibData->getHostByNameQuery.hostent.h_addr_list = virt_addrof(sSocketLibData->getHostByNameQuery.hostentIpaddrList); for (auto i = 0u; i < sSocketLibData->getHostByNameQuery.ipaddrs; ++i) { sSocketLibData->getHostByNameQuery.hostentIpaddrList[i] = virt_addrof(sSocketLibData->getHostByNameQuery.ipaddrList[i]); } sSocketLibData->getHostByNameQuery .hostentIpaddrList[sSocketLibData->getHostByNameQuery.ipaddrs] = nullptr; sSocketLibData->getHostError = GetHostError::OK; return virt_addrof(sSocketLibData->getHostByNameQuery.hostent); } int32_t getpeername(int32_t sockfd, virt_ptr<SocketAddr> addr, virt_ptr<uint32_t> addrlen) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (!addr || !addrlen || *addrlen < sizeof(SocketAddrIn)) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketGetPeerNameRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto response = virt_cast<SocketGetPeerNameRequest *>(buf); auto request = virt_cast<SocketGetPeerNameResponse *>(buf); request->fd = sockfd; request->addrlen = *addrlen; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::GetPeerName, request, sizeof(SocketGetPeerNameRequest), response, sizeof(SocketGetPeerNameResponse)); if (error >= IOSError::OK) { std::memcpy(addr.get(), std::addressof(response->addr), response->addrlen); *addrlen = response->addrlen; } auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t getsockname(int32_t sockfd, virt_ptr<SocketAddr> addr, virt_ptr<uint32_t> addrlen) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (!addr || !addrlen || *addrlen < sizeof(SocketAddrIn)) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketGetSockNameRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto response = virt_cast<SocketGetSockNameRequest *>(buf); auto request = virt_cast<SocketGetSockNameResponse *>(buf); request->fd = sockfd; request->addrlen = *addrlen; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::GetSockName, request, sizeof(SocketGetSockNameRequest), response, sizeof(SocketGetSockNameResponse)); if (error >= IOSError::OK) { std::memcpy(addr.get(), std::addressof(response->addr), response->addrlen); *addrlen = response->addrlen; } auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t listen(int32_t sockfd, int32_t backlog) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (backlog < 0) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketListenRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketListenRequest *>(buf); request->fd = sockfd; request->backlog = backlog; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Listen, request, sizeof(SocketListenRequest), NULL, 0); auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } static void prepareUnalignedBuffer(virt_ptr<const uint8_t> buffer, int32_t len, virt_ptr<uint8_t> alignedBeforeBuffer, virt_ptr<uint8_t> alignedAfterBuffer, virt_ptr<IOSVec> alignedBeforeVec, virt_ptr<IOSVec> alignedMiddleVec, virt_ptr<IOSVec> alignedAfterVec, bool input) { auto bufferAlignedStart = align_up(buffer, IOSVecAlign); auto bufferAlignedEnd = align_down(buffer + len, IOSVecAlign); auto bufferEnd = buffer + len; if (bufferAlignedStart != buffer) { alignedBeforeVec->vaddr = virt_cast<virt_addr>(alignedBeforeBuffer); alignedBeforeVec->len = static_cast<uint32_t>(bufferAlignedStart - buffer); if (input) { std::memcpy(alignedBeforeBuffer.get(), buffer.get(), alignedBeforeVec->len); } } alignedMiddleVec->vaddr = virt_cast<virt_addr>(bufferAlignedStart); alignedMiddleVec->len = static_cast<uint32_t>(bufferEnd - bufferAlignedStart); if (bufferAlignedEnd != bufferEnd) { alignedAfterVec->vaddr = virt_cast<virt_addr>(alignedAfterBuffer); alignedAfterVec->len = static_cast<uint32_t>(bufferEnd - bufferAlignedEnd); if (input) { std::memcpy(alignedAfterBuffer.get(), bufferAlignedEnd.get(), alignedAfterVec->len); } } } static void parseUnalignedBuffer(virt_ptr<uint8_t> buffer, int32_t len, virt_ptr<IOSVec> alignedBeforeVec, virt_ptr<IOSVec> alignedMiddleVec, virt_ptr<IOSVec> alignedAfterVec) { if (alignedBeforeVec->len) { std::memcpy(buffer.get(), virt_cast<void *>(alignedBeforeVec->vaddr).get(), alignedBeforeVec->len); } if (alignedAfterVec->len) { auto offset = alignedBeforeVec->len + alignedMiddleVec->len; std::memcpy(buffer.get() + offset, virt_cast<void *>(alignedAfterVec->vaddr).get(), alignedAfterVec->len); } } int32_t recv(int32_t sockfd, virt_ptr<void> buffer, int32_t len, int32_t flags) { auto alignedBuffers = StackArray<uint8_t, IOSVecAlign * 2, IOSVecAlign> { }; if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (!buffer || len < 0) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(0x88); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketRecvRequest *>(virt_cast<char *>(buf) + 0x80); request->fd = sockfd; request->flags = flags; auto vecs = virt_cast<IOSVec *>(buf); vecs[0].vaddr = virt_cast<virt_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(SocketRecvRequest)); vecs[1].vaddr = virt_addr { 0 }; vecs[1].len = 0u; vecs[2].vaddr = virt_addr { 0 }; vecs[2].len = 0u; vecs[3].vaddr = virt_addr{ 0 }; vecs[3].len = 0u; if (align_check(buffer.get(), IOSVecAlign) && align_check(len, IOSVecAlign)) { vecs[1].vaddr = virt_cast<virt_addr>(buffer); vecs[1].len = static_cast<uint32_t>(len); } else { auto alignedBeforeBuffer = virt_ptr<uint8_t> { alignedBuffers }; auto alignedAfterBuffer = alignedBeforeBuffer + IOSVecAlign; prepareUnalignedBuffer(virt_cast<uint8_t *>(buffer), len, alignedBeforeBuffer, alignedAfterBuffer, virt_addrof(vecs[1]), virt_addrof(vecs[2]), virt_addrof(vecs[3]), false); } auto error = IOS_Ioctlv(sSocketLibData->handle, SocketCommand::Recv, 1, 3, vecs); if (error >= IOSError::OK) { parseUnalignedBuffer(virt_cast<uint8_t *>(buffer), len, virt_addrof(vecs[1]), virt_addrof(vecs[2]), virt_addrof(vecs[3])); } auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t send(int32_t sockfd, virt_ptr<const void> buffer, int32_t len, int32_t flags) { auto alignedBuffers = StackArray<uint8_t, IOSVecAlign * 2, IOSVecAlign> { }; if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (!buffer || len < 0) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(0x88); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketSendRequest *>(virt_cast<char *>(buf) + 0x80); request->fd = sockfd; request->flags = flags; auto vecs = virt_cast<IOSVec *>(buf); vecs[0].vaddr = virt_cast<virt_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(SocketSendRequest)); vecs[1].vaddr = virt_addr { 0 }; vecs[1].len = 0u; vecs[2].vaddr = virt_addr { 0 }; vecs[2].len = 0u; vecs[3].vaddr = virt_addr{ 0 }; vecs[3].len = 0u; if (align_check(buffer.get(), IOSVecAlign) && align_check(len, IOSVecAlign)) { vecs[1].vaddr = virt_cast<virt_addr>(buffer); vecs[1].len = static_cast<uint32_t>(len); } else { auto alignedBeforeBuffer = virt_ptr<uint8_t> { alignedBuffers }; auto alignedAfterBuffer = alignedBeforeBuffer + IOSVecAlign; prepareUnalignedBuffer(virt_cast<const uint8_t *>(buffer), len, alignedBeforeBuffer, alignedAfterBuffer, virt_addrof(vecs[1]), virt_addrof(vecs[2]), virt_addrof(vecs[3]), true); } auto error = IOS_Ioctlv(sSocketLibData->handle, SocketCommand::Send, 4, 0, vecs); auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t setsockopt(int32_t sockfd, int32_t level, int32_t optname, virt_ptr<void> optval, int32_t optlen) { StackArray<uint8_t, 0x40, IOSVecAlign> optvalBuffer; if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (level != -1 && level != 0 && level != 6) { gh_set_errno(SocketError::Inval); return -1; } if (optlen < 0 || static_cast<unsigned>(optlen) > optvalBuffer.size()) { gh_set_errno(SocketError::Inval); return -1; } if (!optval && optlen > 0) { gh_set_errno(SocketError::Inval); return -1; } auto buf = internal::allocateIpcBuffer(0x24); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } // Yes they really do overlap IOSVec and SetOptRequest... :) auto request = virt_cast<SocketSetSockOptRequest *>(buf); request->fd = sockfd; request->level = level; request->optname = optname; auto vecs = virt_cast<IOSVec *>(buf); vecs[0].vaddr = virt_cast<virt_addr>(optvalBuffer); vecs[0].len = static_cast<uint32_t>(optlen); std::memcpy(optvalBuffer.get(), optval.get(), optlen); vecs[1].vaddr = virt_cast<virt_addr>(request); vecs[1].len = static_cast<uint32_t>(sizeof(SocketSetSockOptRequest)); auto error = IOS_Ioctlv(sSocketLibData->handle, SocketCommand::SetSockOpt, 2, 0, vecs); auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t select(int32_t nfds, virt_ptr<SocketFdSet> readfds, virt_ptr<SocketFdSet> writefds, virt_ptr<SocketFdSet> exceptfds, virt_ptr<SocketTimeval> timeout) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } if (nfds < 0) { gh_set_errno(SocketError::NotInitialised); return -1; } if (timeout) { if (timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_sec > 1000000 || timeout->tv_usec > 1000000) { gh_set_errno(SocketError::Inval); return -1; } } if ((!readfds || !*readfds) && (!writefds || !*writefds) && (!exceptfds || !*exceptfds)) { if (!timeout) { gh_set_errno(SocketError::Inval); return -1; } else if (timeout->tv_sec == 0 && timeout->tv_usec == 0) { // No fds and timeout is 0 = success return 0; } } auto buf = internal::allocateIpcBuffer(sizeof(SocketSelectRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto response = virt_cast<SocketSelectResponse *>(buf); auto request = virt_cast<SocketSelectRequest *>(buf); request->nfds = nfds; request->readfds = 0u; request->writefds = 0u; request->exceptfds = 0u; request->hasTimeout = 0; if (readfds) { request->readfds = *readfds; } if (writefds) { request->writefds = *writefds; } if (exceptfds) { request->exceptfds = *exceptfds; } if (timeout) { request->timeout = *timeout; request->hasTimeout = 1; } auto result = 0; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Select, request, sizeof(SocketSelectRequest), response, sizeof(SocketSelectResponse)); result = internal::decodeIosError(error); if (result >= 0) { if (readfds) { *readfds = response->readfds; } if (writefds) { *writefds = response->writefds; } if (exceptfds) { *exceptfds = response->exceptfds; } } internal::freeIpcBuffer(buf); return result; } int32_t socket(int32_t family, int32_t type, int32_t proto) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketSocketRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketSocketRequest *>(buf); request->family = family; request->type = type; request->proto = proto; auto result = 0; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Socket, request, sizeof(SocketSocketRequest), NULL, 0); if (ios::getErrorCategory(error) == ios::ErrorCategory::Socket && ios::getErrorCode(error) == SocketError::GenericError) { // Map generic socket error to no memory. gh_set_errno(SocketError::NoMem); result = -1; } else { result = internal::decodeIosError(error); } internal::freeIpcBuffer(buf); return result; } int32_t socketclose(int32_t sockfd) { if (!internal::isInitialised()) { gh_set_errno(SocketError::NotInitialised); return -1; } auto buf = internal::allocateIpcBuffer(sizeof(SocketCloseRequest)); if (!buf) { gh_set_errno(SocketError::NoMem); return -1; } auto request = virt_cast<SocketCloseRequest *>(buf); request->fd = sockfd; auto error = IOS_Ioctl(sSocketLibData->handle, SocketCommand::Close, request, sizeof(SocketCloseRequest), NULL, 0); auto result = internal::decodeIosError(error); internal::freeIpcBuffer(buf); return result; } int32_t socketlasterr() { return gh_get_errno(); } namespace internal { static bool isInitialised() { auto initialised = true; OSLockMutex(virt_addrof(sSocketLibData->lock)); if (sSocketLibData->handle < 0 || !sSocketLibData->messagePool) { initialised = false; } OSUnlockMutex(virt_addrof(sSocketLibData->lock)); return initialised; } static virt_ptr<void> allocateIpcBuffer(uint32_t size) { auto buf = IPCBufPoolAllocate(sSocketLibData->messagePool, size); if (buf) { std::memset(buf.get(), 0, size); } return buf; } static void freeIpcBuffer(virt_ptr<void> buf) { IPCBufPoolFree(sSocketLibData->messagePool, buf); } static int32_t decodeIosError(IOSError err) { if (err >= 0) { gh_set_errno(0); return err; } auto category = ios::getErrorCategory(err); auto code = ios::getErrorCode(err); auto error = SocketError::Unknown; switch (category) { case ios::ErrorCategory::Socket: error = static_cast<SocketError>(code); break; case ios::ErrorCategory::Kernel: if (code == ios::Error::Access) { error = SocketError::Inval; } else if (code == ios::Error::Intr) { error = SocketError::Aborted; } else if (code == ios::Error::QFull) { error = SocketError::Busy; } else { error = SocketError::Unknown; } break; default: error = SocketError::Unknown; } gh_set_errno(error); return -1; } int32_t performDnsQuery(virt_ptr<const char> name, SocketDnsQueryType queryType, uint32_t a3, uint32_t a4, virt_ptr<SocketDnsQueryResponse> outResponse, bool isAsync) { auto size = 1152u; auto buffer = virt_ptr<void> { nullptr }; if (sSocketLibData->userResolverAlloc) { buffer = cafe::invoke(cpu::this_core::state(), sSocketLibData->userResolverAlloc, size + (IOSVecAlign - 1)); buffer = align_up(buffer, IOSVecAlign); } else { buffer = MEMAllocFromDefaultHeapEx(size, IOSVecAlign); } if (!buffer) { gh_set_errno(SocketError::NoMem); return -1; } std::memset(buffer.get(), 0, size); auto request = virt_cast<SocketDnsQueryRequest *>(virt_cast<char *>(buffer) + 0x80); string_copy(virt_addrof(request->name).get(), name.get(), request->name.size()); request->queryType = queryType; request->unk0x88 = a3; request->unk0x8C = a4; request->isAsync = isAsync ? uint8_t { 1 } : uint8_t { 0 }; auto response = virt_cast<SocketDnsQueryResponse *>(virt_cast<char *>(buffer) + 0x180); auto vecs = virt_cast<IOSVec *>(buffer); vecs[0].vaddr = virt_cast<virt_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(SocketDnsQueryRequest)); vecs[1].vaddr = virt_cast<virt_addr>(response); vecs[1].len = static_cast<uint32_t>(sizeof(SocketDnsQueryResponse)); auto error = IOS_Ioctlv(sSocketLibData->handle, SocketCommand::DnsQuery, 1, 1, vecs); std::memcpy(outResponse.get(), response.get(), sizeof(SocketDnsQueryResponse)); // Repair hostent pointers outResponse->hostent.h_aliases = virt_addrof(outResponse->aliases); for (auto i = 0u; i < outResponse->aliases.size(); ++i) { if (outResponse->aliases[i]) { outResponse->aliases[i] = virt_cast<char *>( virt_cast<virt_addr>(outResponse) + (virt_cast<virt_addr>(outResponse->aliases[i]) - outResponse->selfPointerOffset)); } } outResponse->hostent.h_name = virt_cast<char *>( virt_cast<virt_addr>(outResponse) + (virt_cast<virt_addr>(outResponse->hostent.h_name) - outResponse->selfPointerOffset)); if (sSocketLibData->userResolverFree) { cafe::invoke(cpu::this_core::state(), sSocketLibData->userResolverFree, buffer); } else { MEMFreeToDefaultHeap(buffer); } return error; } } // namespace internal void Library::registerSocketLibSymbols() { RegisterFunctionExport(socket_lib_init); RegisterFunctionExport(socket_lib_finish); RegisterFunctionExport(accept); RegisterFunctionExport(bind); RegisterFunctionExport(connect); RegisterFunctionExport(get_h_errno); RegisterFunctionExport(gethostbyname); RegisterFunctionExport(getpeername); RegisterFunctionExport(getsockname); RegisterFunctionExport(listen); RegisterFunctionExport(recv); RegisterFunctionExport(send); RegisterFunctionExport(set_resolver_allocator); RegisterFunctionExport(setsockopt); RegisterFunctionExport(select); RegisterFunctionExport(socket); RegisterFunctionExport(socketclose); RegisterFunctionExport(socketlasterr); RegisterDataInternal(sSocketLibData); } } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_socket_lib.h ================================================ #pragma once #include "ios/net/ios_net_socket.h" #include "nsysnet_enum.h" #include <cstdint> #include <libcpu/be2_struct.h> namespace cafe::nsysnet { using ios::net::SocketAddr; using ios::net::SocketFdSet; using ios::net::SocketTimeval; struct SocketHostEnt { be2_virt_ptr<char> h_name; be2_virt_ptr<virt_ptr<char>> h_aliases; be2_val<int32_t> h_addrtype; be2_val<int32_t> h_length; be2_virt_ptr<virt_ptr<uint32_t>> h_addr_list; }; CHECK_OFFSET(SocketHostEnt, 0x00, h_name); CHECK_OFFSET(SocketHostEnt, 0x04, h_aliases); CHECK_OFFSET(SocketHostEnt, 0x08, h_addrtype); CHECK_OFFSET(SocketHostEnt, 0x0C, h_length); CHECK_OFFSET(SocketHostEnt, 0x10, h_addr_list); CHECK_SIZE(SocketHostEnt, 0x14); using ResolverAlloc = virt_func_ptr<virt_ptr<void>(uint32_t)>; using ResolverFree = virt_func_ptr<void(virt_ptr<void>)>; int32_t socket_lib_init(); int32_t socket_lib_finish(); int32_t set_resolver_allocator(ResolverAlloc allocFn, ResolverFree freeFn); int32_t accept(int32_t sockfd, virt_ptr<SocketAddr> addr, virt_ptr<int32_t> addrlen); int32_t bind(int32_t sockfd, virt_ptr<SocketAddr> addr, int32_t addrlen); int32_t connect(int32_t sockfd, virt_ptr<SocketAddr> addr, int32_t addrlen); virt_ptr<GetHostError> get_h_errno(); virt_ptr<SocketHostEnt> gethostbyname(virt_ptr<const char> name); int32_t getpeername(int32_t sockfd, virt_ptr<SocketAddr> addr, virt_ptr<uint32_t> addrlen); int32_t getsockname(int32_t sockfd, virt_ptr<SocketAddr> addr, virt_ptr<uint32_t> addrlen); int32_t listen(int32_t sockfd, int32_t backlog); int32_t recv(int32_t sockfd, virt_ptr<void> buffer, int32_t len, int32_t flags); int32_t send(int32_t sockfd, virt_ptr<const void> buffer, int32_t len, int32_t flags); int32_t setsockopt(int32_t sockfd, int32_t level, int32_t optname, virt_ptr<void> optval, int32_t optlen); int32_t select(int32_t nfds, virt_ptr<SocketFdSet> readfds, virt_ptr<SocketFdSet> writefds, virt_ptr<SocketFdSet> exceptfds, virt_ptr<SocketTimeval> timeout); int32_t socket(int32_t family, int32_t type, int32_t proto); int32_t socketclose(int32_t sockfd); } // namespace cafe::nsysnet ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysuhs/nsysuhs.cpp ================================================ #include "nsysuhs.h" namespace cafe::nsysuhs { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nsysuhs ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysuhs/nsysuhs.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nsysuhs { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nsysuhs, "nsysuhs.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nsysuhs ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysuvd/nsysuvd.cpp ================================================ #include "nsysuvd.h" namespace cafe::nsysuvd { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::nsysuvd ================================================ FILE: src/libdecaf/src/cafe/libraries/nsysuvd/nsysuvd.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::nsysuvd { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::nsysuvd, "nsysuvd.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::nsysuvd ================================================ FILE: src/libdecaf/src/cafe/libraries/ntag/ntag.cpp ================================================ #include "ntag.h" namespace cafe::ntag { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::ntag ================================================ FILE: src/libdecaf/src/cafe/libraries/ntag/ntag.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::ntag { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::ntag, "ntag.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::ntag ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore.cpp ================================================ #include "padscore.h" namespace cafe::padscore { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerKpadSymbols(); registerWpadSymbols(); } } // namespace cafe::padscore ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::padscore { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::padscore, "padscore.rpl") { } protected: virtual void registerSymbols() override; private: void registerKpadSymbols(); void registerWpadSymbols(); }; } // namespace cafe::padscore ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore_enum.h ================================================ #ifndef CAFE_PADSCORE_ENUM_H #define CAFE_PADSCORE_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(padscore) ENUM_BEG(KPADReadError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(NoController, -2) ENUM_END(KPADReadError) ENUM_BEG(WPADBatteryLevel, uint8_t) ENUM_VALUE(Min, 0x0000) ENUM_VALUE(Low, 0x0001) ENUM_VALUE(Medium, 0x0002) ENUM_VALUE(High, 0x0003) ENUM_VALUE(Max, 0x0004) ENUM_END(WPADBatteryLevel) ENUM_BEG(WPADButton, uint32_t) ENUM_VALUE(Left, 0x0001) ENUM_VALUE(Right, 0x0002) ENUM_VALUE(Down, 0x0004) ENUM_VALUE(Up, 0x0008) ENUM_VALUE(Plus, 0x0010) ENUM_VALUE(Btn2, 0x0100) ENUM_VALUE(Btn1, 0x0200) ENUM_VALUE(B, 0x0400) ENUM_VALUE(A, 0x0800) ENUM_VALUE(Minus, 0x1000) ENUM_VALUE(Z, 0x2000) ENUM_VALUE(C, 0x4000) ENUM_VALUE(Home, 0x8000) ENUM_END(WPADButton) ENUM_BEG(WPADClassicButton, uint32_t) ENUM_VALUE(Up, 0x0001) ENUM_VALUE(Left, 0x0002) ENUM_VALUE(ZR, 0x0004) ENUM_VALUE(X, 0x0008) ENUM_VALUE(A, 0x0010) ENUM_VALUE(Y, 0x0020) ENUM_VALUE(B, 0x0040) ENUM_VALUE(ZL, 0x0080) ENUM_VALUE(R, 0x0200) ENUM_VALUE(Plus, 0x0400) ENUM_VALUE(Home, 0x0800) ENUM_VALUE(Minus, 0x1000) ENUM_VALUE(L, 0x2000) ENUM_VALUE(Down, 0x4000) ENUM_VALUE(Right, 0x8000) ENUM_END(WPADClassicButton) ENUM_BEG(WPADChan, uint32_t) ENUM_VALUE(Chan0, 0) ENUM_VALUE(Chan1, 1) ENUM_VALUE(Chan2, 2) ENUM_VALUE(Chan3, 3) ENUM_VALUE(NumChans, 4) ENUM_END(WPADChan) ENUM_BEG(WPADDataFormat, uint8_t) ENUM_VALUE(ProController, 22) ENUM_END(WPADDataFormat) ENUM_BEG(WPADExtensionType, uint8_t) ENUM_VALUE(Core, 0) ENUM_VALUE(Nunchuk, 1) ENUM_VALUE(Classic, 2) ENUM_VALUE(MotionPlus, 5) ENUM_VALUE(MotionPlusNunchuk, 6) ENUM_VALUE(MotionPlusClassic, 7) ENUM_VALUE(ProController, 31) ENUM_VALUE(NoController, 253) ENUM_END(WPADExtensionType) ENUM_BEG(WPADError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(NoController, -1) ENUM_END(WPADError) ENUM_BEG(WPADLibraryStatus, uint32_t) ENUM_VALUE(Uninitialised, 0) ENUM_VALUE(Initialised, 1) ENUM_END(WPADLibraryStatus) ENUM_BEG(WPADMotorCommand, uint32_t) ENUM_VALUE(Stop, 0) ENUM_VALUE(Rumble, 1) ENUM_END(WPADMotorCommand) ENUM_BEG(WPADProButton, uint32_t) ENUM_VALUE(Up, 0x00000001) ENUM_VALUE(Left, 0x00000002) ENUM_VALUE(ZR, 0x00000004) ENUM_VALUE(X, 0x00000008) ENUM_VALUE(A, 0x00000010) ENUM_VALUE(Y, 0x00000020) ENUM_VALUE(B, 0x00000040) ENUM_VALUE(ZL, 0x00000080) ENUM_VALUE(R, 0x00000200) ENUM_VALUE(Plus, 0x00000400) ENUM_VALUE(Home, 0x00000800) ENUM_VALUE(Minus, 0x00001000) ENUM_VALUE(L, 0x00002000) ENUM_VALUE(Down, 0x00004000) ENUM_VALUE(Right, 0x00008000) ENUM_VALUE(StickR, 0x00010000) ENUM_VALUE(StickL, 0x00020000) ENUM_VALUE(StickLUp, 0x00200000) ENUM_VALUE(StickLDown, 0x00100000) ENUM_VALUE(StickLLeft, 0x00040000) ENUM_VALUE(StickLRight, 0x00080000) ENUM_VALUE(StickRUp, 0x02000000) ENUM_VALUE(StickRDown, 0x01000000) ENUM_VALUE(StickRLeft, 0x00400000) ENUM_VALUE(StickRRight, 0x00800000) ENUM_END(WPADProButton) ENUM_NAMESPACE_EXIT(padscore) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef CAFE_PADSCORE_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore_kpad.cpp ================================================ #include "padscore.h" #include "padscore_kpad.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::padscore { void KPADInit() { decaf_warn_stub(); KPADInitEx(NULL, 0); } void KPADInitEx(virt_ptr<void> a1, uint32_t a2) { decaf_warn_stub(); WPADInit(); } void KPADShutdown() { decaf_warn_stub(); } //! Enable "Direct Pointing Device" void KPADEnableDPD(KPADChan chan) { decaf_warn_stub(); } //! Disable "Direct Pointing Device" void KPADDisableDPD(KPADChan chan) { decaf_warn_stub(); } uint32_t KPADGetMplsWorkSize() { return 0x5FE0; } void KPADSetMplsWorkarea(virt_ptr<void> buffer) { decaf_warn_stub(); } int32_t KPADRead(KPADChan chan, virt_ptr<KPADStatus> data, uint32_t size) { decaf_warn_stub(); auto readError = StackObject<KPADReadError> { }; auto result = KPADReadEx(chan, data, size, readError); if (*readError != KPADReadError::OK) { return *readError; } else { return result; } } int32_t KPADReadEx(KPADChan chan, virt_ptr<KPADStatus> data, uint32_t size, virt_ptr<KPADReadError> outError) { decaf_warn_stub(); if (outError) { *outError = KPADReadError::NoController; } return 0; } void Library::registerKpadSymbols() { RegisterFunctionExport(KPADInit); RegisterFunctionExport(KPADInitEx); RegisterFunctionExport(KPADShutdown); RegisterFunctionExport(KPADDisableDPD); RegisterFunctionExport(KPADEnableDPD); RegisterFunctionExport(KPADGetMplsWorkSize); RegisterFunctionExport(KPADSetMplsWorkarea); RegisterFunctionExport(KPADRead); RegisterFunctionExport(KPADReadEx); } } // namespace cafe::padscore ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore_kpad.h ================================================ #pragma once #include "padscore_enum.h" #include "padscore_wpad.h" #include <libcpu/be2_struct.h> namespace cafe::padscore { #pragma pack(push, 1) using KPADChan = WPADChan; using KPADDataFormat = WPADDataFormat; using KPADExtensionType = WPADExtensionType; struct KPADVec2D { be2_val<float> x; be2_val<float> y; }; CHECK_OFFSET(KPADVec2D, 0x00, x); CHECK_OFFSET(KPADVec2D, 0x04, y); CHECK_SIZE(KPADVec2D, 0x08); struct KPADExtClassicStatus { be2_val<uint32_t> hold; be2_val<uint32_t> trigger; be2_val<uint32_t> release; be2_struct<KPADVec2D> leftStick; be2_struct<KPADVec2D> rightStick; be2_val<float> leftTrigger; be2_val<float> rightTrigger; }; CHECK_OFFSET(KPADExtClassicStatus, 0x00, hold); CHECK_OFFSET(KPADExtClassicStatus, 0x04, trigger); CHECK_OFFSET(KPADExtClassicStatus, 0x08, release); CHECK_OFFSET(KPADExtClassicStatus, 0x0C, leftStick); CHECK_OFFSET(KPADExtClassicStatus, 0x14, rightStick); CHECK_OFFSET(KPADExtClassicStatus, 0x1C, leftTrigger); CHECK_OFFSET(KPADExtClassicStatus, 0x20, rightTrigger); struct KPADExtNunchukStatus { be2_struct<KPADVec2D> stick; }; CHECK_OFFSET(KPADExtNunchukStatus, 0x00, stick); struct KPADExtProControllerStatus { be2_val<uint32_t> hold; be2_val<uint32_t> trigger; be2_val<uint32_t> release; be2_struct<KPADVec2D> leftStick; be2_struct<KPADVec2D> rightStick; be2_val<int32_t> charging; be2_val<int32_t> wired; }; CHECK_OFFSET(KPADExtProControllerStatus, 0x00, hold); CHECK_OFFSET(KPADExtProControllerStatus, 0x04, trigger); CHECK_OFFSET(KPADExtProControllerStatus, 0x08, release); CHECK_OFFSET(KPADExtProControllerStatus, 0x0C, leftStick); CHECK_OFFSET(KPADExtProControllerStatus, 0x14, rightStick); CHECK_OFFSET(KPADExtProControllerStatus, 0x1C, charging); CHECK_OFFSET(KPADExtProControllerStatus, 0x20, wired); struct KPADExtStatus { union { be2_struct<KPADExtClassicStatus> classic; be2_struct<KPADExtNunchukStatus> nunchuk; be2_struct<KPADExtProControllerStatus> proController; PADDING(0x50); }; }; CHECK_SIZE(KPADExtStatus, 0x50); struct KPADStatus { //! Indicates what KPADButtons are held down be2_val<uint32_t> hold; //! Indicates what KPADButtons have been pressed since last sample be2_val<uint32_t> trigger; //! Indicates what KPADButtons have been released since last sample be2_val<uint32_t> release; UNKNOWN(5 * 4); be2_struct<KPADVec2D> pos; UNKNOWN(3 * 4); be2_struct<KPADVec2D> angle; UNKNOWN(8 * 4); //! Type of data stored in extStatus be2_val<KPADExtensionType> extensionType; //! Value from KPADError be2_val<int8_t> error; be2_val<uint8_t> posValid; be2_val<KPADDataFormat> format; // Extension data, check with extensionType to see what is valid to read be2_struct<KPADExtStatus> extStatus; UNKNOWN(16 * 4); }; CHECK_OFFSET(KPADStatus, 0x00, hold); CHECK_OFFSET(KPADStatus, 0x04, trigger); CHECK_OFFSET(KPADStatus, 0x08, release); CHECK_OFFSET(KPADStatus, 0x20, pos); CHECK_OFFSET(KPADStatus, 0x34, angle); CHECK_OFFSET(KPADStatus, 0x5C, extensionType); CHECK_OFFSET(KPADStatus, 0x5D, error); CHECK_OFFSET(KPADStatus, 0x5E, posValid); CHECK_OFFSET(KPADStatus, 0x5F, format); CHECK_OFFSET(KPADStatus, 0x60, extStatus); CHECK_SIZE(KPADStatus, 0xF0); #pragma pack(pop) void KPADInit(); void KPADInitEx(virt_ptr<void> a1, uint32_t a2); void KPADShutdown(); void KPADEnableDPD(KPADChan chan); void KPADDisableDPD(KPADChan chan); uint32_t KPADGetMplsWorkSize(); void KPADSetMplsWorkarea(virt_ptr<void> buffer); int32_t KPADRead(KPADChan chan, virt_ptr<KPADStatus> data, uint32_t size); int32_t KPADReadEx(KPADChan chan, virt_ptr<KPADStatus> data, uint32_t size, virt_ptr<KPADReadError> outError); } // namespace cafe::padscore ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore_wpad.cpp ================================================ #include "padscore.h" #include "padscore_wpad.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::padscore { struct WpadData { struct ChanData { be2_val<WPADDataFormat> dataFormat; be2_val<WPADConnectCallback> connectCallback; be2_val<WPADExtensionCallback> extensionCallback; be2_val<WPADSamplingCallback> samplingCallback; }; be2_val<WPADLibraryStatus> status; be2_array<ChanData, WPADChan::NumChans> chanData; }; static virt_ptr<WpadData> sWpadData = nullptr; void WPADInit() { sWpadData->status = WPADLibraryStatus::Initialised; } WPADLibraryStatus WPADGetStatus() { return sWpadData->status; } void WPADShutdown() { sWpadData->status = WPADLibraryStatus::Uninitialised; } void WPADControlMotor(WPADChan chan, WPADMotorCommand command) { decaf_warn_stub(); } void WPADDisconnect(WPADChan chan) { decaf_warn_stub(); } void WPADEnableURCC(BOOL enable) { decaf_warn_stub(); } void WPADEnableWiiRemote(BOOL enable) { decaf_warn_stub(); } WPADBatteryLevel WPADGetBatteryLevel(WPADChan chan) { decaf_warn_stub(); return WPADBatteryLevel::High; } int8_t WPADGetSpeakerVolume() { decaf_warn_stub(); return 0; } WPADError WPADProbe(WPADChan chan, virt_ptr<WPADExtensionType> outExtensionType) { decaf_warn_stub(); if (outExtensionType) { *outExtensionType = WPADExtensionType::NoController; } return WPADError::NoController; } void WPADRead(WPADChan chan, virt_ptr<void> data) { decaf_warn_stub(); if (data) { auto baseStatus = virt_cast<WPADStatus *>(data); baseStatus->err = static_cast<int8_t>(WPADError::NoController); baseStatus->extensionType = WPADExtensionType::NoController; } } void WPADSetAutoSleepTime(uint8_t time) { decaf_warn_stub(); } WPADConnectCallback WPADSetConnectCallback(WPADChan chan, WPADConnectCallback callback) { decaf_warn_stub(); if (chan >= WPADChan::NumChans) { return nullptr; } auto prev = sWpadData->chanData[chan].connectCallback; sWpadData->chanData[chan].connectCallback = callback; return prev; } WPADError WPADSetDataFormat(WPADChan chan, WPADDataFormat format) { decaf_warn_stub(); if (chan < WPADChan::NumChans) { sWpadData->chanData[chan].dataFormat = format; } return WPADError::NoController; } WPADExtensionCallback WPADSetExtensionCallback(WPADChan chan, WPADExtensionCallback callback) { decaf_warn_stub(); if (chan >= WPADChan::NumChans) { return nullptr; } auto prev = sWpadData->chanData[chan].extensionCallback; sWpadData->chanData[chan].extensionCallback = callback; return prev; } WPADSamplingCallback WPADSetSamplingCallback(WPADChan chan, WPADSamplingCallback callback) { decaf_warn_stub(); if (chan >= WPADChan::NumChans) { return nullptr; } auto prev = sWpadData->chanData[chan].samplingCallback; sWpadData->chanData[chan].samplingCallback = callback; return prev; } void Library::registerWpadSymbols() { RegisterFunctionExport(WPADInit); RegisterFunctionExport(WPADGetStatus); RegisterFunctionExport(WPADShutdown); RegisterFunctionExport(WPADControlMotor); RegisterFunctionExport(WPADDisconnect); RegisterFunctionExport(WPADEnableURCC); RegisterFunctionExport(WPADEnableWiiRemote); RegisterFunctionExport(WPADGetBatteryLevel); RegisterFunctionExport(WPADGetSpeakerVolume); RegisterFunctionExport(WPADProbe); RegisterFunctionExport(WPADRead); RegisterFunctionExport(WPADSetAutoSleepTime); RegisterFunctionExport(WPADSetConnectCallback); RegisterFunctionExport(WPADSetDataFormat); RegisterFunctionExport(WPADSetExtensionCallback); RegisterFunctionExport(WPADSetSamplingCallback); RegisterDataInternal(sWpadData); } } // namespace cafe::padscore ================================================ FILE: src/libdecaf/src/cafe/libraries/padscore/padscore_wpad.h ================================================ #pragma once #include "padscore_enum.h" #include <libcpu/be2_struct.h> namespace cafe::padscore { #pragma pack(push, 1) using WPADConnectCallback = virt_func_ptr< void (WPADChan chan, WPADError error)>; using WPADExtensionCallback = virt_func_ptr< void (WPADChan chan, WPADError error)>; using WPADSamplingCallback = virt_func_ptr< void (WPADChan chan)>; struct WPADVec2D { be2_val<int16_t> x; be2_val<int16_t> y; }; CHECK_OFFSET(WPADVec2D, 0x00, x); CHECK_OFFSET(WPADVec2D, 0x02, y); CHECK_SIZE(WPADVec2D, 0x04); struct WPADStatus { UNKNOWN(0x28); be2_val<WPADExtensionType> extensionType; be2_val<int8_t> err; PADDING(2); }; CHECK_OFFSET(WPADStatus, 0x28, extensionType); CHECK_OFFSET(WPADStatus, 0x29, err); CHECK_SIZE(WPADStatus, 0x2C); struct WPADStatusProController { be2_struct<WPADStatus> base; be2_val<uint32_t> buttons; be2_struct<WPADVec2D> leftStick; be2_struct<WPADVec2D> rightStick; UNKNOWN(8); be2_val<WPADDataFormat> dataFormat; PADDING(3); }; CHECK_OFFSET(WPADStatusProController, 0x00, base); CHECK_OFFSET(WPADStatusProController, 0x2C, buttons); CHECK_OFFSET(WPADStatusProController, 0x30, leftStick); CHECK_OFFSET(WPADStatusProController, 0x34, rightStick); CHECK_OFFSET(WPADStatusProController, 0x40, dataFormat); CHECK_SIZE(WPADStatusProController, 0x44); void WPADInit(); WPADLibraryStatus WPADGetStatus(); void WPADShutdown(); void WPADControlMotor(WPADChan chan, WPADMotorCommand command); void WPADDisconnect(WPADChan chan); void WPADEnableURCC(BOOL enable); void WPADEnableWiiRemote(BOOL enable); WPADBatteryLevel WPADGetBatteryLevel(WPADChan chan); int8_t WPADGetSpeakerVolume(); WPADError WPADProbe(WPADChan chan, virt_ptr<WPADExtensionType> outExtensionType); void WPADRead(WPADChan chan, virt_ptr<void> data); void WPADSetAutoSleepTime(uint8_t time); WPADConnectCallback WPADSetConnectCallback(WPADChan chan, WPADConnectCallback callback); WPADError WPADSetDataFormat(WPADChan chan, WPADDataFormat format); WPADExtensionCallback WPADSetExtensionCallback(WPADChan chan, WPADExtensionCallback callback); WPADSamplingCallback WPADSetSamplingCallback(WPADChan chan, WPADSamplingCallback callback); #pragma pack(pop) } // namespace cafe::padscore ================================================ FILE: src/libdecaf/src/cafe/libraries/proc_ui/proc_ui.cpp ================================================ #include "proc_ui.h" namespace cafe::proc_ui { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerMessagesFunctions(); } } // namespace cafe::proc_ui ================================================ FILE: src/libdecaf/src/cafe/libraries/proc_ui/proc_ui.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::proc_ui { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::proc_ui, "proc_ui.rpl") { } protected: virtual void registerSymbols() override; private: void registerMessagesFunctions(); }; } // namespace cafe::proc_ui ================================================ FILE: src/libdecaf/src/cafe/libraries/proc_ui/proc_ui_enum.h ================================================ #ifndef CAFE_PROC_UI_ENUM_H #define CAFE_PROC_UI_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(proc_ui) ENUM_BEG(ProcUICallbackType, uint32_t) ENUM_VALUE(Acquire, 0) ENUM_VALUE(Release, 1) ENUM_VALUE(Exit, 2) ENUM_VALUE(NetIoStart, 3) ENUM_VALUE(NetIoStop, 4) ENUM_VALUE(HomeButtonDenied, 5) ENUM_VALUE(Max, 6) ENUM_END(ProcUICallbackType) ENUM_BEG(ProcUIStatus, uint32_t) ENUM_VALUE(InForeground, 0) ENUM_VALUE(InBackground, 1) ENUM_VALUE(ReleaseForeground, 2) ENUM_VALUE(Exiting, 3) ENUM_END(ProcUIStatus) ENUM_NAMESPACE_EXIT(proc_ui) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // CAFE_PROC_UI_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/proc_ui/proc_ui_messages.cpp ================================================ #include "proc_ui.h" #include "proc_ui_messages.h" namespace cafe::proc_ui { struct RegisteredCallback { ProcUICallback callback; virt_ptr<void> param; }; struct MessagesData { be2_val<BOOL> running; be2_val<BOOL> sentAcquireMessage; be2_val<ProcUISaveCallback> saveCallback; be2_val<ProcUISaveCallbackEx> saveCallbackEx; be2_virt_ptr<void> saveCallbackExUserArg; be2_array<RegisteredCallback, ProcUICallbackType::Max> registeredCallbacks; }; static virt_ptr<MessagesData> sMessagesData = nullptr; void ProcUIInit(ProcUISaveCallback saveCallback) { sMessagesData->saveCallback = saveCallback; sMessagesData->saveCallbackEx = nullptr; sMessagesData->saveCallbackExUserArg = nullptr; sMessagesData->running = TRUE; } void ProcUIInitEx(ProcUISaveCallbackEx saveCallbackEx, virt_ptr<void> arg) { sMessagesData->saveCallback = nullptr; sMessagesData->saveCallbackEx = saveCallbackEx; sMessagesData->saveCallbackExUserArg = arg; } BOOL ProcUIIsRunning() { return sMessagesData->running; } ProcUIStatus ProcUIProcessMessages(BOOL block) { // TODO: ProcUIProcessMessages return ProcUIStatus::InForeground; } ProcUIStatus ProcUISubProcessMessages(BOOL block) { // TODO: ProcUISubProcessMessages return ProcUIStatus::InForeground; } void ProcUIRegisterCallback(ProcUICallbackType type, ProcUICallback callback, virt_ptr<void> userArg, uint32_t unk) { if (type < sMessagesData->registeredCallbacks.size()) { sMessagesData->registeredCallbacks[type].callback = callback; sMessagesData->registeredCallbacks[type].param = userArg; } } void ProcUISetMEM1Storage(virt_ptr<void> buffer, uint32_t size) { // TODO: ProcUISetMEM1Storage } void Library::registerMessagesFunctions() { RegisterFunctionExport(ProcUIInit); RegisterFunctionExport(ProcUIInitEx); RegisterFunctionExport(ProcUIIsRunning); RegisterFunctionExport(ProcUIProcessMessages); RegisterFunctionExport(ProcUISubProcessMessages); RegisterFunctionExport(ProcUIRegisterCallback); RegisterFunctionExport(ProcUISetMEM1Storage); RegisterDataInternal(sMessagesData); } } // namespace cafe::proc_ui ================================================ FILE: src/libdecaf/src/cafe/libraries/proc_ui/proc_ui_messages.h ================================================ #pragma once #include "proc_ui_enum.h" #include <libcpu/be2_struct.h> namespace cafe::proc_ui { using ProcUISaveCallback = virt_func_ptr< void ()>; using ProcUISaveCallbackEx = virt_func_ptr< uint32_t (virt_ptr<void> userArg)>; using ProcUICallback = virt_func_ptr< uint32_t (virt_ptr<void> userArg)>; void ProcUIInit(ProcUISaveCallback saveCallback); void ProcUIInitEx(ProcUISaveCallbackEx saveCallbackEx, virt_ptr<void> userArg); BOOL ProcUIIsRunning(); ProcUIStatus ProcUIProcessMessages(BOOL block); ProcUIStatus ProcUISubProcessMessages(BOOL block); void ProcUIRegisterCallback(ProcUICallbackType type, ProcUICallback callback, virt_ptr<void> userArg, uint32_t unk); void ProcUISetMEM1Storage(virt_ptr<void> buffer, uint32_t size); } // namespace cafe::proc_ui ================================================ FILE: src/libdecaf/src/cafe/libraries/snd_core/snd_core.h ================================================ #pragma once #include "cafe/libraries/sndcore2/sndcore2.h" namespace cafe::snd_core { class Library : public sndcore2::Library { public: Library() : sndcore2::Library(hle::LibraryId::snd_core, "snd_core.rpl") { } }; } // namespace cafe::snd_core ================================================ FILE: src/libdecaf/src/cafe/libraries/snd_user/snd_user.h ================================================ #pragma once #include "cafe/libraries/snduser2/snduser2.h" namespace cafe::snd_user { class Library : public snduser2::Library { public: Library() : snduser2::Library(hle::LibraryId::snd_user, "snd_user.rpl") { } }; } // namespace cafe::snd_user ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2.cpp ================================================ #include "sndcore2.h" namespace cafe::sndcore2 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerAiSymbols(); registerConfigSymbols(); registerDeviceSymbols(); registerRmtSymbols(); registerVoiceSymbols(); registerVsSymbols(); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::sndcore2 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::sndcore2, "sndcore2.rpl") { } // Constructor for snd_core to use Library(hle::LibraryId id, const char *name) : hle::Library(id, name) { } protected: virtual void registerSymbols() override; private: void registerAiSymbols(); void registerConfigSymbols(); void registerDeviceSymbols(); void registerRmtSymbols(); void registerVoiceSymbols(); void registerVsSymbols(); }; } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_ai.cpp ================================================ #include "sndcore2.h" #include "sndcore2_ai.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::sndcore2 { uint32_t AIGetDMALength() { decaf_warn_stub(); return 0; } uint32_t AIGetDMAStartAddr() { decaf_warn_stub(); return 0; } uint32_t AI2GetDMALength() { decaf_warn_stub(); return 0; } uint32_t AI2GetDMAStartAddr() { decaf_warn_stub(); return 0; } void Library::registerAiSymbols() { RegisterFunctionExport(AIGetDMALength); RegisterFunctionExport(AIGetDMAStartAddr); RegisterFunctionExport(AI2GetDMALength); RegisterFunctionExport(AI2GetDMAStartAddr); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_ai.h ================================================ #pragma once #include <cstdint> namespace cafe::sndcore2 { uint32_t AIGetDMALength(); uint32_t AIGetDMAStartAddr(); uint32_t AI2GetDMALength(); uint32_t AI2GetDMAStartAddr(); } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_config.cpp ================================================ #include "sndcore2.h" #include "sndcore2_config.h" #include "sndcore2_voice.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/coreinit/coreinit_alarm.h" #include "cafe/libraries/coreinit/coreinit_interrupts.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/coreinit/coreinit_systeminfo.h" #include "cafe/libraries/coreinit/coreinit_thread.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include "decaf_sound.h" #include <common/log.h> #include <fmt/core.h> #include <libcpu/cpu_formatters.h> using namespace cafe::coreinit; namespace cafe::sndcore2 { static const size_t MaxFrameCallbacks = 64; // Enough for 6 channels of 3ms at 48kHz static constexpr auto AXNumBufferSamples = 6 * 144; struct StaticConfigData { be2_val<BOOL> initialised; be2_val<int32_t> outputRate; be2_val<int32_t> outputChannels; be2_val<uint32_t> defaultMixerSelect; be2_struct<OSAlarm> frameAlarm; be2_array<AXFrameCallback, MaxFrameCallbacks> appFrameCallbacks; be2_val<AXFrameCallback> frameCallback; be2_struct<OSThread> frameCallbackThread; be2_struct<OSThreadQueue> frameCallbackThreadQueue; be2_array<char, 32> frameCallbackThreadName; be2_array<uint8_t, 16 * 1024> frameCallbackThreadStack; // Sound is mixed by the DSP in little-endian std::array<int32_t, AXNumBufferSamples> mixBuffer; std::array<int16_t, AXNumBufferSamples> outputBuffer; }; static OSThreadEntryPointFn FrameCallbackThreadEntryPoint = nullptr; static AlarmCallbackFn FrameAlarmHandler = nullptr; static virt_ptr<StaticConfigData> sConfigData = nullptr; static std::atomic<int32_t> sProtectLock = { 0 }; void AXInit() { auto params = StackObject<AXInitParams> { }; params->renderer = AXRendererFreq::Freq32khz; params->pipeline = AXInitPipeline::Single; AXInitWithParams(params); } void AXInitWithParams(virt_ptr<AXInitParams> params) { if (AXIsInit()) { return; } switch (params->renderer) { case AXRendererFreq::Freq32khz: sConfigData->outputRate = 32000; break; case AXRendererFreq::Freq48khz: sConfigData->outputRate = 48000; break; default: decaf_abort(fmt::format("Unimplemented AXInitRenderer {}", params->renderer)); } sConfigData->outputChannels = 2; // TODO: surround support internal::initDevices(); internal::initVoices(); internal::initEvents(); if (auto driver = decaf::getSoundDriver()) { if (!driver->start(48000, sConfigData->outputChannels)) { gLog->error("Sound driver failed to start, disabling sound output"); decaf::setSoundDriver(nullptr); } } sConfigData->initialised = TRUE; } BOOL AXIsInit() { return sConfigData->initialised; } void AXQuit() { decaf_warn_stub(); } void AXInitProfile(virt_ptr<AXProfile> profile, uint32_t count) { decaf_warn_stub(); } AXRendererFreq AXGetRendererFreq() { if (sConfigData->outputRate == 32000) { return AXRendererFreq::Freq32khz; } else { return AXRendererFreq::Freq48khz; } } uint32_t AXGetSwapProfile(virt_ptr<AXProfile> profile, uint32_t count) { decaf_warn_stub(); return 0; } uint32_t AXGetDefaultMixerSelect() { decaf_warn_stub(); return sConfigData->defaultMixerSelect; } AXResult AXSetDefaultMixerSelect(uint32_t defaultMixerSelect) { decaf_warn_stub(); sConfigData->defaultMixerSelect = defaultMixerSelect; return AXResult::Success; } AXResult AXRegisterAppFrameCallback(AXFrameCallback callback) { if (!callback) { return AXResult::CallbackInvalid; } for (auto i = 0; i < MaxFrameCallbacks; ++i) { if (sConfigData->appFrameCallbacks[i] == callback) { decaf_abort("Application double-registered app frame callback"); } } for (auto i = 0; i < MaxFrameCallbacks; ++i) { if (!sConfigData->appFrameCallbacks[i]) { sConfigData->appFrameCallbacks[i] = callback; return AXResult::Success; } } return AXResult::TooManyCallbacks; } AXResult AXDeregisterAppFrameCallback(AXFrameCallback callback) { if (!callback) { return AXResult::CallbackInvalid; } for (auto i = 0; i < MaxFrameCallbacks; ++i) { if (sConfigData->appFrameCallbacks[i] == callback) { sConfigData->appFrameCallbacks[i] = nullptr; return AXResult::Success; } } return AXResult::CallbackNotFound; } AXFrameCallback AXRegisterFrameCallback(AXFrameCallback callback) { auto oldCallback = sConfigData->frameCallback; sConfigData->frameCallback = callback; return oldCallback; } int32_t AXUserBegin() { decaf_warn_stub(); // TODO: Implement this properly return sProtectLock.fetch_add(1); } int32_t AXUserEnd() { decaf_warn_stub(); // TODO: Implement this properly return sProtectLock.fetch_sub(1); } BOOL AXUserIsProtected() { decaf_warn_stub(); // TODO: Implement this properly return sProtectLock.load() > 0; } uint32_t AXGetInputSamplesPerFrame() { if (sConfigData->outputRate == 32000) { return 96; } else if (sConfigData->outputRate == 48000) { return 144; } else { decaf_abort(fmt::format("Unexpected output rate {}", sConfigData->outputRate)); } } uint32_t AXGetInputSamplesPerSec() { return (AXGetInputSamplesPerFrame() / 3) * 1000; } void AXPrepareEfxData(virt_ptr<void> buffer, uint32_t size) { // Nothing to do here, we have implicit cache coherency } namespace internal { static uint32_t frameCallbackThreadEntry(uint32_t core_id, virt_ptr<void>) { static const int NumOutputSamples = (48000 * 3) / 1000; uint16_t numInputSamples = static_cast<uint16_t>(sConfigData->outputRate * 3 / 1000); uint16_t numOutputChannels = static_cast<uint16_t>(sConfigData->outputChannels); while (true) { coreinit::internal::lockScheduler(); coreinit::internal::sleepThreadNoLock(virt_addrof(sConfigData->frameCallbackThreadQueue)); coreinit::internal::rescheduleSelfNoLock(); coreinit::internal::unlockScheduler(); if (sConfigData->frameCallback) { cafe::invoke(cpu::this_core::state(), sConfigData->frameCallback); } for (auto i = 0; i < MaxFrameCallbacks; ++i) { if (sConfigData->appFrameCallbacks[i]) { cafe::invoke(cpu::this_core::state(), sConfigData->appFrameCallbacks[i]); } } decaf_check(static_cast<size_t>(NumOutputSamples * numOutputChannels) <= sConfigData->mixBuffer.size()); internal::mixOutput(&sConfigData->mixBuffer[0], numInputSamples, numOutputChannels); auto driver = decaf::getSoundDriver(); if (driver) { for (int i = 0; i < NumOutputSamples * sConfigData->outputChannels; ++i) { sConfigData->outputBuffer[i] = static_cast<int16_t>(std::min<int32_t>(std::max<int32_t>(sConfigData->mixBuffer[i], -32768), 32767)); } driver->output(&sConfigData->outputBuffer[0], NumOutputSamples); } } return 0; } static void startFrameAlarmThread() { auto thread = virt_addrof(sConfigData->frameCallbackThread); auto stack = virt_addrof(sConfigData->frameCallbackThreadStack); auto stackSize = sConfigData->frameCallbackThreadStack.size(); sConfigData->frameCallbackThreadName = "AX Callback Thread"; OSCreateThreadType(thread, FrameCallbackThreadEntryPoint, 0, nullptr, virt_cast<uint32_t *>(stack + stackSize), stackSize, 15, static_cast<OSThreadAttributes>(1 << cpu::this_core::id()), OSThreadType::AppIo); OSSetThreadName(thread, virt_addrof(sConfigData->frameCallbackThreadName)); OSResumeThread(thread); } static void frameAlarmHandler(virt_ptr<OSAlarm> alarm, virt_ptr<OSContext> context) { coreinit::internal::lockScheduler(); coreinit::internal::wakeupThreadNoLock(virt_addrof(sConfigData->frameCallbackThreadQueue)); coreinit::internal::unlockScheduler(); } void initEvents() { using namespace coreinit; sConfigData->frameCallback = nullptr; for (auto i = 0; i < MaxFrameCallbacks; ++i) { sConfigData->appFrameCallbacks[i] = nullptr; } startFrameAlarmThread(); auto ticks = static_cast<OSTime>(OSGetSystemInfo()->busSpeed / 4) * 3 / 1000; OSCreateAlarm(virt_addrof(sConfigData->frameAlarm)); OSSetPeriodicAlarm(virt_addrof(sConfigData->frameAlarm), OSGetTime(), ticks, FrameAlarmHandler); } int getOutputRate() { return sConfigData->outputRate; } } // namespace internal void Library::registerConfigSymbols() { RegisterFunctionExport(AXInit); RegisterFunctionExport(AXInitWithParams); RegisterFunctionExport(AXIsInit); RegisterFunctionExport(AXQuit); RegisterFunctionExport(AXInitProfile); RegisterFunctionExport(AXGetRendererFreq); RegisterFunctionExport(AXGetSwapProfile); RegisterFunctionExport(AXGetDefaultMixerSelect); RegisterFunctionExport(AXSetDefaultMixerSelect); RegisterFunctionExport(AXRegisterAppFrameCallback); RegisterFunctionExport(AXDeregisterAppFrameCallback); RegisterFunctionExport(AXRegisterFrameCallback); RegisterFunctionExportName("AXRegisterCallback", AXRegisterFrameCallback); RegisterFunctionExport(AXUserBegin); RegisterFunctionExport(AXUserEnd); RegisterFunctionExport(AXUserIsProtected); RegisterFunctionExport(AXGetInputSamplesPerFrame); RegisterFunctionExport(AXGetInputSamplesPerSec); RegisterFunctionExport(AXPrepareEfxData); RegisterDataInternal(sConfigData); RegisterFunctionInternal(internal::frameAlarmHandler, FrameAlarmHandler); RegisterFunctionInternal(internal::frameCallbackThreadEntry, FrameCallbackThreadEntryPoint); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_config.h ================================================ #pragma once #include "sndcore2_enum.h" #include <libcpu/be2_struct.h> namespace cafe::sndcore2 { #pragma pack(push, 1) struct AXProfile; struct AXInitParams { be2_val<AXRendererFreq> renderer; UNKNOWN(4); be2_val<AXInitPipeline> pipeline; }; CHECK_OFFSET(AXInitParams, 0x00, renderer); CHECK_OFFSET(AXInitParams, 0x08, pipeline); CHECK_SIZE(AXInitParams, 0x0C); #pragma pack(pop) using AXFrameCallback = virt_func_ptr< void() >; void AXInit(); void AXInitWithParams(virt_ptr<AXInitParams> params); BOOL AXIsInit(); void AXQuit(); void AXInitProfile(virt_ptr<AXProfile> profile, uint32_t count); AXRendererFreq AXGetRendererFreq(); uint32_t AXGetSwapProfile(virt_ptr<AXProfile> profile, uint32_t count); AXResult AXSetDefaultMixerSelect(uint32_t); AXFrameCallback AXRegisterFrameCallback(AXFrameCallback callback); AXResult AXRegisterAppFrameCallback(AXFrameCallback callback); AXResult AXDeregisterAppFrameCallback(AXFrameCallback callback); uint32_t AXGetInputSamplesPerFrame(); uint32_t AXGetInputSamplesPerSec(); void AXPrepareEfxData(virt_ptr<void> buffer, uint32_t size); int32_t AXUserBegin(); int32_t AXUserEnd(); BOOL AXUserIsProtected(); namespace internal { void initEvents(); int getOutputRate(); } // namespace internal } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_constants.h ================================================ #pragma once #include <cstdint> namespace cafe::sndcore2 { static constexpr auto AXNumTvDevices = 1u; static constexpr auto AXNumTvChannels = 6u; static constexpr auto AXNumTvBus = 4u; static constexpr auto AXNumDrcDevices = 2u; static constexpr auto AXNumDrcChannels = 4u; static constexpr auto AXNumDrcBus = 4u; static constexpr auto AXNumRmtDevices = 4u; static constexpr auto AXNumRmtChannels = 1u; static constexpr auto AXNumRmtBus = 1u; static constexpr auto AXMaxNumVoices = 96u; } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_device.cpp ================================================ #include "sndcore2.h" #include "sndcore2_config.h" #include "sndcore2_constants.h" #include "sndcore2_device.h" #include "sndcore2_voice.h" #include "decaf_sound.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include <array> #include <common/fixed.h> #include <libcpu/mmu.h> namespace cafe::sndcore2 { constexpr auto NumOutputSamples = 48000 * 3 / 1000; constexpr auto DefaultVolume = ufixed_1_15_t { 1.0 }; struct AuxData { AXAuxCallback callback; virt_ptr<void> userData; ufixed_1_15_t returnVolume; }; struct DeviceData { std::array<AuxData, AXAuxId::Max> aux; bool linearUpsample; ufixed_1_15_t volume; }; struct DeviceTypeData { std::array<DeviceData, 4> devices; bool compressor; AXDeviceFinalMixCallback finalMixCallback; bool upsampleAfterFinalMix; AXDeviceMode mode; }; struct StaticDeviceData { be2_struct<DeviceTypeData> tvDevices; be2_struct<DeviceTypeData> drcDevices; be2_struct<DeviceTypeData> rmtDevices; be2_array<virt_ptr<int32_t>, 8> samplePtrs; be2_val<int32_t> samples[8][144]; be2_struct<AXAuxCallbackData> auxCallbackData; be2_struct<AXDeviceFinalMixData> finalMixCallbackData; }; static virt_ptr<StaticDeviceData> sDeviceData = nullptr; namespace internal { static DeviceData * getDevice(AXDeviceType type, uint32_t deviceId) { switch (type) { case AXDeviceType::TV: decaf_check(deviceId < AXNumTvDevices); return &sDeviceData->tvDevices.devices[deviceId]; case AXDeviceType::DRC: decaf_check(deviceId < AXNumDrcDevices); return &sDeviceData->drcDevices.devices[deviceId]; case AXDeviceType::RMT: decaf_check(deviceId < AXNumRmtDevices); return &sDeviceData->rmtDevices.devices[deviceId]; default: return nullptr; } } template<typename Type> static Type * getMemPageAddress(uint32_t memPageNumber) { // We have to do this this way due to the way that mem::translate handles // nullptr's. In the case of AX here, our memPageNumber can be 0, causing // mem::translate to return 0, which is not what we want. return reinterpret_cast<Type *>(cpu::getBaseVirtualAddress() + (static_cast<uint64_t>(memPageNumber) << 29)); } struct AudioDecoder { // Basic information internal::AXCafeVoiceData offsets; AXVoiceType type; uint32_t loopCount; AXVoiceAdpcm adpcm; AXVoiceAdpcmLoopData adpcmLoop; bool isEof; void fromVoice(virt_ptr<AXVoiceExtras> extras) { offsets = extras->data; type = extras->type; loopCount = extras->loopCount; adpcm = extras->adpcm; adpcmLoop = extras->adpcmLoop; isEof = false; } void toVoice(virt_ptr<AXVoiceExtras> extras) { extras->data = offsets; extras->loopCount = loopCount; extras->adpcm = adpcm; } AudioDecoder& advance() { // Update prev sample auto sample = read(); adpcm.prevSample[1] = adpcm.prevSample[0]; adpcm.prevSample[0] = fixed_to_data(sample); if (offsets.currentOffsetAbs == offsets.endOffsetAbs) { // According to Dolphin, the loop back happens regardless // of whether the voice is in looping mode offsets.currentOffsetAbs = offsets.loopOffsetAbs; if (offsets.loopFlag) { adpcm.predScale = adpcmLoop.predScale; if (type != AXVoiceType::Streaming) { adpcm.prevSample[0] = adpcmLoop.prevSample[0]; adpcm.prevSample[1] = adpcmLoop.prevSample[1]; } } else { decaf_check(!isEof); isEof = true; } loopCount++; } else { offsets.currentOffsetAbs += 1; if (offsets.format == AXVoiceFormat::ADPCM) { // Read next header if were there if ((offsets.currentOffsetAbs & 0xf) < virt_addr { 2 }) { decaf_check((offsets.currentOffsetAbs & 0xf) == virt_addr { 0 }); auto data = getMemPageAddress<uint8_t>(offsets.memPageNumber); adpcm.predScale = data[offsets.currentOffsetAbs / 2]; offsets.currentOffsetAbs += 2; } } } return *this; } bool eof() { return isEof; } Pcm16Sample read() { decaf_check(!isEof); auto sampleIndex = static_cast<uint32_t>(offsets.currentOffsetAbs); if (offsets.format == AXVoiceFormat::ADPCM) { decaf_check((sampleIndex & 0xf) >= 2); auto data = getMemPageAddress<uint8_t>(offsets.memPageNumber); auto scale = 1 << (adpcm.predScale.value() & 0xF); auto coeffIndex = (adpcm.predScale.value() >> 4) & 7; auto coeff1 = adpcm.coefficients[coeffIndex * 2 + 0].value(); auto coeff2 = adpcm.coefficients[coeffIndex * 2 + 1].value(); auto yn1 = adpcm.prevSample[0].value(); auto yn2 = adpcm.prevSample[1].value(); // Extract the 4-bit signed sample from the appropriate byte int sampleData = data[sampleIndex / 2]; if (sampleIndex % 2 == 0) { sampleData >>= 4; } else { sampleData &= 0xF; } if (sampleData >= 8) { sampleData -= 16; } // Calculate sample auto adpcmSample = (scale * sampleData) + ((0x400 + (coeff1 * yn1) + (coeff2 * yn2)) >> 11); // Clamp the output auto clampedSample = std::min(std::max(adpcmSample, -32767), 32767); // Write to the output return fixed_from_data<Pcm16Sample>(static_cast<int16_t>(clampedSample)); } else if (offsets.format == AXVoiceFormat::LPCM16) { auto data = getMemPageAddress<be2_val<int16_t>>(offsets.memPageNumber); return fixed_from_data<Pcm16Sample>(data[sampleIndex]); } else if (offsets.format == AXVoiceFormat::LPCM8) { auto data = getMemPageAddress<uint8_t>(offsets.memPageNumber); return fixed_from_data<Pcm16Sample>(static_cast<int16_t>(data[sampleIndex] << 8)); } else { decaf_abort("Unexpected AXVoice data format"); } } }; void sampleVoice(virt_ptr<AXVoice> voice, Pcm16Sample *samples, int numSamples) { static const auto FpOne = ufixed_16_16_t(1); static const auto FpZero = ufixed_16_16_t(0); memset(samples, 0, numSamples * sizeof(Pcm16Sample)); auto extras = getVoiceExtras(voice->index); auto offsetFrac = ufixed_16_16_t(extras->src.currentOffsetFrac.value()); AudioDecoder decoder; decoder.fromVoice(extras); for (auto i = 0; i < numSamples; ++i) { // Read in the current sample Pcm16Sample sample; if (offsetFrac == FpZero) { sample = decoder.read(); } else { AudioDecoder nextDecoder(decoder); nextDecoder.advance(); if (!nextDecoder.eof()) { sample = nextDecoder.read(); } else { sample = 0; } } if (offsetFrac == FpZero) { samples[i] = sample; } else { auto thisSampleMul = FpOne - offsetFrac; auto lastSampleMul = offsetFrac; auto lastSample = fixed_from_data<Pcm16Sample>(extras->src.lastSample[0]); samples[i] = sample * thisSampleMul + lastSample * lastSampleMul; } offsetFrac += extras->src.ratio.value(); while (offsetFrac >= FpOne) { // Advance the voice by one sample offsetFrac -= FpOne; decoder.advance(); // Update all the last sample listings. Most of these are used // for FFT resampling (which we don't currently handle). extras->src.lastSample[3] = extras->src.lastSample[2]; extras->src.lastSample[2] = extras->src.lastSample[1]; extras->src.lastSample[1] = extras->src.lastSample[0]; extras->src.lastSample[0] = fixed_to_data(sample); // If we reached the end of the voice data, we should just leave if (decoder.eof()) { break; } // If we read through multiple samples due to a high SRC ratio, // then we need to actually read the upcoming sample in expectation // of the fact that its about to be stored in lastSample. if (offsetFrac >= FpOne) { sample = decoder.read(); } } if (decoder.eof()) { break; } } if (decoder.eof()) { voice->state = AXVoiceState::Stopped; } decoder.toVoice(extras); extras->src.currentOffsetFrac = ufixed_0_16_t { offsetFrac }; } void applyADSR(virt_ptr<AXVoice> voice, Pcm16Sample *samples, int numSamples) { auto extras = getVoiceExtras(voice->index); for (int i = 0; i < numSamples; i++) { samples[i] = samples[i] * (extras->ve.volume.value() + extras->ve.delta.value() * i); } extras->ve.volume += extras->ve.delta.value() * numSamples; } void decodeVoiceSamples(int numSamples) { const auto voices = getAcquiredVoices(); for (auto voice : voices) { auto extras = getVoiceExtras(voice->index); if (voice->state == AXVoiceState::Stopped) { extras->numSamples = 0; continue; } extras->numSamples = numSamples; sampleVoice(voice, extras->samples, numSamples); applyADSR(voice, extras->samples, numSamples); } // TODO: Apply Volume Evelope (ADSR) // TODO: Apply Biquad Filter // TODO: Apply Low Pass Filter } static Pcm16Sample gTvSamples[AXNumTvDevices][AXNumTvChannels][NumOutputSamples]; static void invokeAuxCallback(AuxData &aux, uint32_t numChannels, uint32_t numSamples, Pcm16Sample samples[6][144]) { if (aux.callback) { auto auxCbData = virt_addrof(sDeviceData->auxCallbackData); auxCbData->samples = numSamples; auxCbData->channels = numChannels; for (auto ch = 0u; ch < numChannels; ++ch) { for (auto i = 0u; i < numSamples; ++i) { sDeviceData->samples[ch][i] = static_cast<int32_t>(samples[ch][i]); } sDeviceData->samplePtrs[ch] = virt_addrof(sDeviceData->samples[ch][0]); } cafe::invoke(cpu::this_core::state(), aux.callback, virt_addrof(sDeviceData->samplePtrs), aux.userData, auxCbData); for (auto ch = 0u; ch < numChannels; ++ch) { for (auto i = 0u; i < numSamples; ++i) { samples[ch][i] = fixed_from_data<Pcm16Sample>( static_cast<int16_t>(sDeviceData->samples[ch][i])); } } } } static void invokeFinalMixCallback(DeviceTypeData &device, uint16_t numDevices, uint16_t numChannels, uint16_t numSamples, Pcm16Sample samples[4][6][144]) { if (device.finalMixCallback) { auto mixCbData = virt_addrof(sDeviceData->finalMixCallbackData); mixCbData->channels = numChannels; mixCbData->samples = numSamples; mixCbData->numDevices = numDevices; mixCbData->channelsOut = mixCbData->channels; for (auto dev = 0u; dev < numDevices; ++dev) { for (auto ch = 0u; ch < numChannels; ++ch) { auto axChanId = (dev * numChannels) + ch; for (auto i = 0u; i < numSamples; ++i) { int16_t sample = fixed_to_data(samples[dev][ch][i]); sDeviceData->samples[axChanId][i] = static_cast<int32_t>(sample); } sDeviceData->samplePtrs[axChanId] = virt_addrof(sDeviceData->samples[axChanId][0]); } } mixCbData->data = virt_addrof(sDeviceData->samplePtrs); cafe::invoke(cpu::this_core::state(), device.finalMixCallback, mixCbData); for (auto dev = 0u; dev < numDevices; ++dev) { for (auto ch = 0u; ch < numChannels; ++ch) { auto axChanId = (dev * numChannels) + ch; for (auto i = 0u; i < numSamples; ++i) { samples[dev][ch][i] = fixed_from_data<Pcm16Sample>( static_cast<int16_t>(sDeviceData->samples[axChanId][i])); } } } } } AXVoiceExtras::MixVolume & getVoiceMixVolume(virt_ptr<AXVoiceExtras> extras, AXDeviceType type, uint32_t device, uint32_t channel, uint32_t bus) { if (type == AXDeviceType::TV) { decaf_check(device < AXNumTvDevices); decaf_check(channel < AXNumTvChannels); decaf_check(bus < AXNumTvBus); return extras->tvVolume[device][channel][bus]; } else if (type == AXDeviceType::DRC) { decaf_check(device < AXNumDrcDevices); decaf_check(channel < AXNumDrcChannels); decaf_check(bus < AXNumDrcBus); return extras->drcVolume[device][channel][bus]; } else if (type == AXDeviceType::RMT) { decaf_check(device < AXNumRmtDevices); decaf_check(channel < AXNumRmtChannels); decaf_check(bus < AXNumRmtBus); return extras->rmtVolume[device][channel][bus]; } else { decaf_abort("Unexpected device type"); } } static virt_ptr<DeviceTypeData> getDeviceGroup(AXDeviceType type) { switch (type) { case AXDeviceType::TV: return virt_addrof(sDeviceData->tvDevices); case AXDeviceType::DRC: return virt_addrof(sDeviceData->drcDevices); case AXDeviceType::RMT: return virt_addrof(sDeviceData->rmtDevices); default: decaf_abort("Unexpected device type"); } } static uint16_t getDeviceNumDevices(AXDeviceType type) { decaf_check(type < AXDeviceType::Max); static const uint16_t devices[] = { AXNumTvDevices, AXNumDrcDevices, AXNumRmtDevices }; return devices[type]; } static uint16_t getDeviceNumBuses(AXDeviceType type) { decaf_check(type < AXDeviceType::Max); static const uint16_t busses[] = { AXNumTvBus, AXNumDrcBus, AXNumRmtBus }; return busses[type]; } static uint16_t getDeviceNumChannels(AXDeviceType type) { decaf_check(type < AXDeviceType::Max); static const uint16_t channels[] = { AXNumTvChannels, AXNumDrcChannels, AXNumRmtChannels }; return channels[type]; } static void mixDevice(AXDeviceType type, uint16_t numSamples) { static const auto AXMaxDevices = 4; static const auto AXMaxBuses = 4; static const auto AXMaxChannels = 6; auto devices = getDeviceGroup(type); auto numDevices = getDeviceNumDevices(type); auto numBus = getDeviceNumBuses(type); auto numChannels = getDeviceNumChannels(type); decaf_check(numDevices <= AXMaxDevices); decaf_check(numBus <= AXMaxBuses); decaf_check(numChannels <= AXMaxChannels); decaf_check(numSamples == 96 || numSamples == 144); Pcm16Sample busSamples[AXMaxBuses][AXMaxDevices][AXMaxChannels][NumOutputSamples]; const auto voices = getAcquiredVoices(); memset(busSamples, 0, sizeof(Pcm16Sample) * AXMaxBuses * AXMaxDevices * AXMaxChannels * NumOutputSamples); for (auto voice : voices) { auto extras = getVoiceExtras(voice->index); if (!extras->numSamples) { continue; } decaf_check(extras->numSamples == numSamples); for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) { for (auto bus = 0u; bus < numBus; ++bus) { for (auto channel = 0u; channel < numChannels; ++channel) { auto &volume = getVoiceMixVolume(extras, type, deviceId, channel, bus); auto &out = busSamples[bus][deviceId][channel]; for (auto i = 0u; i < numSamples; ++i) { out[i] += extras->samples[i] * volume.volume; } volume.volume += volume.delta; } } } } for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) { auto &device = devices->devices[deviceId]; for (auto bus = 1u; bus < numBus; ++bus) { invokeAuxCallback(device.aux[bus - 1], numChannels, numSamples, busSamples[bus][deviceId]); } } auto &mainBus = busSamples[0]; // Downmix all aux busses to main bus for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) { auto &device = devices->devices[deviceId]; for (auto bus = 1u; bus < numBus; ++bus) { auto returnVolume = device.aux[bus - 1].returnVolume; auto subBus = busSamples[bus]; for (auto channel = 0u; channel < numChannels; ++channel) { for (auto i = 0u; i < numSamples; ++i) { mainBus[deviceId][channel][i] += subBus[deviceId][channel][i] * returnVolume; } } } } // Apply overall device volume for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) { auto &device = devices->devices[deviceId]; for (auto channel = 0u; channel < numChannels; ++channel) { for (auto i = 0u; i < numSamples; ++i) { mainBus[deviceId][channel][i] = mainBus[deviceId][channel][i] * device.volume; } } } // Now we need to perform upsampling (I think) auto upsample32to48 = [](Pcm16Sample *samples) { // currently lazy... auto output = samples; Pcm16Sample input[96]; memcpy(input, output, sizeof(Pcm16Sample) * 96); // Perform upsampling for (auto i = 0u; i < NumOutputSamples; ++i) { float sampleIdx = static_cast<float>(i) / 144.0f * 96.0f; auto sampleLo = static_cast<uint32_t>(std::min(143.0f, std::floor(sampleIdx))); auto sampleHi = static_cast<uint32_t>(std::min(95.0f, std::ceil(sampleIdx))); float sampleFrac = sampleIdx - sampleLo; output[i] = (input[sampleLo] * (1.0f - sampleFrac)) + (input[sampleHi] * sampleFrac); } }; // Perform upsampling and final mix callback invokation if (devices->upsampleAfterFinalMix) { invokeFinalMixCallback(*devices, numDevices, numChannels, numSamples, mainBus); if (numSamples != NumOutputSamples) { for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) { for (auto channel = 0u; channel < numChannels; ++channel) { upsample32to48(mainBus[deviceId][channel]); } } } } else { if (numSamples != NumOutputSamples) { for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) { for (auto channel = 0u; channel < numChannels; ++channel) { upsample32to48(mainBus[deviceId][channel]); } } } invokeFinalMixCallback(*devices, numDevices, numChannels, NumOutputSamples, mainBus); } // TODO: Apply compressor // TODO: Channel upmix/downmix, but I think we should let the audio driver (aka SDL) handle that if (type == AXDeviceType::TV) { // Copy the generated data out for later pickup memcpy(gTvSamples, mainBus, sizeof(Pcm16Sample) * numDevices * numChannels * NumOutputSamples); } else if (type == AXDeviceType::DRC) { // We currently just discard the generated DRC audio } else if (type == AXDeviceType::RMT) { // We also discard generated RMT audio } else { decaf_abort("Unexpected device type during copy-out"); } } void mixOutput(int32_t* buffer, uint16_t numSamples, uint16_t numChannels) { // Decode audio samples from the source voices decodeVoiceSamples(numSamples); // Mix all the devices mixDevice(AXDeviceType::TV, numSamples); mixDevice(AXDeviceType::DRC, numSamples); mixDevice(AXDeviceType::RMT, numSamples); // Send off the TV device 0 data to be played on host for (auto i = 0; i < NumOutputSamples; ++i) { for (auto ch = 0; ch < numChannels; ++ch) { buffer[numChannels * i + ch] = fixed_to_data(gTvSamples[0][ch][i]); } } } } // namespace internal AXResult AXGetDeviceMode(AXDeviceType type, virt_ptr<AXDeviceMode> outMode) { if (!outMode) { return AXResult::Success; } switch (type) { case AXDeviceType::TV: *outMode = sDeviceData->tvDevices.mode; break; case AXDeviceType::DRC: *outMode = sDeviceData->drcDevices.mode; break; case AXDeviceType::RMT: *outMode = sDeviceData->rmtDevices.mode; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXSetDeviceMode(AXDeviceType type, AXDeviceMode mode) { auto devices = internal::getDeviceGroup(type); switch (type) { case AXDeviceType::TV: devices->mode = mode; break; case AXDeviceType::DRC: devices->mode = mode; break; case AXDeviceType::RMT: devices->mode = mode; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXGetDeviceFinalMixCallback(AXDeviceType type, virt_ptr<AXDeviceFinalMixCallback> outFunc) { if (!outFunc) { return AXResult::Success; } auto devices = internal::getDeviceGroup(type); switch (type) { case AXDeviceType::TV: *outFunc = devices->finalMixCallback; break; case AXDeviceType::DRC: *outFunc = devices->finalMixCallback; break; case AXDeviceType::RMT: *outFunc = devices->finalMixCallback; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXRegisterDeviceFinalMixCallback(AXDeviceType type, AXDeviceFinalMixCallback func) { auto devices = internal::getDeviceGroup(type); switch (type) { case AXDeviceType::TV: devices->finalMixCallback = func; break; case AXDeviceType::DRC: devices->finalMixCallback = func; break; case AXDeviceType::RMT: devices->finalMixCallback = func; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXGetAuxCallback(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, virt_ptr<AXAuxCallback> outCallback, virt_ptr<virt_ptr<void>> outUserData) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } if (outCallback) { *outCallback = device->aux[auxId].callback; } if (outUserData) { *outUserData = device->aux[auxId].userData; } return AXResult::Success; } AXResult AXRegisterAuxCallback(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, AXAuxCallback callback, virt_ptr<void> userData) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } device->aux[auxId].callback = callback; device->aux[auxId].userData = userData; return AXResult::Success; } AXResult AXSetDeviceLinearUpsampler(AXDeviceType type, uint32_t deviceId, BOOL linear) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } device->linearUpsample = !!linear; return AXResult::Success; } AXResult AXSetDeviceCompressor(AXDeviceType type, BOOL compressor) { switch (type) { case AXDeviceType::TV: sDeviceData->tvDevices.compressor = !!compressor; break; case AXDeviceType::DRC: sDeviceData->drcDevices.compressor = !!compressor; break; case AXDeviceType::RMT: sDeviceData->rmtDevices.compressor = !!compressor; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXGetDeviceUpsampleStage(AXDeviceType type, virt_ptr<BOOL> outUpsampleAfterFinalMixCallback) { if (!outUpsampleAfterFinalMixCallback) { return AXResult::Success; } switch (type) { case AXDeviceType::TV: *outUpsampleAfterFinalMixCallback = sDeviceData->tvDevices.upsampleAfterFinalMix ? TRUE : FALSE; break; case AXDeviceType::DRC: *outUpsampleAfterFinalMixCallback = sDeviceData->drcDevices.upsampleAfterFinalMix ? TRUE : FALSE; break; case AXDeviceType::RMT: *outUpsampleAfterFinalMixCallback = sDeviceData->rmtDevices.upsampleAfterFinalMix ? TRUE : FALSE; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXSetDeviceUpsampleStage(AXDeviceType type, BOOL upsampleAfterFinalMixCallback) { switch (type) { case AXDeviceType::TV: sDeviceData->tvDevices.upsampleAfterFinalMix = !!upsampleAfterFinalMixCallback; break; case AXDeviceType::DRC: sDeviceData->drcDevices.upsampleAfterFinalMix = !!upsampleAfterFinalMixCallback; break; case AXDeviceType::RMT: sDeviceData->rmtDevices.upsampleAfterFinalMix = !!upsampleAfterFinalMixCallback; break; default: return AXResult::InvalidDeviceType; } return AXResult::Success; } AXResult AXGetDeviceVolume(AXDeviceType type, uint32_t deviceId, virt_ptr<uint16_t> outVolume) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } if (outVolume) { *outVolume = fixed_to_data(device->volume); } return AXResult::Success; } AXResult AXSetDeviceVolume(AXDeviceType type, uint32_t deviceId, uint16_t volume) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } device->volume = fixed_from_data<ufixed_1_15_t>(volume); return AXResult::Success; } AXResult AXGetAuxReturnVolume(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, virt_ptr<uint16_t> outVolume) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } if (outVolume) { *outVolume = fixed_to_data(device->aux[auxId].returnVolume); } return AXResult::Success; } AXResult AXSetAuxReturnVolume(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, uint16_t volume) { auto device = internal::getDevice(type, deviceId); if (!device) { return AXResult::InvalidDeviceType; } device->aux[auxId].returnVolume = fixed_from_data<ufixed_1_15_t>(volume); return AXResult::Success; } namespace internal { void initDevices() { for (auto &device : sDeviceData->tvDevices.devices) { device.volume = DefaultVolume; for (auto &aux : device.aux) { aux.returnVolume = DefaultVolume; } } for (auto &device : sDeviceData->drcDevices.devices) { device.volume = DefaultVolume; for (auto &aux : device.aux) { aux.returnVolume = DefaultVolume; } } for (auto &device : sDeviceData->rmtDevices.devices) { device.volume = DefaultVolume; for (auto &aux : device.aux) { aux.returnVolume = DefaultVolume; } } } } // namespace internal void Library::registerDeviceSymbols() { RegisterFunctionExport(AXGetDeviceMode); RegisterFunctionExport(AXSetDeviceMode); RegisterFunctionExport(AXGetDeviceFinalMixCallback); RegisterFunctionExport(AXRegisterDeviceFinalMixCallback); RegisterFunctionExport(AXGetAuxCallback); RegisterFunctionExport(AXRegisterAuxCallback); RegisterFunctionExport(AXSetDeviceLinearUpsampler); RegisterFunctionExport(AXSetDeviceCompressor); RegisterFunctionExport(AXGetDeviceUpsampleStage); RegisterFunctionExport(AXSetDeviceUpsampleStage); RegisterFunctionExport(AXGetDeviceVolume); RegisterFunctionExport(AXSetDeviceVolume); RegisterFunctionExport(AXGetAuxReturnVolume); RegisterFunctionExport(AXSetAuxReturnVolume); RegisterDataInternal(sDeviceData); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_device.h ================================================ #pragma once #include "sndcore2_enum.h" #include <libcpu/be2_struct.h> namespace cafe::sndcore2 { #pragma pack(push, 1) struct AXAuxCallbackData { be2_val<uint32_t> channels; be2_val<uint32_t> samples; }; CHECK_OFFSET(AXAuxCallbackData, 0x0, channels); CHECK_OFFSET(AXAuxCallbackData, 0x4, samples); CHECK_SIZE(AXAuxCallbackData, 0x8); struct AXDeviceFinalMixData { be2_virt_ptr<virt_ptr<int32_t>> data; be2_val<uint16_t> channels; be2_val<uint16_t> samples; be2_val<uint16_t> numDevices; be2_val<uint16_t> channelsOut; }; CHECK_OFFSET(AXDeviceFinalMixData, 0x0, data); CHECK_OFFSET(AXDeviceFinalMixData, 0x4, channels); CHECK_OFFSET(AXDeviceFinalMixData, 0x6, samples); CHECK_OFFSET(AXDeviceFinalMixData, 0x8, numDevices); CHECK_OFFSET(AXDeviceFinalMixData, 0xa, channelsOut); CHECK_SIZE(AXDeviceFinalMixData, 0xc); #pragma pack(pop) using AXDeviceFinalMixCallback = virt_func_ptr< void(virt_ptr<AXDeviceFinalMixData>) >; using AXAuxCallback = virt_func_ptr< void(virt_ptr<virt_ptr<int32_t>>, virt_ptr<void>, virt_ptr<AXAuxCallbackData>) >; AXResult AXGetDeviceMode(AXDeviceType type, virt_ptr<AXDeviceMode> mode); AXResult AXSetDeviceMode(AXDeviceType type, AXDeviceMode mode); AXResult AXGetDeviceFinalMixCallback(AXDeviceType type, virt_ptr<AXDeviceFinalMixCallback> outCallback); AXResult AXRegisterDeviceFinalMixCallback(AXDeviceType type, AXDeviceFinalMixCallback callback); AXResult AXGetAuxCallback(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, virt_ptr<AXAuxCallback> outCallback, virt_ptr<virt_ptr<void>> outUserData); AXResult AXRegisterAuxCallback(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, AXAuxCallback callback, virt_ptr<void> userData); AXResult AXSetDeviceLinearUpsampler(AXDeviceType type, uint32_t deviceId, BOOL linear); AXResult AXSetDeviceCompressor(AXDeviceType type, BOOL compressor); AXResult AXGetDeviceUpsampleStage(AXDeviceType type, virt_ptr<BOOL> outUpsampleAfterFinalMixCallback); AXResult AXSetDeviceUpsampleStage(AXDeviceType type, BOOL upsampleAfterFinalMixCallback); AXResult AXGetDeviceVolume(AXDeviceType type, uint32_t deviceId, virt_ptr<uint16_t> outVolume); AXResult AXSetDeviceVolume(AXDeviceType type, uint32_t deviceId, uint16_t volume); AXResult AXGetAuxReturnVolume(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, virt_ptr<uint16_t> outVolume); AXResult AXSetAuxReturnVolume(AXDeviceType type, uint32_t deviceId, AXAuxId auxId, uint16_t volume); namespace internal { void mixOutput(int32_t* buffer, uint16_t numSamples, uint16_t numChannels); void initDevices(); } // namespace internal } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_enum.h ================================================ #ifndef CAFE_SNDCORE2_ENUM_H #define CAFE_SNDCORE2_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(sndcore2) ENUM_BEG(AXAuxId, uint32_t) ENUM_VALUE(A, 0) ENUM_VALUE(B, 1) ENUM_VALUE(C, 2) ENUM_VALUE(Max, 3) ENUM_END(AXAuxId) ENUM_BEG(AXBusType, uint32_t) ENUM_VALUE(Main, 0) ENUM_VALUE(A, 1) ENUM_VALUE(B, 2) ENUM_VALUE(C, 3) ENUM_VALUE(Max, 4) ENUM_END(AXBusType) ENUM_BEG(AXChannels, uint32_t) ENUM_VALUE(Left, 0) ENUM_VALUE(Right, 1) ENUM_VALUE(LeftSurround, 2) ENUM_VALUE(RightSurround, 3) ENUM_VALUE(Center, 4) ENUM_VALUE(Sub, 5) ENUM_VALUE(Max, 6) ENUM_END(AXChannels) ENUM_BEG(AXDeviceMode, uint32_t) // Unknown ENUM_END(AXDeviceMode) ENUM_BEG(AXDeviceType, uint32_t) ENUM_VALUE(TV, 0) ENUM_VALUE(DRC, 1) ENUM_VALUE(RMT, 2) // Classic Controller, Wiimote etc. ENUM_VALUE(Max, 3) ENUM_END(AXDeviceType) ENUM_BEG(AXDRCOutput, uint32_t) // Unknown ENUM_END(AXDRCOutput) ENUM_BEG(AXDRCVSLC, uint32_t) // Unknown ENUM_END(AXDRCVSLC) ENUM_BEG(AXDRCVSMode, uint32_t) // Unknown ENUM_END(AXDRCVSMode) ENUM_BEG(AXDRCVSSpeakerPosition, uint32_t) // Unknown ENUM_END(AXDRCVSSpeakerPosition) ENUM_BEG(AXDRCVSSurroundLevelGain, uint32_t) // Unknown ENUM_END(AXDRCVSSurroundLevelGain) ENUM_BEG(AXInitPipeline, uint32_t) ENUM_VALUE(Single, 0) ENUM_VALUE(FourStage, 1) ENUM_END(AXInitPipeline) ENUM_BEG(AXRendererFreq, uint32_t) ENUM_VALUE(Freq32khz, 0) ENUM_VALUE(Freq48khz, 1) ENUM_END(AXRendererFreq) ENUM_BEG(AXResult, int32_t) ENUM_VALUE(Success, 0) ENUM_VALUE(InvalidDeviceType, -1) ENUM_VALUE(InvalidDRCVSMode, -13) ENUM_VALUE(TooManyCallbacks, -15) ENUM_VALUE(CallbackNotFound, -16) ENUM_VALUE(CallbackInvalid, -17) ENUM_VALUE(VoiceIsRunning, -18) ENUM_VALUE(DelayTooBig, -19) ENUM_END(AXResult) ENUM_BEG(AXVoiceFormat, uint16_t) ENUM_VALUE(ADPCM, 0x00) ENUM_VALUE(LPCM16, 0x0A) ENUM_VALUE(LPCM8, 0x19) ENUM_END(AXVoiceFormat) ENUM_BEG(AXVoiceLoop, uint16_t) ENUM_VALUE(Disabled, 0) ENUM_VALUE(Enabled, 1) ENUM_END(AXVoiceLoop) ENUM_BEG(AXRenderer, uint32_t) ENUM_VALUE(DSP, 0) ENUM_VALUE(CPU, 1) ENUM_VALUE(Auto, 2) ENUM_END(AXRenderer) ENUM_BEG(AXVoiceSrcType, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Linear, 1) ENUM_VALUE(Unk0, 2) ENUM_VALUE(Unk1, 3) ENUM_VALUE(Unk2, 4) ENUM_END(AXVoiceSrcType) ENUM_BEG(AXVoiceState, uint32_t) ENUM_VALUE(Stopped, 0) ENUM_VALUE(Playing, 1) ENUM_END(AXVoiceState) ENUM_BEG(AXVoiceType, uint16_t) ENUM_VALUE(Default, 0) ENUM_VALUE(Streaming, 1) ENUM_END(AXVoiceType) ENUM_BEG(AXVoiceSrcRatioResult, int32_t) ENUM_VALUE(Success, 0) ENUM_VALUE(RatioLessThanZero, -1) ENUM_VALUE(RatioGreaterThanSomething, -2) ENUM_END(AXVoiceSrcRatioResult) ENUM_NAMESPACE_ENTER(internal) ENUM_BEG(AXVoiceSyncBits, uint32_t) ENUM_VALUE(SrcType, 1 << 0) ENUM_VALUE(State, 1 << 2) ENUM_VALUE(Type, 1 << 3) ENUM_VALUE(Itd, 1 << 5) ENUM_VALUE(ItdTarget, 1 << 6) ENUM_VALUE(Ve, 1 << 8) ENUM_VALUE(VeDelta, 1 << 9) ENUM_VALUE(Addr, 1 << 10) ENUM_VALUE(Loop, 1 << 11) ENUM_VALUE(LoopOffset, 1 << 12) ENUM_VALUE(EndOffset, 1 << 13) ENUM_VALUE(CurrentOffset, 1 << 14) ENUM_VALUE(Adpcm, 1 << 15) ENUM_VALUE(Src, 1 << 16) ENUM_VALUE(SrcRatio, 1 << 17) ENUM_VALUE(AdpcmLoop, 1 << 18) ENUM_VALUE(Lpf, 1 << 19) ENUM_VALUE(LpfCoefs, 1 << 20) ENUM_VALUE(Biquad, 1 << 21) ENUM_VALUE(BiquadCoefs, 1 << 22) ENUM_VALUE(RmtOn, 1 << 23) ENUM_VALUE(RmtSrc, 1 << 27) ENUM_VALUE(RmtIIR, 1 << 28) ENUM_VALUE(RmtIIRCoefs0, 1 << 29) ENUM_VALUE(RmtIIRCoefs1, 1 << 30) ENUM_END(AXVoiceSyncBits) ENUM_NAMESPACE_EXIT(internal) ENUM_NAMESPACE_EXIT(sndcore2) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef CAFE_SNDCORE2_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_rmt.cpp ================================================ #include "sndcore2.h" #include "sndcore2_rmt.h" #include <cafe/libraries/cafe_hle_stub.h> namespace cafe::sndcore2 { int32_t AXRmtGetSamplesLeft() { decaf_warn_stub(); return 0; } int32_t AXRmtGetSamples(int32_t, virt_ptr<uint8_t> buffer, int32_t samples) { decaf_warn_stub(); return 0; } int32_t AXRmtAdvancePtr(int32_t numSamples) { decaf_warn_stub(); return 0; } void Library::registerRmtSymbols() { RegisterFunctionExport(AXRmtGetSamplesLeft); RegisterFunctionExport(AXRmtGetSamples); RegisterFunctionExport(AXRmtAdvancePtr); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_rmt.h ================================================ #pragma once #include "sndcore2_enum.h" #include <libcpu/be2_struct.h> namespace cafe::sndcore2 { int32_t AXRmtGetSamplesLeft(); int32_t AXRmtGetSamples(int32_t, virt_ptr<uint8_t> buffer, int32_t samples); int32_t AXRmtAdvancePtr(int32_t numSamples); } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_voice.cpp ================================================ #include "sndcore2.h" #include "sndcore2_config.h" #include "sndcore2_constants.h" #include "sndcore2_voice.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_stub.h" #include "decaf_config.h" #include "decaf_sound.h" #include <common/decaf_assert.h> #include <common/platform_dir.h> #include <array> #include <fmt/format.h> #include <fstream> #include <libcpu/cpu_formatters.h> #include <queue> namespace cafe::sndcore2 { struct StaticVoiceData { be2_array<AXVoice, AXMaxNumVoices> voices; be2_array<internal::AXVoiceExtras, AXMaxNumVoices> voiceExtras; }; // TODO: Move away from std::vector static std::vector<virt_ptr<AXVoice>> sAcquiredVoices; // TODO: Move away from std::queue static std::queue<virt_ptr<AXVoice>> sAvailVoiceStack; static virt_ptr<StaticVoiceData> sVoiceData = nullptr; virt_ptr<AXVoice> AXAcquireVoice(uint32_t priority, AXVoiceCallbackFn callback, virt_ptr<void> userContext) { return AXAcquireVoiceEx(priority, virt_func_cast<AXVoiceCallbackExFn>(virt_func_cast<virt_addr>(callback)), userContext); } virt_ptr<AXVoice> AXAcquireVoiceEx(uint32_t priority, AXVoiceCallbackExFn callback, virt_ptr<void> userContext) { if (sAvailVoiceStack.empty()) { // If there are no available voices, try to force-deallocate one... for (auto &i : sAcquiredVoices) { if (i->priority < priority) { // TODO: Send callback for forcing the FreeVoice AXFreeVoice(i); break; } } } if (sAvailVoiceStack.empty()) { // No voices available to acquire return nullptr; } // Grab our voice from the stack auto foundVoice = sAvailVoiceStack.front(); sAvailVoiceStack.pop(); // Reset the voice auto voiceIndex = foundVoice->index; std::memset(foundVoice.get(), 0, sizeof(AXVoice)); foundVoice->index = voiceIndex; // Configure the voice with stuff we know about foundVoice->priority = priority; foundVoice->callbackEx = callback; foundVoice->userContext = userContext; auto extras = internal::getVoiceExtras(foundVoice->index); std::memset(extras.get(), 0, sizeof(internal::AXVoiceExtras)); extras->src.ratio = ufixed_16_16_t { 1.0 }; // Save this to the acquired voice list so that it can be // forcefully freed if a higher priority voice is needed. sAcquiredVoices.push_back(foundVoice); return foundVoice; } BOOL AXCheckVoiceOffsets(virt_ptr<AXVoiceOffsets> offsets) { return TRUE; } void AXFreeVoice(virt_ptr<AXVoice> voice) { auto voiceIter = std::find(sAcquiredVoices.begin(), sAcquiredVoices.end(), voice); decaf_check(voiceIter != sAcquiredVoices.end()); // Erase this voice from the acquired list sAcquiredVoices.erase(voiceIter); // Make this voice available on the available stack! sAvailVoiceStack.push(voice); } uint32_t AXGetMaxVoices() { return AXMaxNumVoices; } uint32_t AXGetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice, virt_ptr<const void> samples) { auto offsets = StackObject<AXVoiceOffsets> { }; AXGetVoiceOffsetsEx(voice, offsets, samples); return offsets->currentOffset; } uint32_t AXGetVoiceLoopCount(virt_ptr<AXVoice> voice) { return sVoiceData->voiceExtras[voice->index].loopCount; } uint32_t AXGetVoiceMixerSelect(virt_ptr<AXVoice> voice) { return voice->renderer; } void AXGetVoiceOffsetsEx(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets, virt_ptr<const void> samples) { voice->offsets.data = samples; AXGetVoiceOffsets(voice, offsets); voice->offsets = *offsets; } void AXGetVoiceOffsets(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets) { auto extras = internal::getVoiceExtras(voice->index); *offsets = voice->offsets; auto pageAddress = extras->data.memPageNumber << 29; auto baseAddress = virt_cast<virt_addr>(offsets->data); if (extras->data.format == AXVoiceFormat::ADPCM) { auto pageAddressSamples = static_cast<int64_t>(pageAddress) << 1; auto baseAddressSamples = static_cast<int64_t>(baseAddress) << 1; offsets->loopOffset = static_cast<uint32_t>(pageAddressSamples + static_cast<int64_t>(extras->data.loopOffsetAbs) - baseAddressSamples); offsets->endOffset = static_cast<uint32_t>(pageAddressSamples + static_cast<int64_t>(extras->data.endOffsetAbs) - baseAddressSamples); offsets->currentOffset = static_cast<uint32_t>(pageAddressSamples + static_cast<int64_t>(extras->data.currentOffsetAbs) - baseAddressSamples); } else if (extras->data.format == AXVoiceFormat::LPCM16) { auto pageAddressSamples = pageAddress >> 1; auto baseAddressSamples = baseAddress >> 1; offsets->loopOffset = static_cast<uint32_t>(extras->data.loopOffsetAbs + pageAddressSamples - baseAddressSamples); offsets->endOffset = static_cast<uint32_t>(extras->data.endOffsetAbs + pageAddressSamples - baseAddressSamples); offsets->currentOffset = static_cast<uint32_t>(extras->data.currentOffsetAbs + pageAddressSamples - baseAddressSamples); } else if (extras->data.format == AXVoiceFormat::LPCM8) { offsets->loopOffset = static_cast<uint32_t>(extras->data.loopOffsetAbs + pageAddress - baseAddress); offsets->endOffset = static_cast<uint32_t>(extras->data.endOffsetAbs + pageAddress - baseAddress); offsets->currentOffset = static_cast<uint32_t>(extras->data.currentOffsetAbs + pageAddress - baseAddress); } else { decaf_abort(fmt::format("Unexpected voice data format {}", extras->data.format)) } } BOOL AXIsVoiceRunning(virt_ptr<AXVoice> voice) { auto extras = internal::getVoiceExtras(voice->index); return (extras->state != AXVoiceState::Stopped) ? 1 : 0; } void AXSetVoiceAdpcm(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceAdpcm> adpcm) { auto extras = internal::getVoiceExtras(voice->index); extras->adpcm = *adpcm; extras->syncBits |= internal::AXVoiceSyncBits::Adpcm; } void AXSetVoiceAdpcmLoop(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceAdpcmLoopData> loopData) { auto extras = internal::getVoiceExtras(voice->index); extras->adpcmLoop = *loopData; extras->syncBits |= internal::AXVoiceSyncBits::AdpcmLoop; } void AXSetVoiceCurrentOffset(virt_ptr<AXVoice> voice, uint32_t offset) { auto extras = internal::getVoiceExtras(voice->index); auto baseAddress = virt_cast<virt_addr>(voice->offsets.data) & 0x1FFFFFFF; if (extras->data.format == AXVoiceFormat::ADPCM) { extras->data.currentOffsetAbs = (baseAddress << 1) + offset; } else if (extras->data.format == AXVoiceFormat::LPCM16) { extras->data.currentOffsetAbs = (baseAddress >> 1) + offset; } else if (extras->data.format == AXVoiceFormat::LPCM8) { extras->data.currentOffsetAbs = baseAddress + offset; } else { decaf_abort(fmt::format("Unexpected voice data type {}", extras->data.format)); } extras->syncBits |= internal::AXVoiceSyncBits::CurrentOffset; } void AXSetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice, uint32_t offset, virt_ptr<const void> samples) { auto offsets = StackObject<AXVoiceOffsets> { }; voice->offsets.data = samples; AXGetVoiceOffsets(voice, offsets); AXSetVoiceCurrentOffset(voice, offset); } AXResult AXSetVoiceDeviceMix(virt_ptr<AXVoice> voice, AXDeviceType type, uint32_t deviceId, virt_ptr<AXVoiceDeviceMixData> mixData) { auto extras = internal::getVoiceExtras(voice->index); switch (type) { case AXDeviceType::TV: decaf_check(deviceId < AXNumTvDevices); for (auto c = 0; c < AXNumTvChannels; ++c) { for (auto b = 0; b < AXNumTvBus; ++b) { extras->tvVolume[deviceId][c][b].volume = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].volume); extras->tvVolume[deviceId][c][b].delta = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].delta); } } break; case AXDeviceType::DRC: decaf_check(deviceId < AXNumDrcDevices); for (auto c = 0; c < AXNumDrcChannels; ++c) { for (auto b = 0; b < AXNumDrcBus; ++b) { extras->drcVolume[deviceId][c][b].volume = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].volume); extras->drcVolume[deviceId][c][b].delta = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].delta); } } break; case AXDeviceType::RMT: decaf_check(deviceId < AXNumRmtDevices); for (auto c = 0; c < AXNumRmtChannels; ++c) { for (auto b = 0; b < AXNumRmtBus; ++b) { extras->rmtVolume[deviceId][c][b].volume = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].volume); extras->rmtVolume[deviceId][c][b].delta = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].delta); } } break; } return AXResult::Success; } void AXSetVoiceEndOffset(virt_ptr<AXVoice> voice, uint32_t offset) { auto extras = internal::getVoiceExtras(voice->index); auto baseAddress = virt_cast<virt_addr>(voice->offsets.data) & 0x1FFFFFFF; voice->offsets.endOffset = offset; if (extras->data.format == AXVoiceFormat::ADPCM) { extras->data.endOffsetAbs = virt_addr { (baseAddress << 1) + offset }; } else if (extras->data.format == AXVoiceFormat::LPCM16) { extras->data.endOffsetAbs = virt_addr { (baseAddress >> 1) + offset }; } else if (extras->data.format == AXVoiceFormat::LPCM8) { extras->data.endOffsetAbs = baseAddress + offset; } else { decaf_abort(fmt::format("Unexpected voice data type {}", extras->data.format)); } extras->syncBits |= internal::AXVoiceSyncBits::EndOffset; } void AXSetVoiceEndOffsetEx(virt_ptr<AXVoice> voice, uint32_t offset, virt_ptr<const void> samples) { auto offsets = StackObject<AXVoiceOffsets> { }; voice->offsets.data = samples; AXGetVoiceOffsets(voice, offsets); AXSetVoiceEndOffset(voice, offset); } AXResult AXSetVoiceInitialTimeDelay(virt_ptr<AXVoice> voice, uint16_t delay) { if (AXIsVoiceRunning(voice)) { return AXResult::VoiceIsRunning; } if (delay > AXGetInputSamplesPerFrame()) { return AXResult::DelayTooBig; } auto extras = internal::getVoiceExtras(voice->index); extras->itdOn = uint16_t { 1 }; extras->itdDelay = delay; extras->syncBits |= internal::AXVoiceSyncBits::Itd; voice->syncBits |= internal::AXVoiceSyncBits::Itd; return AXResult::Success; } void AXSetVoiceLoopOffset(virt_ptr<AXVoice> voice, uint32_t offset) { auto extras = internal::getVoiceExtras(voice->index); auto baseAddress = virt_cast<virt_addr>(voice->offsets.data) & 0x1FFFFFFF; voice->offsets.loopOffset = offset; if (extras->data.format == AXVoiceFormat::ADPCM) { extras->data.loopOffsetAbs = virt_addr { (baseAddress << 1) + offset }; } else if (extras->data.format == AXVoiceFormat::LPCM16) { extras->data.loopOffsetAbs = virt_addr { (baseAddress >> 1) + offset }; } else if (extras->data.format == AXVoiceFormat::LPCM8) { extras->data.loopOffsetAbs = baseAddress + offset; } else { decaf_abort(fmt::format("Unexpected voice data type {}", extras->data.format)); } extras->syncBits |= internal::AXVoiceSyncBits::LoopOffset; } void AXSetVoiceLoopOffsetEx(virt_ptr<AXVoice> voice, uint32_t offset, virt_ptr<const void> samples) { voice->offsets.data = samples; auto offsets = StackObject<AXVoiceOffsets> { }; AXGetVoiceOffsets(voice, offsets); AXSetVoiceLoopOffset(voice, offset); } void AXSetVoiceLoop(virt_ptr<AXVoice> voice, AXVoiceLoop loop) { auto extras = internal::getVoiceExtras(voice->index); voice->offsets.loopingEnabled = loop; extras->data.loopFlag = loop; extras->syncBits |= internal::AXVoiceSyncBits::Loop; } uint32_t AXSetVoiceMixerSelect(virt_ptr<AXVoice> voice, uint32_t mixerSelect) { auto oldMiderSelect = voice->renderer; decaf_warn_stub(); voice->renderer = static_cast<AXRenderer>(mixerSelect); return oldMiderSelect; } void AXSetVoiceOffsets(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets) { decaf_check(offsets->data); voice->offsets = *offsets; internal::AXCafeVoiceData absOffsets; absOffsets.format = offsets->dataType; absOffsets.loopFlag = offsets->loopingEnabled; auto samples = virt_cast<virt_addr>(offsets->data); if (offsets->dataType == AXVoiceFormat::ADPCM) { absOffsets.loopOffsetAbs = ((samples << 1) + offsets->loopOffset) & 0x3fffffff; absOffsets.endOffsetAbs = ((samples << 1) + offsets->endOffset) & 0x3fffffff; absOffsets.currentOffsetAbs = ((samples << 1) + offsets->currentOffset) & 0x3fffffff; absOffsets.memPageNumber = static_cast<uint16_t>((samples + (offsets->currentOffset >> 1)) >> 29); } else if (offsets->dataType == AXVoiceFormat::LPCM16) { absOffsets.loopOffsetAbs = ((samples >> 1) + offsets->loopOffset) & 0x0fffffff; absOffsets.endOffsetAbs = ((samples >> 1) + offsets->endOffset) & 0x0fffffff; absOffsets.currentOffsetAbs = ((samples >> 1) + offsets->currentOffset) & 0x0fffffff; absOffsets.memPageNumber = static_cast<uint16_t>((samples + (offsets->currentOffset << 1)) >> 29); } else if (offsets->dataType == AXVoiceFormat::LPCM8) { absOffsets.loopOffsetAbs = (samples + offsets->loopOffset) & 0x1fffffff; absOffsets.endOffsetAbs = (samples + offsets->endOffset) & 0x1fffffff; absOffsets.currentOffsetAbs = (samples + offsets->currentOffset) & 0x1fffffff; absOffsets.memPageNumber = static_cast<uint16_t>((samples + offsets->currentOffset) >> 29); } else { decaf_abort(fmt::format("Unexpected voice data type {}", offsets->dataType)); } internal::setVoiceAddresses(voice, absOffsets); } void AXSetVoiceOffsetsEx(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets, virt_ptr<void> samples) { auto adjOffsets = StackObject<AXVoiceOffsets> { }; *adjOffsets = *offsets; adjOffsets->data = samples; AXSetVoiceOffsets(voice, adjOffsets); } void AXSetVoicePriority(virt_ptr<AXVoice> voice, uint32_t priority) { voice->priority = priority; } void AXSetVoiceRmtOn(virt_ptr<AXVoice> voice, uint16_t on) { decaf_warn_stub(); } void AXSetVoiceRmtIIRCoefs(virt_ptr<AXVoice> voice, uint16_t filter, var_args) { decaf_warn_stub(); } void AXSetVoiceSrc(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceSrc> src) { auto extras = internal::getVoiceExtras(voice->index); extras->src = *src; voice->syncBits |= internal::AXVoiceSyncBits::Src; } AXVoiceSrcRatioResult AXSetVoiceSrcRatio(virt_ptr<AXVoice> voice, float ratio) { if (ratio < 0.0f) { return AXVoiceSrcRatioResult::RatioLessThanZero; } auto extras = internal::getVoiceExtras(voice->index); extras->src.ratio = ufixed_16_16_t { ratio }; voice->syncBits |= internal::AXVoiceSyncBits::SrcRatio; return AXVoiceSrcRatioResult::Success; } void AXSetVoiceSrcType(virt_ptr<AXVoice> voice, AXVoiceSrcType type) { auto extras = internal::getVoiceExtras(voice->index); if (type == AXVoiceSrcType::None) { extras->srcMode = uint16_t { 2 }; } else if (type == AXVoiceSrcType::Linear) { extras->srcMode = uint16_t { 1 }; } else if (type == AXVoiceSrcType::Unk0) { extras->srcMode = uint16_t { 0 }; extras->srcModeUnk = uint16_t { 0 }; } else if (type == AXVoiceSrcType::Unk1) { extras->srcMode = uint16_t { 0 }; extras->srcModeUnk = uint16_t { 1 }; } else if (type == AXVoiceSrcType::Unk2) { extras->srcMode = uint16_t { 0 }; extras->srcModeUnk = uint16_t { 2 }; } voice->syncBits |= internal::AXVoiceSyncBits::SrcType; } void AXSetVoiceState(virt_ptr<AXVoice> voice, AXVoiceState state) { auto extras = internal::getVoiceExtras(voice->index); if (voice->state != state) { extras->state = static_cast<uint16_t>(state); voice->state = state; voice->syncBits |= internal::AXVoiceSyncBits::State; } } void AXSetVoiceType(virt_ptr<AXVoice> voice, AXVoiceType type) { auto extras = internal::getVoiceExtras(voice->index); extras->type = type; voice->syncBits |= internal::AXVoiceSyncBits::Type; } void AXSetVoiceVe(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceVeData> veData) { auto extras = internal::getVoiceExtras(voice->index); extras->ve = *veData; voice->syncBits |= internal::AXVoiceSyncBits::Ve; } void AXSetVoiceVeDelta(virt_ptr<AXVoice> voice, sfixed_1_0_15_t delta) { auto extras = internal::getVoiceExtras(voice->index); if (extras->ve.delta.value() != delta) { extras->ve.delta = delta; voice->syncBits |= internal::AXVoiceSyncBits::VeDelta; } } int32_t AXVoiceBegin(virt_ptr<AXVoice> voice) { decaf_warn_stub(); // TODO: Implement this properly return AXUserBegin(); } int32_t AXVoiceEnd(virt_ptr<AXVoice> voice) { decaf_warn_stub(); // TODO: Implement this properly return AXUserEnd(); } BOOL AXVoiceIsProtected(virt_ptr<AXVoice> voice) { decaf_warn_stub(); return FALSE; } namespace internal { void initVoices() { auto index = 0u; for (auto &voice : sVoiceData->voices) { voice.index = index++; sAvailVoiceStack.push(virt_addrof(voice)); } } void setVoiceAddresses(virt_ptr<AXVoice> voice, AXCafeVoiceData &offsets) { auto extras = internal::getVoiceExtras(voice->index); extras->data = offsets; if (offsets.format == AXVoiceFormat::ADPCM) { // Ensure offset not on ADPCM header decaf_check((offsets.loopOffsetAbs & 0xF) >= virt_addr { 2 }); decaf_check((offsets.endOffsetAbs & 0xF) >= virt_addr { 2 }); decaf_check((offsets.currentOffsetAbs & 0xF) >= virt_addr { 2 }); } else if (offsets.format == AXVoiceFormat::LPCM8) { std::memset(&extras->adpcm, 0, sizeof(AXVoiceAdpcm)); extras->adpcm.gain = uint16_t { 0x100 }; voice->syncBits |= internal::AXVoiceSyncBits::Adpcm; } else if (offsets.format == AXVoiceFormat::LPCM16) { std::memset(&extras->adpcm, 0, sizeof(AXVoiceAdpcm)); extras->adpcm.gain = uint16_t { 0x800 }; voice->syncBits |= internal::AXVoiceSyncBits::Adpcm; } else { decaf_abort(fmt::format("Unexpected audio data format {}", offsets.format)); } voice->syncBits &= ~( internal::AXVoiceSyncBits::Loop | internal::AXVoiceSyncBits::LoopOffset | internal::AXVoiceSyncBits::EndOffset | internal::AXVoiceSyncBits::CurrentOffset); voice->syncBits |= internal::AXVoiceSyncBits::Addr; } const std::vector<virt_ptr<AXVoice>> getAcquiredVoices() { return sAcquiredVoices; } virt_ptr<AXVoiceExtras> getVoiceExtras(int index) { decaf_check(index >= 0 && index < AXMaxNumVoices); return virt_addrof(sVoiceData->voiceExtras[index]); } } // namespace internal void Library::registerVoiceSymbols() { RegisterFunctionExport(AXAcquireVoice); RegisterFunctionExport(AXAcquireVoiceEx); RegisterFunctionExport(AXCheckVoiceOffsets); RegisterFunctionExport(AXFreeVoice); RegisterFunctionExport(AXGetMaxVoices); RegisterFunctionExport(AXGetVoiceCurrentOffsetEx); RegisterFunctionExport(AXGetVoiceLoopCount); RegisterFunctionExport(AXGetVoiceMixerSelect); RegisterFunctionExport(AXGetVoiceOffsets); RegisterFunctionExport(AXGetVoiceOffsetsEx); RegisterFunctionExport(AXIsVoiceRunning); RegisterFunctionExport(AXSetVoiceAdpcm); RegisterFunctionExport(AXSetVoiceAdpcmLoop); RegisterFunctionExport(AXSetVoiceCurrentOffset); RegisterFunctionExport(AXSetVoiceDeviceMix); RegisterFunctionExport(AXSetVoiceEndOffset); RegisterFunctionExport(AXSetVoiceEndOffsetEx); RegisterFunctionExport(AXSetVoiceInitialTimeDelay); RegisterFunctionExport(AXSetVoiceLoopOffset); RegisterFunctionExport(AXSetVoiceLoopOffsetEx); RegisterFunctionExport(AXSetVoiceLoop); RegisterFunctionExport(AXSetVoiceMixerSelect); RegisterFunctionExport(AXSetVoiceOffsets); RegisterFunctionExport(AXSetVoiceOffsetsEx); RegisterFunctionExport(AXSetVoicePriority); RegisterFunctionExport(AXSetVoiceRmtOn); RegisterFunctionExport(AXSetVoiceRmtIIRCoefs); RegisterFunctionExport(AXSetVoiceSrc); RegisterFunctionExport(AXSetVoiceSrcType); RegisterFunctionExport(AXSetVoiceSrcRatio); RegisterFunctionExport(AXSetVoiceState); RegisterFunctionExport(AXSetVoiceType); RegisterFunctionExport(AXSetVoiceVe); RegisterFunctionExport(AXSetVoiceVeDelta); RegisterFunctionExport(AXVoiceBegin); RegisterFunctionExport(AXVoiceEnd); RegisterFunctionExport(AXVoiceIsProtected); RegisterDataInternal(sVoiceData); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_voice.h ================================================ #pragma once #include "sndcore2_constants.h" #include "sndcore2_device.h" #include "sndcore2_enum.h" #include "cafe/cafe_ppc_interface.h" #include <common/fixed.h> #include <cstdint> #include <libcpu/be2_struct.h> #include <vector> namespace cafe::sndcore2 { using Pcm16Sample = sfixed_1_0_15_t; using AXVoiceCallbackFn = virt_func_ptr< virt_ptr<void>() >; using AXVoiceCallbackExFn = virt_func_ptr< virt_ptr<void>(uint32_t, uint32_t) >; #pragma pack(push, 1) struct AXVoice; struct AXVoiceLink { be2_virt_ptr<AXVoice> next; be2_virt_ptr<AXVoice> prev; }; CHECK_OFFSET(AXVoiceLink, 0x0, next); CHECK_OFFSET(AXVoiceLink, 0x4, prev); CHECK_SIZE(AXVoiceLink, 0x8); struct AXVoiceOffsets { be2_val<AXVoiceFormat> dataType; be2_val<AXVoiceLoop> loopingEnabled; be2_val<uint32_t> loopOffset; be2_val<uint32_t> endOffset; be2_val<uint32_t> currentOffset; be2_virt_ptr<const void> data; }; CHECK_OFFSET(AXVoiceOffsets, 0x0, dataType); CHECK_OFFSET(AXVoiceOffsets, 0x2, loopingEnabled); CHECK_OFFSET(AXVoiceOffsets, 0x4, loopOffset); CHECK_OFFSET(AXVoiceOffsets, 0x8, endOffset); CHECK_OFFSET(AXVoiceOffsets, 0xc, currentOffset); CHECK_OFFSET(AXVoiceOffsets, 0x10, data); CHECK_SIZE(AXVoiceOffsets, 0x14); struct AXVoice { //! The index of this voice out of the total voices be2_val<uint32_t> index; //! Current play state of this voice be2_val<AXVoiceState> state; //! Current volume of this voice be2_val<uint32_t> volume; //! The renderer to use for this voice be2_val<AXRenderer> renderer; //! this is a link used in the stack, we do this in host-memory currently be2_struct<AXVoiceLink> link; //! A link to the next callback to invoke be2_virt_ptr<AXVoice> cbNext; //! The priority of this voice used for force-acquiring a voice be2_val<uint32_t> priority; //! The callback to call if this is force-free'd by another acquire be2_val<AXVoiceCallbackFn> callback; //! The user context to send to the callbacks be2_virt_ptr<void> userContext; //! A bitfield representing different things needing to be synced. be2_val<uint32_t> syncBits; UNKNOWN(0x8); //! The current offset data! be2_struct<AXVoiceOffsets> offsets; //! An extended version of the callback above be2_val<AXVoiceCallbackExFn> callbackEx; //! The reason for the callback being invoked be2_val<uint32_t> callbackReason; be2_val<float> unk0; be2_val<float> unk1; }; CHECK_OFFSET(AXVoice, 0x0, index); CHECK_OFFSET(AXVoice, 0x4, state); CHECK_OFFSET(AXVoice, 0x8, volume); CHECK_OFFSET(AXVoice, 0xc, renderer); CHECK_OFFSET(AXVoice, 0x10, link); CHECK_OFFSET(AXVoice, 0x18, cbNext); CHECK_OFFSET(AXVoice, 0x1c, priority); CHECK_OFFSET(AXVoice, 0x20, callback); CHECK_OFFSET(AXVoice, 0x24, userContext); CHECK_OFFSET(AXVoice, 0x28, syncBits); CHECK_OFFSET(AXVoice, 0x34, offsets); CHECK_OFFSET(AXVoice, 0x48, callbackEx); CHECK_OFFSET(AXVoice, 0x4c, callbackReason); CHECK_OFFSET(AXVoice, 0x50, unk0); CHECK_OFFSET(AXVoice, 0x54, unk1); CHECK_SIZE(AXVoice, 0x58); struct AXVoiceDeviceBusMixData { be2_val<uint16_t> volume; be2_val<int16_t> delta; }; CHECK_OFFSET(AXVoiceDeviceBusMixData, 0x0, volume); CHECK_OFFSET(AXVoiceDeviceBusMixData, 0x2, delta); CHECK_SIZE(AXVoiceDeviceBusMixData, 0x4); struct AXVoiceDeviceMixData { be2_array<AXVoiceDeviceBusMixData, 4> bus; }; CHECK_OFFSET(AXVoiceDeviceMixData, 0x0, bus); CHECK_SIZE(AXVoiceDeviceMixData, 0x10); struct AXVoiceVeData { be2_val<ufixed_0_16_t> volume; be2_val<sfixed_1_0_15_t> delta; }; CHECK_OFFSET(AXVoiceVeData, 0x0, volume); CHECK_OFFSET(AXVoiceVeData, 0x2, delta); CHECK_SIZE(AXVoiceVeData, 0x4); struct AXVoiceAdpcmLoopData { be2_val<uint16_t> predScale; be2_array<int16_t, 2> prevSample; }; CHECK_OFFSET(AXVoiceAdpcmLoopData, 0x0, predScale); CHECK_OFFSET(AXVoiceAdpcmLoopData, 0x2, prevSample); CHECK_SIZE(AXVoiceAdpcmLoopData, 0x6); struct AXVoiceAdpcm { be2_array<int16_t, 16> coefficients; be2_val<uint16_t> gain; be2_val<uint16_t> predScale; be2_array<int16_t, 2> prevSample; }; CHECK_OFFSET(AXVoiceAdpcm, 0x0, coefficients); CHECK_OFFSET(AXVoiceAdpcm, 0x20, gain); CHECK_OFFSET(AXVoiceAdpcm, 0x22, predScale); CHECK_OFFSET(AXVoiceAdpcm, 0x24, prevSample); CHECK_SIZE(AXVoiceAdpcm, 0x28); // Note: "Src" = "sample rate converter", not "source" struct AXVoiceSrc { // Playback rate be2_val<ufixed_16_16_t> ratio; // Used by the resampler be2_val<ufixed_0_16_t> currentOffsetFrac; be2_array<int16_t, 4> lastSample; }; CHECK_OFFSET(AXVoiceSrc, 0x0, ratio); CHECK_OFFSET(AXVoiceSrc, 0x4, currentOffsetFrac); CHECK_OFFSET(AXVoiceSrc, 0x6, lastSample); CHECK_SIZE(AXVoiceSrc, 0xe); #pragma pack(pop) virt_ptr<AXVoice> AXAcquireVoice(uint32_t priority, AXVoiceCallbackFn callback, virt_ptr<void> userContext); virt_ptr<AXVoice> AXAcquireVoiceEx(uint32_t priority, AXVoiceCallbackExFn callback, virt_ptr<void> userContext); BOOL AXCheckVoiceOffsets(virt_ptr<AXVoiceOffsets> offsets); void AXFreeVoice(virt_ptr<AXVoice> voice); uint32_t AXGetMaxVoices(); uint32_t AXGetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice, virt_ptr<const void> samples); uint32_t AXGetVoiceLoopCount(virt_ptr<AXVoice> voice); uint32_t AXGetVoiceMixerSelect(virt_ptr<AXVoice> voice); void AXGetVoiceOffsetsEx(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets, virt_ptr<const void> samples); void AXGetVoiceOffsets(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets); BOOL AXIsVoiceRunning(virt_ptr<AXVoice> voice); void AXSetVoiceAdpcm(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceAdpcm> adpcm); void AXSetVoiceAdpcmLoop(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceAdpcmLoopData> loopData); void AXSetVoiceCurrentOffset(virt_ptr<AXVoice> voice, uint32_t offset); void AXSetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice, uint32_t offset, virt_ptr<const void> samples); AXResult AXSetVoiceDeviceMix(virt_ptr<AXVoice> voice, AXDeviceType type, uint32_t id, virt_ptr<AXVoiceDeviceMixData> mixData); void AXSetVoiceEndOffset(virt_ptr<AXVoice> voice, uint32_t offset); void AXSetVoiceEndOffsetEx(virt_ptr<AXVoice> voice, uint32_t offset, virt_ptr<const void> samples); AXResult AXSetVoiceInitialTimeDelay(virt_ptr<AXVoice> voice, uint16_t delay); void AXSetVoiceLoopOffset(virt_ptr<AXVoice> voice, uint32_t offset); void AXSetVoiceLoopOffsetEx(virt_ptr<AXVoice> voice, uint32_t offset, virt_ptr<const void> samples); void AXSetVoiceLoop(virt_ptr<AXVoice> voice, AXVoiceLoop loop); uint32_t AXSetVoiceMixerSelect(virt_ptr<AXVoice> voice, uint32_t mixerSelect); void AXSetVoiceOffsets(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets); void AXSetVoiceOffsetsEx(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceOffsets> offsets, virt_ptr<void> samples); void AXSetVoicePriority(virt_ptr<AXVoice> voice, uint32_t priority); void AXSetVoiceRmtOn(virt_ptr<AXVoice> voice, uint16_t on); void AXSetVoiceRmtIIRCoefs(virt_ptr<AXVoice> voice, uint16_t filter, var_args); void AXSetVoiceSrc(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceSrc> src); AXVoiceSrcRatioResult AXSetVoiceSrcRatio(virt_ptr<AXVoice> voice, float ratio); void AXSetVoiceSrcType(virt_ptr<AXVoice> voice, AXVoiceSrcType type); void AXSetVoiceState(virt_ptr<AXVoice> voice, AXVoiceState state); void AXSetVoiceType(virt_ptr<AXVoice> voice, AXVoiceType type); void AXSetVoiceVe(virt_ptr<AXVoice> voice, virt_ptr<AXVoiceVeData> veData); void AXSetVoiceVeDelta(virt_ptr<AXVoice> voice, sfixed_1_0_15_t delta); namespace internal { #pragma pack(push, 1) struct AXCafeVoiceData { be2_val<AXVoiceLoop> loopFlag; be2_val<AXVoiceFormat> format; be2_val<uint16_t> memPageNumber; be2_val<virt_addr> loopOffsetAbs; be2_val<virt_addr> endOffsetAbs; be2_val<virt_addr> currentOffsetAbs; }; CHECK_OFFSET(AXCafeVoiceData, 0x0, loopFlag); CHECK_OFFSET(AXCafeVoiceData, 0x2, format); CHECK_OFFSET(AXCafeVoiceData, 0x4, memPageNumber); CHECK_OFFSET(AXCafeVoiceData, 0x6, loopOffsetAbs); CHECK_OFFSET(AXCafeVoiceData, 0xa, endOffsetAbs); CHECK_OFFSET(AXCafeVoiceData, 0xe, currentOffsetAbs); CHECK_SIZE(AXCafeVoiceData, 0x12); struct AXCafeVoiceExtras { UNKNOWN(0x8); uint16_t srcMode; uint16_t srcModeUnk; UNKNOWN(0x2); AXVoiceType type; UNKNOWN(0x15a); uint16_t state; uint16_t itdOn; UNKNOWN(0x2); uint16_t itdDelay; UNKNOWN(0x8); AXVoiceVeData ve; AXCafeVoiceData data; AXVoiceAdpcm adpcm; AXVoiceSrc src; AXVoiceAdpcmLoopData adpcmLoop; UNKNOWN(0xe4); uint32_t syncBits; UNKNOWN(0xc); }; CHECK_OFFSET(AXCafeVoiceExtras, 0x8, srcMode); CHECK_OFFSET(AXCafeVoiceExtras, 0xa, srcModeUnk); CHECK_OFFSET(AXCafeVoiceExtras, 0xe, type); CHECK_OFFSET(AXCafeVoiceExtras, 0x16a, state); CHECK_OFFSET(AXCafeVoiceExtras, 0x16c, itdOn); CHECK_OFFSET(AXCafeVoiceExtras, 0x170, itdDelay); CHECK_OFFSET(AXCafeVoiceExtras, 0x17a, ve); CHECK_OFFSET(AXCafeVoiceExtras, 0x17e, data); CHECK_OFFSET(AXCafeVoiceExtras, 0x190, adpcm); CHECK_OFFSET(AXCafeVoiceExtras, 0x1b8, src); CHECK_OFFSET(AXCafeVoiceExtras, 0x1c6, adpcmLoop); CHECK_OFFSET(AXCafeVoiceExtras, 0x2b0, syncBits); CHECK_SIZE(AXCafeVoiceExtras, 0x2c0); struct AXVoiceExtras : AXCafeVoiceExtras { struct MixVolume { ufixed_1_15_t volume; ufixed_1_15_t delta; }; // Volume on each of 6 surround channels for TV output MixVolume tvVolume[AXNumTvDevices][AXNumTvChannels][AXNumTvBus]; // Volume on each of 4 channels for DRC output (2 stereo channels + ???) MixVolume drcVolume[AXNumDrcDevices][AXNumDrcChannels][AXNumDrcBus]; // Volume for each of 4 controller speakers MixVolume rmtVolume[AXNumRmtDevices][AXNumRmtChannels][AXNumRmtBus]; // Number of loops so far uint32_t loopCount; // Used during decoding uint32_t numSamples; Pcm16Sample samples[144]; }; #pragma pack(pop) void initVoices(); void setVoiceAddresses(virt_ptr<AXVoice> voice, AXCafeVoiceData &offsets); const std::vector<virt_ptr<AXVoice>> getAcquiredVoices(); virt_ptr<AXVoiceExtras> getVoiceExtras(int index); } // namespace internal } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_vs.cpp ================================================ #include "sndcore2.h" #include "sndcore2_vs.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::sndcore2 { AXResult AXGetDRCVSMode(virt_ptr<AXDRCVSMode> outMode) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSMode(AXDRCVSMode mode) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSDownmixBalance(AXDRCOutput output, float balance) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSLC(AXDRCVSLC lc) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSLimiter(BOOL limit) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSLimiterThreshold(float threshold) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSOutputGain(AXDRCOutput output, float gain) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSSpeakerPosition(AXDRCOutput output, AXDRCVSSpeakerPosition pos) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSSurroundDepth(AXDRCOutput output, float depth) { decaf_warn_stub(); return AXResult::Success; } AXResult AXSetDRCVSSurroundLevelGain(AXDRCVSSurroundLevelGain gain) { decaf_warn_stub(); return AXResult::Success; } void Library::registerVsSymbols() { RegisterFunctionExport(AXGetDRCVSMode); RegisterFunctionExport(AXSetDRCVSMode); RegisterFunctionExport(AXSetDRCVSDownmixBalance); RegisterFunctionExport(AXSetDRCVSLC); RegisterFunctionExport(AXSetDRCVSLimiter); RegisterFunctionExport(AXSetDRCVSLimiterThreshold); RegisterFunctionExport(AXSetDRCVSOutputGain); RegisterFunctionExport(AXSetDRCVSSpeakerPosition); RegisterFunctionExport(AXSetDRCVSSurroundDepth); RegisterFunctionExport(AXSetDRCVSSurroundLevelGain); } } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_vs.h ================================================ #pragma once #include "sndcore2_enum.h" #include <libcpu/be2_struct.h> namespace cafe::sndcore2 { AXResult AXGetDRCVSMode(virt_ptr<AXDRCVSMode> outMode); AXResult AXSetDRCVSMode(AXDRCVSMode mode); AXResult AXSetDRCVSDownmixBalance(AXDRCOutput output, float balance); AXResult AXSetDRCVSLC(AXDRCVSLC lc); AXResult AXSetDRCVSLimiter(BOOL limit); AXResult AXSetDRCVSLimiterThreshold(float threshold); AXResult AXSetDRCVSOutputGain(AXDRCOutput output, float gain); AXResult AXSetDRCVSSpeakerPosition(AXDRCOutput output, AXDRCVSSpeakerPosition pos); AXResult AXSetDRCVSSurroundDepth(AXDRCOutput output, float depth); AXResult AXSetDRCVSSurroundLevelGain(AXDRCVSSurroundLevelGain gain); } // namespace cafe::sndcore2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_hooks.h" namespace cafe::snduser2 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { internal::initialiseAxfxHooks(); return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerAxArtSymbols(); registerAxfxChorusSymbols(); registerAxfxChorusExpSymbols(); registerAxfxDelaySymbols(); registerAxfxDelayExpSymbols(); registerAxfxHooksSymbols(); registerAxfxMultiChReverbSymbols(); registerAxfxReverbHiSymbols(); registerAxfxReverbHiExpSymbols(); registerAxfxReverbStdSymbols(); registerAxfxReverbStdExpSymbols(); registerMixSymbols(); registerSpSymbols(); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::snduser2 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::snduser2, "snduser2.rpl") { } // For snd_user.rpl to use Library(hle::LibraryId id, const char *name) : hle::Library(id, name) { } protected: virtual void registerSymbols() override; private: void registerAxArtSymbols(); void registerAxfxChorusSymbols(); void registerAxfxChorusExpSymbols(); void registerAxfxDelaySymbols(); void registerAxfxDelayExpSymbols(); void registerAxfxHooksSymbols(); void registerAxfxMultiChReverbSymbols(); void registerAxfxReverbHiSymbols(); void registerAxfxReverbHiExpSymbols(); void registerAxfxReverbStdSymbols(); void registerAxfxReverbStdExpSymbols(); void registerMixSymbols(); void registerSpSymbols(); }; } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axart.cpp ================================================ #include "snduser2.h" #include "snduser2_axart.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::snduser2 { void AXARTServiceSounds() { decaf_warn_stub(); } void Library::registerAxArtSymbols() { RegisterFunctionExport(AXARTServiceSounds); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axart.h ================================================ #pragma once namespace cafe::snduser2 { void AXARTServiceSounds(); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx.h ================================================ #pragma once #include "snduser2_enum.h" #include "cafe/libraries/sndcore2/sndcore2_device.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { using AXAuxCallbackData = sndcore2::AXAuxCallbackData; struct AXFXBuffers { virt_ptr<int32_t> left; virt_ptr<int32_t> right; virt_ptr<int32_t> surround; }; CHECK_OFFSET(AXFXBuffers, 0x00, left); CHECK_OFFSET(AXFXBuffers, 0x04, right); CHECK_OFFSET(AXFXBuffers, 0x08, surround); CHECK_SIZE(AXFXBuffers, 0x0C); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorus.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_chorus.h" #include "snduser2_axfx_chorusexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; /** * Converts the params from the AXFXChorus struct to the AXFXChorusExp params. */ static void translateChorusUserParamsToExp(virt_ptr<AXFXChorus> chorus) { // TODO: Implement translateChorusUserParamsToExp decaf_warn_stub(); } int32_t AXFXChorusGetMemSize(virt_ptr<AXFXChorus> chorus) { return AXFXChorusExpGetMemSize(virt_addrof(chorus->chorusExp)); } BOOL AXFXChorusInit(virt_ptr<AXFXChorus> chorus) { translateChorusUserParamsToExp(chorus); return AXFXChorusExpInit(virt_addrof(chorus->chorusExp)); } BOOL AXFXChorusShutdown(virt_ptr<AXFXChorus> chorus) { AXFXChorusExpShutdown(virt_addrof(chorus->chorusExp)); return TRUE; } void AXFXChorusCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXChorus> chorus) { AXFXChorusExpCallback(buffers, virt_addrof(chorus->chorusExp)); } BOOL AXFXChorusSettings(virt_ptr<AXFXChorus> chorus) { translateChorusUserParamsToExp(chorus); return AXFXChorusExpSettings(virt_addrof(chorus->chorusExp)); } void Library::registerAxfxChorusSymbols() { RegisterFunctionExport(AXFXChorusGetMemSize); RegisterFunctionExport(AXFXChorusInit); RegisterFunctionExport(AXFXChorusShutdown); RegisterFunctionExport(AXFXChorusCallback); RegisterFunctionExport(AXFXChorusSettings); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorus.h ================================================ #pragma once #include "snduser2_axfx.h" #include "snduser2_axfx_chorusexp.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXChorus { be2_struct<AXFXChorusExp> chorusExp; UNKNOWN(0xC); }; CHECK_SIZE(AXFXChorus, 0xAC); int32_t AXFXChorusGetMemSize(virt_ptr<AXFXChorus> chorus); BOOL AXFXChorusInit(virt_ptr<AXFXChorus> chorus); BOOL AXFXChorusShutdown(virt_ptr<AXFXChorus> chorus); void AXFXChorusCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXChorus> chorus); BOOL AXFXChorusSettings(virt_ptr<AXFXChorus> chorus); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorusexp.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_chorusexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; int32_t AXFXChorusExpGetMemSize(virt_ptr<AXFXChorusExp> chorus) { return 12 * (AXGetInputSamplesPerSec() / 10); } BOOL AXFXChorusExpInit(virt_ptr<AXFXChorusExp> chorus) { decaf_warn_stub(); return TRUE; } void AXFXChorusExpShutdown(virt_ptr<AXFXChorusExp> chorus) { decaf_warn_stub(); } void AXFXChorusExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXChorusExp> chorus) { decaf_warn_stub(); } BOOL AXFXChorusExpSettings(virt_ptr<AXFXChorusExp> chorus) { decaf_warn_stub(); return TRUE; } BOOL AXFXChorusExpSettingsUpdate(virt_ptr<AXFXChorusExp> chorus) { decaf_warn_stub(); return TRUE; } void Library::registerAxfxChorusExpSymbols() { RegisterFunctionExport(AXFXChorusExpGetMemSize); RegisterFunctionExport(AXFXChorusExpInit); RegisterFunctionExport(AXFXChorusExpShutdown); RegisterFunctionExport(AXFXChorusExpCallback); RegisterFunctionExport(AXFXChorusExpSettings); RegisterFunctionExport(AXFXChorusExpSettingsUpdate); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorusexp.h ================================================ #pragma once #include "snduser2_axfx.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXChorusExp { UNKNOWN(0xA0); }; CHECK_SIZE(AXFXChorusExp, 0xA0); int32_t AXFXChorusExpGetMemSize(virt_ptr<AXFXChorusExp> chorus); BOOL AXFXChorusExpInit(virt_ptr<AXFXChorusExp> chorus); void AXFXChorusExpShutdown(virt_ptr<AXFXChorusExp> chorus); void AXFXChorusExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXChorusExp> chorus); BOOL AXFXChorusExpSettings(virt_ptr<AXFXChorusExp> chorus); BOOL AXFXChorusExpSettingsUpdate(virt_ptr<AXFXChorusExp> chorus); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delay.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_delay.h" #include "snduser2_axfx_hooks.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; int32_t AXFXDelayGetMemSize(virt_ptr<AXFXDelay> delay) { auto samplersPerMS = AXGetInputSamplesPerSec() / 1000; auto totalDelayMS = delay->userDelayMS[0] + delay->userDelayMS[1] + delay->userDelayMS[2]; return sizeof(int32_t) * totalDelayMS * samplersPerMS; } BOOL AXFXDelayInit(virt_ptr<AXFXDelay> delay) { auto samplersPerMS = AXGetInputSamplesPerSec() / 1000; delay->stateFlags = AXFXDelayStateFlags::Shutdown; // Calculate delay buffer size for (auto i = 0u; i < 3; ++i) { if (!delay->userDelayMS[i]) { AXFXDelayShutdown(delay); return FALSE; } delay->delayBufferMaxNumSamples[i] = samplersPerMS * delay->userDelayMS[i]; } // Allocate delay buffers for (auto i = 0u; i < 3; ++i) { delay->delayBuffer[i] = virt_cast<uint32_t *>( internal::axfxAlloc(4 * delay->delayBufferMaxNumSamples[i])); if (!delay->delayBuffer[i]) { AXFXDelayShutdown(delay); return FALSE; } } // Initialise parameters for (auto i = 0u; i < 3; ++i) { if (delay->userFeedbackGain[i] > 100 || delay->userOutputGain[i] > 100) { AXFXDelayShutdown(delay); return FALSE; } delay->delayBufferSamplePos[i] = 0u; delay->feedbackGain[i] = static_cast<int32_t>(128.0f * (delay->userFeedbackGain[i] / 100.0f)); delay->outputGain[i] = static_cast<int32_t>(128.0f * (delay->userOutputGain[i] / 100.0f)); } return TRUE; } void AXFXDelayShutdown(virt_ptr<AXFXDelay> delay) { delay->stateFlags |= AXFXDelayStateFlags::Shutdown; for (auto i = 0u; i < 3; ++i) { internal::axfxFree(delay->delayBuffer[i]); delay->delayBuffer[i] = nullptr; } } void AXFXDelayCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXDelay> delay) { // TODO: Implement AXFXDelayCallback decaf_warn_stub(); } void Library::registerAxfxDelaySymbols() { RegisterFunctionExport(AXFXDelayGetMemSize); RegisterFunctionExport(AXFXDelayInit); RegisterFunctionExport(AXFXDelayShutdown); RegisterFunctionExport(AXFXDelayCallback); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delay.h ================================================ #pragma once #include "snduser2_axfx.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { #include "common/enum_start.inl" ENUM_BEG(AXFXDelayStateFlags, uint32_t) ENUM_VALUE(Shutdown, 1 << 0) ENUM_VALUE(Initialised, 1 << 1) ENUM_END(AXFXDelayStateFlags) #include "common/enum_end.inl" struct AXFXDelay { //! Buffer to store the delayed samples in. be2_array<virt_ptr<uint32_t>, 3> delayBuffer; //! Current position of per channel delayBuffer in number of samples. be2_array<uint32_t, 3> delayBufferSamplePos; //! Size of the per channel delayBuffer in number of samples. be2_array<uint32_t, 3> delayBufferMaxNumSamples; //! Feedback gain. be2_array<int32_t, 3> feedbackGain; //! Output gain. be2_array<int32_t, 3> outputGain; //! State. be2_val<AXFXDelayStateFlags> stateFlags; //! User provided parameter for duration of the delay. be2_array<uint32_t, 3> userDelayMS; //! User provided parameter for feedbackGain be2_array<uint32_t, 3> userFeedbackGain; //! User provided parameter for outputGain be2_array<uint32_t, 3> userOutputGain; }; CHECK_OFFSET(AXFXDelay, 0x00, delayBuffer); CHECK_OFFSET(AXFXDelay, 0x0C, delayBufferSamplePos); CHECK_OFFSET(AXFXDelay, 0x18, delayBufferMaxNumSamples); CHECK_OFFSET(AXFXDelay, 0x3C, stateFlags); CHECK_OFFSET(AXFXDelay, 0x40, userDelayMS); CHECK_OFFSET(AXFXDelay, 0x4C, userFeedbackGain); CHECK_OFFSET(AXFXDelay, 0x58, userOutputGain); CHECK_SIZE(AXFXDelay, 0x64); int32_t AXFXDelayGetMemSize(virt_ptr<AXFXDelay> delay); BOOL AXFXDelayInit(virt_ptr<AXFXDelay> delay); void AXFXDelayShutdown(virt_ptr<AXFXDelay> delay); void AXFXDelayCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXDelay> delay); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delayexp.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_delayexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; int32_t AXFXDelayExpGetMemSize(virt_ptr<AXFXDelayExp> delay) { auto samplersPerMS = AXGetInputSamplesPerSec() / 1000.0f; auto perChannelSamples = delay->userDelayMS * samplersPerMS; auto numSamples = 3 * perChannelSamples; return static_cast<int32_t>(sizeof(int32_t) * numSamples); } BOOL AXFXDelayExpInit(virt_ptr<AXFXDelayExp> delay) { decaf_warn_stub(); return TRUE; } void AXFXDelayExpShutdown(virt_ptr<AXFXDelayExp> delay) { decaf_warn_stub(); } void AXFXDelayExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXDelayExp> delay) { decaf_warn_stub(); } BOOL AXFXDelayExpSettings(virt_ptr<AXFXDelayExp> delay) { decaf_warn_stub(); return TRUE; } BOOL AXFXDelayExpSettingsUpdate(virt_ptr<AXFXDelayExp> delay) { decaf_warn_stub(); return TRUE; } void Library::registerAxfxDelayExpSymbols() { RegisterFunctionExport(AXFXDelayExpGetMemSize); RegisterFunctionExport(AXFXDelayExpInit); RegisterFunctionExport(AXFXDelayExpShutdown); RegisterFunctionExport(AXFXDelayExpCallback); RegisterFunctionExport(AXFXDelayExpSettings); RegisterFunctionExport(AXFXDelayExpSettingsUpdate); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delayexp.h ================================================ #pragma once #include "snduser2_axfx.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { #include "common/enum_start.inl" ENUM_BEG(AXFXDelayExpStateFlags, uint32_t) ENUM_VALUE(Shutdown, 1 << 0) ENUM_VALUE(Initialised, 1 << 1) ENUM_END(AXFXDelayExpStateFlags) #include "common/enum_end.inl" struct AXFXDelayExp { UNKNOWN(0x34); //! State be2_val<AXFXDelayExpStateFlags> stateFlags; //! User provided parameter for duration of the delay buffer. be2_val<float> userDelayMS; // Unknown size... }; CHECK_OFFSET(AXFXDelayExp, 0x34, stateFlags); CHECK_OFFSET(AXFXDelayExp, 0x38, userDelayMS); UNKNOWN_SIZE(AXFXDelayExp); int32_t AXFXDelayExpGetMemSize(virt_ptr<AXFXDelayExp> delay); BOOL AXFXDelayExpInit(virt_ptr<AXFXDelayExp> delay); void AXFXDelayExpShutdown(virt_ptr<AXFXDelayExp> delay); void AXFXDelayExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXDelayExp> delay); BOOL AXFXDelayExpSettings(virt_ptr<AXFXDelayExp> delay); BOOL AXFXDelayExpSettingsUpdate(virt_ptr<AXFXDelayExp> delay); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_hooks.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_hooks.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include <libcpu/state.h> namespace cafe::snduser2 { struct StaticAxfxHooksData { be2_val<AXFXAllocFn> allocFuncPtr; be2_val<AXFXFreeFn> freeFuncPtr; }; static virt_ptr<StaticAxfxHooksData> sAxfxHooksData = nullptr; static AXFXAllocFn sAxfxDefaultAlloc = nullptr; static AXFXFreeFn sAxfxDefaultFree = nullptr; void AXFXGetHooks(virt_ptr<AXFXAllocFn> allocFn, virt_ptr<AXFXFreeFn> freeFn) { *allocFn = sAxfxHooksData->allocFuncPtr; *freeFn = sAxfxHooksData->freeFuncPtr; } void AXFXSetHooks(AXFXAllocFn allocFn, AXFXFreeFn freeFn) { sAxfxHooksData->allocFuncPtr = allocFn; sAxfxHooksData->freeFuncPtr = freeFn; } namespace internal { static virt_ptr<void> defaultAxfxAlloc(uint32_t size) { return coreinit::MEMAllocFromDefaultHeap(size); } static void defaultAxfxFree(virt_ptr<void> ptr) { coreinit::MEMFreeToDefaultHeap(ptr); } virt_ptr<void> axfxAlloc(uint32_t size) { return cafe::invoke(cpu::this_core::state(), sAxfxHooksData->allocFuncPtr, size); } void axfxFree(virt_ptr<void> ptr) { cafe::invoke(cpu::this_core::state(), sAxfxHooksData->freeFuncPtr, ptr); } void initialiseAxfxHooks() { sAxfxHooksData->allocFuncPtr = sAxfxDefaultAlloc; sAxfxHooksData->freeFuncPtr = sAxfxDefaultFree; } } // namespace internal void Library::registerAxfxHooksSymbols() { RegisterFunctionExport(AXFXGetHooks); RegisterFunctionExport(AXFXSetHooks); RegisterDataInternal(sAxfxHooksData); RegisterFunctionInternal(internal::defaultAxfxAlloc, sAxfxDefaultAlloc); RegisterFunctionInternal(internal::defaultAxfxFree, sAxfxDefaultFree); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_hooks.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::snduser2 { using AXFXAllocFn = virt_func_ptr< virt_ptr<void> (uint32_t size) >; using AXFXFreeFn = virt_func_ptr< void (virt_ptr<void> ptr) >; void AXFXGetHooks(virt_ptr<AXFXAllocFn> allocFn, virt_ptr<AXFXFreeFn> freeFn); void AXFXSetHooks(AXFXAllocFn allocFn, AXFXFreeFn freeFn); namespace internal { virt_ptr<void> axfxAlloc(uint32_t size); void axfxFree(virt_ptr<void> ptr); void initialiseAxfxHooks(); } // namespace internal } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_multichreverb.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_multichreverb.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::snduser2 { int32_t AXFXMultiChReverbGetMemSize(virt_ptr<AXFXMultiChReverb> reverb) { decaf_warn_stub(); return 32; } BOOL AXFXMultiChReverbInit(virt_ptr<AXFXMultiChReverb> reverb, AXFXReverbType type, AXFXSampleRate sampleRate) { auto samplesPerSecond = 32000; if (sampleRate == AXFXSampleRate::Rate48khz) { samplesPerSecond = 48000; } decaf_warn_stub(); return TRUE; } void AXFXMultiChReverbShutdown(virt_ptr<AXFXMultiChReverb> reverb) { decaf_warn_stub(); } BOOL AXFXMultiChReverbParametersPreset(virt_ptr<AXFXMultiChReverb> reverb, AXFXReverbPreset preset) { decaf_warn_stub(); return TRUE; } BOOL AXFXMultiChReverbSettingsUpdate(virt_ptr<AXFXMultiChReverb> reverb) { decaf_warn_stub(); return TRUE; } BOOL AXFXMultiChReverbSettingsUpdateNoReset(virt_ptr<AXFXMultiChReverb> reverb) { decaf_warn_stub(); return TRUE; } void AXFXMultiChReverbCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXMultiChReverb> data, virt_ptr<AXAuxCallbackData> auxData) { decaf_warn_stub(); } void Library::registerAxfxMultiChReverbSymbols() { RegisterFunctionExport(AXFXMultiChReverbGetMemSize); RegisterFunctionExport(AXFXMultiChReverbInit); RegisterFunctionExport(AXFXMultiChReverbShutdown); RegisterFunctionExport(AXFXMultiChReverbParametersPreset); RegisterFunctionExport(AXFXMultiChReverbSettingsUpdate); RegisterFunctionExport(AXFXMultiChReverbSettingsUpdateNoReset); RegisterFunctionExport(AXFXMultiChReverbCallback); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_multichreverb.h ================================================ #pragma once #include "snduser2_axfx.h" #include "snduser2_enum.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXMultiChReverb { UNKNOWN(0x24); be2_val<AXFXReverbType> type; //! Samples per second, set to either 48000 or 32000 in AXFXMultiChReverbInit. be2_val<uint32_t> samplesPerSecond; }; CHECK_OFFSET(AXFXMultiChReverb, 0x24, type); CHECK_OFFSET(AXFXMultiChReverb, 0x28, samplesPerSecond); UNKNOWN_SIZE(AXFXMultiChReverb); int32_t AXFXMultiChReverbGetMemSize(virt_ptr<AXFXMultiChReverb> reverb); BOOL AXFXMultiChReverbInit(virt_ptr<AXFXMultiChReverb> reverb, AXFXReverbType type, AXFXSampleRate sampleRate); void AXFXMultiChReverbShutdown(virt_ptr<AXFXMultiChReverb> reverb); BOOL AXFXMultiChReverbParametersPreset(virt_ptr<AXFXMultiChReverb> reverb, AXFXReverbPreset preset); BOOL AXFXMultiChReverbSettingsUpdate(virt_ptr<AXFXMultiChReverb> reverb); BOOL AXFXMultiChReverbSettingsUpdateNoReset(virt_ptr<AXFXMultiChReverb> reverb); void AXFXMultiChReverbCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXMultiChReverb> data, virt_ptr<AXAuxCallbackData> auxData); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhi.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_reverbhi.h" #include "snduser2_axfx_reverbhiexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; /** * Converts the params from the AXFXReverbHi struct to the AXFXReverbHiExp params. */ static void translateReverbHiUserParamsToExp(virt_ptr<AXFXReverbHi> reverb) { // TODO: Implement translateReverbUserParamsToExp decaf_warn_stub(); } int32_t AXFXReverbHiGetMemSize(virt_ptr<AXFXReverbHi> reverb) { reverb->reverbExp.preDelaySeconds = reverb->userPreDelaySeconds; return AXFXReverbHiExpGetMemSize(virt_addrof(reverb->reverbExp)); } BOOL AXFXReverbHiInit(virt_ptr<AXFXReverbHi> reverb) { translateReverbHiUserParamsToExp(reverb); return AXFXReverbHiExpInit(virt_addrof(reverb->reverbExp)); } BOOL AXFXReverbHiShutdown(virt_ptr<AXFXReverbHi> reverb) { AXFXReverbHiExpShutdown(virt_addrof(reverb->reverbExp)); return TRUE; } void AXFXReverbHiCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbHi> reverb) { AXFXReverbHiExpCallback(buffers, virt_addrof(reverb->reverbExp)); } BOOL AXFXReverbHiSettings(virt_ptr<AXFXReverbHi> reverb) { translateReverbHiUserParamsToExp(reverb); return AXFXReverbHiExpSettings(virt_addrof(reverb->reverbExp)); } void Library::registerAxfxReverbHiSymbols() { RegisterFunctionExport(AXFXReverbHiGetMemSize); RegisterFunctionExport(AXFXReverbHiInit); RegisterFunctionExport(AXFXReverbHiShutdown); RegisterFunctionExport(AXFXReverbHiCallback); RegisterFunctionExport(AXFXReverbHiSettings); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhi.h ================================================ #pragma once #include "snduser2_axfx.h" #include "snduser2_axfx_reverbhiexp.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXReverbHi { be2_struct<AXFXReverbHiExp> reverbExp; UNKNOWN(0x10); //! Reverb Pre-Delay in seconds be2_val<float> userPreDelaySeconds; UNKNOWN(0x4); }; CHECK_OFFSET(AXFXReverbHi, 0x158, userPreDelaySeconds); CHECK_SIZE(AXFXReverbHi, 0x160); int32_t AXFXReverbHiGetMemSize(virt_ptr<AXFXReverbHi> reverb); BOOL AXFXReverbHiInit(virt_ptr<AXFXReverbHi> reverb); BOOL AXFXReverbHiShutdown(virt_ptr<AXFXReverbHi> reverb); void AXFXReverbHiCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbHi> reverb); BOOL AXFXReverbHiSettings(virt_ptr<AXFXReverbHi> reverb); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhiexp.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_reverbhiexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; static constexpr int32_t ReverbHiMemSizeSamplesBase = 0xF31; static constexpr int32_t ReverbHiMemSizeSamplesTable[] = { 0x6FD, 0x7CF, 0x91D, 0x1B1, 0x95, 0x2F, 0x49, 0x43, 0x95, 0x125, 0x1C1, 0xFB, 0x67, 0x2F, 0x49, 0x43, 0x3B3, 0x551, 0x5FB, 0x1B1, 0x89, 0x2F, 0x49, 0x43, 0x4FF, 0x5FB, 0x7B5, 0x1FD, 0x95, 0x2F, 0x49, 0x43, 0x5FB, 0x737, 0x8F9, 0x233, 0xB3, 0x2F, 0x49, 0x43, 0x71F, 0x935, 0xA85, 0x23B, 0x89, 0x2F, 0x49, 0x43, 0x71F, 0x935, 0xA85, 0x23B, 0xB3, 0x2F, 0x49, 0x43, }; int32_t AXFXReverbHiExpGetMemSize(virt_ptr<AXFXReverbHiExp> reverb) { auto delaySamples = reverb->preDelaySeconds * static_cast<float>(AXGetInputSamplesPerSec()); auto perChanSamples = delaySamples + ReverbHiMemSizeSamplesBase + ReverbHiMemSizeSamplesTable[48] + ReverbHiMemSizeSamplesTable[49] + ReverbHiMemSizeSamplesTable[50] + ReverbHiMemSizeSamplesTable[51] + ReverbHiMemSizeSamplesTable[52]; auto numSamples = perChanSamples * 3 + ReverbHiMemSizeSamplesTable[53] + ReverbHiMemSizeSamplesTable[54] + ReverbHiMemSizeSamplesTable[55]; return static_cast<int32_t>(sizeof(int32_t) * numSamples); } BOOL AXFXReverbHiExpInit(virt_ptr<AXFXReverbHiExp> reverb) { decaf_warn_stub(); return TRUE; } void AXFXReverbHiExpShutdown(virt_ptr<AXFXReverbHiExp> reverb) { decaf_warn_stub(); } void AXFXReverbHiExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbHiExp> reverb) { decaf_warn_stub(); } BOOL AXFXReverbHiExpSettings(virt_ptr<AXFXReverbHiExp> reverb) { decaf_warn_stub(); return TRUE; } BOOL AXFXReverbHiExpSettingsUpdate(virt_ptr<AXFXReverbHiExp> reverb) { decaf_warn_stub(); return TRUE; } void Library::registerAxfxReverbHiExpSymbols() { RegisterFunctionExport(AXFXReverbHiExpGetMemSize); RegisterFunctionExport(AXFXReverbHiExpInit); RegisterFunctionExport(AXFXReverbHiExpShutdown); RegisterFunctionExport(AXFXReverbHiExpCallback); RegisterFunctionExport(AXFXReverbHiExpSettings); RegisterFunctionExport(AXFXReverbHiExpSettingsUpdate); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhiexp.h ================================================ #pragma once #include "snduser2_axfx.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXReverbHiExp { UNKNOWN(0x114); //! Reverb Pre-Delay in seconds be2_val<float> preDelaySeconds; UNKNOWN(0x30); }; CHECK_OFFSET(AXFXReverbHiExp, 0x114, preDelaySeconds); CHECK_SIZE(AXFXReverbHiExp, 0x148); int32_t AXFXReverbHiExpGetMemSize(virt_ptr<AXFXReverbHiExp> reverb); BOOL AXFXReverbHiExpInit(virt_ptr<AXFXReverbHiExp> reverb); void AXFXReverbHiExpShutdown(virt_ptr<AXFXReverbHiExp> reverb); void AXFXReverbHiExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbHiExp> reverb); BOOL AXFXReverbHiExpSettings(virt_ptr<AXFXReverbHiExp> reverb); BOOL AXFXReverbHiExpSettingsUpdate(virt_ptr<AXFXReverbHiExp> reverb); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstd.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_reverbstd.h" #include "snduser2_axfx_reverbstdexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; /** * Converts the params from the AXFXReverbStd struct to the AXFXReverbStdExp params. */ static void translateReverbStdUserParamsToExp(virt_ptr<AXFXReverbStd> reverb) { // TODO: Implement translateReverbUserParamsToExp decaf_warn_stub(); } int32_t AXFXReverbStdGetMemSize(virt_ptr<AXFXReverbStd> reverb) { reverb->reverbExp.preDelaySeconds = reverb->userPreDelaySeconds; return AXFXReverbStdExpGetMemSize(virt_addrof(reverb->reverbExp)); } BOOL AXFXReverbStdInit(virt_ptr<AXFXReverbStd> reverb) { translateReverbStdUserParamsToExp(reverb); return AXFXReverbStdExpInit(virt_addrof(reverb->reverbExp)); } BOOL AXFXReverbStdShutdown(virt_ptr<AXFXReverbStd> reverb) { AXFXReverbStdExpShutdown(virt_addrof(reverb->reverbExp)); return TRUE; } void AXFXReverbStdCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbStd> reverb) { AXFXReverbStdExpCallback(buffers, virt_addrof(reverb->reverbExp)); } BOOL AXFXReverbStdSettings(virt_ptr<AXFXReverbStd> reverb) { translateReverbStdUserParamsToExp(reverb); return AXFXReverbStdExpSettings(virt_addrof(reverb->reverbExp)); } void Library::registerAxfxReverbStdSymbols() { RegisterFunctionExport(AXFXReverbStdGetMemSize); RegisterFunctionExport(AXFXReverbStdInit); RegisterFunctionExport(AXFXReverbStdShutdown); RegisterFunctionExport(AXFXReverbStdCallback); RegisterFunctionExport(AXFXReverbStdSettings); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstd.h ================================================ #pragma once #include "snduser2_axfx.h" #include "snduser2_axfx_reverbstdexp.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXReverbStd { be2_struct<AXFXReverbStdExp> reverbExp; UNKNOWN(0x10); //! Reverb Pre-Delay in seconds be2_val<float> userPreDelaySeconds; }; CHECK_SIZE(AXFXReverbStd, 0xFC); int32_t AXFXReverbStdGetMemSize(virt_ptr<AXFXReverbStd> reverb); BOOL AXFXReverbStdInit(virt_ptr<AXFXReverbStd> reverb); BOOL AXFXReverbStdShutdown(virt_ptr<AXFXReverbStd> reverb); void AXFXReverbStdCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbStd> reverb); BOOL AXFXReverbStdSettings(virt_ptr<AXFXReverbStd> reverb); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstdexp.cpp ================================================ #include "snduser2.h" #include "snduser2_axfx_reverbstdexp.h" #include "cafe/libraries/cafe_hle_stub.h" #include "cafe/libraries/sndcore2/sndcore2_config.h" namespace cafe::snduser2 { using namespace cafe::sndcore2; static constexpr int32_t ReverbStdMemSizeSamplesBase = 0x503; static constexpr int32_t ReverbStdMemSizeSamplesTable[] = { 0x6FD, 0x7CF, 0x1B1, 0x95, 0x95, 0x125, 0xFB, 0x67, 0x3B3, 0x551, 0x1B1, 0x89, 0x4FF, 0x5FB, 0x1FD, 0x95, 0x5FB, 0x737, 0x233, 0xB3, 0x71F, 0x935, 0x23B, 0x89, 0x71F, 0x935, 0x23B, 0xB3, 0xA3, 0x13D, 0x1DF, 0x281, 0x31D, 0x3C7, 0x463, }; int32_t AXFXReverbStdExpGetMemSize(virt_ptr<AXFXReverbStdExp> reverb) { auto delaySamples = reverb->preDelaySeconds * static_cast<float>(AXGetInputSamplesPerSec()); auto perChanSamples = delaySamples + ReverbStdMemSizeSamplesBase + ReverbStdMemSizeSamplesTable[24] + ReverbStdMemSizeSamplesTable[25] + ReverbStdMemSizeSamplesTable[26] + ReverbStdMemSizeSamplesTable[27]; auto numSamples = 3 * perChanSamples; return static_cast<int32_t>(sizeof(int32_t) * numSamples); } BOOL AXFXReverbStdExpInit(virt_ptr<AXFXReverbStdExp> reverb) { decaf_warn_stub(); return TRUE; } void AXFXReverbStdExpShutdown(virt_ptr<AXFXReverbStdExp> reverb) { decaf_warn_stub(); } void AXFXReverbStdExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbStdExp> reverb) { decaf_warn_stub(); } BOOL AXFXReverbStdExpSettings(virt_ptr<AXFXReverbStdExp> reverb) { decaf_warn_stub(); return TRUE; } BOOL AXFXReverbStdExpSettingsUpdate(virt_ptr<AXFXReverbStdExp> reverb) { decaf_warn_stub(); return TRUE; } void Library::registerAxfxReverbStdExpSymbols() { RegisterFunctionExport(AXFXReverbStdExpGetMemSize); RegisterFunctionExport(AXFXReverbStdExpInit); RegisterFunctionExport(AXFXReverbStdExpShutdown); RegisterFunctionExport(AXFXReverbStdExpCallback); RegisterFunctionExport(AXFXReverbStdExpSettings); RegisterFunctionExport(AXFXReverbStdExpSettingsUpdate); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstdexp.h ================================================ #pragma once #include "snduser2_axfx.h" #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct AXFXReverbStdExp { UNKNOWN(0xB0); be2_val<uint32_t> stateFlags; UNKNOWN(4); //! Reverb Pre-Delay in seconds be2_val<float> preDelaySeconds; UNKNOWN(0x2C); }; CHECK_OFFSET(AXFXReverbStdExp, 0xB0, stateFlags); CHECK_OFFSET(AXFXReverbStdExp, 0xB8, preDelaySeconds); CHECK_SIZE(AXFXReverbStdExp, 0xE8); int32_t AXFXReverbStdExpGetMemSize(virt_ptr<AXFXReverbStdExp> reverb); BOOL AXFXReverbStdExpInit(virt_ptr<AXFXReverbStdExp> reverb); void AXFXReverbStdExpShutdown(virt_ptr<AXFXReverbStdExp> reverb); void AXFXReverbStdExpCallback(virt_ptr<AXFXBuffers> buffers, virt_ptr<AXFXReverbStdExp> reverb); BOOL AXFXReverbStdExpSettings(virt_ptr<AXFXReverbStdExp> reverb); BOOL AXFXReverbStdExpSettingsUpdate(virt_ptr<AXFXReverbStdExp> reverb); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_enum.h ================================================ #ifndef CAFE_SNDUSER2_ENUM_H #define CAFE_SNDUSER2_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(snduser2) ENUM_BEG(AXFXSampleRate, uint32_t) ENUM_VALUE(Rate32khz, 1) ENUM_VALUE(Rate48khz, 2) ENUM_END(AXFXSampleRate) ENUM_BEG(AXFXReverbPreset, uint32_t) ENUM_VALUE(Unknown1, 1) ENUM_VALUE(Unknown2, 2) ENUM_VALUE(Unknown3, 3) ENUM_VALUE(Unknown4, 4) ENUM_VALUE(Unknown5, 5) ENUM_END(AXFXReverbPreset) ENUM_BEG(AXFXReverbType, uint32_t) ENUM_VALUE(Unknown1, 1) ENUM_VALUE(Unknown2, 2) ENUM_VALUE(Unknown3, 3) ENUM_VALUE(Unknown4, 4) ENUM_END(AXFXReverbType) ENUM_NAMESPACE_EXIT(snduser2) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef CAFE_SNDUSER2_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_mix.cpp ================================================ #include "snduser2.h" #include "snduser2_mix.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::snduser2 { void MIXUpdateSettings() { decaf_warn_stub(); } void Library::registerMixSymbols() { RegisterFunctionExport(MIXUpdateSettings); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_mix.h ================================================ #pragma once namespace cafe::snduser2 { void MIXUpdateSettings(); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_sp.cpp ================================================ #include "snduser2.h" #include "snduser2_sp.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::snduser2 { void SPInitSoundTable(virt_ptr<SPSoundTable> table, virt_ptr<int32_t> samples, virt_ptr<uint32_t> outSamplesSize) { decaf_warn_stub(); } virt_ptr<SPSoundEntry> SPGetSoundEntry(virt_ptr<SPSoundTable> table, uint32_t index) { if (index >= table->numEntries) { return nullptr; } return virt_addrof(table->entries) + index; } void Library::registerSpSymbols() { RegisterFunctionExport(SPInitSoundTable); RegisterFunctionExport(SPGetSoundEntry); //RegisterFunctionExport(SPPrepareSound); //RegisterFunctionExport(SPPrepareEnd); } } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/snduser2/snduser2_sp.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::snduser2 { struct SPSoundEntry { UNKNOWN(0x1C); }; CHECK_SIZE(SPSoundEntry, 0x1C); struct SPSoundTable { be2_val<uint32_t> numEntries; //! This is actually a dynamically sized array. be2_array<SPSoundEntry, 1> entries; }; CHECK_OFFSET(SPSoundTable, 0x00, numEntries); CHECK_OFFSET(SPSoundTable, 0x04, entries); void SPInitSoundTable(virt_ptr<SPSoundTable> table, virt_ptr<int32_t> samples, virt_ptr<uint32_t> outSamplesSize); virt_ptr<SPSoundEntry> SPGetSoundEntry(virt_ptr<SPSoundTable> table, uint32_t index); } // namespace cafe::snduser2 ================================================ FILE: src/libdecaf/src/cafe/libraries/swkbd/swkbd.cpp ================================================ #include "swkbd.h" namespace cafe::swkbd { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerKeyboardSymbols(); } } // namespace cafe::swkbd ================================================ FILE: src/libdecaf/src/cafe/libraries/swkbd/swkbd.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::swkbd { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::swkbd, "swkbd.rpl") { } protected: virtual void registerSymbols() override; private: void registerKeyboardSymbols(); }; } // namespace cafe::swkbd ================================================ FILE: src/libdecaf/src/cafe/libraries/swkbd/swkbd_enum.h ================================================ #ifndef SWKBD_ENUM_H #define SWKBD_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(swkbd) ENUM_BEG(ControllerType, int32_t) ENUM_VALUE(Unknown0, 0) ENUM_END(ControllerType) ENUM_BEG(LanguageType, int32_t) ENUM_VALUE(Japanese, 0) ENUM_VALUE(English, 1) ENUM_END(LanguageType) ENUM_BEG(RegionType, int32_t) ENUM_VALUE(Japan, 0) ENUM_VALUE(USA, 1) ENUM_VALUE(Europe, 2) ENUM_END(RegionType) ENUM_BEG(State, int32_t) ENUM_VALUE(Hidden, 0) ENUM_VALUE(FadeIn, 1) ENUM_VALUE(Visible, 2) ENUM_VALUE(FadeOut, 3) ENUM_VALUE(Max, 4) ENUM_END(State) ENUM_NAMESPACE_EXIT(swkbd) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef SWKBD_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/swkbd/swkbd_keyboard.cpp ================================================ #include "swkbd.h" #include "swkbd_keyboard.h" #include "common/strutils.h" #include "decaf_softwarekeyboard.h" #include <codecvt> #include <locale> #include <mutex> #include <string> namespace cafe::swkbd { struct StaticKeyboardData { be2_val<State> inputFormState; be2_val<State> keyboardState; be2_val<bool> okButtonPressed; be2_val<bool> cancelButtonPressed; be2_virt_ptr<uint8_t> workMemory; be2_virt_ptr<char16_t> textBuffer; be2_val<uint32_t> textBufferSize; }; static virt_ptr<StaticKeyboardData> sKeyboardData = nullptr; static std::mutex sMutex; static void setReceiverArg(const ReceiverArg &receiverArg) { if (receiverArg.textBuffer && receiverArg.textBufferSize) { sKeyboardData->textBuffer = receiverArg.textBuffer; sKeyboardData->textBufferSize = receiverArg.textBufferSize; } else { sKeyboardData->textBuffer = virt_cast<char16_t *>(sKeyboardData->workMemory); sKeyboardData->textBufferSize = 0xFFFFu; } } bool AppearInputForm(virt_ptr<const AppearArg> arg) { { std::unique_lock<std::mutex> lock { sMutex }; sKeyboardData->inputFormState = State::Visible; sKeyboardData->keyboardState = State::Visible; sKeyboardData->okButtonPressed = false; sKeyboardData->cancelButtonPressed = false; setReceiverArg(arg->keyboardArg.receiverArg); } if (auto driver = decaf::softwareKeyboardDriver()) { driver->onOpen({}); } return true; } bool AppearKeyboard(virt_ptr<const KeyboardArg> arg) { { std::unique_lock<std::mutex> lock { sMutex }; sKeyboardData->keyboardState = State::Visible; sKeyboardData->okButtonPressed = false; sKeyboardData->cancelButtonPressed = false; setReceiverArg(arg->receiverArg); } if (auto driver = decaf::softwareKeyboardDriver()) { driver->onOpen({}); } return true; } void CalcSubThreadFont() { } void CalcSubThreadPredict() { } void Calc(virt_ptr<const ControllerInfo> info) { } void ConfirmUnfixAll() { } void Create(virt_ptr<unsigned char> workMemory, RegionType regionType, unsigned int unk, virt_ptr<coreinit::FSClient> fsclient) { sKeyboardData->workMemory = workMemory; } void Destroy() { } bool DisappearInputForm() { sKeyboardData->keyboardState = State::Hidden; if (auto driver = decaf::softwareKeyboardDriver()) { driver->onClose(); } return true; } bool DisappearKeyboard() { sKeyboardData->keyboardState = State::Hidden; if (auto driver = decaf::softwareKeyboardDriver()) { driver->onClose(); } return true; } void DrawDRC() { } void DrawTV() { } void GetDrawStringInfo(virt_ptr<DrawStringInfo> info) { } virt_ptr<const char16_t> GetInputFormString() { return virt_cast<const char16_t *>(sKeyboardData->textBuffer); } void GetKeyboardCondition(virt_ptr<KeyboardCondition> condition) { } State GetStateInputForm() { return sKeyboardData->inputFormState; } State GetStateKeyboard() { return sKeyboardData->keyboardState; } void InactivateSelectCursor() { } bool InitLearnDic(virt_ptr<void> dictionary) { return true; } bool IsCoveredWithSubWindow() { return false; } bool IsDecideCancelButton(virt_ptr<bool> outIsSelected) { return sKeyboardData->cancelButtonPressed; } bool IsDecideOkButton(virt_ptr<bool> outIsSelected) { return sKeyboardData->okButtonPressed; } bool IsKeyboardTarget(virt_ptr<const IEventReceiver> receiver) { return false; } bool IsNeedCalcSubThreadFont() { return false; } bool IsNeedCalcSubThreadPredict() { return false; } bool IsSelectCursorActive() { return false; } void MuteAllSound(bool mute) { } void SetControllerRemo(ControllerType controller) { } void SetCursorPos(int32_t pos) { } void SetEnableOkButton(bool enable) { } void SetInputFormString(virt_ptr<const char16_t> str) { std::unique_lock<std::mutex> lock { sMutex }; string_copy<char16_t>(sKeyboardData->textBuffer.get(), static_cast<size_t>(sKeyboardData->textBufferSize), str.get(), static_cast<size_t>(sKeyboardData->textBufferSize - 1)); if (auto driver = decaf::softwareKeyboardDriver()) { driver->onInputStringChanged(sKeyboardData->textBuffer.getRawPointer()); } } void SetReceiver(virt_ptr<const ReceiverArg> arg) { } void SetSelectFrom(int32_t pos) { } void SetUserControllerEventObj(virt_ptr<IControllerEventObj> obj) { } void SetUserSoundObj(virt_ptr<ISoundObj> obj) { } void SetVersion(int32_t version) { } void Library::registerKeyboardSymbols() { RegisterFunctionExportName("SwkbdAppearInputForm__3RplFRCQ3_2nn5swkbd9AppearArg", AppearInputForm); RegisterFunctionExportName("SwkbdAppearKeyboard__3RplFRCQ3_2nn5swkbd11KeyboardArg", AppearKeyboard); RegisterFunctionExportName("SwkbdCalcSubThreadFont__3RplFv", CalcSubThreadFont); RegisterFunctionExportName("SwkbdCalcSubThreadPredict__3RplFv", CalcSubThreadPredict); RegisterFunctionExportName("SwkbdCalc__3RplFRCQ3_2nn5swkbd14ControllerInfo", Calc); RegisterFunctionExportName("SwkbdConfirmUnfixAll__3RplFv", ConfirmUnfixAll); RegisterFunctionExportName("SwkbdCreate__3RplFPUcQ3_2nn5swkbd10RegionTypeUiP8FSClient", Create); RegisterFunctionExportName("SwkbdDestroy__3RplFv", Destroy); RegisterFunctionExportName("SwkbdDisappearInputForm__3RplFv", DisappearInputForm); RegisterFunctionExportName("SwkbdDisappearKeyboard__3RplFv", DisappearKeyboard); RegisterFunctionExportName("SwkbdDrawDRC__3RplFv", DrawDRC); RegisterFunctionExportName("SwkbdDrawTV__3RplFv", DrawTV); RegisterFunctionExportName("SwkbdGetDrawStringInfo__3RplFPQ3_2nn5swkbd14DrawStringInfo", GetDrawStringInfo); RegisterFunctionExportName("SwkbdGetInputFormString__3RplFv", GetInputFormString); RegisterFunctionExportName("SwkbdGetKeyboardCondition__3RplFPQ3_2nn5swkbd17KeyboardCondition", GetKeyboardCondition); RegisterFunctionExportName("SwkbdGetStateInputForm__3RplFv", GetStateInputForm); RegisterFunctionExportName("SwkbdGetStateKeyboard__3RplFv", GetStateKeyboard); RegisterFunctionExportName("SwkbdInactivateSelectCursor__3RplFv", InactivateSelectCursor); RegisterFunctionExportName("SwkbdInitLearnDic__3RplFPv", InitLearnDic); RegisterFunctionExportName("SwkbdIsCoveredWithSubWindow__3RplFv", IsCoveredWithSubWindow); RegisterFunctionExportName("SwkbdIsDecideCancelButton__3RplFPb", IsDecideCancelButton); RegisterFunctionExportName("SwkbdIsDecideOkButton__3RplFPb", IsDecideOkButton); RegisterFunctionExportName("SwkbdIsKeyboardTarget__3RplFPQ3_2nn5swkbd14IEventReceiver", IsKeyboardTarget); RegisterFunctionExportName("SwkbdIsNeedCalcSubThreadFont__3RplFv", IsNeedCalcSubThreadFont); RegisterFunctionExportName("SwkbdIsNeedCalcSubThreadPredict__3RplFv", IsNeedCalcSubThreadPredict); RegisterFunctionExportName("SwkbdIsSelectCursorActive__3RplFv", IsSelectCursorActive); RegisterFunctionExportName("SwkbdMuteAllSound__3RplFb", MuteAllSound); RegisterFunctionExportName("SwkbdSetControllerRemo__3RplFQ3_2nn5swkbd14ControllerType", SetControllerRemo); RegisterFunctionExportName("SwkbdSetCursorPos__3RplFi", SetCursorPos); RegisterFunctionExportName("SwkbdSetEnableOkButton__3RplFb", SetEnableOkButton); RegisterFunctionExportName("SwkbdSetInputFormString__3RplFPCw", SetInputFormString); RegisterFunctionExportName("SwkbdSetReceiver__3RplFRCQ3_2nn5swkbd11ReceiverArg", SetReceiver); RegisterFunctionExportName("SwkbdSetSelectFrom__3RplFi", SetSelectFrom); RegisterFunctionExportName("SwkbdSetUserControllerEventObj__3RplFPQ3_2nn5swkbd19IControllerEventObj", SetUserControllerEventObj); RegisterFunctionExportName("SwkbdSetUserSoundObj__3RplFPQ3_2nn5swkbd9ISoundObj", SetUserSoundObj); RegisterFunctionExportName("SwkbdSetVersion__3RplFi", SetVersion); RegisterDataInternal(sKeyboardData); } namespace internal { void inputAccept() { std::unique_lock<std::mutex> lock { sMutex }; if (sKeyboardData->keyboardState != State::Visible) { return; } sKeyboardData->okButtonPressed = true; sKeyboardData->cancelButtonPressed = false; } void inputReject() { std::unique_lock<std::mutex> lock { sMutex }; if (sKeyboardData->keyboardState != State::Visible) { return; } sKeyboardData->okButtonPressed = false; sKeyboardData->cancelButtonPressed = true; } void setInputString(std::u16string_view text) { std::unique_lock<std::mutex> lock { sMutex }; if (sKeyboardData->keyboardState != State::Visible) { return; } string_copy<char16_t>(sKeyboardData->textBuffer.get(), static_cast<size_t>(sKeyboardData->textBufferSize), text.data(), text.size()); } } // namespace internal } // namespace cafe::swkbd ================================================ FILE: src/libdecaf/src/cafe/libraries/swkbd/swkbd_keyboard.h ================================================ #pragma once #include "decaf_input.h" #include "swkbd_enum.h" #include <common/platform.h> #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::coreinit { struct FSClient; } // namespace cafe::coreinit namespace cafe::kpad { struct KPADStatus; } // namespace cafe::kpad namespace cafe::vpad { struct VPADStatus; } // namespace cafe::vpad namespace cafe::swkbd { struct ConfigArg { be2_val<LanguageType> languageType; be2_val<uint32_t> unk_0x04; be2_val<uint32_t> unk_0x08; be2_val<uint32_t> unk_0x0C; be2_val<uint32_t> unk_0x10; be2_val<int32_t> unk_0x14; UNKNOWN(0x9C - 0x18); be2_val<uint32_t> unk_0x9C; UNKNOWN(4); be2_val<int32_t> unk_0xA4; }; CHECK_OFFSET(ConfigArg, 0x00, languageType); CHECK_OFFSET(ConfigArg, 0x04, unk_0x04); CHECK_OFFSET(ConfigArg, 0x08, unk_0x08); CHECK_OFFSET(ConfigArg, 0x0C, unk_0x0C); CHECK_OFFSET(ConfigArg, 0x10, unk_0x10); CHECK_OFFSET(ConfigArg, 0x14, unk_0x14); CHECK_OFFSET(ConfigArg, 0x9C, unk_0x9C); CHECK_OFFSET(ConfigArg, 0xA4, unk_0xA4); CHECK_SIZE(ConfigArg, 0xA8); struct ReceiverArg { be2_val<uint32_t> unk_0x00; be2_virt_ptr<char16_t> textBuffer; be2_val<uint32_t> textBufferSize; be2_val<int32_t> unk_0x0C; be2_val<uint32_t> unk_0x10; be2_val<int32_t> unk_0x14; }; CHECK_OFFSET(ReceiverArg, 0x00, unk_0x00); CHECK_OFFSET(ReceiverArg, 0x04, textBuffer); CHECK_OFFSET(ReceiverArg, 0x08, textBufferSize); CHECK_OFFSET(ReceiverArg, 0x0C, unk_0x0C); CHECK_OFFSET(ReceiverArg, 0x10, unk_0x10); CHECK_OFFSET(ReceiverArg, 0x14, unk_0x14); CHECK_SIZE(ReceiverArg, 0x18); struct KeyboardArg { ConfigArg configArg; ReceiverArg receiverArg; }; CHECK_SIZE(KeyboardArg, 0xC0); struct InputFormArg { be2_val<uint32_t> unk_0x00; be2_val<int32_t> unk_0x04; be2_val<uint32_t> unk_0x08; be2_val<uint32_t> unk_0x0C; be2_val<int32_t> maxTextLength; be2_val<uint32_t> unk_0x14; be2_val<uint32_t> unk_0x18; be2_val<bool> unk_0x1C; be2_val<bool> unk_0x1D; be2_val<bool> unk_0x1E; PADDING(1); }; CHECK_OFFSET(InputFormArg, 0x00, unk_0x00); CHECK_OFFSET(InputFormArg, 0x04, unk_0x04); CHECK_OFFSET(InputFormArg, 0x08, unk_0x08); CHECK_OFFSET(InputFormArg, 0x0C, unk_0x0C); CHECK_OFFSET(InputFormArg, 0x10, maxTextLength); CHECK_OFFSET(InputFormArg, 0x14, unk_0x14); CHECK_OFFSET(InputFormArg, 0x18, unk_0x18); CHECK_OFFSET(InputFormArg, 0x1C, unk_0x1C); CHECK_OFFSET(InputFormArg, 0x1D, unk_0x1D); CHECK_OFFSET(InputFormArg, 0x1E, unk_0x1E); CHECK_SIZE(InputFormArg, 0x20); struct AppearArg { be2_struct<KeyboardArg> keyboardArg; be2_struct<InputFormArg> inputFormArg; }; CHECK_OFFSET(AppearArg, 0x00, keyboardArg); CHECK_OFFSET(AppearArg, 0xC0, inputFormArg); CHECK_SIZE(AppearArg, 0xE0); struct CreateArg { be2_virt_ptr<void> workMemory; be2_val<RegionType> regionType; be2_val<uint32_t> unk_0x08; be2_virt_ptr<coreinit::FSClient> fsClient; }; CHECK_OFFSET(CreateArg, 0x00, workMemory); CHECK_OFFSET(CreateArg, 0x04, regionType); CHECK_OFFSET(CreateArg, 0x08, unk_0x08); CHECK_OFFSET(CreateArg, 0x0C, fsClient); CHECK_SIZE(CreateArg, 0x10); struct ControllerInfo { be2_virt_ptr<vpad::VPADStatus> vpad; be2_array<virt_ptr<kpad::KPADStatus>, 4> kpad; }; CHECK_OFFSET(ControllerInfo, 0x00, vpad); CHECK_OFFSET(ControllerInfo, 0x04, kpad); CHECK_SIZE(ControllerInfo, 0x14); struct DrawStringInfo { UNKNOWN(0x1C); }; CHECK_SIZE(DrawStringInfo, 0x1C); struct KeyboardCondition { be2_val<uint32_t> unk_0x00; be2_val<uint32_t> unk_0x04; }; CHECK_OFFSET(KeyboardCondition, 0x00, unk_0x00); CHECK_OFFSET(KeyboardCondition, 0x04, unk_0x04); CHECK_SIZE(KeyboardCondition, 0x8); struct IEventReceiver; struct IControllerEventObj; struct ISoundObj; bool AppearInputForm(virt_ptr<const AppearArg> arg); bool AppearKeyboard(virt_ptr<const KeyboardArg> arg); void CalcSubThreadFont(); void CalcSubThreadPredict(); void Calc(virt_ptr<const ControllerInfo> info); void ConfirmUnfixAll(); void Create(virt_ptr<unsigned char> buffer, RegionType regionType, unsigned int unk, virt_ptr<coreinit::FSClient> fsclient); void Destroy(); bool DisappearInputForm(); bool DisappearKeyboard(); void DrawDRC(); void DrawTV(); void GetDrawStringInfo(virt_ptr<DrawStringInfo> info); virt_ptr<const char16_t> GetInputFormString(); void GetKeyboardCondition(virt_ptr<KeyboardCondition> condition); State GetStateInputForm(); State GetStateKeyboard(); void InactivateSelectCursor(); bool InitLearnDic(virt_ptr<void> dictionary); bool IsCoveredWithSubWindow(); bool IsDecideCancelButton(virt_ptr<bool> outIsSelected); bool IsDecideOkButton(virt_ptr<bool> outIsSelected); bool IsKeyboardTarget(virt_ptr<const IEventReceiver> receiver); bool IsNeedCalcSubThreadFont(); bool IsNeedCalcSubThreadPredict(); bool IsSelectCursorActive(); void MuteAllSound(bool mute); void SetControllerRemo(ControllerType type); void SetCursorPos(int32_t pos); void SetEnableOkButton(bool enable); void SetInputFormString(virt_ptr<const char16_t> str); void SetReceiver(virt_ptr<const ReceiverArg> arg); void SetSelectFrom(int32_t pos); void SetUserControllerEventObj(virt_ptr<IControllerEventObj> obj); void SetUserSoundObj(virt_ptr<ISoundObj> obj); void SetVersion(int32_t version); namespace internal { void inputAccept(); void inputReject(); void setInputString(std::u16string_view text); } // namespace internal } // namespace cafe::swkbd ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp.cpp ================================================ #include "sysapp.h" namespace cafe::sysapp { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerCallerArgsSymbols(); registerTitleSymbols(); } } // namespace cafe::sysapp ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::sysapp { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::sysapp, "sysapp.rpl") { } protected: virtual void registerSymbols() override; private: void registerCallerArgsSymbols(); void registerTitleSymbols(); }; } // namespace cafe::zlib125 ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp_callerargs.cpp ================================================ #include "sysapp.h" #include "sysapp_callerargs.h" #include <cafe/libraries/cafe_hle_stub.h> namespace cafe::sysapp { uint32_t SYSGetCallerPFID() { return SYSGetCallerUPID(); } uint64_t SYSGetCallerTitleId() { decaf_warn_stub(); return 0ull; } uint32_t SYSGetCallerUPID() { decaf_warn_stub(); return 0u; } int32_t SYSGetLauncherArgs(virt_ptr<void> args) { decaf_warn_stub(); std::memset(args.get(), 0, 0xC); return 1; } int32_t SYSGetStandardResult(virt_ptr<uint32_t> arg1, uint32_t arg2, uint32_t arg3) { decaf_warn_stub(); std::memset(arg1.get(), 0, 0x4); return 1; } void Library::registerCallerArgsSymbols() { RegisterFunctionExport(SYSGetCallerPFID); RegisterFunctionExport(SYSGetCallerTitleId); RegisterFunctionExport(SYSGetCallerUPID); RegisterFunctionExport(SYSGetStandardResult); RegisterFunctionExportName("_SYSGetLauncherArgs", SYSGetLauncherArgs); } } // namespace cafe::sysapp ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp_callerargs.h ================================================ #pragma once #include <cstdint> namespace cafe::sysapp { uint32_t SYSGetCallerPFID(); uint64_t SYSGetCallerTitleId(); uint32_t SYSGetCallerUPID(); int32_t SYSGetLauncherArgs(virt_ptr<void> args); int32_t SYSGetStandardResult(virt_ptr<uint32_t> arg1, uint32_t arg2, uint32_t arg3); } // namespace cafe::sysapp ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp_enum.h ================================================ #ifndef SYSAPP_ENUM_H #define SYSAPP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(sysapp) ENUM_BEG(SystemAppId, int32_t) ENUM_VALUE(Updater, 0) ENUM_VALUE(SystemSettings, 1) ENUM_VALUE(ParentalControls, 2) ENUM_VALUE(UserSettings, 3) ENUM_VALUE(MiiMaker, 4) ENUM_VALUE(AccountSettings, 5) ENUM_VALUE(DailyLog, 6) ENUM_VALUE(Notifications, 7) ENUM_VALUE(HealthAndSafety, 8) ENUM_VALUE(ElectronicManual, 9) ENUM_VALUE(WiiUChat, 10) ENUM_VALUE(SoftwareDataTransfer, 11) ENUM_VALUE(Max, 12) ENUM_END(SystemAppId) ENUM_NAMESPACE_EXIT(sysapp) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef SYSAPP_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp_title.cpp ================================================ #include "sysapp.h" #include "sysapp_title.h" #include "cafe/libraries/coreinit/coreinit_mcp.h" #include "cafe/libraries/coreinit/coreinit_enum_string.h" #include "cafe/cafe_stackobject.h" #include <common/decaf_assert.h> #include <fmt/core.h> namespace cafe::sysapp { using namespace cafe::coreinit; static const uint64_t sSysAppTitleId[][3] = { { // Updater 0x0005001010040000ull, 0x0005001010040100ull, 0x0005001010040200ull, }, { // System Settings 0x0005001010047000ull, 0x0005001010047100ull, 0x0005001010047200ull, }, { // Parental Controls 0x0005001010048000ull, 0x0005001010048100ull, 0x0005001010048200ull, }, { // User Settings 0x0005001010049000ull, 0x0005001010049100ull, 0x0005001010049200ull, }, { // Mii Maker 0x000500101004A000ull, 0x000500101004A100ull, 0x000500101004A200ull, }, { // Account Settings 0x000500101004B000ull, 0x000500101004B100ull, 0x000500101004B200ull, }, { // Daily log 0x000500101004C000ull, 0x000500101004C100ull, 0x000500101004C200ull, }, { // Notifications 0x000500101004D000ull, 0x000500101004D100ull, 0x000500101004D200ull, }, { // Health and Safety Information 0x000500101004E000ull, 0x000500101004E100ull, 0x000500101004E200ull, }, { // Electronic Manual 0x0005001B10059000ull, 0x0005001B10059100ull, 0x0005001B10059200ull, }, { // Wii U Chat 0x000500101005A000ull, 0x000500101005A100ull, 0x000500101005A200ull, }, { // "Software/Data Transfer" 0x0005001010062000ull, 0x0005001010062100ull, 0x0005001010062200ull, }, }; /** * _SYSGetSystemApplicationTitleId */ uint64_t SYSGetSystemApplicationTitleId(SystemAppId id) { auto settings = StackObject<MCPSysProdSettings> { }; decaf_check(id < SystemAppId::Max); auto mcp = MCP_Open(); MCP_GetSysProdSettings(mcp, settings); MCP_Close(mcp); return SYSGetSystemApplicationTitleIdByProdArea(id, settings->product_area); } /** * _SYSGetSystemApplicationTitleIdByProdArea */ uint64_t SYSGetSystemApplicationTitleIdByProdArea(SystemAppId id, MCPRegion prodArea) { auto regionIdx = 1u; if (prodArea == coreinit::MCPRegion::Japan) { regionIdx = 0u; } else if (prodArea == coreinit::MCPRegion::Europe || prodArea == coreinit::MCPRegion::Unknown8) { regionIdx = 2u; } return sSysAppTitleId[id][regionIdx]; } void Library::registerTitleSymbols() { RegisterFunctionExportName("_SYSGetSystemApplicationTitleId", SYSGetSystemApplicationTitleId); RegisterFunctionExportName("_SYSGetSystemApplicationTitleIdByProdArea", SYSGetSystemApplicationTitleIdByProdArea); } } // namespace cafe::sysapp ================================================ FILE: src/libdecaf/src/cafe/libraries/sysapp/sysapp_title.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_mcp.h" #include "sysapp_enum.h" #include <cstdint> namespace cafe::sysapp { uint64_t SYSGetSystemApplicationTitleId(SystemAppId id); uint64_t SYSGetSystemApplicationTitleIdByProdArea(SystemAppId id, coreinit::MCPRegion region); } // namespace cafe::sysapp ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl.cpp ================================================ #include "tcl.h" #include "tcl_aperture.h" #include "tcl_driver.h" #include "tcl_interrupthandler.h" #include "tcl_ring.h" namespace cafe::tcl { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { internal::initialiseTclDriver(); internal::initialiseApertures(); internal::initialiseInterruptHandler(); internal::initialiseRing(); return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerApertureSymbols(); registerDriverSymbols(); registerInterruptHandlerSymbols(); registerRegisterSymbols(); registerRingSymbols(); } } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::tcl { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::tcl, "tcl.rpl") { } protected: virtual void registerSymbols() override; private: void registerApertureSymbols(); void registerDriverSymbols(); void registerInterruptHandlerSymbols(); void registerRegisterSymbols(); void registerRingSymbols(); }; } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_aperture.cpp ================================================ #include "tcl.h" #include "tcl_aperture.h" #include "tcl_driver.h" #include "cafe/cafe_tinyheap.h" #include "cafe/libraries/coreinit/coreinit_memory.h" namespace cafe::tcl { using namespace cafe::coreinit; struct AllocatedAperture { be2_val<phys_addr> surface; be2_virt_ptr<void> apertureMemory; }; struct StaticApertureData { be2_val<virt_addr> baseVirtualAddress; be2_val<uint32_t> allocatedMask; be2_array<AllocatedAperture, 32> apertures; be2_array<uint8_t, TinyHeapHeaderSize + 32 * TinyHeapBlockSize> trackingHeap; }; static virt_ptr<StaticApertureData> sApertureData = nullptr; TCLStatus TCLAllocTilingAperture(phys_addr addr, uint32_t pitch, uint32_t height, uint32_t bytesPerPixel, uint32_t tileMode, uint32_t endian, virt_ptr<TCLApertureHandle> outHandle, virt_ptr<virt_addr> outAddress) { if (!internal::tclDriverInitialised()) { return TCLStatus::NotInitialised; } auto handle = 32u; for (auto i = 0u; i < 32; ++i) { if (sApertureData->allocatedMask & (1 << i)) { continue; } handle = i; break; } if (handle >= 32) { return TCLStatus::OutOfMemory; } auto ptr = virt_ptr<void> { nullptr }; auto size = pitch * height * bytesPerPixel; if (TinyHeap_Alloc(virt_cast<TinyHeap *>(virt_addrof(sApertureData->trackingHeap)), size, 256, &ptr) != TinyHeapError::OK) { return TCLStatus::OutOfMemory; } auto address = sApertureData->baseVirtualAddress + static_cast<uint32_t>(virt_cast<virt_addr>(ptr)); sApertureData->allocatedMask |= 1 << handle; sApertureData->apertures[handle].surface = addr; sApertureData->apertures[handle].apertureMemory = ptr; if (outHandle) { *outHandle = handle; } if (outAddress) { *outAddress = address; } return TCLStatus::OK; } TCLStatus TCLFreeTilingAperture(TCLApertureHandle handle) { if (!internal::tclDriverInitialised()) { return TCLStatus::NotInitialised; } if (handle >= 32) { return TCLStatus::InvalidArg; } if (!(sApertureData->allocatedMask & (1 << handle))) { return TCLStatus::InvalidArg; } TinyHeap_Free(virt_cast<TinyHeap *>(virt_addrof(sApertureData->trackingHeap)), sApertureData->apertures[handle].apertureMemory); sApertureData->apertures[handle].apertureMemory = nullptr; sApertureData->apertures[handle].surface = phys_addr { 0 }; sApertureData->allocatedMask &= ~(1 << handle); return TCLStatus::OK; } namespace internal { void initialiseApertures() { // Create a heap TinyHeap_Setup(virt_cast<TinyHeap *>(virt_addrof(sApertureData->trackingHeap)), sApertureData->trackingHeap.size(), nullptr, 0x02000000); sApertureData->baseVirtualAddress = OSPhysicalToEffectiveUncached(phys_addr { 0xD0000000 }); } } // namespace internal void Library::registerApertureSymbols() { RegisterFunctionExport(TCLAllocTilingAperture); RegisterFunctionExport(TCLFreeTilingAperture); RegisterDataInternal(sApertureData); } } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_aperture.h ================================================ #pragma once #include "tcl_enum.h" #include <libcpu/be2_struct.h> namespace cafe::tcl { using TCLApertureHandle = uint32_t; TCLStatus TCLAllocTilingAperture(phys_addr addr, uint32_t pitch, uint32_t height, uint32_t bytesPerPixel, uint32_t tileMode, uint32_t endian, virt_ptr<TCLApertureHandle> outHandle, virt_ptr<virt_addr> outAddress); TCLStatus TCLFreeTilingAperture(TCLApertureHandle handle); namespace internal { void initialiseApertures(); } // namespace internal } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_driver.cpp ================================================ #include "tcl.h" #include "tcl_driver.h" #include "cafe/libraries/coreinit/coreinit_driver.h" #include <libcpu/be2_struct.h> namespace cafe::tcl { struct StaticDriverData { be2_val<BOOL> isInitialised; be2_val<BOOL> hangWait; }; static virt_ptr<StaticDriverData> sDriverData = nullptr; TCLStatus TCLGetInfo(virt_ptr<TCLInfo> info) { if (!internal::tclDriverInitialised()) { return TCLStatus::NotInitialised; } info->asicType = TCLAsicType::Unknown5; info->chipRevision = TCLChipRevision::Unknown78; info->cpMicrocodeVersion = TCLCpMicrocodeVersion::Unknown16; info->quadPipes = 4u; info->parameterCacheWidth = 16u; info->rb = 2u; info->addrLibHandle = virt_cast<void *>(virt_addr { 0xF00DBAAD }); info->sclk = 549999755u; return TCLStatus::OK; } void TCLSetHangWait(BOOL hangWait) { sDriverData->hangWait = hangWait; } namespace internal { void initialiseTclDriver() { // TODO: OSDriver_Register sDriverData->isInitialised = true; } bool tclDriverInitialised() { return sDriverData->isInitialised ? true : false; } } // namespace internal void Library::registerDriverSymbols() { RegisterFunctionExport(TCLGetInfo); RegisterFunctionExport(TCLSetHangWait); RegisterDataInternal(sDriverData); } } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_driver.h ================================================ #pragma once #include "tcl_enum.h" namespace cafe::tcl { struct TCLInfo { be2_val<TCLAsicType> asicType; be2_val<TCLChipRevision> chipRevision; be2_val<TCLCpMicrocodeVersion> cpMicrocodeVersion; be2_val<uint32_t> quadPipes; be2_val<uint32_t> parameterCacheWidth; be2_val<uint32_t> rb; be2_virt_ptr<void> addrLibHandle; be2_val<uint32_t> sclk; }; TCLStatus TCLGetInfo(virt_ptr<TCLInfo> info); void TCLSetHangWait(BOOL hangWait); namespace internal { void initialiseTclDriver(); bool tclDriverInitialised(); } // namespace internal } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_enum.h ================================================ #ifndef TCL_ENUM_H #define TCL_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(tcl) ENUM_BEG(TCLAsicType, uint32_t) ENUM_VALUE(Unknown5, 5) ENUM_END(TCLAsicType) ENUM_BEG(TCLChipRevision, uint32_t) ENUM_VALUE(Unknown78, 78) ENUM_END(TCLChipRevision) ENUM_BEG(TCLCpMicrocodeVersion, uint32_t) ENUM_VALUE(Unknown16, 16) ENUM_END(TCLCpMicrocodeVersion) ENUM_BEG(TCLRegisterID, uint32_t) ENUM_VALUE(Max, 0x10000) ENUM_END(TCLRegisterID) ENUM_BEG(TCLStatus, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(InvalidArg, 3) ENUM_VALUE(NotInitialised, 5) ENUM_VALUE(OutOfMemory, 6) ENUM_VALUE(Timeout, 22) ENUM_END(TCLStatus) FLAGS_BEG(TCLSubmitFlags, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(NoWriteConfirmTimestamp, 1 << 21) FLAGS_VALUE(NoCacheFlush, 1 << 27) FLAGS_VALUE(CacheFlushInvalidate, 1 << 28) FLAGS_VALUE(UpdateTimestamp, 1 << 29) FLAGS_END(TCLSubmitFlags) ENUM_BEG(TCLTimestampID, int32_t) ENUM_VALUE(CPSubmitted, 0) ENUM_VALUE(CPRetired, 1) ENUM_VALUE(DMAERetired, 2) ENUM_END(TCLTimestampID) ENUM_NAMESPACE_EXIT(tcl) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef TCL_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_interrupthandler.cpp ================================================ #include "tcl.h" #include "tcl_enum.h" #include "tcl_interrupthandler.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_interrupts.h" #include <libcpu/be2_atomic.h> #include <libcpu/be2_struct.h> #include <libgpu/gpu_ih.h> namespace cafe::tcl { using namespace cafe::coreinit; constexpr auto MaxNumInterruptTypes = 256u; constexpr auto MaxNumHandlersPerInterrupt = 4u; struct RegisteredInterruptHandler { be2_val<TCLInterruptHandlerFn> callback; be2_virt_ptr<void> userData; }; struct StaticInterruptHandlerData { be2_array<be2_array<RegisteredInterruptHandler, MaxNumHandlersPerInterrupt>, MaxNumInterruptTypes> handlers; be2_atomic<uint32_t> interruptCount; be2_atomic<uint32_t> interruptEntriesRead; std::array<be2_atomic<uint32_t>, 0x100> interruptTypeCount; }; static virt_ptr<StaticInterruptHandlerData> sInterruptHandlerData = nullptr; static OSUserInterruptHandler sInterruptHandler = nullptr; TCLStatus TCLIHEnableInterrupt(TCLInterruptType type, BOOL enable) { auto cp_int_cntl = latte::CP_INT_CNTL::get(0); switch (type) { case TCLInterruptType::UNKNOWN_192: cp_int_cntl = cp_int_cntl.UNK17_INT_ENABLE(true); break; case TCLInterruptType::CP_RB: cp_int_cntl = cp_int_cntl.RB_INT_ENABLE(true); break; case TCLInterruptType::CP_IB1: cp_int_cntl = cp_int_cntl.IB1_INT_ENABLE(true); break; case TCLInterruptType::CP_IB2: cp_int_cntl = cp_int_cntl.IB2_INT_ENABLE(true); break; case TCLInterruptType::CP_RESERVED_BITS: cp_int_cntl = cp_int_cntl.RESERVED_BITS_EXCEPTION(true); break; case TCLInterruptType::CP_EOP_EVENT: cp_int_cntl = cp_int_cntl.TIME_STAMP_INT_ENABLE(true); break; case TCLInterruptType::SCRATCH: cp_int_cntl = cp_int_cntl.SCRATCH_INT_ENABLE(true); break; case TCLInterruptType::CP_BAD_OPCODE: cp_int_cntl = cp_int_cntl.BAD_OPCODE_EXCEPTION(true); break; case TCLInterruptType::CP_CTX_EMPTY: cp_int_cntl = cp_int_cntl.CNTX_EMPTY_INT_ENABLE(true); break; case TCLInterruptType::CP_CTX_BUSY: cp_int_cntl = cp_int_cntl.CNTX_BUSY_INT_ENABLE(true); break; case TCLInterruptType::DMA_CTX_EMPTY: // dma_int_cntl.CTXEMPTY_INT_ENABLE(true); case TCLInterruptType::DMA_TRAP_EVENT: // dma_int_cntl.TRAP_ENABLE(true); case TCLInterruptType::DMA_SEM_INCOMPLETE: // dma_int_cntl.SEM_INCOMPLETE_INT_ENABLE(true); case TCLInterruptType::DMA_SEM_WAIT: // dma_int_cntl.SEM_WAIT_INT_ENABLE(true); default: return TCLStatus::InvalidArg; } if (enable) { gpu::ih::enable(cp_int_cntl); } else { gpu::ih::disable(cp_int_cntl); } return TCLStatus::OK; } TCLStatus TCLIHRegister(TCLInterruptType type, TCLInterruptHandlerFn callback, virt_ptr<void> userData) { if (type >= sInterruptHandlerData->handlers.size()) { return TCLStatus::InvalidArg; } for (auto &handler : sInterruptHandlerData->handlers[type]) { if (!handler.callback) { handler.callback = callback; handler.userData = userData; return TCLStatus::OK; } } return TCLStatus::OutOfMemory; } TCLStatus TCLIHUnregister(TCLInterruptType type, TCLInterruptHandlerFn callback, virt_ptr<void> userData) { if (type >= sInterruptHandlerData->handlers.size()) { return TCLStatus::InvalidArg; } for (auto &handler : sInterruptHandlerData->handlers[type]) { if (handler.callback == callback && handler.userData == userData) { handler.callback = nullptr; handler.userData = nullptr; return TCLStatus::OK; } } return TCLStatus::InvalidArg; } TCLStatus TCLGetInterruptCount(TCLInterruptType type, BOOL resetCount, virt_ptr<uint32_t> count) { if (type == 0x100) { if (resetCount) { *count = sInterruptHandlerData->interruptCount.fetch_and(0); } else { *count = sInterruptHandlerData->interruptCount.load(); } return TCLStatus::OK; } if (type == 0x101) { if (resetCount) { *count = sInterruptHandlerData->interruptEntriesRead.fetch_and(0); } else { *count = sInterruptHandlerData->interruptEntriesRead.load(); } return TCLStatus::OK; } if (type < sInterruptHandlerData->handlers.size()) { if (resetCount) { *count = sInterruptHandlerData->interruptTypeCount[type].fetch_and(0); } else { *count = sInterruptHandlerData->interruptTypeCount[type].load(); } return TCLStatus::OK; } return TCLStatus::InvalidArg; } namespace internal { static void gpuInterruptHandler(OSInterruptType type, virt_ptr<OSContext> interruptedContext) { auto interruptEntry = StackObject<TCLInterruptEntry> { }; auto entries = gpu::ih::read(); sInterruptHandlerData->interruptCount.fetch_add(1); for (auto &entry : entries) { interruptEntry->interruptSourceID = static_cast<TCLInterruptType>(entry.word0); interruptEntry->reservedWord1 = entry.word1; interruptEntry->interruptSourceData = entry.word2; interruptEntry->reservedWord3 = entry.word3; sInterruptHandlerData->interruptEntriesRead.fetch_add(1); // Dispatch interrupt entry to registered handlers for (auto &handler : sInterruptHandlerData->handlers[entry.word0]) { if (handler.callback) { cafe::invoke(cpu::this_core::state(), handler.callback, interruptEntry, handler.userData); } } } } void initialiseInterruptHandler() { OSSetInterruptHandler(OSInterruptType::Gpu7, sInterruptHandler); gpu::ih::setInterruptCallback([]() { cpu::interrupt(1, cpu::GPU7_INTERRUPT); }); } } // namespace internal void Library::registerInterruptHandlerSymbols() { RegisterFunctionExport(TCLIHEnableInterrupt); RegisterFunctionExport(TCLIHRegister); RegisterFunctionExport(TCLIHUnregister); RegisterFunctionExport(TCLGetInterruptCount); RegisterDataInternal(sInterruptHandlerData); RegisterFunctionInternal(internal::gpuInterruptHandler, sInterruptHandler); } } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_interrupthandler.h ================================================ #pragma once #include "tcl_enum.h" #include <libcpu/be2_struct.h> #include <libgpu/latte/latte_enum_cp.h> namespace cafe::tcl { using TCLInterruptType = latte::CP_INT_SRC_ID; struct TCLInterruptEntry { be2_val<TCLInterruptType> interruptSourceID; be2_val<uint32_t> reservedWord1; be2_val<uint32_t> interruptSourceData; be2_val<uint32_t> reservedWord3; }; CHECK_OFFSET(TCLInterruptEntry, 0x00, interruptSourceID); CHECK_OFFSET(TCLInterruptEntry, 0x04, reservedWord1); CHECK_OFFSET(TCLInterruptEntry, 0x08, interruptSourceData); CHECK_OFFSET(TCLInterruptEntry, 0x0C, reservedWord3); CHECK_SIZE(TCLInterruptEntry, 0x10); using TCLInterruptHandlerFn = virt_func_ptr< void (virt_ptr<TCLInterruptEntry> interruptEntry, virt_ptr<void> userData) >; TCLStatus TCLIHEnableInterrupt(TCLInterruptType type, BOOL enable); TCLStatus TCLIHRegister(TCLInterruptType type, TCLInterruptHandlerFn callback, virt_ptr<void> userData); TCLStatus TCLIHUnregister(TCLInterruptType type, TCLInterruptHandlerFn callback, virt_ptr<void> userData); TCLStatus TCLGetInterruptCount(TCLInterruptType type, BOOL resetCount, virt_ptr<uint32_t> count); namespace internal { void initialiseInterruptHandler(); } // namespace internal } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_register.cpp ================================================ #include "tcl.h" #include "tcl_register.h" namespace cafe::tcl { TCLStatus TCLReadRegister(TCLRegisterID id, virt_ptr<uint32_t> outValue) { if (id >= TCLRegisterID::Max) { return TCLStatus::InvalidArg; } return TCLStatus::NotInitialised; } TCLStatus TCLWriteRegister(TCLRegisterID id, uint32_t value) { if (id >= TCLRegisterID::Max) { return TCLStatus::InvalidArg; } return TCLStatus::NotInitialised; } void Library::registerRegisterSymbols() { RegisterFunctionExport(TCLReadRegister); RegisterFunctionExport(TCLWriteRegister); } } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_register.h ================================================ #pragma once #include "tcl_enum.h" #include <libcpu/be2_struct.h> namespace cafe::tcl { TCLStatus TCLReadRegister(TCLRegisterID id, virt_ptr<uint32_t> outValue); TCLStatus TCLWriteRegister(TCLRegisterID id, uint32_t value); } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_ring.cpp ================================================ #include "tcl.h" #include "tcl_interrupthandler.h" #include "tcl_ring.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/coreinit/coreinit_event.h" #include "cafe/libraries/coreinit/coreinit_memory.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include <libcpu/be2_atomic.h> #include <libgpu/gpu_ringbuffer.h> #include <libgpu/latte/latte_pm4.h> #include <libgpu/latte/latte_pm4_commands.h> #include <libgpu/latte/latte_pm4_sizer.h> #include <libgpu/latte/latte_pm4_writer.h> namespace cafe::tcl { using namespace latte; using namespace cafe::coreinit; struct StaticRingData { be2_array<char, 32> waitCpRetireTimestampEventName; be2_struct<OSEvent> waitCpRetireTimestampEvent; be2_struct<OSEvent> waitDmaeRetireTimestampEvent; be2_atomic<uint64_t> cpSubmitTimestamp; be2_atomic<uint64_t> cpRetireTimestamp; be2_atomic<uint64_t> dmaeRetireTimestamp; //! Physical address of the above cpRetireTimestamp variable. be2_val<phys_addr> cpRetireTimestampAddress; }; static virt_ptr<StaticRingData> sRingData; TCLStatus TCLReadTimestamp(TCLTimestampID id, virt_ptr<TCLTimestamp> outValue) { switch (id) { case TCLTimestampID::CPSubmitted: *outValue = sRingData->cpSubmitTimestamp.load() - 1; break; case TCLTimestampID::CPRetired: *outValue = sRingData->cpRetireTimestamp.load(); break; case TCLTimestampID::DMAERetired: *outValue = sRingData->dmaeRetireTimestamp.load(); break; default: return TCLStatus::InvalidArg; } return TCLStatus::OK; } TCLStatus TCLWaitTimestamp(TCLTimestampID id, TCLTimestamp timestamp, OSTime timeout) { auto endTime = OSGetSystemTime() + timeout; if (id == TCLTimestampID::CPRetired) { while (timestamp > sRingData->cpRetireTimestamp.load()) { if (OSGetSystemTime() >= endTime) { return TCLStatus::Timeout; } OSWaitEventWithTimeout(virt_addrof(sRingData->waitCpRetireTimestampEvent), 500000); } } else { return TCLStatus::InvalidArg; } return TCLStatus::OK; } TCLStatus TCLSubmit(phys_ptr<void> buffer, uint32_t bufferSize, virt_ptr<TCLSubmitFlags> submitFlags, virt_ptr<TCLTimestamp> lastSubmittedTimestamp) { return TCLStatus::NotInitialised; } template<typename Type> void writePM4(uint32_t *buffer, uint32_t &bufferPosWords, const Type &command) { // Remove const for the .serialise function auto &cmd = const_cast<Type &>(command); // Calculate the total size this object will be latte::pm4::PacketSizer sizer; cmd.serialise(sizer); auto totalSize = sizer.getSize() + 1; // Serialize the packet to the given buffer auto writer = latte::pm4::PacketWriter { buffer, bufferPosWords, Type::Opcode, totalSize }; cmd.serialise(writer); } static TCLTimestamp insertRetiredTimestamp(bool cacheFlushTimestamp, bool cacheFlushInvalidate, bool writeConfirm) { auto submitTimestamp = sRingData->cpSubmitTimestamp.fetch_add(1); auto eventType = VGT_EVENT_TYPE::BOTTOM_OF_PIPE_TS; if (cacheFlushTimestamp) { if (cacheFlushInvalidate) { eventType = VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_TS_EVENT; } else { eventType = VGT_EVENT_TYPE::CACHE_FLUSH_TS; } } // Submit an EVENT_WRITE_EOP to update the retire timestamp std::array<uint32_t, 6> buffer; auto bufferPos = 0u; writePM4(buffer.data(), bufferPos, pm4::EventWriteEOP { VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(eventType) .EVENT_INDEX(VGT_EVENT_INDEX::TS), pm4::EW_ADDR_LO::get(0) .ADDR_LO(static_cast<uint32_t>(sRingData->cpRetireTimestampAddress) >> 2) .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN64), pm4::EWP_ADDR_HI::get(0) .DATA_SEL(pm4::EWP_DATA_64) .INT_SEL(writeConfirm ? pm4::EWP_INT_WRITE_CONFIRM : pm4::EWP_INT_NONE), static_cast<uint32_t>(submitTimestamp & 0xFFFFFFFF), static_cast<uint32_t>(submitTimestamp >> 32) }); gpu::ringbuffer::write({ buffer.data(), bufferPos }); return submitTimestamp; } TCLStatus TCLSubmitToRing(virt_ptr<uint32_t> buffer, uint32_t numWords, virt_ptr<TCLSubmitFlags> submitFlags, virt_ptr<TCLTimestamp> lastSubmittedTimestamp) { auto flags = TCLSubmitFlags::None; auto submitTimestamp = TCLTimestamp { 0 }; if (submitFlags) { flags = *submitFlags; } gpu::ringbuffer::write({ buffer.getRawPointer(), numWords }); if (flags & TCLSubmitFlags::UpdateTimestamp) { submitTimestamp = insertRetiredTimestamp(!(flags & TCLSubmitFlags::NoCacheFlush), !!(flags & TCLSubmitFlags::CacheFlushInvalidate), !(flags & TCLSubmitFlags::NoWriteConfirmTimestamp)); } else { submitTimestamp = sRingData->cpSubmitTimestamp.load(); } if (lastSubmittedTimestamp) { *lastSubmittedTimestamp = submitTimestamp; } return TCLStatus::OK; } namespace internal { static TCLInterruptHandlerFn sCpEopEventCallback = nullptr; static void cpEopEventCallback(virt_ptr<TCLInterruptEntry> interruptEntry, virt_ptr<void> userData) { OSSignalEvent(virt_addrof(sRingData->waitCpRetireTimestampEvent)); } void initialiseRing() { // TODO: be2_atomic virt_addrof sRingData->cpRetireTimestampAddress = coreinit::OSEffectiveToPhysical(cpu::translate(&sRingData->cpRetireTimestamp)); sRingData->waitCpRetireTimestampEventName = "{ GX2 CP Retire }"; OSInitEventEx(virt_addrof(sRingData->waitCpRetireTimestampEvent), FALSE, OSEventMode::AutoReset, virt_addrof(sRingData->waitCpRetireTimestampEventName)); TCLIHRegister(TCLInterruptType::CP_EOP_EVENT, sCpEopEventCallback, nullptr); // tcl.rpl also register these but only do a COSWarn in the callbacks, nothing interesting. // TCLIHRegister(TCLInterruptType::CP_RESERVED_BITS, sCpReservedBitsException, nullptr); // TCLIHRegister(TCLInterruptType::CP_BAD_OPCODE, sCpBadOpcodeException, nullptr); } } // namespace internal void Library::registerRingSymbols() { RegisterFunctionExport(TCLReadTimestamp); RegisterFunctionExport(TCLWaitTimestamp); RegisterFunctionExport(TCLSubmit); RegisterFunctionExport(TCLSubmitToRing); RegisterDataInternal(sRingData); RegisterFunctionInternal(internal::cpEopEventCallback, internal::sCpEopEventCallback); } } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tcl/tcl_ring.h ================================================ #pragma once #include "tcl_enum.h" #include "cafe/libraries/coreinit/coreinit_time.h" #include <libcpu/be2_struct.h> namespace cafe::tcl { using TCLTimestamp = uint64_t; TCLStatus TCLReadTimestamp(TCLTimestampID id, virt_ptr<TCLTimestamp> outValue); TCLStatus TCLWaitTimestamp(TCLTimestampID id, TCLTimestamp timestamp, coreinit::OSTime timeout); TCLStatus TCLSubmit(phys_ptr<void> buffer, uint32_t bufferSize, virt_ptr<TCLSubmitFlags> submitFlags, virt_ptr<TCLTimestamp> outLastSubmittedTimestamp); TCLStatus TCLSubmitToRing(virt_ptr<uint32_t> buffer, uint32_t numWords, virt_ptr<TCLSubmitFlags> submitFlags, virt_ptr<TCLTimestamp> outLastSubmittedTimestamp); namespace internal { void initialiseRing(); } // namespace internal } // namespace cafe::tcl ================================================ FILE: src/libdecaf/src/cafe/libraries/tve/tve.cpp ================================================ #include "tve.h" namespace cafe::tve { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::tve ================================================ FILE: src/libdecaf/src/cafe/libraries/tve/tve.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::tve { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::tve, "tve.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::tve ================================================ FILE: src/libdecaf/src/cafe/libraries/uac/uac.cpp ================================================ #include "uac.h" namespace cafe::uac { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::uac ================================================ FILE: src/libdecaf/src/cafe/libraries/uac/uac.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::uac { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::uac, "uac.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::uac ================================================ FILE: src/libdecaf/src/cafe/libraries/uac_rpl/uac_rpl.cpp ================================================ #include "uac_rpl.h" namespace cafe::uac_rpl { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::uac_rpl ================================================ FILE: src/libdecaf/src/cafe/libraries/uac_rpl/uac_rpl.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::uac_rpl { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::uac_rpl, "uac_rpl.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::uac_rpl ================================================ FILE: src/libdecaf/src/cafe/libraries/usb_mic/usb_mic.cpp ================================================ #include "usb_mic.h" namespace cafe::usb_mic { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::usb_mic ================================================ FILE: src/libdecaf/src/cafe/libraries/usb_mic/usb_mic.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::usb_mic { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::usb_mic, "usb_mic.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::usb_mic ================================================ FILE: src/libdecaf/src/cafe/libraries/uvc/uvc.cpp ================================================ #include "uvc.h" namespace cafe::uvc { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::uvc ================================================ FILE: src/libdecaf/src/cafe/libraries/uvc/uvc.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::uvc { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::uvc, "uvc.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::uvc ================================================ FILE: src/libdecaf/src/cafe/libraries/uvd/uvd.cpp ================================================ #include "uvd.h" namespace cafe::uvd { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); } } // namespace cafe::uvd ================================================ FILE: src/libdecaf/src/cafe/libraries/uvd/uvd.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::uvd { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::uvd, "uvd.rpl") { } protected: virtual void registerSymbols() override; private: }; } // namespace cafe::uvd ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad.cpp ================================================ #include "vpad.h" namespace cafe::vpad { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerControllerSymbols(); registerGyroSymbols(); registerMotorSymbols(); } } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::vpad { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::vpad, "vpad.rpl") { } protected: virtual void registerSymbols() override; private: void registerControllerSymbols(); void registerGyroSymbols(); void registerMotorSymbols(); }; } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_controller.cpp ================================================ #include "vpad.h" #include "vpad_controller.h" #include "input/input.h" #include <vector> #include <utility> namespace cafe::vpad { struct StaticControllerData { be2_array<VPADTouchCalibrationParam, 2> calibrationParam { VPADTouchCalibrationParam { uint16_t { 0 }, uint16_t { 0 }, 1280.0f / 4096.0f, 720.0f / 4096.0f }, VPADTouchCalibrationParam { uint16_t { 0 }, uint16_t { 0 }, 1280.0f / 4096.0f, 720.0f / 4096.0f } }; be2_val<VPADButtons> lastButtonState = VPADButtons { 0 }; }; static virt_ptr<StaticControllerData> sControllerData = nullptr; void VPADInit() { } void VPADSetAccParam(VPADChan chan, float unk1, float unk2) { } void VPADSetBtnRepeat(VPADChan chan, float unk1, float unk2) { } /** * VPADRead * * \return * Returns the number of samples read. */ uint32_t VPADRead(VPADChan chan, virt_ptr<VPADStatus> buffers, uint32_t bufferCount, virt_ptr<VPADReadError> outError) { if (bufferCount < 1) { if (outError) { *outError = VPADReadError::NoSamples; } return 0; } if (chan >= VPADChan::Max) { if (outError) { *outError = VPADReadError::InvalidController; } return 0; } memset(virt_addrof(buffers[0]).get(), 0, sizeof(VPADStatus)); auto status = input::vpad::Status { }; input::sampleVpadController(static_cast<int>(chan), status); if (!status.connected) { if (outError) { *outError = VPADReadError::InvalidController; } return 0; } auto &buffer = buffers[0]; auto hold = VPADButtons { 0 }; if (status.buttons.sync) { hold |= VPADButtons::Sync; } if (status.buttons.home) { hold |= VPADButtons::Home; } if (status.buttons.minus) { hold |= VPADButtons::Minus; } if (status.buttons.plus) { hold |= VPADButtons::Plus; } if (status.buttons.r) { hold |= VPADButtons::R; } if (status.buttons.l) { hold |= VPADButtons::L; } if (status.buttons.zr) { hold |= VPADButtons::ZR; } if (status.buttons.zl) { hold |= VPADButtons::ZL; } if (status.buttons.down) { hold |= VPADButtons::Down; } if (status.buttons.up) { hold |= VPADButtons::Up; } if (status.buttons.right) { hold |= VPADButtons::Right; } if (status.buttons.left) { hold |= VPADButtons::Left; } if (status.buttons.x) { hold |= VPADButtons::X; } if (status.buttons.y) { hold |= VPADButtons::Y; } if (status.buttons.b) { hold |= VPADButtons::B; } if (status.buttons.a) { hold |= VPADButtons::A; } if (status.buttons.stickR) { hold |= VPADButtons::StickR; } if (status.buttons.stickL) { hold |= VPADButtons::StickL; } buffer.hold = hold; buffer.trigger = (~sControllerData->lastButtonState) & hold; buffer.release = sControllerData->lastButtonState & (~hold); sControllerData->lastButtonState = hold; // Update axis state buffer.leftStick.x = status.leftStickX; buffer.leftStick.y = status.leftStickY; buffer.rightStick.x = status.rightStickX; buffer.rightStick.y = status.rightStickY; // Update touchpad data if (status.touch.down) { buffer.tpNormal.touched = uint16_t { 1 }; buffer.tpNormal.x = static_cast<uint16_t>(status.touch.x * 4096.0f); buffer.tpNormal.y = static_cast<uint16_t>((1.0f - status.touch.y) * 4096.0f); buffer.tpNormal.validity = VPADTouchPadValidity::Valid; } else { buffer.tpNormal.touched = uint16_t { 0 }; buffer.tpNormal.validity = VPADTouchPadValidity::InvalidX | VPADTouchPadValidity::InvalidY; } // For now, lets just copy instantaneous position tpNormal to tpFiltered. // My guess is that tpFiltered1/2 "filter" results over a period of time // to allow for smoother input, due to the fact that touch screens aren't // super precise and people's fingers are fat. I would guess tpFiltered1 // is filtered over a shorter period and tpFiltered2 over a longer period. buffer.tpFiltered1 = buffer.tpNormal; buffer.tpFiltered2 = buffer.tpNormal; if (outError) { *outError = VPADReadError::Success; } return 1; } void VPADGetTPCalibrationParam(VPADChan chan, virt_ptr<VPADTouchCalibrationParam> outParam) { *outParam = sControllerData->calibrationParam[chan]; } void VPADGetTPCalibratedPoint(VPADChan chan, virt_ptr<VPADTouchData> calibratedData, virt_ptr<const VPADTouchData> uncalibratedData) { auto &calibrationParam = sControllerData->calibrationParam[chan]; calibratedData->touched = uncalibratedData->touched; calibratedData->validity = uncalibratedData->validity; calibratedData->x = static_cast<uint16_t>( static_cast<float>(uncalibratedData->x - calibrationParam.adjustX) * calibrationParam.scaleX); calibratedData->y = static_cast<uint16_t>( static_cast<float>((4096 - uncalibratedData->y) - calibrationParam.adjustY) * calibrationParam.scaleY); } void VPADGetTPCalibratedPointEx(VPADChan chan, VPADTouchPadResolution tpReso, virt_ptr<VPADTouchData> calibratedData, virt_ptr<const VPADTouchData> uncalibratedData) { auto &calibrationParam = sControllerData->calibrationParam[chan]; calibratedData->touched = uncalibratedData->touched; calibratedData->validity = uncalibratedData->validity; auto scaleX = 1.0f, scaleY = 1.0f; if (tpReso == VPADTouchPadResolution::Tp_1920x1080) { scaleX = 1920.0f / 1280.0f; scaleY = 1080.0f / 720.0f; } else if (tpReso == VPADTouchPadResolution::Tp_854x480) { scaleX = 854.0f / 1280.0f; scaleY = 480.0f / 720.0f; } calibratedData->x = static_cast<uint16_t>( static_cast<float>(uncalibratedData->x - calibrationParam.adjustX) * calibrationParam.scaleX * scaleX); calibratedData->y = static_cast<uint16_t>( static_cast<float>((4096 - uncalibratedData->y) - calibrationParam.adjustY) * calibrationParam.scaleY * scaleY); } void VPADSetTPCalibrationParam(VPADChan chan, virt_ptr<const VPADTouchCalibrationParam> param) { sControllerData->calibrationParam[chan] = *param; } bool VPADBASEGetHeadphoneStatus(VPADChan chan) { return false; } void Library::registerControllerSymbols() { RegisterFunctionExport(VPADInit); RegisterFunctionExport(VPADSetAccParam); RegisterFunctionExport(VPADSetBtnRepeat); RegisterFunctionExport(VPADRead); RegisterFunctionExport(VPADGetTPCalibrationParam); RegisterFunctionExport(VPADGetTPCalibratedPoint); RegisterFunctionExport(VPADGetTPCalibratedPointEx); RegisterFunctionExport(VPADSetTPCalibrationParam); RegisterFunctionExport(VPADBASEGetHeadphoneStatus); RegisterDataInternal(sControllerData); } } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_controller.h ================================================ #pragma once #include "vpad_enum.h" #include <libcpu/be2_struct.h> namespace cafe::vpad { /** * \defgroup vpad_status VPAD Controller Status * \ingroup vpad * @{ */ struct VPADVec2D { be2_val<float> x; be2_val<float> y; }; CHECK_OFFSET(VPADVec2D, 0x00, x); CHECK_OFFSET(VPADVec2D, 0x04, y); CHECK_SIZE(VPADVec2D, 0x08); struct VPADVec3D { be2_val<float> x; be2_val<float> y; be2_val<float> z; }; CHECK_OFFSET(VPADVec3D, 0x00, x); CHECK_OFFSET(VPADVec3D, 0x04, y); CHECK_OFFSET(VPADVec3D, 0x08, z); CHECK_SIZE(VPADVec3D, 0x0C); struct VPADAccStatus { be2_struct<VPADVec3D> acc; be2_val<float> magnitude; be2_val<float> variation; be2_struct<VPADVec2D> vertical; }; CHECK_OFFSET(VPADAccStatus, 0x00, acc); CHECK_OFFSET(VPADAccStatus, 0x0C, magnitude); CHECK_OFFSET(VPADAccStatus, 0x10, variation); CHECK_OFFSET(VPADAccStatus, 0x14, vertical); CHECK_SIZE(VPADAccStatus, 0x1c); struct VPADDirection { be2_struct<VPADVec3D> x; be2_struct<VPADVec3D> y; be2_struct<VPADVec3D> z; }; CHECK_OFFSET(VPADDirection, 0x00, x); CHECK_OFFSET(VPADDirection, 0x0C, y); CHECK_OFFSET(VPADDirection, 0x18, z); CHECK_SIZE(VPADDirection, 0x24); struct VPADGyroStatus { be2_val<float> unk1; be2_val<float> unk2; be2_val<float> unk3; be2_val<float> unk4; be2_val<float> unk5; be2_val<float> unk6; }; CHECK_OFFSET(VPADGyroStatus, 0x00, unk1); CHECK_OFFSET(VPADGyroStatus, 0x04, unk2); CHECK_OFFSET(VPADGyroStatus, 0x08, unk3); CHECK_OFFSET(VPADGyroStatus, 0x0C, unk4); CHECK_OFFSET(VPADGyroStatus, 0x10, unk5); CHECK_OFFSET(VPADGyroStatus, 0x14, unk6); CHECK_SIZE(VPADGyroStatus, 0x18); struct VPADTouchCalibrationParam { be2_val<uint16_t> adjustX; be2_val<uint16_t> adjustY; be2_val<float> scaleX; be2_val<float> scaleY; }; CHECK_OFFSET(VPADTouchCalibrationParam, 0x00, adjustX); CHECK_OFFSET(VPADTouchCalibrationParam, 0x02, adjustY); CHECK_OFFSET(VPADTouchCalibrationParam, 0x04, scaleX); CHECK_OFFSET(VPADTouchCalibrationParam, 0x08, scaleY); CHECK_SIZE(VPADTouchCalibrationParam, 0x0C); struct VPADTouchData { //! The x-coordinate of a touched point. be2_val<uint16_t> x; //! The y-coordinate of a touched point. be2_val<uint16_t> y; //! 0 if screen is not currently being touched be2_val<uint16_t> touched; //! Bitfield of #VPADTouchPadValidity to indicate how touch sample accuracy be2_val<uint16_t> validity; }; CHECK_OFFSET(VPADTouchData, 0x00, x); CHECK_OFFSET(VPADTouchData, 0x02, y); CHECK_OFFSET(VPADTouchData, 0x04, touched); CHECK_OFFSET(VPADTouchData, 0x06, validity); CHECK_SIZE(VPADTouchData, 0x08); struct VPADStatus { //! Indicates what buttons are held down be2_val<VPADButtons> hold; //! Indicates what buttons have been pressed since last sample be2_val<VPADButtons> trigger; //! Indicates what buttons have been released since last sample be2_val<VPADButtons> release; //! Position of left analog stick be2_struct<VPADVec2D> leftStick; //! Position of right analog stick be2_struct<VPADVec2D> rightStick; //! Status of DRC accelorometer be2_struct<VPADAccStatus> accelorometer; //! Status of DRC gyro be2_struct<VPADGyroStatus> gyro; UNKNOWN(0x02); //! Current touch position on DRC be2_struct<VPADTouchData> tpNormal; //! Filtered touch position, first level of smoothing be2_struct<VPADTouchData> tpFiltered1; //! Filtered touch position, second level of smoothing be2_struct<VPADTouchData> tpFiltered2; UNKNOWN(0x28); //! Status of DRC magnetometer be2_struct<VPADVec3D> mag; //! Current volume set by the slide control be2_val<uint8_t> slideVolume; //! Battery level of controller be2_val<uint8_t> battery; //! Status of DRC microphone be2_val<uint8_t> micStatus; //! Unknown volume related value be2_val<uint8_t> slideVolumeEx; UNKNOWN(0x07); }; CHECK_OFFSET(VPADStatus, 0x00, hold); CHECK_OFFSET(VPADStatus, 0x04, trigger); CHECK_OFFSET(VPADStatus, 0x08, release); CHECK_OFFSET(VPADStatus, 0x0C, leftStick); CHECK_OFFSET(VPADStatus, 0x14, rightStick); CHECK_OFFSET(VPADStatus, 0x1C, accelorometer); CHECK_OFFSET(VPADStatus, 0x38, gyro); CHECK_OFFSET(VPADStatus, 0x52, tpNormal); CHECK_OFFSET(VPADStatus, 0x5A, tpFiltered1); CHECK_OFFSET(VPADStatus, 0x62, tpFiltered2); CHECK_OFFSET(VPADStatus, 0x94, mag); CHECK_OFFSET(VPADStatus, 0xA0, slideVolume); CHECK_OFFSET(VPADStatus, 0xA1, battery); CHECK_OFFSET(VPADStatus, 0xA2, micStatus); CHECK_OFFSET(VPADStatus, 0xA3, slideVolumeEx); CHECK_SIZE(VPADStatus, 0xAC); void VPADInit(); void VPADSetAccParam(VPADChan chan, float unk1, float unk2); void VPADSetBtnRepeat(VPADChan chan, float unk1, float unk2); uint32_t VPADRead(VPADChan chan, virt_ptr<VPADStatus> buffers, uint32_t bufferCount, virt_ptr<VPADReadError> outError); void VPADGetTPCalibrationParam(VPADChan chan, virt_ptr<VPADTouchCalibrationParam> outParam); void VPADGetTPCalibratedPoint(VPADChan chan, virt_ptr<VPADTouchData> calibratedData, virt_ptr<const VPADTouchData> uncalibratedData); void VPADGetTPCalibratedPointEx(VPADChan chan, VPADTouchPadResolution tpReso, virt_ptr<VPADTouchData> calibratedData, virt_ptr<const VPADTouchData> uncalibratedData); void VPADSetTPCalibrationParam(VPADChan chan, virt_ptr<const VPADTouchCalibrationParam> param); bool VPADBASEGetHeadphoneStatus(VPADChan chan); /** @} */ } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_enum.h ================================================ #ifndef VPAD_ENUM_H #define VPAD_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(cafe) ENUM_NAMESPACE_ENTER(vpad) FLAGS_BEG(VPADButtons, uint32_t) FLAGS_VALUE(Sync, 1 << 0) FLAGS_VALUE(Home, 1 << 1) FLAGS_VALUE(Minus, 1 << 2) FLAGS_VALUE(Plus, 1 << 3) FLAGS_VALUE(R, 1 << 4) FLAGS_VALUE(L, 1 << 5) FLAGS_VALUE(ZR, 1 << 6) FLAGS_VALUE(ZL, 1 << 7) FLAGS_VALUE(Down, 1 << 8) FLAGS_VALUE(Up, 1 << 9) FLAGS_VALUE(Right, 1 << 10) FLAGS_VALUE(Left, 1 << 11) FLAGS_VALUE(Y, 1 << 12) FLAGS_VALUE(X, 1 << 13) FLAGS_VALUE(B, 1 << 14) FLAGS_VALUE(A, 1 << 15) FLAGS_VALUE(StickR, 1 << 17) FLAGS_VALUE(StickL, 1 << 18) FLAGS_END(VPADButtons) ENUM_BEG(VPADChan, int32_t) ENUM_VALUE(Chan0, 0) ENUM_VALUE(Chan1, 1) ENUM_VALUE(Max, 2) ENUM_END(VPADChan) ENUM_BEG(VPADTouchPadResolution, int32_t) ENUM_VALUE(Tp_1920x1080, 0) ENUM_VALUE(Tp_1280x720, 1) ENUM_VALUE(Tp_854x480, 2) ENUM_END(VPADTouchPadResolution) FLAGS_BEG(VPADTouchPadValidity, uint16_t) //! Both X and Y touchpad positions are accurate FLAGS_VALUE(Valid, 0) //! X position is inaccurate FLAGS_VALUE(InvalidX, 1 << 0) //! Y position is inaccurate FLAGS_VALUE(InvalidY, 1 << 1) FLAGS_END(VPADTouchPadValidity) ENUM_BEG(VPADReadError, int32_t) ENUM_VALUE(Success, 0) ENUM_VALUE(NoSamples, -1) ENUM_VALUE(InvalidController, -2) ENUM_END(VPADReadError) ENUM_NAMESPACE_EXIT(vpad) ENUM_NAMESPACE_EXIT(cafe) #include <common/enum_end.inl> #endif // ifdef VPAD_ENUM_H ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_gyro.cpp ================================================ #include "vpad.h" #include "vpad_gyro.h" namespace cafe::vpad { float VPADIsEnableGyroAccRevise(VPADChan chan) { return 0.0f; } float VPADIsEnableGyroZeroPlay(VPADChan chan) { return 0.0f; } float VPADIsEnableGyroZeroDrift(VPADChan chan) { return 0.0f; } float VPADIsEnableGyroDirRevise(VPADChan chan) { return 0.0f; } void Library::registerGyroSymbols() { RegisterFunctionExport(VPADIsEnableGyroAccRevise); RegisterFunctionExport(VPADIsEnableGyroZeroPlay); RegisterFunctionExport(VPADIsEnableGyroZeroDrift); RegisterFunctionExport(VPADIsEnableGyroDirRevise); } } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_gyro.h ================================================ #pragma once #include "vpad_enum.h" #include <libcpu/be2_struct.h> namespace cafe::vpad { float VPADIsEnableGyroAccRevise(VPADChan chan); float VPADIsEnableGyroZeroPlay(VPADChan chan); float VPADIsEnableGyroZeroDrift(VPADChan chan); float VPADIsEnableGyroDirRevise(VPADChan chan); } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_motor.cpp ================================================ #include "vpad.h" #include "vpad_motor.h" namespace cafe::vpad { int32_t VPADControlMotor(VPADChan chan, virt_ptr<void> buffer, uint32_t size) { return 0; } void VPADStopMotor(VPADChan chan) { } void Library::registerMotorSymbols() { RegisterFunctionExport(VPADControlMotor); RegisterFunctionExport(VPADStopMotor); } } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpad/vpad_motor.h ================================================ #pragma once #include "vpad_enum.h" #include <libcpu/be2_struct.h> namespace cafe::vpad { int32_t VPADControlMotor(VPADChan chan, virt_ptr<void> buffer, uint32_t size); void VPADStopMotor(VPADChan chan); } // namespace cafe::vpad ================================================ FILE: src/libdecaf/src/cafe/libraries/vpadbase/vpadbase.cpp ================================================ #include "vpadbase.h" namespace cafe::vpadbase { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerControllerSymbols(); } } // namespace cafe::vpadbase ================================================ FILE: src/libdecaf/src/cafe/libraries/vpadbase/vpadbase.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::vpadbase { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::vpadbase, "vpadbase.rpl") { } protected: virtual void registerSymbols() override; private: void registerControllerSymbols(); }; } // namespace cafe::vpadbase ================================================ FILE: src/libdecaf/src/cafe/libraries/vpadbase/vpadbase_controller.cpp ================================================ #include "vpadbase.h" #include "vpadbase_controller.h" #include "cafe/libraries/cafe_hle_stub.h" namespace cafe::vpadbase { BOOL VPADBASEGetHeadphoneStatus(int32_t chan) { decaf_warn_stub(); return FALSE; } void Library::registerControllerSymbols() { RegisterFunctionExport(VPADBASEGetHeadphoneStatus); } } // namespace cafe::vpadbase ================================================ FILE: src/libdecaf/src/cafe/libraries/vpadbase/vpadbase_controller.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::vpadbase { BOOL VPADBASEGetHeadphoneStatus(int32_t chan); } // namespace cafe::vpadbase ================================================ FILE: src/libdecaf/src/cafe/libraries/zlib125/zlib125.cpp ================================================ #include "zlib125.h" namespace cafe::zlib125 { static int32_t rpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle, coreinit::OSDynLoad_EntryReason reason) { return 0; } void Library::registerSymbols() { RegisterEntryPoint(rpl_entry); registerZlibSymbols(); } } // namespace cafe::zlib125 ================================================ FILE: src/libdecaf/src/cafe/libraries/zlib125/zlib125.h ================================================ #pragma once #include "cafe/libraries/cafe_hle_library.h" namespace cafe::zlib125 { class Library : public hle::Library { public: Library() : hle::Library(hle::LibraryId::zlib125, "zlib125.rpl") { } protected: virtual void registerSymbols() override; private: void registerZlibSymbols(); }; } // namespace cafe::zlib125 ================================================ FILE: src/libdecaf/src/cafe/libraries/zlib125/zlib125_zlib.cpp ================================================ #include "zlib125.h" #include "cafe/cafe_ppc_interface_invoke_guest.h" #include "cafe/libraries/coreinit/coreinit_memdefaultheap.h" #include <common/decaf_assert.h> #include <libcpu/mmu.h> #include <zlib.h> namespace cafe::zlib125 { static std::map<uint32_t, z_stream> gStreamMap; // WiiU games will be using a 32bit zlib where stuff is in big endian order in memory // this means all structures like z_streamp have to be swapped endian to a temp structure using zlib125_alloc_func = virt_func_ptr< virt_ptr<void>(virt_ptr<void>, uint32_t, uint32_t)>; using zlib125_free_func = virt_func_ptr< void(virt_ptr<void>, virt_ptr<void>)>; struct zlib125_stream { be2_virt_ptr<Bytef> next_in; be2_val<uint32_t> avail_in; be2_val<uint32_t> total_in; be2_virt_ptr<Bytef> next_out; be2_val<uint32_t> avail_out; be2_val<uint32_t> total_out; be2_virt_ptr<char> msg; be2_virt_ptr<struct internal_state> state; be2_val<zlib125_alloc_func> zalloc; be2_val<zlib125_free_func> zfree; be2_virt_ptr<void> opaque; be2_val<int32_t> data_type; be2_val<uint32_t> adler; be2_val<uint32_t> reserved; }; CHECK_OFFSET(zlib125_stream, 0x00, next_in); CHECK_OFFSET(zlib125_stream, 0x04, avail_in); CHECK_OFFSET(zlib125_stream, 0x08, total_in); CHECK_OFFSET(zlib125_stream, 0x0C, next_out); CHECK_OFFSET(zlib125_stream, 0x10, avail_out); CHECK_OFFSET(zlib125_stream, 0x14, total_out); CHECK_OFFSET(zlib125_stream, 0x18, msg); CHECK_OFFSET(zlib125_stream, 0x1C, state); CHECK_OFFSET(zlib125_stream, 0x20, zalloc); CHECK_OFFSET(zlib125_stream, 0x24, zfree); CHECK_OFFSET(zlib125_stream, 0x28, opaque); CHECK_OFFSET(zlib125_stream, 0x2C, data_type); CHECK_OFFSET(zlib125_stream, 0x30, adler); CHECK_OFFSET(zlib125_stream, 0x34, reserved); CHECK_SIZE(zlib125_stream, 0x38); struct zlib125_header { be2_val<int32_t> text; be2_val<uint32_t> time; be2_val<int32_t> xflags; be2_val<int32_t> os; be2_virt_ptr<uint8_t> extra; be2_val<uint32_t> extra_len; be2_val<uint32_t> extra_max; be2_virt_ptr<uint8_t> name; be2_val<uint32_t> name_max; be2_virt_ptr<uint8_t> comment; be2_val<uint32_t> comm_max; be2_val<int32_t> hcrc; be2_val<int32_t> done; }; CHECK_OFFSET(zlib125_header, 0x00, text); CHECK_OFFSET(zlib125_header, 0x04, time); CHECK_OFFSET(zlib125_header, 0x08, xflags); CHECK_OFFSET(zlib125_header, 0x0C, os); CHECK_OFFSET(zlib125_header, 0x10, extra); CHECK_OFFSET(zlib125_header, 0x14, extra_len); CHECK_OFFSET(zlib125_header, 0x18, extra_max); CHECK_OFFSET(zlib125_header, 0x1C, name); CHECK_OFFSET(zlib125_header, 0x20, name_max); CHECK_OFFSET(zlib125_header, 0x24, comment); CHECK_OFFSET(zlib125_header, 0x28, comm_max); CHECK_OFFSET(zlib125_header, 0x2C, hcrc); CHECK_OFFSET(zlib125_header, 0x30, done); CHECK_SIZE(zlib125_header, 0x34); static void * zlibAllocWrapper(void *opaque, unsigned items, unsigned size) { auto wstrm = reinterpret_cast<zlib125_stream *>(opaque); if (wstrm->zalloc) { auto ptr = cafe::invoke(cpu::this_core::state(), wstrm->zalloc, wstrm->opaque, static_cast<uint32_t>(items), static_cast<uint32_t>(size)); return ptr.get(); } else { auto ptr = coreinit::MEMAllocFromDefaultHeap(items * size); return ptr.get(); } } static void zlibFreeWrapper(void *opaque, void *address) { auto wstrm = reinterpret_cast<zlib125_stream *>(opaque); auto ptr = virt_cast<void *>(cpu::translate(address)); if (wstrm->zfree) { cafe::invoke(cpu::this_core::state(), wstrm->zfree, wstrm->opaque, ptr); } else { coreinit::MEMFreeToDefaultHeap(ptr); } } z_stream * getZStream(virt_ptr<zlib125_stream> in) { auto zstream = &gStreamMap[virt_cast<virt_addr>(in).getAddress()]; zstream->opaque = in.get(); zstream->zalloc = &zlibAllocWrapper; zstream->zfree = &zlibFreeWrapper; return zstream; } void eraseZStream(virt_ptr<zlib125_stream> in) { gStreamMap.erase(virt_cast<virt_addr>(in).getAddress()); } static int zlib125_deflate(virt_ptr<zlib125_stream> wstrm, int32_t flush) { auto zstrm = getZStream(wstrm); zstrm->next_in = wstrm->next_in.get(); zstrm->avail_in = wstrm->avail_in; zstrm->total_in = wstrm->total_in; zstrm->next_out = wstrm->next_out.get(); zstrm->avail_out = wstrm->avail_out; zstrm->total_out = wstrm->total_out; zstrm->data_type = wstrm->data_type; zstrm->adler = wstrm->adler; auto result = deflate(zstrm, flush); wstrm->next_in = virt_cast<Bytef *>(cpu::translate(zstrm->next_in)); wstrm->avail_in = static_cast<uint32_t>(zstrm->avail_in); wstrm->total_in = static_cast<uint32_t>(zstrm->total_in); wstrm->next_out = virt_cast<Bytef *>(cpu::translate(zstrm->next_out)); wstrm->avail_out = static_cast<uint32_t>(zstrm->avail_out); wstrm->total_out = static_cast<uint32_t>(zstrm->total_out); wstrm->data_type = static_cast<int32_t>(zstrm->data_type); wstrm->adler = static_cast<uint32_t>(zstrm->adler); return result; } static int zlib125_deflateInit_(virt_ptr<zlib125_stream> wstrm, int32_t level, virt_ptr<const char> version, int32_t stream_size) { decaf_check(sizeof(zlib125_stream) == stream_size); auto zstrm = getZStream(wstrm); auto result = deflateInit_(zstrm, level, version.get(), sizeof(z_stream)); wstrm->msg = nullptr; return result; } static int zlib125_deflateInit2_(virt_ptr<zlib125_stream> wstrm, int32_t level, int32_t method, int32_t windowBits, int32_t memLevel, int32_t strategy, virt_ptr<const char> version, int32_t stream_size) { decaf_check(sizeof(zlib125_stream) == stream_size); auto zstrm = getZStream(wstrm); auto result = deflateInit2_(zstrm, level, method, windowBits, memLevel, strategy, version.get(), sizeof(z_stream)); wstrm->msg = nullptr; return result; } static uint32_t zlib125_deflateBound(virt_ptr<zlib125_stream> wstrm, uint32_t sourceLen) { auto zstrm = getZStream(wstrm); return deflateBound(zstrm, sourceLen); } static int zlib125_deflateReset(virt_ptr<zlib125_stream> wstrm) { auto zstrm = getZStream(wstrm); return deflateReset(zstrm); } static int zlib125_deflateEnd(virt_ptr<zlib125_stream> wstrm) { auto zstrm = getZStream(wstrm); return deflateEnd(zstrm); } static int zlib125_inflate(virt_ptr<zlib125_stream> wstrm, int32_t flush) { auto zstrm = getZStream(wstrm); zstrm->next_in = wstrm->next_in.get(); zstrm->avail_in = wstrm->avail_in; zstrm->total_in = wstrm->total_in; zstrm->next_out = wstrm->next_out.get(); zstrm->avail_out = wstrm->avail_out; zstrm->total_out = wstrm->total_out; zstrm->data_type = wstrm->data_type; zstrm->adler = wstrm->adler; auto result = inflate(zstrm, flush); wstrm->next_in = virt_cast<Bytef *>(cpu::translate(zstrm->next_in)); wstrm->avail_in = static_cast<uint32_t>(zstrm->avail_in); wstrm->total_in = static_cast<uint32_t>(zstrm->total_in); wstrm->next_out = virt_cast<Bytef *>(cpu::translate(zstrm->next_out)); wstrm->avail_out = static_cast<uint32_t>(zstrm->avail_out); wstrm->total_out = static_cast<uint32_t>(zstrm->total_out); wstrm->data_type = static_cast<int32_t>(zstrm->data_type); wstrm->adler = static_cast<uint32_t>(zstrm->adler); return result; } static int zlib125_inflateInit_(virt_ptr<zlib125_stream> wstrm, virt_ptr<const char> version, int32_t stream_size) { decaf_check(sizeof(zlib125_stream) == stream_size); auto zstrm = getZStream(wstrm); auto result = inflateInit_(zstrm, version.get(), sizeof(z_stream)); wstrm->msg = nullptr; return result; } static int zlib125_inflateInit2_(virt_ptr<zlib125_stream> wstrm, int32_t windowBits, virt_ptr<const char> version, int32_t stream_size) { decaf_check(sizeof(zlib125_stream) == stream_size); auto zstrm = getZStream(wstrm); auto result = inflateInit2_(zstrm, windowBits, version.get(), sizeof(z_stream)); wstrm->msg = nullptr; return result; } static int zlib125_inflateReset(virt_ptr<zlib125_stream> wstrm) { auto zstrm = getZStream(wstrm); return inflateReset(zstrm); } static int zlib125_inflateReset2(virt_ptr<zlib125_stream> wstrm, int32_t windowBits) { auto zstrm = getZStream(wstrm); return inflateReset2(zstrm, windowBits); } static int zlib125_inflateEnd(virt_ptr<zlib125_stream> wstrm) { auto zstrm = getZStream(wstrm); auto result = inflateEnd(zstrm); eraseZStream(wstrm); return result; } static uint32_t zlib125_adler32(uint32_t adler, virt_ptr<const uint8_t> buf, uint32_t len) { return static_cast<uint32_t>(adler32(adler, buf.get(), len)); } static uint32_t zlib125_crc32(uint32_t crc, virt_ptr<const uint8_t> buf, uint32_t len) { return static_cast<uint32_t>(crc32(crc, buf.get(), len)); } static int zlib125_compress(virt_ptr<uint8_t> dest, virt_ptr<uint32_t> destLen, virt_ptr<const uint8_t> source, uint32_t sourceLen) { auto realDestLen = static_cast<uLong>(*destLen); auto result = compress(dest.get(), &realDestLen, source.get(), sourceLen); *destLen = static_cast<uint32_t>(realDestLen); return result; } static uint32_t zlib125_compressBound(uint32_t sourceLen) { return static_cast<uint32_t>(compressBound(sourceLen)); } static int zlib125_uncompress(virt_ptr<uint8_t> dest, virt_ptr<uint32_t> destLen, virt_ptr<const uint8_t> source, uint32_t sourceLen) { auto realDestLen = static_cast<uLong>(*destLen); auto result = uncompress(dest.get(), &realDestLen, source.get(), sourceLen); *destLen = static_cast<uint32_t>(realDestLen); return result; } static uint32_t zlib125_zlibCompileFlags() { return static_cast<uint32_t>(zlibCompileFlags()); } void Library::registerZlibSymbols() { RegisterFunctionExportName("adler32", zlib125_adler32); RegisterFunctionExportName("crc32", zlib125_crc32); RegisterFunctionExportName("deflate", zlib125_deflate); RegisterFunctionExportName("deflateInit_", zlib125_deflateInit_); RegisterFunctionExportName("deflateInit2_", zlib125_deflateInit2_); RegisterFunctionExportName("deflateBound", zlib125_deflateBound); RegisterFunctionExportName("deflateReset", zlib125_deflateReset); RegisterFunctionExportName("deflateEnd", zlib125_deflateEnd); RegisterFunctionExportName("inflate", zlib125_inflate); RegisterFunctionExportName("inflateInit_", zlib125_inflateInit_); RegisterFunctionExportName("inflateInit2_", zlib125_inflateInit2_); RegisterFunctionExportName("inflateReset", zlib125_inflateReset); RegisterFunctionExportName("inflateReset2", zlib125_inflateReset2); RegisterFunctionExportName("inflateEnd", zlib125_inflateEnd); RegisterFunctionExportName("compress", zlib125_compress); RegisterFunctionExportName("compressBound", zlib125_compressBound); RegisterFunctionExportName("uncompress", zlib125_uncompress); RegisterFunctionExportName("zlibCompileFlags", zlib125_zlibCompileFlags); } } // namespace cafe::zlib125 ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_basics.cpp ================================================ #include "cafe_loader_basics.h" #include "cafe_loader_bounce.h" #include "cafe_loader_elffile.h" #include "cafe_loader_error.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_log.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_zlib.h" #include "cafe/cafe_tinyheap.h" #include "cafe/cafe_stackobject.h" #include <array> #include <common/strutils.h> #include <libcpu/be2_struct.h> #include <libcpu/cpu_formatters.h> namespace cafe::loader::internal { constexpr auto MaxSDKVersion = 21301u; constexpr auto MinSDKVersion = 20500u; static virt_ptr<rpl::SectionHeader> getSectionHeader(virt_ptr<LOADED_RPL> rpl, virt_ptr<rpl::SectionHeader> sectionHeaderBuffer, uint32_t idx) { auto base = virt_cast<virt_addr>(sectionHeaderBuffer); auto offset = idx * rpl->elfHeader.shentsize; return virt_cast<rpl::SectionHeader *>(base + offset); } static virt_ptr<rpl::SectionHeader> getSectionHeader(virt_ptr<LOADED_RPL> rpl, uint32_t idx) { return getSectionHeader(rpl, virt_cast<rpl::SectionHeader *>(rpl->sectionHeaderBuffer), idx); } static int32_t LiInitBufferTracking(LiBasicsLoadArgs *loadArgs) { virt_ptr<void> allocPtr; uint32_t allocSize; uint32_t largestFree; auto error = LiCacheLineCorrectAllocEx(loadArgs->readHeapTracking, align_up(loadArgs->pathNameLen + 1, 4), 4, &allocPtr, 1, &allocSize, &largestFree, loadArgs->fileType); if (error != 0) { return error; } auto rpl = loadArgs->loadedRpl; rpl->pathBuffer = allocPtr; rpl->pathBufferSize = allocSize; string_copy(virt_cast<char *>(rpl->pathBuffer).get(), rpl->pathBufferSize, loadArgs->pathName.get(), loadArgs->pathNameLen + 1); rpl->upcomingBufferNumber = 1u; rpl->lastChunkBuffer = loadArgs->chunkBuffer; rpl->fileOffset = loadArgs->fileOffset; rpl->upcomingFileOffset = loadArgs->chunkBufferSize; rpl->totalBytesRead = loadArgs->chunkBufferSize; rpl->upid = loadArgs->upid; rpl->fileType = loadArgs->fileType; rpl->virtualFileBaseOffset = loadArgs->chunkBufferSize; if (loadArgs->chunkBufferSize == 0x400000) { error = LiRefillUpcomingBounceBuffer(rpl, 2); } else { LiInitBuffer(false); } if (error != 0 && rpl->pathBuffer) { LiCacheLineCorrectFreeEx(loadArgs->readHeapTracking, rpl->pathBuffer, rpl->pathBufferSize); } return error; } static int32_t LiCheckFileBounds(virt_ptr<LOADED_RPL> rpl) { auto shBase = virt_cast<virt_addr>(rpl->sectionHeaderBuffer); auto dataMin = 0xFFFFFFFFu; auto dataMax = 0u; auto readMin = 0xFFFFFFFFu; auto readMax = 0u; auto textMin = 0xFFFFFFFFu; auto textMax = 0u; auto tempMin = 0xFFFFFFFFu; auto tempMax = 0u; for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize); if (sectionHeader->size == 0 || sectionHeader->type == rpl::SHT_RPL_FILEINFO || sectionHeader->type == rpl::SHT_RPL_IMPORTS || sectionHeader->type == rpl::SHT_RPL_CRCS || sectionHeader->type == rpl::SHT_NOBITS) { continue; } if ((sectionHeader->flags & rpl::SHF_EXECINSTR) && sectionHeader->type != rpl::SHT_RPL_EXPORTS) { textMin = std::min<uint32_t>(textMin, sectionHeader->offset); textMax = std::max<uint32_t>(textMax, sectionHeader->offset + sectionHeader->size); } else { if (sectionHeader->flags & rpl::SHF_ALLOC) { if (sectionHeader->flags & rpl::SHF_WRITE) { dataMin = std::min<uint32_t>(dataMin, sectionHeader->offset); dataMax = std::max<uint32_t>(dataMax, sectionHeader->offset + sectionHeader->size); } else { readMin = std::min<uint32_t>(readMin, sectionHeader->offset); readMax = std::max<uint32_t>(readMax, sectionHeader->offset + sectionHeader->size); } } else { tempMin = std::min<uint32_t>(tempMin, sectionHeader->offset); tempMax = std::max<uint32_t>(tempMax, sectionHeader->offset + sectionHeader->size); } } } if (dataMin == 0xFFFFFFFFu) { dataMin = (rpl->elfHeader.shnum * rpl->elfHeader.shentsize) + rpl->elfHeader.shoff; dataMax = dataMin; } if (readMin == 0xFFFFFFFFu) { readMin = dataMax; readMax = dataMax; } if (textMin == 0xFFFFFFFFu) { textMin = readMax; textMax = readMax; } if (tempMin == 0xFFFFFFFFu) { tempMin = textMax; tempMax = textMax; } if (dataMin < rpl->elfHeader.shoff) { Loader_ReportError("*** SecHrs, FileInfo, or CRCs in bad spot in file. Return %d.", Error::CheckFileBoundsFailed); goto error; } // Data if (dataMin > dataMax) { Loader_ReportError("*** DataMin > DataMax. break."); goto error; } if (dataMin > readMin) { Loader_ReportError("*** DataMin > ReadMin. break."); goto error; } if (dataMax > readMin) { Loader_ReportError("*** DataMax > ReadMin. break."); goto error; } // Read if (readMin > readMax) { Loader_ReportError("*** ReadMin > ReadMax. break."); goto error; } if (readMin > textMin) { Loader_ReportError("*** ReadMin > TextMin. break."); goto error; } if (readMax > textMin) { Loader_ReportError("*** ReadMax > TextMin. break."); goto error; } // Text if (textMin > textMax) { Loader_ReportError("*** TextMin > TextMax. break."); goto error; } if (textMin > tempMin) { Loader_ReportError("*** TextMin > TempMin. break."); goto error; } if (textMax > tempMin) { Loader_ReportError("*** TextMax > TempMin. break."); goto error; } // Temp if (tempMin > tempMax) { Loader_ReportError("*** TempMin > TempMax. break."); goto error; } return 0; error: LiSetFatalError(0x18729B, rpl->fileType, 1, "LiCheckFileBounds", 0x247); return Error::CheckFileBoundsFailed; } int32_t LiLoadRPLBasics(virt_ptr<char> moduleName, uint32_t moduleNameLen, virt_ptr<void> chunkBuffer, virt_ptr<TinyHeap> codeHeapTracking, virt_ptr<TinyHeap> dataHeapTracking, bool allocModuleName, uint32_t r9, virt_ptr<LOADED_RPL> *outLoadedRpl, LiBasicsLoadArgs *loadArgs, uint32_t arg_C) { struct LoadAttemptErrorData { int32_t error; ios::mcp::MCPFileType fileType; uint32_t fatalErr; std::string fatalFunction; uint32_t fatalLine; uint32_t fatalMsgType; }; std::array<LoadAttemptErrorData, 3> loadAttemptErrors; auto globals = getGlobalStorage(); auto loadAttempt = int32_t { 0 }; auto chunkReadSize = uint32_t { 0 }; auto error = int32_t { 0 }; while (true) { error = LiWaitOneChunk(&chunkReadSize, loadArgs->pathName.get(), loadArgs->fileType); if (error == 0) { break; } if (loadAttempt < 2) { if (LiGetFatalError()) { auto &attemptErrors = loadAttemptErrors[loadAttempt]; attemptErrors.error = error; attemptErrors.fileType = loadArgs->fileType; attemptErrors.fatalErr = LiGetFatalError(); attemptErrors.fatalFunction = LiGetFatalFunction(); attemptErrors.fatalLine = LiGetFatalLine(); attemptErrors.fatalMsgType = LiGetFatalMsgType(); LiResetFatalError(); } else { loadAttemptErrors[loadAttempt].error = error; } if (loadAttempt == 0) { if (loadArgs->fileType != ios::mcp::MCPFileType::CafeOS) { loadArgs->fileType = ios::mcp::MCPFileType::CafeOS; } else { loadArgs->fileType = ios::mcp::MCPFileType::ProcessCode; } } else { loadArgs->fileType = ios::mcp::MCPFileType::SharedDataCode; } LiInitBuffer(false); chunkReadSize = 0; loadArgs->chunkBufferSize = 0u; auto outChunkBufferSize = uint32_t { 0 }; auto outChunkBuffer = virt_ptr<void> { nullptr }; error = LiBounceOneChunk(loadArgs->pathName.get(), loadArgs->fileType, loadArgs->upid, &outChunkBufferSize, loadArgs->fileOffset, 1, &outChunkBuffer); loadArgs->chunkBuffer = outChunkBuffer; loadArgs->chunkBufferSize = outChunkBufferSize; ++loadAttempt; } if (error != 0) { Loader_ReportError("***Loader failure {} first time, {} second time and {} third time. loading \"{}\".", loadAttemptErrors[0].error, loadAttemptErrors[1].error, error, loadArgs->pathName.get()); return error; } } loadArgs->chunkBufferSize = chunkReadSize; LiCheckAndHandleInterrupts(); // Load and validate the ELF header auto filePhStride = uint32_t { 0 }; auto fileShStride = uint32_t { 0 }; auto fileElfHeader = virt_ptr<rpl::Header> { nullptr }; auto fileSectionHeaders = virt_ptr<rpl::SectionHeader> { nullptr }; error = ELFFILE_ValidateAndPrepareMinELF(chunkBuffer, chunkReadSize, &fileElfHeader, &fileSectionHeaders, &fileShStride, &filePhStride); if (error) { Loader_ReportError("*** Failed ELF file checks (err=0x{:08X}", error); LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x325); return error; } // Check that this ELF looks like a Wii U Cafe RPL if (fileElfHeader->fileClass != rpl::ELFCLASS32 || fileElfHeader->encoding != rpl::ELFDATA2MSB || fileElfHeader->abi != rpl::EABI_CAFE || fileElfHeader->elfVersion > rpl::EV_CURRENT || fileElfHeader->machine != rpl::EM_PPC || fileElfHeader->version != 1 || fileElfHeader->shnum < 2) { return -470025; } // Initialise temporary RPL basics auto tmpLoadedRpl = StackObject<LOADED_RPL> { }; auto rpl = virt_ptr<LOADED_RPL> { tmpLoadedRpl }; std::memset(rpl.get(), 0, sizeof(LOADED_RPL)); if (r9 == 0) { rpl->globals = getGlobalStorage(); } std::memcpy(virt_addrof(rpl->elfHeader).get(), fileElfHeader.get(), sizeof(rpl::Header)); // Check some offsets are valid if (!rpl->elfHeader.shentsize) { rpl->elfHeader.shentsize = static_cast<uint16_t>(sizeof(rpl::SectionHeader)); } if (rpl->elfHeader.shoff >= chunkReadSize || ((rpl->elfHeader.shnum - 1) * rpl->elfHeader.shentsize) + rpl->elfHeader.shoff >= chunkReadSize) { LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x33F); return -470077; } auto shRplCrcs = getSectionHeader(rpl, fileSectionHeaders, rpl->elfHeader.shnum - 2); if (shRplCrcs->offset >= chunkReadSize || shRplCrcs->offset + shRplCrcs->size >= chunkReadSize) { LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x348); return -470077; } auto shRplFileInfo = getSectionHeader(rpl, fileSectionHeaders, rpl->elfHeader.shnum - 1); if (shRplFileInfo->offset + shRplFileInfo->size >= chunkReadSize) { LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x351); return -470078; } // Check RPL file info if (shRplFileInfo->type != rpl::SHT_RPL_FILEINFO || shRplFileInfo->flags & rpl::SHF_DEFLATED) { Loader_ReportError("***shnum-1 section type = 0x{:08X}, flags=0x{:08X}", shRplFileInfo->type, shRplFileInfo->flags); LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x35A); return -470082; } auto fileInfo = virt_cast<rpl::RPLFileInfo_v4_2 *>(virt_cast<virt_addr>(fileElfHeader) + shRplFileInfo->offset); if (fileInfo->version < rpl::RPLFileInfo_v4_2::Version) { Loader_ReportError("*** COS requires that {} be built with at least SDK {}.{:02}.{}, it was built with an older SDK", moduleName, 2, 5, 0); LiSetFatalError(0x187298, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x38B); return -470062; } if (fileInfo->minVersion < MinSDKVersion || fileInfo->minVersion > MaxSDKVersion) { auto major = fileInfo->minVersion / 10000; auto minor = (fileInfo->minVersion % 10000) / 100; auto patch = fileInfo->minVersion % 100; Loader_ReportError("*** COS requires that {} be built with at least SDK {}.{:02}.{}, it was built with SDK {}.{:02}.{}", moduleName, 2, 5, 0, major, minor, patch); LiSetFatalError(0x187298, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x38B); return -470062; } auto allocPtr = virt_ptr<void> { nullptr }; auto allocSize = uint32_t { 0 }; auto largestFree = uint32_t { 0 }; auto sectionCrcs = virt_ptr<uint32_t> { nullptr }; auto fileInfoCrc = uint32_t { 0 }; if (fileInfo->textSize) { error = LiCacheLineCorrectAllocEx(codeHeapTracking, fileInfo->textSize, fileInfo->textAlign, &allocPtr, 0, &allocSize, &largestFree, loadArgs->fileType); if (error != 0) { Loader_ReportError("***Could not allocate uncompressed text ({}) in {} heap \"{}\"; (needed {}, available {}).", fileInfo->textSize, r9 ? "shared code" : "code", moduleName, fileInfo->textSize, largestFree); goto lblError; } rpl->textBuffer = allocPtr; rpl->textBufferSize = allocSize; } error = LiCacheLineCorrectAllocEx(dataHeapTracking, sizeof(LOADED_RPL), 4, &allocPtr, 0, &allocSize, &largestFree, loadArgs->fileType); if (error != 0) { Loader_ReportError("*** Failed {} alloc (err=0x{:08X}); (needed {}, available {})", r9 ? "readheap" : "workarea", error, sizeof(LOADED_RPL), largestFree); goto lblError; } rpl = virt_cast<LOADED_RPL *>(allocPtr); std::memcpy(rpl.get(), tmpLoadedRpl.get(), sizeof(LOADED_RPL)); rpl->selfBufferSize = allocSize; loadArgs->loadedRpl = rpl; error = LiInitBufferTracking(loadArgs); if (error != 0) { goto lblError; } error = LiCacheLineCorrectAllocEx(dataHeapTracking, rpl->elfHeader.shnum * 8, 4, &allocPtr, 1, &allocSize, &largestFree, rpl->fileType); if (error != 0) { Loader_ReportError("***Allocate Error {}, Failed to allocate {} bytes for section addresses; (needed {}, available {}).", error, rpl->elfHeader.shnum * 8, rpl->sectionAddressBufferSize, largestFree); goto lblError; } rpl->sectionAddressBuffer = virt_cast<virt_addr *>(allocPtr); rpl->sectionAddressBufferSize = allocSize; error = LiCacheLineCorrectAllocEx(dataHeapTracking, rpl->elfHeader.shnum * rpl->elfHeader.shentsize, 4, &allocPtr, 1, &allocSize, &largestFree, rpl->fileType); if (error != 0) { Loader_ReportError("*** Could not allocate space for section headers in {} heap; (needed {}, available {})", r9 ? "shared readonly" : "readonly", rpl->sectionHeaderBufferSize, largestFree); goto lblError; } rpl->sectionHeaderBuffer = allocPtr; rpl->sectionHeaderBufferSize = allocSize; std::memcpy(rpl->sectionHeaderBuffer.get(), virt_cast<void *>(virt_cast<virt_addr>(fileElfHeader) + rpl->elfHeader.shoff).get(), rpl->elfHeader.shnum * rpl->elfHeader.shentsize); if (allocModuleName) { error = LiCacheLineCorrectAllocEx(globals->processCodeHeap, align_up(moduleNameLen, 4), 4, &allocPtr, 1, &allocSize, &largestFree, rpl->fileType); if (error != 0) { Loader_ReportError("*** Could not allocate space for module name; (needed {}, available {})\n", rpl->moduleNameBufferSize, largestFree); goto lblError; } rpl->moduleNameBuffer = virt_cast<char *>(allocPtr); rpl->moduleNameBufferSize = allocSize; string_copy(virt_cast<char *>(rpl->moduleNameBuffer).get(), moduleName.get(), rpl->moduleNameBufferSize); } else { rpl->moduleNameBuffer = moduleName; rpl->moduleNameBufferSize = moduleNameLen; } rpl->moduleNameLen = moduleNameLen; rpl->moduleNameBuffer[rpl->moduleNameLen] = char { 0 }; // Load SHT_RPL_CRCS shRplCrcs = getSectionHeader(rpl, rpl->elfHeader.shnum - 2); if (shRplCrcs->type != rpl::SHT_RPL_CRCS || (shRplCrcs->flags & rpl::SHF_DEFLATED)) { Loader_ReportError("***shnum-2 section type = 0x{:08X}, flags=0x{:08X}", shRplCrcs->type, shRplCrcs->flags); LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x403); error = -470081; goto lblError; } error = LiCacheLineCorrectAllocEx(globals->processCodeHeap, shRplCrcs->size, -static_cast<int32_t>(shRplCrcs->addralign), &allocPtr, 1, &allocSize, &largestFree, rpl->fileType); if (error != 0) { Loader_ReportError("*** Could not allocate space for CRCs; (needed {}, available {})", rpl->crcBufferSize, largestFree); goto lblError; } rpl->crcBuffer = allocPtr; rpl->crcBufferSize = allocSize; sectionCrcs = virt_cast<uint32_t *>(rpl->crcBuffer); std::memcpy(rpl->crcBuffer.get(), virt_cast<void *>(virt_cast<virt_addr>(fileElfHeader) + shRplCrcs->offset).get(), shRplCrcs->size); rpl->sectionAddressBuffer[rpl->elfHeader.shnum - 2] = virt_cast<virt_addr>(rpl->crcBuffer); // Load SHT_RPL_FILEINFO shRplFileInfo = getSectionHeader(rpl, rpl->elfHeader.shnum - 1); if (shRplFileInfo->type != rpl::SHT_RPL_FILEINFO || (shRplFileInfo->flags & rpl::SHF_DEFLATED)) { Loader_ReportError("***shnum-1 section type = 0x{:08X}, flags=0x{:08X}", shRplFileInfo->type, shRplFileInfo->flags); LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x41A); error = -470082; goto lblError; } rpl->fileInfoSize = shRplFileInfo->size; error = LiCacheLineCorrectAllocEx(dataHeapTracking, shRplFileInfo->size, shRplFileInfo->addralign, &allocPtr, 1, &allocSize, &largestFree, rpl->fileType); if (error != 0) { Loader_ReportError("*** Could not allocate space for file info; (needed {}, available {})", rpl->fileInfoBufferSize, largestFree); goto lblError; } rpl->fileInfoBuffer = virt_cast<rpl::RPLFileInfo_v4_2 *>(allocPtr); rpl->fileInfoBufferSize = allocSize; Loader_ReportNotice("RPL_LAYOUT:{},FILE,start,=\"{}\"", rpl->moduleNameBuffer, rpl->fileInfoBuffer); Loader_ReportNotice("RPL_LAYOUT:{},FILE,end,=\"{}\"", rpl->moduleNameBuffer, virt_cast<virt_addr>(rpl->fileInfoBuffer) + rpl->fileInfoBufferSize); std::memcpy(rpl->fileInfoBuffer.get(), virt_cast<void *>(virt_cast<virt_addr>(fileElfHeader) + shRplFileInfo->offset).get(), shRplFileInfo->size); rpl->sectionAddressBuffer[rpl->elfHeader.shnum - 1] = virt_cast<virt_addr>(rpl->fileInfoBuffer); fileInfoCrc = LiCalcCRC32(0, rpl->fileInfoBuffer, shRplFileInfo->size); if (fileInfoCrc != sectionCrcs[rpl->elfHeader.shnum - 1]) { Loader_ReportError("***FileInfo CRC failed check."); LiSetFatalError(0x18729B, loadArgs->fileType, 1, "LiLoadRPLBasics", 0x433); error = -470083; goto lblError; } rpl->fileInfoBuffer->tlsModuleIndex = int16_t { -1 }; error = LiCheckFileBounds(rpl); if (error == 0) { rpl->virtualFileBase = fileElfHeader; *outLoadedRpl = rpl; return error; } lblError: if (rpl) { if (rpl->fileInfoBuffer) { LiCacheLineCorrectFreeEx(dataHeapTracking, rpl->fileInfoBuffer, rpl->fileInfoBufferSize); } if (rpl->crcBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->crcBuffer, rpl->crcBufferSize); } if (rpl->moduleNameBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->moduleNameBuffer, rpl->moduleNameBufferSize); } if (rpl->sectionHeaderBuffer) { LiCacheLineCorrectFreeEx(dataHeapTracking, rpl->sectionHeaderBuffer, rpl->sectionHeaderBufferSize); } if (rpl->sectionAddressBuffer) { LiCacheLineCorrectFreeEx(dataHeapTracking, rpl->sectionAddressBuffer, rpl->sectionAddressBufferSize); } if (rpl->pathBuffer) { LiCacheLineCorrectFreeEx(dataHeapTracking, rpl->pathBuffer, rpl->pathBufferSize); } if (rpl->textBuffer) { LiCacheLineCorrectFreeEx(codeHeapTracking, rpl->textBuffer, rpl->textBufferSize); } LiCacheLineCorrectFreeEx(codeHeapTracking, rpl, rpl->selfBufferSize); } return error; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_basics.h ================================================ #pragma once #include "cafe/cafe_tinyheap.h" #include "cafe/kernel/cafe_kernel_processid.h" #include "ios/mcp/ios_mcp_enum.h" #include <libcpu/be2_struct.h> namespace cafe::loader { struct LOADED_RPL; namespace internal { struct LiBasicsLoadArgs { be2_val<cafe::kernel::UniqueProcessId> upid; be2_virt_ptr<LOADED_RPL> loadedRpl; be2_virt_ptr<TinyHeap> readHeapTracking; be2_val<uint32_t> pathNameLen; be2_virt_ptr<char> pathName; UNKNOWN(0x4); be2_val<ios::mcp::MCPFileType> fileType; be2_virt_ptr<void> chunkBuffer; be2_val<uint32_t> chunkBufferSize; be2_val<uint32_t> fileOffset; }; CHECK_OFFSET(LiBasicsLoadArgs, 0x00, upid); CHECK_OFFSET(LiBasicsLoadArgs, 0x04, loadedRpl); CHECK_OFFSET(LiBasicsLoadArgs, 0x08, readHeapTracking); CHECK_OFFSET(LiBasicsLoadArgs, 0x0C, pathNameLen); CHECK_OFFSET(LiBasicsLoadArgs, 0x10, pathName); CHECK_OFFSET(LiBasicsLoadArgs, 0x18, fileType); CHECK_OFFSET(LiBasicsLoadArgs, 0x1C, chunkBuffer); CHECK_OFFSET(LiBasicsLoadArgs, 0x20, chunkBufferSize); CHECK_OFFSET(LiBasicsLoadArgs, 0x24, fileOffset); CHECK_SIZE(LiBasicsLoadArgs, 0x28); int32_t LiLoadRPLBasics(virt_ptr<char> moduleName, uint32_t moduleNameLen, virt_ptr<void> chunkBuffer, virt_ptr<TinyHeap> codeHeapTracking, virt_ptr<TinyHeap> dataHeapTracking, bool allocModuleName, uint32_t r9, virt_ptr<LOADED_RPL> *outLoadedRpl, LiBasicsLoadArgs *loadArgs, uint32_t arg_C); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_bounce.cpp ================================================ #include "cafe_loader_bounce.h" #include "cafe_loader_error.h" #include "cafe_loader_iop.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_log.h" #include "cafe/cafe_stackobject.h" #include "cafe/kernel/cafe_kernel_mmu.h" #include "cafe/kernel/cafe_kernel_processid.h" #include "ios/ios_enum.h" #include "ios/fs/ios_fs_enum.h" #include "ios/mcp/ios_mcp_enum.h" #include "ios/mcp/ios_mcp_mcp_request.h" #include <libcpu/be2_struct.h> #include <libcpu/cpu_formatters.h> using namespace ios::mcp; using namespace cafe::kernel; namespace cafe::loader::internal { constexpr auto ChunkSize = 0x400000u; static bool sgFinishedLoadingBuffer = false; static MCPFileType sgFileType = MCPFileType::ProcessCode; static UniqueProcessId sgProcId = UniqueProcessId::Invalid; static uint32_t sgGotBytes = 0; static uint32_t sgTotalBytes = 0; static uint32_t sgFileOffset = 0; static uint32_t sgBufferNumber = 0; static ios::Error sgBounceError = ios::Error::OK; static std::string sgLoadName; void LiInitBuffer(bool unk) { if (unk) { sgFinishedLoadingBuffer = true; } sgFileType = MCPFileType::ProcessCode; sgFileOffset = 0u; sgBufferNumber = 0u; sgProcId = UniqueProcessId::Invalid; sgLoadName.clear(); sgTotalBytes = 0u; sgBounceError = ios::Error::OK; sgGotBytes = 0u; } ios::Error LiBounceOneChunk(std::string_view name, ios::mcp::MCPFileType fileType, kernel::UniqueProcessId upid, uint32_t *outChunkBytes, uint32_t offset, uint32_t bufferNumber, virt_ptr<void> *outChunk) { LiCheckAndHandleInterrupts(); auto bounceBufferMapping = kernel::getVirtualMemoryMap(kernel::VirtualMemoryRegion::LoaderBounceBuffer); auto bounceBufferAddr = bounceBufferMapping.vaddr; if (bufferNumber != 1) { bounceBufferAddr += ChunkSize; } sgLoadName = name; sgFileOffset = offset; sgBufferNumber = bufferNumber; sgFileType = fileType; sgProcId = upid; auto error = LiLoadAsync(name, virt_cast<void *>(bounceBufferAddr), ChunkSize, offset, fileType, getRamPartitionIdFromUniqueProcessId(upid)); sgBounceError = error; if (error < ios::Error::OK) { LiSetFatalError(0x1872A7, fileType, 0, "LiBounceOneChunk", 131); Loader_ReportError("***LiLoadAsync failed. err={}.", error); LiCheckAndHandleInterrupts(); return error; } if (outChunkBytes) { *outChunkBytes = ChunkSize; } if (outChunk) { *outChunk = virt_cast<void *>(bounceBufferAddr); } sgFinishedLoadingBuffer = (sgFinishedLoadingBuffer == 0) ? TRUE : FALSE; LiCheckAndHandleInterrupts(); return ios::Error::OK; } ios::Error LiWaitOneChunk(uint32_t *outBytesRead, std::string_view name, MCPFileType fileType) { auto bytesRead = uint32_t { 0 }; auto error = LiWaitIopCompleteWithInterrupts(&bytesRead); sgBounceError = error; if (error < ios::Error::OK) { return error; } sgGotBytes = bytesRead; sgTotalBytes += bytesRead; if (outBytesRead) { *outBytesRead = bytesRead; } sgFinishedLoadingBuffer = (sgFinishedLoadingBuffer == 0) ? TRUE : FALSE; return ios::Error::OK; } int32_t LiCleanUpBufferAfterModuleLoaded() { if (!sgLoadName[0] || sgFinishedLoadingBuffer) { return 0; } if (sgTotalBytes & (ChunkSize - 1)) { Loader_ReportError( "LiCleanUpBufferAfterModuleLoaded: {} finished loading but left load buffers in unusable state; error {} (0x{:08X}).", sgLoadName, -470105, -470105); LiSetFatalError(0x18729Bu, sgFileType, 1, "LiCleanUpBufferAfterModuleLoaded", 359); return -470105; } auto bytesRead = uint32_t { 0 }; auto error = LiWaitOneChunk(&bytesRead, sgLoadName, sgFileType); if (error) { Loader_ReportError( "LiCleanUpBufferAfterModuleLoaded: Loader incorrectly calculated that {} finished loading but got error {} (0x{:08X}).", sgLoadName, error, error); return error; } if (bytesRead) { Loader_ReportError( "LiCleanUpBufferAfterModuleLoaded: Loader incorrectly calculated that {} finished loading; error {} (0x{:08X}).", sgLoadName, -470105, -470105); LiSetFatalError(0x18729Bu, sgFileType, 1, "LiCleanUpBufferAfterModuleLoaded", 367); return -470105; } return 0; } int32_t LiRefillUpcomingBounceBuffer(virt_ptr<LOADED_RPL> rpl, int32_t bufferNumber) { auto chunkReadSize = uint32_t { 0 }; auto chunkBuffer = virt_ptr<void> { nullptr }; auto error = LiBounceOneChunk(virt_cast<char *>(rpl->pathBuffer).get(), rpl->fileType, rpl->upid, &chunkReadSize, rpl->upcomingFileOffset, bufferNumber, &chunkBuffer); rpl->chunkBuffer = chunkBuffer; if (error != ios::Error::OK) { Loader_ReportError( "***LiBounceOneChunk failed loading \"{}\" of type {} at offset 0x{:08X} err={}.", rpl->pathBuffer, rpl->fileType, rpl->upcomingFileOffset, error); } return error; } void LiCloseBufferIfError() { if (sgLoadName[0]) { auto loadName = std::string_view { sgLoadName }; if (!sgFinishedLoadingBuffer) { LiWaitOneChunk(NULL, loadName, sgFileType); } if (sgBounceError == ios::Error::OK) { while(sgGotBytes == ChunkSize) { Loader_ReportWarn("***Loader completely reading {} from offset 0x{:08X}.", loadName, sgFileOffset + ChunkSize); LiBounceOneChunk(loadName, sgFileType, sgProcId, nullptr, sgFileOffset + sgGotBytes, (sgBufferNumber == 1) ? 2 : 1, nullptr); LiWaitOneChunk(NULL, loadName, sgFileType); } } } LiInitBuffer(false); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_bounce.h ================================================ #pragma once #include "cafe/kernel/cafe_kernel_processid.h" #include "ios/ios_enum.h" #include "ios/mcp/ios_mcp_enum.h" #include <libcpu/be2_struct.h> namespace cafe::loader { struct LOADED_RPL; namespace internal { void LiInitBuffer(bool unk); ios::Error LiBounceOneChunk(std::string_view name, ios::mcp::MCPFileType fileType, kernel::UniqueProcessId upid, uint32_t *outChunkBytes, uint32_t offset, uint32_t bufferNumber, virt_ptr<void> *outChunk); ios::Error LiWaitOneChunk(uint32_t *outBytesRead, std::string_view name, ios::mcp::MCPFileType fileType); int32_t LiCleanUpBufferAfterModuleLoaded(); int32_t LiRefillUpcomingBounceBuffer(virt_ptr<LOADED_RPL> rpl, int32_t bufferNumber); void LiCloseBufferIfError(); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_elffile.cpp ================================================ #include "cafe_loader_elffile.h" #include <common/byte_swap.h> namespace cafe::loader::internal { static void ELFFILE_SwapFileHeader(virt_ptr<rpl::Header> header) { header->type = byte_swap(header->type); header->machine = byte_swap(header->machine); header->version = byte_swap(header->version); header->entry = byte_swap(header->entry); header->phoff = byte_swap(header->phoff); header->shoff = byte_swap(header->shoff); header->flags = byte_swap(header->flags); header->ehsize = byte_swap(header->ehsize); header->phentsize = byte_swap(header->phentsize); header->phnum = byte_swap(header->phnum); header->shentsize = byte_swap(header->shentsize); header->shnum = byte_swap(header->shnum); header->shstrndx = byte_swap(header->shstrndx); } static void ELFFILE_SwapSectionHeader(virt_ptr<rpl::SectionHeader> header) { header->name = byte_swap(header->name); header->type = byte_swap(header->type); header->flags = byte_swap(header->flags); header->addr = byte_swap(header->addr); header->offset = byte_swap(header->offset); header->size = byte_swap(header->size); header->link = byte_swap(header->link); header->info = byte_swap(header->info); header->addralign = byte_swap(header->addralign); header->entsize = byte_swap(header->entsize); } int ELFFILE_ValidateAndPrepareMinELF(virt_ptr<void> chunkBuffer, size_t chunkSize, virt_ptr<rpl::Header> *outHeader, virt_ptr<rpl::SectionHeader> *outSectionHeaders, uint32_t *outShEntSize, uint32_t *outPhEntSize) { const auto currentEncoding = rpl::ELFDATA2MSB; auto header = virt_cast<rpl::Header *>(chunkBuffer); if (chunkSize < 0x104) { return 0xBAD00018; } if (header->magic[0] != 0x7F || header->magic[1] != 'E' || header->magic[2] != 'L' || header->magic[3] != 'F') { return 0xBAD00019; } if (header->fileClass != rpl::ELFCLASS32) { return 0xBAD0001A; } if (header->elfVersion > rpl::EV_CURRENT) { return 0xBAD0001B; } if (header->encoding != currentEncoding) { ELFFILE_SwapFileHeader(header); } if (!header->machine) { return 0xBAD0001C; } if (header->version != 1) { return 0xBAD0001D; } auto ehsize = static_cast<uint32_t>(header->ehsize); if (ehsize) { if (header->ehsize < sizeof(rpl::Header)) { return 0xBAD0001E; } } else { ehsize = static_cast<uint32_t>(sizeof(rpl::Header)); } auto phoff = header->phoff; if (phoff && (phoff < ehsize || phoff >= chunkSize)) { return 0xBAD0001F; } auto shoff = header->shoff; if (shoff && (shoff < ehsize || shoff >= chunkSize)) { return 0xBAD00020; } if (header->shstrndx && header->shstrndx >= header->shnum) { return 0xBAD00021; } auto phentsize = header->phentsize ? static_cast<uint16_t>(header->phentsize) : static_cast<uint16_t>(32); if (header->phoff && (header->phoff + phentsize * header->phnum) > chunkSize) { return 0xBAD00022; } auto shentsize = header->shentsize ? static_cast<uint32_t>(header->shentsize) : static_cast<uint32_t>(sizeof(rpl::SectionHeader)); if (header->shoff && (header->shoff + shentsize * header->shnum) > chunkSize) { return 0xBAD00023; } if (header->encoding != currentEncoding) { for (auto i = 1u; i < header->shnum; ++i) { ELFFILE_SwapSectionHeader( virt_cast<rpl::SectionHeader *>( virt_cast<virt_addr>(chunkBuffer) + shoff + (i * shentsize))); } } for (auto i = 1u; i < header->shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>( virt_cast<virt_addr>(chunkBuffer) + shoff + (i * shentsize)); if (sectionHeader->size && sectionHeader->type != rpl::SHT_NOBITS) { if (sectionHeader->offset < ehsize) { return 0xBAD00024; } if (sectionHeader->offset >= shoff && sectionHeader->offset < (shoff + header->shnum * shentsize)) { return 0xBAD00027; } } } // TODO: loader.elf loads program headers too. *outHeader = header; *outShEntSize = shentsize; *outPhEntSize = phentsize; *outSectionHeaders = virt_cast<rpl::SectionHeader *>(virt_cast<virt_addr>(chunkBuffer) + shoff); return 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_elffile.h ================================================ #pragma once #include "cafe_loader_rpl.h" #include <libcpu/be2_struct.h> namespace cafe::loader::internal { int ELFFILE_ValidateAndPrepareMinELF(virt_ptr<void> chunkBuffer, size_t chunkSize, virt_ptr<rpl::Header> *outHeader, virt_ptr<rpl::SectionHeader> *outSectionHeaders, uint32_t *outShEntSize, uint32_t *outPhEntSize); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_entry.cpp ================================================ #include "cafe_loader_entry.h" #include "cafe_loader_error.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_ipcldriver.h" #include "cafe_loader_link.h" #include "cafe_loader_prep.h" #include "cafe_loader_setup.h" #include "cafe_loader_shared.h" #include "cafe_loader_log.h" #include "cafe_loader_query.h" #include <common/strutils.h> #include <mutex> namespace cafe::loader { static virt_ptr<kernel::Context> gpLoaderEntry_ProcContext = nullptr; static int32_t gpLoaderEntry_ProcConfig = -1; static LOADER_Code gpLoaderEntry_DispatchCode = LOADER_Code::Invalid; static bool gpLoaderEntry_LoaderIntsAllowed = false; static kernel::ProcessFlags gProcFlags = kernel::ProcessFlags::get(0); static uint32_t gProcTitleLoc = 0; static bool sLoaderInUserMode = true; static std::mutex sLoaderMutex; static int32_t LOADER_Entry(virt_ptr<LOADER_EntryParams> entryParams) { auto error = 0; if (!internal::IPCLDriver_IsInitialised()) { internal::IPCLDriver_Init(); internal::IPCLDriver_Open(); internal::LiInitIopInterface(); } switch (entryParams->dispatch.code) { case LOADER_Code::Prep: error = internal::LOADER_Prep(entryParams->procId, entryParams->dispatch.minFileInfo); break; case LOADER_Code::Setup: error = internal::LOADER_Setup(entryParams->procId, entryParams->dispatch.handle, FALSE, entryParams->dispatch.minFileInfo); break; case LOADER_Code::Purge: error = internal::LOADER_Setup(entryParams->procId, entryParams->dispatch.handle, TRUE, entryParams->dispatch.minFileInfo); break; case LOADER_Code::Link: error = internal::LOADER_Link(entryParams->procId, entryParams->dispatch.linkInfo, entryParams->dispatch.linkInfoSize, entryParams->dispatch.minFileInfo); break; case LOADER_Code::Query: error = internal::LOADER_Query(entryParams->procId, entryParams->dispatch.handle, entryParams->dispatch.minFileInfo); break; case LOADER_Code::UserGainControl: getGlobalStorage()->userHasControl = TRUE; error = 0; break; default: decaf_abort(fmt::format("Unimplemented LOADER_ENTRY code {}", static_cast<int>(entryParams->dispatch.code.value()))); } if (error) { if (auto fatalError = internal::LiGetFatalError()) { auto minFileInfo = entryParams->dispatch.minFileInfo; minFileInfo->error = error; minFileInfo->fatalErr = internal::LiGetFatalError(); minFileInfo->fatalMsgType = internal::LiGetFatalMsgType(); minFileInfo->fatalLine = internal::LiGetFatalMsgType(); minFileInfo->fatalFunction = internal::LiGetFatalFunction(); minFileInfo->fatalFunction[63] = char { 0 }; } } return error; } int32_t LoaderStart(BOOL isDispatch, virt_ptr<LOADER_EntryParams> entryParams) { std::unique_lock<std::mutex> lock { sLoaderMutex }; sLoaderInUserMode = true; if (isDispatch) { return LOADER_Entry(entryParams); } // Initialise static data internal::initialiseStaticDataHeap(); internal::initialiseIopStaticData(); internal::initialiseIpclDriverStaticData(); // Initialise globals auto kernelIpcStorage = getKernelIpcStorage(); gpLoaderEntry_ProcContext = entryParams->procContext; gpLoaderEntry_ProcConfig = entryParams->procConfig; gpLoaderEntry_DispatchCode = entryParams->dispatch.code; gpLoaderEntry_LoaderIntsAllowed = !!entryParams->interruptsAllowed; gProcFlags = kernelIpcStorage->processFlags; gProcTitleLoc = kernelIpcStorage->procTitleLoc; // Initialise loader internal::initialiseSharedHeaps(); // Clear errors kernelIpcStorage->fatalErr = 0; kernelIpcStorage->fatalMsgType = 0u; kernelIpcStorage->fatalLine = 0u; kernelIpcStorage->fatalFunction[0] = char { 0 }; kernelIpcStorage->loaderInitError = 0; internal::LiResetFatalError(); auto error = internal::LOADER_Init(kernelIpcStorage->targetProcessId, kernelIpcStorage->numCodeAreaHeapBlocks, kernelIpcStorage->maxCodeSize, kernelIpcStorage->maxDataSize, virt_addrof(kernelIpcStorage->rpxModule), virt_addrof(kernelIpcStorage->loadedModuleList), virt_addrof(kernelIpcStorage->startInfo)); if (error) { if (!internal::LiGetFatalError()) { internal::LiSetFatalError(0x1872A7u, 0, 1, "__LoaderStart", 227); } if (!internal::LiGetFatalError()) { internal::LiPanic("entry.c", 239, "***RPX failed loader but didn't generate fatal error information; err={}.", error); } kernelIpcStorage->loaderInitError = error; kernelIpcStorage->fatalLine = internal::LiGetFatalLine(); kernelIpcStorage->fatalErr = internal::LiGetFatalError(); kernelIpcStorage->fatalMsgType = internal::LiGetFatalMsgType(); string_copy(virt_addrof(kernelIpcStorage->fatalFunction).get(), kernelIpcStorage->fatalFunction.size(), internal::LiGetFatalFunction().data(), kernelIpcStorage->fatalFunction.size() - 1); kernelIpcStorage->fatalFunction[kernelIpcStorage->fatalFunction.size() - 1] = char { 0 }; } if (kernelIpcStorage->targetProcessId == kernel::UniqueProcessId::Root) { // TODO: Syscall Loader_FinishInitAndPreload } else { // TODO: Syscall Loader_ProfileEntry // TODO: Syscall Loader_ContinueStartProcess } return 0; } void lockLoader() { sLoaderMutex.lock(); } void unlockLoader() { sLoaderMutex.unlock(); } virt_ptr<LOADED_RPL> getLoadedRpx() { return getGlobalStorage()->loadedRpx; } virt_ptr<LOADED_RPL> getLoadedRplLinkedList() { return getGlobalStorage()->firstLoadedRpl; } namespace internal { uint32_t getProcTitleLoc() { return gProcTitleLoc; } kernel::ProcessFlags getProcFlags() { return gProcFlags; } } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_entry.h ================================================ #pragma once #include "cafe_loader_minfileinfo.h" #include "cafe/kernel/cafe_kernel_context.h" #include "cafe/kernel/cafe_kernel_processid.h" #include <libcpu/be2_struct.h> namespace cafe::loader { struct LOADED_RPL; enum class LOADER_Code : int32_t { Invalid = -1, Prep = 1, Setup = 2, Purge = 3, Link = 4, Query = 5, Tag = 6, UserGainControl = 7, Done = 8, GetLoaderHeapStatistics = 9, }; struct LOADER_EntryDispatch { be2_val<LOADER_Code> code; be2_val<LOADER_Handle> handle; be2_virt_ptr<LOADER_MinFileInfo> minFileInfo; be2_virt_ptr<LOADER_LinkInfo> linkInfo; be2_val<uint32_t> linkInfoSize; UNKNOWN(0x20 - 0x14); }; CHECK_OFFSET(LOADER_EntryDispatch, 0x00, code); CHECK_OFFSET(LOADER_EntryDispatch, 0x04, handle); CHECK_OFFSET(LOADER_EntryDispatch, 0x08, minFileInfo); CHECK_OFFSET(LOADER_EntryDispatch, 0x0C, linkInfo); CHECK_OFFSET(LOADER_EntryDispatch, 0x10, linkInfoSize); CHECK_SIZE(LOADER_EntryDispatch, 0x20); struct LOADER_EntryParams { be2_virt_ptr<kernel::Context> procContext; be2_val<kernel::UniqueProcessId> procId; be2_val<int32_t> procConfig; be2_virt_ptr<kernel::Context> context; be2_val<BOOL> interruptsAllowed; be2_val<uint32_t> unk0x14; be2_struct<LOADER_EntryDispatch> dispatch; }; CHECK_OFFSET(LOADER_EntryParams, 0x00, procContext); CHECK_OFFSET(LOADER_EntryParams, 0x04, procId); CHECK_OFFSET(LOADER_EntryParams, 0x08, procConfig); CHECK_OFFSET(LOADER_EntryParams, 0x0C, context); CHECK_OFFSET(LOADER_EntryParams, 0x10, interruptsAllowed); CHECK_OFFSET(LOADER_EntryParams, 0x14, unk0x14); CHECK_OFFSET(LOADER_EntryParams, 0x18, dispatch); CHECK_SIZE(LOADER_EntryParams, 0x38); int32_t LoaderStart(BOOL isEntryCall, virt_ptr<LOADER_EntryParams> entryParams); void lockLoader(); void unlockLoader(); virt_ptr<LOADED_RPL> getLoadedRpx(); virt_ptr<LOADED_RPL> getLoadedRplLinkedList(); namespace internal { uint32_t getProcTitleLoc(); kernel::ProcessFlags getProcFlags(); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_error.cpp ================================================ #include "cafe_loader_error.h" #include <cstdint> #include <string> namespace cafe::loader::internal { static int32_t gFatalErr = 0; // 0xEFE1AEB0 static uint32_t gFatalLine = 0u; // 0xEFE1AEB4 static uint32_t gFatalMsgType = 0u; // 0xEFE1AEB8 static std::string gFatalFunction; // 0xEFE1AEBC void LiSetFatalError(int32_t baseError, uint32_t fileType, uint32_t unk, std::string_view function, uint32_t line) { if (gFatalErr) { // Only one fatal error at a time! return; } gFatalErr = baseError; gFatalMsgType = fileType; gFatalFunction = function; gFatalLine = line; } int32_t LiGetFatalError() { return gFatalErr; } const std::string & LiGetFatalFunction() { return gFatalFunction; } uint32_t LiGetFatalLine() { return gFatalLine; } uint32_t LiGetFatalMsgType() { return gFatalMsgType; } void LiResetFatalError() { gFatalErr = 0; gFatalFunction.clear(); gFatalLine = 0; gFatalMsgType = 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_error.h ================================================ #pragma once #include <cstdint> #include <string_view> namespace cafe::loader { enum Error : int32_t { DifferentProcess = -470008, CheckFileBoundsFailed = -470026, ZlibMemError = -470084, ZlibVersionError = -470085, ZlibStreamError = -470086, ZlibDataError = -470087, ZlibBufError = -470088, ZlibUnknownError = -470100, }; namespace internal { void LiSetFatalError(int32_t baseError, uint32_t fileType, uint32_t unk, std::string_view function, uint32_t line); int32_t LiGetFatalError(); const std::string & LiGetFatalFunction(); uint32_t LiGetFatalLine(); uint32_t LiGetFatalMsgType(); void LiResetFatalError(); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_flush.cpp ================================================ #include "cafe_loader_flush.h" #include "cafe_loader_iop.h" namespace cafe::loader::internal { void LiSafeFlushCode(virt_addr base, uint32_t size) { } void LiFlushDataRangeNoSync(virt_addr base, uint32_t size) { } void Loader_FlushDataRangeNoSync(virt_addr addr, uint32_t size) { LiCheckAndHandleInterrupts(); while (size > 0) { auto flushSize = std::min<uint32_t>(size, 0x20000); LiFlushDataRangeNoSync(addr, size); LiCheckAndHandleInterrupts(); addr += flushSize; size -= flushSize; } } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_flush.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::loader::internal { void LiSafeFlushCode(virt_addr base, uint32_t size); void LiFlushDataRangeNoSync(virt_addr base, uint32_t size); void Loader_FlushDataRangeNoSync(virt_addr addr, uint32_t size); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_globals.cpp ================================================ #include "cafe_loader_globals.h" namespace cafe::loader { // 0xEFE00000 - 0xEFE01000 = Root RPX Name static virt_ptr<char> sLoadRpxName = virt_cast<char *>(virt_addr { 0xEFE00000 }); // 0xEFE01000 - 0xEFE02000 = Loader global variables static virt_ptr<GlobalStorage> sGlobalStorage = virt_cast<GlobalStorage *>(virt_addr { 0xEFE01000 }); // 0xEFE02000 - 0xEFE0A400 = Loader context & stack static virt_ptr<ContextStorage> sContextStorage = virt_cast<ContextStorage *>(virt_addr { 0xEFE02000 }); // 0xEFE0A400 - 0xEFE0A4C0 = Kernel <-> Loader data static virt_ptr<KernelIpcStorage> sKernelIpcStorage = virt_cast<KernelIpcStorage *>(virt_addr { 0xEFE0A400 }); void setLoadRpxName(std::string_view name) { std::memcpy(sLoadRpxName.get(), name.data(), name.size()); sLoadRpxName[name.size()] = char { 0 }; } virt_ptr<char> getLoadRpxName() { return sLoadRpxName; } virt_ptr<GlobalStorage> getGlobalStorage() { return sGlobalStorage; } virt_ptr<ContextStorage> getContextStorage() { return sContextStorage; } virt_ptr<KernelIpcStorage> getKernelIpcStorage() { return sKernelIpcStorage; } } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_globals.h ================================================ #pragma once #include "cafe/loader/cafe_loader_basics.h" #include "cafe/kernel/cafe_kernel_context.h" #include "cafe/loader/cafe_loader_entry.h" #include "cafe/loader/cafe_loader_init.h" #include "cafe/kernel/cafe_kernel_processid.h" #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::loader { // 0xEFE00000 - 0xEFE01000 = Root RPX Name struct RootRPX { be2_array<char, 0x1000> name; }; CHECK_OFFSET(RootRPX, 0x00, name); CHECK_SIZE(RootRPX, 0x1000); // 0xEFE01000 - 0xEFE02000 = Loader global variables struct GlobalStorage { be2_val<kernel::UniqueProcessId> currentUpid; be2_virt_ptr<TinyHeap> processCodeHeap; be2_val<uint32_t> processCodeHeapTrackingBlockSize; be2_val<uint32_t> numCodeAreaHeapBlocks; be2_val<uint32_t> availableCodeSize; be2_val<uint32_t> maxCodeSize; be2_val<uint32_t> maxDataSize; be2_val<uint32_t> sdaBase; be2_val<uint32_t> sda2Base; be2_val<BOOL> userHasControl; be2_virt_ptr<LOADED_RPL> loadedRpx; be2_virt_ptr<LOADED_RPL> firstLoadedRpl; be2_virt_ptr<LOADED_RPL> lastLoadedRpl; UNKNOWN(0xC); }; CHECK_OFFSET(GlobalStorage, 0x00, currentUpid); CHECK_OFFSET(GlobalStorage, 0x04, processCodeHeap); CHECK_OFFSET(GlobalStorage, 0x08, processCodeHeapTrackingBlockSize); CHECK_OFFSET(GlobalStorage, 0x0C, numCodeAreaHeapBlocks); CHECK_OFFSET(GlobalStorage, 0x10, availableCodeSize); CHECK_OFFSET(GlobalStorage, 0x14, maxCodeSize); CHECK_OFFSET(GlobalStorage, 0x18, maxDataSize); CHECK_OFFSET(GlobalStorage, 0x1C, sdaBase); CHECK_OFFSET(GlobalStorage, 0x20, sda2Base); CHECK_OFFSET(GlobalStorage, 0x24, userHasControl); CHECK_OFFSET(GlobalStorage, 0x28, loadedRpx); CHECK_OFFSET(GlobalStorage, 0x2C, firstLoadedRpl); CHECK_OFFSET(GlobalStorage, 0x30, lastLoadedRpl); CHECK_SIZE(GlobalStorage, 0x40); // 0xEFE02000 - 0xEFE0A400 = Loader context & stack struct ContextStorage { be2_array<uint8_t, 0x2400> stack0; be2_array<uint8_t, 0x2400> stack1; be2_array<uint8_t, 0x2400> stack2; be2_struct<kernel::Context> context0; UNKNOWN(0x4E0); be2_struct<kernel::Context> context1; UNKNOWN(0x4E0); be2_struct<kernel::Context> context2; UNKNOWN(0x4E0); }; CHECK_OFFSET(ContextStorage, 0x0000, stack0); CHECK_OFFSET(ContextStorage, 0x2400, stack1); CHECK_OFFSET(ContextStorage, 0x4800, stack2); CHECK_OFFSET(ContextStorage, 0x6C00, context0); CHECK_OFFSET(ContextStorage, 0x7400, context1); CHECK_OFFSET(ContextStorage, 0x7C00, context2); CHECK_SIZE(ContextStorage, 0x8400); // 0xEFE0A400 - 0xEFE0A4C0 = SharedData struct KernelIpcStorage { //! Set to 0 and never read? be2_val<uint32_t> unk0x00; be2_val<kernel::ProcessFlags> processFlags; be2_val<kernel::UniqueProcessId> callerProcessId; be2_val<kernel::UniqueProcessId> targetProcessId; be2_val<uint32_t> numCodeAreaHeapBlocks; be2_val<uint32_t> maxCodeSize; be2_val<uint32_t> maxDataSize; be2_val<uint32_t> procTitleLoc; be2_virt_ptr<loader::LOADED_RPL> rpxModule; be2_virt_ptr<loader::LOADED_RPL> loadedModuleList; be2_val<uint32_t> unk0x28; be2_val<uint32_t> fatalMsgType; be2_val<int32_t> fatalErr; //! Error returned from LOADER_Init called from loader start be2_val<int32_t> loaderInitError; be2_val<uint32_t> fatalLine; be2_array<char, 0x40> fatalFunction; be2_struct<loader::RPL_STARTINFO> startInfo; be2_struct<loader::LOADER_EntryParams> entryParams; }; CHECK_OFFSET(KernelIpcStorage, 0x00, unk0x00); CHECK_OFFSET(KernelIpcStorage, 0x04, processFlags); CHECK_OFFSET(KernelIpcStorage, 0x08, callerProcessId); CHECK_OFFSET(KernelIpcStorage, 0x0C, targetProcessId); CHECK_OFFSET(KernelIpcStorage, 0x10, numCodeAreaHeapBlocks); CHECK_OFFSET(KernelIpcStorage, 0x14, maxCodeSize); CHECK_OFFSET(KernelIpcStorage, 0x18, maxDataSize); CHECK_OFFSET(KernelIpcStorage, 0x1C, procTitleLoc); CHECK_OFFSET(KernelIpcStorage, 0x20, rpxModule); CHECK_OFFSET(KernelIpcStorage, 0x24, loadedModuleList); CHECK_OFFSET(KernelIpcStorage, 0x28, unk0x28); CHECK_OFFSET(KernelIpcStorage, 0x2C, fatalMsgType); CHECK_OFFSET(KernelIpcStorage, 0x30, fatalErr); CHECK_OFFSET(KernelIpcStorage, 0x34, loaderInitError); CHECK_OFFSET(KernelIpcStorage, 0x38, fatalLine); CHECK_OFFSET(KernelIpcStorage, 0x3C, fatalFunction); CHECK_OFFSET(KernelIpcStorage, 0x7C, startInfo); CHECK_OFFSET(KernelIpcStorage, 0xA4, entryParams); CHECK_SIZE(KernelIpcStorage, 0xDC); // 0xEFE0B000 - 0xEFE80000 = loader .rodata & .data & .bss void setLoadRpxName(std::string_view name); virt_ptr<char> getLoadRpxName(); virt_ptr<GlobalStorage> getGlobalStorage(); virt_ptr<ContextStorage> getContextStorage(); virt_ptr<KernelIpcStorage> getKernelIpcStorage(); } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_heap.cpp ================================================ #include "cafe_loader_error.h" #include "cafe_loader_flush.h" #include "cafe_loader_heap.h" #include <common/frameallocator.h> #include <common/log.h> namespace cafe::loader::internal { static FrameAllocator sStaticDataHeap; int32_t LiCacheLineCorrectAllocEx(virt_ptr<TinyHeap> heap, uint32_t textSize, int32_t textAlign, virt_ptr<void> *outPtr, uint32_t /*unused*/, uint32_t *outAllocSize, uint32_t *outLargestFree, ios::mcp::MCPFileType fileType) { auto fromEnd = false; textSize = align_up(textSize, 128); *outAllocSize = textSize; if (textAlign < 0) { textAlign = -textAlign; fromEnd = true; } if (textAlign == 0 && textAlign < 64) { textAlign = 64; } if (fromEnd) { textAlign = -textAlign; } auto tinyHeapError = TinyHeap_Alloc(heap, textSize, textAlign, outPtr); if (tinyHeapError < TinyHeapError::OK) { LiSetFatalError(0x187298, fileType, 0, "LiCacheLineCorrectAllocEx", 0x88); *outLargestFree = TinyHeap_GetLargestFree(heap); return static_cast<int32_t>(tinyHeapError); } std::memset(outPtr->get(), 0, textSize); return 0; } void LiCacheLineCorrectFreeEx(virt_ptr<TinyHeap> heap, virt_ptr<void> ptr, uint32_t size) { TinyHeap_Free(heap, ptr); LiSafeFlushCode(virt_cast<virt_addr>(ptr), size); } void initialiseStaticDataHeap() { sStaticDataHeap = FrameAllocator { virt_cast<void *>(virt_addr { 0xEFE0B000 }).get(), 0xEFE80000 - 0xEFE0B000, }; } virt_ptr<void> allocStaticData(size_t size, size_t align) { return virt_cast<void *>(cpu::translate(sStaticDataHeap.allocate(size, align))); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_heap.h ================================================ #pragma once #include "cafe/cafe_tinyheap.h" #include "ios/mcp/ios_mcp_enum.h" #include <cstdint> #include <libcpu/be2_struct.h> namespace cafe::loader::internal { int32_t LiCacheLineCorrectAllocEx(virt_ptr<TinyHeap> heap, uint32_t textSize, int32_t textAlign, virt_ptr<void> *outPtr, uint32_t unk, uint32_t *outAllocSize, uint32_t *outLargestFree, ios::mcp::MCPFileType fileType); void LiCacheLineCorrectFreeEx(virt_ptr<TinyHeap> heap, virt_ptr<void> ptr, uint32_t size); void initialiseStaticDataHeap(); virt_ptr<void> allocStaticData(size_t size, size_t align = 4u); template<typename Type> virt_ptr<Type> allocStaticData() { return virt_cast<Type *>(allocStaticData(sizeof(Type), alignof(Type))); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_init.cpp ================================================ #include "cafe_loader_basics.h" #include "cafe_loader_bounce.h" #include "cafe_loader_entry.h" #include "cafe_loader_error.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_ipcldriver.h" #include "cafe_loader_prep.h" #include "cafe_loader_setup.h" #include "cafe_loader_shared.h" #include "cafe_loader_log.h" #include "cafe_loader_minfileinfo.h" #include "cafe_loader_utils.h" #include "cafe/cafe_stackobject.h" #include <libcpu/cpu_formatters.h> namespace cafe::loader::internal { static uint32_t sDataAreaSize = 0; static uint32_t sNumDataAreaAllocations = 0; static int32_t LiLoadCoreIntoProcess(virt_ptr<RPL_STARTINFO> startInfo) { auto error = 0; auto globals = getGlobalStorage(); Loader_LogEntry(2, 1, 512, "LiLoadCoreIntoProcess"); if (!getProcFlags().disableSharedLibraries()) { auto sharedModule = findLoadedSharedModule("coreinit"); if (!sharedModule) { Loader_ReportError("***Failed to find shared coreinit on module list."); LiSetFatalError(0x18729Bu, 0, 1, "LiLoadCoreIntoProcess", 161); Loader_LogEntry(2, 1, 1024, "LiLoadCoreIntoProcess"); return -470010; } auto allocPtr = virt_ptr<void> { nullptr }; auto allocSize = uint32_t { 0 }; auto largestFree = uint32_t { 0 }; error = LiCacheLineCorrectAllocEx(globals->processCodeHeap, sizeof(LOADED_RPL), 4, &allocPtr, 1, &allocSize, &largestFree, sharedModule->fileType); if (error) { Loader_ReportError( "***Failed to allocate system coreinit RPL tracking for process; (needed {}, available {}).\n", allocSize, largestFree); Loader_LogEntry(2, 1, 1024, "LiLoadCoreIntoProcess"); return error; } auto trackingModule = virt_cast<LOADED_RPL *>(allocPtr); *trackingModule = *sharedModule; trackingModule->selfBufferSize = allocSize; trackingModule->globals = globals; // Add to global loaded module linked list trackingModule->nextLoadedRpl = nullptr; if (globals->lastLoadedRpl) { globals->lastLoadedRpl->nextLoadedRpl = trackingModule; } else { globals->firstLoadedRpl = trackingModule; } globals->lastLoadedRpl = trackingModule; startInfo->coreinit = trackingModule; } else { decaf_abort("Unimplemented LiLoadCoreIntoProcess for disableSharedLibraries"); } Loader_LogEntry(2, 1, 1024, "LiLoadCoreIntoProcess"); return error; } int32_t LOADER_Init(kernel::UniqueProcessId upid, uint32_t numCodeAreaHeapBlocks, uint32_t maxCodeSize, uint32_t maxDataSize, virt_ptr<virt_ptr<LOADED_RPL>> outLoadedRpx, virt_ptr<virt_ptr<LOADED_RPL>> outModuleList, virt_ptr<RPL_STARTINFO> startInfo) { auto trackingBlockSize = align_up(TinyHeapBlockSize * numCodeAreaHeapBlocks + TinyHeapHeaderSize, 0x40); auto globals = getGlobalStorage(); auto error = 0; auto chunkBufferSize = uint32_t { 0 }; auto chunkBuffer = virt_ptr<void> { nullptr }; auto loadRpxName = std::string_view { }; auto loadFileType = ios::mcp::MCPFileType::CafeOS; auto fileName = StackArray<char, 64> { }; auto moduleName = StackArray<char, 64> { }; auto fileNameLen = uint32_t { 0 }; auto loadArgs = LiBasicsLoadArgs { }; auto rpx = virt_ptr<LOADED_RPL> { nullptr }; auto fileInfo = virt_ptr<rpl::RPLFileInfo_v4_2> { nullptr }; auto dataArea = virt_addr { 0 }; sDataAreaSize = 0; sNumDataAreaAllocations = 0; maxCodeSize = std::min(maxCodeSize, 0x0E000000u); globals->numCodeAreaHeapBlocks = numCodeAreaHeapBlocks; globals->currentUpid = upid; globals->processCodeHeapTrackingBlockSize = trackingBlockSize; globals->maxCodeSize = maxCodeSize; globals->maxDataSize = maxDataSize; globals->availableCodeSize = maxCodeSize - trackingBlockSize; globals->userHasControl = FALSE; globals->firstLoadedRpl = nullptr; globals->lastLoadedRpl = nullptr; globals->loadedRpx = nullptr; std::memset(startInfo.get(), 0, sizeof(RPL_STARTINFO)); startInfo->dataAreaEnd = virt_addr { 0x10000000u } + maxDataSize; // Setup code heap globals->processCodeHeap = virt_cast<TinyHeap *>(virt_addr { 0x10000000 - trackingBlockSize }); auto heapError = TinyHeap_Setup(globals->processCodeHeap, globals->processCodeHeapTrackingBlockSize, virt_cast<void *>(virt_addr { 0x10000000 - globals->availableCodeSize - trackingBlockSize }), globals->availableCodeSize); if (heapError != TinyHeapError::OK) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Init", 204); Loader_ReportError("*** Process code heap setup failed.\n"); goto error; } if (!IPCLDriver_IsInitialised()) { IPCLDriver_Init(); IPCLDriver_Open(); LiInitIopInterface(); } error = LiInitSharedForProcess(startInfo); Loader_LogEntry(2, 1, 0, "LOADER_Init LoadINIT ShrForPro ret={}.", error); if (error) { Loader_ReportError("*** Process shared resource init failure."); goto error; } LiInitBuffer(true); if (upid == kernel::UniqueProcessId::Root) { loadRpxName = std::string_view { "root.rpx" }; loadFileType = ios::mcp::MCPFileType::CafeOS; } else { loadRpxName = getLoadRpxName().get(); loadFileType = ios::mcp::MCPFileType::ProcessCode; } error = LiBounceOneChunk(loadRpxName, loadFileType, upid, &chunkBufferSize, 0, 1, &chunkBuffer); Loader_LogEntry(2, 1, 0, "LOADER_Init LoadINIT BncLoad ret, pName={}, mProcId={}, &pLoaded={}, err={}.", loadRpxName, static_cast<int>(upid), chunkBuffer, error); if (error) { Loader_ReportError( "***LiBounceOneChunk failed loading \"{}\" of type {} at offset 0x{:08X} err={}.", loadRpxName, loadFileType, 0, error); goto error; } fileNameLen = std::min<uint32_t>(static_cast<uint32_t>(loadRpxName.size()), 59); std::memcpy(fileName.get(), loadRpxName.data(), fileNameLen); // Resolve module name and copy to guest stack buffer loadRpxName = LiResolveModuleName(loadRpxName); std::memcpy(moduleName.get(), loadRpxName.data(), loadRpxName.size()); moduleName[loadRpxName.size()] = char { 0 }; loadArgs.upid = upid; loadArgs.loadedRpl = nullptr; loadArgs.readHeapTracking = globals->processCodeHeap; loadArgs.pathNameLen = fileNameLen; loadArgs.pathName = fileName; loadArgs.fileType = loadFileType; loadArgs.chunkBuffer = chunkBuffer; loadArgs.chunkBufferSize = chunkBufferSize; loadArgs.fileOffset = 0u; error = LiLoadForPrep(moduleName, static_cast<uint32_t>(loadRpxName.size()), chunkBuffer, &rpx, &loadArgs, 0); if (error) { Loader_ReportError("***LiLoadForPrep() failed with err={}.", error); goto error; } globals->loadedRpx = rpx; rpx->loadStateFlags |= LoadStateFlags::LoaderStateFlag4; // Allocate data buffer dataArea = virt_addr { startInfo->dataAreaStart }; fileInfo = rpx->fileInfoBuffer; if (fileInfo->dataSize) { dataArea = align_up(dataArea, fileInfo->dataAlign); rpx->dataBuffer = virt_cast<void *>(dataArea); dataArea = align_up(dataArea + fileInfo->dataSize, 64); ++sNumDataAreaAllocations; } // Allocate load buffer if (fileInfo->loadSize != fileInfo->fileInfoPad) { dataArea = align_up(dataArea, fileInfo->loadAlign); rpx->loadBuffer = virt_cast<void *>(dataArea); dataArea = align_up(dataArea + fileInfo->loadSize - fileInfo->fileInfoPad, 64); ++sNumDataAreaAllocations; } sDataAreaSize += static_cast<uint32_t>(dataArea - startInfo->dataAreaStart); if (dataArea < virt_addr { 0x10000000 } || dataArea >= startInfo->dataAreaEnd) { Loader_ReportError( "*** Insufficient data area space in process. end @ {}, areaend @ {}", dataArea, startInfo->dataAreaEnd); LiSetFatalError(0x18729Bu, rpx->fileType, 1, "LOADER_Init", 338); error = -470019; goto error; } startInfo->dataAreaStart = dataArea; if (upid == kernel::UniqueProcessId::Root) { startInfo->systemHeapSize = 0x4000u; startInfo->stackSize = 0x4000u; } else { if (fileInfo->stackSize) { startInfo->stackSize = fileInfo->stackSize; } else { startInfo->stackSize = 0x10000u; } startInfo->systemHeapSize = std::max<uint32_t>(fileInfo->heapSize, 0x8000u); } error = LiSetupOneRPL(upid, rpx, globals->processCodeHeap, globals->processCodeHeap); if (error) { goto error; } Loader_LogEntry(2, 1, 0, "LOADER_Init LoadINIT Setup1RPL ret, mpName={}, aProcId={}, mpCodeHeapTracking={}, err={}.", rpx->moduleNameBuffer, static_cast<int>(upid), globals->processCodeHeap, 0); if ((startInfo->dataAreaEnd - startInfo->dataAreaStart) < startInfo->systemHeapSize) { Loader_ReportError("***Insufficient space for stacks and system heap in process to start it.\n"); LiSetFatalError(0x18729Bu, rpx->fileType, 1, "LOADER_Init", 375); error = -470020; goto error; } // Relocate sda base globals->sdaBase = 0u; globals->sda2Base = 0u; for (auto i = 1u; i < rpx->elfHeader.shnum; ++i) { auto sectionHeader = getSectionHeader(rpx, i); auto sectionAddress = rpx->sectionAddressBuffer[i]; if (!sectionHeader->size || !sectionAddress || (sectionHeader->flags & rpl::SHF_EXECINSTR)) { continue; } if ((fileInfo->sdaBase - 0x8000) >= sectionHeader->addr && (fileInfo->sdaBase - 0x8000) < (sectionHeader->addr + sectionHeader->size)) { globals->sdaBase = sectionAddress + fileInfo->sdaBase - sectionHeader->addr; } if ((fileInfo->sda2Base - 0x8000) >= sectionHeader->addr && (fileInfo->sda2Base - 0x8000) < (sectionHeader->addr + sectionHeader->size)) { globals->sda2Base = sectionAddress + fileInfo->sda2Base - sectionHeader->addr; } } if (!globals->sdaBase) { globals->sdaBase = virt_cast<virt_addr>(rpx->dataBuffer) + 0x8000u; } if (!globals->sda2Base) { globals->sda2Base = virt_cast<virt_addr>(rpx->loadBuffer) + 0x8000u; } startInfo->sdaBase = globals->sdaBase; startInfo->sda2Base = globals->sda2Base; error = LiLoadCoreIntoProcess(startInfo); Loader_LogEntry(2, 1, 0, "LOADER_Init LoadINIT LdCrIntoPc ret, mpName={}, aProcId={}, mpCodeHeapTracking={}, err={}.", rpx->moduleNameBuffer, static_cast<int>(upid), globals->processCodeHeap, error); if (error) { goto error; } if (!startInfo->coreinit->entryPoint) { Loader_ReportError("***Error: No main program entrypoint found."); LiSetFatalError(0x18729Bu, rpx->fileType, 1, "LOADER_Init", 432); error = -470080; goto error; } startInfo->entryPoint = startInfo->coreinit->entryPoint; startInfo->unk0x10 = 0u; *outLoadedRpx = rpx; *outModuleList = globals->firstLoadedRpl; Loader_LogEntry(2, 1, 1024, "LOADER_Init"); for (auto module = globals->firstLoadedRpl; module; module = module->nextLoadedRpl) { gLog->debug("Loaded module {}", module->moduleNameBuffer); for (auto i = 0u; i < module->elfHeader.shnum; ++i) { auto size = 0u; if (module->sectionHeaderBuffer) { size = getSectionHeader(module, i)->size; } gLog->debug(" Section {}, {} - {}", i, module->sectionAddressBuffer[i], module->sectionAddressBuffer[i] + size); } } return 0; error: globals->currentUpid = kernel::UniqueProcessId::Kernel; LiCloseBufferIfError(); Loader_LogEntry(2, 1, 1024, "LOADER_Init"); return error; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_init.h ================================================ #pragma once #include "cafe/kernel/cafe_kernel_processid.h" #include <libcpu/be2_struct.h> namespace cafe::loader { struct LOADED_RPL; struct RPL_STARTINFO { be2_val<virt_addr> entryPoint; be2_val<virt_addr> dataAreaStart; be2_val<virt_addr> sdaBase; be2_val<virt_addr> sda2Base; be2_val<uint32_t> unk0x10; be2_val<uint32_t> stackSize; be2_val<uint32_t> systemHeapSize; be2_val<uint32_t> appFlags; be2_virt_ptr<LOADED_RPL> coreinit; be2_val<virt_addr> dataAreaEnd; }; CHECK_OFFSET(RPL_STARTINFO, 0x00, entryPoint); CHECK_OFFSET(RPL_STARTINFO, 0x04, dataAreaStart); CHECK_OFFSET(RPL_STARTINFO, 0x08, sdaBase); CHECK_OFFSET(RPL_STARTINFO, 0x0C, sda2Base); CHECK_OFFSET(RPL_STARTINFO, 0x10, unk0x10); CHECK_OFFSET(RPL_STARTINFO, 0x14, stackSize); CHECK_OFFSET(RPL_STARTINFO, 0x18, systemHeapSize); CHECK_OFFSET(RPL_STARTINFO, 0x1C, appFlags); CHECK_OFFSET(RPL_STARTINFO, 0x20, coreinit); CHECK_OFFSET(RPL_STARTINFO, 0x24, dataAreaEnd); CHECK_SIZE(RPL_STARTINFO, 0x28); namespace internal { int32_t LOADER_Init(kernel::UniqueProcessId upid, uint32_t numCodeAreaHeapBlocks, uint32_t maxCodeSize, uint32_t maxDataSize, virt_ptr<virt_ptr<LOADED_RPL>> outLoadedRpx, virt_ptr<virt_ptr<LOADED_RPL>> outModuleList, virt_ptr<RPL_STARTINFO> startInfo); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_iop.cpp ================================================ #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_ipcldriver.h" #include "cafe/cafe_tinyheap.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include "cafe/kernel/cafe_kernel_ipc.h" #include "cafe/kernel/cafe_kernel_process.h" #include "ios/mcp/ios_mcp_mcp.h" #include <array> #include <cstdint> #include <common/cbool.h> #include <libcpu/be2_struct.h> #include <libcpu/cpu_control.h> using namespace ios::mcp; using namespace cafe::kernel; namespace cafe::loader::internal { constexpr auto HeapSizePerCore = 0x2000u; constexpr auto HeapAllocAlign = 0x40u; struct LiLoadReply { be2_val<BOOL> done; be2_virt_ptr<void> requestBuffer; be2_val<ios::Error> error; be2_val<BOOL> pending; }; struct StaticIopData { be2_val<ios::Handle> mcpHandle; be2_struct<LiLoadReply> loadReply; alignas(0x40) be2_array<uint8_t, HeapSizePerCore * 3> heapBufs; }; static virt_ptr<StaticIopData> sIopData = nullptr; static virt_ptr<TinyHeap> iop_percore_getheap() { return virt_cast<TinyHeap *>( virt_addrof(sIopData->heapBufs) + HeapSizePerCore * cpu::this_core::id()); } static void iop_percore_initheap() { auto heap = iop_percore_getheap(); auto data = virt_cast<void *>(virt_cast<virt_addr>(heap) + 0x430); auto alignedData = align_up(data, HeapAllocAlign); auto alignedOffset = virt_cast<virt_addr>(alignedData) - virt_cast<virt_addr>(data); TinyHeap_Setup(heap, 0x430, alignedData, static_cast<uint32_t>(HeapSizePerCore - 0x430 - alignedOffset)); } static virt_ptr<void> iop_percore_malloc(uint32_t size) { auto heap = iop_percore_getheap(); size = align_up(size, HeapAllocAlign); auto allocPtr = virt_ptr<void> { nullptr }; TinyHeap_Alloc(heap, size, HeapAllocAlign, &allocPtr); return allocPtr; } static void iop_percore_free(virt_ptr<void> buffer) { auto heap = iop_percore_getheap(); TinyHeap_Free(heap, buffer); } void LiInitIopInterface() { iop_percore_initheap(); if (sIopData->mcpHandle <= 0) { sIopData->mcpHandle = kernel::getMcpHandle(); } } static void Loader_AsyncCallback(ios::Error error, virt_ptr<void> context) { auto reply = virt_cast<LiLoadReply *>(context); if (!reply) { return; } if (reply->requestBuffer) { iop_percore_free(reply->requestBuffer); reply->requestBuffer = nullptr; } reply->error = error; reply->done = TRUE; } static ios::Error LiPollForCompletion() { virt_ptr<IPCLDriver> driver; auto error = IPCLDriver_GetInstance(&driver); if (error < ios::Error::OK) { return error; } auto request = ipckDriverLoaderPollCompletion(); if (!request) { return ios::Error::QEmpty; } return IPCLDriver_ProcessReply(driver, request); } int32_t LiCheckInterrupts() { cpu::this_core::checkInterrupts(); return 0; } void LiCheckAndHandleInterrupts() { LiCheckInterrupts(); } ios::Error LiLoadAsync(std::string_view name, virt_ptr<void> outBuffer, uint32_t outBufferSize, uint32_t pos, MCPFileType fileType, RamPartitionId rampid) { auto request = virt_cast<MCPRequestLoadFile *>(iop_percore_malloc(sizeof(MCPRequestLoadFile))); request->pos = pos; request->fileType = fileType; request->cafeProcessId = static_cast<uint32_t>(rampid); request->name = name; sIopData->loadReply.done = FALSE; sIopData->loadReply.pending = TRUE; sIopData->loadReply.requestBuffer = request; sIopData->loadReply.error = ios::Error::InvalidArg; auto error = IPCLDriver_IoctlAsync(sIopData->mcpHandle, MCPCommand::LoadFile, request, sizeof(MCPRequestLoadFile), outBuffer, outBufferSize, &Loader_AsyncCallback, virt_addrof(sIopData->loadReply)); if (error < ios::Error::OK) { iop_percore_free(request); } return error; } static ios::Error LiWaitAsyncReply(virt_ptr<LiLoadReply> reply) { while (!reply->done) { LiPollForCompletion(); } reply->done = FALSE; reply->pending = FALSE; return reply->error; } static ios::Error LiWaitAsyncReplyWithInterrupts(virt_ptr<LiLoadReply> reply) { while (!reply->done) { LiPollForCompletion(); if (!reply->done) { cpu::this_core::waitNextInterrupt(); } } reply->done = FALSE; reply->pending = FALSE; return reply->error; } ios::Error LiWaitIopComplete(uint32_t *outBytesRead) { auto error = LiWaitAsyncReply(virt_addrof(sIopData->loadReply)); if (error < 0) { *outBytesRead = 0; } else { *outBytesRead = static_cast<uint32_t>(error); error = ios::Error::OK; } return error; } ios::Error LiWaitIopCompleteWithInterrupts(uint32_t *outBytesRead) { auto error = LiWaitAsyncReplyWithInterrupts(virt_addrof(sIopData->loadReply)); if (error < 0) { *outBytesRead = 0; } else { *outBytesRead = static_cast<uint32_t>(error); error = ios::Error::OK; } return error; } void initialiseIopStaticData() { sIopData = allocStaticData<StaticIopData>(); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_iop.h ================================================ #pragma once #include "cafe/kernel/cafe_kernel_processid.h" #include "ios/ios_error.h" #include "ios/ios_ipc.h" #include "ios/mcp/ios_mcp_enum.h" namespace cafe::loader::internal { void LiInitIopInterface(); void LiCheckAndHandleInterrupts(); ios::Error LiLoadAsync(std::string_view name, virt_ptr<void> outBuffer, uint32_t outBufferSize, uint32_t pos, ios::mcp::MCPFileType fileType, kernel::RamPartitionId rampid); ios::Error LiWaitIopComplete(uint32_t *outBytesRead); ios::Error LiWaitIopCompleteWithInterrupts(uint32_t *outBytesRead); void initialiseIopStaticData(); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_ipcldriver.cpp ================================================ #include "cafe_loader_heap.h" #include "cafe_loader_ipcldriver.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include "cafe/cafe_stackobject.h" #include <libcpu/state.h> using namespace cafe::kernel; namespace cafe::loader::internal { struct StaticIpclData { be2_array<IPCLDriver, 3> drivers; be2_array<IPCKDriverRequest, IPCLBufferCount * 3> ipclResourceRequestBuffer; }; static virt_ptr<StaticIpclData> sIpclData; bool IPCLDriver_IsInitialised() { return sIpclData->drivers[cpu::this_core::id()].status != IPCLDriverStatus::Invalid; } ios::Error IPCLDriver_Init() { virt_ptr<IPCLDriver> driver; IPCLDriver_GetInstance(&driver); std::memset(driver.get(), 0, sizeof(IPCLDriver)); driver->coreId = cpu::this_core::id(); driver->ipckRequestBuffer = virt_addrof(sIpclData->ipclResourceRequestBuffer[driver->coreId]); driver->status = IPCLDriverStatus::Initialised; std::memset(driver->ipckRequestBuffer.get(), 0, sizeof(IPCKDriverRequest) * IPCLBufferCount); return ios::Error::OK; } ios::Error IPCLDriver_InitRequestParameterBlocks(virt_ptr<IPCLDriver> driver) { for (auto i = 0u; i < IPCLBufferCount; ++i) { auto ipckRequestBuffer = virt_addrof(driver->ipckRequestBuffer[i]); auto &request = driver->requests[i]; request.ipckRequestBuffer = ipckRequestBuffer; request.asyncCallback = nullptr; request.asyncCallbackData = nullptr; } return ios::Error::OK; } ios::Error IPCLDriver_Open() { virt_ptr<IPCLDriver> driver; IPCLDriver_GetInstance(&driver); if (driver->status != IPCLDriverStatus::Closed && driver->status != IPCLDriverStatus::Initialised) { return ios::Error::NotReady; } IPCLDriver_InitRequestParameterBlocks(driver); IPCLDriver_FIFOInit(virt_addrof(driver->freeFifo)); IPCLDriver_FIFOInit(virt_addrof(driver->outboundFifo)); for (auto i = 0u; i < IPCLBufferCount; ++i) { IPCLDriver_FIFOPush(virt_addrof(driver->freeFifo), virt_addrof(driver->requests[i])); } auto error = kernel::ipckDriverLoaderOpen(); if (error < ios::Error::OK) { return error; } driver->status = IPCLDriverStatus::Open; return ios::Error::OK; } ios::Error IPCLDriver_GetInstance(virt_ptr<IPCLDriver> *outDriver) { auto driver = virt_addrof(sIpclData->drivers[cpu::this_core::id()]); *outDriver = driver; if (driver->status < IPCLDriverStatus::Open) { return ios::Error::NotReady; } else { return ios::Error::OK; } } ios::Error IPCLDriver_AllocateRequestBlock(virt_ptr<IPCLDriver> driver, virt_ptr<IPCLDriverRequest> *outRequest, ios::Handle handle, ios::Command command, IPCLAsyncCallbackFn callback, virt_ptr<void> callbackContext) { virt_ptr<IPCLDriverRequest> request; auto error = IPCLDriver_FIFOPop(virt_addrof(driver->freeFifo), &request); if (error < ios::Error::OK) { driver->failedAllocateRequestBlock++; return error; } // Initialise IPCLDriverRequest request->allocated = TRUE; request->asyncCallback = callback; request->asyncCallbackData = callbackContext; // Initialise IPCKDriverRequest auto ipckRequest = request->ipckRequestBuffer; std::memset(virt_addrof(ipckRequest->request.args).get(), 0, sizeof(ipckRequest->request.args)); ipckRequest->request.command = command; ipckRequest->request.handle = handle; ipckRequest->request.flags = 0u; ipckRequest->request.clientPid = 0; ipckRequest->request.reply = ios::Error::OK; *outRequest = request; return ios::Error::OK; } ios::Error IPCLDriver_FreeRequestBlock(virt_ptr<IPCLDriver> driver, virt_ptr<IPCLDriverRequest> request) { auto error = IPCLDriver_FIFOPush(virt_addrof(driver->freeFifo), request); request->allocated = FALSE; if (error < ios::Error::OK) { driver->failedFreeRequestBlock++; } return error; } static ios::Error defensiveProcessIncomingMessagePointer(virt_ptr<IPCLDriver> driver, virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequest, virt_ptr<IPCLDriverRequest> *outIpclRequest) { if (ipckRequest < driver->ipckRequestBuffer) { return ios::Error::Invalid; } auto index = ipckRequest - driver->ipckRequestBuffer; if (index >= IPCLBufferCount) { return ios::Error::Invalid; } if (!driver->requests[index].allocated) { driver->invalidReplyMessagePointerNotAlloc++; return ios::Error::Invalid; } *outIpclRequest = virt_addrof(driver->requests[index]); return ios::Error::OK; } static void ipclProcessReply(virt_ptr<IPCLDriver> driver, virt_ptr<IPCLDriverRequest> request) { driver->repliesReceived++; auto ipckRequest = request->ipckRequestBuffer; switch (ipckRequest->request.command) { case ios::Command::Open: if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosOpenAsyncRequestFail++; } else { driver->iosOpenAsyncRequestSuccess++; } } break; case ios::Command::Close: if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosCloseAsyncRequestFail++; } else { driver->iosCloseAsyncRequestSuccess++; } } break; case ios::Command::Read: decaf_check(ipckRequest->buffer1 || !ipckRequest->request.args.read.length); if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosReadAsyncRequestFail++; } else { driver->iosReadAsyncRequestSuccess++; } } break; case ios::Command::Write: decaf_check(ipckRequest->buffer1 || !ipckRequest->request.args.write.length); if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosWriteAsyncRequestFail++; } else { driver->iosWriteAsyncRequestSuccess++; } } break; case ios::Command::Seek: if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosSeekAsyncRequestFail++; } else { driver->iosSeekAsyncRequestSuccess++; } } break; case ios::Command::Ioctl: decaf_check(ipckRequest->buffer1 || !ipckRequest->request.args.ioctl.inputLength); decaf_check(ipckRequest->buffer2 || !ipckRequest->request.args.ioctl.outputLength); if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosIoctlAsyncRequestFail++; } else { driver->iosIoctlAsyncRequestSuccess++; } } break; case ios::Command::Ioctlv: decaf_check(ipckRequest->buffer1 || (ipckRequest->request.args.ioctlv.numVecIn + ipckRequest->request.args.ioctlv.numVecOut) == 0); if (request->asyncCallback) { if (ipckRequest->request.reply < ios::Error::OK) { driver->iosIoctlvAsyncRequestFail++; } else { driver->iosIoctlvAsyncRequestSuccess++; } } break; default: driver->invalidReplyCommand++; } } ios::Error IPCLDriver_ProcessReply(virt_ptr<IPCLDriver> driver, virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequest) { auto request = virt_ptr<IPCLDriverRequest> { nullptr }; if (driver->status < IPCLDriverStatus::Open) { driver->unexpectedReplyInterrupt++; return ios::Error::Invalid; } auto error = defensiveProcessIncomingMessagePointer(driver, ipckRequest, &request); if (error < ios::Error::OK) { driver->invalidReplyMessagePointer++; return error; } ipclProcessReply(driver, request); if (request->asyncCallback) { request->asyncCallback(ipckRequest->request.reply, request->asyncCallbackData); IPCLDriver_FreeRequestBlock(driver, request); driver->asyncTransactionsCompleted++; } return error; } ios::Error IPCLDriver_ProcessIOSIoctlRequest(virt_ptr<IPCLDriver> driver, virt_ptr<IPCLDriverRequest> request, uint32_t command, virt_ptr<const void> inputBuffer, uint32_t inputLength, virt_ptr<void> outputBuffer, uint32_t outputLength) { auto ipckRequest = request->ipckRequestBuffer; ipckRequest->request.args.ioctl.request = command; ipckRequest->request.args.ioctl.inputBuffer = nullptr; ipckRequest->request.args.ioctl.inputLength = inputLength; ipckRequest->request.args.ioctl.outputBuffer = nullptr; ipckRequest->request.args.ioctl.outputLength = outputLength; ipckRequest->buffer1 = inputBuffer; ipckRequest->buffer2 = outputBuffer; return ios::Error::OK; } static ios::Error sendFIFOToKernel(virt_ptr<IPCLDriver> driver) { auto error = ios::Error::OK; auto poppedRequest = virt_ptr<IPCLDriverRequest> { nullptr }; if (driver->status != IPCLDriverStatus::Open) { return error; } while (error == ios::Error::OK) { error = IPCLDriver_PeekFIFO(virt_addrof(driver->outboundFifo), virt_addrof(driver->currentSendTransaction)); if (error < ios::Error::OK) { break; } driver->status = IPCLDriverStatus::Submitting; error = ipckDriverLoaderSubmitRequest(driver->currentSendTransaction->ipckRequestBuffer); if (error == ios::Error::OK) { IPCLDriver_FIFOPop(virt_addrof(driver->outboundFifo), &poppedRequest); decaf_check(poppedRequest == driver->currentSendTransaction); } driver->status = IPCLDriverStatus::Open; } return error; } ios::Error IPCLDriver_SubmitRequestBlock(virt_ptr<IPCLDriver> driver, virt_ptr<IPCLDriverRequest> request) { // Flush out any pending requests sendFIFOToKernel(driver); auto error = IPCLDriver_FIFOPush(virt_addrof(driver->outboundFifo), request); if (error < ios::Error::OK) { driver->failedRequestSubmitOutboundFIFOFull++; // Try flush again sendFIFOToKernel(driver); } else { driver->requestsSubmitted++; sendFIFOToKernel(driver); error = ios::Error::OK; } return error; } ios::Error IPCLDriver_IoctlAsync(ios::Handle handle, uint32_t command, virt_ptr<const void> inputBuffer, uint32_t inputLength, virt_ptr<void> outputBuffer, uint32_t outputLength, IPCLAsyncCallbackFn callback, virt_ptr<void> callbackContext) { if (!inputBuffer && inputLength > 0) { return ios::Error::InvalidArg; } if (!outputBuffer && outputLength > 0) { return ios::Error::InvalidArg; } auto driver = virt_ptr<IPCLDriver> { nullptr }; auto error = IPCLDriver_GetInstance(&driver); if (error < ios::Error::OK) { return error; } auto request = virt_ptr<IPCLDriverRequest> { nullptr }; error = IPCLDriver_AllocateRequestBlock(driver, &request, handle, ios::Command::Ioctl, callback, callbackContext); if (error < ios::Error::OK) { return error; } error = IPCLDriver_ProcessIOSIoctlRequest(driver, request, command, inputBuffer, inputLength, outputBuffer, outputLength); if (error >= ios::Error::OK) { error = IPCLDriver_SubmitRequestBlock(driver, request); } if (error < ios::Error::OK) { IPCLDriver_FreeRequestBlock(driver, request); driver->iosIoctlAsyncRequestSubmitFail++; } else { driver->iosIoctlAsyncRequestSubmitSuccess++; } return error; } void initialiseIpclDriverStaticData() { sIpclData = allocStaticData<StaticIpclData>(); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_ipcldriver.h ================================================ #pragma once #include "cafe_loader_ipcldriverfifo.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include "ios/ios_enum.h" #include "ios/ios_ipc.h" #include <cstdint> #include <common/cbool.h> #include <functional> #include <libcpu/be2_struct.h> namespace cafe::loader::internal { #ifndef DECAF_LOADER_LLE using IPCLAsyncCallbackFn = void(*)(ios::Error, virt_ptr<void>); #endif #pragma pack(push, 1) constexpr auto IPCLBufferCount = 0x4u; enum class IPCLDriverStatus : uint32_t { Invalid = 0, Closed = 1, Initialised = 2, Open = 3, Submitting = 4, }; struct IPCLDriverRequest { be2_val<BOOL> allocated; #ifdef DECAF_LOADER_LLE be2_val<uint32_t> asyncCallback; be2_virt_ptr<void> asyncCallbackData; UNKNOWN(4); #else IPCLAsyncCallbackFn asyncCallback; be2_virt_ptr<void> asyncCallbackData; #endif be2_virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequestBuffer; }; CHECK_OFFSET(IPCLDriverRequest, 0x00, allocated); CHECK_OFFSET(IPCLDriverRequest, 0x04, asyncCallback); #ifdef DECAF_LOADER_LLE CHECK_OFFSET(IPCLDriverRequest, 0x08, asyncCallbackData); #endif CHECK_OFFSET(IPCLDriverRequest, 0x10, ipckRequestBuffer); CHECK_SIZE(IPCLDriverRequest, 0x14); struct IPCLDriver { be2_val<IPCLDriverStatus> status; UNKNOWN(0x4); be2_val<uint32_t> coreId; be2_virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequestBuffer; be2_virt_ptr<IPCLDriverRequest> currentSendTransaction; be2_val<uint32_t> iosOpenRequestFail; be2_val<uint32_t> iosOpenRequestSuccess; be2_val<uint32_t> iosOpenAsyncRequestSubmitFail; be2_val<uint32_t> iosOpenAsyncRequestSubmitSuccess; be2_val<uint32_t> iosOpenAsyncRequestFail; be2_val<uint32_t> iosOpenAsyncRequestSuccess; be2_val<uint32_t> iosCloseRequestFail; be2_val<uint32_t> iosCloseRequestSuccess; be2_val<uint32_t> iosCloseAsyncRequestSubmitFail; be2_val<uint32_t> iosCloseAsyncRequestSubmitSuccess; be2_val<uint32_t> iosCloseAsyncRequestFail; be2_val<uint32_t> iosCloseAsyncRequestSuccess; be2_val<uint32_t> iosReadRequestFail; be2_val<uint32_t> iosReadRequestSuccess; be2_val<uint32_t> iosReadAsyncRequestSubmitFail; be2_val<uint32_t> iosReadAsyncRequestSubmitSuccess; be2_val<uint32_t> iosReadAsyncRequestFail; be2_val<uint32_t> iosReadAsyncRequestSuccess; be2_val<uint32_t> iosWriteRequestFail; be2_val<uint32_t> iosWriteRequestSuccess; be2_val<uint32_t> iosWriteAsyncRequestSubmitFail; be2_val<uint32_t> iosWriteAsyncRequestSubmitSuccess; be2_val<uint32_t> iosWriteAsyncRequestFail; be2_val<uint32_t> iosWriteAsyncRequestSuccess; be2_val<uint32_t> iosSeekRequestFail; be2_val<uint32_t> iosSeekRequestSuccess; be2_val<uint32_t> iosSeekAsyncRequestSubmitFail; be2_val<uint32_t> iosSeekAsyncRequestSubmitSuccess; be2_val<uint32_t> iosSeekAsyncRequestFail; be2_val<uint32_t> iosSeekAsyncRequestSuccess; be2_val<uint32_t> iosIoctlRequestFail; be2_val<uint32_t> iosIoctlRequestSuccess; be2_val<uint32_t> iosIoctlAsyncRequestSubmitFail; be2_val<uint32_t> iosIoctlAsyncRequestSubmitSuccess; be2_val<uint32_t> iosIoctlAsyncRequestFail; be2_val<uint32_t> iosIoctlAsyncRequestSuccess; be2_val<uint32_t> iosIoctlvRequestFail; be2_val<uint32_t> iosIoctlvRequestSuccess; be2_val<uint32_t> iosIoctlvAsyncRequestSubmitFail; be2_val<uint32_t> iosIoctlvAsyncRequestSubmitSuccess; be2_val<uint32_t> iosIoctlvAsyncRequestFail; be2_val<uint32_t> iosIoctlvAsyncRequestSuccess; be2_val<uint32_t> requestsProcessed; be2_val<uint32_t> requestsSubmitted; be2_val<uint32_t> repliesReceived; be2_val<uint32_t> asyncTransactionsCompleted; UNKNOWN(4); be2_val<uint32_t> syncTransactionsCompleted; be2_val<uint32_t> invalidReplyAddress; be2_val<uint32_t> unexpectedReplyInterrupt; be2_val<uint32_t> unexpectedAckInterrupt; be2_val<uint32_t> invalidReplyMessagePointer; be2_val<uint32_t> invalidReplyMessagePointerNotAlloc; be2_val<uint32_t> invalidReplyCommand; be2_val<uint32_t> failedAllocateRequestBlock; be2_val<uint32_t> failedFreeRequestBlock; be2_val<uint32_t> failedRequestSubmitOutboundFIFOFull; be2_struct<IPCLDriverFIFO<IPCLBufferCount>> freeFifo; be2_struct<IPCLDriverFIFO<IPCLBufferCount>> outboundFifo; be2_array<IPCLDriverRequest, IPCLBufferCount> requests; }; CHECK_OFFSET(IPCLDriver, 0x00, status); CHECK_OFFSET(IPCLDriver, 0x08, coreId); CHECK_OFFSET(IPCLDriver, 0x0C, ipckRequestBuffer); CHECK_OFFSET(IPCLDriver, 0x10, currentSendTransaction); CHECK_OFFSET(IPCLDriver, 0x14, iosOpenRequestFail); CHECK_OFFSET(IPCLDriver, 0x18, iosOpenRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x1C, iosOpenAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0x20, iosOpenAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0x24, iosOpenAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0x28, iosOpenAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x2C, iosCloseRequestFail); CHECK_OFFSET(IPCLDriver, 0x30, iosCloseRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x34, iosCloseAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0x38, iosCloseAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0x3C, iosCloseAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0x40, iosCloseAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x44, iosReadRequestFail); CHECK_OFFSET(IPCLDriver, 0x48, iosReadRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x4C, iosReadAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0x50, iosReadAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0x54, iosReadAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0x58, iosReadAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x5C, iosWriteRequestFail); CHECK_OFFSET(IPCLDriver, 0x60, iosWriteRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x64, iosWriteAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0x68, iosWriteAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0x6C, iosWriteAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0x70, iosWriteAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x74, iosSeekRequestFail); CHECK_OFFSET(IPCLDriver, 0x78, iosSeekRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x7C, iosSeekAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0x80, iosSeekAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0x84, iosSeekAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0x88, iosSeekAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x8C, iosIoctlRequestFail); CHECK_OFFSET(IPCLDriver, 0x90, iosIoctlRequestSuccess); CHECK_OFFSET(IPCLDriver, 0x94, iosIoctlAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0x98, iosIoctlAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0x9C, iosIoctlAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0xA0, iosIoctlAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0xA4, iosIoctlvRequestFail); CHECK_OFFSET(IPCLDriver, 0xA8, iosIoctlvRequestSuccess); CHECK_OFFSET(IPCLDriver, 0xAC, iosIoctlvAsyncRequestSubmitFail); CHECK_OFFSET(IPCLDriver, 0xB0, iosIoctlvAsyncRequestSubmitSuccess); CHECK_OFFSET(IPCLDriver, 0xB4, iosIoctlvAsyncRequestFail); CHECK_OFFSET(IPCLDriver, 0xB8, iosIoctlvAsyncRequestSuccess); CHECK_OFFSET(IPCLDriver, 0xBC, requestsProcessed); CHECK_OFFSET(IPCLDriver, 0xC0, requestsSubmitted); CHECK_OFFSET(IPCLDriver, 0xC4, repliesReceived); CHECK_OFFSET(IPCLDriver, 0xC8, asyncTransactionsCompleted); CHECK_OFFSET(IPCLDriver, 0xD0, syncTransactionsCompleted); CHECK_OFFSET(IPCLDriver, 0xD4, invalidReplyAddress); CHECK_OFFSET(IPCLDriver, 0xD8, unexpectedReplyInterrupt); CHECK_OFFSET(IPCLDriver, 0xDC, unexpectedAckInterrupt); CHECK_OFFSET(IPCLDriver, 0xE0, invalidReplyMessagePointer); CHECK_OFFSET(IPCLDriver, 0xE4, invalidReplyMessagePointerNotAlloc); CHECK_OFFSET(IPCLDriver, 0xE8, invalidReplyCommand); CHECK_OFFSET(IPCLDriver, 0xEC, failedAllocateRequestBlock); CHECK_OFFSET(IPCLDriver, 0xF0, failedFreeRequestBlock); CHECK_OFFSET(IPCLDriver, 0xF4, failedRequestSubmitOutboundFIFOFull); CHECK_OFFSET(IPCLDriver, 0xF8, freeFifo); CHECK_OFFSET(IPCLDriver, 0x118, outboundFifo); CHECK_OFFSET(IPCLDriver, 0x138, requests); CHECK_SIZE(IPCLDriver, 0x188); #pragma pack(pop) bool IPCLDriver_IsInitialised(); ios::Error IPCLDriver_Init(); ios::Error IPCLDriver_Open(); ios::Error IPCLDriver_GetInstance(virt_ptr<IPCLDriver> *outDriver); ios::Error IPCLDriver_ProcessReply(virt_ptr<IPCLDriver> driver, virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequest); ios::Error IPCLDriver_IoctlAsync(ios::Handle handle, uint32_t command, virt_ptr<const void> inputBuffer, uint32_t inputLength, virt_ptr<void> outputBuffer, uint32_t outputLength, IPCLAsyncCallbackFn callback, virt_ptr<void> callbackContext); void initialiseIpclDriverStaticData(); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_ipcldriverfifo.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/ios_ipc.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include <cstdint> #include <common/cbool.h> #include <functional> #include <libcpu/be2_struct.h> namespace cafe::loader::internal { #pragma pack(push, 1) struct IPCLDriverRequest; /** * FIFO queue for IPCLDriverRequests. * * Functions similar to a ring buffer. */ template<size_t MaxSize> struct IPCLDriverFIFO { //! The current item index to push to be2_val<int32_t> pushIndex; //! The current item index to pop from be2_val<int32_t> popIndex; //! The number of items in the queue be2_val<int32_t> count; //! Tracks the highest amount of items there has been in the queue be2_val<int32_t> maxCount; //! Items in the queue be2_array<virt_ptr<IPCLDriverRequest>, MaxSize> requests; }; CHECK_OFFSET(IPCLDriverFIFO<4>, 0x00, pushIndex); CHECK_OFFSET(IPCLDriverFIFO<4>, 0x04, popIndex); CHECK_OFFSET(IPCLDriverFIFO<4>, 0x08, count); CHECK_OFFSET(IPCLDriverFIFO<4>, 0x0C, maxCount); CHECK_OFFSET(IPCLDriverFIFO<4>, 0x10, requests); CHECK_SIZE(IPCLDriverFIFO<4>, 0x20); #pragma pack(pop) /** * Initialise a IPCLDriverFIFO structure. */ template<size_t MaxSize> inline void IPCLDriver_FIFOInit(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo) { fifo->pushIndex = 0; fifo->popIndex = -1; fifo->count = 0; fifo->requests.fill(nullptr); } /** * Push a request into an IPCLDriverFIFO structure * * \retval ios::Error::OK * Success. * * \retval ios::Error::QFull * There was no free space in the queue to push the request. */ template<size_t MaxSize> inline ios::Error IPCLDriver_FIFOPush(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo, virt_ptr<IPCLDriverRequest> request) { if (fifo->pushIndex == fifo->popIndex) { return ios::Error::QFull; } fifo->requests[fifo->pushIndex] = request; if (fifo->popIndex == -1) { fifo->popIndex = fifo->pushIndex; } fifo->count += 1; fifo->pushIndex = static_cast<int32_t>((fifo->pushIndex + 1) % MaxSize); if (fifo->count > fifo->maxCount) { fifo->maxCount = fifo->count; } return ios::Error::OK; } /** * Pop a request into an IPCLDriverFIFO structure. * * \retval ios::Error::OK * Success. * * \retval ios::Error::QEmpty * There was no requests to pop from the queue. */ template<size_t MaxSize> inline ios::Error IPCLDriver_FIFOPop(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo, virt_ptr<IPCLDriverRequest> *outRequest) { if (fifo->popIndex == -1) { return ios::Error::QEmpty; } auto request = fifo->requests[fifo->popIndex]; fifo->count -= 1; if (fifo->count == 0) { fifo->popIndex = -1; } else { fifo->popIndex = static_cast<int32_t>((fifo->popIndex + 1) % MaxSize); } *outRequest = request; return ios::Error::OK; } /** * Peek the next request which would be popped from a IPCLDriverFIFO structure. * * \retval ios::Error::OK * Success. * * \retval ios::Error::QEmpty * There was no requests to pop from the queue. */ template<size_t MaxSize> ios::Error IPCLDriver_PeekFIFO(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo, virt_ptr<virt_ptr<IPCLDriverRequest>> outRequest) { if (fifo->popIndex == -1) { return ios::Error::QEmpty; } *outRequest = fifo->requests[fifo->popIndex]; return ios::Error::OK; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_link.cpp ================================================ #include "cafe_loader_error.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_link.h" #include "cafe_loader_log.h" #include "cafe_loader_purge.h" #include "cafe_loader_reloc.h" #include "cafe_loader_query.h" #include "cafe_loader_utils.h" #include <algorithm> #include <cctype> #include <libcpu/cpu_formatters.h> namespace cafe::loader::internal { static virt_ptr<LOADED_RPL> LiFindRPLByName(virt_ptr<char> name) { char buffer[64]; auto resolvedName = LiResolveModuleName(name.get()); if (resolvedName.size() >= 64) { resolvedName = resolvedName.substr(0, 63); } std::transform(resolvedName.begin(), resolvedName.end(), buffer, [](char x) { return static_cast<char>(::tolower(x)); }); buffer[resolvedName.size()] = 0; resolvedName = buffer; auto globals = getGlobalStorage(); for (auto module = globals->firstLoadedRpl; module; module = module->nextLoadedRpl) { if (module->moduleNameLen != resolvedName.size()) { continue; } if (resolvedName.compare(module->moduleNameBuffer.get()) == 0) { return module; } } return nullptr; } static void sReportCodeHeap(virt_ptr<GlobalStorage> globals, const char *msg) { } static int32_t sCheckOne(virt_ptr<LOADED_RPL> module) { module->loadStateFlags |= LoaderStateFlag2; // Get the section header string table if (!module->elfHeader.shstrndx) { Loader_ReportError( "*** Error: Could not get section string table index for \"{}\".", module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "sCheckOne", 73); return -470071; } auto shStrAddr = module->sectionAddressBuffer[module->elfHeader.shstrndx]; if (!shStrAddr) { Loader_ReportError( "*** Error: Could not get section string table for \"{}\".", module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "sCheckOne", 65); return -470072; } for (auto i = 0u; i < module->elfHeader.shnum; ++i) { auto sectionHeader = getSectionHeader(module, i); if (sectionHeader->type != rpl::SHT_RPL_IMPORTS) { continue; } auto name = virt_cast<char *>(shStrAddr + sectionHeader->name) + 9; auto rpl = LiFindRPLByName(name); if (!rpl) { Loader_ReportError("*** Imp Sec num {} @ 0x{}", i, sectionHeader); Loader_ReportError("*** Imp Name 0x{:02X} 0x{:02X} 0x{:02X} 0x{:02X}", name[0], name[1], name[2], name[3]); Loader_ReportError( "*** Error. Could not find module \"{}\" during linking of \"{}\"!", name, module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "sCheckOne", 99); return -470021; } if (rpl->loadStateFlags & LoaderStateFlag2) { rpl->loadStateFlags |= LoaderStateFlag8; module->loadStateFlags |= LoaderStateFlag8; } if (!rpl->entryPoint && !(rpl->loadStateFlags & LoaderStateFlag8)) { auto error = sCheckOne(rpl); if (error) { return error; } } } module->loadStateFlags &= ~LoaderStateFlag2; return 0; } static int32_t sCheckCircular(uint32_t numModules, virt_ptr<virt_ptr<LOADED_RPL>> modules, uint32_t checkIndex) { for (auto i = 0u; i < numModules; ++i) { modules[i]->loadStateFlags &= ~LoaderStateFlag2; } auto result = sCheckOne(modules[checkIndex]); for (auto i = 0u; i < numModules; ++i) { modules[i]->loadStateFlags &= ~LoaderStateFlag2; } return result; } static int32_t sValidateLinkData(virt_ptr<GlobalStorage> globals, virt_ptr<LOADER_LinkInfo> linkInfo, uint32_t linkInfoSize, virt_ptr<virt_ptr<LOADED_RPL>> *outRplPointers, uint32_t *outRplPointersAllocSize) { if (auto error = LiValidateAddress(linkInfo, linkInfoSize, 3, -470022, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "link data")) { LiSetFatalError(0x18729Bu, 0, 1, "sValidateLinkData", 165); return error; } if (linkInfoSize < sizeof(LOADER_LinkInfo)) { Loader_ReportError("*** invalid link data size."); LiSetFatalError(0x18729Bu, 0, 1, "sValidateLinkData", 174); return -470020; } if (linkInfo->size != linkInfoSize) { Loader_ReportError("*** incorrect link data size."); LiSetFatalError(0x18729Bu, 0, 1, "sValidateLinkData", 183); return -470020; } if (!linkInfo->numModules) { Loader_ReportError("*** incorrect # of modules being linked."); LiSetFatalError(0x18729Bu, 0, 1, "sValidateLinkData", 191); return -470022; } if (sizeof(LOADER_LinkModule) * linkInfo->numModules + 8 != linkInfo->size) { Loader_ReportError("*** link data size does not match calculation."); LiSetFatalError(0x18729Bu, 0, 1, "sValidateLinkData", 203); return -470023; } auto allocPtr = virt_ptr<void> { nullptr }; auto allocSize = uint32_t { 0 }; auto largestFree = uint32_t { 0 }; if (auto error = LiCacheLineCorrectAllocEx(globals->processCodeHeap, 4 * linkInfo->numModules, 4, &allocPtr, 1, &allocSize, &largestFree, ios::mcp::MCPFileType::ProcessCode)) { Loader_ReportError( "*** memory allocation failed {} bytes, for list of LOADED_RPL pointers mNumModules = {}; (needed {}, available {}).", 4 * linkInfo->numModules, linkInfo->numModules, allocSize, largestFree); LiSetFatalError(0x187298u, 0, 0, "sValidateLinkData", 214); return -470021; } // Generate the rpl pointer list auto rplPointers = virt_cast<virt_ptr<LOADED_RPL> *>(allocPtr); for (auto i = 0u; i < linkInfo->numModules; ++i) { auto rpl = virt_ptr<LOADED_RPL> { nullptr }; if (!linkInfo->modules[i].loaderHandle) { rpl = globals->loadedRpx; } else { rpl = getModule(linkInfo->modules[i].loaderHandle); if (!rpl) { Loader_ReportError("*** Module with base {} not found in attempt to link.", linkInfo->modules[i].loaderHandle); LiSetFatalError(0x18729Bu, 0, 1, "sValidateLinkData", 239); LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize); return -470022; } } rplPointers[i] = rpl; if (rpl->loadStateFlags & LoaderStateFlags_Unk0x20000000) { continue; } if (!(rpl->loadStateFlags & LoaderSetup)) { Loader_ReportError("*** Module with base {} has not been set up.", linkInfo->modules[i].loaderHandle); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sValidateLinkData", 251); LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize); return -470023; } if (rpl->entryPoint) { Loader_ReportError("*** Module with base 0x{:08X} has already been linked.", linkInfo->modules[i].loaderHandle); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sValidateLinkData", 259); LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize); return - 470024; } } for (auto i = 0u; i < linkInfo->numModules; ++i) { if (rplPointers[i]->loadStateFlags & LoaderStateFlags_Unk0x20000000) { continue; } if (auto error = sCheckCircular(linkInfo->numModules, rplPointers, i)) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize); return error; } } *outRplPointers = rplPointers; *outRplPointersAllocSize = allocSize; return 0; } static void sSetLinkOutput(virt_ptr<LOADER_LinkInfo> linkInfo, virt_ptr<virt_ptr<LOADED_RPL>> linkModules) { for (auto i = 0u; i < linkInfo->numModules; ++i) { auto &linkOutput = linkInfo->modules[i]; auto &linkModule = linkModules[i]; if (!linkModule->entryPoint) { Loader_Panic(0x130008, "*** Linker trying to return successful output when a module ({}) is not linked!", linkModule->moduleNameBuffer); } linkOutput.entryPoint = linkModule->entryPoint; linkOutput.textAddr = linkModule->textAddr; linkOutput.textOffset = linkModule->textOffset; linkOutput.textSize = linkModule->textSize; linkOutput.dataAddr = linkModule->dataAddr; linkOutput.dataOffset = linkModule->dataOffset; linkOutput.dataSize = linkModule->dataSize; linkOutput.loadAddr = linkModule->loadAddr; linkOutput.loadOffset = linkModule->loadOffset; linkOutput.loadSize = linkModule->loadSize; } } int32_t LOADER_Link(kernel::UniqueProcessId upid, virt_ptr<LOADER_LinkInfo> linkInfo, uint32_t linkInfoSize, virt_ptr<LOADER_MinFileInfo> minFileInfo) { auto error = int32_t { 0 }; auto globals = getGlobalStorage(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Link", 731); return Error::DifferentProcess; } auto linkModules = virt_ptr<virt_ptr<LOADED_RPL>> { nullptr }; auto linkModulesAllocSize = uint32_t { 0 }; error = sValidateLinkData(globals, linkInfo, linkInfoSize, &linkModules, &linkModulesAllocSize); if (error) { Loader_ReportError("*** Link Data not valid."); sReportCodeHeap(globals, "link done"); return error; } error = LiValidateMinFileInfo(minFileInfo, "LOADER_Link"); if (error) { sReportCodeHeap(globals, "link done"); return error; } auto numUnlinkedModules = 0u; for (auto i = 0u; i < linkInfo->numModules; ++i) { if (!(linkModules[i]->loadStateFlags & LoaderStateFlags_Unk0x20000000)) { ++numUnlinkedModules; } } if (!numUnlinkedModules) { sSetLinkOutput(linkInfo, linkModules); LiCacheLineCorrectFreeEx(globals->processCodeHeap, linkModules, linkModulesAllocSize); sReportCodeHeap(globals, "link done"); return 0; } auto allocSize = uint32_t { 0 }; auto largestFree = uint32_t { 0 }; auto allocPtr = virt_ptr<void> { nullptr }; error = LiCacheLineCorrectAllocEx(globals->processCodeHeap, 4 * numUnlinkedModules, 4, &allocPtr, 1, &allocSize, &largestFree, ios::mcp::MCPFileType::ProcessCode); if (error) { Loader_ReportError( "*** memory allocation failed {} bytes, for list of LOADED_RPL pointers actNumLink = {}; (needed {}, available {}).", 4 * numUnlinkedModules, numUnlinkedModules, allocSize, largestFree); LiCacheLineCorrectFreeEx(globals->processCodeHeap, linkModules, linkModulesAllocSize); LiSetFatalError(0x187298u, 0, 0, "LOADER_Link", 773); return -470021; } auto unlinkedModules = virt_cast<virt_ptr<LOADED_RPL> *>(allocPtr); auto unlinkedModulesSize = allocSize; numUnlinkedModules = 0u; for (auto i = 0u; i < linkInfo->numModules; ++i) { if (!(linkModules[i]->loadStateFlags & LoaderStateFlags_Unk0x20000000)) { unlinkedModules[numUnlinkedModules] = linkModules[i]; unlinkedModules[numUnlinkedModules]->loadStateFlags |= LoaderStateFlag2; ++numUnlinkedModules; } } sReportCodeHeap(globals, "fixup start"); auto maxShnum = 0u; auto numLoadStateFlag8 = 0u; auto importTracking = virt_ptr<LiImportTracking> { nullptr }; auto importTrackingSize = uint32_t { 0 }; auto remainingUnlinkedModules = 0u; auto unlinkedModuleIndex = 0u; for (auto i = 0u; i < numUnlinkedModules; ++i) { if (unlinkedModules[i]->elfHeader.shnum > maxShnum) { maxShnum = unlinkedModules[i]->elfHeader.shnum; } if (unlinkedModules[i]->loadStateFlags & LoaderStateFlag8) { numLoadStateFlag8++; } } // Allocate import tracking error = LiCacheLineCorrectAllocEx(globals->processCodeHeap, sizeof(LiImportTracking) * maxShnum, 4, &allocPtr, 1, &allocSize, &largestFree, ios::mcp::MCPFileType::ProcessCode); if (error) { Loader_ReportError( "*** Could not allocate space for largest # of sections ({}); (needed {}, available {}).", maxShnum, allocSize, largestFree); goto out; } importTracking = virt_cast<LiImportTracking *>(allocPtr); importTrackingSize = allocSize; if (numLoadStateFlag8 != 0) { for (auto i = 0u; i < numUnlinkedModules; ++i) { LiCheckAndHandleInterrupts(); auto module = unlinkedModules[i]; if (module->loadStateFlags & LoaderStateFlag8) { if (!module->elfHeader.shstrndx) { Loader_ReportError( "*** Error: Could not get section string table index for \"{}\".", module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 839); error = -470071; goto out; } auto shStrAddr = module->sectionAddressBuffer[module->elfHeader.shstrndx]; if (!shStrAddr) { Loader_ReportError( "*** Error: Could not get section string table for \"{}\".", module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 831); error = -470072; goto out; } for (auto j = 1u; j < module->elfHeader.shnum; ++j) { auto sectionHeader = getSectionHeader(module, j); if (sectionHeader->type == rpl::SHT_RPL_IMPORTS) { auto name = virt_cast<char *>(shStrAddr + sectionHeader->name) + 9; auto rpl = LiFindRPLByName(name); if (!rpl) { Loader_ReportError("*** Error: Could not get imported RPL name."); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 854); error = -470073; goto out; } if ((rpl->loadStateFlags & LoaderStateFlag2) && (rpl->loadStateFlags & LoaderStateFlag8)) { error = LiFixupRelocOneRPL(rpl, nullptr, 1); if (error) { Loader_ReportError( "*** Error. Could not find module \"{}\" during linking!", name); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 863); error = -470010; goto out; } } } } } } } // PLEASE SOMEONE TELL ME WHAT THIS LOAD STATE FLAGS MEANS!! for (auto i = 0u; i < numUnlinkedModules; ++i) { unlinkedModules[i]->loadStateFlags |= LoaderStateFlag2; } // Loop through trying to link everything remainingUnlinkedModules = numUnlinkedModules; unlinkedModuleIndex = 0u; while (remainingUnlinkedModules > 0) { LiCheckAndHandleInterrupts(); auto module = unlinkedModules[unlinkedModuleIndex]; if (module->loadStateFlags & LoaderStateFlag2) { std::memset(importTracking.get(), 0, sizeof(LiImportTracking) * module->elfHeader.shnum); if (!module->elfHeader.shstrndx) { Loader_ReportError( "*** Error: Could not get section string table index for \"{}\".", module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 927); error = -470071; goto out; } auto shStrAddr = module->sectionAddressBuffer[module->elfHeader.shstrndx]; if (!shStrAddr) { Loader_ReportError( "*** Error: Could not get section string table for \"{}\".", module->moduleNameBuffer); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 919); error = -470072; goto out; } auto j = 1u; for (; j < module->elfHeader.shnum; ++j) { auto sectionHeader = getSectionHeader(module, j); if (sectionHeader->type == rpl::SHT_RPL_IMPORTS) { auto name = virt_cast<char *>(shStrAddr + sectionHeader->name) + 9; auto rpl = LiFindRPLByName(name); if (!rpl) { Loader_ReportError( "*** Error. Could not find module \"{}\" during linking!", name); LiSetFatalError(0x18729Bu, module->fileType, 1, "LOADER_Link", 950); error = -470010; goto out; } if ((rpl->loadStateFlags & LoaderStateFlag2) && !(rpl->loadStateFlags & LoaderStateFlag8)) { break; } if (sectionHeader->flags & rpl::SHF_EXECINSTR) { importTracking[j].numExports = rpl->numFuncExports; importTracking[j].exports = virt_cast<rpl::Export *>(rpl->funcExports); } else { importTracking[j].numExports = rpl->numDataExports; importTracking[j].exports = virt_cast<rpl::Export *>(rpl->dataExports); } importTracking[j].tlsModuleIndex = rpl->fileInfoBuffer->tlsModuleIndex; importTracking[j].rpl = rpl; } } if (j == module->elfHeader.shnum) { auto unk = (module->loadStateFlags & LoaderStateFlag8) ? 2 : 0; error = LiFixupRelocOneRPL(module, importTracking, unk); if (error) { break; } } --remainingUnlinkedModules; } unlinkedModuleIndex++; if (unlinkedModuleIndex >= numUnlinkedModules) { unlinkedModuleIndex = 0; } } sReportCodeHeap(globals, "fixup done"); LiCacheLineCorrectFreeEx(globals->processCodeHeap, importTracking, importTrackingSize); out: // PLEASE SOMEONE TELL ME WHAT THIS LOAD STATE FLAGS MEANS!! for (auto i = 0u; i < numUnlinkedModules; ++i) { unlinkedModules[i]->loadStateFlags &= ~LoaderStateFlag8; } if (error) { // Purge all unlinked modules for (auto i = 0u; i < numUnlinkedModules; ++i) { auto module = virt_ptr<LOADED_RPL> { nullptr }; auto prev = virt_ptr<LOADED_RPL> { nullptr }; for (module = globals->firstLoadedRpl; module; module = module->nextLoadedRpl) { if (module == unlinkedModules[i]) { break; } prev = module; } if (!module) { Loader_ReportError("**** Module disappeared while being linked!"); } else if (!(module->loadStateFlags & LoaderStateFlag4)) { if (!module->nextLoadedRpl) { globals->lastLoadedRpl = prev; } if (prev) { prev->nextLoadedRpl = module->nextLoadedRpl; } LiPurgeOneUnlinkedModule(module); } } } else { sSetLinkOutput(linkInfo, linkModules); } for (auto i = 0u; i < numUnlinkedModules; ++i) { auto module = unlinkedModules[unlinkedModuleIndex]; if (!module) { continue; } if (module->compressedRelocationsBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, module->compressedRelocationsBuffer, module->compressedRelocationsBufferSize); module->compressedRelocationsBuffer = nullptr; } if (module->crcBuffer && module->crcBufferSize) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, module->crcBuffer, module->crcBufferSize); module->crcBuffer = nullptr; module->crcBufferSize = 0u; module->sectionAddressBuffer[module->elfHeader.shnum - 2] = virt_addr { 0 }; } } LiCacheLineCorrectFreeEx(globals->processCodeHeap, linkModules, linkModulesAllocSize); LiCacheLineCorrectFreeEx(globals->processCodeHeap, unlinkedModules, unlinkedModulesSize); sReportCodeHeap(globals, "link done"); return 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_link.h ================================================ #pragma once #include "cafe_loader_minfileinfo.h" namespace cafe::loader::internal { int32_t LOADER_Link(kernel::UniqueProcessId upid, virt_ptr<LOADER_LinkInfo> linkInfo, uint32_t linkInfoSize, virt_ptr<LOADER_MinFileInfo> minFileInfo); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_loaded_rpl.h ================================================ #pragma once #include "cafe_loader_rpl.h" #include "cafe/kernel/cafe_kernel_processid.h" #include "ios/mcp/ios_mcp_enum.h" #include <libcpu/be2_struct.h> namespace cafe::loader { enum LoadStateFlags : uint32_t { LoaderPrep = 0, LoaderSetup = 1 << 0, LoaderStateFlag2 = 1 << 1, LoaderStateFlag4 = 1 << 2, //! Likely means this is a circular dependency LoaderStateFlag8 = 1 << 3, //! Likely means linked LoaderStateFlags_Unk0x20000000 = 0x20000000, }; struct LOADER_UserFileInfo; struct LOADED_RPL { be2_virt_ptr<void> globals; be2_val<uint32_t> selfBufferSize; be2_virt_ptr<char> moduleNameBuffer; be2_val<uint32_t> moduleNameLen; be2_val<uint32_t> moduleNameBufferSize; be2_virt_ptr<void> pathBuffer; be2_val<uint32_t> pathBufferSize; be2_struct<rpl::Header> elfHeader; be2_virt_ptr<void> sectionHeaderBuffer; be2_val<uint32_t> sectionHeaderBufferSize; be2_virt_ptr<rpl::RPLFileInfo_v4_2> fileInfoBuffer; be2_val<uint32_t> fileInfoSize; be2_val<uint32_t> fileInfoBufferSize; be2_virt_ptr<void> crcBuffer; be2_val<uint32_t> crcBufferSize; be2_val<uint32_t> lastSectionCrc; be2_val<LoadStateFlags> loadStateFlags; be2_val<virt_addr> entryPoint; be2_val<uint32_t> upcomingBufferNumber; be2_virt_ptr<void> lastChunkBuffer; be2_val<uint32_t> virtualFileBaseOffset; be2_val<uint32_t> fileOffset; be2_val<uint32_t> upcomingFileOffset; be2_val<ios::mcp::MCPFileType> fileType; be2_val<uint32_t> totalBytesRead; be2_virt_ptr<void> chunkBuffer; UNKNOWN(0xAC - 0x98); be2_val<cafe::kernel::UniqueProcessId> upid; be2_virt_ptr<void> virtualFileBase; be2_virt_ptr<void> textBuffer; be2_val<uint32_t> textBufferSize; be2_virt_ptr<void> dataBuffer; be2_virt_ptr<void> loadBuffer; be2_virt_ptr<void> compressedRelocationsBuffer; be2_val<uint32_t> compressedRelocationsBufferSize; be2_val<virt_addr> postTrampBuffer; be2_val<virt_addr> textAddr; be2_val<uint32_t> textOffset; be2_val<uint32_t> textSize; be2_val<virt_addr> dataAddr; be2_val<uint32_t> dataOffset; be2_val<uint32_t> dataSize; be2_val<virt_addr> loadAddr; be2_val<uint32_t> loadOffset; be2_val<uint32_t> loadSize; be2_virt_ptr<virt_addr> sectionAddressBuffer; be2_val<uint32_t> sectionAddressBufferSize; be2_val<uint32_t> numFuncExports; be2_virt_ptr<void> funcExports; be2_val<uint32_t> numDataExports; be2_virt_ptr<void> dataExports; //! Pointer to last struct returned from LOADER_GetFileInfo. be2_virt_ptr<LOADER_UserFileInfo> userFileInfo; //! Size of the file info structure returned from LOADER_GetFileInfo. be2_val<uint32_t> userFileInfoSize; be2_virt_ptr<LOADED_RPL> nextLoadedRpl; }; CHECK_OFFSET(LOADED_RPL, 0x00, globals); CHECK_OFFSET(LOADED_RPL, 0x04, selfBufferSize); CHECK_OFFSET(LOADED_RPL, 0x08, moduleNameBuffer); CHECK_OFFSET(LOADED_RPL, 0x0C, moduleNameLen); CHECK_OFFSET(LOADED_RPL, 0x10, moduleNameBufferSize); CHECK_OFFSET(LOADED_RPL, 0x14, pathBuffer); CHECK_OFFSET(LOADED_RPL, 0x18, pathBufferSize); CHECK_OFFSET(LOADED_RPL, 0x1C, elfHeader); CHECK_OFFSET(LOADED_RPL, 0x50, sectionHeaderBuffer); CHECK_OFFSET(LOADED_RPL, 0x54, sectionHeaderBufferSize); CHECK_OFFSET(LOADED_RPL, 0x58, fileInfoBuffer); CHECK_OFFSET(LOADED_RPL, 0x5C, fileInfoSize); CHECK_OFFSET(LOADED_RPL, 0x60, fileInfoBufferSize); CHECK_OFFSET(LOADED_RPL, 0x64, crcBuffer); CHECK_OFFSET(LOADED_RPL, 0x68, crcBufferSize); CHECK_OFFSET(LOADED_RPL, 0x6C, lastSectionCrc); CHECK_OFFSET(LOADED_RPL, 0x70, loadStateFlags); CHECK_OFFSET(LOADED_RPL, 0x74, entryPoint); CHECK_OFFSET(LOADED_RPL, 0x78, upcomingBufferNumber); CHECK_OFFSET(LOADED_RPL, 0x7C, lastChunkBuffer); CHECK_OFFSET(LOADED_RPL, 0x80, virtualFileBaseOffset); CHECK_OFFSET(LOADED_RPL, 0x84, fileOffset); CHECK_OFFSET(LOADED_RPL, 0x88, upcomingFileOffset); CHECK_OFFSET(LOADED_RPL, 0x8C, fileType); CHECK_OFFSET(LOADED_RPL, 0x90, totalBytesRead); CHECK_OFFSET(LOADED_RPL, 0x94, chunkBuffer); CHECK_OFFSET(LOADED_RPL, 0xAC, upid); CHECK_OFFSET(LOADED_RPL, 0xB0, virtualFileBase); CHECK_OFFSET(LOADED_RPL, 0xB4, textBuffer); CHECK_OFFSET(LOADED_RPL, 0xB8, textBufferSize); CHECK_OFFSET(LOADED_RPL, 0xBC, dataBuffer); CHECK_OFFSET(LOADED_RPL, 0xC0, loadBuffer); CHECK_OFFSET(LOADED_RPL, 0xC4, compressedRelocationsBuffer); CHECK_OFFSET(LOADED_RPL, 0xC8, compressedRelocationsBufferSize); CHECK_OFFSET(LOADED_RPL, 0xCC, postTrampBuffer); CHECK_OFFSET(LOADED_RPL, 0xD0, textAddr); CHECK_OFFSET(LOADED_RPL, 0xD4, textOffset); CHECK_OFFSET(LOADED_RPL, 0xD8, textSize); CHECK_OFFSET(LOADED_RPL, 0xDC, dataAddr); CHECK_OFFSET(LOADED_RPL, 0xE0, dataOffset); CHECK_OFFSET(LOADED_RPL, 0xE4, dataSize); CHECK_OFFSET(LOADED_RPL, 0xE8, loadAddr); CHECK_OFFSET(LOADED_RPL, 0xEC, loadOffset); CHECK_OFFSET(LOADED_RPL, 0xF0, loadSize); CHECK_OFFSET(LOADED_RPL, 0xF4, sectionAddressBuffer); CHECK_OFFSET(LOADED_RPL, 0xF8, sectionAddressBufferSize); CHECK_OFFSET(LOADED_RPL, 0xFC, numFuncExports); CHECK_OFFSET(LOADED_RPL, 0x100, funcExports); CHECK_OFFSET(LOADED_RPL, 0x104, numDataExports); CHECK_OFFSET(LOADED_RPL, 0x108, dataExports); CHECK_OFFSET(LOADED_RPL, 0x10C, userFileInfo); CHECK_OFFSET(LOADED_RPL, 0x110, userFileInfoSize); CHECK_OFFSET(LOADED_RPL, 0x114, nextLoadedRpl); CHECK_SIZE(LOADED_RPL, 0x118); } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_log.h ================================================ #pragma once #include "cafe_loader_entry.h" #include <common/log.h> namespace cafe::loader::internal { template<typename... Args> void Loader_ReportError(const char *fmt, Args... args) { gLog->error(fmt, args...); } template<typename... Args> void Loader_ReportWarn(const char *fmt, Args... args) { if (getProcFlags().debugLevel() >= kernel::DebugLevel::Warn) { gLog->warn(fmt, args...); } } template<typename... Args> void Loader_ReportInfo(const char *fmt, Args... args) { if (getProcFlags().debugLevel() >= kernel::DebugLevel::Info) { gLog->info(fmt, args...); } } template<typename... Args> void Loader_ReportNotice(const char *fmt, Args... args) { if (getProcFlags().debugLevel() >= kernel::DebugLevel::Notice) { gLog->debug(fmt, args...); } } template<typename... Args> void Loader_ReportVerbose(const char *fmt, Args... args) { if (getProcFlags().debugLevel() >= kernel::DebugLevel::Verbose) { gLog->trace(fmt, args...); } } template<typename... Args> void Loader_LogEntry(uint32_t unk1, uint32_t unk2, uint32_t unk3, const char *fmt, Args... args) { gLog->debug(fmt, args...); } template<typename... Args> void Loader_Panic(uint32_t unk1, const char *fmt, Args... args) { gLog->error(fmt, args...); decaf_abort("Loader_Panic"); } template<typename... Args> void LiPanic(const char *file, int line, const char *fmt, Args... args) { gLog->error(fmt, args...); gLog->error("Loader_Panic: {}, line {}", file, line); Loader_Panic(0x130016, "in \"{}\" on line {}.", file, line); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_minfileinfo.cpp ================================================ #include "cafe_loader_error.h" #include "cafe_loader_log.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_minfileinfo.h" namespace cafe::loader::internal { bool Loader_ValidateAddrRange(virt_addr addr, uint32_t size) { // TODO: Syscall to kernel validate addr range return TRUE; } int32_t LiValidateAddress(virt_ptr<void> ptr, uint32_t size, uint32_t alignMask, int32_t errorCode, virt_addr minAddr, virt_addr maxAddr, std::string_view name) { auto addr = virt_cast<virt_addr>(ptr); if (addr < minAddr || addr >= maxAddr) { if (!name.empty()) { Loader_ReportError("***bad {} address.", name); } return errorCode; } if (alignMask && (addr & alignMask)) { if (!name.empty()) { Loader_ReportError("***bad {} address alignment.", name); } return errorCode; } if (size && !Loader_ValidateAddrRange(addr, size)) { if (!name.empty()) { Loader_ReportError("***bad {} address buffer.", name); } return errorCode; } return 0; } /** * sUpdateFileInfoForUser */ void updateFileInfoForUser(virt_ptr<LOADED_RPL> rpl, virt_ptr<LOADER_UserFileInfo> userFileInfo, virt_ptr<LOADER_MinFileInfo> minFileInfo) { if (rpl->loadStateFlags & LoaderStateFlags_Unk0x20000000) { if (userFileInfo) { userFileInfo->fileInfoFlags |= rpl::RPL_FLAG_4; } else if (minFileInfo) { minFileInfo->fileInfoFlags |= rpl::RPL_FLAG_4; } } } int32_t LiGetMinFileInfo(virt_ptr<LOADED_RPL> rpl, virt_ptr<LOADER_MinFileInfo> info) { auto fileInfo = rpl->fileInfoBuffer; *info->outSizeOfFileInfo = rpl->fileInfoSize; if (fileInfo->runtimeFileInfoSize) { *info->outSizeOfFileInfo = fileInfo->runtimeFileInfoSize; } info->dataSize = fileInfo->dataSize; info->dataAlign = fileInfo->dataAlign; info->loadSize = fileInfo->loadSize; info->loadAlign = fileInfo->loadAlign; info->fileInfoFlags = fileInfo->flags; if (rpl->fileType == ios::mcp::MCPFileType::ProcessCode) { info->fileLocation = getProcTitleLoc(); } if (info->inoutNextTlsModuleNumber && (fileInfo->flags & 8)) { if (fileInfo->tlsModuleIndex != -1) { Loader_ReportError("*** unexpected module index.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiGetMinFileInfo", 261); return -470064; } fileInfo->tlsModuleIndex = *info->inoutNextTlsModuleNumber; *info->inoutNextTlsModuleNumber += 1; } if (fileInfo->filename) { auto path = virt_cast<char *>(rpl->fileInfoBuffer) + fileInfo->filename; *info->outPathStringSize = static_cast<uint32_t>(strnlen(path.get(), rpl->fileInfoSize - fileInfo->filename) + 1); } else { *info->outPathStringSize = 0u; } updateFileInfoForUser(rpl, nullptr, info); return 0; } int32_t LiValidateMinFileInfo(virt_ptr<LOADER_MinFileInfo> minFileInfo, std::string_view funcName) { if (!minFileInfo) { Loader_ReportError("*** Null minimum file info pointer."); LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 316); return -470058; } auto error = LiValidateAddress(minFileInfo, sizeof(LOADER_MinFileInfo), 0, -470058, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "minimum file info"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 325); return error; } if (minFileInfo->size != sizeof(LOADER_MinFileInfo)) { Loader_ReportError("***{} received invalid minimum file control block size.", funcName); LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 334); return -470059; } if (minFileInfo->version != 4) { Loader_ReportError("***{} received invalid minimum file control block version.", funcName); LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 342); return -470059; } if (minFileInfo->outKernelHandle) { error = LiValidateAddress(minFileInfo->outKernelHandle, 4, 0, -470027, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "kernel handle for module (out of valid range)"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 353); return error; } } if (minFileInfo->moduleNameBuffer) { error = LiValidateAddress(minFileInfo->moduleNameBuffer, minFileInfo->moduleNameBufferLen, 0, -470027, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "module name"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 364); return error; } } if (minFileInfo->outNumberOfSections) { error = LiValidateAddress(minFileInfo->outNumberOfSections, 4, 0, -470027, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "number of sections"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 375); return error; } } if (minFileInfo->outSizeOfFileInfo) { error = LiValidateAddress(minFileInfo->outSizeOfFileInfo, 4, 0, -470027, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "size of file info"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 386); return error; } } if (minFileInfo->outPathStringSize) { error = LiValidateAddress(minFileInfo->outPathStringSize, 4, 0, -470027, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "size of path string"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 397); return error; } } if (minFileInfo->inoutNextTlsModuleNumber) { error = LiValidateAddress(minFileInfo->inoutNextTlsModuleNumber, 4, 0, -470027, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "next TLS module number"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LiValidateMinFileInfo", 408); return error; } } return 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_minfileinfo.h ================================================ #pragma once #include "cafe_loader_basics.h" #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::loader { //! Unique pointer representing a module handle, we actually use the pointer //! value of the LOADED_RPL.moduleNameBuffer using LOADER_Handle = virt_ptr<void>; struct LOADER_LinkModule { be2_val<LOADER_Handle> loaderHandle; be2_val<virt_addr> entryPoint; be2_val<virt_addr> textAddr; be2_val<uint32_t> textOffset; be2_val<uint32_t> textSize; be2_val<virt_addr> dataAddr; be2_val<uint32_t> dataOffset; be2_val<uint32_t> dataSize; be2_val<virt_addr> loadAddr; be2_val<uint32_t> loadOffset; be2_val<uint32_t> loadSize; }; CHECK_OFFSET(LOADER_LinkModule, 0x00, loaderHandle); CHECK_OFFSET(LOADER_LinkModule, 0x04, entryPoint); CHECK_OFFSET(LOADER_LinkModule, 0x08, textAddr); CHECK_OFFSET(LOADER_LinkModule, 0x0C, textOffset); CHECK_OFFSET(LOADER_LinkModule, 0x10, textSize); CHECK_OFFSET(LOADER_LinkModule, 0x14, dataAddr); CHECK_OFFSET(LOADER_LinkModule, 0x18, dataOffset); CHECK_OFFSET(LOADER_LinkModule, 0x1C, dataSize); CHECK_OFFSET(LOADER_LinkModule, 0x20, loadAddr); CHECK_OFFSET(LOADER_LinkModule, 0x24, loadOffset); CHECK_OFFSET(LOADER_LinkModule, 0x28, loadSize); CHECK_SIZE(LOADER_LinkModule, 0x2C); struct LOADER_LinkInfo { be2_val<uint32_t> size; be2_val<uint32_t> numModules; be2_array<LOADER_LinkModule, 1> modules; }; CHECK_OFFSET(LOADER_LinkInfo, 0x00, size); CHECK_OFFSET(LOADER_LinkInfo, 0x04, numModules); CHECK_OFFSET(LOADER_LinkInfo, 0x08, modules); CHECK_SIZE(LOADER_LinkInfo, 0x34); struct LOADER_SectionInfo { be2_val<uint32_t> type; be2_val<uint32_t> flags; be2_val<virt_addr> address; union { //! Size of the section, set when type != SHT_RPL_IMPORTS be2_val<uint32_t> size; //! Name offset of the section, set when type == SHT_RPL_IMPORTS be2_val<uint32_t> name; }; }; CHECK_OFFSET(LOADER_SectionInfo, 0x00, type); CHECK_OFFSET(LOADER_SectionInfo, 0x04, flags); CHECK_OFFSET(LOADER_SectionInfo, 0x08, address); CHECK_OFFSET(LOADER_SectionInfo, 0x0C, size); CHECK_OFFSET(LOADER_SectionInfo, 0x0C, name); CHECK_SIZE(LOADER_SectionInfo, 0x10); struct LOADER_UserFileInfo { be2_val<uint32_t> size; be2_val<uint32_t> magic; be2_val<uint32_t> pathStringLength; be2_virt_ptr<char> pathString; be2_val<uint32_t> fileInfoFlags; be2_val<int16_t> tlsModuleIndex; be2_val<int16_t> tlsAlignShift; be2_val<virt_addr> tlsAddressStart; be2_val<uint32_t> tlsSectionSize; be2_val<uint32_t> shstrndx; be2_val<uint32_t> titleLocation; UNKNOWN(0x60 - 0x28); }; CHECK_OFFSET(LOADER_UserFileInfo, 0x00, size); CHECK_OFFSET(LOADER_UserFileInfo, 0x04, magic); CHECK_OFFSET(LOADER_UserFileInfo, 0x08, pathStringLength); CHECK_OFFSET(LOADER_UserFileInfo, 0x0C, pathString); CHECK_OFFSET(LOADER_UserFileInfo, 0x10, fileInfoFlags); CHECK_OFFSET(LOADER_UserFileInfo, 0x14, tlsModuleIndex); CHECK_OFFSET(LOADER_UserFileInfo, 0x16, tlsAlignShift); CHECK_OFFSET(LOADER_UserFileInfo, 0x18, tlsAddressStart); CHECK_OFFSET(LOADER_UserFileInfo, 0x1C, tlsSectionSize); CHECK_OFFSET(LOADER_UserFileInfo, 0x20, shstrndx); CHECK_OFFSET(LOADER_UserFileInfo, 0x24, titleLocation); CHECK_SIZE(LOADER_UserFileInfo, 0x60); struct LOADER_MinFileInfo { be2_val<uint32_t> size; be2_val<uint32_t> version; be2_virt_ptr<char> moduleNameBuffer; be2_val<uint32_t> moduleNameBufferLen; be2_virt_ptr<LOADER_Handle> outKernelHandle; be2_virt_ptr<uint32_t> outNumberOfSections; be2_virt_ptr<LOADER_SectionInfo> outSectionInfo; be2_virt_ptr<uint32_t> outSizeOfFileInfo; be2_virt_ptr<LOADER_UserFileInfo> outFileInfo; be2_val<uint32_t> dataSize; be2_val<uint32_t> dataAlign; be2_virt_ptr<void> dataBuffer; be2_val<uint32_t> loadSize; be2_val<uint32_t> loadAlign; be2_virt_ptr<void> loadBuffer; be2_val<uint32_t> fileInfoFlags; be2_virt_ptr<uint32_t> inoutNextTlsModuleNumber; be2_val<uint32_t> pathStringSize; be2_virt_ptr<uint32_t> outPathStringSize; be2_virt_ptr<char> pathStringBuffer; be2_val<uint32_t> fileLocation; be2_val<uint32_t> fatalMsgType; be2_val<int32_t> fatalErr; be2_val<int32_t> error; be2_val<uint32_t> fatalLine; be2_array<char, 64> fatalFunction; }; CHECK_OFFSET(LOADER_MinFileInfo, 0x00, size); CHECK_OFFSET(LOADER_MinFileInfo, 0x04, version); CHECK_OFFSET(LOADER_MinFileInfo, 0x08, moduleNameBuffer); CHECK_OFFSET(LOADER_MinFileInfo, 0x0C, moduleNameBufferLen); CHECK_OFFSET(LOADER_MinFileInfo, 0x10, outKernelHandle); CHECK_OFFSET(LOADER_MinFileInfo, 0x14, outNumberOfSections); CHECK_OFFSET(LOADER_MinFileInfo, 0x18, outSectionInfo); CHECK_OFFSET(LOADER_MinFileInfo, 0x1C, outSizeOfFileInfo); CHECK_OFFSET(LOADER_MinFileInfo, 0x20, outFileInfo); CHECK_OFFSET(LOADER_MinFileInfo, 0x24, dataSize); CHECK_OFFSET(LOADER_MinFileInfo, 0x28, dataAlign); CHECK_OFFSET(LOADER_MinFileInfo, 0x2C, dataBuffer); CHECK_OFFSET(LOADER_MinFileInfo, 0x30, loadSize); CHECK_OFFSET(LOADER_MinFileInfo, 0x34, loadAlign); CHECK_OFFSET(LOADER_MinFileInfo, 0x38, loadBuffer); CHECK_OFFSET(LOADER_MinFileInfo, 0x3C, fileInfoFlags); CHECK_OFFSET(LOADER_MinFileInfo, 0x40, inoutNextTlsModuleNumber); CHECK_OFFSET(LOADER_MinFileInfo, 0x44, pathStringSize); CHECK_OFFSET(LOADER_MinFileInfo, 0x48, outPathStringSize); CHECK_OFFSET(LOADER_MinFileInfo, 0x4C, pathStringBuffer); CHECK_OFFSET(LOADER_MinFileInfo, 0x50, fileLocation); CHECK_OFFSET(LOADER_MinFileInfo, 0x54, fatalMsgType); CHECK_OFFSET(LOADER_MinFileInfo, 0x58, fatalErr); CHECK_OFFSET(LOADER_MinFileInfo, 0x5C, error); CHECK_OFFSET(LOADER_MinFileInfo, 0x60, fatalLine); CHECK_OFFSET(LOADER_MinFileInfo, 0x64, fatalFunction); CHECK_SIZE(LOADER_MinFileInfo, 0xA4); namespace internal { bool Loader_ValidateAddrRange(virt_addr addr, uint32_t size); int32_t LiValidateAddress(virt_ptr<void> ptr, uint32_t size, uint32_t alignMask, int32_t errorCode, virt_addr minAddr, virt_addr maxAddr, std::string_view name); int32_t LiGetMinFileInfo(virt_ptr<LOADED_RPL> rpl, virt_ptr<LOADER_MinFileInfo> info); int32_t LiValidateMinFileInfo(virt_ptr<LOADER_MinFileInfo> minFileInfo, std::string_view funcName); void updateFileInfoForUser(virt_ptr<LOADED_RPL> rpl, virt_ptr<LOADER_UserFileInfo> userFileInfo, virt_ptr<LOADER_MinFileInfo> minFileInfo); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_prep.cpp ================================================ #include "cafe_loader_bounce.h" #include "cafe_loader_entry.h" #include "cafe_loader_error.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_basics.h" #include "cafe_loader_purge.h" #include "cafe_loader_prep.h" #include "cafe_loader_shared.h" #include "cafe_loader_log.h" #include "cafe_loader_minfileinfo.h" #include "cafe_loader_utils.h" #include "cafe/cafe_stackobject.h" #include "cafe/kernel/cafe_kernel_processid.h" #include <common/strutils.h> namespace cafe::loader::internal { int32_t LiLoadForPrep(virt_ptr<char> moduleName, uint32_t moduleNameLen, virt_ptr<void> chunkBuffer, virt_ptr<LOADED_RPL> *outLoadedRpl, LiBasicsLoadArgs *loadArgs, uint32_t unk) { auto globals = getGlobalStorage(); auto rpl = virt_ptr<LOADED_RPL> { nullptr }; auto error = LiLoadRPLBasics(moduleName, moduleNameLen, chunkBuffer, globals->processCodeHeap, globals->processCodeHeap, true, 0, &rpl, loadArgs, unk); if (error) { return error; } auto fileInfo = rpl->fileInfoBuffer; if (globals->loadedRpx && (fileInfo->flags & rpl::RPL_IS_RPX)) { Loader_ReportError("***Attempt to load RPX when main program already loaded.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiLoadForPrep", 1175); error = -470093; } else if (!globals->loadedRpx && !(fileInfo->flags & rpl::RPL_IS_RPX)) { Loader_ReportError("***Attempt to load non-RPX as main program.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiLoadForPrep", 1183); error = -470094; } else { rpl->nextLoadedRpl = nullptr; if (globals->lastLoadedRpl) { globals->lastLoadedRpl->nextLoadedRpl = rpl; globals->lastLoadedRpl = rpl; } else { globals->firstLoadedRpl = rpl; globals->lastLoadedRpl = rpl; } *outLoadedRpl = rpl; return 0; } if (rpl) { LiPurgeOneUnlinkedModule(rpl); } return error; } int32_t LOADER_Prep(kernel::UniqueProcessId upid, virt_ptr<LOADER_MinFileInfo> minFileInfo) { auto globals = getGlobalStorage(); auto error = 0; LiCheckAndHandleInterrupts(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Prep", 1262); LiCloseBufferIfError(); return Error::DifferentProcess; } if (minFileInfo) { error = LiValidateMinFileInfo(minFileInfo, "LOADER_Prep"); if (error) { LiCloseBufferIfError(); return error; } } *minFileInfo->outKernelHandle = nullptr; LiResolveModuleName(virt_addrof(minFileInfo->moduleNameBuffer), virt_addrof(minFileInfo->moduleNameBufferLen)); auto moduleName = std::string_view { minFileInfo->moduleNameBuffer.get(), minFileInfo->moduleNameBufferLen }; if (minFileInfo->moduleNameBufferLen == 8 && strncmp(minFileInfo->moduleNameBuffer.get(), "coreinit", 8) == 0) { Loader_ReportError("*** Loader Failure (system module re-load)."); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Prep", 1305); LiCloseBufferIfError(); return -470029; } for (auto itr = globals->firstLoadedRpl; itr; itr = itr->nextLoadedRpl) { if (itr->moduleNameLen == minFileInfo->moduleNameBufferLen && strncmp(itr->moduleNameBuffer.get(), minFileInfo->moduleNameBuffer.get(), minFileInfo->moduleNameBufferLen) == 0) { Loader_ReportError("*** module \"{}\" already loaded.\n", std::string_view { minFileInfo->moduleNameBuffer.get(), minFileInfo->moduleNameBufferLen }); LiSetFatalError(0x18729Bu, itr->fileType, 1, "LOADER_Prep", 1292); LiCloseBufferIfError(); return -470028; } } // Check if module already loaded as a shared library if (!getProcFlags().disableSharedLibraries()) { auto sharedModule = findLoadedSharedModule(moduleName); if (sharedModule) { auto allocPtr = virt_ptr<void> { nullptr }; auto allocSize = uint32_t { 0 }; auto largestFree = uint32_t { 0 }; error = LiCacheLineCorrectAllocEx(getGlobalStorage()->processCodeHeap, sizeof(LOADED_RPL), 4, &allocPtr, 1, &allocSize, &largestFree, sharedModule->fileType); if (error) { Loader_ReportError( "***Allocate Error {}, Failed to allocate {} bytes for system shared RPL tracking for {} in current process; (needed {}, available {}).", error, allocSize, moduleName, allocSize, largestFree); LiCloseBufferIfError(); return error; } auto trackingModule = virt_cast<LOADED_RPL *>(allocPtr); *trackingModule = *sharedModule; trackingModule->selfBufferSize = allocSize; trackingModule->globals = globals; trackingModule->loadStateFlags &= ~LoaderStateFlag4; // Add to global loaded module linked list trackingModule->nextLoadedRpl = nullptr; if (globals->lastLoadedRpl) { globals->lastLoadedRpl->nextLoadedRpl = trackingModule; } else { globals->firstLoadedRpl = trackingModule; } globals->lastLoadedRpl = trackingModule; *minFileInfo->outKernelHandle = trackingModule->moduleNameBuffer; *minFileInfo->outNumberOfSections = trackingModule->elfHeader.shnum; error = LiGetMinFileInfo(trackingModule, minFileInfo); if (error) { LiCloseBufferIfError(); } return error; } } auto filename = StackArray<char, 64> { }; auto filenameLen = std::min<uint32_t>(minFileInfo->moduleNameBufferLen, 59); std::memcpy(filename.get(), minFileInfo->moduleNameBuffer.get(), filenameLen); string_copy(filename.get() + filenameLen, ".rpl", filename.size() - filenameLen); LiCheckAndHandleInterrupts(); LiInitBuffer(false); auto chunkBuffer = virt_ptr<void> { nullptr }; auto chunkBufferSize = uint32_t { 0 }; error = LiBounceOneChunk(filename.get(), ios::mcp::MCPFileType::CafeOS, globals->currentUpid, &chunkBufferSize, 0, 1, &chunkBuffer); LiCheckAndHandleInterrupts(); if (error) { Loader_ReportError( "***LiBounceOneChunk failed loading \"{}\" of type {} at offset 0x{:08X} err={}.", filename.get(), 1, 0, error); LiCloseBufferIfError(); return error; } auto loadArgs = LiBasicsLoadArgs { }; auto rpl = virt_ptr<LOADED_RPL> { nullptr }; loadArgs.upid = upid; loadArgs.loadedRpl = nullptr; loadArgs.readHeapTracking = globals->processCodeHeap; loadArgs.pathNameLen = filenameLen + 5; loadArgs.pathName = filename; loadArgs.fileType = ios::mcp::MCPFileType::CafeOS; loadArgs.chunkBuffer = chunkBuffer; loadArgs.chunkBufferSize = chunkBufferSize; loadArgs.fileOffset = 0u; error = LiLoadForPrep(filename, filenameLen, chunkBuffer, &rpl, &loadArgs, 0); if (error) { Loader_ReportError("***LiLoadForPrep failure {}. loading \"{}\".", error, filename.get()); LiCloseBufferIfError(); return error; } *minFileInfo->outKernelHandle = rpl->moduleNameBuffer; *minFileInfo->outNumberOfSections = rpl->elfHeader.shnum; error = LiGetMinFileInfo(rpl, minFileInfo); if (error) { LiCloseBufferIfError(); } return error; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_prep.h ================================================ #pragma once #include "cafe/kernel/cafe_kernel_processid.h" #include <libcpu/be2_struct.h> namespace cafe::loader { struct LOADER_MinFileInfo; struct LOADED_RPL; namespace internal { struct LiBasicsLoadArgs; int32_t LiLoadForPrep(virt_ptr<char> moduleName, uint32_t moduleNameLen, virt_ptr<void> chunkBuffer, virt_ptr<LOADED_RPL> *outLoadedRpl, LiBasicsLoadArgs *loadArgs, uint32_t unk); int32_t LOADER_Prep(kernel::UniqueProcessId upid, virt_ptr<LOADER_MinFileInfo> minFileInfo); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_purge.cpp ================================================ #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_log.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_purge.h" namespace cafe::loader::internal { void LiPurgeOneUnlinkedModule(virt_ptr<LOADED_RPL> rpl) { auto globals = getGlobalStorage(); if (rpl->globals != globals) { Loader_ReportWarn("*** Purge of module in foreign process!"); return; } if (!(rpl->loadStateFlags & LoaderStateFlags_Unk0x20000000)) { if (rpl->textBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->textBuffer, rpl->textBufferSize); } if (rpl->compressedRelocationsBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->compressedRelocationsBuffer, rpl->compressedRelocationsBufferSize); } if (rpl->moduleNameBuffer && rpl->moduleNameBufferSize) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->moduleNameBuffer, rpl->moduleNameBufferSize); } if (rpl->pathBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->pathBuffer, rpl->pathBufferSize); } if (rpl->sectionHeaderBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->sectionHeaderBuffer, rpl->sectionHeaderBufferSize); } if (rpl->fileInfoBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->fileInfoBuffer, rpl->fileInfoBufferSize); } if (rpl->crcBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->crcBuffer, rpl->crcBufferSize); } if (rpl->sectionAddressBuffer) { LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl->sectionAddressBuffer, rpl->sectionAddressBufferSize); } } std::memset(rpl.get(), 0, sizeof(LOADED_RPL)); LiCacheLineCorrectFreeEx(globals->processCodeHeap, rpl, rpl->selfBufferSize); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_purge.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace cafe::loader { struct LOADED_RPL; namespace internal { void LiPurgeOneUnlinkedModule(virt_ptr<LOADED_RPL> rpl); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_query.cpp ================================================ #include "cafe_loader_bounce.h" #include "cafe_loader_error.h" #include "cafe_loader_log.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_globals.h" #include "cafe_loader_minfileinfo.h" #include "cafe_loader_utils.h" #include "cafe_loader_query.h" #include <common/strutils.h> #include <cstring> namespace cafe::loader::internal { virt_ptr<LOADED_RPL> getModule(LOADER_Handle handle) { auto globals = getGlobalStorage(); for (auto rpl = globals->firstLoadedRpl; rpl; rpl = rpl->nextLoadedRpl) { if (rpl->moduleNameBuffer == handle) { return rpl; } } return nullptr; } int32_t LOADER_GetSecInfo(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<uint32_t> outNumberOfSections, virt_ptr<LOADER_SectionInfo> outSectionInfo) { auto globals = getGlobalStorage(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetSecInfo", 45); LiCloseBufferIfError(); return Error::DifferentProcess; } // Verify outNumberOfSections pointer if (auto error = LiValidateAddress(outNumberOfSections, sizeof(uint32_t), 0, -470009, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "number of sections")) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetSecInfo", 56); LiCloseBufferIfError(); return error; } // Find the module if (!handle) { handle = globals->loadedRpx->moduleNameBuffer; } auto rpl = getModule(handle); if (!rpl) { Loader_ReportError("*** module not found."); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetSecInfo", 75); LiCloseBufferIfError(); return -470010; } // Check section count if (!outSectionInfo || !*outNumberOfSections) { *outNumberOfSections = rpl->elfHeader.shnum; return 0; } if (*outNumberOfSections < rpl->elfHeader.shnum) { Loader_ReportError("*** num sections is too small.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_GetSecInfo", 91); LiCloseBufferIfError(); return -470011; } *outNumberOfSections = rpl->elfHeader.shnum; // Verify the outSectionInfo pointer if (auto error = LiValidateAddress(outSectionInfo, sizeof(LOADER_SectionInfo) * rpl->elfHeader.shnum, 0, -470012, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "section return")) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetSecInfo", 56); LiCloseBufferIfError(); return error; } // Fill out the section info data std::memset(outSectionInfo.get(), 0, sizeof(LOADER_SectionInfo) * rpl->elfHeader.shnum); for (auto i = 1; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = getSectionHeader(rpl, i); if (sectionHeader->type == rpl::SHT_RELA || sectionHeader->type == rpl::SHT_RPL_FILEINFO || sectionHeader->type == rpl::SHT_RPL_CRCS) { continue; } auto &info = outSectionInfo[i]; info.type = sectionHeader->type; info.flags = sectionHeader->flags; info.address = rpl->sectionAddressBuffer[i]; if (sectionHeader->type == rpl::SHT_RPL_IMPORTS) { info.name = sectionHeader->name; } else { info.size = sectionHeader->size; } } return 0; } int32_t LOADER_GetFileInfo(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<uint32_t> outSizeOfFileInfo, virt_ptr<LOADER_UserFileInfo> outFileInfo, virt_ptr<uint32_t> nextTlsModuleNumber, virt_ptr<uint32_t> outFileLocation) { auto globals = getGlobalStorage(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetFileInfo", 59); LiCloseBufferIfError(); return Error::DifferentProcess; } // Verify outSizeOfFileInfo pointer if (auto error = LiValidateAddress(outSizeOfFileInfo, sizeof(uint32_t), 0, -470060, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "file info bytes")) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetFileInfo", 70); LiCloseBufferIfError(); return error; } // Find the module if (!handle) { handle = globals->loadedRpx->moduleNameBuffer; } auto rpl = getModule(handle); if (!rpl) { Loader_ReportError("*** module not found."); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetFileInfo", 89); LiCloseBufferIfError(); return -470010; } // Check file info size auto fileInfoSize = rpl->fileInfoSize; auto fileInfo = rpl->fileInfoBuffer; if (fileInfo->runtimeFileInfoSize) { fileInfoSize = fileInfo->runtimeFileInfoSize; } rpl->userFileInfoSize = fileInfoSize; if (!outFileInfo || !*outSizeOfFileInfo) { *outSizeOfFileInfo = fileInfoSize; if (outFileLocation) { if (rpl->fileType == ios::mcp::MCPFileType::ProcessCode) { *outFileLocation = getProcTitleLoc(); } else { *outFileLocation = 0u; } } return 0; } if (*outSizeOfFileInfo < fileInfoSize) { Loader_ReportError("*** file info size is too small.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_GetFileInfo", 124); LiCloseBufferIfError(); return -470066; } *outSizeOfFileInfo = fileInfoSize; // Update the TLS module number if necessary if (nextTlsModuleNumber) { if (auto error = LiValidateAddress(nextTlsModuleNumber, sizeof(uint32_t), 0, -470097, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "next TLS number")) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetFileInfo", 137); LiCloseBufferIfError(); return error; } if (fileInfo->flags & rpl::RPL_HAS_TLS) { if (fileInfo->tlsModuleIndex != -1) { Loader_ReportError("*** unexpected module index.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_GetFileInfo", 152); LiCloseBufferIfError(); return -470064; } fileInfo->tlsModuleIndex = static_cast<int16_t>((*nextTlsModuleNumber)++); } } // Verify the outFileInfo pointer if (auto error = LiValidateAddress(outFileInfo, sizeof(LOADER_UserFileInfo), 0, -470062, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "file info return")) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetFileInfo", 165); LiCloseBufferIfError(); return error; } // Set the file info rpl->userFileInfo = outFileInfo; outFileInfo->size = static_cast<uint32_t>(sizeof(LOADER_UserFileInfo)); outFileInfo->magic = 0xCABE0402u; outFileInfo->pathStringLength = 0u; outFileInfo->pathString = nullptr; outFileInfo->fileInfoFlags = fileInfo->flags; outFileInfo->tlsAlignShift = fileInfo->tlsAlignShift; outFileInfo->tlsModuleIndex = fileInfo->tlsModuleIndex; outFileInfo->shstrndx = rpl->elfHeader.shstrndx; if (rpl->fileType == ios::mcp::MCPFileType::ProcessCode) { outFileInfo->titleLocation = getProcTitleLoc(); } else { outFileInfo->titleLocation = 0u; } updateFileInfoForUser(rpl, outFileInfo, nullptr); return 0; } int32_t LOADER_GetPathString(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<uint32_t> outPathStringSize, virt_ptr<char> pathStringBuffer, virt_ptr<LOADER_UserFileInfo> outFileInfo) { auto globals = getGlobalStorage(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetPathString", 304); LiCloseBufferIfError(); return Error::DifferentProcess; } // Verify outPathStringSize pointer if (auto error = LiValidateAddress(outPathStringSize, sizeof(uint32_t), 0, -470098, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "file info bytes")) { // Yes they copy and pasted this in loader.elf LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetPathString", 315); LiCloseBufferIfError(); return error; } // Find the module if (!handle) { handle = globals->loadedRpx->moduleNameBuffer; } auto rpl = getModule(handle); if (!rpl) { Loader_ReportError("*** module not found."); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetPathString", 334); LiCloseBufferIfError(); return -470010; } // Check path string size if (!rpl->fileInfoBuffer || !rpl->fileInfoBuffer->filename) { *outPathStringSize = 0u; return 0; } auto path = virt_cast<char *>(rpl->fileInfoBuffer) + rpl->fileInfoBuffer->filename; auto pathLength = static_cast<uint32_t>( strnlen(path.get(), rpl->fileInfoBufferSize - rpl->fileInfoBuffer->filename) + 1); if (!pathStringBuffer || *outPathStringSize == 0 || pathLength == 0) { *outPathStringSize = pathLength; return 0; } if (*outPathStringSize < pathLength) { Loader_ReportError("*** notify path size is too small.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_GetPathString", 354); return -470068; } *outPathStringSize = pathLength; // Verify the pathStringBuffer pointer if (auto error = LiValidateAddress(pathStringBuffer, pathLength, 0, -470099, virt_addr { 0x10000000 }, virt_addr { 0xC0000000 }, "path string return")) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_GetPathString", 366); LiCloseBufferIfError(); return error; } // Copy the path string string_copy(pathStringBuffer.get(), path.get(), pathLength); pathStringBuffer[pathLength - 1] = char { 0 }; if (outFileInfo) { outFileInfo->pathString = pathStringBuffer; outFileInfo->pathStringLength = pathLength; } return 0; } int32_t LOADER_Query(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<LOADER_MinFileInfo> minFileInfo) { auto globals = getGlobalStorage(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Query", 40); return Error::DifferentProcess; } if (auto error = LiValidateMinFileInfo(minFileInfo, "LOADER_Query")) { return error; } if (minFileInfo->outNumberOfSections) { if (auto error = LOADER_GetSecInfo(upid, handle, minFileInfo->outNumberOfSections, minFileInfo->outSectionInfo)) { return error; } } if (minFileInfo->outSizeOfFileInfo) { if (auto error = LOADER_GetFileInfo(upid, handle, minFileInfo->outSizeOfFileInfo, minFileInfo->outFileInfo, minFileInfo->inoutNextTlsModuleNumber, virt_addrof(minFileInfo->fileLocation))) { return error; } } if (minFileInfo->outPathStringSize) { if (auto error = LOADER_GetPathString(upid, handle, minFileInfo->outPathStringSize, minFileInfo->pathStringBuffer, minFileInfo->outFileInfo)) { return error; } } return 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_query.h ================================================ #pragma once #include "cafe_loader_minfileinfo.h" namespace cafe::loader::internal { virt_ptr<LOADED_RPL> getModule(LOADER_Handle handle); int32_t LOADER_GetSecInfo(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<uint32_t> outNumberOfSections, virt_ptr<LOADER_SectionInfo> outSectionInfo); int32_t LOADER_GetFileInfo(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<uint32_t> outSizeOfFileInfo, virt_ptr<LOADER_UserFileInfo> outFileInfo, virt_ptr<uint32_t> nextTlsModuleNumber, virt_ptr<uint32_t> outFileLocation); int32_t LOADER_GetPathString(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<uint32_t> outPathStringSize, virt_ptr<char> pathStringBuffer, virt_ptr<LOADER_UserFileInfo> outFileInfo); int32_t LOADER_Query(kernel::UniqueProcessId upid, LOADER_Handle handle, virt_ptr<LOADER_MinFileInfo> minFileInfo); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_reloc.cpp ================================================ #include "cafe_loader_basics.h" #include "cafe_loader_error.h" #include "cafe_loader_flush.h" #include "cafe_loader_iop.h" #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_reloc.h" #include "cafe_loader_rpl.h" #include "cafe_loader_log.h" #include "cafe_loader_globals.h" #include "cafe_loader_utils.h" #include "cafe/libraries/cafe_hle.h" #include <libcpu/be2_struct.h> #include <libcpu/cpu_formatters.h> #include <libcpu/espresso/espresso_instructionset.h> #include <libcpu/espresso/espresso_spr.h> #include <zlib.h> namespace cafe::loader::internal { constexpr auto TrampSize = uint32_t { 16 }; static std::array<uint8_t, 0x1FF8> sRelocBuffer; static virt_ptr<rpl::Export> LiBinSearchExport(virt_ptr<rpl::Export> exports, uint32_t numExports, virt_ptr<char> name) { if (!exports || !numExports || !name || !name[0]) { return nullptr; } auto strTable = virt_cast<char *>(virt_cast<virt_addr>(exports) - 8); auto left = 0u; auto right = numExports; while (left < right) { auto index = left + (right - left) / 2; auto exportName = strTable + (exports[index].name & 0x7FFFFFFF); auto cmpValue = strcmp(name.get(), exportName.get()); if (cmpValue == 0) { return exports + index; } else if (cmpValue < 0) { right = index; } else { left = index + 1; } } return nullptr; } static int32_t sFixupOneSymbolTable(virt_ptr<LOADED_RPL> rpl, uint32_t sectionIndex, virt_ptr<rpl::SectionHeader> sectionHeader, virt_ptr<LiImportTracking> imports, uint32_t unk) { auto strTable = virt_ptr<char> { nullptr }; LiCheckAndHandleInterrupts(); if (sectionHeader->link < rpl->elfHeader.shnum) { strTable = virt_cast<char *>(rpl->sectionAddressBuffer[sectionHeader->link]); } auto symbolEntrySize = sectionHeader->entsize; if (!symbolEntrySize) { symbolEntrySize = static_cast<uint32_t>(sizeof(rpl::Symbol)); } auto symbolBuffer = rpl->sectionAddressBuffer[sectionIndex]; auto numSymbols = sectionHeader->size / symbolEntrySize; for (auto i = 1u; i < numSymbols; ++i) { auto symbol = virt_cast<rpl::Symbol *>(symbolBuffer + (i * symbolEntrySize)); if (symbol->shndx == 0 || symbol->shndx >= rpl->elfHeader.shnum) { continue; } auto targetSectionAddress = rpl->sectionAddressBuffer[symbol->shndx]; if (!targetSectionAddress) { symbol->value = 0xCD000000u | i; continue; } LiCheckAndHandleInterrupts(); auto targetSectionHeader = getSectionHeader(rpl, symbol->shndx); auto targetSectionOffset = symbol->value - targetSectionHeader->addr; if (!imports || imports[symbol->shndx].numExports == 0 || targetSectionOffset < 8) { // auto binding = symbol->info >> 4; auto type = symbol->info & 0xf; // Not really sure what this is doing tbh if (type != rpl::STT_TLS && (targetSectionHeader->type != rpl::SHT_RPL_IMPORTS || unk != 1) && unk != 2) { symbol->value = static_cast<uint32_t>(targetSectionAddress + targetSectionOffset); } continue; } if (!strTable) { Loader_ReportError("*** imports require a string table but there isn't one - can't link blind symbol."); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sFixupOneSymbolTable", 529); return -470007; } auto symbolName = strTable + symbol->name; if (!symbolName[0]) { symbol->value = 0xCD000000u | i; continue; } auto &import = imports[symbol->shndx]; auto symbolExport = LiBinSearchExport(import.exports, import.numExports, symbolName); if (symbolExport) { symbol->value = symbolExport->value; } else { symbol->value = cafe::hle::registerUnimplementedSymbol( std::string_view { import.rpl->moduleNameBuffer.get(), import.rpl->moduleNameLen }, symbolName.get()); if (!symbol->value) { // Must not be from a HLE library, so let's error out like normal Loader_ReportError("*** could not find imported symbol \"{}\".", symbolName); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sFixupOneSymbolTable", 551); return -470005; } } } return 0; } static int32_t LiCpu_RelocAdd(bool isRpx, virt_addr targetSectionBuffer, rpl::RelocationType relaType, bool isWeakSymbol, uint32_t offset, uint32_t addend, uint32_t symbolValue, virt_addr *preTrampBuffer, uint32_t *preTrampAvailableEntries, virt_addr *postTrampBuffer, uint32_t *postTrampAvailableEntries, int32_t tlsModuleIndex, uint32_t fileType) { auto target = targetSectionBuffer + offset; auto value = symbolValue + addend; auto relValue = value - static_cast<uint32_t>(target); if (isRpx) { // SDA based relocations are only valid for a .rpx switch (relaType) { case rpl::R_PPC_EMB_SDA21: { auto ins = espresso::Instruction { +*virt_cast<uint32_t *>(target) }; if (ins.rA == 2) { value -= getGlobalStorage()->sda2Base; } else if (ins.rA == 13) { value -= getGlobalStorage()->sdaBase; } else if (ins.rA != 0) { Loader_ReportError("***ERROR: SDA21_HI malformed.\n"); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 166); return -470039; } ins.simm = static_cast<uint16_t>(value); *virt_cast<uint32_t *>(target) = ins.value; return 0; } case rpl::R_PPC_EMB_RELSDA: case rpl::R_PPC_DIAB_SDA21_LO: case rpl::R_PPC_DIAB_SDA21_HI: case rpl::R_PPC_DIAB_SDA21_HA: case rpl::R_PPC_DIAB_RELSDA_LO: case rpl::R_PPC_DIAB_RELSDA_HI: case rpl::R_PPC_DIAB_RELSDA_HA: // TODO: Implement these relocation types decaf_abort(fmt::format("Unimplemented relocation type {}", relaType)); return -470038; } } switch (relaType) { case rpl::R_PPC_NONE: break; case rpl::R_PPC_ADDR32: *virt_cast<uint32_t *>(target) = value; break; case rpl::R_PPC_ADDR16_LO: *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(value & 0xFFFF); break; case rpl::R_PPC_ADDR16_HI: *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(value >> 16); break; case rpl::R_PPC_ADDR16_HA: *virt_cast<uint16_t *>(target) = static_cast<uint16_t>((value + 0x8000) >> 16); break; case rpl::R_PPC_DTPMOD32: *virt_cast<int32_t *>(target) = tlsModuleIndex; break; case rpl::R_PPC_DTPREL32: *virt_cast<uint32_t *>(target) = value; break; case rpl::R_PPC_GHS_REL16_HA: *virt_cast<uint16_t *>(target) = static_cast<uint16_t>((relValue + 0x8000) >> 16); break; case rpl::R_PPC_GHS_REL16_HI: *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(relValue >> 16); break; case rpl::R_PPC_GHS_REL16_LO: *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(relValue & 0xFFFF); break; case rpl::R_PPC_REL14: { if (isWeakSymbol && !symbolValue) { symbolValue = static_cast<uint32_t>(target); value = symbolValue + addend; } auto distance = static_cast<int32_t>(value) - static_cast<int32_t>(target); if (distance > 0x7FFC || distance < -0x7FFC) { Loader_ReportError("***14-bit relative branch cannot hit target."); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 398); return -470039; } if (distance & 3) { Loader_ReportError("***RELOC ERROR {}: lower 2 bits must be zero before shifting.", -470040); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 408); return -470040; } if ((distance >= 0 && (distance & 0xFFFF8000)) || (distance < 0 && ((distance & 0xFFFF8000) != 0xFFFF8000))) { Loader_ReportError( "***RELOC ERROR {}: upper 17 bits before shift must all be the same.", -470040); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 429); return -470040; } *virt_cast<uint32_t *>(target) = (*virt_cast<uint32_t *>(target) & 0xFFBF0003) | static_cast<uint16_t>(distance & 0xFFFC); break; } case rpl::R_PPC_REL24: { if (isWeakSymbol && !symbolValue) { symbolValue = static_cast<uint32_t>(target); value = symbolValue + addend; } // Check if we need to generate a trampoline auto distance = static_cast<int32_t>(value) - static_cast<int32_t>(target); if (distance > 0x1FFFFFC || distance < -0x1FFFFFC) { auto tramp = virt_ptr<uint32_t> { nullptr }; if (*postTrampBuffer - target <= 0x1FFFFFC) { if (*postTrampAvailableEntries == 0) { Loader_ReportError("Post tramp buffer exhausted."); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 323); return -470037; } tramp = virt_cast<uint32_t *>(*postTrampBuffer); *postTrampBuffer += 16; *postTrampAvailableEntries -= 1; } else if (target - *preTrampBuffer <= 0x1FFFFFC) { if (*preTrampAvailableEntries == 0) { Loader_ReportError("Pre tramp buffer exhausted."); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 310); return -470037; } tramp = virt_cast<uint32_t *>(*preTrampBuffer); *preTrampBuffer -= 16; *preTrampAvailableEntries -= 1; } else { Loader_ReportError("**Cannot link 24-bit jump (too far to tramp buffer)."); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 303); return -470037; } /* Write the trampoline: * lis r11, 0 * ori r11, r11, 0 * mtctr r11 * bctr */ auto lis = espresso::encodeInstruction(espresso::InstructionID::addis); lis.rD = 11; lis.rA = 0; lis.simm = (value >> 16) & 0xFFFF; auto ori = espresso::encodeInstruction(espresso::InstructionID::ori); ori.rA = 11; ori.rS = 11; ori.uimm = value & 0xFFFF; auto mtctr = espresso::encodeInstruction(espresso::InstructionID::mtspr); mtctr.rS = 11; espresso::encodeSPR(mtctr, espresso::SPR::CTR); auto bctr = espresso::encodeInstruction(espresso::InstructionID::bcctr); bctr.bo = 0b10100; tramp[0] = lis.value; tramp[1] = ori.value; tramp[2] = mtctr.value; tramp[3] = bctr.value; symbolValue = static_cast<uint32_t>(virt_cast<virt_addr>(tramp)); value = symbolValue + addend; distance = static_cast<int32_t>(value) - static_cast<int32_t>(target); } if (distance & 3) { Loader_ReportError( "***RELOC ERROR {}: lower 2 bits must be zero before shifting.", -470022); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 348); return -470037; } if (distance < 0 && (distance & 0xFE000000) != 0xFE000000) { Loader_ReportError( "***RELOC ERROR {}: upper 7 bits before shift must all be the same (1).", -470040); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 359); return -470038; } if (distance >= 0 && (distance & 0xFE000000)) { Loader_ReportError( "***RELOC ERROR {}: upper 7 bits before shift must all be the same (0).", -470040); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 371); return -470038; } *virt_cast<uint32_t *>(target) = (*virt_cast<uint32_t *>(target) & 0xFC000003) | (static_cast<uint32_t>(distance) & 0x3FFFFFC); break; } default: Loader_ReportError("***ERROR: Unsupported Relocation_Add Type ({}):", relaType); LiSetFatalError(0x18729Bu, fileType, 1, "LiCpu_RelocAdd", 462); return -470038; } return 0; } static int32_t sExecReloc(virt_ptr<LOADED_RPL> rpl, bool isRpx, uint32_t sectionIndex, virt_ptr<rpl::SectionHeader> sectionHeader, virt_addr *preTrampBuffer, uint32_t *preTrampBufferSize, virt_addr *postTrampBuffer, uint32_t *postTrampBufferSize, virt_ptr<LiImportTracking> imports) { if (sectionHeader->info >= rpl->elfHeader.shnum || !rpl->sectionAddressBuffer[sectionHeader->info]) { return 0; } auto targetSectionBuffer = rpl->sectionAddressBuffer[sectionHeader->info]; auto targetSectionVirtualAddress = getSectionHeader(rpl, sectionHeader->info)->addr; if (sectionHeader->link >= rpl->elfHeader.shnum || !rpl->sectionAddressBuffer[sectionHeader->link]) { Loader_ReportError("*** relocations symbol table missing."); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 93); return -470003; } auto symbolSectionHeader = getSectionHeader(rpl, sectionHeader->link); auto symbolSectionEntSize = symbolSectionHeader->entsize; auto symbolSection = rpl->sectionAddressBuffer[sectionHeader->link]; if (!symbolSectionEntSize) { symbolSectionEntSize = static_cast<uint32_t>(sizeof(rpl::Symbol)); } auto symbolCount = symbolSectionHeader->size / symbolSectionEntSize; if (symbolCount == 0) { Loader_ReportError("*** relocations symbol table empty."); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 107); return -470004; } auto symbolStringTable = virt_ptr<char> { nullptr }; if (symbolSectionHeader->link < rpl->elfHeader.shnum) { symbolStringTable = virt_cast<char *>(rpl->sectionAddressBuffer[symbolSectionHeader->link]); } auto relaEntSize = sectionHeader->entsize; auto relaSectionSize = sectionHeader->size; auto relaSectionAddress = rpl->sectionAddressBuffer[sectionIndex]; if (sectionHeader->flags & rpl::SHF_DEFLATED) { relaSectionSize = *virt_cast<uint32_t *>(relaSectionAddress); } if (!relaEntSize) { relaEntSize = static_cast<uint32_t>(sizeof(rpl::Rela)); } else if (relaEntSize != sizeof(rpl::Rela)) { Loader_ReportError( "*** sExecReloc: relocation section sh_entsize is not {}; err = {}", sizeof(rpl::Rela), -470052); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 144); return -470052; } auto relaCount = relaSectionSize / relaEntSize; if (relaSectionSize != relaCount * relaEntSize) { Loader_ReportError( "*** sExecReloc: relocation section size not a multiple of %d; err = %d.\n", sizeof(rpl::Rela), -470053); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 154); return -470053; } if (relaCount <= 0) { return 0; } auto stream = z_stream { }; std::memset(&stream, 0, sizeof(stream)); if (sectionHeader->flags & rpl::SHF_DEFLATED) { auto zlibError = inflateInit(&stream); if (zlibError != Z_OK) { switch (zlibError) { case Z_STREAM_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 186); Loader_ReportError("*** sExecReloc: could not initialize ZLIB uncompressor; ZLIB err = {}.", Error::ZlibStreamError); return Error::ZlibStreamError; case Z_MEM_ERROR: LiSetFatalError(0x187298u, rpl->fileType, 0, "sExecReloc", 178); Loader_ReportError("*** sExecReloc: could not initialize ZLIB uncompressor; ZLIB err = {}.", Error::ZlibMemError); return Error::ZlibMemError; case Z_VERSION_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 182); Loader_ReportError("*** sExecReloc: could not initialize ZLIB uncompressor; ZLIB err = {}.", Error::ZlibVersionError); return Error::ZlibVersionError; default: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 191); Loader_ReportError("***Unknown ZLIB error {} (0x{:08X}).", zlibError, zlibError); return Error::ZlibUnknownError; } } stream.avail_in = sectionHeader->size; stream.next_in = virt_cast<Bytef *>(relaSectionAddress + 4).get(); } auto remainingBytes = relaSectionSize; auto remainingRela = relaCount; auto zlibError = Z_STREAM_END; while (remainingBytes > 0) { // Read whole shit auto availableBytes = remainingBytes; auto relas = virt_cast<rpl::Rela *>(relaSectionAddress).get(); auto error = 0; if (sectionHeader->flags & rpl::SHF_DEFLATED) { availableBytes = std::min<uInt>(remainingBytes, static_cast<uInt>(sRelocBuffer.size())); stream.avail_out = availableBytes; stream.next_out = reinterpret_cast<Bytef *>(sRelocBuffer.data()); LiCheckAndHandleInterrupts(); zlibError = inflate(&stream, Z_NO_FLUSH); if (zlibError != Z_BUF_ERROR && (zlibError < Z_OK || zlibError == Z_NEED_DICT)) { switch (zlibError) { case Z_MEM_ERROR: LiSetFatalError(0x187298u, rpl->fileType, 0, "sExecReloc", 236); error = Error::ZlibMemError; break; case Z_NEED_DICT: zlibError = Z_DATA_ERROR; // fallthrough case Z_DATA_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 240); error = Error::ZlibDataError; break; case Z_STREAM_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 244); error = Error::ZlibStreamError; break; default: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 191); error = Error::ZlibUnknownError; break; } Loader_ReportError("*** sExecReloc: unable to uncompress relocation section; ZLIB err = {}.", zlibError); inflateEnd(&stream); return error; } if (stream.avail_out) { Loader_ReportError("*** sExecReloc: ZLIB inflate's avail_out is {}, expected 0; err = {}.", stream.avail_out, -470055); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 254); inflateEnd(&stream); return -470055; } if (stream.total_out != sizeof(rpl::Rela) * (stream.total_out / sizeof(rpl::Rela))) { Loader_ReportError( "*** sExecReloc: inflate did not read a multiple of sizeof(ELF32_RELOC_ADD); err = {}.", -470055); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 261); inflateEnd(&stream); return -470055; } relas = reinterpret_cast<rpl::Rela *>(sRelocBuffer.data()); } auto availableRela = availableBytes / sizeof(rpl::Rela); for (auto relaIndex = 0u; relaIndex < availableRela; ++relaIndex) { auto &rela = relas[relaIndex]; auto symbolIndex = rela.info >> 8; auto relaType = static_cast<rpl::RelocationType>(rela.info & 0xFF); LiCheckAndHandleInterrupts(); if (symbolIndex > symbolCount) { Loader_ReportError( "***{} Rel invalid: rel ix {}, ref sym ix {}, max is {}. type {}", sectionIndex, relaIndex, symbolIndex, symbolCount, static_cast<int>(relaType)); Loader_ReportError("*** r_info 0x{:08X}\n", rela.offset); Loader_ReportError("*** r_addend 0x{:08X}\n", rela.addend); Loader_ReportError("*** r_offset 0x{:08X}\n", rela.offset); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 286); error = -470005; break; } auto symbol = virt_cast<rpl::Symbol *>(symbolSection + symbolIndex * symbolSectionEntSize); auto symbolValue = symbol->value; auto symbolBinding = symbol->info >> 4; auto symbolType = symbol->info & 0xf; if ((symbolValue & 0xFF000000) == 0xCD000000) { if (symbolBinding != rpl::STB_WEAK) { if (symbolStringTable) { Loader_ReportError( "***Rel invalid: sym ix {} val 0x{:08X} \"{}\"", symbolIndex, symbolValue, symbolStringTable + symbol->name); } else { Loader_ReportError( "***Rel invalid: sym ix {} val 0x{:08X}", symbolIndex, symbolValue); } LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 308); error = -470006; break; } else { symbolValue = 0u; } } auto tlsModuleIndex = -1; if (relaType == rpl::R_PPC_DTPMOD32 || relaType == rpl::R_PPC_DTPREL32) { if (symbolType != rpl::STT_TLS) { Loader_ReportError("***TLS relocation used with non-TLS symbol 0x{:08X}", symbolValue); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 349); error = -470065; break; } if (imports && imports[symbol->shndx].numExports) { tlsModuleIndex = imports[symbol->shndx].tlsModuleIndex; if (tlsModuleIndex == -1) { Loader_ReportError("***TLS relocation used with non-TLS import module 0x{:08X}.", symbolValue); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 327); error = -470101; break; } } else { tlsModuleIndex = rpl->fileInfoBuffer->tlsModuleIndex; if (tlsModuleIndex == -1) { Loader_ReportError("***TLS relocation used with non-TLS module 0x{:08X}.", symbolValue); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 339); error = -470102; break; } } } error = LiCpu_RelocAdd(isRpx, targetSectionBuffer, relaType, symbolBinding == rpl::STB_WEAK, rela.offset - targetSectionVirtualAddress, rela.addend, symbolValue, preTrampBuffer, preTrampBufferSize, postTrampBuffer, postTrampBufferSize, tlsModuleIndex, rpl->fileType); if (error) { break; } --remainingRela; } if (error) { if (sectionHeader->flags & rpl::SHF_DEFLATED) { inflateEnd(&stream); } return error; } remainingBytes -= availableBytes; } if (sectionHeader->flags & rpl::SHF_DEFLATED) { inflateEnd(&stream); } if (remainingRela) { Loader_ReportError( "*** sExecReloc: not all relocations were uncompressed; err = {}.", -470053); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 379); return -470053; } if (zlibError != Z_STREAM_END) { Loader_ReportError( "*** sExecReloc: streaming failure err (zlib_ret != Z_STREAM_END) = {}.", -470055); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sExecReloc", 404); return -470055; } LiCheckAndHandleInterrupts(); return 0; } int32_t LiFixupRelocOneRPL(virt_ptr<LOADED_RPL> rpl, virt_ptr<LiImportTracking> imports, uint32_t unk) { LiCheckAndHandleInterrupts(); // Apply imports to symbol table for (auto i = 1u; i < static_cast<unsigned int>(rpl->elfHeader.shnum - 2); ++i) { auto sectionHeader = getSectionHeader(rpl, i); if (sectionHeader->type == rpl::SHT_SYMTAB) { if (auto error = sFixupOneSymbolTable(rpl, i, sectionHeader, imports, unk)) { return error; } } } auto isRpx = rpl == getGlobalStorage()->loadedRpx; auto textAddress = virt_cast<virt_addr>(rpl->textBuffer); // Pre tramp buffer grows downawrds auto preTrampBufferEnd = (textAddress + rpl->fileInfoBuffer->trampAdjust) & 0xFFFFFFF0u; auto preTrampNext = preTrampBufferEnd - TrampSize; auto preTrampAvailable = static_cast<uint32_t>((preTrampBufferEnd - textAddress) / TrampSize); // Post tramp buffer grows upwards auto postTrampBufferSize = (textAddress + rpl->fileInfoBuffer->textSize) - rpl->postTrampBuffer; auto postTrampNext = virt_addr { rpl->postTrampBuffer }; auto postTrampAvailable = static_cast<uint32_t>(postTrampBufferSize / TrampSize); auto textMax = rpl->postTrampBuffer + (postTrampAvailable * TrampSize); // Apply relocations for (auto i = 1u; i < static_cast<unsigned int>(rpl->elfHeader.shnum - 2); ++i) { auto sectionHeader = getSectionHeader(rpl, i); if (sectionHeader->type == rpl::SHT_RELA) { if (auto error = sExecReloc(rpl, isRpx, i, sectionHeader, &preTrampNext, &preTrampAvailable, &postTrampNext, &postTrampAvailable, imports)) { return error; } } } // Flush the code cache if (textAddress) { if (textMax - textAddress > rpl->textBufferSize) { Loader_ReportError("*** Fatal error: code overflowed its allocation."); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiFixupRelocOneRPL", 695); return -470069; } LiSafeFlushCode(textAddress, rpl->textBufferSize); } // Relocate entry point rpl->entryPoint = virt_addr { 0 }; for (auto i = 1u; i < static_cast<unsigned int>(rpl->elfHeader.shnum - 2); ++i) { auto sectionHeader = getSectionHeader(rpl, i); auto sectionAddress = rpl->sectionAddressBuffer[i]; LiCheckAndHandleInterrupts(); if (!sectionHeader->size || !sectionAddress) { continue; } if (sectionHeader->type != rpl::SHT_PROGBITS || !(sectionHeader->flags & rpl::SHF_EXECINSTR)) { Loader_FlushDataRangeNoSync(sectionAddress, sectionHeader->size); } if (rpl->elfHeader.entry >= sectionHeader->addr && rpl->elfHeader.entry < sectionHeader->addr + sectionHeader->size) { rpl->entryPoint = sectionAddress + (rpl->elfHeader.entry - sectionHeader->addr); break; } } if (!rpl->entryPoint) { Loader_ReportError("*** Could not find/relocate module entry point. this is required.\n"); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiFixupRelocOneRPL", 740); return -470092; } rpl->loadStateFlags &= ~LoaderStateFlag2; return 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_reloc.h ================================================ #pragma once #include "cafe_loader_basics.h" #include "cafe_loader_rpl.h" #include <libcpu/be2_struct.h> namespace cafe::loader::internal { struct LiImportTracking { be2_val<uint32_t> numExports; be2_virt_ptr<rpl::Export> exports; be2_val<uint32_t> tlsModuleIndex; be2_virt_ptr<LOADED_RPL> rpl; }; CHECK_OFFSET(LiImportTracking, 0x00, numExports); CHECK_OFFSET(LiImportTracking, 0x04, exports); CHECK_OFFSET(LiImportTracking, 0x08, tlsModuleIndex); CHECK_OFFSET(LiImportTracking, 0x0C, rpl); CHECK_SIZE(LiImportTracking, 0x10); int32_t LiFixupRelocOneRPL(virt_ptr<LOADED_RPL> rpl, virt_ptr<LiImportTracking> imports, uint32_t unk); } // namespace cafe::loader::internal; ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_rpl.h ================================================ #pragma once #include <libcpu/be2_struct.h> #pragma pack(push, 1) namespace cafe::loader::rpl { enum Machine : uint16_t { EM_PPC = 20, }; enum Encoding : uint8_t { ELFDATANONE = 0, ELFDATA2LSB = 1, ELFDATA2MSB = 2, }; enum Class : uint8_t { ELFCLASS32 = 1, }; enum : uint8_t { EABI_CAFE = 0xCA, }; enum : uint8_t { EABI_VERSION_CAFE = 0xFE, }; enum Version : uint8_t { EV_NONE = 0, EV_CURRENT = 1, }; enum SectionFlags : uint32_t { SHF_WRITE = 0x1, SHF_ALLOC = 0x2, SHF_EXECINSTR = 0x4, SHF_TLS = 0x04000000, SHF_DEFLATED = 0x08000000, SHF_MASKPROC = 0xF0000000, }; enum SectionType : uint32_t { SHT_NULL = 0, SHT_PROGBITS = 1, SHT_SYMTAB = 2, SHT_STRTAB = 3, SHT_RELA = 4, SHT_HASH = 5, SHT_DYNAMIC = 6, SHT_NOTE = 7, SHT_NOBITS = 8, SHT_REL = 9, SHT_SHLIB = 10, SHT_DYNSYM = 11, SHT_INIT_ARRAY = 14, SHT_FINI_ARRAY = 15, SHT_PREINIT_ARRAY = 16, SHT_GROUP = 17, SHT_SYMTAB_SHNDX = 18, SHT_LOPROC = 0x70000000u, SHT_HIPROC = 0x7fffffffu, SHT_LOUSER = 0x80000000u, SHT_RPL_EXPORTS = 0x80000001u, SHT_RPL_IMPORTS = 0x80000002u, SHT_RPL_CRCS = 0x80000003u, SHT_RPL_FILEINFO = 0x80000004u, SHT_HIUSER = 0xffffffffu, }; enum SymbolBinding : uint32_t { STB_LOCAL = 0, STB_GLOBAL = 1, STB_WEAK = 2, STB_GNU_UNIQUE = 10, STB_LOOS = 10, STB_HIOS = 12, STB_LOPROC = 13, STB_HIPROC = 15, }; enum SymbolType : uint32_t { STT_NOTYPE = 0, STT_OBJECT = 1, STT_FUNC = 2, STT_SECTION = 3, STT_FILE = 4, STT_COMMON = 5, STT_TLS = 6, STT_LOOS = 7, STT_HIOS = 8, STT_GNU_IFUNC = 10, STT_LOPROC = 13, STT_HIPROC = 15, }; enum SectionIndex : uint16_t { SHN_UNDEF = 0, SHN_LORESERVE = 0xff00, SHN_ABS = 0xfff1, SHN_COMMON = 0xfff2, SHN_XINDEX = 0xffff, SHN_HIRESERVE = 0xffff }; enum RelocationType : uint32_t { R_PPC_NONE = 0, R_PPC_ADDR32 = 1, R_PPC_ADDR16_LO = 4, R_PPC_ADDR16_HI = 5, R_PPC_ADDR16_HA = 6, R_PPC_REL24 = 10, R_PPC_REL14 = 11, R_PPC_DTPMOD32 = 68, R_PPC_DTPREL32 = 78, R_PPC_EMB_SDA21 = 109, R_PPC_EMB_RELSDA = 116, R_PPC_DIAB_SDA21_LO = 180, R_PPC_DIAB_SDA21_HI = 181, R_PPC_DIAB_SDA21_HA = 182, R_PPC_DIAB_RELSDA_LO = 183, R_PPC_DIAB_RELSDA_HI = 184, R_PPC_DIAB_RELSDA_HA = 185, R_PPC_GHS_REL16_HA = 251, R_PPC_GHS_REL16_HI = 252, R_PPC_GHS_REL16_LO = 253, }; enum FileInfoFlags : uint32_t { RPL_IS_RPX = 1 << 1, RPL_FLAG_4 = 1 << 2, RPL_HAS_TLS = 1 << 3, }; struct Header { be2_array<uint8_t, 4> magic; // File identification. be2_val<uint8_t> fileClass; // File class. be2_val<uint8_t> encoding; // Data encoding. be2_val<uint8_t> elfVersion; // File version. be2_val<uint8_t> abi; // OS/ABI identification. be2_val<uint8_t> abiVersion; // OS/ABI version. be2_array<uint8_t, 7> pad; be2_val<uint16_t> type; // Type of file (ET_*) be2_val<uint16_t> machine; // Required architecture for this file (EM_*) be2_val<uint32_t> version; // Must be equal to 1 be2_val<uint32_t> entry; // Address to jump to in order to start program be2_val<uint32_t> phoff; // Program header table's file offset, in bytes be2_val<uint32_t> shoff; // Section header table's file offset, in bytes be2_val<uint32_t> flags; // Processor-specific flags be2_val<uint16_t> ehsize; // Size of ELF header, in bytes be2_val<uint16_t> phentsize; // Size of an entry in the program header table be2_val<uint16_t> phnum; // Number of entries in the program header table be2_val<uint16_t> shentsize; // Size of an entry in the section header table be2_val<uint16_t> shnum; // Number of entries in the section header table be2_val<uint16_t> shstrndx; // Sect hdr table index of sect name string table }; CHECK_OFFSET(Header, 0x00, magic); CHECK_OFFSET(Header, 0x04, fileClass); CHECK_OFFSET(Header, 0x05, encoding); CHECK_OFFSET(Header, 0x06, elfVersion); CHECK_OFFSET(Header, 0x07, abi); CHECK_OFFSET(Header, 0x08, abiVersion); CHECK_OFFSET(Header, 0x10, type); CHECK_OFFSET(Header, 0x12, machine); CHECK_OFFSET(Header, 0x14, version); CHECK_OFFSET(Header, 0x18, entry); CHECK_OFFSET(Header, 0x1C, phoff); CHECK_OFFSET(Header, 0x20, shoff); CHECK_OFFSET(Header, 0x24, flags); CHECK_OFFSET(Header, 0x28, ehsize); CHECK_OFFSET(Header, 0x2A, phentsize); CHECK_OFFSET(Header, 0x2C, phnum); CHECK_OFFSET(Header, 0x2E, shentsize); CHECK_OFFSET(Header, 0x30, shnum); CHECK_OFFSET(Header, 0x32, shstrndx); CHECK_SIZE(Header, 0x34); struct ProgramHeader { be2_val<uint32_t> type; be2_val<uint32_t> offset; be2_val<uint32_t> vaddr; be2_val<uint32_t> paddr; be2_val<uint32_t> filesz; be2_val<uint32_t> memsz; be2_val<uint32_t> flags; be2_val<uint32_t> align; }; CHECK_OFFSET(ProgramHeader, 0x00, type); CHECK_OFFSET(ProgramHeader, 0x04, offset); CHECK_OFFSET(ProgramHeader, 0x08, vaddr); CHECK_OFFSET(ProgramHeader, 0x0C, paddr); CHECK_OFFSET(ProgramHeader, 0x10, filesz); CHECK_OFFSET(ProgramHeader, 0x14, memsz); CHECK_OFFSET(ProgramHeader, 0x18, flags); CHECK_OFFSET(ProgramHeader, 0x1C, align); CHECK_SIZE(ProgramHeader, 0x20); struct SectionHeader { //! Section name (index into string table) be2_val<uint32_t> name; //! Section type (SHT_*) be2_val<uint32_t> type; //! Section flags (SHF_*) be2_val<uint32_t> flags; //! Address where section is to be loaded be2_val<uint32_t> addr; //! File offset of section data, in bytes be2_val<uint32_t> offset; //! Size of section, in bytes be2_val<uint32_t> size; //! Section type-specific header table index link be2_val<uint32_t> link; //! Section type-specific extra information be2_val<uint32_t> info; //! Section address alignment be2_val<uint32_t> addralign; //! Size of records contained within the section be2_val<uint32_t> entsize; }; CHECK_OFFSET(SectionHeader, 0x00, name); CHECK_OFFSET(SectionHeader, 0x04, type); CHECK_OFFSET(SectionHeader, 0x08, flags); CHECK_OFFSET(SectionHeader, 0x0C, addr); CHECK_OFFSET(SectionHeader, 0x10, offset); CHECK_OFFSET(SectionHeader, 0x14, size); CHECK_OFFSET(SectionHeader, 0x18, link); CHECK_OFFSET(SectionHeader, 0x1C, info); CHECK_OFFSET(SectionHeader, 0x20, addralign); CHECK_OFFSET(SectionHeader, 0x24, entsize); CHECK_SIZE(SectionHeader, 0x28); struct Symbol { //! Symbol name (index into string table) be2_val<uint32_t> name; //! Value or address associated with the symbol be2_val<uint32_t> value; //! Size of the symbol be2_val<uint32_t> size; //! Symbol's type and binding attributes be2_val<uint8_t> info; //! Must be zero; reserved be2_val<uint8_t> other; //! Which section (header table index) it's defined in (SHN_*) be2_val<uint16_t> shndx; }; CHECK_OFFSET(Symbol, 0x00, name); CHECK_OFFSET(Symbol, 0x04, value); CHECK_OFFSET(Symbol, 0x08, size); CHECK_OFFSET(Symbol, 0x0C, info); CHECK_OFFSET(Symbol, 0x0D, other); CHECK_OFFSET(Symbol, 0x0E, shndx); CHECK_SIZE(Symbol, 0x10); struct Rela { be2_val<uint32_t> offset; be2_val<uint32_t> info; be2_val<int32_t> addend; }; CHECK_OFFSET(Rela, 0x00, offset); CHECK_OFFSET(Rela, 0x04, info); CHECK_OFFSET(Rela, 0x08, addend); CHECK_SIZE(Rela, 0x0C); struct Imports { be2_val<uint32_t> count; be2_val<uint32_t> signature; }; CHECK_OFFSET(Imports, 0x00, count); CHECK_OFFSET(Imports, 0x04, signature); CHECK_SIZE(Imports, 0x08); struct Exports { be2_val<uint32_t> count; be2_val<uint32_t> signature; }; CHECK_OFFSET(Exports, 0x00, count); CHECK_OFFSET(Exports, 0x04, signature); CHECK_SIZE(Exports, 0x08); struct Export { be2_val<uint32_t> value; be2_val<uint32_t> name; }; CHECK_OFFSET(Export, 0x00, value); CHECK_OFFSET(Export, 0x04, name); CHECK_SIZE(Export, 0x08); struct DeflatedHeader { be2_val<uint32_t> inflatedSize; }; CHECK_OFFSET(DeflatedHeader, 0x00, inflatedSize); CHECK_SIZE(DeflatedHeader, 0x04); struct RplCrc { be2_val<uint32_t> crc; }; CHECK_OFFSET(RplCrc, 0x00, crc); CHECK_SIZE(RplCrc, 0x04); struct RPLFileInfo_v3_0 { static constexpr auto Version = 0xCAFE0300u; be2_val<uint32_t> version; be2_val<uint32_t> textSize; be2_val<uint32_t> textAlign; be2_val<uint32_t> dataSize; be2_val<uint32_t> dataAlign; be2_val<uint32_t> loadSize; be2_val<uint32_t> loadAlign; be2_val<uint32_t> tempSize; be2_val<uint32_t> trampAdjust; be2_val<uint32_t> sdaBase; be2_val<uint32_t> sda2Base; be2_val<uint32_t> stackSize; be2_val<uint32_t> filename; UNKNOWN(0x0C); }; CHECK_OFFSET(RPLFileInfo_v3_0, 0x00, version); CHECK_OFFSET(RPLFileInfo_v3_0, 0x04, textSize); CHECK_OFFSET(RPLFileInfo_v3_0, 0x08, textAlign); CHECK_OFFSET(RPLFileInfo_v3_0, 0x0C, dataSize); CHECK_OFFSET(RPLFileInfo_v3_0, 0x10, dataAlign); CHECK_OFFSET(RPLFileInfo_v3_0, 0x14, loadSize); CHECK_OFFSET(RPLFileInfo_v3_0, 0x18, loadAlign); CHECK_OFFSET(RPLFileInfo_v3_0, 0x1C, tempSize); CHECK_OFFSET(RPLFileInfo_v3_0, 0x20, trampAdjust); CHECK_OFFSET(RPLFileInfo_v3_0, 0x24, sdaBase); CHECK_OFFSET(RPLFileInfo_v3_0, 0x28, sda2Base); CHECK_OFFSET(RPLFileInfo_v3_0, 0x2C, stackSize); CHECK_OFFSET(RPLFileInfo_v3_0, 0x30, filename); CHECK_SIZE(RPLFileInfo_v3_0, 0x40); struct RPLFileInfo_v4_1 { static constexpr auto Version = 0xCAFE0401u; be2_val<uint32_t> version; be2_val<uint32_t> textSize; be2_val<uint32_t> textAlign; be2_val<uint32_t> dataSize; be2_val<uint32_t> dataAlign; be2_val<uint32_t> loadSize; be2_val<uint32_t> loadAlign; be2_val<uint32_t> tempSize; be2_val<uint32_t> trampAdjust; be2_val<uint32_t> sdaBase; be2_val<uint32_t> sda2Base; be2_val<uint32_t> stackSize; be2_val<uint32_t> filename; be2_val<uint32_t> flags; be2_val<uint32_t> heapSize; be2_val<uint32_t> tagOffset; }; CHECK_OFFSET(RPLFileInfo_v4_1, 0x00, version); CHECK_OFFSET(RPLFileInfo_v4_1, 0x04, textSize); CHECK_OFFSET(RPLFileInfo_v4_1, 0x08, textAlign); CHECK_OFFSET(RPLFileInfo_v4_1, 0x0C, dataSize); CHECK_OFFSET(RPLFileInfo_v4_1, 0x10, dataAlign); CHECK_OFFSET(RPLFileInfo_v4_1, 0x14, loadSize); CHECK_OFFSET(RPLFileInfo_v4_1, 0x18, loadAlign); CHECK_OFFSET(RPLFileInfo_v4_1, 0x1C, tempSize); CHECK_OFFSET(RPLFileInfo_v4_1, 0x20, trampAdjust); CHECK_OFFSET(RPLFileInfo_v4_1, 0x24, sdaBase); CHECK_OFFSET(RPLFileInfo_v4_1, 0x28, sda2Base); CHECK_OFFSET(RPLFileInfo_v4_1, 0x2C, stackSize); CHECK_OFFSET(RPLFileInfo_v4_1, 0x30, filename); CHECK_OFFSET(RPLFileInfo_v4_1, 0x34, flags); CHECK_OFFSET(RPLFileInfo_v4_1, 0x38, heapSize); CHECK_OFFSET(RPLFileInfo_v4_1, 0x3C, tagOffset); CHECK_SIZE(RPLFileInfo_v4_1, 0x40); struct RPLFileInfo_v4_2 { static constexpr auto Version = 0xCAFE0402u; be2_val<uint32_t> version; be2_val<uint32_t> textSize; be2_val<uint32_t> textAlign; be2_val<uint32_t> dataSize; be2_val<uint32_t> dataAlign; be2_val<uint32_t> loadSize; be2_val<uint32_t> loadAlign; be2_val<uint32_t> tempSize; be2_val<uint32_t> trampAdjust; be2_val<uint32_t> sdaBase; be2_val<uint32_t> sda2Base; be2_val<uint32_t> stackSize; be2_val<uint32_t> filename; be2_val<uint32_t> flags; be2_val<uint32_t> heapSize; be2_val<uint32_t> tagOffset; be2_val<uint32_t> minVersion; be2_val<int32_t> compressionLevel; be2_val<uint32_t> trampAddition; be2_val<uint32_t> fileInfoPad; be2_val<uint32_t> cafeSdkVersion; be2_val<uint32_t> cafeSdkRevision; be2_val<int16_t> tlsModuleIndex; be2_val<uint16_t> tlsAlignShift; be2_val<uint32_t> runtimeFileInfoSize; }; CHECK_OFFSET(RPLFileInfo_v4_2, 0x00, version); CHECK_OFFSET(RPLFileInfo_v4_2, 0x04, textSize); CHECK_OFFSET(RPLFileInfo_v4_2, 0x08, textAlign); CHECK_OFFSET(RPLFileInfo_v4_2, 0x0C, dataSize); CHECK_OFFSET(RPLFileInfo_v4_2, 0x10, dataAlign); CHECK_OFFSET(RPLFileInfo_v4_2, 0x14, loadSize); CHECK_OFFSET(RPLFileInfo_v4_2, 0x18, loadAlign); CHECK_OFFSET(RPLFileInfo_v4_2, 0x1C, tempSize); CHECK_OFFSET(RPLFileInfo_v4_2, 0x20, trampAdjust); CHECK_OFFSET(RPLFileInfo_v4_2, 0x24, sdaBase); CHECK_OFFSET(RPLFileInfo_v4_2, 0x28, sda2Base); CHECK_OFFSET(RPLFileInfo_v4_2, 0x2C, stackSize); CHECK_OFFSET(RPLFileInfo_v4_2, 0x30, filename); CHECK_OFFSET(RPLFileInfo_v4_2, 0x34, flags); CHECK_OFFSET(RPLFileInfo_v4_2, 0x38, heapSize); CHECK_OFFSET(RPLFileInfo_v4_2, 0x3C, tagOffset); CHECK_OFFSET(RPLFileInfo_v4_2, 0x40, minVersion); CHECK_OFFSET(RPLFileInfo_v4_2, 0x44, compressionLevel); CHECK_OFFSET(RPLFileInfo_v4_2, 0x48, trampAddition); CHECK_OFFSET(RPLFileInfo_v4_2, 0x4C, fileInfoPad); CHECK_OFFSET(RPLFileInfo_v4_2, 0x50, cafeSdkVersion); CHECK_OFFSET(RPLFileInfo_v4_2, 0x54, cafeSdkRevision); CHECK_OFFSET(RPLFileInfo_v4_2, 0x58, tlsModuleIndex); CHECK_OFFSET(RPLFileInfo_v4_2, 0x5A, tlsAlignShift); CHECK_OFFSET(RPLFileInfo_v4_2, 0x5C, runtimeFileInfoSize); CHECK_SIZE(RPLFileInfo_v4_2, 0x60); } // namespace cafe::loader::rpl #pragma pack(pop) ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_setup.cpp ================================================ #include "cafe_loader_bounce.h" #include "cafe_loader_entry.h" #include "cafe_loader_error.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_iop.h" #include "cafe_loader_purge.h" #include "cafe_loader_setup.h" #include "cafe_loader_query.h" #include "cafe_loader_log.h" #include "cafe_loader_minfileinfo.h" #include "cafe/cafe_stackobject.h" #include <array> #include <common/align.h> #include <libcpu/cpu_formatters.h> #include <zlib.h> namespace cafe::loader::internal { struct SegmentBounds { uint32_t min = 0xffffffffu; uint32_t max = 0; uint32_t allocMax = 0; const char *name = nullptr; }; struct RplSegmentBounds { SegmentBounds data; SegmentBounds load; SegmentBounds text; SegmentBounds temp; SegmentBounds &operator[](size_t idx) { if (idx == 0) { return data; } else if (idx == 1) { return load; } else if (idx == 2) { return text; } else { return temp; } } }; static void LiClearUserBss(bool userHasControl, kernel::UniqueProcessId upid, virt_ptr<void> base, uint32_t size) { if (!userHasControl) { // Check upid if (upid == kernel::UniqueProcessId::HomeMenu || upid == kernel::UniqueProcessId::OverlayMenu || upid == kernel::UniqueProcessId::ErrorDisplay || upid == kernel::UniqueProcessId::Game) { return; } } std::memset(base.get(), 0, size); } static int32_t GetNextBounce(virt_ptr<LOADED_RPL> rpl) { uint32_t chunkBytesRead = 0; auto error = LiWaitOneChunk(&chunkBytesRead, virt_cast<char *>(rpl->pathBuffer).get(), rpl->fileType); if (error != 0) { return error; } rpl->fileOffset = rpl->upcomingFileOffset; rpl->upcomingFileOffset += chunkBytesRead; rpl->totalBytesRead += chunkBytesRead; rpl->lastChunkBuffer = rpl->chunkBuffer; auto readBufferNumber = rpl->upcomingBufferNumber; if (readBufferNumber == 1) { rpl->upcomingBufferNumber = 2u; rpl->virtualFileBaseOffset += chunkBytesRead; } else { rpl->upcomingBufferNumber = 1u; rpl->virtualFileBase = virt_cast<void *>(virt_cast<virt_addr>(rpl->virtualFileBase) - rpl->virtualFileBaseOffset); rpl->virtualFileBaseOffset = chunkBytesRead; } if (chunkBytesRead == 0x400000) { return LiRefillUpcomingBounceBuffer(rpl, readBufferNumber); } LiInitBuffer(false); return 0; } static int32_t sLiPrepareBounceBufferForReading(virt_ptr<LOADED_RPL> rpl, uint32_t sectionIndex, std::string_view name, uint32_t sectionOffset, uint32_t *outBytesRead, uint32_t readSize, virt_ptr<void> *outBuffer) { LiCheckAndHandleInterrupts(); if (rpl->upcomingFileOffset <= rpl->fileOffset) { if (sectionIndex == 0) { Loader_ReportError("*** {} Segment {}: bounce buffer has no size or is corrupted.", rpl->moduleNameBuffer, name); } else { Loader_ReportError("*** {} Segment {}'s segment {}: bounce buffer has no size or is corrupted.", rpl->moduleNameBuffer, name, sectionIndex); } LiSetFatalError(0x18729B, rpl->fileType, 1, "sLiPrepareBounceBufferForReading", 0xAD); return -470074; } while (sectionOffset >= rpl->upcomingFileOffset) { auto error = GetNextBounce(rpl); if (error != 0) { break; } } if (sectionOffset < rpl->fileOffset) { if (sectionIndex <= 0) { Loader_ReportError( "*** {} Segment {}'s segment {} offset is before the current read position.", rpl->moduleNameBuffer, name, sectionIndex); } else { Loader_ReportError("*** {} Segment {}'s base is before the current read position.", rpl->moduleNameBuffer, name); } LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLiPrepareBounceBufferForReading", 193); return -470075; } if (sectionOffset == rpl->upcomingFileOffset) { if (sectionIndex <= 0) { Loader_ReportError( "*** {} Segment {}'s segment {}: file has nothing left to read or bounce buffer is corrupted.", rpl->moduleNameBuffer, name, sectionIndex); } else { Loader_ReportError( "*** {} Segment {}: file has nothing left to read or bounce buffer is corrupted.", rpl->moduleNameBuffer, name); } LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLiPrepareBounceBufferForReading", 210); return -470076; } auto bytesRead = rpl->upcomingFileOffset - sectionOffset; if (readSize < bytesRead) { bytesRead = readSize; } *outBytesRead = bytesRead; *outBuffer = virt_cast<void *>(virt_cast<virt_addr>(rpl->virtualFileBase) + sectionOffset); return 0; } int32_t sLiRefillBounceBufferForReading(virt_ptr<LOADED_RPL> rpl, uint32_t *outBytesRead, uint32_t size, virt_ptr<void> *outBuffer) { LiCheckAndHandleInterrupts(); *outBytesRead = 0; auto result = GetNextBounce(rpl); if (!result) { auto bytesRead = rpl->upcomingFileOffset - rpl->fileOffset; if (size < bytesRead) { bytesRead = size; } *outBytesRead = bytesRead; *outBuffer = rpl->lastChunkBuffer; } return result; } int32_t ZLIB_UncompressFromStream(virt_ptr<LOADED_RPL> rpl, uint32_t sectionIndex, std::string_view boundsName, uint32_t fileOffset, uint32_t deflatedSize, virt_ptr<void> inflatedBuffer, uint32_t *inflatedSize) { auto inflatedBytesMax = *inflatedSize; auto deflatedBytesRemaining = deflatedSize; auto bounceBuffer = virt_ptr<void> { nullptr }; auto bounceBufferSize = uint32_t { 0 }; LiCheckAndHandleInterrupts(); auto error = sLiPrepareBounceBufferForReading(rpl, sectionIndex, boundsName, fileOffset, &bounceBufferSize, deflatedSize, &bounceBuffer); if (error) { return error; } auto stream = z_stream { }; std::memset(&stream, 0, sizeof(stream)); auto zlibError = inflateInit(&stream); if (zlibError != Z_OK) { switch (zlibError) { case Z_STREAM_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 332); return Error::ZlibStreamError; case Z_MEM_ERROR: LiSetFatalError(0x187298u, rpl->fileType, 0, "ZLIB_UncompressFromStream", 319); return Error::ZlibMemError; case Z_VERSION_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 332); return Error::ZlibVersionError; default: Loader_ReportError("***Unknown ZLIB error {} (0x{}).", zlibError, zlibError); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 332); return Error::ZlibUnknownError; } } constexpr auto InflateChunkSize = 0x3000u; rpl->lastSectionCrc = 0u; stream.next_out = reinterpret_cast<Bytef *>(inflatedBuffer.get()); while (true) { // TODO: Loader_UpdateHeartBeat(); LiCheckAndHandleInterrupts(); stream.avail_in = bounceBufferSize; stream.next_in = reinterpret_cast<Bytef *>(bounceBuffer.get()); while (stream.avail_in) { LiCheckAndHandleInterrupts(); stream.avail_out = std::min<uInt>(InflateChunkSize, inflatedBytesMax - stream.total_out); zlibError = inflate(&stream, 0); if (zlibError != Z_OK && zlibError != Z_STREAM_END) { switch (zlibError) { case Z_STREAM_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 405); return -470086; case Z_MEM_ERROR: LiSetFatalError(0x187298u, rpl->fileType, 0, "ZLIB_UncompressFromStream", 415); error = -470084; break; case Z_DATA_ERROR: LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 419); error = -470087; break; default: Loader_ReportError("***Unknown ZLIB error {} (0x{}).", zlibError, zlibError); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 424); error = -470100; } inflateEnd(&stream); return error; } decaf_check(stream.total_out <= inflatedBytesMax); } deflatedBytesRemaining -= bounceBufferSize; if (!deflatedBytesRemaining) { break; } error = sLiRefillBounceBufferForReading(rpl, &bounceBufferSize, deflatedBytesRemaining, &bounceBuffer); if (error) { LiSetFatalError(0x18729Bu, rpl->fileType, 1, "ZLIB_UncompressFromStream", 520); inflateEnd(&stream); *inflatedSize = stream.total_out; return -470087; } } inflateEnd(&stream); *inflatedSize = stream.total_out; return 0; } static int32_t LiSetupOneAllocSection(kernel::UniqueProcessId upid, virt_ptr<LOADED_RPL> rpl, int32_t sectionIndex, virt_ptr<rpl::SectionHeader> sectionHeader, int32_t unk_a5, SegmentBounds *bounds, virt_ptr<void> base, uint32_t baseAlign, uint32_t unk_a9) { auto globals = getGlobalStorage(); LiCheckAndHandleInterrupts(); auto sectionAddress = virt_cast<virt_addr>(base) + (sectionHeader->addr - bounds->min); rpl->sectionAddressBuffer[sectionIndex] = sectionAddress; if (!align_check(sectionAddress, sectionHeader->addralign)) { Loader_ReportError("***{} section {} alignment failure.", bounds->name, sectionIndex); Loader_ReportError("Ptr = {}", sectionAddress); Loader_ReportError("{} base = {}", bounds->name, base); Loader_ReportError("{} base align = {}", bounds->name, baseAlign); Loader_ReportError("SecHdr->addr = 0x{:08X}", sectionHeader->addr); Loader_ReportError("bound[{}].base = 0x{:08X}", bounds->name, bounds->min); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLiSetupOneAllocSection", 1510); return -470043; } if (!unk_a9 && sectionHeader->type == rpl::SHT_NOBITS && unk_a5) { auto userHasControl = (upid == kernel::UniqueProcessId::Invalid) ? true : !!globals->userHasControl; LiClearUserBss(userHasControl, upid, virt_cast<void *>(sectionAddress), sectionHeader->size); } else { auto bytesAvailable = uint32_t { 0 }; auto sectionData = virt_ptr<void> { nullptr }; auto error = sLiPrepareBounceBufferForReading(rpl, sectionIndex, bounds->name, sectionHeader->offset, &bytesAvailable, sectionHeader->size, §ionData); if (error) { return error; } if (!unk_a9 && (sectionHeader->flags & rpl::SHF_DEFLATED)) { auto inflatedExpectedSizeBuffer = std::array<uint8_t, sizeof(uint32_t)> { }; auto readBytes = uint32_t { 0 }; while (readBytes < inflatedExpectedSizeBuffer.size()) { std::memcpy( inflatedExpectedSizeBuffer.data() + readBytes, sectionData.get(), std::min<size_t>(bytesAvailable, inflatedExpectedSizeBuffer.size() - readBytes)); readBytes += bytesAvailable; if (readBytes >= inflatedExpectedSizeBuffer.size()) { break; } error = sLiRefillBounceBufferForReading( rpl, &bytesAvailable, static_cast<uint32_t>(inflatedExpectedSizeBuffer.size() - readBytes), §ionData); if (error) { return error; } } auto inflatedExpectedSize = *reinterpret_cast<be2_val<uint32_t> *>( inflatedExpectedSizeBuffer.data()); if (inflatedExpectedSize) { auto inflatedBytes = static_cast<uint32_t>(inflatedExpectedSize); error = ZLIB_UncompressFromStream(rpl, sectionIndex, bounds->name, sectionHeader->offset + 4, sectionHeader->size - 4, virt_cast<void *>(sectionAddress), &inflatedBytes); if (error) { Loader_ReportError( "***{} {} {} Decompression ({}->{}) failure.", rpl->moduleNameBuffer, bounds->name, sectionIndex, sectionHeader->size - 4, inflatedExpectedSize); return error; } if (inflatedBytes != inflatedExpectedSize) { Loader_ReportError( "***{} {} {} Decompression ({}->{}) failure. Anticipated uncompressed size would be {}; got {}", rpl->moduleNameBuffer, bounds->name, sectionIndex, sectionHeader->size - 4, inflatedExpectedSize, inflatedExpectedSize, inflatedBytes); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLiSetupOneAllocSection", 1604); return -470090; } sectionHeader->size = inflatedBytes; } } else { auto bytesRead = 0u; while (true) { // TODO: Loader_UpdateHeartBeat(); LiCheckAndHandleInterrupts(); std::memcpy(virt_cast<void *>(sectionAddress + bytesRead).get(), sectionData.get(), bytesAvailable); bytesRead += bytesAvailable; if (bytesRead >= sectionHeader->size) { break; } error = sLiRefillBounceBufferForReading(rpl, &bytesAvailable, sectionHeader->size - bytesRead, §ionData); if (error) { return error; } } } } bounds->allocMax = std::max<uint32_t>(bounds->allocMax, sectionHeader->addr + sectionHeader->size); if (bounds->allocMax > bounds->max) { Loader_ReportError( "*** {} section {} segment {} makerpl's segment size was wrong: real time calculated size =0x{:08X} makerpl's size=0x{:08X}.", rpl->moduleNameBuffer, sectionIndex, bounds->name, bounds->allocMax, bounds->max); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLiSetupOneAllocSection", 1676); return -470091; } return 0; } int32_t LiSetupOneRPL(kernel::UniqueProcessId upid, virt_ptr<LOADED_RPL> rpl, virt_ptr<TinyHeap> codeHeapTracking, virt_ptr<TinyHeap> dataHeapTracking) { int32_t result = 0; // Calculate segment bounds RplSegmentBounds bounds; bounds.data.name = "DATA"; bounds.load.name = "LOADERINFO"; bounds.text.name = "TEXT"; bounds.temp.name = "TEMP"; auto shBase = virt_cast<virt_addr>(rpl->sectionHeaderBuffer); for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize); if (sectionHeader->size == 0 || sectionHeader->type == rpl::SHT_RPL_FILEINFO || sectionHeader->type == rpl::SHT_RPL_CRCS || sectionHeader->type == rpl::SHT_RPL_IMPORTS) { continue; } if (sectionHeader->flags & rpl::SHF_ALLOC) { if ((sectionHeader->flags & rpl::SHF_EXECINSTR) && sectionHeader->type != rpl::SHT_RPL_EXPORTS) { bounds.text.min = std::min<uint32_t>(bounds.text.min, sectionHeader->addr); } else { if (sectionHeader->flags & rpl::SHF_WRITE) { bounds.data.min = std::min<uint32_t>(bounds.data.min, sectionHeader->addr); } else { bounds.load.min = std::min<uint32_t>(bounds.load.min, sectionHeader->addr); } } } else { bounds.temp.min = std::min<uint32_t>(bounds.temp.min, sectionHeader->offset); bounds.temp.max = std::max<uint32_t>(bounds.temp.max, sectionHeader->offset + sectionHeader->size); } } if (bounds.data.min == -1) { bounds.data.min = 0; } if (bounds.load.min == -1) { bounds.load.min = 0; } if (bounds.text.min == -1) { bounds.text.min = 0; } if (bounds.temp.min == -1) { bounds.temp.min = 0; } auto fileInfo = rpl->fileInfoBuffer; bounds.text.max = (bounds.text.min + fileInfo->textSize) - fileInfo->trampAdjust; bounds.data.max = bounds.data.min + fileInfo->dataSize; bounds.load.max = (bounds.load.min + fileInfo->loadSize) - fileInfo->fileInfoPad; auto textSize = static_cast<uint32_t>(bounds.text.max - bounds.text.min); auto dataSize = static_cast<uint32_t>(bounds.data.max - bounds.data.min); auto loadSize = static_cast<uint32_t>(bounds.load.max - bounds.load.min); auto tempSize = static_cast<uint32_t>(bounds.temp.max - bounds.temp.min); if (fileInfo->trampAdjust >= textSize || fileInfo->textSize - fileInfo->trampAdjust < textSize || fileInfo->dataSize < dataSize || fileInfo->loadSize - fileInfo->fileInfoPad < loadSize || fileInfo->tempSize < tempSize) { Loader_ReportError("***Bounds check failure."); Loader_ReportError("b%d: %08X %08x", 0, bounds.data.min, bounds.data.max); Loader_ReportError("b%d: %08X %08x", 1, bounds.load.min, bounds.load.max); Loader_ReportError("b%d: %08X %08x", 2, bounds.text.min, bounds.text.max); Loader_ReportError("b%d: %08X %08x", 3, bounds.temp.min, bounds.temp.max); Loader_ReportError("TrampAdj = %08X", fileInfo->trampAdjust); Loader_ReportError("Text = %08X", fileInfo->textSize); Loader_ReportError("Data = %08X", fileInfo->dataSize); Loader_ReportError("Read = %08X", fileInfo->loadSize - fileInfo->fileInfoPad); Loader_ReportError("Temp = %08X", fileInfo->tempSize); LiSetFatalError(0x18729B, rpl->fileType, 1, "LiSetupOneRPL", 0x715); result = -470042; goto error; } if (rpl->dataBuffer) { for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize); LiCheckAndHandleInterrupts(); if (sectionHeader->size && !rpl->sectionAddressBuffer[i] && (sectionHeader->flags & rpl::SHF_ALLOC) && (sectionHeader->flags & rpl::SHF_WRITE)) { result = LiSetupOneAllocSection(upid, rpl, i, sectionHeader, 1, &bounds.data, rpl->dataBuffer, fileInfo->dataAlign, 0); if (result) { goto error; } } } } if (rpl->loadBuffer) { for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize); LiCheckAndHandleInterrupts(); if (sectionHeader->size && !rpl->sectionAddressBuffer[i] && (sectionHeader->flags & rpl::SHF_ALLOC)) { if (sectionHeader->type == rpl::SHT_RPL_EXPORTS || sectionHeader->type == rpl::SHT_RPL_IMPORTS || !(sectionHeader->flags & (rpl::SHF_EXECINSTR | rpl::SHF_WRITE))) { result = LiSetupOneAllocSection(upid, rpl, i, sectionHeader, 0, &bounds.load, rpl->loadBuffer, fileInfo->loadAlign, (sectionHeader->type == rpl::SHT_RPL_IMPORTS) ? 1 : 0); if (result) { goto error; } if (sectionHeader->type == rpl::SHT_RPL_EXPORTS) { if (sectionHeader->flags & rpl::SHF_EXECINSTR) { rpl->numFuncExports = *virt_cast<uint32_t *>(rpl->sectionAddressBuffer[i]); rpl->funcExports = virt_cast<void *>(rpl->sectionAddressBuffer[i] + 8); } else { rpl->numDataExports = *virt_cast<uint32_t *>(rpl->sectionAddressBuffer[i]); rpl->dataExports = virt_cast<void *>(rpl->sectionAddressBuffer[i] + 8); } } } } } } if (fileInfo->textSize) { if (!rpl->textBuffer) { Loader_ReportError("Missing TEXT allocation."); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiSetupOneRPL", 1918); result = -470057; goto error; } for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize); LiCheckAndHandleInterrupts(); if (sectionHeader->size && !rpl->sectionAddressBuffer[i] && (sectionHeader->flags & rpl::SHF_ALLOC) && (sectionHeader->flags & rpl::SHF_EXECINSTR) && sectionHeader->type != rpl::SHT_RPL_EXPORTS) { result = LiSetupOneAllocSection(upid, rpl, i, sectionHeader, 0, &bounds.text, virt_cast<void *>(virt_cast<virt_addr>(rpl->textBuffer) + fileInfo->trampAdjust), fileInfo->textAlign, 0); if (result) { goto error; } } } } if (bounds.temp.min != bounds.temp.max) { auto compressedRelocationsBuffer = virt_ptr<void> { nullptr }; auto compressedRelocationsBufferSize = uint32_t { 0 }; auto memoryAvailable = uint32_t { 0 }; auto data = virt_ptr<void> { nullptr }; auto readBytes = 0u; tempSize = bounds.temp.max - bounds.temp.min; dataSize = uint32_t { 0 }; result = LiCacheLineCorrectAllocEx(codeHeapTracking, tempSize, -32, &compressedRelocationsBuffer, 1, &compressedRelocationsBufferSize, &memoryAvailable, rpl->fileType); if (result) { Loader_ReportError( "*** allocation failed for {} size = {}, align = {} from {} heap; (needed {}, available {}).", "compressed relocations", tempSize, -32, "RPL Code", compressedRelocationsBufferSize, memoryAvailable); goto error; } rpl->compressedRelocationsBuffer = compressedRelocationsBuffer; rpl->compressedRelocationsBufferSize = compressedRelocationsBufferSize; result = sLiPrepareBounceBufferForReading(rpl, 0, bounds.temp.name, bounds.temp.min, &dataSize, tempSize, &data); if (result) { goto error; } while (tempSize > 0) { // TODO: Loader_UpdateHeartBeat LiCheckAndHandleInterrupts(); std::memcpy(virt_cast<void *>(virt_cast<virt_addr>(compressedRelocationsBuffer) + readBytes).get(), data.get(), dataSize); readBytes += dataSize; tempSize -= dataSize; if (!tempSize) { break; } result = sLiRefillBounceBufferForReading(rpl, &dataSize, tempSize, &data); if (result) { goto error; } } for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize); LiCheckAndHandleInterrupts(); if (sectionHeader->size && !rpl->sectionAddressBuffer[i]) { rpl->sectionAddressBuffer[i] = virt_cast<virt_addr>(compressedRelocationsBuffer) + (sectionHeader->offset - bounds.temp.min); bounds.temp.allocMax = std::max<uint32_t>(bounds.temp.allocMax, sectionHeader->addr + sectionHeader->size); if (bounds.temp.allocMax > bounds.temp.max) { Loader_ReportError( "***Section {} segment {} makerpl's section size was wrong: mRealTimeLimit=0x%08X, mLimit=0x%08X. Error is Loader's fault.", i, bounds.temp.name, bounds.temp.allocMax, bounds.temp.max); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiSetupOneRPL", 2034); result = -470091; goto error; } } } } for (auto i = 0u; i < 4; ++i) { if (bounds[i].allocMax > bounds[i].max) { Loader_ReportError( "***Segment %s makerpl's segment size was wrong: mRealTimeLimit=0x%08X, mLimit=0x%08X. Error is Loader's fault.\n", bounds[i].name, bounds[i].allocMax, bounds[i].max); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LiSetupOneRPL", 2052); result = -470091; goto error; } } rpl->postTrampBuffer = align_up(virt_cast<virt_addr>(rpl->textBuffer) + fileInfo->trampAdjust + (bounds.text.allocMax - bounds.text.min), 16); rpl->textAddr = virt_cast<virt_addr>(rpl->textBuffer) + fileInfo->trampAdjust; rpl->textOffset = rpl->textAddr - bounds.text.min; rpl->textSize = static_cast<uint32_t>(bounds.text.max - bounds.text.min); rpl->dataAddr = virt_cast<virt_addr>(rpl->dataBuffer); rpl->dataOffset = rpl->dataAddr - bounds.data.min; rpl->dataSize = static_cast<uint32_t>(bounds.data.max - bounds.data.min); rpl->loadAddr = virt_cast<virt_addr>(rpl->loadBuffer); rpl->loadOffset = rpl->loadAddr - bounds.load.min; rpl->loadSize = static_cast<uint32_t>(bounds.load.max - bounds.load.min); rpl->loadStateFlags |= LoadStateFlags::LoaderSetup; result = LiCleanUpBufferAfterModuleLoaded(); if (result) { goto error; } return 0; error: if (rpl->compressedRelocationsBuffer) { LiCacheLineCorrectFreeEx(codeHeapTracking, rpl->compressedRelocationsBuffer, rpl->compressedRelocationsBufferSize); rpl->compressedRelocationsBuffer = nullptr; } LiCloseBufferIfError(); Loader_ReportError("***LiSetupOneRPL({}) failed with err={}.", rpl->moduleNameBuffer, result); return result; } static bool sCheckDataRange(virt_addr address, uint32_t maxDataSize) { return address >= virt_addr { 0x10000000 } && address < (virt_addr { 0x10000000 } + maxDataSize); } static int32_t sValidateSetupParams(virt_addr address, uint32_t size, uint32_t align, uint32_t maxSize, int32_t error, const char *areaName) { if (!sCheckDataRange(address, maxSize)) { Loader_ReportError( "*** invalid {} area address. apArea={}, lo addr={}, hi addr={}", areaName, address, virt_addr { 0x10000000 }, virt_addr { maxSize + 0x10000000 }); return error; } if (!sCheckDataRange(address + size, maxSize)) { Loader_ReportError( "*** invalid {} area buffer. apArea+aAreaBytes={}, lo addr={}, hi addr={}", areaName, address + size, virt_addr { 0x10000000 }, virt_addr { maxSize + 0x10000000 }); return error; } if (!align_check(address, align)) { Loader_ReportError("*** invalid {} area buffer alignment.", areaName); return error; } if (size && !Loader_ValidateAddrRange(address, size)) { Loader_ReportError("*** invalid {} area buffer range {}..{}.", areaName, address, address + size); return error; } return 0; } int32_t LOADER_Setup(kernel::UniqueProcessId upid, LOADER_Handle handle, BOOL isPurge, virt_ptr<LOADER_MinFileInfo> minFileInfo) { auto globals = getGlobalStorage(); if (globals->currentUpid != upid) { Loader_ReportError("*** Loader address space not set for process {} but called for {}.", static_cast<int>(globals->currentUpid.value()), static_cast<int>(upid)); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Setup", 2184); LiCloseBufferIfError(); return Error::DifferentProcess; } if (minFileInfo) { if (auto error = LiValidateMinFileInfo(minFileInfo, "LOADER_Setup")) { LiCloseBufferIfError(); return error; } } auto error = LiValidateAddress(handle, 0, 0, -470046, virt_addr { 0x02000000 }, virt_addr { 0x10000000 }, "kernel handle for module (out of valid range-read)"); if (error) { if (getProcFlags().disableSharedLibraries()) { Loader_ReportError("*** bad kernel handle for module (out of valid range-read)"); LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Setup", 2215); LiCloseBufferIfError(); return error; } error = LiValidateAddress(handle, 0, 0, -470046, virt_addr { 0xEFE0B000 }, virt_addr { 0xEFE80000 }, "kernel handle for module (out of valid range-read)"); if (error) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Setup", 2215); LiCloseBufferIfError(); return error; } } // Find module auto prev = virt_ptr<LOADED_RPL> { nullptr }; auto rpl = virt_ptr<LOADED_RPL> { nullptr }; for (rpl = globals->firstLoadedRpl; rpl; rpl = rpl->nextLoadedRpl) { if (rpl->moduleNameBuffer == handle) { break; } prev = rpl; } if (!rpl) { LiSetFatalError(0x18729Bu, 0, 1, "LOADER_Setup", 2240); LiCloseBufferIfError(); return -470010; } // If this is a purge command, perform the purge if (isPurge) { if (rpl->loadStateFlags & LoaderStateFlag4) { return 0; } // Remove from linked list if (prev) { prev->nextLoadedRpl = rpl->nextLoadedRpl; } if (!rpl->nextLoadedRpl) { globals->lastLoadedRpl = prev; } LiPurgeOneUnlinkedModule(rpl); return 0; } // Check if we have already setup if (rpl->loadStateFlags & LoaderSetup) { Loader_ReportError("*** module already set up."); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_Setup", 2267); LiCloseBufferIfError(); return -470047; } // Check input if (minFileInfo->dataSize) { error = sValidateSetupParams(virt_cast<virt_addr>(minFileInfo->dataBuffer), minFileInfo->dataSize, minFileInfo->dataAlign, globals->maxDataSize, -470021, "data"); if (error) { LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_Setup", 2283); LiCloseBufferIfError(); return error; } } if (minFileInfo->loadSize) { error = sValidateSetupParams(virt_cast<virt_addr>(minFileInfo->loadBuffer), minFileInfo->loadSize, minFileInfo->loadAlign, globals->maxDataSize, -470048, "loader_info"); if (error) { LiSetFatalError(0x18729Bu, rpl->fileType, 1, "LOADER_Setup", 2300); LiCloseBufferIfError(); return error; } } // Perform setup rpl->dataBuffer = minFileInfo->dataBuffer; rpl->loadBuffer = minFileInfo->loadBuffer; error = LiSetupOneRPL(upid, rpl, globals->processCodeHeap, globals->processCodeHeap); if (error) { rpl->dataBuffer = nullptr; rpl->loadBuffer = nullptr; LiCloseBufferIfError(); return error; } // Do queries error = LOADER_GetSecInfo(upid, handle, minFileInfo->outNumberOfSections, minFileInfo->outSectionInfo); if (error) { LiCloseBufferIfError(); return error; } error = LOADER_GetFileInfo(upid, handle, minFileInfo->outSizeOfFileInfo, minFileInfo->outFileInfo, nullptr, nullptr); if (error) { LiCloseBufferIfError(); return error; } error = LOADER_GetPathString(upid, handle, minFileInfo->outPathStringSize, minFileInfo->pathStringBuffer, minFileInfo->outFileInfo); if (error) { LiCloseBufferIfError(); return error; } return 0; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_setup.h ================================================ #pragma once #include "cafe_loader_loaded_rpl.h" #include "cafe_loader_minfileinfo.h" #include "cafe/kernel/cafe_kernel_processid.h" #include <cstdint> #include <libcpu/be2_struct.h> namespace cafe::loader::internal { int32_t LiSetupOneRPL(kernel::UniqueProcessId upid, virt_ptr<LOADED_RPL> rpl, virt_ptr<TinyHeap> codeHeapTracking, virt_ptr<TinyHeap> dataHeapTracking); int32_t LOADER_Setup(kernel::UniqueProcessId upid, LOADER_Handle handle, BOOL isPurge, virt_ptr<LOADER_MinFileInfo> minFileInfo); } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_shared.cpp ================================================ #include "cafe_loader_bounce.h" #include "cafe_loader_entry.h" #include "cafe_loader_error.h" #include "cafe_loader_flush.h" #include "cafe_loader_globals.h" #include "cafe_loader_heap.h" #include "cafe_loader_init.h" #include "cafe_loader_iop.h" #include "cafe_loader_basics.h" #include "cafe_loader_log.h" #include "cafe_loader_minfileinfo.h" #include "cafe_loader_prep.h" #include "cafe_loader_reloc.h" #include "cafe_loader_setup.h" #include "cafe_loader_shared.h" #include "cafe_loader_utils.h" #include "cafe/cafe_stackobject.h" #include "cafe/libraries/cafe_hle_library.h" #include <libcpu/cpu_formatters.h> #include <zlib.h> namespace cafe::loader::internal { constexpr const char * SharedLibraryList[] { "tve.rpl", "nsysccr.rpl", "nsysnet.rpl", "uvc.rpl", "tcl.rpl", "nn_pdm.rpl", "dmae.rpl", "dc.rpl", "vpadbase.rpl", "vpad.rpl", "avm.rpl", "gx2.rpl", "snd_core.rpl", }; struct LiCompressedSharedDataTracking { be2_virt_ptr<void> data; be2_virt_ptr<void> intialisationData; be2_val<uint32_t> compressedSize; be2_val<uint32_t> size; }; CHECK_OFFSET(LiCompressedSharedDataTracking, 0x00, data); CHECK_OFFSET(LiCompressedSharedDataTracking, 0x04, intialisationData); CHECK_OFFSET(LiCompressedSharedDataTracking, 0x08, compressedSize); CHECK_OFFSET(LiCompressedSharedDataTracking, 0x0C, size); CHECK_SIZE(LiCompressedSharedDataTracking, 0x10); struct LoaderShared { be2_virt_ptr<LOADED_RPL> loadedModules; be2_virt_ptr<void> dataBufferHead; be2_val<uint32_t> numFreeTrackCompBlocks; be2_virt_ptr<LiCompressedSharedDataTracking> freeTrackCompBlocks; be2_val<uint32_t> numUsedTrackCompBlocks; be2_virt_ptr<LiCompressedSharedDataTracking> usedTrackCompBlocks; }; CHECK_OFFSET(LoaderShared, 0x00, loadedModules); CHECK_OFFSET(LoaderShared, 0x04, dataBufferHead); CHECK_OFFSET(LoaderShared, 0x08, numFreeTrackCompBlocks); CHECK_OFFSET(LoaderShared, 0x0C, freeTrackCompBlocks); CHECK_OFFSET(LoaderShared, 0x10, numUsedTrackCompBlocks); CHECK_OFFSET(LoaderShared, 0x14, usedTrackCompBlocks); CHECK_SIZE(LoaderShared, 0x18); static virt_ptr<LoaderShared> gpLoaderShared = nullptr; static virt_ptr<TinyHeap> gpSharedCodeHeapTracking = nullptr; static virt_ptr<TinyHeap> gpSharedReadHeapTracking = nullptr; static virt_ptr<TinyHeap> sgpTrackComp = nullptr; static virt_ptr<void> sHleUnimplementedStubMemory = nullptr; static uint32_t sHleUnimplementedStubMemorySize = 0; static int32_t sLoadOneShared(std::string_view filename) { auto moduleName = StackObject<virt_ptr<char>> { }; auto moduleNameLen = StackObject<uint32_t> { }; auto fileNameBuffer = StackArray<char, 32> { }; auto chunkSize = uint32_t { 0 }; auto chunkBuffer = virt_ptr<void> { nullptr }; auto rpl = virt_ptr<LOADED_RPL> { nullptr }; auto rplBasicLoadArgs = LiBasicsLoadArgs { }; auto error = int32_t { 0 }; Loader_LogEntry(2, 0, 0, "sLoadOneShared Loading Shared RPL {}", filename); Loader_LogEntry(2, 0, 0, "sLoadOneShared LiSyncBnce start {}", filename); LiInitBuffer(false); error = LiBounceOneChunk(filename, ios::mcp::MCPFileType::CafeOS, kernel::UniqueProcessId::Kernel, &chunkSize, 0, 1, &chunkBuffer); if (error != 0) { Loader_ReportError("***LiBounceOneChunk failed loading \"{}\" of type {} at offset 0x{:08X} err={}", filename, 1, 0, error); return error; } Loader_LogEntry(2, 0, 0, "sLoadOneShared LiSyncBnce end {}", filename); std::memcpy(fileNameBuffer.get(), filename.data(), filename.size()); fileNameBuffer[filename.size()] = 0; *moduleName = fileNameBuffer; *moduleNameLen = static_cast<uint32_t>(filename.size()); LiResolveModuleName(moduleName, moduleNameLen); rplBasicLoadArgs.fileOffset = 0u; rplBasicLoadArgs.pathNameLen = static_cast<uint32_t>(filename.size()); rplBasicLoadArgs.pathName = fileNameBuffer; rplBasicLoadArgs.chunkBuffer = chunkBuffer; rplBasicLoadArgs.chunkBufferSize = chunkSize; rplBasicLoadArgs.readHeapTracking = gpSharedReadHeapTracking; rplBasicLoadArgs.upid = kernel::UniqueProcessId::Kernel; rplBasicLoadArgs.fileType = ios::mcp::MCPFileType::CafeOS; error = LiLoadRPLBasics(*moduleName, *moduleNameLen, chunkBuffer, gpSharedCodeHeapTracking, gpSharedReadHeapTracking, true, // TODO: Change to false and keep module name in loader .data memory 1, &rpl, &rplBasicLoadArgs, 0); if (error != 0) { return error; } rpl->loadStateFlags |= LoaderStateFlags_Unk0x20000000 | LoaderStateFlag4; auto fileInfo = rpl->fileInfoBuffer; if (fileInfo->dataSize) { auto dataBufferHeadAddr = virt_cast<virt_addr>(gpLoaderShared->dataBufferHead); // Align data buffer start dataBufferHeadAddr = align_up(dataBufferHeadAddr, fileInfo->dataAlign); rpl->dataBuffer = virt_cast<void *>(dataBufferHeadAddr); // Align data buffer end to 64 bytes dataBufferHeadAddr = align_up(dataBufferHeadAddr + fileInfo->dataSize, 64); gpLoaderShared->dataBufferHead = virt_cast<void *>(dataBufferHeadAddr); } if (fileInfo->loadSize != fileInfo->fileInfoPad) { auto allocPtr = virt_ptr<void> { nullptr }; auto tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking, fileInfo->loadSize - fileInfo->fileInfoPad, -static_cast<int32_t>(fileInfo->loadAlign), &allocPtr); if (tinyHeapError != TinyHeapError::OK) { Loader_ReportError("Could not allocate read-only space for shared library \"{}\"", rpl->moduleNameBuffer); return static_cast<int32_t>(tinyHeapError); } rpl->loadBuffer = allocPtr; } Loader_LogEntry(2, 0, 0, "sLoadOneShared LiSetupOneRPL start."); error = LiSetupOneRPL(kernel::UniqueProcessId::Invalid, rpl, gpSharedCodeHeapTracking, gpSharedReadHeapTracking); if (error) { Loader_ReportError("LiSetupOneRPL failed for shared library \"{}\".", filename); return error; } Loader_LogEntry(2, 0, 0, "sLoadOneShared LiSetupOneRPL end."); if (!rpl->elfHeader.shstrndx) { Loader_ReportError( "*** Error: Could not get section string table index for \"{}\".", filename); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLoadOneShared", 213); return -470071; } if (!rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]) { Loader_ReportError("*** Error: Could not get section string table for \"%s\".", filename); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLoadOneShared", 204); return -470072; } auto shStr = virt_cast<char *>(rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]); auto importTracking = virt_ptr<LiImportTracking> { nullptr }; auto importTrackingSize = uint32_t { 0 }; // Setup import tracking for (auto i = 1; i < rpl->elfHeader.shnum - 2; ++i) { auto sectionHeader = virt_cast<rpl::SectionHeader *>( virt_cast<virt_addr>(rpl->sectionHeaderBuffer) + i * rpl->elfHeader.shentsize); if (!sectionHeader->size || sectionHeader->type != rpl::SHT_RPL_IMPORTS) { continue; } // Make sure we have memory for import tracking if (!importTracking) { auto largestFree = uint32_t { 0 }; error = LiCacheLineCorrectAllocEx(getGlobalStorage()->processCodeHeap, sizeof(LiImportTracking) * (rpl->elfHeader.shnum - 2), 4, reinterpret_cast<virt_ptr<void> *>(&importTracking), 1, &importTrackingSize, &largestFree, rpl->fileType); if (error) { Loader_ReportError( "***Could not allocate space for shared import tracking in local heap; (needed {}, available {}).", importTrackingSize, largestFree); return error; } } // Load imported rpl auto name = shStr + sectionHeader->name + strlen(".fimport_"); auto nameLen = strlen(name.get()); auto importModule = virt_ptr<LOADED_RPL> { nullptr }; for (auto module = gpLoaderShared->loadedModules; module; module = module->nextLoadedRpl) { if (module->moduleNameLen == nameLen && strncmp(name.get(), module->moduleNameBuffer.get(), nameLen) == 0) { importModule = module; break; } } if (!importModule) { Loader_ReportError( "*** \"{}\" imports from \"{}\" which is not loaded as a shared library.", filename, name); LiSetFatalError(0x18729Bu, rpl->fileType, 1, "sLoadOneShared", 253); LiCacheLineCorrectFreeEx(getGlobalStorage()->processCodeHeap, importTracking, importTrackingSize); return -470010; } if (sectionHeader->flags & rpl::SHF_EXECINSTR) { importTracking[i].numExports = importModule->numFuncExports; importTracking[i].exports = virt_cast<rpl::Export *>(importModule->funcExports); } else { importTracking[i].numExports = importModule->numDataExports; importTracking[i].exports = virt_cast<rpl::Export *>(importModule->dataExports); } importTracking[i].tlsModuleIndex = importModule->fileInfoBuffer->tlsModuleIndex; importTracking[i].rpl = rpl; } // Process relocations and imports Loader_LogEntry(2, 0, 0, "sLoadOneShared LiFixupRelocOneRPL start."); error = LiFixupRelocOneRPL(rpl, importTracking, 0); Loader_LogEntry(2, 0, 0, "sLoadOneShared LiFixupRelocOneRPL end."); if (error) { Loader_ReportError("LiFixupRelocOneRPL failed for shared library \"{}\".", filename); return error; } for (auto i = 1; i < rpl->elfHeader.shnum - 2; ++i) { auto sectionHeader = getSectionHeader(rpl, i); if (sectionHeader->size && sectionHeader->type == rpl::SHT_NOBITS) { auto tinyHeapError = TinyHeap_AllocAt(sgpTrackComp, virt_cast<void *>(rpl->sectionAddressBuffer[i]), sectionHeader->size); if (tinyHeapError != TinyHeapError::OK) { Loader_Panic(0x13000B, "*** Critical error in tracking shared bss."); } } } if (rpl->compressedRelocationsBuffer) { LiCacheLineCorrectFreeEx(gpSharedCodeHeapTracking, rpl->compressedRelocationsBuffer, rpl->compressedRelocationsBufferSize); rpl->compressedRelocationsBuffer = nullptr; } if (importTracking) { LiCacheLineCorrectFreeEx(getGlobalStorage()->processCodeHeap, importTracking, importTrackingSize); importTracking = nullptr; } LiCacheLineCorrectFreeEx(getGlobalStorage()->processCodeHeap, rpl->crcBuffer, rpl->crcBufferSize); rpl->crcBuffer = nullptr; rpl->crcBufferSize = 0u; rpl->sectionAddressBuffer[rpl->elfHeader.shnum - 2] = virt_addr { 0 }; rpl->nextLoadedRpl = gpLoaderShared->loadedModules; gpLoaderShared->loadedModules = rpl; return 0; } static int32_t LiInitSharedForAll() { Loader_LogEntry(2, 1, 512, "LiInitSharedForAll"); // Setup tracking compression block heap auto outAllocPtr = virt_ptr<void> { nullptr }; auto tinyHeapError = TinyHeap_Alloc(gpSharedCodeHeapTracking, 0x430, -4, &outAllocPtr); if (tinyHeapError < TinyHeapError::OK) { Loader_Panic(0x13000C, "***Could not allocate memory for tracking compression blocks for shared data."); } sgpTrackComp = virt_cast<TinyHeap *>(outAllocPtr); tinyHeapError = TinyHeap_Setup(sgpTrackComp, 0x430, virt_cast<void *>(virt_addr { 0x10000000 }), 0x60000000); // 1536 mb?? if (tinyHeapError < TinyHeapError::OK) { Loader_Panic(0x13000E, "***Could not setup heap for tracking compression blocks for shared data."); } // First load coreinit.rpl auto error = sLoadOneShared("coreinit.rpl"); if (error) { Loader_Panic(0x13000F, "***Could not bounceload coreinit.rpl to shared code area."); } // Load remaining shared libraries Loader_LogEntry(2, 1, 0, "LiInitSharedForAll load GRP start"); for (auto &name : SharedLibraryList) { error = sLoadOneShared(name); if (error) { Loader_Panic(0x130010, "*** Could not bounceload shared library to shared code area."); } } Loader_LogEntry(2, 1, 0, "LiInitSharedForAll load GRP end"); gpLoaderShared->dataBufferHead = align_up(gpLoaderShared->dataBufferHead, 0x100); // Count number of track comp blocks for (auto block = TinyHeap_Enum(sgpTrackComp, nullptr, nullptr, nullptr); block; block = TinyHeap_Enum(sgpTrackComp, block, nullptr, nullptr)) { ++gpLoaderShared->numUsedTrackCompBlocks; } for (auto block = TinyHeap_EnumFree(sgpTrackComp, nullptr, nullptr, nullptr); block; block = TinyHeap_EnumFree(sgpTrackComp, block, nullptr, nullptr)) { ++gpLoaderShared->numFreeTrackCompBlocks; } tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking, static_cast<int32_t>(sizeof(LiCompressedSharedDataTracking) * (gpLoaderShared->numFreeTrackCompBlocks + gpLoaderShared->numUsedTrackCompBlocks)), -4, &outAllocPtr); if (tinyHeapError != TinyHeapError::OK) { Loader_Panic(0x130011, "***Coult not allocate enough space for compressed shared data tracking."); } gpLoaderShared->freeTrackCompBlocks = virt_cast<LiCompressedSharedDataTracking *>(outAllocPtr); gpLoaderShared->usedTrackCompBlocks = virt_cast<LiCompressedSharedDataTracking *>( virt_cast<virt_addr>(outAllocPtr) + (sizeof(LiCompressedSharedDataTracking) * gpLoaderShared->numFreeTrackCompBlocks)); auto blockIndex = 0u; auto blockPointer = virt_ptr<void> { 0 }; auto blockSize = uint32_t { 0 }; for (auto block = TinyHeap_Enum(sgpTrackComp, nullptr, &blockPointer, &blockSize); block; block = TinyHeap_Enum(sgpTrackComp, block, &blockPointer, &blockSize)) { gpLoaderShared->usedTrackCompBlocks[blockIndex].data = blockPointer; gpLoaderShared->usedTrackCompBlocks[blockIndex].intialisationData = nullptr; gpLoaderShared->usedTrackCompBlocks[blockIndex].compressedSize = 0u; gpLoaderShared->usedTrackCompBlocks[blockIndex].size = blockSize; ++blockIndex; } blockIndex = 0u; for (auto block = TinyHeap_EnumFree(sgpTrackComp, nullptr, &blockPointer, &blockSize); block; block = TinyHeap_EnumFree(sgpTrackComp, block, &blockPointer, &blockSize)) { gpLoaderShared->freeTrackCompBlocks[blockIndex].data = blockPointer; gpLoaderShared->freeTrackCompBlocks[blockIndex].intialisationData = nullptr; gpLoaderShared->freeTrackCompBlocks[blockIndex].compressedSize = 0u; gpLoaderShared->freeTrackCompBlocks[blockIndex].size = blockSize; ++blockIndex; } --gpLoaderShared->numFreeTrackCompBlocks; // Why? i do not know. // Allocate temporary buffer to use for compressing free blocks auto compressedInitialisationData = virt_ptr<void> { nullptr }; tinyHeapError = TinyHeap_Alloc(gpSharedCodeHeapTracking, TinyHeap_GetLargestFree(gpSharedCodeHeapTracking) - 4, 4, &compressedInitialisationData); if (tinyHeapError != TinyHeapError::OK) { Loader_Panic(0x130012, "***Could not allocate space for compressed initialization data."); } for (auto i = 0u; i < gpLoaderShared->numFreeTrackCompBlocks; ++i) { auto &block = gpLoaderShared->freeTrackCompBlocks[i]; auto compressedBlockSize = uLongf { block.size }; if (block.size > 0x200) { error = compress(reinterpret_cast<Bytef *>(compressedInitialisationData.get()), &compressedBlockSize, reinterpret_cast<Bytef *>(block.data.get()), block.size); if (error != Z_OK) { if (error == Z_MEM_ERROR) { error = Error::ZlibMemError; LiSetFatalError(0x187298u, 1u, 0, "LiInitSharedForAll", 487); } else if (error == Z_BUF_ERROR) { error = Error::ZlibBufError; LiSetFatalError(0x18729Bu, 1u, 1, "LiInitSharedForAll", 483); } Loader_Panic(0x130013, "***Could not compress initialization data for processes shared libraries."); } } if (((100 * compressedBlockSize) / block.size) <= 90) { // Stored the compressed initialisation data when compressed size < 90% tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking, static_cast<int32_t>(compressedBlockSize), -4, &outAllocPtr); if (tinyHeapError != TinyHeapError::OK) { Loader_Panic(0x130015, "***Could not allocate space for compressed shared initialization data."); error = static_cast<int32_t>(tinyHeapError); break; } block.intialisationData = outAllocPtr; block.compressedSize = static_cast<uint32_t>(compressedBlockSize); std::memcpy(block.intialisationData.get(), compressedInitialisationData.get(), compressedBlockSize); Loader_FlushDataRangeNoSync(virt_cast<virt_addr>(block.intialisationData), block.compressedSize); } else { // Store uncompressed tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking, block.size, -4, &outAllocPtr); if (tinyHeapError != TinyHeapError::OK) { Loader_Panic(0x130015, "***Could not allocate space for compressed shared initialization data."); error = static_cast<int32_t>(tinyHeapError); break; } block.intialisationData = outAllocPtr; std::memcpy(block.intialisationData.get(), block.data.get(), block.size); Loader_FlushDataRangeNoSync(virt_cast<virt_addr>(block.intialisationData), block.size); } } TinyHeap_Free(gpSharedCodeHeapTracking, compressedInitialisationData); TinyHeap_Free(gpSharedCodeHeapTracking, sgpTrackComp); Loader_LogEntry(2, 1, 1024, "LiInitSharedForAll"); return error; } int32_t initialiseSharedHeaps() { constexpr auto SharedCodeTrackingSize = 0x830u; constexpr auto SharedReadTrackingSize = 0x1030u; constexpr auto LoaderSharedAddr = virt_addr { 0xFA000000 }; constexpr auto SharedCodeHeapTrackingAddr = LoaderSharedAddr + sizeof(LoaderShared); constexpr auto SharedReadHeapTrackingAddr = SharedCodeHeapTrackingAddr + SharedCodeTrackingSize; constexpr auto SharedCodeHeapAddr = virt_addr { 0x01000000 }; constexpr auto SharedCodeHeapSize = uint32_t { 0x007E0000 }; constexpr auto SharedReadHeapAddr = virt_addr { 0xF8000000 }; constexpr auto SharedReadHeapSize = uint32_t { 0x03000000 }; constexpr auto SharedReadHeapReserveSize = uint32_t { 0x02000000 }; // Unknown gpLoaderShared = virt_cast<LoaderShared *>(LoaderSharedAddr); gpSharedCodeHeapTracking = virt_cast<TinyHeap *>(SharedCodeHeapTrackingAddr); gpSharedReadHeapTracking = virt_cast<TinyHeap *>(SharedReadHeapTrackingAddr); if (getProcFlags().isFirstProcess()) { if (TinyHeap_Setup(gpSharedCodeHeapTracking, SharedCodeTrackingSize, virt_cast<void *>(SharedCodeHeapAddr), SharedCodeHeapSize) != TinyHeapError::OK) { Loader_Panic(0x130002, "***Could not initialize shared code heap tracking."); } if (TinyHeap_Setup(gpSharedReadHeapTracking, SharedReadTrackingSize, virt_cast<void *>(SharedReadHeapAddr), SharedReadHeapSize) != TinyHeapError::OK) { Loader_Panic(0x130003, "***Could not initialize data heap tracking."); } // Reserve space for loader .text section if (TinyHeap_AllocAt(gpSharedCodeHeapTracking, virt_cast<void *>(SharedCodeHeapAddr), align_up(0x1C758, 1024)) != TinyHeapError::OK) { Loader_Panic(0x130004, "***Could not reserve shared code space for loader."); } // Reserve unknown chunk in shared read heap if (TinyHeap_AllocAt(gpSharedReadHeapTracking, virt_cast<void *>(SharedReadHeapAddr), SharedReadHeapReserveSize) != TinyHeapError::OK) { Loader_Panic(0x130005, "***Could not reserve read/only space for heap tracking."); } // Reserve gpLoaderShared if (TinyHeap_AllocAt(gpSharedReadHeapTracking, gpLoaderShared, sizeof(LoaderShared)) != TinyHeapError::OK) { Loader_Panic(0x130006, "***Could not reserve read/only space for heap tracking."); } // Reserve gpSharedCodeHeapTracking and gpSharedReadHeapTracking if (TinyHeap_AllocAt(gpSharedReadHeapTracking, virt_cast<void *>(SharedCodeHeapTrackingAddr), SharedCodeTrackingSize + SharedReadTrackingSize) != TinyHeapError::OK) { Loader_Panic(0x130007, "***Could not reserve read/only space for heap tracking."); } // Clear gpLoaderShared std::memset(gpLoaderShared.get(), 0, sizeof(LoaderShared)); gpLoaderShared->dataBufferHead = virt_cast<void *>(virt_addr { 0x10000000 }); Loader_ReportWarn("Title Loc is {}", getProcTitleLoc()); } return 0; } virt_ptr<LOADED_RPL> findLoadedSharedModule(std::string_view name) { for (auto module = gpLoaderShared->loadedModules; module; module = module->nextLoadedRpl) { auto moduleName = std::string_view { module->moduleNameBuffer.get(), module->moduleNameLen }; if (moduleName == name) { return module; } } return nullptr; } int32_t LiInitSharedForProcess(virt_ptr<RPL_STARTINFO> initData) { auto globals = getGlobalStorage(); Loader_LogEntry(2, 1, 512, "LiInitSharedForProcess"); if (globals->currentUpid != kernel::UniqueProcessId::Root) { if (getProcFlags().disableSharedLibraries()) { initData->dataAreaStart = 0x10000000u; Loader_ReportWarn("Shared Libraries disabled in process {}", static_cast<int>(globals->currentUpid.value())); Loader_LogEntry(2, 1, 1024, "LiInitSharedForProcess"); return 0; } } if (getProcFlags().isFirstProcess()) { auto error = LiInitSharedForAll(); if (!error) { initData->dataAreaStart = virt_cast<virt_addr>(gpLoaderShared->dataBufferHead); // Allocate some memory to place HLE unimplemented function call stubs sHleUnimplementedStubMemorySize = TinyHeap_GetLargestFree(gpSharedCodeHeapTracking) - 4; TinyHeap_Alloc(gpSharedCodeHeapTracking, sHleUnimplementedStubMemorySize, 4, &sHleUnimplementedStubMemory); hle::setUnimplementedFunctionStubMemory(sHleUnimplementedStubMemory, sHleUnimplementedStubMemorySize); } Loader_LogEntry(2, 1, 1024, "LiInitSharedForProcess"); return error; } // TODO: Finish LiInitSharedForProcess for non-first processes decaf_abort("LiInitSharedForProcess not implemented for not first process"); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_shared.h ================================================ #pragma once #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::loader { struct RPL_STARTINFO; struct LOADED_RPL; namespace internal { int32_t initialiseSharedHeaps(); virt_ptr<LOADED_RPL> findLoadedSharedModule(std::string_view moduleName); int32_t LiInitSharedForProcess(virt_ptr<RPL_STARTINFO> initData); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_utils.h ================================================ #pragma once #include "cafe_loader_basics.h" #include "cafe_loader_rpl.h" #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::loader::internal { inline std::string_view LiResolveModuleName(std::string_view name) { auto pos = name.find_last_of("\\/"); if (pos != std::string_view::npos) { name = name.substr(pos + 1); } pos = name.find_first_of("."); if (pos != std::string_view::npos) { name = name.substr(0, pos); } return name; } inline void LiResolveModuleName(virt_ptr<virt_ptr<char>> moduleName, virt_ptr<uint32_t> moduleNameLen) { auto name = std::string_view { moduleName->get(), *moduleNameLen }; auto pos = name.find_last_of("\\/"); if (pos != std::string_view::npos) { auto diff = static_cast<uint32_t>(pos + 1); name = name.substr(diff); *moduleName += diff; *moduleNameLen -= diff; } pos = name.find_first_of("."); if (pos != std::string_view::npos) { auto diff = static_cast<uint32_t>(name.size() - pos); *moduleNameLen -= diff; } (*moduleName)[*moduleNameLen] = char { 0 }; } inline virt_ptr<rpl::SectionHeader> getSectionHeader(virt_ptr<LOADED_RPL> rpl, uint32_t index) { return virt_cast<rpl::SectionHeader *>( virt_cast<virt_addr>(rpl->sectionHeaderBuffer) + (index * rpl->elfHeader.shentsize)); } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_zlib.cpp ================================================ #include "cafe_loader_iop.h" #include "cafe_loader_zlib.h" #include <zlib.h> namespace cafe::loader::internal { uint32_t LiCalcCRC32(uint32_t crc, virt_ptr<const void> data, uint32_t size) { LiCheckAndHandleInterrupts(); if (!data || !size) { return crc; } crc = crc32(crc, reinterpret_cast<const Bytef *>(data.get()), size); LiCheckAndHandleInterrupts(); return crc; } } // namespace cafe::loader::internal ================================================ FILE: src/libdecaf/src/cafe/loader/cafe_loader_zlib.h ================================================ #pragma once #include <libcpu/be2_struct.h> #include <string_view> namespace cafe::loader { struct LOADED_RPL; namespace internal { uint32_t LiCalcCRC32(uint32_t crc, virt_ptr<const void> data, uint32_t size); int32_t ZLIB_UncompressFromStream(virt_ptr<LOADED_RPL> basics, uint32_t sectionIndex, std::string_view boundsName, uint32_t fileOffset, uint32_t deflatedSize, virt_ptr<void> dst, uint32_t *inflatedSize); } // namespace internal } // namespace cafe::loader ================================================ FILE: src/libdecaf/src/cafe/nn/cafe_nn_ipc_bufferallocator.cpp ================================================ #include "cafe_nn_ipc_bufferallocator.h" #include "cafe/libraries/coreinit/coreinit_ios.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" using namespace cafe::coreinit; namespace nn::ipc { BufferAllocator::BufferAllocator() { OSInitMutex(virt_addrof(mMutex)); } void BufferAllocator::initialise(virt_ptr<void> buffer, uint32_t size) { auto numBuffers = size / BufferSize; // Set up a linked list of free buffers auto bufferAddr = virt_cast<virt_addr>(buffer); mFreeBuffers = virt_cast<FreeBuffer *>(bufferAddr); for (auto i = 0u; i < numBuffers; ++i) { auto curBuffer = virt_cast<FreeBuffer *>(bufferAddr); auto nextBuffer = virt_cast<FreeBuffer *>(bufferAddr + BufferSize); if (i < numBuffers - 1) { curBuffer->next = nextBuffer; } else { curBuffer->next = nullptr; } bufferAddr += BufferSize; } } virt_ptr<void> BufferAllocator::allocate(uint32_t size) { auto result = virt_ptr<void> { nullptr }; decaf_check(size <= BufferSize); OSLockMutex(virt_addrof(mMutex)); result = mFreeBuffers; mFreeBuffers = mFreeBuffers->next; OSUnlockMutex(virt_addrof(mMutex)); return result; } void BufferAllocator::deallocate(virt_ptr<void> ptr) { auto buffer = virt_cast<FreeBuffer *>(ptr); OSLockMutex(virt_addrof(mMutex)); buffer->next = mFreeBuffers; mFreeBuffers = buffer; OSUnlockMutex(virt_addrof(mMutex)); } } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/cafe/nn/cafe_nn_ipc_bufferallocator.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_ios.h" #include "cafe/libraries/coreinit/coreinit_mutex.h" #include <libcpu/be2_struct.h> namespace nn::ipc { class BufferAllocator { static constexpr auto BufferSize = 256u; struct FreeBuffer { be2_virt_ptr<FreeBuffer> next; }; public: BufferAllocator(); void initialise(virt_ptr<void> buffer, uint32_t size); virt_ptr<void> allocate(uint32_t size); void deallocate(virt_ptr<void> ptr); private: be2_struct<cafe::coreinit::OSMutex> mMutex; be2_virt_ptr<FreeBuffer> mFreeBuffers; }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/cafe/nn/cafe_nn_ipc_client.cpp ================================================ #include "cafe_nn_ipc_client.h" #include "cafe/libraries/coreinit/coreinit_ios.h" #include "nn/nn_result.h" using namespace cafe::coreinit; namespace nn::ipc { Result Client::initialise(virt_ptr<const char> device) { auto error = IOS_Open(device, IOSOpenMode::None); if (error < 0) { return ios::convertError(error); } mHandle = static_cast<IOSHandle>(error); return ios::ResultOK; } Result Client::close() { return ios::convertError(IOS_Close(mHandle)); } bool Client::isInitialised() const { return mHandle >= 0; } Result Client::sendSyncRequest(const detail::ClientCommandData &command) { auto error = IOS_Ioctlv(mHandle, 0, command.numVecIn, command.numVecOut, command.vecsBuffer); if (error < 0) { return ios::convertError(error); } return ResultSuccess; } } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/cafe/nn/cafe_nn_ipc_client.h ================================================ #pragma once #include "cafe_nn_ipc_client_command.h" #include "cafe/libraries/coreinit/coreinit_ios.h" #include "nn/ios/nn_ios_error.h" #include "nn/nn_result.h" namespace nn::ipc { class Client { public: Result initialise(virt_ptr<const char> device); Result close(); bool isInitialised() const; template<typename CommandType> Result sendSyncRequest(const ClientCommand<CommandType> &command) { return sendSyncRequest(command.getCommandData()); } private: Result sendSyncRequest(const detail::ClientCommandData &command); private: cafe::coreinit::IOSHandle mHandle { -1 }; }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/cafe/nn/cafe_nn_ipc_client_command.h ================================================ #pragma once #include "cafe_nn_ipc_bufferallocator.h" #include "nn/nn_result.h" #include "nn/ios/nn_ios_error.h" #include "nn/ipc/nn_ipc_format.h" #include "nn/ipc/nn_ipc_managedbuffer.h" #include <array> #include <libcpu/be2_struct.h> namespace nn::ipc { namespace detail { template<typename... Ts> struct ManagedBufferCount; template<> struct ManagedBufferCount<> { static constexpr auto Input = 0; static constexpr auto Output = 0; }; template<typename T, typename... Ts> struct ManagedBufferCount<T, Ts...> { static constexpr auto Input = 0 + ManagedBufferCount<Ts...>::Input; static constexpr auto Output = 0 + ManagedBufferCount<Ts...>::Output; }; template<typename T, typename... Ts> struct ManagedBufferCount<InBuffer<T>, Ts...> { static constexpr auto Input = 1 + ManagedBufferCount<Ts...>::Input; static constexpr auto Output = 0 + ManagedBufferCount<Ts...>::Output; }; template<typename T, typename... Ts> struct ManagedBufferCount<InOutBuffer<T>, Ts...> { static constexpr auto Input = 0 + ManagedBufferCount<Ts...>::Input; static constexpr auto Output = 1 + ManagedBufferCount<Ts...>::Output; }; template<typename T, typename... Ts> struct ManagedBufferCount<OutBuffer<T>, Ts...> { static constexpr auto Input = 0 + ManagedBufferCount<Ts...>::Input; static constexpr auto Output = 1 + ManagedBufferCount<Ts...>::Output; }; struct ManagedBufferInfo { virt_ptr<void> ipcBuffer = nullptr; virt_ptr<void> userBuffer; uint32_t userBufferSize; virt_ptr<void> unalignedBeforeBuffer; uint32_t unalignedBeforeBufferSize; virt_ptr<void> alignedBuffer; uint32_t alignedBufferSize; virt_ptr<void> unalignedAfterBuffer; uint32_t unalignedAfterBufferSize; bool output; }; struct ClientCommandData { virt_ptr<BufferAllocator> allocator; virt_ptr<void> requestBuffer; virt_ptr<void> responseBuffer; virt_ptr<::ios::IoctlVec> vecsBuffer; ManagedBufferInfo *ioBuffers; int numVecIn; int numVecOut; }; template<typename Type> struct IpcSerialiser { static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx, int &outputVecIdx, const Type &value) { auto ptr = virt_cast<Type *>(virt_cast<virt_addr>(data.requestBuffer) + offset); *ptr = value; offset += sizeof(Type); } }; template<> struct IpcSerialiser<ManagedBuffer> { static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx, int &outputVecIdx, const ManagedBuffer &userBuffer) { // The user buffer pointer is not guaranteed to be aligned so we must // split the buffer by separately reading / writing the unaligned data at // the start and end of the user buffer. auto &ioBuffer = data.ioBuffers[(inputVecIdx + outputVecIdx) / 2]; ioBuffer.ipcBuffer = data.allocator->allocate(256); ioBuffer.userBuffer = userBuffer.ptr; ioBuffer.userBufferSize = userBuffer.size; ioBuffer.output = userBuffer.output; auto midPoint = virt_cast<virt_addr>(ioBuffer.ipcBuffer) + 64; auto unalignedStart = virt_cast<virt_addr>(userBuffer.ptr); auto unalignedEnd = unalignedStart + userBuffer.size; auto alignedStart = align_up(unalignedStart, 64); auto alignedEnd = align_down(unalignedEnd, 64); if (unalignedEnd <= alignedStart) { // Whole buffer is before alignment ioBuffer.unalignedBeforeBufferSize = ioBuffer.userBufferSize; ioBuffer.unalignedBeforeBuffer = virt_cast<void *>(midPoint - ioBuffer.unalignedBeforeBufferSize); ioBuffer.alignedBuffer = nullptr; ioBuffer.alignedBufferSize = 0; ioBuffer.unalignedAfterBuffer = nullptr; ioBuffer.unalignedAfterBufferSize = 0; } else { // Split over unaligned before / aligned / unaligned after ioBuffer.unalignedBeforeBufferSize = static_cast<uint32_t>(alignedStart - unalignedStart); ioBuffer.unalignedBeforeBuffer = virt_cast<void *>(midPoint - ioBuffer.unalignedBeforeBufferSize); ioBuffer.alignedBuffer = virt_cast<void *>(alignedStart); ioBuffer.alignedBufferSize = static_cast<uint32_t>(alignedEnd - alignedStart); ioBuffer.unalignedAfterBufferSize = static_cast<uint32_t>(unalignedEnd - alignedEnd); ioBuffer.unalignedAfterBuffer = virt_cast<void *>(midPoint); } if (userBuffer.input) { // Copy the unaligned buffer input std::memcpy(ioBuffer.unalignedBeforeBuffer.get(), virt_cast<void *>(unalignedStart).get(), ioBuffer.unalignedBeforeBufferSize); std::memcpy( ioBuffer.unalignedAfterBuffer.get(), virt_cast<void *>(unalignedEnd - ioBuffer.unalignedAfterBufferSize).get(), ioBuffer.unalignedAfterBufferSize); } // Calculate our ioctlv vecs indices auto alignedBufferIndex = uint8_t { 0 }; auto unalignedBufferIndex = uint8_t { 0 }; auto bufferIndexOffset = uint8_t { 0 }; if (userBuffer.output) { alignedBufferIndex = static_cast<uint8_t>(outputVecIdx++); unalignedBufferIndex = static_cast<uint8_t>(outputVecIdx++); bufferIndexOffset = 1u; } else { alignedBufferIndex = static_cast<uint8_t>(inputVecIdx++); unalignedBufferIndex = static_cast<uint8_t>(inputVecIdx++); bufferIndexOffset = static_cast<uint8_t>(1 + data.numVecOut); } // Update our ioctlv vecs buffer auto &alignedBufferVec = data.vecsBuffer[bufferIndexOffset + alignedBufferIndex]; auto &unalignedBufferVec = data.vecsBuffer[bufferIndexOffset + unalignedBufferIndex]; alignedBufferVec.vaddr = virt_cast<virt_addr>(ioBuffer.alignedBuffer); alignedBufferVec.len = ioBuffer.alignedBufferSize; if (ioBuffer.unalignedBeforeBufferSize + ioBuffer.unalignedAfterBufferSize) { unalignedBufferVec.vaddr = virt_cast<virt_addr>(ioBuffer.unalignedBeforeBuffer); unalignedBufferVec.len = ioBuffer.unalignedBeforeBufferSize + ioBuffer.unalignedAfterBufferSize; } else { unalignedBufferVec.vaddr = virt_addr { 0u }; unalignedBufferVec.len = 0u; } // Serialise the buffer info to the request auto managedBuffer = virt_cast<ManagedBufferParameter *>( virt_cast<virt_addr>(data.requestBuffer) + offset); managedBuffer->alignedBufferSize = ioBuffer.alignedBufferSize; managedBuffer->unalignedBeforeBufferSize = static_cast<uint8_t>(ioBuffer.unalignedBeforeBufferSize); managedBuffer->unalignedAfterBufferSize = static_cast<uint8_t>(ioBuffer.unalignedAfterBufferSize); managedBuffer->alignedBufferIndex = alignedBufferIndex; managedBuffer->unalignedBufferIndex = unalignedBufferIndex; offset += 8; } }; template<typename Type> struct IpcSerialiser<::nn::ipc::InBuffer<Type>> { static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx, int &outputVecIdx, const ManagedBuffer &userBuffer) { IpcSerialiser<ManagedBuffer>::write(data, offset, inputVecIdx, outputVecIdx, userBuffer); } }; template<typename Type> struct IpcSerialiser<::nn::ipc::InOutBuffer<Type>> { static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx, int &outputVecIdx, const ManagedBuffer &userBuffer) { IpcSerialiser<ManagedBuffer>::write(data, offset, inputVecIdx, outputVecIdx, userBuffer); } }; template<typename Type> struct IpcSerialiser<::nn::ipc::OutBuffer<Type>> { static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx, int &outputVecIdx, const ManagedBuffer &userBuffer) { IpcSerialiser<ManagedBuffer>::write(data, offset, inputVecIdx, outputVecIdx, userBuffer); } }; template<typename Type> struct IpcDeserialiser { static void read(ClientCommandData &data, size_t &offset, Type &value) { auto ptr = virt_cast<Type *>( virt_cast<virt_addr>(data.responseBuffer) + offset); value = *ptr; offset += sizeof(Type); } }; template<int, int, typename... Types> struct ClientCommandHelper; template<int ServiceId, int CommandId, typename... ParameterTypes, typename... ResponseTypes> struct ClientCommandHelper<ServiceId, CommandId, std::tuple<ParameterTypes...>, std::tuple<ResponseTypes...>> { static constexpr auto NumInputBuffers = ManagedBufferCount<ParameterTypes...>::Input; static constexpr auto NumOutputBuffers = ManagedBufferCount<ParameterTypes...>::Output; static constexpr auto NumManagedBuffers = NumInputBuffers + NumOutputBuffers; public: ClientCommandHelper(virt_ptr<BufferAllocator> allocator) { // Allocate buffers mData.allocator = allocator; mData.vecsBuffer = virt_cast<::ios::IoctlVec *>(allocator->allocate(128)); mData.requestBuffer = allocator->allocate(128); mData.responseBuffer = allocator->allocate(128); mData.ioBuffers = mManagedBufferInfo.data(); mData.numVecIn = 1 + 2 * NumOutputBuffers; mData.numVecOut = 1 + 2 * NumInputBuffers; std::memset(mData.vecsBuffer.get(), 0, 128); std::memset(mData.requestBuffer.get(), 0, 128); std::memset(mData.responseBuffer.get(), 0, 128); // Setup request header auto request = virt_cast<RequestHeader *>(mData.requestBuffer); request->unk0x00 = 1u; request->command = static_cast<uint32_t>(CommandId); request->unk0x08 = 0u; request->service = static_cast<uint32_t>(ServiceId); // Setup vecs buffer mData.vecsBuffer[0].vaddr = virt_cast<virt_addr>(mData.responseBuffer); mData.vecsBuffer[0].len = 128u; mData.vecsBuffer[mData.numVecIn].vaddr = virt_cast<virt_addr>(mData.requestBuffer); mData.vecsBuffer[mData.numVecIn].len = 128u; } ~ClientCommandHelper() { if (mData.vecsBuffer) { mData.allocator->deallocate(mData.vecsBuffer); } if (mData.requestBuffer) { mData.allocator->deallocate(mData.requestBuffer); } if (mData.responseBuffer) { mData.allocator->deallocate(mData.responseBuffer); } for (auto &ioBuffer : mManagedBufferInfo) { if (ioBuffer.ipcBuffer) { mData.allocator->deallocate(ioBuffer.ipcBuffer); } } } public: const ClientCommandData &getCommandData() const { return mData; } void setParameters(ParameterTypes... params) { auto offset = sizeof(RequestHeader); auto inputVecIdx = 0; auto outputVecIdx = 0; (IpcSerialiser<ParameterTypes>::write(mData, offset, inputVecIdx, outputVecIdx, params), ...); } Result readResponse(ResponseTypes &... responses) { auto header = virt_cast<ResponseHeader *>(mData.responseBuffer); auto result = Result { static_cast<uint32_t>(static_cast<int32_t>(header->result)) }; // Read unaligned output buffer data for (auto &ioBuffer : mManagedBufferInfo) { if (!ioBuffer.output) { continue; } std::memcpy(ioBuffer.userBuffer.get(), ioBuffer.unalignedBeforeBuffer.get(), ioBuffer.unalignedBeforeBufferSize); auto userAfterBufferAddr = virt_cast<virt_addr>(ioBuffer.userBuffer) + ioBuffer.userBufferSize - ioBuffer.unalignedAfterBufferSize; std::memcpy(virt_cast<void *>(userAfterBufferAddr).get(), ioBuffer.unalignedAfterBuffer.get(), ioBuffer.unalignedAfterBufferSize); } // Read response values auto offset = sizeof(ResponseHeader); (IpcDeserialiser<ResponseTypes>::read(mData, offset, responses), ...); return result; } private: ClientCommandData mData; std::array<ManagedBufferInfo, NumManagedBuffers> mManagedBufferInfo; }; } // namespace detail template<typename CommandType> class ClientCommand; template<typename CommandType> class ClientCommand : public detail::ClientCommandHelper<CommandType::service, CommandType::command, typename CommandType::parameters, typename CommandType::response> { public: ClientCommand(virt_ptr<BufferAllocator> allocator) : detail::ClientCommandHelper<CommandType::service, CommandType::command, typename CommandType::parameters, typename CommandType::response>(allocator) { } }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/cafe/nn/cafe_nn_os_criticalsection.h ================================================ #pragma once #include "cafe/libraries/coreinit/coreinit_fastmutex.h" #include <libcpu/be2_struct.h> namespace nn::os { struct CriticalSection { CriticalSection() { cafe::coreinit::OSFastMutex_Init(virt_addrof(_mutex), nullptr); } void lock() { cafe::coreinit::OSFastMutex_Lock(virt_addrof(_mutex)); } bool try_lock() { return !!cafe::coreinit::OSFastMutex_TryLock(virt_addrof(_mutex)); } void unlock() { cafe::coreinit::OSFastMutex_Unlock(virt_addrof(_mutex)); } be2_struct<cafe::coreinit::OSFastMutex> _mutex; }; CHECK_OFFSET(CriticalSection, 0x00, _mutex); CHECK_SIZE(CriticalSection, 0x2C); } // namespace nn::os ================================================ FILE: src/libdecaf/src/debug_api/debug_api_analyse.cpp ================================================ #include "decaf_debug_api.h" #include "cafe/loader/cafe_loader_entry.h" #include "cafe/loader/cafe_loader_loaded_rpl.h" #include <fmt/core.h> #include <libcpu/espresso/espresso_disassembler.h> #include <libcpu/espresso/espresso_instructionset.h> #include <libcpu/mem.h> namespace decaf::debug { struct FunctionListPredicate { bool operator () (const AnalyseDatabase::Function &func, VirtualAddress addr) { return func.start < addr; } bool operator () (VirtualAddress addr, const AnalyseDatabase::Function &func) { return addr < func.start; } }; struct RFunctionListPredicate { bool operator () (const AnalyseDatabase::Function &func, VirtualAddress addr) { return func.start > addr; } }; const AnalyseDatabase::Function * analyseLookupFunction(const AnalyseDatabase &db, VirtualAddress address) { auto itr = std::lower_bound(db.functions.begin(), db.functions.end(), address, FunctionListPredicate { }); if (itr == db.functions.end() || itr->start != address) { return nullptr; } return &*itr; } template<typename ConstOptionalDatabase> static auto findFunctionContainingAddress(ConstOptionalDatabase &db, VirtualAddress address) { auto itr = std::lower_bound(db.functions.rbegin(), db.functions.rend(), address, RFunctionListPredicate{ }); if (itr != db.functions.rend()) { auto &func = *itr; if (address >= func.start && address < func.end) { // The function needs to have an end, or be the first two instructions // since we apply some special display logic to the first two instructions // in a never-ending function... if (func.end != 0xFFFFFFFF || (address == func.start || address == func.start + 4)) { return &func; } } } return static_cast<decltype(&*itr)>(nullptr); } AnalyseDatabase::Lookup analyseLookupAddress(const AnalyseDatabase &db, VirtualAddress address) { auto info = AnalyseDatabase::Lookup { }; if (auto itr = db.instructions.find(address); itr != db.instructions.end()) { info.instruction = &itr->second; } info.function = findFunctionContainingAddress(db, address); return info; } uint32_t analyseScanFunctionEnd(VirtualAddress start) { static const uint32_t MaxScannedBytes = 0x1000u; auto fnStart = start; auto fnMax = start; auto fnEnd = uint32_t { 0xFFFFFFFFu }; for (auto addr = start; addr < start + MaxScannedBytes; addr += 4) { if (!cpu::isValidAddress(cpu::VirtualAddress { addr })) { break; } auto instr = mem::read<espresso::Instruction>(addr); auto data = espresso::decodeInstruction(instr); if (!data) { // If we can't decode this instruction, then we gone done fucked up break; } if (addr > fnMax) { fnMax = addr; } if (espresso::isBranchInstruction(data->id)) { auto branchInfo = espresso::disassembleBranchInfo(data->id, instr, addr, 0, 0, 0); // Ignore call instructions if (!branchInfo.isCall) { if (branchInfo.isVariable) { // We hit a variable non-call instruction, we can't scan // any further than this. If we don't have any instructions // further down, this is the final. if (fnMax > addr) { addr = fnMax; continue; } else { fnEnd = fnMax + 4; break; } } else { if (addr == fnMax && !branchInfo.isConditional) { if (branchInfo.target >= fnStart && branchInfo.target < addr) { // If we are the last instruction, and this instruction unconditionally // branches backwards, that means that we must be at the end of the func. fnEnd = fnMax + 4; break; } } // We cannot follow unconditional branches outside of the function body // that we have already determined, this is because we don't want to follow // tail calls! if (branchInfo.target > fnMax && branchInfo.isConditional) { fnMax = branchInfo.target; } } } } } return fnEnd; } static std::string defaultFunctionName(VirtualAddress address) { return fmt::format("sub_{:08x}", address); } static void markAsFunction(AnalyseDatabase &db, VirtualAddress address, std::string_view name = {}) { // Check if the address is already marked as a function auto function = findFunctionContainingAddress(db, address); if (function) { if (!name.empty()) { // Update the name if a name was passed in function->name = name; } return; } auto func = AnalyseDatabase::Function { }; func.start = address; func.end = analyseScanFunctionEnd(address); if (name.empty()) { func.name = defaultFunctionName(address); } else { func.name = name; } db.functions.insert( std::upper_bound(db.functions.begin(), db.functions.end(), func.start, FunctionListPredicate { }), func); } void analyseToggleAsFunction(AnalyseDatabase &db, VirtualAddress address) { auto itr = std::lower_bound(db.functions.begin(), db.functions.end(), address, FunctionListPredicate{ }); if (itr == db.functions.end() || itr->start != address) { markAsFunction(db, address); } else { db.functions.erase(itr); } } void analyseLoadedModules(AnalyseDatabase &db) { cafe::loader::lockLoader(); for (auto rpl = cafe::loader::getLoadedRplLinkedList(); rpl; rpl = rpl->nextLoadedRpl) { auto symTabHdr = virt_ptr<cafe::loader::rpl::SectionHeader> { nullptr }; auto symTabAddr = virt_addr { 0 }; auto strTabAddr = virt_addr { 0 }; auto textStartAddr = rpl->textAddr; auto textEndAddr = rpl->textAddr + rpl->textSize; // Find symbol section if (rpl->sectionHeaderBuffer) { for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<cafe::loader::rpl::SectionHeader *>( virt_cast<virt_addr>(rpl->sectionHeaderBuffer) + (i * rpl->elfHeader.shentsize)); if (sectionHeader->type == cafe::loader::rpl::SHT_SYMTAB) { symTabHdr = sectionHeader; symTabAddr = rpl->sectionAddressBuffer[i]; strTabAddr = rpl->sectionAddressBuffer[symTabHdr->link]; break; } } } if (symTabHdr && symTabAddr && strTabAddr) { auto symTabEntSize = symTabHdr->entsize ? static_cast<size_t>(symTabHdr->entsize) : sizeof(cafe::loader::rpl::Symbol); auto symTabEntries = symTabHdr->size / symTabEntSize; for (auto i = 0u; i < symTabEntries; ++i) { auto symbol = virt_cast<cafe::loader::rpl::Symbol *>( symTabAddr + (i * symTabEntSize)); auto symbolAddress = virt_addr { static_cast<uint32_t>(symbol->value) }; if ((symbol->info & 0xf) == cafe::loader::rpl::STT_FUNC && symbolAddress >= textStartAddr && symbolAddress < textEndAddr) { auto name = virt_cast<const char *>(strTabAddr + symbol->name); markAsFunction(db, symbol->value, name.get()); } } } } cafe::loader::unlockLoader(); } void analyseCode(AnalyseDatabase &db, VirtualAddress start, VirtualAddress end) { for (auto addr = start; addr < end; addr += 4) { auto instr = mem::read<espresso::Instruction>(addr); auto data = espresso::decodeInstruction(instr); if (!data) { continue; } if (espresso::isBranchInstruction(data->id)) { auto branchInfo = espresso::disassembleBranchInfo(data->id, instr, addr, 0, 0, 0); if (!branchInfo.isCall && !branchInfo.isVariable) { db.instructions[branchInfo.target].sourceBranches.push_back(addr); } // If this is a call, and its not variable, we should mark // the target as a function, since it likely is... if (branchInfo.isCall && !branchInfo.isVariable) { markAsFunction(db, branchInfo.target); } } } } } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debug_api/debug_api_cafe.cpp ================================================ #include "decaf_debug_api.h" #include "cafe/loader/cafe_loader_entry.h" #include "cafe/loader/cafe_loader_loaded_rpl.h" #include "cafe/libraries/coreinit/coreinit_enum_string.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/coreinit/coreinit_thread.h" #include "cafe/libraries/sndcore2/sndcore2_enum.h" #include "cafe/libraries/sndcore2/sndcore2_voice.h" #include "cafe/loader/cafe_loader_entry.h" #include "cafe/kernel/cafe_kernel_loader.h" #include "debugger/debugger.h" namespace decaf::debug { bool findClosestSymbol(VirtualAddress addr, uint32_t *outSymbolDistance, char *symbolNameBuffer, uint32_t symbolNameBufferLength, char *moduleNameBuffer, uint32_t moduleNameBufferLength) { return cafe::kernel::internal::findClosestSymbol(virt_addr { addr }, outSymbolDistance, symbolNameBuffer, symbolNameBufferLength, moduleNameBuffer, moduleNameBufferLength) == 0; } bool getLoadedModuleInfo(CafeModuleInfo &info) { auto rpx = cafe::loader::getLoadedRpx(); if (!rpx) { return false; } info.textAddr = static_cast<uint32_t>(rpx->textAddr); info.textSize = rpx->textSize; info.dataAddr = static_cast<uint32_t>(rpx->dataAddr); info.dataSize = rpx->textSize; return true; } bool sampleCafeMemorySegments(std::vector<CafeMemorySegment> &segments) { cafe::loader::lockLoader(); for (auto rpl = cafe::loader::getLoadedRplLinkedList(); rpl; rpl = rpl->nextLoadedRpl) { if (!rpl->sectionAddressBuffer || !rpl->sectionAddressBuffer || !rpl->moduleNameBuffer || !rpl->moduleNameLen || !rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]) { continue; } auto rplName = std::string_view { rpl->moduleNameBuffer.get(), rpl->moduleNameLen }; auto shStrTab = virt_cast<const char *>(rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]) .get(); for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) { auto sectionHeader = virt_cast<cafe::loader::rpl::SectionHeader *>( virt_cast<virt_addr>(rpl->sectionHeaderBuffer) + (i * rpl->elfHeader.shentsize)); if (rpl->sectionAddressBuffer[i] && sectionHeader->size != 0 && (sectionHeader->flags & cafe::loader::rpl::SHF_ALLOC)) { auto &segment = segments.emplace_back(); segment.name = fmt::format("{}:{}", rplName, shStrTab + sectionHeader->name); segment.address = static_cast<uint32_t>(rpl->sectionAddressBuffer[i]); segment.size = sectionHeader->size; segment.align = sectionHeader->addralign; segment.read = sectionHeader->flags & cafe::loader::rpl::SHF_ALLOC; segment.write = sectionHeader->flags & cafe::loader::rpl::SHF_WRITE; segment.execute = sectionHeader->flags & cafe::loader::rpl::SHF_EXECINSTR; } } } cafe::loader::unlockLoader(); return true; } static void sampleThreadInfo(CafeThread &info, virt_ptr<cafe::coreinit::OSThread> thread) { info.handle = static_cast<uint32_t>(virt_cast<virt_addr>(thread)); info.id = thread->id; info.name = thread->name ? thread->name.get() : ""; info.priority = thread->priority; info.basePriority = thread->basePriority; info.state = static_cast<CafeThread::ThreadState>(thread->state.value()); info.affinity = static_cast<CafeThread::ThreadAffinity>(thread->attr & cafe::coreinit::OSThreadAttributes::AffinityAny); info.stackStart = static_cast<uint32_t>(virt_cast<virt_addr>(thread->stackStart)); info.stackEnd = static_cast<uint32_t>(virt_cast<virt_addr>(thread->stackEnd)); info.executionTime = std::chrono::nanoseconds { thread->coreTimeConsumedNs.value() }; } static void copyThreadRegisters(CafeThread &info, virt_ptr<cafe::coreinit::OSThread> thread) { info.cia = thread->context.cia; info.nia = thread->context.nia; info.gpr = thread->context.gpr; info.fpr = thread->context.fpr; info.ps1 = thread->context.psf; info.cr = thread->context.cr; info.xer = thread->context.xer; info.lr = thread->context.lr; info.ctr = thread->context.ctr; info.msr = 0u; } static void copyContextRegisters(CafeThread &info, const decaf::debug::CpuContext *context) { info.cia = context->cia; info.nia = context->nia; info.gpr = context->gpr; info.fpr = context->fpr; info.ps1 = context->ps1; info.cr = context->cr; info.xer = context->xer; info.lr = context->lr; info.ctr = context->ctr; info.msr = context->msr; } bool sampleCafeRunningThread(int coreId, CafeThread &info) { if (!decaf::debug::isPaused()) { return false; } cafe::coreinit::internal::lockScheduler(); auto thread = cafe::coreinit::internal::getCoreRunningThread(coreId); if (!thread) { cafe::coreinit::internal::unlockScheduler(); return false; } sampleThreadInfo(info, thread); cafe::coreinit::internal::unlockScheduler(); // Copy registers from paused context info.coreId = coreId; copyContextRegisters(info, decaf::debug::getPausedContext(coreId)); info.executionTime += std::chrono::nanoseconds { cafe::coreinit::internal::getCoreThreadRunningTime(coreId) }; return true; } bool sampleCafeThreads(std::vector<CafeThread> &threads) { auto paused = decaf::debug::isPaused(); cafe::coreinit::internal::lockScheduler(); virt_ptr<cafe::coreinit::OSThread> runningThreads[] = { cafe::coreinit::internal::getCoreRunningThread(0), cafe::coreinit::internal::getCoreRunningThread(1), cafe::coreinit::internal::getCoreRunningThread(2), }; for (auto thread = cafe::coreinit::internal::getFirstActiveThread(); thread; thread = thread->activeLink.next) { auto &info = threads.emplace_back(); sampleThreadInfo(info, thread); info.coreId = -1; if (paused) { for (auto i = 0u; i < 3; ++i) { if (thread == runningThreads[i]) { info.coreId = i; copyContextRegisters(info, decaf::debug::getPausedContext(i)); info.executionTime += std::chrono::nanoseconds { cafe::coreinit::internal::getCoreThreadRunningTime(i) }; break; } } } if (info.coreId == -1) { copyThreadRegisters(info, thread); } } cafe::coreinit::internal::unlockScheduler(); return true; } bool sampleCafeVoices(std::vector<CafeVoice> &voiceInfos) { auto voices = cafe::sndcore2::internal::getAcquiredVoices(); voiceInfos.resize(voices.size()); for (auto i = 0u; i < voices.size(); ++i) { auto voice = voices[i]; auto extras = cafe::sndcore2::internal::getVoiceExtras(voice->index); auto &voiceInfo = voiceInfos[i]; voiceInfo.index = voice->index; voiceInfo.state = static_cast<CafeVoice::State>(voice->state); voiceInfo.format = static_cast<CafeVoice::Format>(voice->offsets.dataType); voiceInfo.type = static_cast<CafeVoice::VoiceType>(extras->type); voiceInfo.data = static_cast<VirtualAddress>(virt_cast<virt_addr>(voice->offsets.data)); voiceInfo.currentOffset = static_cast<int>(voice->offsets.currentOffset); voiceInfo.loopOffset = static_cast<int>(voice->offsets.loopOffset); voiceInfo.endOffset = static_cast<int>(voice->offsets.endOffset); voiceInfo.loopingEnabled = (voice->offsets.loopingEnabled != cafe::sndcore2::AXVoiceLoop::Disabled); } return true; } } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debug_api/debug_api_controller.cpp ================================================ #include "decaf_debug_api.h" #include "debug_api_controller.h" #include "debugger/debugger.h" #include "decaf_config.h" #include <array> #include <atomic> #include <condition_variable> #include <libcpu/state.h> #include <libcpu/mem.h> #include <libcpu/cpu_control.h> #include <libcpu/cpu_breakpoints.h> #include <libcpu/espresso/espresso_disassembler.h> #include <libcpu/espresso/espresso_instructionset.h> #include <mutex> namespace decaf::debug { struct Controller { bool enabled = false; //! Whether we are currently paused. std::atomic_bool isPaused; //! Used to synchronise cores across a pause. std::mutex pauseMutex; //! Used to synchronise cores across a pause. std::condition_variable pauseReleaseCond; //! The context running on each core at the time of a pause. std::array<cpu::Core *, 3> pausedContexts; //! Which core initiated the pause by sending a DbgBreak interrupt. int pauseInitiator = -1; //! Which cores are trying to pause. std::atomic<unsigned> coresPausing; //! Which cores are trying to resume. std::atomic<unsigned> coresResuming; //! Public copy of pausedContexts std::array<CpuContext, 3> contexts; bool entryPointFound = false; //! Callback to call on debug interrupt PauseCallback callback; } sController; static bool copyPauseContext(int core) { auto pauseContext = sController.pausedContexts[core]; if (!pauseContext) { return false; } auto &context = sController.contexts[core]; context.cia = pauseContext->cia; context.nia = pauseContext->nia; for (auto i = 0u; i < 32; ++i) { context.gpr[i] = pauseContext->gpr[i]; context.fpr[i] = pauseContext->fpr[i].paired0; context.ps1[i] = pauseContext->fpr[i].paired1; } context.cr = pauseContext->cr.value; context.xer = pauseContext->xer.value; context.lr = pauseContext->lr; context.ctr = pauseContext->ctr; context.fpscr = pauseContext->fpscr.value; context.pvr = pauseContext->pvr.value; context.msr = pauseContext->msr.value; for (auto i = 0u; i < 16; ++i) { context.sr[i] = pauseContext->sr[i]; } for (auto i = 0u; i < 8; ++i) { context.gqr[i] = pauseContext->gqr[i].value; } context.dar = pauseContext->dar; context.dsisr = pauseContext->dsisr; context.srr0 = pauseContext->srr0; // If we are inside a kernel call we need to adjust gpr[1] due to how we // use lazy stack frame creation auto instr = mem::read<espresso::Instruction>(pauseContext->nia - 4); auto data = espresso::decodeInstruction(instr); if (data && data->id == espresso::InstructionID::kc) { context.gpr[1] = pauseContext->systemCallStackHead; } return true; } bool ready() { return sController.entryPointFound; } void setPauseCallback(PauseCallback callback) { sController.callback = callback; } bool pause() { for (auto i = 0; i < 3; ++i) { cpu::interrupt(i, cpu::DBGBREAK_INTERRUPT); } return true; } bool resume() { if (sController.isPaused.exchange(false)) { sController.pausedContexts.fill(nullptr); sController.pauseReleaseCond.notify_all(); } return true; } bool isPaused() { return sController.isPaused.load(); } int getPauseInitiatorCoreId() { return sController.pauseInitiator; } const CpuContext * getPausedContext(int core) { if (!isPaused() || core < 0 || core > 2) { return nullptr; } return &sController.contexts[core]; } static uint32_t calculateNextInstr(const cpu::CoreRegs *state, bool stepOver) { auto instr = mem::read<espresso::Instruction>(state->nia); auto data = espresso::decodeInstruction(instr); if (data && espresso::isBranchInstruction(data->id)) { auto branchInfo = espresso::disassembleBranchInfo(data->id, instr, state->nia, state->ctr, state->cr.value, state->lr); if (branchInfo.isCall && stepOver) { // This is a call and we are stepping over... return state->nia + 4; } if (branchInfo.conditionSatisfied) { return branchInfo.target; } else { return state->nia + 4; } } else { // This is not a branch instruction return state->nia + 4; } } bool stepInto(int core) { if (core < 0 || core > 2) { return false; } auto next = calculateNextInstr(sController.pausedContexts[core], false); cpu::addBreakpoint(next, cpu::Breakpoint::SingleFire); resume(); return true; } bool stepOver(int core) { if (core < 0 || core > 2) { return false; } auto next = calculateNextInstr(sController.pausedContexts[core], true); cpu::addBreakpoint(next, cpu::Breakpoint::SingleFire); resume(); return true; } bool hasBreakpoint(VirtualAddress address) { return cpu::hasBreakpoint(address); } bool addBreakpoint(VirtualAddress address) { if (!cpu::isValidAddress(static_cast<cpu::VirtualAddress>(address))) { return false; } cpu::addBreakpoint(address, cpu::Breakpoint::MultiFire); return true; } bool removeBreakpoint(VirtualAddress address) { if (!cpu::isValidAddress(static_cast<cpu::VirtualAddress>(address))) { return false; } cpu::removeBreakpoint(address); return true; } void handleDebugBreakInterrupt() { static constexpr unsigned NoCores = 0; static constexpr unsigned AllCores = (1 << 0) | (1 << 1) | (1 << 2); std::unique_lock<std::mutex> lock { sController.pauseMutex }; auto coreId = cpu::this_core::id(); sController.pausedContexts[coreId] = cpu::this_core::state(); copyPauseContext(coreId); // Check to see if we were the last core to join on the fun auto coreBit = 1 << coreId; auto coresPausing = sController.coresPausing.fetch_or(coreBit); if (coresPausing == NoCores) { // This is the first core to hit a breakpoint sController.pauseInitiator = coreId; // Signal the rest of the cores to stop for (auto i = 0; i < 3; ++i) { cpu::interrupt(i, cpu::DBGBREAK_INTERRUPT); } } if ((coresPausing | coreBit) == AllCores) { // All cores are now paused! sController.isPaused.store(true); sController.coresPausing.store(0); sController.coresResuming.store(0); } // Call the pause callback if (sController.callback) { sController.callback(); } // Spin around the release condition while we are paused while (sController.coresPausing.load() || sController.isPaused.load()) { sController.pauseReleaseCond.wait(lock); } // Clear any additional debug interrupts that occured cpu::this_core::clearInterrupt(cpu::DBGBREAK_INTERRUPT); if ((sController.coresResuming.fetch_or(coreBit) | coreBit) == AllCores) { // This is the final core to resume, wake up the other cores sController.pauseReleaseCond.notify_all(); } else { // Wait until all cores are ready to resume while ((sController.coresResuming.load() | coreBit) != AllCores) { sController.pauseReleaseCond.wait(lock); } } } void notifyEntry(uint32_t preinit, uint32_t entry) { if (config()->debugger.break_on_entry) { if (preinit) { addBreakpoint(preinit); } if (entry) { addBreakpoint(entry); } } sController.entryPointFound = true; } } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debug_api/debug_api_controller.h ================================================ #pragma once namespace decaf::debug { void handleDebugBreakInterrupt(); void notifyEntry(uint32_t preinit, uint32_t entry); } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debug_api/debug_api_cpu.cpp ================================================ #include "decaf_debug_api.h" #include <libcpu/cpu_breakpoints.h> namespace decaf::debug { void sampleCpuBreakpoints(std::vector<CpuBreakpoint> &breakpoints) { breakpoints.clear(); if (auto list = cpu::getBreakpoints()) { for (auto &bp : *list) { breakpoints.push_back({ bp.type == bp.SingleFire ? CpuBreakpoint::SingleFire : CpuBreakpoint::MultiFire, VirtualAddress { bp.address }, bp.savedCode, }); } } } } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debug_api/debug_api_memory.cpp ================================================ #include "decaf_debug_api.h" #include <libcpu/mem.h> namespace decaf::debug { bool isValidVirtualAddress(VirtualAddress address) { return cpu::isValidAddress(cpu::VirtualAddress { address }); } size_t getMemoryPageSize() { return cpu::PageSize; } size_t readMemory(VirtualAddress address, void *dst, size_t size) { auto out = reinterpret_cast<uint8_t *>(dst); constexpr auto pageMask = ~(cpu::PageSize - 1); auto bytesRemaining = size; // Copy bytes, checking validity of each memory page as we cross it while (bytesRemaining > 0) { auto currentPage = address & pageMask; if (!cpu::isValidAddress(cpu::VirtualAddress { currentPage })) { break; } auto nextPage = currentPage + cpu::PageSize; auto readBytes = std::min<size_t>(bytesRemaining, nextPage - address); std::memcpy(out, mem::translate<uint8_t>(address), readBytes); address += static_cast<uint32_t>(readBytes); out += readBytes; bytesRemaining -= readBytes; } return size - bytesRemaining; } size_t writeMemory(VirtualAddress address, const void *src, size_t size) { auto in = reinterpret_cast<const uint8_t *>(src); constexpr auto pageMask = ~(cpu::PageSize - 1); auto bytesRemaining = size; // Copy bytes, checking validity of each memory page as we cross it while (bytesRemaining > 0) { auto currentPage = address & pageMask; if (!cpu::isValidAddress(cpu::VirtualAddress{ currentPage })) { break; } auto nextPage = currentPage + cpu::PageSize; auto readBytes = std::min<size_t>(bytesRemaining, nextPage - address); std::memcpy(mem::translate<uint8_t>(address), in, readBytes); address += static_cast<uint32_t>(readBytes); in += readBytes; bytesRemaining -= readBytes; } return size - bytesRemaining; } } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debug_api/debug_api_pm4.cpp ================================================ #include "decaf_debug_api.h" #include "cafe/libraries/gx2/gx2_internal_pm4cap.h" namespace decaf::debug { Pm4CaptureState pm4CaptureState() { switch (cafe::gx2::internal::captureState()) { case cafe::gx2::internal::CaptureState::Disabled: return Pm4CaptureState::Disabled; case cafe::gx2::internal::CaptureState::Enabled: return Pm4CaptureState::Enabled; case cafe::gx2::internal::CaptureState::WaitEndNextFrame: return Pm4CaptureState::WaitEndNextFrame; case cafe::gx2::internal::CaptureState::WaitStartNextFrame: return Pm4CaptureState::WaitStartNextFrame; default: return Pm4CaptureState::Disabled; } } bool pm4CaptureNextFrame() { if (cafe::gx2::internal::captureState() != cafe::gx2::internal::CaptureState::Disabled) { return false; } cafe::gx2::internal::captureNextFrame(); return true; } bool pm4CaptureBegin() { if (cafe::gx2::internal::captureState() != cafe::gx2::internal::CaptureState::Disabled) { return false; } cafe::gx2::internal::captureStartAtNextSwap(); return true; } bool pm4CaptureEnd() { if (cafe::gx2::internal::captureState() != cafe::gx2::internal::CaptureState::Enabled) { return false; } cafe::gx2::internal::captureStopAtNextSwap(); return true; } } // namespace decaf::debug ================================================ FILE: src/libdecaf/src/debugger/debugger.cpp ================================================ #include "debugger.h" #include "debugger_server_gdb.h" #include "decaf.h" #include "decaf_config.h" #include "decaf_debug_api.h" namespace debugger { static GdbServer sGdbServer { }; void initialise() { if (decaf::config()->debugger.gdb_stub) { sGdbServer.start(decaf::config()->debugger.gdb_stub_port); } } void shutdown() { // Force resume any paused cores. ::decaf::debug::resume(); } void draw(unsigned width, unsigned height) { sGdbServer.process(); } } // namespace debugger ================================================ FILE: src/libdecaf/src/debugger/debugger.h ================================================ #pragma once #include <cstdint> #include <string> namespace debugger { void initialise(); void shutdown(); void draw(unsigned width, unsigned height); } // namespace debugger ================================================ FILE: src/libdecaf/src/debugger/debugger_server.h ================================================ #pragma once namespace debugger { class DebuggerServer { public: virtual ~DebuggerServer() = default; virtual bool start(int port) = 0; virtual void process() = 0; }; } // namespace debugger ================================================ FILE: src/libdecaf/src/debugger/debugger_server_gdb.cpp ================================================ #include "debugger_server_gdb.h" #include "decaf_config.h" #include "decaf_debug_api.h" #include "decaf_log.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/coreinit/coreinit_thread.h" #include <algorithm> #include <common/platform_socket.h> #include <common/strutils.h> #include <common/log.h> #include <fmt/core.h> #include <iterator> #include <libcpu/cpu_formatters.h> #include <libcpu/mem.h> #include <numeric> namespace debugger { #include "debugger_server_gdb_xml.inl" enum GdbCommand { Ack = '+', Nack = '-', Break = 0x03, Start = '$', End = '#', Query = 'q', EnableExtendedMode = '!', GetHaltReason = '?', ReadRegister = 'p', ReadGeneralRegisters = 'g', ReadMemory = 'm', AddBreakpoint = 'Z', RemoveBreakpoint = 'z', VCommand = 'v', SetActiveThread = 'H', }; enum RegisterID { PC = 64, MSR = 65, CR = 66, LR = 67, CTR = 68, XER = 69, }; enum BreakpointType { Execute = 1, }; bool GdbServer::start(int port) { if (!mLog) { mLog = decaf::makeLogger("gdb"); } mListenSocket = socket(PF_INET, SOCK_STREAM, 0); if (mListenSocket < 0) { mLog->error("Failed to create socket"); return false; } // Set socket to SO_REUSEADDR so it can always bind on the same port auto sockOpt = 1; if (setsockopt(mListenSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&sockOpt), sizeof(sockOpt)) < 0) { mLog->error("Failed to set SO_REUSEADDR on socket"); closeServer(); return false; } auto bindAddress = sockaddr_in { 0 }; bindAddress.sin_family = AF_INET; bindAddress.sin_port = htons(static_cast<uint16_t>(port)); bindAddress.sin_addr.s_addr = INADDR_ANY; if (bind(mListenSocket, reinterpret_cast<const sockaddr*>(&bindAddress), sizeof(sockaddr_in)) < 0) { mLog->error("Failed to bind on socket"); closeServer(); return false; } if (listen(mListenSocket, 1) < 0) { mLog->error("Failed to listen on socket"); closeServer(); return false; } platform::socketSetBlocking(mListenSocket, false); return true; } void GdbServer::closeServer() { if (mListenSocket != InvalidSocket) { platform::socketClose(mListenSocket); mListenSocket = InvalidSocket; } } void GdbServer::closeClient() { if (mClientSocket != InvalidSocket) { platform::socketClose(mClientSocket); mClientSocket = InvalidSocket; } } void GdbServer::handleBreak() { mWasPaused = false; decaf::debug::pause(); } static std::string encodeXml(const std::string &src) { auto result = std::string { }; result.reserve(src.size()); for (auto i = 0u; i < src.size(); ++i) { auto c = src[i]; if (c == '#' || c == '$' || c == '*' || c == '}') { result.push_back('}'); result.push_back(c ^ 0x20); } else if (c == '\n' || c == '\r') { continue; } else { result.push_back(c); } } return result; } void GdbServer::handleQuery(const std::string &command) { if (begins_with(command, "qSupported")) { auto features = std::string { }; features += "PacketSize=4096"; features += ";qXfer:features:read+"; features += ";qXfer:threads:read+"; // TODO: features += ";qXfer:libraries:read+"; // TODO: features += ";qXfer:memory-map:read+"; // TODO: features += ";QStartNoAckMode+"; // TODO: features += ";QThreadEvents+"; sendCommand(features); } else if (begins_with(command, "qfThreadInfo")) { fmt::memory_buffer reply; cafe::coreinit::internal::lockScheduler(); auto firstThread = cafe::coreinit::internal::getFirstActiveThread(); if (firstThread) { reply.push_back('m'); } else { reply.push_back('l'); } for (auto thread = firstThread; thread; thread = thread->activeLink.next) { if (thread != firstThread) { reply.push_back(','); } fmt::format_to(std::back_inserter(reply), "{:04X}", thread->id.value()); } cafe::coreinit::internal::unlockScheduler(); sendCommand(to_string(reply)); } else if (begins_with(command, "qAttached")) { sendCommand("1"); } else if (begins_with(command, "qsThreadInfo")) { sendCommand("l"); } else if (begins_with(command, "qC")) { if (mCurrentThread.id != -1) { auto reply = fmt::format("QC{:04X}", mCurrentThread.id); sendCommand(reply); } else { auto initiator = decaf::debug::getPauseInitiatorCoreId(); if (decaf::debug::sampleCafeRunningThread(initiator, mCurrentThread)) { auto reply = fmt::format("QC{:04X}", mCurrentThread.id); sendCommand(reply); } else { sendCommand(""); } } } else if (begins_with(command, "qXfer:features:read:")) { std::vector<std::string> split; split_string(command, ':', split); auto xmlName = split[3]; auto xml = std::string { }; if (split[3] == "target.xml") { xml = encodeXml(sGdbTargetXML); } else if (split[3] == "power-core.xml") { xml = encodeXml(sGdbPowerCoreXml); } auto args = split[4]; split.clear(); split_string(args, ',', split); auto offset = std::stoul(split[0], 0, 16); auto size = std::stoul(split[1], 0, 16); auto reply = std::string { }; if (offset + size < xml.size()) { reply += "m"; reply += std::string { xml.data() + offset, size }; } else { reply += "l"; reply += std::string { xml.data() + offset, xml.size() - offset }; } sendCommand(reply); } else if (begins_with(command, "qXfer:threads:read:")) { fmt::memory_buffer reply; fmt::format_to(std::back_inserter(reply), "l<?xml version=\"1.0\"?>"); fmt::format_to(std::back_inserter(reply), "<threads>"); cafe::coreinit::internal::lockScheduler(); auto firstThread = cafe::coreinit::internal::getFirstActiveThread(); for (auto thread = firstThread; thread; thread = thread->activeLink.next) { fmt::format_to(std::back_inserter(reply), "<thread id=\"{}\" core=\"0\"", thread->id); if (thread->name) { fmt::format_to(std::back_inserter(reply), " name=\"{}\"", encodeXml(thread->name.get())); } fmt::format_to(std::back_inserter(reply), "></thread>"); } fmt::format_to(std::back_inserter(reply), "</threads>"); cafe::coreinit::internal::unlockScheduler(); sendCommand(std::string_view { reply.data(), reply.size() }); } else if (begins_with(command, "qTStatus")) { // Trace not supported sendCommand(""); } else { mLog->warn("Unknown query command {}", command); sendCommand(""); } } void GdbServer::handleEnableExtendedMode(const std::string &command) { sendCommand(""); } void GdbServer::handleGetHaltReason(const std::string &command) { sendCommand("T05"); } void GdbServer::handleReadRegister(const std::string &command) { auto id = std::stoul(command.substr(1), 0, 16); auto value = uint32_t { 0 }; if (mCurrentThread.handle) { if (id < 32) { value = mCurrentThread.gpr[id]; } else { switch (id) { case RegisterID::PC: value = mCurrentThread.nia; break; case RegisterID::MSR: value = mCurrentThread.msr; break; case RegisterID::CR: value = mCurrentThread.cr; break; case RegisterID::LR: value = mCurrentThread.lr; break; case RegisterID::CTR: value = mCurrentThread.ctr; break; case RegisterID::XER: value = mCurrentThread.xer; break; } } } sendCommand(fmt::format("{:08X}", value)); } void GdbServer::handleReadGeneralRegisters(const std::string &command) { fmt::memory_buffer reply; for (auto i = 0; i < 32; ++i) { auto value = uint32_t { 0 }; if (mCurrentThread.handle) { value = mCurrentThread.gpr[i]; } fmt::format_to(std::back_inserter(reply), "{:08X}", value); } sendCommand(to_string(reply)); } void GdbServer::handleReadMemory(const std::string &command) { fmt::memory_buffer reply; std::vector<std::string> split; split_string(command.data() + 1, ',', split); auto address = std::stoul(split[0], 0, 16); auto size = std::stoul(split[1], 0, 16); for (auto i = 0u; i < size; ++i) { auto value = uint8_t { 0 }; if (cpu::isValidAddress(cpu::VirtualAddress { static_cast<uint32_t>(address + i) })) { value = mem::read<uint8_t>(address + i); } fmt::format_to(std::back_inserter(reply), "{:02X}", value); } sendCommand(to_string(reply)); } void GdbServer::handleAddBreakpoint(const std::string &command) { std::vector<std::string> split; split_string(command.data() + 1, ',', split); auto type = std::stoul(split[0], 0, 16); auto address = std::stoul(split[1], 0, 16); // auto length = std::stoul(split[2], 0, 16); if (type != BreakpointType::Execute) { sendCommand("E02"); } else { decaf::debug::addBreakpoint(address); sendCommand("OK"); } } void GdbServer::handleRemoveBreakpoint(const std::string &command) { std::vector<std::string> split; split_string(command.data() + 1, ',', split); auto type = std::stoul(split[0], 0, 16); auto address = std::stoul(split[1], 0, 16); // auto length = std::stoul(split[2], 0, 16); if (type != BreakpointType::Execute) { sendCommand("E02"); } else { decaf::debug::removeBreakpoint(address); sendCommand("OK"); } } void GdbServer::handleSetActiveThread(const std::string &command) { // auto type = command[1]; auto id = std::stoi(command.data() + 2, 0, 16); // Find thread by id std::vector<decaf::debug::CafeThread> threads; mCurrentThread.handle = 0; mCurrentThread.id = -1; if (decaf::debug::sampleCafeThreads(threads)) { for (auto &thread : threads) { if (thread.id == id) { mCurrentThread = thread; break; } } } if (id == -1 || id == 0) { sendCommand("OK"); } else if (mCurrentThread.id == id) { sendCommand("OK"); } else { sendCommand("E22"); } } void GdbServer::handleVContQuery(const std::vector<std::string> &command) { sendCommand("vCont;c;C;s;S"); } void GdbServer::handleVCont(const std::vector<std::string> &command) { std::vector<std::string> split; split_string(command[1], ':', split); if (split[0] == "c") { mWasPaused = false; decaf::debug::resume(); } else if (split[0] == "s") { auto threadId = mCurrentThread.id; if (split.size() > 1) { threadId = std::stoi(split[1], 0, 16); } for (auto i = 0; i < 3; ++i) { auto thread = decaf::debug::CafeThread { }; if (decaf::debug::sampleCafeRunningThread(i, thread)) { if (thread.id == threadId) { decaf::debug::stepInto(i); return; } } } } } void GdbServer::handleCommand(const std::string &command) { switch (command[0]) { case GdbCommand::Query: handleQuery(command); break; case GdbCommand::EnableExtendedMode: handleEnableExtendedMode(command); break; case GdbCommand::GetHaltReason: handleGetHaltReason(command); break; case GdbCommand::ReadRegister: handleReadRegister(command); break; case GdbCommand::ReadGeneralRegisters: handleReadGeneralRegisters(command); break; case GdbCommand::ReadMemory: handleReadMemory(command); break; case GdbCommand::AddBreakpoint: handleAddBreakpoint(command); break; case GdbCommand::RemoveBreakpoint: handleRemoveBreakpoint(command); break; case GdbCommand::VCommand: { std::vector<std::string> vCommand; split_string(command, ';', vCommand); if (vCommand[0] == "vCont") { handleVCont(vCommand); } else if (vCommand[0] == "vCont?") { handleVContQuery(vCommand); } else if (vCommand[0] == "vMustReplyEmpty") { sendCommand(""); } else { mLog->warn("Unknown vCommand {}", command); sendCommand(""); } break; } case GdbCommand::SetActiveThread: handleSetActiveThread(command); break; default: mLog->warn("Unknown command {}", command); sendCommand(""); } } template<class InputIt> static uint8_t calculateChecksum(InputIt first, InputIt last) { return static_cast<uint8_t>(std::accumulate(first, last, 0, std::plus<int>())); } void GdbServer::sendAck() { auto ack = char { GdbCommand::Ack }; auto result = send(mClientSocket, &ack, 1, 0); if (result < 0) { mLog->error("Error sending ack"); } } void GdbServer::sendNack() { auto ack = char { GdbCommand::Nack }; auto result = send(mClientSocket, &ack, 1, 0); if (result < 0) { mLog->error("Error sending nack"); } } void GdbServer::sendCommand(std::string_view command) { auto packet = std::string { }; packet.push_back(GdbCommand::Start); packet.append(command); packet.push_back(GdbCommand::End); auto checksum = calculateChecksum(command.begin(), command.end()); packet.append(fmt::format("{:02X}", checksum)); auto bytesSent = 0; while (bytesSent < packet.size()) { auto result = send(mClientSocket, packet.data() + bytesSent, static_cast<int>(packet.size() - bytesSent), 0); if (result < 0) { mLog->error("Error sending command"); break; } bytesSent += result; } mLastCommand = command; } void GdbServer::process() { if (mListenSocket == InvalidSocket && mClientSocket == InvalidSocket) { return; } fd_set readfds; auto nfds = 0; auto tv = timeval { 0, 0 }; FD_ZERO(&readfds); if (mListenSocket != InvalidSocket) { FD_SET(mListenSocket, &readfds); nfds = std::max(nfds, static_cast<int>(mListenSocket)); } if (mClientSocket != InvalidSocket) { FD_SET(mClientSocket, &readfds); nfds = std::max(nfds, static_cast<int>(mClientSocket)); } select(nfds + 1, &readfds, NULL, NULL, &tv); if (mListenSocket != InvalidSocket && FD_ISSET(mListenSocket, &readfds)) { auto clientAddress = sockaddr_in { 0 }; auto clientAddressLen = socklen_t { sizeof(sockaddr_in) }; auto clientSocket = accept(mListenSocket, reinterpret_cast<sockaddr*>(&clientAddress), &clientAddressLen); if (clientSocket < 0) { mLog->error("Failed to accept on socket"); } else if (mClientSocket != InvalidSocket) { mLog->error("Rejecting connection because we already have a client connected."); platform::socketClose(clientSocket); } else { mClientSocket = clientSocket; } } else if (mClientSocket != InvalidSocket && FD_ISSET(mClientSocket, &readfds)) { while (mClientSocket != InvalidSocket) { char byte = 0; auto result = recv(mClientSocket, &byte, 1, 0); if (result < 0) { if (platform::socketWouldBlock(result)) { break; } else { mLog->debug("Client disconnected, recv returned {}", result); closeClient(); } } else if (result == 0) { mLog->debug("Client disconnected gracefully"); closeClient(); } else { if (mReadState == ReadState::ReadStart) { if (byte == GdbCommand::Ack) { continue; } else if (byte == GdbCommand::Nack) { sendCommand(mLastCommand); continue; } else if (byte == GdbCommand::Break) { handleBreak(); } else if (byte != GdbCommand::Start) { mLog->error("Unexpected start of packet {}", byte); closeClient(); } else { mReadState = ReadState::ReadCommand; } } else if (mReadState == ReadState::ReadCommand) { if (byte == GdbCommand::End) { mReadState = ReadState::ReadChecksum; } else { mReadBuffer.push_back(byte); } } else if (mReadState == ReadState::ReadChecksum) { mChecksumBuffer.push_back(byte); if (mChecksumBuffer.size() == 2) { auto readChecksum = std::stoi(mChecksumBuffer, nullptr, 16); auto calculatedChecksum = calculateChecksum(mReadBuffer.begin(), mReadBuffer.end()); if (readChecksum != calculatedChecksum) { mLog->error("Unexpected command checksum {} != {}", readChecksum, calculatedChecksum); sendNack(); } else { sendAck(); handleCommand(mReadBuffer); } mReadState = ReadState::ReadStart; mReadBuffer.clear(); mChecksumBuffer.clear(); } } } } } // Check for a transition in paused state. mPaused = decaf::debug::isPaused(); if (mPaused) { auto initiator = decaf::debug::getPauseInitiatorCoreId(); auto context = decaf::debug::getPausedContext(initiator); if (context->nia != mPausedNia) { mWasPaused = false; } if (!mWasPaused) { if (mClientSocket != InvalidSocket) { sendCommand("T05"); } mPausedNia = context->nia; mWasPaused = true; } } } } // namespace debugger ================================================ FILE: src/libdecaf/src/debugger/debugger_server_gdb.h ================================================ #pragma once #include "debugger_server.h" #include "decaf_debug_api.h" #include <common/platform.h> #include <common/platform_socket.h> #include <string> #include <string_view> #include <vector> #include <spdlog/spdlog.h> namespace debugger { class GdbServer : public DebuggerServer { static constexpr platform::Socket InvalidSocket = static_cast<platform::Socket>(-1); enum class ReadState { ReadStart, ReadCommand, ReadChecksum }; public: virtual ~GdbServer() = default; virtual bool start(int port) override; virtual void process() override; void closeServer(); void closeClient(); void sendAck(); void sendNack(); void sendCommand(std::string_view command); void handleCommand(const std::string &command); void handleBreak(); void handleQuery(const std::string &command); void handleEnableExtendedMode(const std::string &command); void handleGetHaltReason(const std::string &command); void handleReadRegister(const std::string &command); void handleReadGeneralRegisters(const std::string &command); void handleReadMemory(const std::string &command); void handleAddBreakpoint(const std::string &command); void handleRemoveBreakpoint(const std::string &command); void handleVCont(const std::vector<std::string> &command); void handleVContQuery(const std::vector<std::string> &command); void handleSetActiveThread(const std::string &command); private: std::shared_ptr<spdlog::logger> mLog; bool mPaused = false; bool mWasPaused = false; uint32_t mPausedNia = 0; decaf::debug::CafeThread mCurrentThread = { }; platform::Socket mListenSocket = InvalidSocket; platform::Socket mClientSocket = InvalidSocket; ReadState mReadState = ReadState::ReadStart; std::string mChecksumBuffer; std::string mReadBuffer; std::string mLastCommand; }; } // namespace debugger ================================================ FILE: src/libdecaf/src/debugger/debugger_server_gdb_xml.inl ================================================ static constexpr const char * sGdbTargetXML = R"(<?xml version="1.0"?> <!DOCTYPE target SYSTEM "gdb-target.dtd"> <target> <architecture>powerpc:common</architecture> <xi:include href="power-core.xml"/> </target> )"; static constexpr const char * sGdbPowerCoreXml = R"(<?xml version="1.0"?> <!-- Copyright (C) 2007, 2008 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. --> <!DOCTYPE feature SYSTEM "gdb-target.dtd"> <feature name="org.gnu.gdb.power.core"> <reg name="r0" bitsize="32" type="uint32"/> <reg name="r1" bitsize="32" type="uint32"/> <reg name="r2" bitsize="32" type="uint32"/> <reg name="r3" bitsize="32" type="uint32"/> <reg name="r4" bitsize="32" type="uint32"/> <reg name="r5" bitsize="32" type="uint32"/> <reg name="r6" bitsize="32" type="uint32"/> <reg name="r7" bitsize="32" type="uint32"/> <reg name="r8" bitsize="32" type="uint32"/> <reg name="r9" bitsize="32" type="uint32"/> <reg name="r10" bitsize="32" type="uint32"/> <reg name="r11" bitsize="32" type="uint32"/> <reg name="r12" bitsize="32" type="uint32"/> <reg name="r13" bitsize="32" type="uint32"/> <reg name="r14" bitsize="32" type="uint32"/> <reg name="r15" bitsize="32" type="uint32"/> <reg name="r16" bitsize="32" type="uint32"/> <reg name="r17" bitsize="32" type="uint32"/> <reg name="r18" bitsize="32" type="uint32"/> <reg name="r19" bitsize="32" type="uint32"/> <reg name="r20" bitsize="32" type="uint32"/> <reg name="r21" bitsize="32" type="uint32"/> <reg name="r22" bitsize="32" type="uint32"/> <reg name="r23" bitsize="32" type="uint32"/> <reg name="r24" bitsize="32" type="uint32"/> <reg name="r25" bitsize="32" type="uint32"/> <reg name="r26" bitsize="32" type="uint32"/> <reg name="r27" bitsize="32" type="uint32"/> <reg name="r28" bitsize="32" type="uint32"/> <reg name="r29" bitsize="32" type="uint32"/> <reg name="r30" bitsize="32" type="uint32"/> <reg name="r31" bitsize="32" type="uint32"/> <reg name="pc" bitsize="32" type="code_ptr" regnum="64"/> <reg name="msr" bitsize="32" type="uint32"/> <reg name="cr" bitsize="32" type="uint32"/> <reg name="lr" bitsize="32" type="code_ptr"/> <reg name="ctr" bitsize="32" type="uint32"/> <reg name="xer" bitsize="32" type="uint32"/> </feature> )"; ================================================ FILE: src/libdecaf/src/decaf.cpp ================================================ #include "decaf.h" #include "decaf_config.h" #include "decaf_graphics.h" #include "decaf_input.h" #include "decaf_slc.h" #include "decaf_sound.h" #include "cafe/kernel/cafe_kernel.h" #include "cafe/kernel/cafe_kernel_process.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/coreinit/coreinit_thread.h" #include "cafe/libraries/swkbd/swkbd_keyboard.h" #include "debugger/debugger.h" #include "vfs/vfs_host_device.h" #include "vfs/vfs_virtual_device.h" #include "input/input.h" #include "ios/ios.h" #include <chrono> #include <common/decaf_assert.h> #include <common/log.h> #include <common/platform.h> #include <common/platform_dir.h> #include <condition_variable> #include <curl/curl.h> #include <filesystem> #include <fmt/core.h> #include <libcpu/cpu.h> #include <libcpu/mem.h> #include <mutex> #ifdef PLATFORM_WINDOWS #include <WinSock2.h> #endif namespace decaf { std::string makeConfigPath(const std::string &filename) { auto configPath = std::filesystem::path { platform::getConfigDirectory() }; return (configPath / "decaf" / filename).string(); } bool createConfigDirectory() { return platform::createParentDirectories(makeConfigPath(".")); } std::string getResourcePath(const std::string &filename) { #ifdef PLATFORM_WINDOWS return decaf::config()->system.resources_path + "/" + filename; #else std::string userPath = decaf::config()->system.resources_path + "/" + filename; std::string systemPath = std::string(DECAF_INSTALL_RESOURCESDIR) + "/" + filename; if (platform::fileExists(userPath)) { return userPath; } if (platform::fileExists(systemPath)) { return systemPath; } decaf_abort(fmt::format("Failed to find resource {}", filename)); #endif } bool initialise(const std::string &gamePath) { if (!getInputDriver()) { gLog->error("No input driver set"); return false; } if (!getGraphicsDriver()) { gLog->error("No graphics driver set"); return false; } if (auto result = curl_global_init(CURL_GLOBAL_ALL); result != CURLE_OK) { gLog->error("curl_global_init returned {}", result); } // Initialise cpu (because this initialises memory) ::cpu::initialise(); // Setup debugger debugger::initialise(); // Setup filesystem auto filesystem = std::make_shared<vfs::VirtualDevice>("/"); auto user = vfs::User { 0, 0 }; // Find a valid application to run auto path = std::filesystem::path { gamePath }; auto volPath = std::filesystem::path { }; auto rpxPath = std::filesystem::path { }; if (std::filesystem::is_directory(path)) { if (std::filesystem::is_regular_file(path / "code" / "cos.xml")) { // Found path/code/cos.xml volPath = path; } else if (std::filesystem::is_regular_file(path / "data" / "code" / "cos.xml")) { // Found path/data/code/cos.xml volPath = path / "data"; } } else if (std::filesystem::is_regular_file(path)) { auto parent1 = path.parent_path(); auto parent2 = parent1.parent_path(); if (std::filesystem::is_regular_file(parent2 / "code" / "cos.xml")) { // Found file/../code/cos.xml volPath = parent2; } else if (path.extension().compare(".rpx") == 0) { // Found file.rpx rpxPath = path; } } // Initialise /vol filesystem->makeFolder(user, "/vol"); filesystem->makeFolder(user, "/vol/sys"); filesystem->makeFolder(user, "/vol/temp"); if (!volPath.empty()) { filesystem->mountDevice(user, "/vol/code", std::make_shared<vfs::HostDevice>(volPath / "code")); filesystem->mountDevice(user, "/vol/content", std::make_shared<vfs::HostDevice>(volPath / "content")); filesystem->mountDevice(user, "/vol/meta", std::make_shared<vfs::HostDevice>(volPath / "meta")); } else if (!rpxPath.empty()) { filesystem->mountDevice(user, "/vol/code", std::make_shared<vfs::HostDevice>(rpxPath.parent_path())); if (!decaf::config()->system.content_path.empty()) { filesystem->mountDevice(user, "/vol/content", std::make_shared<vfs::HostDevice>(decaf::config()->system.content_path)); } cafe::kernel::setExecutableFilename(rpxPath.filename().string()); } else { gLog->error("Could not find valid application at {}", path.string()); return false; } // Ensure paths exist if (!decaf::config()->system.hfio_path.empty()) { auto ec = std::error_code { }; std::filesystem::create_directories(decaf::config()->system.hfio_path, ec); } if (!decaf::config()->system.mlc_path.empty()) { auto ec = std::error_code { }; std::filesystem::create_directories(decaf::config()->system.mlc_path, ec); } if (!decaf::config()->system.slc_path.empty()) { auto ec = std::error_code { }; std::filesystem::create_directories(decaf::config()->system.slc_path, ec); } if (!decaf::config()->system.sdcard_path.empty()) { auto ec = std::error_code { }; std::filesystem::create_directories(decaf::config()->system.sdcard_path, ec); } // Add device folder filesystem->makeFolder(user, "/dev"); // Mount devices filesystem->mountDevice(user, "/dev/mlc01", std::make_shared<vfs::HostDevice>(decaf::config()->system.mlc_path)); filesystem->mountDevice(user, "/dev/slc01", std::make_shared<vfs::HostDevice>(decaf::config()->system.slc_path)); filesystem->mountDevice(user, "/dev/ramdisk01", std::make_shared<vfs::VirtualDevice>()); if (!decaf::config()->system.hfio_path.empty()) { filesystem->mountDevice(user, "/dev/hfio01", std::make_shared<vfs::HostDevice>(decaf::config()->system.hfio_path)); } // Initialise file system with necessary files internal::initialiseSlc(decaf::config()->system.slc_path); // Setup ios ios::setFileSystem(std::move(filesystem)); return true; } void start() { // Start ios ios::start(); } bool stopping() { return cafe::kernel::stopping(); } int waitForExit() { // Wait for IOS to finish ios::join(); // Wait for PPC to finish cafe::kernel::join(); // Make sure we clean up decaf::shutdown(); return cafe::kernel::getProcessExitCode(cafe::kernel::RamPartitionId::MainApplication); } void shutdown() { // Shut down debugger debugger::shutdown(); // Stop IOS ios::stop(); // Stop PPC cafe::kernel::stop(); // Wait for IOS and PPC to stop ios::join(); cafe::kernel::join(); // Stop graphics driver auto graphicsDriver = getGraphicsDriver(); if (graphicsDriver) { graphicsDriver->stop(); } setGraphicsDriver(nullptr); // Stop sound driver auto soundDriver = getSoundDriver(); if (soundDriver) { soundDriver->stop(); } setSoundDriver(nullptr); } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_configstorage.cpp ================================================ #include "decaf_config.h" #include "decaf_configstorage.h" #include <common/configstorage.h> namespace decaf { static ConfigStorage<Settings> sSettings; void setConfig(const Settings &settings) { sSettings.set(std::make_shared<Settings>(settings)); } std::shared_ptr<const Settings> config() { return sSettings.get(); } void registerConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener) { sSettings.addListener(listener); } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_configstorage.h ================================================ #pragma once #include "decaf_config.h" #include <common/configstorage.h> namespace decaf { void registerConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener); } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_erreula.cpp ================================================ #include "decaf_erreula.h" #include "cafe/libraries/erreula/erreula_errorviewer.h" namespace decaf { static ErrEulaDriver *sErrEulaDriver = nullptr; void setErrEulaDriver(ErrEulaDriver *driver) { sErrEulaDriver = driver; } ErrEulaDriver * errEulaDriver() { return sErrEulaDriver; } void ErrEulaDriver::buttonClicked() { cafe::nn_erreula::internal::buttonClicked(); } void ErrEulaDriver::button1Clicked() { cafe::nn_erreula::internal::button1Clicked(); } void ErrEulaDriver::button2Clicked() { cafe::nn_erreula::internal::button2Clicked(); } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_eventlistener.cpp ================================================ #include "decaf_eventlistener.h" #include <algorithm> #include <mutex> #include <vector> namespace decaf { struct { std::mutex mutex; std::vector<EventListener *> listeners; } sEventListenerData; void addEventListener(EventListener *listener) { std::unique_lock<std::mutex> lock { sEventListenerData.mutex }; sEventListenerData.listeners.push_back(listener); } void removeEventListener(EventListener *listener) { std::unique_lock<std::mutex> lock { sEventListenerData.mutex }; sEventListenerData.listeners.erase( std::remove(sEventListenerData.listeners.begin(), sEventListenerData.listeners.end(), listener)); } namespace event { void onGameLoaded(const decaf::GameInfo &info) { sEventListenerData.mutex.lock(); auto listeners = sEventListenerData.listeners; sEventListenerData.mutex.unlock(); for (auto listener : listeners) { listener->onGameLoaded(info); } } } // namespace event } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_events.h ================================================ #pragma once #include "decaf_game.h" namespace decaf::event { void onGameLoaded(const decaf::GameInfo &info); } // namespace decaf::event ================================================ FILE: src/libdecaf/src/decaf_graphics.cpp ================================================ #include "decaf_graphics.h" #include <common/decaf_assert.h> namespace decaf { static gpu::GraphicsDriver * sGraphicsDriver = nullptr; void setGraphicsDriver(gpu::GraphicsDriver *driver) { sGraphicsDriver = driver; } gpu::GraphicsDriver * getGraphicsDriver() { return sGraphicsDriver; } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_input.cpp ================================================ #include "decaf_input.h" namespace decaf { InputDriver * sInputDriver = nullptr; void setInputDriver(InputDriver *driver) { sInputDriver = driver; } InputDriver * getInputDriver() { return sInputDriver; } } // namespace input ================================================ FILE: src/libdecaf/src/decaf_log.cpp ================================================ #include "decaf_config.h" #include "decaf_configstorage.h" #include "decaf_log.h" #include "cafe/libraries/coreinit/coreinit_scheduler.h" #include "cafe/libraries/coreinit/coreinit_thread.h" #include <common/log.h> #include <common/strutils.h> #include <decaf_buildinfo.h> #include <filesystem> #include <iterator> #include <libcpu/cpu_control.h> #include <libcpu/cpu_formatters.h> #include <memory> #include <spdlog/async.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/sinks/stdout_sinks.h> namespace decaf { // TODO: Move initialiseLogging here and do shit!!! static std::vector<spdlog::sink_ptr> sLogSinks; class GlobalLogFormatter : public spdlog::formatter { public: virtual void format(const spdlog::details::log_msg &msg, spdlog::memory_buf_t &dest) override { auto tm_time = spdlog::details::os::localtime(spdlog::log_clock::to_time_t(msg.time)); auto duration = msg.time.time_since_epoch(); auto micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count() % 1000000; fmt::format_to(std::back_inserter(dest), "[{:02}:{:02}:{:02}.{:06} {}:", tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, micros, spdlog::level::to_string_view(msg.level)); auto core = cpu::this_core::state(); if (core) { auto thread = cafe::coreinit::internal::getCurrentThread(); if (thread) { fmt::format_to(std::back_inserter(dest), "p{:01X} t{:02X}", core->id, thread->id); } else { fmt::format_to(std::back_inserter(dest), "p{:01X} t{:02X}", core->id, 0xFF); } } else if (msg.logger_name.size()) { fmt::format_to(std::back_inserter(dest), "{}", msg.logger_name); } else { fmt::format_to(std::back_inserter(dest), "h{}", msg.thread_id); } fmt::format_to(std::back_inserter(dest), "] {}{}", std::string_view { msg.payload.data(), msg.payload.size() }, spdlog::details::os::default_eol); } virtual std::unique_ptr<formatter> clone() const override { return std::make_unique<GlobalLogFormatter>(); } }; static void initialiseLogSinks(std::string_view filename) { if (decaf::config()->log.to_stdout) { sLogSinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>()); } if (decaf::config()->log.to_file) { auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); auto time = std::localtime(&now); auto logFilename = fmt::format("{}_{}-{:02}-{:02}_{:02}-{:02}-{:02}.txt", filename, time->tm_year + 1900, time->tm_mon, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); auto path = std::filesystem::path { decaf::config()->log.directory } / logFilename; sLogSinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.string())); } if (decaf::config()->log.async) { spdlog::init_thread_pool(1024, 1); } static std::once_flag sRegisteredConfigChangeListener; std::call_once(sRegisteredConfigChangeListener, []() { decaf::registerConfigChangeListener( [](const decaf::Settings &settings) { auto defaultLevel = spdlog::level::from_str(settings.log.level); spdlog::apply_all([&](std::shared_ptr<spdlog::logger> logger) { auto level = defaultLevel; for (auto &item : settings.log.levels) { if (iequals(item.first, logger->name())) { level = spdlog::level::from_str(item.second); } } logger->set_level(level); }); }); }); } static void initialiseGlobalLogger() { auto logLevel = spdlog::level::from_str(decaf::config()->log.level); auto logger = std::shared_ptr<spdlog::logger> { }; if (decaf::config()->log.async) { logger = std::make_shared<spdlog::async_logger>("decaf", std::begin(sLogSinks), std::end(sLogSinks), spdlog::thread_pool()); } else { logger = std::make_shared<spdlog::logger>("decaf", std::begin(sLogSinks), std::end(sLogSinks)); logger->flush_on(spdlog::level::trace); } logger->set_level(logLevel); logger->set_formatter(std::make_unique<GlobalLogFormatter>()); spdlog::register_logger(logger); gLog = logger; } void initialiseLogging(std::string_view filename) { initialiseLogSinks(filename); initialiseGlobalLogger(); gLog->info("Built on {} from {}", BUILD_DATE, GIT_DESC); } std::shared_ptr<spdlog::logger> makeLogger(std::string name, std::vector<spdlog::sink_ptr> sinks) { auto config = decaf::config(); auto logger = std::shared_ptr<spdlog::logger> { }; sinks.insert(sinks.end(), sLogSinks.begin(), sLogSinks.end()); if (config->log.async) { logger = std::make_shared<spdlog::async_logger>(name, std::begin(sinks), std::end(sinks), spdlog::thread_pool()); } else { logger = std::make_shared<spdlog::logger>(name, std::begin(sinks), std::end(sinks)); logger->flush_on(spdlog::level::trace); } auto level = spdlog::level::from_str(config->log.level); for (auto &item : config->log.levels) { if (iequals(item.first, name)) { level = spdlog::level::from_str(item.second); } } logger->set_level(level); logger->set_formatter(std::make_unique<GlobalLogFormatter>()); spdlog::register_logger(logger); return logger; } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_nullinputdriver.cpp ================================================ #include "decaf_nullinputdriver.h" namespace decaf { using namespace input; void NullInputDriver::sampleVpadController(int channel, input::vpad::Status &status) { status.connected = false; } void NullInputDriver::sampleWpadController(int channel, input::wpad::Status &status) { status.type = input::wpad::BaseControllerType::Disconnected; } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_slc.cpp ================================================ #include "decaf_config.h" #include "decaf_slc.h" #include "ios/auxil/ios_auxil_enum.h" #include <common/decaf_assert.h> #include <common/platform_dir.h> #include <common/strutils.h> #include <filesystem> #include <fmt/core.h> #include <pugixml.hpp> #include <vector> using ios::auxil::UCDataType; namespace decaf::internal { struct InitialiseUCSysConfig { const char *name; UCDataType dataType; const char *defaultValue = nullptr; uint32_t length = 0; uint32_t access = 777; }; static InitialiseUCSysConfig DefaultCafe[] = { { "cafe", UCDataType::Complex }, { "cafe/version", UCDataType::UnsignedShort, "5" }, { "cafe/language", UCDataType::UnsignedInt, "1" }, { "cafe/cntry_reg", UCDataType::UnsignedInt, "110" }, { "cafe/eula_agree", UCDataType::UnsignedByte, "0" }, { "cafe/eula_version", UCDataType::UnsignedInt, "100" }, { "cafe/initial_launch", UCDataType::UnsignedByte, "2" }, { "cafe/eco", UCDataType::UnsignedByte, "0" }, { "cafe/fast_boot", UCDataType::UnsignedByte, "0" }, }; constexpr InitialiseUCSysConfig DefaultCaffeine[] = { { "caffeine", UCDataType::Complex }, { "caffeine/version", UCDataType::UnsignedShort, "3" }, { "caffeine/enable", UCDataType::UnsignedByte, "1" }, { "caffeine/ad_enable", UCDataType::UnsignedByte, "1" }, { "caffeine/push_enable", UCDataType::UnsignedByte, "1" }, { "caffeine/push_time_slot", UCDataType::UnsignedInt, "2132803072" }, { "caffeine/push_interval", UCDataType::UnsignedShort, "420" }, { "caffeine/drcled_enable", UCDataType::UnsignedByte, "1" }, { "caffeine/push_capabilty", UCDataType::UnsignedShort, "65535" }, { "caffeine/invisible_titles", UCDataType::HexBinary, nullptr, 512 }, }; constexpr InitialiseUCSysConfig DefaultParent[] = { { "parent", UCDataType::Complex }, { "parent/version", UCDataType::UnsignedShort, "10" }, { "parent/enable", UCDataType::UnsignedByte, "0" }, { "parent/pin_code", UCDataType::String, nullptr, 5 }, { "parent/sec_question", UCDataType::UnsignedByte, "0" }, { "parent/sec_answer", UCDataType::String, nullptr, 257 }, { "parent/custom_sec_question", UCDataType::String, nullptr, 205 }, { "parent/email_address", UCDataType::String, nullptr, 1025 }, { "parent/permit_delete_all", UCDataType::UnsignedByte, "0" }, { "parent/rating_organization", UCDataType::UnsignedInt, "7" }, }; constexpr InitialiseUCSysConfig DefaultSpotpass[] = { { "spotpass", UCDataType::Complex }, { "spotpass/version", UCDataType::UnsignedByte, "2" }, { "spotpass/enable", UCDataType::UnsignedByte, "1" }, { "spotpass/auto_dl_app", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info1", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info2", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info3", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info4", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info5", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info6", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info7", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info8", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info9", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info10", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info11", UCDataType::UnsignedByte, "1" }, { "spotpass/upload_console_info12", UCDataType::UnsignedByte, "1" }, }; constexpr InitialiseUCSysConfig DefaultParentalAccount[] = { { "p_acct{}", UCDataType::Complex }, { "p_acct{}/version", UCDataType::UnsignedShort, "10" }, { "p_acct{}/game_rating", UCDataType::UnsignedInt, "18" }, { "p_acct{}/eshop_purchase", UCDataType::UnsignedByte, "0" }, { "p_acct{}/friend_reg", UCDataType::UnsignedByte, "0" }, { "p_acct{}/acct_modify", UCDataType::UnsignedByte, "0" }, { "p_acct{}/data_manage", UCDataType::UnsignedByte, "0" }, { "p_acct{}/int_setting", UCDataType::UnsignedByte, "0" }, { "p_acct{}/country_setting", UCDataType::UnsignedByte, "0" }, { "p_acct{}/sys_init", UCDataType::UnsignedByte, "0" }, { "p_acct{}/int_browser", UCDataType::UnsignedByte, "0" }, { "p_acct{}/int_movie", UCDataType::UnsignedByte, "0" }, { "p_acct{}/net_communication_on_game", UCDataType::UnsignedByte, "0" }, { "p_acct{}/network_launcher", UCDataType::UnsignedByte, "0" }, { "p_acct{}/entertainment_launcher", UCDataType::UnsignedByte, "0" }, }; static void writeDefaultConfig(pugi::xml_node &doc, const InitialiseUCSysConfig *first, const InitialiseUCSysConfig *last, int formatArg = 0) { auto formattedName = std::string { }; for (auto item = first; item != last; ++item) { auto parent = doc; auto name = std::string_view { item->name }; if (name.find('{') != std::string::npos) { formattedName = fmt::format(name.data(), formatArg); name = formattedName; } if (auto pos = name.find_last_of("/"); pos != std::string_view::npos) { auto parentPath = std::string { name.substr(0, pos) }; name = name.substr(pos + 1); parent = doc.first_element_by_path(parentPath.c_str()); decaf_check(parent); } auto node = parent.append_child(name.data()); if (item->defaultValue) { node.text() = item->defaultValue; } else if (item->dataType == UCDataType::HexBinary) { node.text() = std::string(item->length * 2, '0').c_str(); } switch (item->dataType) { case UCDataType::Complex: node.append_attribute("type") = "complex"; break; case UCDataType::UnsignedByte: node.append_attribute("type") = "unsignedByte"; node.append_attribute("length") = "1"; break; case UCDataType::UnsignedShort: node.append_attribute("type") = "unsignedShort"; node.append_attribute("length") = "2"; break; case UCDataType::UnsignedInt: node.append_attribute("type") = "unsignedInt"; node.append_attribute("length") = "4"; break; case UCDataType::SignedInt: node.append_attribute("type") = "signedInt"; node.append_attribute("length") = "4"; break; case UCDataType::HexBinary: node.append_attribute("type") = "hexBinary"; node.append_attribute("length") = item->length; break; case UCDataType::String: node.append_attribute("type") = "string"; node.append_attribute("length") = item->length; break; default: decaf_abort("Unimplemented UCDataType"); } if (item->access) { node.append_attribute("access") = item->access; } } } static void writeDefaultConfig(const std::filesystem::path &path, const InitialiseUCSysConfig *first, const InitialiseUCSysConfig *last, int formatArg = 0) { if (std::filesystem::exists(path)) { return; } auto doc = pugi::xml_document { }; auto decl = doc.prepend_child(pugi::node_declaration); decl.append_attribute("version") = "1.0"; decl.append_attribute("encoding") = "utf-8"; writeDefaultConfig(doc, first, last, formatArg); doc.save_file(path.string().c_str(), " ", 1, pugi::encoding_utf8); } static void applyRegionSettings() { auto language = DefaultCafe[2].defaultValue; auto country = DefaultCafe[3].defaultValue; switch (config()->system.region) { case SystemRegion::Japan: language = "0"; // Japanese country = "3"; // Japan break; case SystemRegion::USA: language = "1"; // English country = "49"; // United States break; case SystemRegion::Europe: language = "1"; // English country = "110"; // United Kingdom break; case SystemRegion::China: language = "6"; // Chinese country = "160"; // China break; case SystemRegion::Korea: language = "7"; // Korean country = "136"; // Best Korea break; case SystemRegion::Taiwan: language = "11"; // Taiwanese country = "128"; // Taiwan break; } DefaultCafe[2].defaultValue = language; DefaultCafe[3].defaultValue = country; } void initialiseSlc(std::string_view path) { // Ensure slc/proc/prefs exists auto prefsPath = std::filesystem::path { path } / "proc" / "prefs"; std::filesystem::create_directories(prefsPath); // Apply decaf region config to XML settings before we write them applyRegionSettings(); // Write some default config settings if they don't already exist writeDefaultConfig(prefsPath / "cafe.xml", std::begin(DefaultCafe), std::end(DefaultCafe)); writeDefaultConfig(prefsPath / "caffeine.xml", std::begin(DefaultCaffeine), std::end(DefaultCaffeine)); writeDefaultConfig(prefsPath / "parent.xml", std::begin(DefaultParent), std::end(DefaultParent)); writeDefaultConfig(prefsPath / "spotpass.xml", std::begin(DefaultSpotpass), std::end(DefaultSpotpass)); for (auto i = 1; i <= 12; ++i) { writeDefaultConfig(prefsPath / fmt::format("p_acct{}.xml", i), std::begin(DefaultParentalAccount), std::end(DefaultParentalAccount), i); } } } // namespace decaf::internal ================================================ FILE: src/libdecaf/src/decaf_slc.h ================================================ #pragma once #include <string_view> namespace decaf::internal { void initialiseSlc(std::string_view path); } // namespace decaf::internal ================================================ FILE: src/libdecaf/src/decaf_softwarekeyboard.cpp ================================================ #include "decaf_softwarekeyboard.h" #include "cafe/libraries/swkbd/swkbd_keyboard.h" namespace decaf { static SoftwareKeyboardDriver *sSoftwareKeyboardDriver = nullptr; void setSoftwareKeyboardDriver(SoftwareKeyboardDriver *driver) { sSoftwareKeyboardDriver = driver; } SoftwareKeyboardDriver * softwareKeyboardDriver() { return sSoftwareKeyboardDriver; } void SoftwareKeyboardDriver::accept() { cafe::swkbd::internal::inputAccept(); } void SoftwareKeyboardDriver::reject() { cafe::swkbd::internal::inputReject(); } void SoftwareKeyboardDriver::setInputString(std::u16string_view text) { cafe::swkbd::internal::setInputString(text); } } // namespace decaf ================================================ FILE: src/libdecaf/src/decaf_sound.cpp ================================================ #include "decaf_sound.h" namespace decaf { SoundDriver * sSoundDriver = nullptr; void setSoundDriver(SoundDriver *driver) { sSoundDriver = driver; } SoundDriver * getSoundDriver() { return sSoundDriver; } } // namespace sound ================================================ FILE: src/libdecaf/src/input/input.cpp ================================================ #include "decaf.h" #include "input.h" namespace input { void sampleVpadController(int channel, vpad::Status &status) { decaf::getInputDriver()->sampleVpadController(channel, status); } void sampleWpadController(int channel, wpad::Status &status) { decaf::getInputDriver()->sampleWpadController(channel, status); } } // namespace input ================================================ FILE: src/libdecaf/src/input/input.h ================================================ #pragma once #include <cstddef> #include "decaf_input.h" namespace input { namespace vpad = decaf::input::vpad; namespace wpad = decaf::input::wpad; void sampleVpadController(int channel, vpad::Status &status); void sampleWpadController(int channel, wpad::Status &status); } // namespace input ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp.cpp ================================================ #include "ios_acp.h" #include "ios_acp_client_save.h" #include "ios_acp_log.h" #include "ios_acp_main_server.h" #include "ios_acp_nnsm.h" #include "ios_acp_pdm_server.h" #include "decaf_log.h" #include "ios/ios_stackobject.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/nn/ios_nn.h" #include <common/log.h> using namespace ios::fs; using namespace ios::kernel; using namespace ios::mcp; namespace ios::acp { constexpr auto LocalHeapSize = 0x20000u; constexpr auto CrossHeapSize = 0x80000u; struct StaticAcpData { be2_val<Handle> fsaHandle; }; static phys_ptr<StaticAcpData> sAcpData = nullptr; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger acpLog = { }; void initialiseStaticData() { sAcpData = allocProcessStatic<StaticAcpData>(); sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } } // namespace internal static void updateSaveAndBossQuota() { // TODO: Update boss and save quota. // Make quota for Mii database in all regions // Note: Real IOS only does it for current system region FSAMakeQuota(sAcpData->fsaHandle, "/vol/storage_mlc01/usr/save/00050010/1004a000/user/common/db", 0x660, 24 * 1024 * 1024); FSAMakeQuota(sAcpData->fsaHandle, "/vol/storage_mlc01/usr/save/00050010/1004a100/user/common/db", 0x660, 24 * 1024 * 1024); FSAMakeQuota(sAcpData->fsaHandle, "/vol/storage_mlc01/usr/save/00050010/1004a200/user/common/db", 0x660, 24 * 1024 * 1024); } static Error acpSaveStart() { auto error = client::save::start(); if (error < Error::OK) { internal::acpLog->error("acpSaveStart: client::save::start failed with error = {}", error); return error; } // TODO: acp::client::save::bossStorage::start(); // TODO: This is not actually called here, but not sure where yet. updateSaveAndBossQuota(); return Error::OK; } static Error acpSaveStop() { return Error::OK; } static Error acpSystemStart() { auto error = FSAOpen(); if (error < Error::OK) { internal::acpLog->error("acpSystemStart: FSAOpen failed with error = {}", error); return error; } sAcpData->fsaHandle = static_cast<Handle>(error); return Error::OK; } static Error acpSystemStop() { if (sAcpData->fsaHandle > 0) { FSAClose(sAcpData->fsaHandle); } return Error::OK; } Error processEntryPoint(phys_ptr<void> /* context */) { StackArray<Message, 10> messages; StackObject<Message> message; MessageQueueId messageQueueId; // Initialise logger internal::acpLog = decaf::makeLogger("IOS_ACP"); // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticMainServerData(); internal::initialiseStaticNnsmData(); internal::initialiseStaticPdmServerData(); // Initialise nn for current process nn::initialiseProcess(); auto error = IOS_SetThreadPriority(CurrentThread, 50); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: IOS_SetThreadPriority failed with error = {}", error); return error; } // Initialise process heaps error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: IOS_CreateLocalProcessHeap failed with error = {}", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: IOS_CreateCrossProcessHeap failed with error = {}", error); return error; } // Setup /dev/acpproc error = IOS_CreateMessageQueue(messages, messages.size()); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: IOS_CreateMessageQueue failed with error = {}", error); return error; } messageQueueId = static_cast<MessageQueueId>(error); error = MCP_RegisterResourceManager("/dev/acpproc", messageQueueId); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: MCP_RegisterResourceManager failed for /dev/acpproc with error = {}", error); return error; } // Start nnsm error = internal::startNnsm(); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: startNnsm failed with error = {}", error); return error; } // Start nn::ipc::Server for /dev/acp_main error = internal::startMainServer(); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: startMainServer failed with error = {}", error); return error; } // Start nn::ipc::Server for /dev/pdm error = internal::startPdmServer(); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: startPdmServer failed with error = {}", error); return error; } // Run acpproc while (true) { error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None); if (error < Error::OK) { internal::acpLog->error( "processEntryPoint: IOS_ReceiveMessage failed with error = {}", error); break; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: case Command::Close: error = Error::OK; break; case Command::Suspend: error = acpSaveStop(); if (error >= Error::OK) { error = acpSystemStop(); } break; case Command::Resume: error = acpSystemStart(); if (error >= Error::OK) { error = acpSaveStart(); } break; default: error = Error::Invalid; } IOS_ResourceReply(request, error); } // Uninitialise nn for current process nn::uninitialiseProcess(); return error; } namespace internal { Handle getFsaHandle() { return sAcpData->fsaHandle; } } // namespace internal } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/ios_ipc.h" #include "ios/kernel/ios_kernel_process.h" namespace ios::acp { Error processEntryPoint(phys_ptr<void> context); namespace internal { Handle getFsaHandle(); } // namespace internal } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_client_save.cpp ================================================ #include "ios_acp.h" #include "ios_acp_client_save.h" #include "ios_acp_log.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/ios_stackobject.h" #include <fmt/core.h> namespace ios::acp::client::save { using namespace ios::fs; static FSAStatus checkExistenceUsingOpenDir(std::string_view path) { // TODO: FSAOpenDir, FSACloseDir return FSAStatus::Cancelled; } static bool checkExistence(std::string_view path) { StackObject<FSAStat> stat; auto error = FSAGetInfoByQuery(internal::getFsaHandle(), path, FSAQueryInfoType::Stat, stat); if (error == FSAStatus::PermissionError) { error = checkExistenceUsingOpenDir(path); } if (error == FSAStatus::OK) { return true; } if (error == FSAStatus::NotFound) { return false; } // Unexpected error! return false; } static FSAStatus createDir(std::string_view path, uint32_t mode) { return FSAMakeDir(internal::getFsaHandle(), path, mode); } static FSAStatus makeQuota(std::string_view path, uint32_t mode, uint64_t size) { return FSAMakeQuota(internal::getFsaHandle(), path, mode, size); } static Error createSystemSaveDir(std::string_view devicePath) { auto path = fmt::format("{}{}", devicePath, "usr/save/system/"); if (!checkExistence(path)) { createDir(path, 0x600); } path = fmt::format("{}{}", devicePath, "usr/save/system/acp/"); if (!checkExistence(path)) { makeQuota(path, 0x600, 0x800000u); } path = fmt::format("{}{}", devicePath, "usr/save/system/act/"); if (!checkExistence(path)) { makeQuota(path, 0x660, 0x800000u); } path = fmt::format("{}{}", devicePath, "usr/save/system/fpd/"); if (!checkExistence(path)) { makeQuota(path, 0x660, 0x200000u); } path = fmt::format("{}{}", devicePath, "usr/save/system/boss/"); if (!checkExistence(path)) { makeQuota(path, 0x660, 0x2000000u); } path = fmt::format("{}{}", devicePath, "usr/save/system/nim/"); if (!checkExistence(path)) { makeQuota(path, 0x660, 0x1A00000u); } path = fmt::format("{}{}", devicePath, "usr/save/system/pdm"); if (!checkExistence(path)) { makeQuota(path, 0x660, 0x1400000u); } path = fmt::format("{}{}", devicePath, "usr/save/system/no_delete"); if (!checkExistence(path)) { makeQuota(path, 0x600, 0x800000u); } // TODO: FlushQuota devicePath/usr/save return Error::OK; } static Error createNoDeleteDirs(std::string_view devicePath) { auto path = fmt::format("{}{}{:08X}/", devicePath, "usr/save/system/no_delete/", 0x00050000); if (!checkExistence(path)) { createDir(path, 0x600); } path = fmt::format("{}{}{:08X}/{:08X}/", devicePath, "usr/save/system/no_delete/", 0x00050000, 0x10100D00); if (!checkExistence(path)) { createDir(path, 0x600); } // TODO: ChangeOwner for path return Error::OK; } static Error createAppBoxCache() { auto path = "/vol/storage_mlc01/usr/save/system/acp/appBoxCache/"; if (!checkExistence(path)) { createDir(path, 0x600); } return Error::OK; } Error start() { auto error = createSystemSaveDir("/vol/storage_mlc01/"); if (error) { internal::acpLog->error( "client::save::start: createSystemSaveDir failed with error = {}", error); return error; } error = createNoDeleteDirs("/vol/storage_mlc01/"); if (error) { internal::acpLog->error( "client::save::start: createNoDeleteDirs failed with error = {}", error); return error; } error = createAppBoxCache(); if (error) { internal::acpLog->error( "client::save::start: createAppBoxCache failed with error = {}", error); return error; } // TODO: StartTimeStampThread return Error::OK; } } // namespace ios::acp::client::save ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_client_save.h ================================================ #include "ios/ios_enum.h" namespace ios::acp::client::save { Error start(); } // namespace ios::acp::client::save ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_enum.h ================================================ #ifndef IOS_ACP_ENUM_H #define IOS_ACP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(acp) ENUM_BEG(NssmCommand, uint32_t) ENUM_VALUE(RegisterService, 1) ENUM_VALUE(UnregisterService, 2) ENUM_END(NssmCommand) ENUM_NAMESPACE_EXIT(acp) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_ACP_ENUM_H ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_log.h ================================================ #pragma once #include <common/log.h> namespace ios::acp::internal { extern Logger acpLog; } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_main_server.cpp ================================================ #include "ios_acp_log.h" #include "ios_acp_main_server.h" #include "ios_acp_nn_miscservice.h" #include "ios_acp_nn_saveservice.h" #include "ios_acp_spm_extendedstorageservice.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" namespace ios::acp::internal { using namespace kernel; constexpr auto AcpMainNumMessages = 100u; constexpr auto AcpMainThreadStackSize = 0x10000u; constexpr auto AcpMainThreadPriority = 50u; class AcpMainServer : public nn::ipc::Server { public: AcpMainServer() : nn::ipc::Server(true) { } }; struct StaticAcpMainServerData { be2_struct<AcpMainServer> server; be2_array<Message, AcpMainNumMessages> messageBuffer; be2_array<uint8_t, AcpMainThreadStackSize> threadStack; }; static phys_ptr<StaticAcpMainServerData> sMainServerData = nullptr; Error startMainServer() { auto &server = sMainServerData->server; auto result = server.initialise("/dev/acp_main", phys_addrof(sMainServerData->messageBuffer), static_cast<uint32_t>(sMainServerData->messageBuffer.size())); if (result.failed()) { internal::acpLog->error( "startMainServer: Server initialisation failed for /dev/acp_main, result = {}", result.code()); return Error::FailInternal; } // TODO: Services 1, 3, 4, 6, 301, 302, 304 server.registerService<MiscService>(); server.registerService<SaveService>(); server.registerService<ExtendedStorageService>(); result = server.start(phys_addrof(sMainServerData->threadStack) + sMainServerData->threadStack.size(), static_cast<uint32_t>(sMainServerData->threadStack.size()), AcpMainThreadPriority); if (result.failed()) { internal::acpLog->error( "startMainServer: Server start failed for /dev/acp_main, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } void initialiseStaticMainServerData() { sMainServerData = allocProcessStatic<StaticAcpMainServerData>(); } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_main_server.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::acp::internal { Error startMainServer(); void initialiseStaticMainServerData(); } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_metaxml.cpp ================================================ #include "ios_acp.h" #include "ios_acp_metaxml.h" #include "nn/acp/nn_acp_result.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include <common/strutils.h> #include <fmt/core.h> #include <pugixml.hpp> using namespace ios::fs; using namespace ios::kernel; using namespace nn::acp; namespace ios::acp::internal { static nn::Result getU32(pugi::xml_node &node, const char *name, phys_ptr<uint32_t> outValue) { auto child = node.child(name); if (!child) { return ResultXmlItemNotFound; } *outValue = static_cast<uint32_t>(strtoul(child.text().get(), nullptr, 10)); return ResultSuccess; } static nn::Result getHex32(pugi::xml_node &node, const char *name, phys_ptr<uint32_t> outValue) { auto child = node.child(name); if (!child) { return ResultXmlItemNotFound; } *outValue = static_cast<uint32_t>(strtoul(child.text().get(), nullptr, 16)); return ResultSuccess; } static nn::Result getHex64(pugi::xml_node &node, const char *name, phys_ptr<uint64_t> outValue) { auto child = node.child(name); if (!child) { return ResultXmlItemNotFound; } *outValue = static_cast<uint64_t>(strtoull(child.text().get(), nullptr, 16)); return ResultSuccess; } template<uint32_t Size> static nn::Result getString(pugi::xml_node &node, const char *name, be2_array<char, Size> &buffer) { auto child = node.child(name); if (!child) { return ResultXmlItemNotFound; } if (!child.text()) { buffer[0] = char { 0 }; return ResultSuccess; } string_copy(phys_addrof(buffer).get(), child.text().get(), Size); return ResultSuccess; } nn::Result loadMetaXMLFromPath(std::string_view path, phys_ptr<nn::acp::ACPMetaXml> metaXml) { auto xmlSize = uint32_t { 0 }; auto xmlData = phys_ptr<uint8_t> { nullptr }; auto fsaStatus = FSAReadFileIntoCrossProcessHeap(getFsaHandle(), "/vol/meta/meta.xml", &xmlSize, &xmlData); if (fsaStatus < FSAStatus::OK) { return static_cast<nn::Result>(fsaStatus); } auto doc = pugi::xml_document { }; auto parseResult = doc.load_buffer(xmlData.get(), xmlSize); IOS_HeapFree(CrossProcessHeapId, xmlData); if (!parseResult) { return ResultInvalidXmlFile; } auto menu = doc.child("menu"); if (!menu) { return ResultInvalidXmlFile; } auto result = getU32(menu, "version", phys_addrof(metaXml->version)); if (result.failed()) { return result; } result = getString(menu, "product_code", metaXml->product_code); if (result.failed()) { return result; } result = getString(menu, "content_platform", metaXml->content_platform); if (result.failed()) { return result; } result = getString(menu, "company_code", metaXml->company_code); if (result.failed()) { return result; } result = getString(menu, "mastering_date", metaXml->mastering_date); if (result.failed()) { return result; } result = getU32(menu, "logo_type", phys_addrof(metaXml->logo_type)); if (result.failed()) { return result; } result = getHex32(menu, "app_launch_type", phys_addrof(metaXml->app_launch_type)); if (result.failed()) { return result; } result = getHex32(menu, "invisible_flag", phys_addrof(metaXml->invisible_flag)); if (result.failed()) { return result; } result = getHex32(menu, "no_managed_flag", phys_addrof(metaXml->no_managed_flag)); if (result.failed()) { return result; } result = getHex32(menu, "no_event_log", phys_addrof(metaXml->no_event_log)); if (result.failed()) { return result; } result = getHex32(menu, "no_icon_database", phys_addrof(metaXml->no_icon_database)); if (result.failed()) { return result; } result = getHex32(menu, "launching_flag", phys_addrof(metaXml->launching_flag)); if (result.failed()) { return result; } result = getHex32(menu, "install_flag", phys_addrof(metaXml->install_flag)); if (result.failed()) { return result; } result = getU32(menu, "closing_msg", phys_addrof(metaXml->closing_msg)); if (result.failed()) { return result; } result = getU32(menu, "title_version", phys_addrof(metaXml->title_version)); if (result.failed()) { return result; } result = getHex64(menu, "title_id", phys_addrof(metaXml->title_id)); if (result.failed()) { return result; } result = getHex32(menu, "group_id", phys_addrof(metaXml->group_id)); if (result.failed()) { return result; } result = getHex64(menu, "boss_id", phys_addrof(metaXml->boss_id)); if (result.failed()) { return result; } result = getHex64(menu, "os_version", phys_addrof(metaXml->os_version)); if (result.failed()) { return result; } result = getHex64(menu, "app_size", phys_addrof(metaXml->app_size)); if (result.failed()) { return result; } result = getHex64(menu, "common_save_size", phys_addrof(metaXml->common_save_size)); if (result.failed()) { return result; } result = getHex64(menu, "account_save_size", phys_addrof(metaXml->account_save_size)); if (result.failed()) { return result; } result = getHex64(menu, "common_boss_size", phys_addrof(metaXml->common_boss_size)); if (result.failed()) { return result; } result = getHex64(menu, "account_boss_size", phys_addrof(metaXml->account_boss_size)); if (result.failed()) { return result; } result = getU32(menu, "save_no_rollback", phys_addrof(metaXml->save_no_rollback)); if (result.failed()) { return result; } result = getU32(menu, "bg_daemon_enable", phys_addrof(metaXml->bg_daemon_enable)); if (result.failed()) { return result; } result = getHex32(menu, "join_game_id", phys_addrof(metaXml->join_game_id)); if (result.failed()) { return result; } result = getHex64(menu, "join_game_mode_mask", phys_addrof(metaXml->join_game_mode_mask)); if (result.failed()) { return result; } result = getU32(menu, "olv_accesskey", phys_addrof(metaXml->olv_accesskey)); if (result.failed()) { return result; } result = getU32(menu, "wood_tin", phys_addrof(metaXml->wood_tin)); if (result.failed()) { return result; } result = getU32(menu, "e_manual", phys_addrof(metaXml->e_manual)); if (result.failed()) { return result; } result = getU32(menu, "e_manual_version", phys_addrof(metaXml->e_manual_version)); if (result.failed()) { return result; } result = getHex32(menu, "region", phys_addrof(metaXml->region)); if (result.failed()) { return result; } result = getU32(menu, "pc_cero", phys_addrof(metaXml->pc_cero)); if (result.failed()) { return result; } result = getU32(menu, "pc_esrb", phys_addrof(metaXml->pc_esrb)); if (result.failed()) { return result; } result = getU32(menu, "pc_bbfc", phys_addrof(metaXml->pc_bbfc)); if (result.failed()) { return result; } result = getU32(menu, "pc_usk", phys_addrof(metaXml->pc_usk)); if (result.failed()) { return result; } result = getU32(menu, "pc_pegi_gen", phys_addrof(metaXml->pc_pegi_gen)); if (result.failed()) { return result; } result = getU32(menu, "pc_pegi_fin", phys_addrof(metaXml->pc_pegi_fin)); if (result.failed()) { return result; } result = getU32(menu, "pc_pegi_prt", phys_addrof(metaXml->pc_pegi_prt)); if (result.failed()) { return result; } result = getU32(menu, "pc_pegi_bbfc", phys_addrof(metaXml->pc_pegi_bbfc)); if (result.failed()) { return result; } result = getU32(menu, "pc_cob", phys_addrof(metaXml->pc_cob)); if (result.failed()) { return result; } result = getU32(menu, "pc_grb", phys_addrof(metaXml->pc_grb)); if (result.failed()) { return result; } result = getU32(menu, "pc_cgsrr", phys_addrof(metaXml->pc_cgsrr)); if (result.failed()) { return result; } result = getU32(menu, "pc_oflc", phys_addrof(metaXml->pc_oflc)); if (result.failed()) { return result; } result = getU32(menu, "pc_reserved0", phys_addrof(metaXml->pc_reserved0)); if (result.failed()) { return result; } result = getU32(menu, "pc_reserved1", phys_addrof(metaXml->pc_reserved1)); if (result.failed()) { return result; } result = getU32(menu, "pc_reserved2", phys_addrof(metaXml->pc_reserved2)); if (result.failed()) { return result; } result = getU32(menu, "pc_reserved3", phys_addrof(metaXml->pc_reserved3)); if (result.failed()) { return result; } result = getU32(menu, "ext_dev_nunchaku", phys_addrof(metaXml->ext_dev_nunchaku)); if (result.failed()) { return result; } result = getU32(menu, "ext_dev_classic", phys_addrof(metaXml->ext_dev_classic)); if (result.failed()) { return result; } result = getU32(menu, "ext_dev_urcc", phys_addrof(metaXml->ext_dev_urcc)); if (result.failed()) { return result; } result = getU32(menu, "ext_dev_board", phys_addrof(metaXml->ext_dev_board)); if (result.failed()) { return result; } result = getU32(menu, "ext_dev_usb_keyboard", phys_addrof(metaXml->ext_dev_usb_keyboard)); if (result.failed()) { return result; } result = getU32(menu, "ext_dev_etc", phys_addrof(metaXml->ext_dev_etc)); if (result.failed()) { return result; } result = getString(menu, "ext_dev_etc_name", metaXml->ext_dev_etc_name); if (result.failed()) { return result; } result = getU32(menu, "eula_version", phys_addrof(metaXml->eula_version)); if (result.failed()) { return result; } result = getU32(menu, "drc_use", phys_addrof(metaXml->drc_use)); if (result.failed()) { return result; } result = getU32(menu, "network_use", phys_addrof(metaXml->network_use)); if (result.failed()) { return result; } result = getU32(menu, "online_account_use", phys_addrof(metaXml->online_account_use)); if (result.failed()) { return result; } result = getU32(menu, "direct_boot", phys_addrof(metaXml->direct_boot)); if (result.failed()) { return result; } for (auto i = 0u; i < metaXml->reserved_flags.size(); ++i) { result = getHex32(menu, fmt::format("reserved_flag{}", i).c_str(), phys_addrof(metaXml->reserved_flags[i])); if (result.failed()) { return result; } } result = getString(menu, "longname_ja", metaXml->longname_ja); if (result.failed()) { return result; } result = getString(menu, "longname_en", metaXml->longname_en); if (result.failed()) { return result; } result = getString(menu, "longname_fr", metaXml->longname_fr); if (result.failed()) { return result; } result = getString(menu, "longname_de", metaXml->longname_de); if (result.failed()) { return result; } result = getString(menu, "longname_it", metaXml->longname_it); if (result.failed()) { return result; } result = getString(menu, "longname_es", metaXml->longname_es); if (result.failed()) { return result; } result = getString(menu, "longname_zhs", metaXml->longname_zhs); if (result.failed()) { return result; } result = getString(menu, "longname_ko", metaXml->longname_ko); if (result.failed()) { return result; } result = getString(menu, "longname_nl", metaXml->longname_nl); if (result.failed()) { return result; } result = getString(menu, "longname_pt", metaXml->longname_pt); if (result.failed()) { return result; } result = getString(menu, "longname_ru", metaXml->longname_ru); if (result.failed()) { return result; } result = getString(menu, "longname_zht", metaXml->longname_zht); if (result.failed()) { return result; } result = getString(menu, "shortname_ja", metaXml->shortname_ja); if (result.failed()) { return result; } result = getString(menu, "shortname_en", metaXml->shortname_en); if (result.failed()) { return result; } result = getString(menu, "shortname_fr", metaXml->shortname_fr); if (result.failed()) { return result; } result = getString(menu, "shortname_de", metaXml->shortname_de); if (result.failed()) { return result; } result = getString(menu, "shortname_it", metaXml->shortname_it); if (result.failed()) { return result; } result = getString(menu, "shortname_es", metaXml->shortname_es); if (result.failed()) { return result; } result = getString(menu, "shortname_zhs", metaXml->shortname_zhs); if (result.failed()) { return result; } result = getString(menu, "shortname_ko", metaXml->shortname_ko); if (result.failed()) { return result; } result = getString(menu, "shortname_nl", metaXml->shortname_nl); if (result.failed()) { return result; } result = getString(menu, "shortname_pt", metaXml->shortname_pt); if (result.failed()) { return result; } result = getString(menu, "shortname_ru", metaXml->shortname_ru); if (result.failed()) { return result; } result = getString(menu, "shortname_zht", metaXml->shortname_zht); if (result.failed()) { return result; } result = getString(menu, "publisher_ja", metaXml->publisher_ja); if (result.failed()) { return result; } result = getString(menu, "publisher_en", metaXml->publisher_en); if (result.failed()) { return result; } result = getString(menu, "publisher_fr", metaXml->publisher_fr); if (result.failed()) { return result; } result = getString(menu, "publisher_de", metaXml->publisher_de); if (result.failed()) { return result; } result = getString(menu, "publisher_it", metaXml->publisher_it); if (result.failed()) { return result; } result = getString(menu, "publisher_es", metaXml->publisher_es); if (result.failed()) { return result; } result = getString(menu, "publisher_zhs", metaXml->publisher_zhs); if (result.failed()) { return result; } result = getString(menu, "publisher_ko", metaXml->publisher_ko); if (result.failed()) { return result; } result = getString(menu, "publisher_nl", metaXml->publisher_nl); if (result.failed()) { return result; } result = getString(menu, "publisher_pt", metaXml->publisher_pt); if (result.failed()) { return result; } result = getString(menu, "publisher_ru", metaXml->publisher_ru); if (result.failed()) { return result; } result = getString(menu, "publisher_zht", metaXml->publisher_zht); if (result.failed()) { return result; } for (auto i = 0u; i < metaXml->add_on_unique_ids.size(); ++i) { result = getHex32(menu, fmt::format("add_on_unique_id{}", i).c_str(), phys_addrof(metaXml->add_on_unique_ids[i])); if (result.failed()) { return result; } } return ResultSuccess; } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_metaxml.h ================================================ #pragma once #include "nn/acp/nn_acp_types.h" #include "nn/nn_result.h" namespace ios::acp::internal { nn::Result loadMetaXMLFromPath(std::string_view path, phys_ptr<nn::acp::ACPMetaXml> metaXml); } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nn_miscservice.cpp ================================================ #include "ios_acp_nn_miscservice.h" #include "ios_acp_metaxml.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/acp/nn_acp_result.h" #include "nn/ipc/nn_ipc_result.h" #include <chrono> #include <ctime> using namespace nn::acp; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::acp::internal { static nn::Result getNetworkTime(CommandHandlerArgs &args) { auto command = ServerCommand<MiscService::GetNetworkTime> { args }; auto networkTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::seconds(time(NULL))).count(); auto unkValue = 1; command.WriteResponse(networkTime, unkValue); return ResultSuccess; } static nn::Result getTitleIdOfMainApplication(CommandHandlerArgs &args) { auto command = ServerCommand<MiscService::GetTitleIdOfMainApplication> { args }; auto buffer = OutBuffer<ACPMetaXml> { }; auto titleId = ACPTitleId { 0 }; // TODO: Cache ACPMetaXML of currently loaded title. // TODO: Read title_id from meta.xml? command.WriteResponse(titleId); return ResultSuccess; } static nn::Result getTitleMetaXml(CommandHandlerArgs &args) { auto command = ServerCommand<MiscService::GetTitleMetaXml> { args }; auto buffer = OutBuffer<ACPMetaXml> { }; auto titleId = ACPTitleId { 0 }; command.ReadRequest(buffer, titleId); // The whole meta xml should be aligned decaf_check(buffer.unalignedAfterBufferSize == 0); decaf_check(buffer.unalignedBeforeBufferSize == 0); auto metaXml = phys_cast<ACPMetaXml *>(buffer.alignedBuffer); // TODO: Use titleId to decide which meta.xml file to read... return loadMetaXMLFromPath("/vol/meta/meta.xml", metaXml); } nn::Result MiscService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case GetNetworkTime::command: return getNetworkTime(args); case GetTitleIdOfMainApplication::command: return getTitleIdOfMainApplication(args); case GetTitleMetaXml::command: return getTitleMetaXml(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nn_miscservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/acp/nn_acp_miscservice.h" namespace ios::acp::internal { struct MiscService : ::nn::acp::services::MiscService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nn_saveservice.cpp ================================================ #include "ios_acp.h" #include "ios_acp_nn_saveservice.h" #include "ios/ios_stackobject.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/acp/nn_acp_result.h" #include "nn/ipc/nn_ipc_result.h" #include <fmt/core.h> using namespace ios::fs; using namespace nn::acp; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::acp::internal { static nn::Result translateError(FSAStatus status) { switch (status) { case FSAStatus::OK: return nn::ResultSuccess; case FSAStatus::NotInit: return ResultNotInitialised; case FSAStatus::Busy: return ResultBusy; case FSAStatus::Cancelled: return ResultCancelled; case FSAStatus::EndOfDir: case FSAStatus::EndOfFile: case FSAStatus::MaxVolumes: case FSAStatus::MaxClients: case FSAStatus::MaxFiles: case FSAStatus::MaxDirs: case FSAStatus::MaxMountpoints: case FSAStatus::OutOfRange: case FSAStatus::OutOfResources: return ResultFsResource; case FSAStatus::NotFound: return ResultNotFound; case FSAStatus::AlreadyOpen: return ResultAlreadyDone; case FSAStatus::AlreadyExists: return ResultAlreadyExists; case FSAStatus::NotEmpty: case FSAStatus::FileTooBig: return ResultInvalidFile; case FSAStatus::DataCorrupted: return ResultDataCorrupted; case FSAStatus::PermissionError: return ResultNoPermission; case FSAStatus::StorageFull: return ResultDeviceFull; case FSAStatus::JournalFull: return ResultJournalFull; case FSAStatus::AccessError: return ResultFileAccessMode; case FSAStatus::UnavailableCmd: case FSAStatus::UnsupportedCmd: case FSAStatus::InvalidParam: case FSAStatus::InvalidPath: case FSAStatus::InvalidBuffer: case FSAStatus::InvalidAlignment: case FSAStatus::InvalidClientHandle: case FSAStatus::InvalidFileHandle: case FSAStatus::InvalidDirHandle: case FSAStatus::NotFile: case FSAStatus::NotDir: return ResultInvalidParameter; case FSAStatus::MediaNotReady: return ResultMediaNotReady; case FSAStatus::MediaError: return ResultMediaBroken; case FSAStatus::WriteProtected: return ResultMediaWriteProtected; default: return ResultFsaFatal; } } static nn::Result CreateDir(std::string_view path) { auto status = FSAMakeDir(getFsaHandle(), path, 0x600); if (status == FSAStatus::OK || status == FSAStatus::AlreadyExists) { return nn::ResultSuccess; } return translateError(status); } static nn::Result MakeQuota(std::string_view path, uint32_t mode, uint64_t size) { auto status = FSAMakeQuota(getFsaHandle(), path, mode, size); if (status == FSAStatus::OK || status == FSAStatus::AlreadyExists) { return nn::ResultSuccess; } return translateError(status); } static std::string GetAccountSaveDirPath(uint32_t persistentId, TitleId titleId) { auto titleHi = static_cast<uint32_t>(titleId >> 32); auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF); if (persistentId == 0) { return fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/common", titleHi, titleLo); } else { return fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/{:08X}", titleHi, titleLo, persistentId); } } static std::string GetAccountBossDirPath(uint32_t persistentId, TitleId titleId) { auto titleHi = static_cast<uint32_t>(titleId >> 32); auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF); if (persistentId == 0) { return fmt::format("/vol/storage_mlc01/usr/boss/{:08x}/{:08x}/user/common", titleHi, titleLo); } else { return fmt::format("/vol/storage_mlc01/usr/boss/{:08x}/{:08x}/user/{:08X}", titleHi, titleLo, persistentId); } } static nn::Result CreateSaveUserDir(TitleId titleId) { auto titleHi = static_cast<uint32_t>(titleId >> 32); auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF); auto result = CreateDir(fmt::format("/vol/storage_mlc01/usr/save/{:08x}", titleHi)); if (!result) { return result; } result = CreateDir(fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}", titleHi, titleLo)); if (!result) { return result; } result = CreateDir(fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user", titleHi, titleLo)); if (!result) { return result; } return nn::ResultSuccess; } static nn::Result CreateSaveMetaFiles(TitleId titleId) { auto titleHi = static_cast<uint32_t>(titleId >> 32); auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF); auto result = CreateDir(fmt::format("/vol/storage_mlc01/usr/save/{:08x}", titleHi)); if (!result) { return result; } result = CreateDir(fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}", titleHi, titleLo)); if (!result) { return result; } result = MakeQuota(fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/meta", titleHi, titleLo), 0x640, 0x80000); if (!result) { return result; } // TODO: FSAChangeMode(path, 0x640) // TODO: Copy current title meta.xml to save meta // TODO: Copy current title iconTex.tga to save meta return nn::ResultSuccess; } static nn::Result CreateBossStorage(uint32_t persistentId, TitleId titleId) { // TODO: Only create if meta->common_boss_size || meta->account_boss_size > 0 auto titleHi = static_cast<uint32_t>(titleId >> 32); auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF); auto result = CreateDir(fmt::format("/vol/storage_mlc01/usr/boss/{:08x}", titleHi)); if (!result) { return result; } result = CreateDir(fmt::format("/vol/storage_mlc01/usr/boss/{:08x}/{:08x}", titleHi, titleLo)); if (!result) { return result; } result = CreateDir(fmt::format("/vol/storage_mlc01/usr/boss/{:08x}/{:08x}/user", titleHi, titleLo)); if (!result) { return result; } // TODO: Use meta->account_boss_size, only create if > 0 result = MakeQuota(GetAccountBossDirPath(persistentId, titleId), 0x600, 0x40000); if (!result) { return result; } // TODO: Use meta->common_boss_size, only create if > 0 result = MakeQuota(GetAccountBossDirPath(0, titleId), 0x600, 0x40000); if (!result) { return result; } // TODO: Change owner to bossOwner auto bossOwner = FSAProcessInfo { }; bossOwner.titleId = 0x100000F3ull; bossOwner.processId = ProcessId::NIM; bossOwner.groupId = 0x400u; return nn::ResultSuccess; } static nn::Result CreateSaveDirAndBossStorageImpl(uint32_t persistentId, TitleId titleId) { auto result = CreateSaveUserDir(titleId); if (!result) { return result; } // TODO: Use meta->account_save_size, only create if > 0 result = MakeQuota(GetAccountSaveDirPath(persistentId, titleId), 0x660, 0x40000); if (!result) { return result; } // TODO: Use meta->common_save_size, only create if > 0 result = MakeQuota(GetAccountSaveDirPath(0, titleId), 0x660, 0x40000); if (!result) { return result; } return CreateBossStorage(persistentId, titleId); } static nn::Result CreateSaveDirAndBossStorage(uint32_t persistentId, TitleId titleId) { auto result = CreateSaveMetaFiles(titleId); if (!result) { return result; } result = CreateSaveDirAndBossStorageImpl(persistentId, titleId); if (!result) { return result; } /* TODO: UpdateSaveTimeStamp Sends a message to the save timestamp thread which will do the work. Thread updates saveinfo.xml with: <?xml version="1.0" encoding="utf-8"?> <info> <account persistentId="00000000"> <timestamp>0000000023cc86be</timestamp> </account> </info> */ return nn::ResultSuccess; } nn::Result createSaveDirInternalEx(uint32_t persistentId, TitleId titleId, ProcessId processId, GroupId groupId) { return CreateSaveDirAndBossStorage(persistentId, titleId); } static nn::Result createSaveDir(CommandHandlerArgs &args) { auto command = ServerCommand<SaveService::CreateSaveDir> { args }; auto deviceType = ACPDeviceType { }; auto persistentId = uint32_t { 0 }; command.ReadRequest(persistentId, deviceType); // TODO: Check that account with persistentId exists // 0xA0335200 = AccountNotExist return createSaveDirInternalEx(persistentId, args.resourceRequest->requestData.titleId, args.resourceRequest->requestData.processId, args.resourceRequest->requestData.groupId); } static nn::Result createSaveDirEx(CommandHandlerArgs &args) { auto command = ServerCommand<SaveService::CreateSaveDirEx> { args }; auto deviceType = ACPDeviceType { }; auto titleId = ACPTitleId { }; auto persistentId = uint32_t { 0 }; command.ReadRequest(persistentId, titleId, deviceType); // TODO: Check that account with persistentId exists // 0xA0335200 = AccountNotExist return createSaveDirInternalEx(persistentId, titleId, args.resourceRequest->requestData.processId, args.resourceRequest->requestData.groupId); } static nn::Result repairSaveMetaDir(CommandHandlerArgs &args) { // TODO: repairSaveMetaDir return nn::ResultSuccess; } static nn::Result mountSaveDir(CommandHandlerArgs &args) { auto titleHi = static_cast<uint32_t>(args.resourceRequest->requestData.titleId >> 32); auto titleLo = static_cast<uint32_t>(args.resourceRequest->requestData.titleId & 0xFFFFFFFF); /* Create the save user and common dir just incase it does not exist. * Note: The real MountSaveDir handler in IOS does not create these paths, * only mounts /vol/save, this must mean the initial path creation is done * somewhere else - I do not know where yet. */ CreateSaveUserDir(args.resourceRequest->requestData.titleId); CreateDir(GetAccountSaveDirPath(0, args.resourceRequest->requestData.titleId)); auto processInfo = FSAProcessInfo { }; processInfo.processId = args.resourceRequest->requestData.processId; processInfo.titleId = args.resourceRequest->requestData.titleId; processInfo.groupId = args.resourceRequest->requestData.groupId; auto fsaStatus = FSAMountWithProcess(getFsaHandle(), fmt::format("/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user", titleHi, titleLo), "/vol/save", FSAMountPriority::Base, &processInfo, nullptr, 0u); if (fsaStatus != FSAStatus::OK) { return translateError(fsaStatus); } return nn::ResultSuccess; } static nn::Result unmountSaveDir(CommandHandlerArgs &args) { auto processInfo = FSAProcessInfo { }; processInfo.processId = args.resourceRequest->requestData.processId; processInfo.titleId = args.resourceRequest->requestData.titleId; processInfo.groupId = args.resourceRequest->requestData.groupId; auto fsaStatus = FSAUnmountWithProcess(getFsaHandle(), "/vol/save", FSAMountPriority::UnmountAll, &processInfo); if (fsaStatus != FSAStatus::OK) { return translateError(fsaStatus); } return nn::ResultSuccess; } static nn::Result isExternalStorageRequired(CommandHandlerArgs &args) { auto command = ServerCommand<SaveService::IsExternalStorageRequired> { args }; command.WriteResponse(0); return nn::ResultSuccess; } static nn::Result mountExternalStorage(CommandHandlerArgs &args) { return nn::ResultSuccess; } static nn::Result unmountExternalStorage(CommandHandlerArgs &args) { return nn::ResultSuccess; } nn::Result SaveService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case CreateSaveDir::command: return createSaveDir(args); case CreateSaveDirEx::command: return createSaveDirEx(args); case IsExternalStorageRequired::command: return isExternalStorageRequired(args); case MountExternalStorage::command: return mountExternalStorage(args); case UnmountExternalStorage::command: return unmountExternalStorage(args); case MountSaveDir::command: return mountSaveDir(args); case UnmountSaveDir::command: return unmountSaveDir(args); case RepairSaveMetaDir::command: return repairSaveMetaDir(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nn_saveservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/acp/nn_acp_saveservice.h" namespace ios::acp::internal { struct SaveService : ::nn::acp::services::SaveService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nnsm.cpp ================================================ #include "ios_acp_enum.h" #include "ios_acp_log.h" #include "ios_acp_nnsm.h" #include "ios/ios_handlemanager.h" #include "ios/ios_stackobject.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/mcp/ios_mcp_ipc.h" #include <libcpu/cpu_formatters.h> namespace ios::acp::internal { using namespace kernel; constexpr auto NnsmNumMessages = 0x10u; constexpr auto NnsmThreadStackSize = 0x2000u; constexpr auto NnsmThreadPriority = 50u; struct StaticNnsmData { be2_val<BOOL> running; be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_array<Message, NnsmNumMessages> messageBuffer; be2_array<uint8_t, NnsmThreadStackSize> threadStack; }; static phys_ptr<StaticNnsmData> sNnsmData = nullptr; static Error nnsmWaitForResume() { StackObject<Message> message; // Read Open auto error = IOS_ReceiveMessage(sNnsmData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { internal::acpLog->error( "nnsmWaitForResume: IOS_ReceiveMessage failed for Open with error = {}", error); return error; } auto request = parseMessage<ResourceRequest>(message); if (request->requestData.command != Command::Open || request->requestData.processId != ProcessId::MCP) { internal::acpLog->error( "nnsmWaitForResume: Received unexpected message, expected Open from MCP, command = {}, processId = {}", request->requestData.command, request->requestData.processId); return Error::FailInternal; } if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) { internal::acpLog->error( "nnsmWaitForResume: IOS_ResourceReply failed for Open with error = {}", error); return error; } // Read Resume error = IOS_ReceiveMessage(sNnsmData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { internal::acpLog->error( "nnsmWaitForResume: IOS_ReceiveMessage failed for Resume with error = {}", error); return error; } request = parseMessage<ResourceRequest>(message); if (request->requestData.command != Command::Resume || request->requestData.processId != ProcessId::MCP) { internal::acpLog->error( "nnsmWaitForResume: Received unexpected message, expected Resume from MCP, command = {}, processId = {}", request->requestData.command, request->requestData.processId); return Error::FailInternal; } // TODO: Send resume message to /dev/nnmisc if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) { internal::acpLog->error( "nnsmWaitForResume: IOS_ResourceReply failed for Resume with error = {}", error); return error; } sNnsmData->running = TRUE; return Error::OK; } static void nnsmIoctl(phys_ptr<ResourceRequest> resourceRequest) { switch (static_cast<NssmCommand>(resourceRequest->requestData.args.ioctl.request)) { case NssmCommand::RegisterService: case NssmCommand::UnregisterService: // TODO: Do something with registered / unregistered services. IOS_ResourceReply(resourceRequest, Error::OK); return; default: IOS_ResourceReply(resourceRequest, Error::InvalidArg); } } static Error nnsmThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; if (auto error = nnsmWaitForResume(); error < Error::OK) { internal::acpLog->error( "nnsmThreadEntry: nnsmWaitForResume failed with error = {}", error); return error; } while (sNnsmData->running) { auto error = IOS_ReceiveMessage(sNnsmData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto resourceRequest = parseMessage<ResourceRequest>(message); switch (resourceRequest->requestData.command) { case Command::Open: case Command::Close: IOS_ResourceReply(resourceRequest, Error::OK); break; case Command::Ioctl: nnsmIoctl(resourceRequest); break; case Command::Suspend: sNnsmData->running = FALSE; IOS_ResourceReply(resourceRequest, Error::OK); break; default: IOS_ResourceReply(resourceRequest, Error::InvalidArg); } } return Error::OK; } Error startNnsm() { // Create message queue auto error = IOS_CreateMessageQueue(phys_addrof(sNnsmData->messageBuffer), static_cast<uint32_t>(sNnsmData->messageBuffer.size())); if (error < Error::OK) { return error; } sNnsmData->messageQueueId = static_cast<MessageQueueId>(error); // Register the device error = mcp::MCP_RegisterResourceManager("/dev/nnsm", sNnsmData->messageQueueId); if (error < Error::OK) { IOS_DestroyMessageQueue(sNnsmData->messageQueueId); return error; } // Create thread error = IOS_CreateThread(&nnsmThreadEntry, nullptr, phys_addrof(sNnsmData->threadStack) + sNnsmData->threadStack.size(), static_cast<uint32_t>(sNnsmData->threadStack.size()), NnsmThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { IOS_DestroyMessageQueue(sNnsmData->messageQueueId); return error; } sNnsmData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sNnsmData->threadId, "NnsmThread"); return IOS_StartThread(sNnsmData->threadId); } void initialiseStaticNnsmData() { sNnsmData = allocProcessStatic<StaticNnsmData>(); } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nnsm.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::acp::internal { Error startNnsm(); void initialiseStaticNnsmData(); } // namespace ios::namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nnsm_ipc.cpp ================================================ #include "ios_acp_enum.h" #include "ios_acp_nnsm_ipc.h" #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" #include <string_view> using namespace ios::kernel; namespace ios::acp { Error NNSM_RegisterServer(std::string_view device) { // Open nnsm auto error = IOS_Open("/dev/nnsm", OpenMode::None); if (error < Error::OK) { return error; } auto handle = static_cast<ResourceHandleId>(error); // Allocate buffer for name auto nameBufferLen = static_cast<uint32_t>(device.size() + 1); auto nameBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, nameBufferLen, 4); std::memcpy(nameBuffer.get(), device.data(), nameBufferLen - 1); phys_cast<char *>(nameBuffer)[nameBufferLen - 1] = char { 0 }; // Send ioctl error = IOS_Ioctl(handle, NssmCommand::RegisterService, nullptr, 0, nameBuffer, nameBufferLen); // Cleanup IOS_Close(handle); IOS_HeapFree(CrossProcessHeapId, nameBuffer); return error; } Error NNSM_UnregisterServer(std::string_view device) { // Open nnsm auto error = IOS_Open("/dev/nnsm", OpenMode::None); if (error < Error::OK) { return error; } auto handle = static_cast<ResourceHandleId>(error); // Allocate buffer for name auto nameBufferLen = static_cast<uint32_t>(device.size() + 1); auto nameBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, nameBufferLen, 4); std::memcpy(nameBuffer.get(), device.data(), nameBufferLen - 1); phys_cast<char *>(nameBuffer)[nameBufferLen - 1] = char { 0 }; // Send ioctl error = IOS_Ioctl(handle, NssmCommand::UnregisterService, nullptr, 0, nameBuffer, nameBufferLen); // Cleanup IOS_Close(handle); IOS_HeapFree(CrossProcessHeapId, nameBuffer); return error; } } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_nnsm_ipc.h ================================================ #pragma once #include "ios/ios_enum.h" #include <string_view> namespace ios::acp { Error NNSM_RegisterServer(std::string_view device); Error NNSM_UnregisterServer(std::string_view device); } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_pdm_cosservice.cpp ================================================ #include "ios_acp_pdm_cosservice.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/ipc/nn_ipc_result.h" #include "nn/pdm/nn_pdm_result.h" #include <chrono> #include <ctime> using namespace nn::pdm; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::acp::internal { static nn::Result getPlayDiaryMaxLength(CommandHandlerArgs &args) { auto command = ServerCommand<PdmCosService::GetPlayDiaryMaxLength> { args }; command.WriteResponse(18250); return ResultSuccess; } static nn::Result getPlayStatsMaxLength(CommandHandlerArgs &args) { auto command = ServerCommand<PdmCosService::GetPlayStatsMaxLength> { args }; command.WriteResponse(256); return ResultSuccess; } nn::Result PdmCosService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case GetPlayDiaryMaxLength::command: return getPlayDiaryMaxLength(args); case GetPlayStatsMaxLength::command: return getPlayStatsMaxLength(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_pdm_cosservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/pdm/nn_pdm_cosservice.h" namespace ios::acp::internal { struct PdmCosService : ::nn::pdm::services::CosService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_pdm_server.cpp ================================================ #include "ios_acp_log.h" #include "ios_acp_pdm_server.h" #include "ios_acp_pdm_cosservice.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" namespace ios::acp::internal { using namespace kernel; constexpr auto PdmNumMessages = 100u; constexpr auto PdmThreadStackSize = 0x4000u; constexpr auto PdmThreadPriority = 50u; class PdmServer : public nn::ipc::Server { public: PdmServer() : nn::ipc::Server(true) { } }; struct StaticPdmServerData { be2_struct<PdmServer> server; be2_array<Message, PdmNumMessages> messageBuffer; be2_array<uint8_t, PdmThreadStackSize> threadStack; }; static phys_ptr<StaticPdmServerData> sPdmServerData = nullptr; Error startPdmServer() { auto &server = sPdmServerData->server; auto result = server.initialise("/dev/pdm", phys_addrof(sPdmServerData->messageBuffer), static_cast<uint32_t>(sPdmServerData->messageBuffer.size())); if (result.failed()) { internal::acpLog->error( "startPdmServer: Server initialisation failed for /dev/pdm, result = {}", result.code()); return Error::FailInternal; } // TODO: Register services 1, 2 server.registerService<PdmCosService>(); result = server.start(phys_addrof(sPdmServerData->threadStack) + sPdmServerData->threadStack.size(), static_cast<uint32_t>(sPdmServerData->threadStack.size()), PdmThreadPriority); if (result.failed()) { internal::acpLog->error( "startPdmServer: Server start failed for /dev/pdm, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } void initialiseStaticPdmServerData() { sPdmServerData = allocProcessStatic<StaticPdmServerData>(); } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_pdm_server.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::acp::internal { Error startPdmServer(); void initialiseStaticPdmServerData(); } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_spm_extendedstorageservice.cpp ================================================ #include "ios_acp_spm_extendedstorageservice.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/nn_result.h" #include "nn/ipc/nn_ipc_result.h" using namespace nn::spm; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::acp::internal { static nn::Result setAutoFatal(CommandHandlerArgs &args) { auto command = ServerCommand<ExtendedStorageService::SetAutoFatal> { args }; auto autoFatal = uint8_t { 0 }; command.ReadRequest(autoFatal); return nn::ResultSuccess; } nn::Result ExtendedStorageService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case SetAutoFatal::command: return setAutoFatal(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::acp::internal ================================================ FILE: src/libdecaf/src/ios/acp/ios_acp_spm_extendedstorageservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/spm/nn_spm_extendedstorageservice.h" namespace ios::acp::internal { struct ExtendedStorageService : ::nn::spm::services::ExtendedStorageService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil.cpp ================================================ #include "ios_auxil.h" #include "ios_auxil_im_device.h" #include "ios_auxil_im_thread.h" #include "ios_auxil_usr_cfg_thread.h" #include "ios_auxil_usr_cfg_service_thread.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/ios_enum.h" #include "ios/ios_stackobject.h" #include <common/log.h> #include <libcpu/be2_struct.h> using namespace ios::kernel; namespace ios::auxil { constexpr auto LocalHeapSize = 0x20000u; constexpr auto CrossHeapSize = 0x80000u; constexpr auto NumMessages = 10u; struct StaticAuxilData { be2_val<MessageQueueId> messageQueueId; be2_array<Message, NumMessages> messageBuffer; }; static phys_ptr<StaticAuxilData> sData = nullptr; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { static void initialiseStaticData() { sData = allocProcessStatic<StaticAuxilData>(); sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } } // namespace internal Error processEntryPoint(phys_ptr<void> context) { // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticImDeviceData(); internal::initialiseStaticImThreadData(); internal::initialiseStaticUsrCfgThreadData(); internal::initialiseStaticUsrCfgServiceThreadData(); // Initialise process heaps auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { gLog->error("AUXIL: Failed to create local process heap, error = {}.", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { gLog->error("AUXIL: Failed to create cross process heap, error = {}.", error); return error; } // Start usr_cfg threads error = internal::startUsrCfgServiceThread(); if (error < Error::OK) { gLog->error("AUXIL: Failed to start usr_cfg service thread, error = {}.", error); return error; } error = internal::startUsrCfgThread(); if (error < Error::OK) { gLog->error("AUXIL: Failed to start usr_cfg thread, error = {}.", error); return error; } // Setup auxilproc error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { gLog->error("AUXIL: Failed to create auxil proc message queue, error = {}.", error); return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); error = mcp::MCP_RegisterResourceManager("/dev/auxilproc", sData->messageQueueId); if (error < Error::OK) { gLog->error("AUXIL: Failed to register /dev/auxilproc, error = {}.", error); return error; } // Run auxilproc thread StackObject<Message> message; while (true) { error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: case Command::Close: { IOS_ResourceReply(request, Error::OK); break; } case Command::Suspend: { internal::stopImThread(); IOS_ResourceReply(request, Error::OK); break; } case Command::Resume: { internal::startImThread(); IOS_ResourceReply(request, Error::OK); break; } case Command::SvcMsg: { if (request->requestData.args.svcMsg.command == 1u) { // TODO: "fast relaunch" } IOS_ResourceReply(request, Error::OK); break; } default: IOS_ResourceReply(request, Error::Invalid); } } } } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil.h ================================================ #pragma once #include "ios/ios_enum.h" #include <libcpu/be2_struct.h> namespace ios::auxil { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_config.cpp ================================================ #include "ios_auxil_config.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/ios_stackobject.h" #include <array> #include <common/strutils.h> #include <iterator> #include <pugixml.hpp> #include <sstream> #include <fmt/format.h> namespace ios::auxil { using namespace ios::fs; using namespace ios::kernel; static std::array<FSAHandle, NumIosProcess> sFsaHandles; Error openFsaHandle() { auto error = IOS_GetCurrentProcessId(); if (error < Error::OK) { return error; } auto pid = static_cast<ProcessId>(error); error = FSAOpen(); if (error < Error::OK) { return error; } auto handle = static_cast<FSAHandle>(error); sFsaHandles[pid] = handle; return Error::OK; } Error closeFsaHandle() { auto error = IOS_GetCurrentProcessId(); if (error < Error::OK) { return error; } auto pid = static_cast<ProcessId>(error); error = FSAClose(sFsaHandles[pid]); sFsaHandles[pid] = Error::Invalid; return error; } static FSAHandle getFsaHandle() { return sFsaHandles.at(IOS_GetCurrentProcessId()); } static phys_ptr<uint8_t> allocFileData(uint32_t size) { auto ptr = IOS_HeapAllocAligned(CrossProcessHeapId, size, 0x40u); if (ptr) { std::memset(ptr.get(), 0, size); } return phys_cast<uint8_t *>(ptr); } static void freeFileData(phys_ptr<uint8_t> fileData, uint32_t size) { IOS_HeapFree(CrossProcessHeapId, fileData); } static UCError readFile(std::string_view filename, uint32_t *outBytesRead, phys_ptr<uint8_t> *outBuffer) { StackObject<FSAStat> stat; FSAFileHandle fileHandle; auto fsaHandle = getFsaHandle(); if (!fsaHandle) { openFsaHandle(); fsaHandle = getFsaHandle(); } auto status = FSAOpenFile(fsaHandle, filename, "r", &fileHandle); if (status < FSAStatus::OK) { return UCError::FileOpen; } status = FSAStatFile(fsaHandle, fileHandle, stat); if (status < FSAStatus::OK) { FSACloseFile(fsaHandle, fileHandle); return UCError::FileStat; } auto fileData = allocFileData(stat->size); if (!fileData) { FSACloseFile(fsaHandle, fileHandle); return UCError::Alloc; } status = FSAReadFile(fsaHandle, fileData, stat->size, 1, fileHandle, FSAReadFlag::None); if (status < FSAStatus::OK) { freeFileData(fileData, stat->size); FSACloseFile(fsaHandle, fileHandle); return UCError::FileRead; } status = FSACloseFile(fsaHandle, fileHandle); if (status < FSAStatus::OK) { freeFileData(fileData, stat->size); return UCError::FileClose; } *outBytesRead = stat->size; *outBuffer = fileData; return UCError::OK; } static UCError writeFile(std::string_view filename, phys_ptr<uint8_t> buffer, uint32_t size) { StackObject<FSAStat> stat; FSAFileHandle fileHandle; auto fsaHandle = getFsaHandle(); if (!buffer) { if (FSARemove(fsaHandle, filename) < FSAStatus::OK) { return UCError::FileRemove; } return UCError::OK; } auto status = FSAOpenFile(fsaHandle, filename, "w", &fileHandle); if (status < FSAStatus::OK) { return UCError::FileOpen; } status = FSAWriteFile(fsaHandle, buffer, size, 1, fileHandle, FSAWriteFlag::None); if (status < FSAStatus::OK) { FSACloseFile(fsaHandle, fileHandle); return UCError::FileWrite; } status = FSACloseFile(fsaHandle, fileHandle); if (status < FSAStatus::OK) { return UCError::FileClose; } return UCError::OK; } static const char * getDataTypeName(UCDataType type) { switch (type) { case UCDataType::UnsignedByte: return "unsignedByte"; case UCDataType::UnsignedShort: return "unsignedShort"; case UCDataType::UnsignedInt: return "unsignedInt"; case UCDataType::SignedInt: return "signedInt"; case UCDataType::Float: return "float"; case UCDataType::String: return "string"; case UCDataType::HexBinary: return "hexBinary"; case UCDataType::Complex: return "complex"; default: return nullptr; } } static UCDataType getDataTypeByName(const char *name) { if (strcmp(name, "unsignedByte") == 0) { return UCDataType::UnsignedByte; } else if (strcmp(name, "unsignedShort") == 0) { return UCDataType::UnsignedShort; } else if (strcmp(name, "unsignedInt") == 0) { return UCDataType::UnsignedInt; } else if (strcmp(name, "signedInt") == 0) { return UCDataType::SignedInt; } else if (strcmp(name, "float") == 0) { return UCDataType::Float; } else if (strcmp(name, "string") == 0) { return UCDataType::String; } else if (strcmp(name, "hexBinary") == 0) { return UCDataType::HexBinary; } else if (strcmp(name, "complex") == 0) { return UCDataType::Complex; } else { return UCDataType::Invalid; } } static uint32_t getAccessFromString(const char *str) { auto access = 0u; if (!str || strlen(str) != 3) { return 0; } for (auto i = 0u; i < 3; ++i) { auto val = str[i] - '0'; if (val < 0 || val > 7) { return 0; } access |= val << (4 * i); } return access; } static std::string getAccessString(uint32_t access) { return fmt::format("{}{}{}", (access >> 8) & 0xf, (access >> 4) & 0xf, access & 0xf); } static std::string to_string(uint8_t *data, size_t size) { fmt::memory_buffer out; for (auto i = 0u; i < size; ++i) { fmt::format_to(std::back_inserter(out), "{:02X}", data[i]); } return to_string(out); } UCFileSys getFileSys(std::string_view name) { auto index = name.find_first_of(':'); if (index == std::string_view::npos) { return UCFileSys::Sys; } auto prefix = name.substr(0, index); if (prefix.compare("sys") == 0) { return UCFileSys::Sys; } else if (prefix.compare("slc") == 0) { return UCFileSys::Slc; } else if (prefix.compare("ram") == 0) { return UCFileSys::Ram; } return UCFileSys::Invalid; } static UCFileSys getFileSys(phys_ptr<UCItem> item) { return getFileSys(std::string_view { phys_addrof(item->name).get() }); } std::string_view getRootKey(std::string_view name) { auto rootKeyStart = name.find_first_of(':'); if (rootKeyStart == std::string_view::npos) { rootKeyStart = 0; } else { rootKeyStart += 1; } auto rootKeyEnd = name.find_first_of('.'); if (rootKeyEnd == std::string_view::npos) { return name.substr(rootKeyStart); } return name.substr(rootKeyStart, rootKeyEnd - rootKeyStart); } static std::string_view getRootKey(phys_ptr<UCItem> item) { return getRootKey(std::string_view { phys_addrof(item->name).get() }); } static UCError getConfigPath(phys_ptr<UCItem> item, std::string_view fileSysPath, std::string &path) { auto fileSys = getFileSys(item); if (fileSys == UCFileSys::Invalid) { return UCError::InvalidLocation; } auto rootKey = getRootKey(item); if (fileSysPath.length() + rootKey.length() + 7 > 64) { return UCError::StringTooLong; } path = fileSysPath; path += rootKey; path += ".xml"; return UCError::OK; } static UCError getItemPathName(phys_ptr<UCItem> item, std::string &outPath, std::string &outName) { auto key = std::string_view { phys_addrof(item->name).get() }; auto colonPos = key.find_first_of(':'); if (colonPos != std::string_view::npos) { key.remove_prefix(colonPos + 1); } auto lastDot = key.find_last_of('.'); if (lastDot == std::string_view::npos) { outPath = {}; outName = key; } else { outPath = key.substr(0, lastDot); outName = key.substr(lastDot + 1); } return UCError::OK; } /** * Ensure all items are in the same XML file and root key. */ static UCError checkItems(phys_ptr<UCItem> items, uint32_t count) { auto fileSys = getFileSys(items); if (fileSys == UCFileSys::Invalid) { return UCError::InvalidLocation; } auto rootKey = getRootKey(items); for (auto i = 0u; i < count; ++i) { auto item = phys_addrof(items[i]); if (getFileSys(item) != fileSys || rootKey.compare(getRootKey(item)) != 0) { return UCError::RootKeysDiffer; } } return UCError::OK; } /** * Read config items from specified file. */ UCError readItemsFromFile(std::string_view path, phys_ptr<UCItem> items, uint32_t count, phys_ptr<IoctlVec> vecs) { auto fileSize = uint32_t { 0 }; auto fileBuffer = phys_ptr<uint8_t> { nullptr }; auto error = readFile(path, &fileSize, &fileBuffer); if (error < UCError::OK) { return error; } // Parse XML auto doc = pugi::xml_document { }; auto parseResult = doc.load_buffer(fileBuffer.get(), fileSize); freeFileData(fileBuffer, fileSize); if (!parseResult) { return UCError::MalformedXML; } for (auto i = 0u; i < count; ++i) { auto item = phys_addrof(items[i]); auto itemPath = std::string { }; auto itemName = std::string { }; error = getItemPathName(item, itemPath, itemName); if (error < UCError::OK) { item->error = error; continue; } // Convert to a pugixml element path auto elementPath = itemPath; replace_all(elementPath, '.', '/'); if (!itemPath.empty()) { elementPath += '/'; } elementPath += itemName; // Find item in the xml document auto node = doc.first_element_by_path(elementPath.c_str()); if (!node) { item->error = UCError::KeyNotFound; continue; } // Verify the data type auto nodeType = getDataTypeByName(node.attribute("type").as_string()); if (nodeType != item->dataType) { item->error = UCError::InvalidType; continue; } // Verify complex data if (item->dataType != UCDataType::Complex && !item->data) { item->error = UCError::InvalidParam; continue; } // Read access (TODO: Verify access??) auto nodeAccess = node.attribute("access"); if (nodeAccess) { item->access = getAccessFromString(nodeAccess.value()); } // Read data phys_ptr<void> itemData; if (vecs) { itemData = phys_cast<void *>(vecs[i + 1].paddr); } else { itemData = item->data; } switch (item->dataType) { case UCDataType::UnsignedByte: if (item->dataSize >= 1) { *phys_cast<uint8_t *>(itemData) = static_cast<uint8_t>(node.text().as_uint()); } else { item->error = UCError::InvalidParam; continue; } break; case UCDataType::UnsignedShort: if (item->dataSize >= 2) { *phys_cast<uint16_t *>(itemData) = static_cast<uint16_t>(node.text().as_uint()); } else { item->error = UCError::InvalidParam; continue; } break; case UCDataType::UnsignedInt: if (item->dataSize >= 4) { *phys_cast<uint32_t *>(itemData) = static_cast<uint32_t>(node.text().as_uint()); } else { item->error = UCError::InvalidParam; continue; } break; case UCDataType::SignedInt: if (item->dataSize >= 4) { *phys_cast<int32_t *>(itemData) = static_cast<int32_t>(node.text().as_int()); } else { item->error = UCError::InvalidParam; continue; } break; case UCDataType::Float: if (item->dataSize >= 4) { *phys_cast<float *>(itemData) = node.text().as_float(); } else { item->error = UCError::InvalidParam; continue; } break; case UCDataType::String: { auto str = trim(node.text().get()); auto size = static_cast<uint32_t>(str.length()); if (size < item->dataSize) { std::memcpy(item->data.get(), str.data(), size + 1); item->dataSize = size + 1u; } else { item->error = UCError::StringTooLong; continue; } break; } case UCDataType::HexBinary: { auto src = trim(node.text().get()); auto size = static_cast<uint32_t>(src.length() / 2u); static auto hexCharToValue = [](char c) { if (c >= 'a' && c <= 'f') { return (c - 'a') + 10; } else if (c >= 'A' && c <= 'F') { return (c - 'A') + 10; } else if (c >= '0' && c <= '9') { return c - '0'; } else { return 0; } }; if (size <= item->dataSize) { auto dst = phys_cast<uint8_t *>(itemData); for (auto j = 0u; j < size; ++j) { auto value = uint8_t { 0 }; value |= hexCharToValue(src[j * 2 + 0]) << 4; value |= hexCharToValue(src[j * 2 + 1]); dst[j] = value; } item->dataSize = static_cast<uint32_t>(size); } else { item->error = UCError::StringTooLong; continue; } break; } case UCDataType::Complex: break; default: item->error = UCError::InvalidType; continue; } item->error = UCError::OK; } return UCError::OK; } /** * Read the given config items. */ UCError readItems(std::string_view fileSysPath, phys_ptr<UCItem> items, uint32_t count, phys_ptr<IoctlVec> vecs) { auto error = checkItems(items, count); if (error < UCError::OK) { return error; } auto path = std::string { }; error = getConfigPath(items, fileSysPath, path); if (error < UCError::OK) { return error; } return readItemsFromFile(path, items, count, vecs); } /** * Write the given items to file. */ UCError writeItems(std::string_view fileSysPath, phys_ptr<UCItem> items, uint32_t count, phys_ptr<IoctlVec> vecs) { pugi::xml_document doc; uint32_t fileSize; phys_ptr<uint8_t> fileBuffer; auto error = checkItems(items, count); if (error < UCError::OK) { return error; } auto path = std::string { }; error = getConfigPath(items, fileSysPath, path); if (error < UCError::OK) { return error; } // Try read existing file first error = readFile(path, &fileSize, &fileBuffer); if (error >= UCError::OK) { auto parseResult = doc.load_buffer(fileBuffer.get(), fileSize); freeFileData(fileBuffer, fileSize); if (!parseResult && parseResult.status != pugi::xml_parse_status::status_no_document_element) { return UCError::MalformedXML; } } // Apply modifications for (auto i = 0u; i < count; ++i) { auto item = phys_addrof(items[i]); auto keyPath = std::string { }; auto keyName = std::string { }; error = getItemPathName(item, keyPath, keyName); if (error < UCError::OK) { item->error = error; continue; } if (keyPath.empty()) { auto node = doc.first_child(); if (node) { if (keyName != node.name()) { item->error = UCError::RootKeysDiffer; continue; } if (strcmp(node.attribute("type").as_string(), "complex")) { item->error = UCError::RootKeysDiffer; continue; } } else { node = doc.append_child(keyName.c_str()); node.append_attribute("type").set_value("complex"); if (item->access) { node.append_attribute("access"); } } if (item->access) { node.attribute("access").set_value(getAccessString(item->access).c_str()); } } else { auto nodePath = keyPath; replace_all(nodePath, '.', '/'); auto parent = doc.first_element_by_path(nodePath.c_str()); if (!parent) { item->error = UCError::KeyNotFound; continue; } auto parentType = parent.attribute("type"); if (strcmp(parentType.as_string(), "complex")) { item->error = UCError::KeyNotFound; continue; } auto node = parent.child(keyName.c_str()); if (!node) { node = parent.append_child(keyName.c_str()); node.append_attribute("type"); if (item->dataType != UCDataType::Complex) { node.append_attribute("length"); } if (item->access) { node.append_attribute("access"); } } auto dataTypeName = getDataTypeName(item->dataType); if (!dataTypeName) { item->error = UCError::InvalidType; continue; } node.attribute("type").set_value(dataTypeName); if (item->dataType != UCDataType::Complex) { node.attribute("length").set_value(item->dataSize); } if (item->access) { node.attribute("access").set_value(getAccessString(item->access).c_str()); } phys_ptr<void> itemData; if (vecs) { itemData = phys_cast<void *>(vecs[i + 1].paddr); } else { itemData = item->data; } switch (item->dataType) { case UCDataType::UnsignedByte: if (item->data) { node.text().set(*phys_cast<uint8_t *>(itemData)); } else { node.text().set(0); } break; case UCDataType::UnsignedShort: if (item->data) { node.text().set(*phys_cast<uint16_t *>(itemData)); } else { node.text().set(0); } break; case UCDataType::UnsignedInt: if (item->data) { node.text().set(*phys_cast<uint32_t *>(itemData)); } else { node.text().set(0); } break; case UCDataType::SignedInt: if (item->data) { node.text().set(*phys_cast<int32_t *>(itemData)); } else { node.text().set(0); } break; case UCDataType::Float: if (item->data) { node.text().set(*phys_cast<float *>(itemData)); } else { node.text().set(0.0f); } break; case UCDataType::String: if (item->data) { // TODO: Check text format, maybe utf8/utf16 etc? node.text().set(phys_cast<char *>(itemData).get()); } else { node.text().set(""); } break; case UCDataType::HexBinary: if (item->data) { node.text().set(to_string(phys_cast<uint8_t *>(itemData).get(), item->dataSize).c_str()); } else { std::string value(static_cast<size_t>(item->dataSize * 2), '0'); node.text().set(value.c_str()); } break; default: item->error = UCError::InvalidType; continue; } } item->error = UCError::OK; } // Write modified config to file std::stringstream ss; doc.save(ss, " ", 1, pugi::encoding_utf8); auto xmlStr = ss.str(); // Copy to a physical memory buffer fileSize = static_cast<uint32_t>(xmlStr.size()); fileBuffer = allocFileData(fileSize); if (!fileBuffer) { return UCError::Alloc; } std::memcpy(fileBuffer.get(), xmlStr.data(), xmlStr.size()); error = writeFile(path, fileBuffer, fileSize); freeFileData(fileBuffer, fileSize); return error; } /** * List up to count items from xml * * Sets name, access, dataSize, dataType */ UCError listItems(phys_ptr<UCItem> items, uint32_t count) { return UCError::Unsupported; } /** * Get access, error, dataSize, dataType for specified items */ UCError queryItems(phys_ptr<UCItem> items, uint32_t count) { return UCError::Unsupported; } /** * Delete specific items from the config xml */ UCError deleteItems(std::string_view fileSysPath, phys_ptr<UCItem> items, uint32_t count) { auto error = checkItems(items, count); if (error < UCError::OK) { return error; } auto path = std::string { }; error = getConfigPath(items, fileSysPath, path); if (error < UCError::OK) { return error; } uint32_t fileSize; phys_ptr<uint8_t> fileBuffer; error = readFile(path, &fileSize, &fileBuffer); if (error < UCError::OK) { return error; } // Parse XML pugi::xml_document doc; auto parseResult = doc.load_buffer(fileBuffer.get(), fileSize); freeFileData(fileBuffer, fileSize); if (!parseResult) { return UCError::MalformedXML; } for (auto i = 0u; i < count; ++i) { auto item = phys_addrof(items[i]); auto itemPath = std::string { }; auto itemName = std::string { }; error = getItemPathName(item, itemPath, itemName); if (error < UCError::OK) { item->error = error; continue; } // Convert to a pugixml element path auto elementPath = itemPath; replace_all(elementPath, '.', '/'); if (!itemPath.empty()) { elementPath += '/'; } elementPath += itemName; // Find item in the xml document auto node = doc.first_element_by_path(elementPath.c_str()); if (!node) { item->error = UCError::KeyNotFound; continue; } // Verify the data type auto nodeType = getDataTypeByName(node.attribute("type").as_string()); if (nodeType != item->dataType) { item->error = UCError::InvalidType; continue; } // Verify complex data if (item->dataType != UCDataType::Complex && !item->data) { item->error = UCError::InvalidParam; continue; } // Remove node! node.parent().remove_child(node.name()); item->error = UCError::OK; } // Write modified config to file std::stringstream ss; doc.save(ss, " ", 1, pugi::encoding_utf8); auto xmlStr = ss.str(); fileSize = static_cast<uint32_t>(xmlStr.size()); fileBuffer = allocFileData(fileSize); if (!fileBuffer) { return UCError::Alloc; } std::memcpy(fileBuffer.get(), xmlStr.data(), xmlStr.size()); error = writeFile(path, fileBuffer, fileSize); freeFileData(fileBuffer, fileSize); return error; } /** * Delete the whole config! */ UCError deleteRoot(phys_ptr<UCItem> items, uint32_t count) { return UCError::Unsupported; } } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_config.h ================================================ #pragma once #include "ios_auxil_enum.h" #include "ios/ios_ipc.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> #include <string_view> namespace ios::auxil { /** * \ingroup ios_auxil * @{ */ #pragma pack(push, 1) struct UCItem { be2_array<char, 64> name; be2_val<uint32_t> access; be2_val<UCDataType> dataType; be2_val<UCError> error; be2_val<uint32_t> dataSize; be2_phys_ptr<void> data; }; CHECK_OFFSET(UCItem, 0x00, name); CHECK_OFFSET(UCItem, 0x40, access); CHECK_OFFSET(UCItem, 0x44, dataType); CHECK_OFFSET(UCItem, 0x48, error); CHECK_OFFSET(UCItem, 0x4C, dataSize); CHECK_OFFSET(UCItem, 0x50, data); CHECK_SIZE(UCItem, 0x54); #pragma pack(pop) Error openFsaHandle(); Error closeFsaHandle(); UCFileSys getFileSys(std::string_view name); std::string_view getRootKey(std::string_view name); UCError readItemsFromFile(std::string_view path, phys_ptr<UCItem> items, uint32_t count, phys_ptr<IoctlVec> vecs); UCError readItems(std::string_view fileSysPath, phys_ptr<UCItem> items, uint32_t count, phys_ptr<IoctlVec> vecs); UCError writeItems(std::string_view fileSysPath, phys_ptr<UCItem> items, uint32_t count, phys_ptr<IoctlVec> vecs); UCError deleteItems(std::string_view fileSysPath, phys_ptr<UCItem> items, uint32_t count); /** @} */ } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_enum.h ================================================ #ifndef IOS_AUXIL_ENUM_H #define IOS_AUXIL_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(auxil) ENUM_BEG(IMCommand, uint32_t) ENUM_VALUE(CopyParameterFromNv, 0) ENUM_VALUE(SetNvParameter, 1) ENUM_VALUE(SetParameter, 2) ENUM_VALUE(GetParameter, 3) ENUM_VALUE(GetHomeButtonParams, 7) ENUM_VALUE(GetTimerRemaining, 8) ENUM_VALUE(GetNvParameter, 9) ENUM_END(IMCommand) ENUM_BEG(IMHomeButtonType, uint32_t) ENUM_VALUE(None, 0) ENUM_VALUE(WiiRemote, 1) ENUM_VALUE(WiiRemoteUrc, 2) ENUM_VALUE(WiiRemoteExtention, 3) ENUM_VALUE(WiiUDrc, 4) ENUM_END(IMHomeButtonType) ENUM_BEG(IMTimer, uint32_t) ENUM_VALUE(Dim, 0) ENUM_VALUE(APD, 1) ENUM_END(IMTimer) ENUM_BEG(IMParameter, uint32_t) ENUM_VALUE(InactiveSeconds, 0) ENUM_VALUE(DimEnabled, 1) ENUM_VALUE(DimPeriod, 2) ENUM_VALUE(APDEnabled, 3) ENUM_VALUE(APDPeriod, 4) ENUM_VALUE(ResetEnable, 5) ENUM_VALUE(ResetSeconds, 6) ENUM_VALUE(PowerOffEnable, 7) ENUM_VALUE(ApdOccurred, 8) ENUM_VALUE(DimEnableTv, 9) ENUM_VALUE(DimEnableDrc, 10) ENUM_VALUE(Max, 11) ENUM_END(IMParameter) ENUM_BEG(UCCommand, uint32_t) ENUM_VALUE(ReadSysConfig, 0x30) ENUM_VALUE(WriteSysConfig, 0x31) ENUM_VALUE(DeleteSysConfig, 0x32) ENUM_VALUE(QuerySysConfig, 0x33) ENUM_VALUE(ListSysConfig, 0x34) ENUM_END(UCCommand) ENUM_BEG(UCDataType, uint32_t) ENUM_VALUE(Undefined, 0x00) ENUM_VALUE(UnsignedByte, 0x01) ENUM_VALUE(UnsignedShort, 0x02) ENUM_VALUE(UnsignedInt, 0x03) ENUM_VALUE(SignedInt, 0x04) ENUM_VALUE(Float, 0x05) ENUM_VALUE(String, 0x06) ENUM_VALUE(HexBinary, 0x07) ENUM_VALUE(Complex, 0x08) ENUM_VALUE(Invalid, 0xFF) ENUM_END(UCDataType) ENUM_BEG(UCError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Error, -1) ENUM_VALUE(Other, -0x200001) ENUM_VALUE(System, -0x200002) ENUM_VALUE(Alloc, -0x200003) ENUM_VALUE(Opcode, -0x200004) ENUM_VALUE(InvalidParam, -0x200005) ENUM_VALUE(InvalidType, -0x200006) ENUM_VALUE(Unsupported, -0x200007) ENUM_VALUE(NonLeafNode, -0x200008) ENUM_VALUE(KeyNotFound, -0x200009) ENUM_VALUE(Modify, -0x20000A) ENUM_VALUE(StringTooLong, -0x20000B) ENUM_VALUE(RootKeysDiffer, -0x20000C) ENUM_VALUE(InvalidLocation, -0x20000D) ENUM_VALUE(BadComment, -0x20000E) ENUM_VALUE(ReadAccess, -0x20000F) ENUM_VALUE(WriteAccess, -0x200010) ENUM_VALUE(CreateAccess, -0x200011) ENUM_VALUE(FileSysName, -0x200012) ENUM_VALUE(FileSysInit, -0x200013) ENUM_VALUE(FileSysMount, -0x200014) ENUM_VALUE(FileOpen, -0x200015) ENUM_VALUE(FileStat, -0x200016) ENUM_VALUE(FileRead, -0x200017) ENUM_VALUE(FileWrite, -0x200018) ENUM_VALUE(FileTooBig, -0x200019) ENUM_VALUE(FileRemove, -0x20001A) ENUM_VALUE(FileRename, -0x20001B) ENUM_VALUE(FileClose, -0x20001C) ENUM_VALUE(FileSeek, -0x20001D) ENUM_VALUE(FileConfirm, -0x20001E) ENUM_VALUE(FileBackup, -0x20001F) ENUM_VALUE(MalformedXML, -0x200020) ENUM_VALUE(Version, -0x200021) ENUM_VALUE(NoIPCBuffers, -0x200022) ENUM_VALUE(FileLockNeeded, -0x200024) ENUM_VALUE(SysProt, -0x200028) ENUM_END(UCError) ENUM_BEG(UCFileSys, uint32_t) ENUM_VALUE(Invalid, 0x00) ENUM_VALUE(Sys, 0x01) ENUM_VALUE(Slc, 0x02) ENUM_VALUE(Ram, 0x03) ENUM_END(UCFileSys) ENUM_NAMESPACE_EXIT(auxil) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_AUXIL_ENUM_H ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im.h ================================================ #pragma once #include "ios_auxil_enum.h" #include "ios_auxil_im_request.h" #include "ios_auxil_im_response.h" ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im_device.cpp ================================================ #include "ios_auxil_im_device.h" #include "ios_auxil_usr_cfg_ipc.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/ios_stackobject.h" #include <map> using namespace ios::kernel; namespace ios::auxil::internal { struct StaticImDeviceData { be2_val<UCHandle> ucHandle; be2_array<uint32_t, IMParameter::Max> parameters; be2_array<uint32_t, IMParameter::Max> nvParameters; be2_array<uint32_t, IMParameter::Max> defaultValues; }; static phys_ptr<StaticImDeviceData> sData = nullptr; static std::map<IMParameter, const char *> sParameterKey { { IMParameter::InactiveSeconds, "inactive_seconds" }, { IMParameter::DimEnabled, "dim_enable" }, { IMParameter::DimPeriod, "dim_seconds" }, { IMParameter::APDEnabled, "apd_enable" }, { IMParameter::APDPeriod, "apd_seconds" }, { IMParameter::ResetEnable, "reset_enable" }, { IMParameter::ResetSeconds, "reset_secnds" }, { IMParameter::PowerOffEnable, "power_off_enable" }, { IMParameter::ApdOccurred, "apd_occurred" }, { IMParameter::DimEnableTv, "dim_tv_enable" }, { IMParameter::DimEnableDrc, "dim_drc_enable" }, }; Error IMDevice::copyParameterFromNv() { for (auto i = 0u; i < IMParameter::Max; ++i) { if (i == IMParameter::PowerOffEnable) { // Do not copy parameter 7?? continue; } setParameter(static_cast<IMParameter>(i), phys_addrof(sData->nvParameters[i])); } return Error::OK; } Error IMDevice::getHomeButtonParams(phys_ptr<IMGetHomeButtonParamResponse> response) { response->type = IMHomeButtonType::None; response->index = 0; return Error::OK; } Error IMDevice::getParameter(IMParameter parameter, phys_ptr<uint32_t> value) { if (parameter >= IMParameter::Max) { return Error::Invalid; } *value = sData->parameters[parameter]; return Error::OK; } Error IMDevice::getNvParameter(IMParameter parameter, phys_ptr<uint32_t> value) { if (parameter >= IMParameter::Max) { return Error::Invalid; } *value = sData->nvParameters[parameter]; return Error::OK; } Error IMDevice::getTimerRemaining(IMTimer timer, phys_ptr<uint32_t> value) { *value = 30u * 60; return Error::OK; } Error IMDevice::setParameter(IMParameter parameter, phys_ptr<uint32_t> value) { if (parameter >= IMParameter::Max) { return Error::Invalid; } sData->parameters[parameter] = *value; return Error::OK; } Error IMDevice::setNvParameter(IMParameter parameter, phys_ptr<uint32_t> value) { if (parameter >= IMParameter::Max) { return Error::Invalid; } sData->nvParameters[parameter] = *value; return Error::OK; } void initialiseStaticImDeviceData() { sData = allocProcessStatic<StaticImDeviceData>(); sData->defaultValues[IMParameter::InactiveSeconds] = 0xAu; sData->defaultValues[IMParameter::DimEnabled] = 1u; sData->defaultValues[IMParameter::DimPeriod] = 300u; sData->defaultValues[IMParameter::APDEnabled] = 1u; sData->defaultValues[IMParameter::APDPeriod] = 3600u; sData->defaultValues[IMParameter::ResetEnable] = 0u; sData->defaultValues[IMParameter::ResetSeconds] = 120u; sData->defaultValues[IMParameter::PowerOffEnable] = 1u; sData->defaultValues[IMParameter::ApdOccurred] = 0u; sData->defaultValues[IMParameter::DimEnableTv] = 1u; sData->defaultValues[IMParameter::DimEnableDrc] = 1u; } Error initialiseImParameters() { StackArray<UCSysConfig, IMParameter::Max> sysConfig; auto error = UCOpen(); if (error < Error::OK) { return Error::Invalid; } sData->ucHandle = static_cast<UCHandle>(error); for (auto i = 0u; i < IMParameter::Max; ++i) { auto parameter = static_cast<IMParameter>(i); auto &cfg = sysConfig[i]; cfg.name = std::string { "slc:im_cfg." } + sParameterKey[parameter]; cfg.data = phys_addrof(sData->nvParameters[i]); cfg.dataSize = 4u; cfg.dataType = UCDataType::UnsignedInt; cfg.error = UCError::OK; cfg.access = 0u; } auto ucError = UCReadSysConfig(sData->ucHandle, IMParameter::Max, sysConfig); if (ucError < UCError::OK) { return Error::Invalid; } auto numErrorParams = 0u; for (auto i = 0u; i < IMParameter::Max; ++i) { if (sysConfig[i].error != UCError::OK) { sData->nvParameters[i] = sData->defaultValues[i]; ++numErrorParams; } } if (numErrorParams > 0) { ucError = UCWriteSysConfig(sData->ucHandle, IMParameter::Max, sysConfig); if (ucError < UCError::OK) { error = Error::Invalid; } } IMDevice device; device.copyParameterFromNv(); return error; } } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im_device.h ================================================ #pragma once #include "ios_auxil_enum.h" #include "ios_auxil_im_request.h" #include "ios_auxil_im_response.h" #include "ios/kernel/ios_kernel_resourcemanager.h" namespace ios::auxil::internal { class IMDevice { public: Error copyParameterFromNv(); Error getHomeButtonParams(phys_ptr<IMGetHomeButtonParamResponse> response); Error getParameter(IMParameter parameter, phys_ptr<uint32_t> value); Error getNvParameter(IMParameter parameter, phys_ptr<uint32_t> value); Error getTimerRemaining(IMTimer timer, phys_ptr<uint32_t> value); Error setParameter(IMParameter parameter, phys_ptr<uint32_t> value); Error setNvParameter(IMParameter parameter, phys_ptr<uint32_t> value); private: }; void initialiseStaticImDeviceData(); Error initialiseImParameters(); } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im_request.h ================================================ #pragma once #include "ios_auxil_enum.h" #include <cstdint> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::auxil { /** * \ingroup ios_auxil * @{ */ #pragma pack(push, 1) struct IMGetParameterRequest { be2_val<IMParameter> parameter; UNKNOWN(4); }; CHECK_OFFSET(IMGetParameterRequest, 0x00, parameter); CHECK_SIZE(IMGetParameterRequest, 0x08); struct IMGetNvParameterRequest { be2_val<IMParameter> parameter; UNKNOWN(4); }; CHECK_OFFSET(IMGetNvParameterRequest, 0x00, parameter); CHECK_SIZE(IMGetNvParameterRequest, 0x08); struct IMGetTimerRemainingRequest { be2_val<IMTimer> timer; UNKNOWN(4); }; CHECK_OFFSET(IMGetTimerRemainingRequest, 0x00, timer); CHECK_SIZE(IMGetTimerRemainingRequest, 0x08); struct IMSetParameterRequest { be2_val<IMParameter> parameter; be2_val<uint32_t> value; }; CHECK_OFFSET(IMSetParameterRequest, 0x00, parameter); CHECK_OFFSET(IMSetParameterRequest, 0x04, value); CHECK_SIZE(IMSetParameterRequest, 0x08); struct IMSetNvParameterRequest { be2_val<IMParameter> parameter; be2_val<uint32_t> value; }; CHECK_OFFSET(IMSetNvParameterRequest, 0x00, parameter); CHECK_OFFSET(IMSetNvParameterRequest, 0x04, value); CHECK_SIZE(IMSetNvParameterRequest, 0x08); struct IMRequest { union { be2_struct<IMGetParameterRequest> getParameter; be2_struct<IMGetNvParameterRequest> getNvParameter; be2_struct<IMGetTimerRemainingRequest>getTimerRemaining; be2_struct<IMSetParameterRequest> setParameter; be2_struct<IMSetNvParameterRequest> setNvParameter; }; }; CHECK_OFFSET(IMRequest, 0x00, getParameter); CHECK_OFFSET(IMRequest, 0x00, getNvParameter); CHECK_OFFSET(IMRequest, 0x00, getTimerRemaining); CHECK_OFFSET(IMRequest, 0x00, setParameter); CHECK_OFFSET(IMRequest, 0x00, setNvParameter); CHECK_SIZE(IMRequest, 0x08); #pragma pack(pop) /** @} */ } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im_response.h ================================================ #pragma once #include "ios_auxil_enum.h" #include <cstdint> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::auxil { /** * \ingroup ios_auxil * @{ */ #pragma pack(push, 1) struct IMGetHomeButtonParamResponse { be2_val<IMHomeButtonType> type; be2_val<int32_t> index; }; CHECK_OFFSET(IMGetHomeButtonParamResponse, 0x00, type); CHECK_OFFSET(IMGetHomeButtonParamResponse, 0x04, index); struct IMGetParameterResponse { be2_val<IMParameter> parameter; be2_val<uint32_t> value; }; CHECK_OFFSET(IMGetParameterResponse, 0x00, parameter); CHECK_OFFSET(IMGetParameterResponse, 0x04, value); struct IMGetNvParameterResponse { be2_val<IMParameter> parameter; be2_val<uint32_t> value; }; CHECK_OFFSET(IMGetNvParameterResponse, 0x00, parameter); CHECK_OFFSET(IMGetNvParameterResponse, 0x04, value); struct IMGetTimerRemainingResponse { be2_val<IMTimer> timer; be2_val<uint32_t> value; }; CHECK_OFFSET(IMGetTimerRemainingResponse, 0x00, timer); CHECK_OFFSET(IMGetTimerRemainingResponse, 0x04, value); struct IMResponse { union { be2_struct<IMGetHomeButtonParamResponse> getHomeButtonParam; be2_struct<IMGetParameterResponse> getParameter; be2_struct<IMGetNvParameterResponse> getNvParameter; be2_struct<IMGetTimerRemainingResponse> getTimerRemaining; }; }; CHECK_OFFSET(IMResponse, 0x00, getHomeButtonParam); CHECK_OFFSET(IMResponse, 0x00, getParameter); CHECK_OFFSET(IMResponse, 0x00, getNvParameter); CHECK_OFFSET(IMResponse, 0x00, getTimerRemaining); CHECK_SIZE(IMResponse, 0x08); #pragma pack(pop) /** @} */ } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im_thread.cpp ================================================ #include "ios_auxil_im_device.h" #include "ios_auxil_im_thread.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_timer.h" #include "ios/ios_handlemanager.h" #include "ios/ios_stackobject.h" #include <memory> namespace ios::auxil::internal { using namespace kernel; using IMDeviceHandle = uint32_t; constexpr auto MaxNumIMDevices = 96u; constexpr auto ImNumMessages = 20u; constexpr auto ImThreadStackSize = 0x800u; constexpr auto ImThreadPriority = 69u; struct StaticImThreadData { be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_array<Message, ImNumMessages> messageBuffer; be2_array<uint8_t, ImThreadStackSize> threadStack; be2_val<Command> stopMessageBuffer; }; static phys_ptr<StaticImThreadData> sImThreadData; static HandleManager<IMDevice, IMDeviceHandle, MaxNumIMDevices> sDevices; static Error imDeviceIoctlv(IMDeviceHandle handle, IMCommand command, phys_ptr<IoctlVec> vecs) { IMDevice *device = nullptr; auto error = sDevices.get(handle, &device); if (error < Error::OK) { return error; } auto request = phys_cast<IMRequest *>(vecs[0].paddr); auto response = phys_cast<IMResponse *>(vecs[1].paddr); switch (command) { case IMCommand::CopyParameterFromNv: error = device->copyParameterFromNv(); break; case IMCommand::SetNvParameter: error = device->setNvParameter(request->setNvParameter.parameter, phys_addrof(request->setNvParameter.value)); break; case IMCommand::SetParameter: error = device->setParameter(request->setParameter.parameter, phys_addrof(request->setParameter.value)); break; case IMCommand::GetParameter: error = device->getParameter(request->getParameter.parameter, phys_addrof(response->getParameter.value)); break; case IMCommand::GetHomeButtonParams: error = device->getHomeButtonParams(phys_addrof(response->getHomeButtonParam)); break; case IMCommand::GetTimerRemaining: error = device->getTimerRemaining(request->getTimerRemaining.timer, phys_addrof(response->getTimerRemaining.value)); break; case IMCommand::GetNvParameter: error = device->getNvParameter(request->getNvParameter.parameter, phys_addrof(response->getNvParameter.value)); break; default: error = Error::InvalidArg; } return error; } static Error imThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; auto error = Error::OK; initialiseImParameters(); while (true) { error = IOS_ReceiveMessage(sImThreadData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { break; } auto request = parseMessage<ResourceRequest>(message); if (request->requestData.command == Command::IpcMsg2) { // Shutdown message from stopImThread error = Error::OK; break; } switch (request->requestData.command) { case Command::Open: { error = sDevices.open(); IOS_ResourceReply(request, error); break; } case Command::Close: { error = sDevices.close(request->requestData.handle); IOS_ResourceReply(request, error); break; } case Command::Ioctlv: { auto handle = static_cast<IMDeviceHandle>(request->requestData.handle); auto command = static_cast<IMCommand>(request->requestData.args.ioctlv.request); error = imDeviceIoctlv(handle, command, request->requestData.args.ioctlv.vecs); IOS_ResourceReply(request, error); break; } case Command::IpcMsg1: { // Timer message break; } default: IOS_ResourceReply(request, Error::InvalidArg); } } sDevices.closeAll(); // TODO: Stop timer // TODO: Close UsrCfg handle return error; } Error startImThread() { // Create message queue auto error = IOS_CreateMessageQueue(phys_addrof(sImThreadData->messageBuffer), static_cast<uint32_t>(sImThreadData->messageBuffer.size())); if (error < Error::OK) { return error; } sImThreadData->messageQueueId = static_cast<MessageQueueId>(error); // Register /dev/im error = IOS_RegisterResourceManager("/dev/im", sImThreadData->messageQueueId); if (error < Error::OK) { return error; } // Create thread error = IOS_CreateThread(&imThreadEntry, nullptr, phys_addrof(sImThreadData->threadStack) + sImThreadData->threadStack.size(), static_cast<uint32_t>(sImThreadData->threadStack.size()), ImThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { IOS_DestroyMessageQueue(sImThreadData->messageQueueId); return error; } sImThreadData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sImThreadData->threadId, "ImThread"); return IOS_StartThread(sImThreadData->threadId); } Error stopImThread() { sImThreadData->stopMessageBuffer = Command::IpcMsg2; auto message = makeMessage(phys_addrof(sImThreadData->stopMessageBuffer)); auto error = IOS_SendMessage(sImThreadData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } error = IOS_JoinThread(sImThreadData->threadId, nullptr); if (error < Error::OK) { return error; } error = IOS_DestroyMessageQueue(sImThreadData->messageQueueId); if (error < Error::OK) { return error; } return Error::OK; } void initialiseStaticImThreadData() { sImThreadData = allocProcessStatic<StaticImThreadData>(); sDevices.closeAll(); } } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_im_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::auxil::internal { Error startImThread(); Error stopImThread(); void initialiseStaticImThreadData(); } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg.h ================================================ #pragma once #include "ios_auxil_enum.h" #include "ios_auxil_usr_cfg_request.h" #include "ios_auxil_usr_cfg_types.h" ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_device.cpp ================================================ #include "ios_auxil_config.h" #include "ios_auxil_usr_cfg_device.h" #include "ios_auxil_usr_cfg_fs.h" #include "ios_auxil_usr_cfg_service_thread.h" #include <array> #include <common/strutils.h> #include <fmt/core.h> #include <pugixml.hpp> #include <sstream> #include <string_view> namespace ios::auxil::internal { static std::array<const char *, 66> sValidRootKeys = { "AVMCfg", "DRCCfg", "NETCfg0", "NETCfg1", "NETCfg2", "NETCfg3", "NETCfg4", "NETCfg5", "NETCmn", "audio", "avm_cdc", "avmflg", "avmStat", "btStd", "cafe", "ccr", "ccr_all", "coppa", "cos", "dc_state", "im_cfg", "nn", "nn_ram", "hbprefs", "p_acct1", "p_acct10", "p_acct11", "p_acct12", "p_acct2", "p_acct3", "p_acct4", "p_acct5", "p_acct6", "p_acct7", "p_acct8", "p_acct9", "parent", "PCFSvTCP", "console", "rmtCfg", "rootflg", "spotpass", "tvecfg", "tveEDID", "tveHdmi", "uvdflag", "wii_acct", "ums", "testProc", "clipbd", "hdmiEDID", "caffeine", "DRCDev", "hai_sys", "s_acct01", "s_acct02", "s_acct03", "s_acct04", "s_acct05", "s_acct06", "s_acct07", "s_acct08", "s_acct09", "s_acct10", "s_acct11", "s_acct12", }; static bool isValidRootKey(std::string_view key) { for (auto validKey : sValidRootKeys) { if (key.compare(validKey) == 0) { return true; } } return false; } static std::string_view getFileSysPath(UCFileSys fileSys) { switch (fileSys) { case UCFileSys::Sys: return "/vol/sys/proc/prefs/"; case UCFileSys::Slc: return "/vol/sys/proc_slc/prefs/"; case UCFileSys::Ram: return "/vol/sys/proc_ram/prefs/"; default: return "*error*"; } } void UCDevice::setCloseRequest(phys_ptr<kernel::ResourceRequest> closeRequest) { mCloseRequest = closeRequest; } void UCDevice::incrementRefCount() { mRefCount++; } void UCDevice::decrementRefCount() { mRefCount--; if (mRefCount == 0) { if (mCloseRequest) { kernel::IOS_ResourceReply(mCloseRequest, Error::OK); } mCloseRequest = nullptr; destroyUCDevice(this); } } UCError UCDevice::deleteSysConfig(uint32_t numVecIn, phys_ptr<IoctlVec> vecs) { auto request = phys_cast<UCReadSysConfigRequest *>(vecs[0].paddr); auto items = phys_cast<UCItem *>(phys_addrof(request->settings[0])); if (request->count == 0) { return UCError::OK; } auto name = std::string_view { phys_addrof(request->settings[0].name).get() }; auto fileSys = getFileSys(name); if (fileSys == UCFileSys::Invalid) { return UCError::InvalidLocation; } auto rootKey = getRootKey(name); if (!isValidRootKey(rootKey)) { return UCError::FileSysName; } return deleteItems(getFileSysPath(fileSys), items, request->count); } UCError UCDevice::readSysConfig(uint32_t numVecIn, phys_ptr<IoctlVec> vecs) { auto request = phys_cast<UCReadSysConfigRequest *>(vecs[0].paddr); auto items = phys_cast<UCItem *>(phys_addrof(request->settings[0])); if (request->count == 0) { return UCError::OK; } auto name = std::string_view { phys_addrof(request->settings[0].name).get() }; auto fileSys = getFileSys(name); if (fileSys == UCFileSys::Invalid) { return UCError::InvalidLocation; } auto rootKey = getRootKey(name); if (!isValidRootKey(rootKey)) { return UCError::FileSysName; } return readItems(getFileSysPath(fileSys), items, request->count, vecs); } UCError UCDevice::writeSysConfig(uint32_t numVecIn, phys_ptr<IoctlVec> vecs) { auto request = phys_cast<UCWriteSysConfigRequest *>(vecs[0].paddr); auto items = phys_cast<UCItem *>(phys_addrof(request->settings[0])); if (request->count == 0) { return UCError::OK; } auto name = std::string_view { phys_addrof(request->settings[0].name).get() }; auto fileSys = getFileSys(name); if (fileSys == UCFileSys::Invalid) { return UCError::InvalidLocation; } auto rootKey = getRootKey(name); if (!isValidRootKey(rootKey)) { return UCError::FileSysName; } return writeItems(getFileSysPath(fileSys), items, request->count, vecs); } } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_device.h ================================================ #pragma once #include "ios_auxil_usr_cfg_types.h" #include "ios_auxil_usr_cfg_request.h" #include "ios/kernel/ios_kernel_resourcemanager.h" namespace ios::auxil::internal { class UCDevice { public: void setCloseRequest(phys_ptr<kernel::ResourceRequest> closeRequest); void incrementRefCount(); void decrementRefCount(); UCError deleteSysConfig(uint32_t numVecIn, phys_ptr<IoctlVec> vecs); UCError readSysConfig(uint32_t numVecIn, phys_ptr<IoctlVec> vecs); UCError writeSysConfig(uint32_t numVecIn, phys_ptr<IoctlVec> vecs); private: int mRefCount = 1; phys_ptr<kernel::ResourceRequest> mCloseRequest = nullptr; }; } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_fs.cpp ================================================ #include "ios_auxil_usr_cfg_fs.h" #include "ios/ios_stackobject.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/fs/ios_fs_fsa_ipc.h" using namespace ios::fs; using namespace ios::kernel; namespace ios::auxil::internal { static FSAHandle sFsaHandle; Error UCInitFSA() { auto error = FSAOpen(); if (error < Error::OK) { return Error::NoResource; } sFsaHandle = static_cast<ResourceHandleId>(error); return Error::OK; } phys_ptr<uint8_t> UCAllocFileData(uint32_t size) { auto ptr = IOS_HeapAllocAligned(CrossProcessHeapId, size, 0x40u); if (ptr) { std::memset(ptr.get(), 0, size); } return phys_cast<uint8_t *>(ptr); } void UCFreeFileData(phys_ptr<uint8_t> fileData, uint32_t size) { IOS_HeapFree(CrossProcessHeapId, fileData); } UCError UCReadConfigFile(std::string_view filename, uint32_t *outSize, phys_ptr<uint8_t> *outData) { StackObject<FSAStat> stat; FSAFileHandle fileHandle; auto status = FSAOpenFile(sFsaHandle, filename, "r", &fileHandle); if (status < FSAStatus::OK) { return UCError::FileOpen; } status = FSAStatFile(sFsaHandle, fileHandle, stat); if (status < FSAStatus::OK) { FSACloseFile(sFsaHandle, fileHandle); return UCError::FileStat; } auto fileData = UCAllocFileData(stat->size); if (!fileData) { FSACloseFile(sFsaHandle, fileHandle); return UCError::Alloc; } status = FSAReadFile(sFsaHandle, fileData, stat->size, 1, fileHandle, FSAReadFlag::None); if (status < FSAStatus::OK) { UCFreeFileData(fileData, stat->size); FSACloseFile(sFsaHandle, fileHandle); return UCError::FileRead; } status = FSACloseFile(sFsaHandle, fileHandle); if (status < FSAStatus::OK) { UCFreeFileData(fileData, stat->size); return UCError::FileClose; } *outSize = stat->size; *outData = fileData; return UCError::OK; } UCError UCWriteConfigFile(std::string_view filename, phys_ptr<uint8_t> buffer, uint32_t size) { StackObject<FSAStat> stat; FSAFileHandle fileHandle; if (!buffer) { // REMOVE } auto status = FSAOpenFile(sFsaHandle, filename, "w", &fileHandle); if (status < FSAStatus::OK) { return UCError::FileOpen; } status = FSAWriteFile(sFsaHandle, buffer, size, 1, fileHandle, FSAWriteFlag::None); if (status < FSAStatus::OK) { FSACloseFile(sFsaHandle, fileHandle); return UCError::FileWrite; } status = FSACloseFile(sFsaHandle, fileHandle); if (status < FSAStatus::OK) { return UCError::FileClose; } return UCError::OK; } } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_fs.h ================================================ #pragma once #include "ios_auxil_enum.h" #include "ios/ios_enum.h" #include <libcpu/be2_struct.h> #include <string_view> namespace ios::auxil::internal { Error UCInitFSA(); phys_ptr<uint8_t> UCAllocFileData(uint32_t size); void UCFreeFileData(phys_ptr<uint8_t> fileData, uint32_t size); UCError UCReadConfigFile(std::string_view filename, uint32_t *outBytesRead, phys_ptr<uint8_t> *outBuffer); UCError UCWriteConfigFile(std::string_view filename, phys_ptr<uint8_t> buffer, uint32_t size); } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_ipc.cpp ================================================ #include "ios_auxil_usr_cfg_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" namespace ios::auxil { using namespace kernel; using UCHandle = kernel::ResourceHandleId; static UCError allocIpcData(uint32_t size, phys_ptr<void> *outData) { auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size); if (!buffer) { return UCError::Alloc; } std::memset(buffer.get(), 0, size); *outData = buffer; return UCError::OK; } static void freeIpcData(phys_ptr<void> ipcData) { IOS_HeapFree(CrossProcessHeapId, ipcData); } Error UCOpen() { return IOS_Open("/dev/usr_cfg", OpenMode::None); } Error UCClose(UCHandle handle) { return IOS_Close(handle); } UCError UCReadSysConfig(UCHandle handle, uint32_t count, phys_ptr<UCSysConfig> settings) { phys_ptr<void> vecBuffer = nullptr; phys_ptr<void> reqBuffer = nullptr; phys_ptr<UCReadSysConfigRequest> request = nullptr; phys_ptr<IoctlVec> vecs = nullptr; auto vecBufSize = static_cast<uint32_t>((1 + count) * sizeof(IoctlVec)); auto reqBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCReadSysConfigRequest)); auto error = allocIpcData(vecBufSize, &vecBuffer); if (error < UCError::OK) { goto out; } error = allocIpcData(reqBufSize, &reqBuffer); if (error < UCError::OK) { goto out; } request = phys_cast<UCReadSysConfigRequest *>(reqBuffer); request->unk0x00 = 0u; request->count = count; std::memcpy(request->settings, settings.get(), sizeof(UCSysConfig) * count); vecs = phys_cast<IoctlVec *>(vecBuffer); vecs[0].len = reqBufSize; vecs[0].paddr = phys_cast<phys_addr>(reqBuffer); for (auto i = 0u; i < count; ++i) { auto size = settings[i].dataSize; vecs[1 + i].len = size; if (size) { phys_ptr<void> dataBuffer; error = allocIpcData(vecBufSize, &dataBuffer); if (error < UCError::OK) { goto out; } vecs[1 + i].paddr = phys_cast<phys_addr>(dataBuffer); } else { vecs[1 + i].paddr = phys_addr { 0u }; } } error = static_cast<UCError>(IOS_Ioctlv(handle, UCCommand::ReadSysConfig, 0, 1 + count, vecs)); for (auto i = 0u; i < count; ++i) { settings[i].error = request->settings[i].error; if (auto len = vecs[i + 1].len) { auto dst = phys_cast<void *>(settings[i].data); auto src = phys_cast<const void *>(vecs[i + 1].paddr); std::memcpy(dst.get(), src.get(), len); } } out: for (auto i = 0u; i < count; ++i) { if (vecs[i + 1].paddr) { freeIpcData(phys_cast<void *>(vecs[i + 1].paddr)); } } if (vecBuffer) { freeIpcData(vecBuffer); } if (reqBuffer) { freeIpcData(reqBuffer); } return error; } UCError UCWriteSysConfig(UCHandle handle, uint32_t count, phys_ptr<UCSysConfig> settings) { phys_ptr<void> vecBuffer = nullptr; phys_ptr<void> reqBuffer = nullptr; phys_ptr<UCWriteSysConfigRequest> request = nullptr; phys_ptr<IoctlVec> vecs = nullptr; auto vecBufSize = static_cast<uint32_t>((1 + count) * sizeof(IoctlVec)); auto reqBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCReadSysConfigRequest)); auto error = allocIpcData(vecBufSize, &vecBuffer); if (error < UCError::OK) { goto out; } error = allocIpcData(reqBufSize, &reqBuffer); if (error < UCError::OK) { goto out; } request = phys_cast<UCWriteSysConfigRequest *>(reqBuffer); request->unk0x00 = 0u; request->count = count; std::memcpy(request->settings, settings.get(), sizeof(UCSysConfig) * count); vecs = phys_cast<IoctlVec *>(vecBuffer); vecs[0].len = reqBufSize; vecs[0].paddr = phys_cast<phys_addr>(reqBuffer); for (auto i = 0u; i < count; ++i) { auto size = settings[i].dataSize; vecs[1 + i].len = size; if (size) { phys_ptr<void> dataBuffer; error = allocIpcData(vecBufSize, &dataBuffer); if (error < UCError::OK) { goto out; } vecs[1 + i].paddr = phys_cast<phys_addr>(dataBuffer); auto src = phys_cast<const void *>(settings[i].data); auto dst = phys_cast<void *>(vecs[i + 1].paddr); std::memcpy(dst.get(), src.get(), size); } else { vecs[1 + i].paddr = phys_addr { 0u }; } } error = static_cast<UCError>(IOS_Ioctlv(handle, UCCommand::WriteSysConfig, 0, 1 + count, vecs)); for (auto i = 0u; i < count; ++i) { settings[i].error = request->settings[i].error; } out: for (auto i = 0u; i < count; ++i) { if (vecs[i + 1].paddr) { freeIpcData(phys_cast<void *>(vecs[i + 1].paddr)); } } if (vecBuffer) { freeIpcData(vecBuffer); } if (reqBuffer) { freeIpcData(reqBuffer); } return error; } } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_ipc.h ================================================ #pragma once #include "ios_auxil_usr_cfg.h" #include "ios/kernel/ios_kernel_ipc.h" namespace ios::auxil { using UCHandle = kernel::ResourceHandleId; Error UCOpen(); Error UCClose(UCHandle handle); UCError UCReadSysConfig(UCHandle handle, uint32_t count, phys_ptr<UCSysConfig> settings); UCError UCWriteSysConfig(UCHandle handle, uint32_t count, phys_ptr<UCSysConfig> settings); } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_request.h ================================================ #pragma once #include "ios_auxil_enum.h" #include "ios_auxil_usr_cfg_types.h" #include <cstdint> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::auxil { /** * \ingroup ios_auxil * @{ */ #pragma pack(push, 1) struct UCDeleteSysConfigRequest { be2_val<uint32_t> unk0x00; be2_val<uint32_t> count; be2_struct<UCSysConfig> settings[1]; // Size=N }; CHECK_OFFSET(UCDeleteSysConfigRequest, 0x0, unk0x00); CHECK_OFFSET(UCDeleteSysConfigRequest, 0x4, count); CHECK_OFFSET(UCDeleteSysConfigRequest, 0x8, settings); CHECK_SIZE(UCDeleteSysConfigRequest, 0x5C); struct UCReadSysConfigRequest { be2_val<uint32_t> unk0x00; be2_val<uint32_t> count; be2_struct<UCSysConfig> settings[1]; // Size=N }; CHECK_OFFSET(UCReadSysConfigRequest, 0x0, unk0x00); CHECK_OFFSET(UCReadSysConfigRequest, 0x4, count); CHECK_OFFSET(UCReadSysConfigRequest, 0x8, settings); CHECK_SIZE(UCReadSysConfigRequest, 0x5C); struct UCWriteSysConfigRequest { be2_val<uint32_t> unk0x00; be2_val<uint32_t> count; be2_struct<UCSysConfig> settings[1]; // Size=N }; CHECK_OFFSET(UCWriteSysConfigRequest, 0x0, unk0x00); CHECK_OFFSET(UCWriteSysConfigRequest, 0x4, count); CHECK_OFFSET(UCWriteSysConfigRequest, 0x8, settings); CHECK_SIZE(UCWriteSysConfigRequest, 0x5C); struct UCRequest { union { be2_struct<UCDeleteSysConfigRequest> deleteSysConfigRequest; be2_struct<UCReadSysConfigRequest> readSysConfigRequest; be2_struct<UCWriteSysConfigRequest> writeSysConfigRequest; }; }; #pragma pack(pop) /** @} */ } // namespace namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_service_thread.cpp ================================================ #include "ios_auxil_usr_cfg_thread.h" #include "ios_auxil_usr_cfg_service_thread.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/ios_handlemanager.h" #include "ios/ios_stackobject.h" namespace ios::auxil::internal { using namespace kernel; constexpr auto UCServiceNumMessages = 10u; constexpr auto UCServiceThreadStackSize = 0x2000u; constexpr auto UCServiceThreadPriority = 70u; constexpr auto MaxNumDevices = 96u; struct StaticUsrCfgServiceData { be2_val<bool> serviceRunning; be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_array<Message, UCServiceNumMessages> messageBuffer; be2_array<uint8_t, UCServiceThreadStackSize> threadStack; }; static phys_ptr<StaticUsrCfgServiceData> sData = nullptr; static HandleManager<UCDevice, UCDeviceHandle, MaxNumDevices> sDevices; void destroyUCDevice(UCDevice *device) { sDevices.close(device); } Error getUCDevice(UCDeviceHandle handle, UCDevice **outDevice) { return sDevices.get(handle, outDevice); } static Error usrCfgServiceThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; while (true) { auto error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { error = sDevices.open(); IOS_ResourceReply(request, error); break; } case Command::Close: { UCDevice *device = nullptr; auto handle = static_cast<UCDeviceHandle>(request->requestData.handle); error = sDevices.get(handle, &device); if (error < Error::OK) { IOS_ResourceReply(request, error); continue; } device->setCloseRequest(request); device->decrementRefCount(); break; } case Command::Ioctlv: { UCDevice *device = nullptr; if (!sData->serviceRunning) { IOS_ResourceReply(request, Error::NotReady); continue; } auto handle = static_cast<UCDeviceHandle>(request->requestData.handle); error = sDevices.get(handle, &device); if (error < Error::OK) { IOS_ResourceReply(request, error); continue; } device->incrementRefCount(); IOS_SendMessage(getUsrCfgMessageQueueId(), *message, MessageFlags::None); break; } case Command::Suspend: { // TODO: Close FSA Handle sData->serviceRunning = false; IOS_ResourceReply(request, Error::OK); break; } case Command::Resume: { // TODO: Open FSA Handle sData->serviceRunning = true; IOS_ResourceReply(request, Error::OK); break; } default: IOS_ResourceReply(request, Error::InvalidArg); } } } Error startUsrCfgServiceThread() { // Create message queue auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); // Register the device error = mcp::MCP_RegisterResourceManager("/dev/usr_cfg", sData->messageQueueId); if (error < Error::OK) { IOS_DestroyMessageQueue(sData->messageQueueId); return error; } // Create thread error = IOS_CreateThread(&usrCfgServiceThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), static_cast<uint32_t>(sData->threadStack.size()), UCServiceThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { IOS_DestroyMessageQueue(sData->messageQueueId); return error; } sData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sData->threadId, "UsrCfgServiceThread"); return IOS_StartThread(sData->threadId); } MessageQueueId getUsrCfgServiceMessageQueueId() { return sData->messageQueueId; } void initialiseStaticUsrCfgServiceThreadData() { sData = allocProcessStatic<StaticUsrCfgServiceData>(); sDevices.closeAll(); } } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_service_thread.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios_auxil_usr_cfg_device.h" namespace ios::auxil::internal { using UCDeviceHandle = int32_t; Error startUsrCfgServiceThread(); kernel::MessageQueueId getUsrCfgServiceMessageQueueId(); Error getUCDevice(UCDeviceHandle handle, UCDevice **outDevice); void destroyUCDevice(UCDevice *device); void initialiseStaticUsrCfgServiceThreadData(); } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_thread.cpp ================================================ #include "ios_auxil_enum.h" #include "ios_auxil_usr_cfg_thread.h" #include "ios_auxil_usr_cfg_service_thread.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/ios_stackobject.h" using namespace ios::kernel; namespace ios::auxil::internal { constexpr auto UsrCfgThreadNumMessages = 10u; constexpr auto UsrCfgThreadStackSize = 0x2000u; constexpr auto UsrCfgThreadPriority = 70u; struct StaticUsrCfgThreadData { be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_array<Message, UsrCfgThreadNumMessages> messageBuffer; be2_array<uint8_t, UsrCfgThreadStackSize> threadStack; }; static phys_ptr<StaticUsrCfgThreadData> sData = nullptr; static Error usrCfgThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; while (true) { auto error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } UCDevice *device = nullptr; auto request = parseMessage<ResourceRequest>(message); auto handle = static_cast<UCDeviceHandle>(request->requestData.handle); error = getUCDevice(handle, &device); if (error >= Error::OK) { if (request->requestData.command != Command::Ioctlv) { error = Error::InvalidArg; } else { auto command = static_cast<UCCommand>(request->requestData.args.ioctlv.request); switch (command) { case UCCommand::DeleteSysConfig: error = static_cast<Error>(device->deleteSysConfig(request->requestData.args.ioctlv.numVecIn, request->requestData.args.ioctlv.vecs)); break; case UCCommand::ReadSysConfig: error = static_cast<Error>(device->readSysConfig(request->requestData.args.ioctlv.numVecIn, request->requestData.args.ioctlv.vecs)); break; case UCCommand::WriteSysConfig: error = static_cast<Error>(device->writeSysConfig(request->requestData.args.ioctlv.numVecIn, request->requestData.args.ioctlv.vecs)); break; default: error = Error::InvalidArg; } } } IOS_ResourceReply(request, error); device->decrementRefCount(); } } Error startUsrCfgThread() { // Create message queue auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); // Create thread error = IOS_CreateThread(&usrCfgThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), static_cast<uint32_t>(sData->threadStack.size()), UsrCfgThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { IOS_DestroyMessageQueue(sData->messageQueueId); return error; } sData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sData->threadId, "UsrCfgThread"); return IOS_StartThread(sData->threadId); } MessageQueueId getUsrCfgMessageQueueId() { return sData->messageQueueId; } void initialiseStaticUsrCfgThreadData() { sData = allocProcessStatic<StaticUsrCfgThreadData>(); } } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_thread.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_messagequeue.h" namespace ios::auxil::internal { Error startUsrCfgThread(); kernel::MessageQueueId getUsrCfgMessageQueueId(); void initialiseStaticUsrCfgThreadData(); } // namespace ios::auxil::internal ================================================ FILE: src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_types.h ================================================ #pragma once #include "ios_auxil_enum.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::auxil { /** * \ingroup ios_auxil * @{ */ #pragma pack(push, 1) struct UCSysConfig { be2_array<char, 64> name; be2_val<uint32_t> access; be2_val<UCDataType> dataType; be2_val<UCError> error; be2_val<uint32_t> dataSize; be2_phys_ptr<void> data; }; CHECK_OFFSET(UCSysConfig, 0x00, name); CHECK_OFFSET(UCSysConfig, 0x40, access); CHECK_OFFSET(UCSysConfig, 0x44, dataType); CHECK_OFFSET(UCSysConfig, 0x48, error); CHECK_OFFSET(UCSysConfig, 0x4C, dataSize); CHECK_OFFSET(UCSysConfig, 0x50, data); CHECK_SIZE(UCSysConfig, 0x54); #pragma pack(pop) /** @} */ } // namespace ios::auxil ================================================ FILE: src/libdecaf/src/ios/bsp/ios_bsp.cpp ================================================ #include "ios_bsp.h" #include "ios_bsp_enum.h" #include "ios_bsp_bsp_request.h" #include "ios_bsp_bsp_response.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/ios_stackobject.h" #include <common/log.h> namespace ios::bsp { using namespace kernel; constexpr auto CrossHeapSize = 0x10000u; struct StaticBspData { be2_array<Message, 0x40> messageBuffer; be2_val<MessageQueueId> messageQueue; }; static phys_ptr<StaticBspData> sBspData = nullptr; static void bspIoctl(phys_ptr<ResourceRequest> resourceRequest, BSPCommand command, be2_phys_ptr<const void> inputBuffer, be2_phys_ptr<void> outputBuffer) { auto request = phys_cast<const BSPRequest *>(inputBuffer); auto response = phys_cast<BSPResponse *>(outputBuffer); auto error = Error::OK; switch (command) { case BSPCommand::GetHardwareVersion: response->getHardwareVersion.hardwareVersion = HardwareVersion::LATTE_B1X_CAFE; break; default: error = Error::Invalid; } IOS_ResourceReply(resourceRequest, error); } static void initialiseStaticBspData() { sBspData = allocProcessStatic<StaticBspData>(); } Error processEntryPoint(phys_ptr<void> /* context */) { // Startup bsp process initialiseStaticBspData(); auto error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { gLog->error("BSP: Failed to create cross process heap, error = {}.", error); return error; } // Initialise /dev/bsp error = IOS_CreateMessageQueue(phys_addrof(sBspData->messageBuffer), static_cast<uint32_t>(sBspData->messageBuffer.size())); if (error < Error::OK) { gLog->error("BSP: Failed to create message queue, error = {}.", error); return error; } sBspData->messageQueue = static_cast<MessageQueueId>(error); error = IOS_RegisterResourceManager("/dev/bsp", sBspData->messageQueue); if (error < Error::OK) { return error; } error = IOS_AssociateResourceManager("/dev/bsp", ResourcePermissionGroup::BSP); if (error < Error::OK) { return error; } IOS_SetBspReady(); while (true) { StackObject<Message> message; error = IOS_ReceiveMessage(sBspData->messageQueue, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { IOS_ResourceReply(request, static_cast<Error>(1)); break; } case Command::Close: { IOS_ResourceReply(request, Error::OK); break; } case Command::Ioctl: { bspIoctl(request, static_cast<BSPCommand>(request->requestData.args.ioctl.request), request->requestData.args.ioctl.inputBuffer, request->requestData.args.ioctl.outputBuffer); break; } default: IOS_ResourceReply(request, Error::Invalid); } } return Error::OK; } } // namespace ios::bsp ================================================ FILE: src/libdecaf/src/ios/bsp/ios_bsp.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::bsp { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::bsp ================================================ FILE: src/libdecaf/src/ios/bsp/ios_bsp_bsp_request.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace ios::bsp { /** * \ingroup ios_dev_bsp * @{ */ #pragma pack(push, 1) struct BSPRequest { UNKNOWN(0x48); }; CHECK_SIZE(BSPRequest, 0x48); #pragma pack(pop) /** @} */ } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/bsp/ios_bsp_bsp_response.h ================================================ #pragma once #include "ios_bsp_enum.h" #include <libcpu/be2_struct.h> namespace ios::bsp { /** * \ingroup ios_dev_bsp * @{ */ #pragma pack(push, 1) struct BSPResponseGetHardwareVersion { be2_val<HardwareVersion> hardwareVersion; }; CHECK_OFFSET(BSPResponseGetHardwareVersion, 0x00, hardwareVersion); CHECK_SIZE(BSPResponseGetHardwareVersion, 0x04); struct BSPResponse { union { be2_struct<BSPResponseGetHardwareVersion> getHardwareVersion; UNKNOWN(0x200); }; }; CHECK_SIZE(BSPResponse, 0x200); #pragma pack(pop) /** @} */ } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/bsp/ios_bsp_enum.h ================================================ #ifndef IOS_BSP_ENUM_H #define IOS_BSP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(bsp) ENUM_BEG(BSPCommand, uint32_t) ENUM_VALUE(GetHardwareVersion, 2) ENUM_END(BSPCommand) ENUM_BEG(HardwareVersion, uint32_t) ENUM_VALUE(Unknown, 0x00000000) // vWii Hardware Versions ENUM_VALUE(HOLLYWOOD_ENG_SAMPLE_1, 0x00000001) ENUM_VALUE(HOLLYWOOD_ENG_SAMPLE_2, 0x10000001) ENUM_VALUE(HOLLYWOOD_PROD_FOR_WII, 0x10100001) ENUM_VALUE(HOLLYWOOD_CORTADO, 0x10100008) ENUM_VALUE(HOLLYWOOD_CORTADO_ESPRESSO, 0x1010000C) ENUM_VALUE(BOLLYWOOD, 0x20000001) ENUM_VALUE(BOLLYWOOD_PROD_FOR_WII, 0x20100001) // WiiU Hardware Versions ENUM_VALUE(LATTE_A11_EV, 0x21100010) ENUM_VALUE(LATTE_A11_CAT, 0x21100020) ENUM_VALUE(LATTE_A12_EV, 0x21200010) ENUM_VALUE(LATTE_A12_CAT, 0x21200020) ENUM_VALUE(LATTE_A2X_EV, 0x22100010) ENUM_VALUE(LATTE_A2X_CAT, 0x22100020) ENUM_VALUE(LATTE_A3X_EV, 0x23100010) ENUM_VALUE(LATTE_A3X_CAT, 0x23100020) ENUM_VALUE(LATTE_A3X_CAFE, 0x23100028) ENUM_VALUE(LATTE_A4X_EV, 0x24100010) ENUM_VALUE(LATTE_A4X_CAT, 0x24100020) ENUM_VALUE(LATTE_A4X_CAFE, 0x24100028) ENUM_VALUE(LATTE_A5X_EV, 0x25100010) ENUM_VALUE(LATTE_A5X_EV_Y, 0x25100011) ENUM_VALUE(LATTE_A5X_CAT, 0x25100020) ENUM_VALUE(LATTE_A5X_CAFE, 0x25100028) ENUM_VALUE(LATTE_B1X_EV, 0x26100010) ENUM_VALUE(LATTE_B1X_EV_Y, 0x26100011) ENUM_VALUE(LATTE_B1X_CAT, 0x26100020) ENUM_VALUE(LATTE_B1X_CAFE, 0x26100028) ENUM_END(HardwareVersion) ENUM_NAMESPACE_EXIT(bsp) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_BSP_ENUM_H ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto.cpp ================================================ #include "ios_crypto.h" #include "ios_crypto_enum.h" #include "ios_crypto_log.h" #include "ios_crypto_request.h" #include "decaf_log.h" #include "ios/ios_stackobject.h" #include "ios/kernel/ios_kernel_debug.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_otp.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/mcp/ios_mcp_ipc.h" #include <gsl/gsl-lite.hpp> #include <openssl/err.h> #include <openssl/evp.h> using namespace ios::kernel; using namespace ios::mcp; namespace ios::crypto { constexpr auto kCryptoHeapSize = 0x1000u; constexpr auto kCrossHeapSize = 0x10000u; constexpr auto kMainThreadNumMessages = 260u; struct CryptoDevice { be2_val<BOOL> used = FALSE; be2_val<ProcessId> processId; }; struct OtpKeys { be2_val<uint32_t> ngId; be2_array<uint8_t, OtpFieldSize::CommonKey> commonKey; be2_array<uint8_t, OtpFieldSize::DrhWlanKey> drhWlanKey; be2_array<uint8_t, OtpFieldSize::MlcKey> mlcKey; be2_array<uint8_t, OtpFieldSize::NgPrivateKey> ngPrivateKey; be2_array<uint8_t, OtpFieldSize::NssPrivateKey> nssPrivateKey; be2_array<uint8_t, OtpFieldSize::RngKey> rngKey; be2_array<uint8_t, OtpFieldSize::SeepromKey> seepromKey; be2_array<uint8_t, OtpFieldSize::SlcHmac> slcHmac; be2_array<uint8_t, OtpFieldSize::SlcKey> slcKey; be2_array<uint8_t, OtpFieldSize::SslRsaKey> sslRsaKey; be2_array<uint8_t, OtpFieldSize::StarbuckAncastKey> starbuckAncastKey; be2_array<uint8_t, OtpFieldSize::XorKey> xorKey; be2_array<uint8_t, OtpFieldSize::Unknown0x50> unknown0x50; be2_array<uint8_t, OtpFieldSize::WiiCommonKey> wiiCommonKey; be2_array<uint8_t, OtpFieldSize::WiiKoreanKey> wiiKoreanKey; be2_array<uint8_t, OtpFieldSize::WiiNandHmac> wiiNandHmac; be2_array<uint8_t, OtpFieldSize::WiiNandKey> wiiNandKey; be2_array<uint8_t, OtpFieldSize::WiiNssPrivateKey> wiiNssPrivateKey; be2_array<uint8_t, OtpFieldSize::VwiiCommonKey> vwiiCommonKey; }; struct KeyData { be2_val<BOOL> used = FALSE; be2_array<uint8_t, 32> data; be2_phys_ptr<KeyData> next = nullptr; }; struct KeyHandle { be2_val<BOOL> used = FALSE; be2_val<ObjectType> objectType; be2_val<SubObjectType> subObjectType; be2_phys_ptr<KeyData> data; be2_val<uint32_t> permission; be2_val<uint32_t> idData; }; struct StaticCryptoData { be2_val<BOOL> initialised = FALSE; be2_val<HeapId> cryptoHeapId = -1; be2_val<MessageQueueId> messageQueueId; be2_array<Message, kMainThreadNumMessages> messages; be2_array<CryptoDevice, 32> handles; be2_struct<OtpKeys> otpKeys; be2_array<KeyData, 256> keyDataList; be2_array<KeyHandle, 128> keyHandleList; }; static phys_ptr<StaticCryptoData> sCryptoData = nullptr; static phys_ptr<void> sCryptoHeap = nullptr; namespace internal { Logger cryptoLog = { }; static void initialiseStaticData() { sCryptoData = allocProcessStatic<StaticCryptoData>(); sCryptoHeap = allocProcessLocalHeap(kCryptoHeapSize); } static phys_ptr<CryptoDevice> getCryptoDevice(uint32_t handle) { if (handle < 0 || handle >= sCryptoData->handles.size()) { return nullptr; } if (!sCryptoData->handles[handle].used) { return nullptr; } return phys_addrof(sCryptoData->handles[handle]); } static phys_ptr<KeyHandle> getCryptoKey(uint32_t handle) { if (handle < 0 || handle >= sCryptoData->keyHandleList.size()) { return nullptr; } if (!sCryptoData->keyHandleList[handle].used) { return nullptr; } return phys_addrof(sCryptoData->keyHandleList[handle]); } static phys_ptr<KeyData> allocateKeyData(uint32_t size) { phys_ptr<KeyData> firstData = nullptr; phys_ptr<KeyData> prevData = nullptr; for (auto i = 0u; size > 0 && i < sCryptoData->keyDataList.size(); ++i) { if (sCryptoData->keyDataList[i].used) { continue; } if (size < sCryptoData->keyDataList[i].data.size()) { size = 0; } else { size -= sCryptoData->keyDataList[i].data.size(); } sCryptoData->keyDataList[i].used = TRUE; if (prevData) { prevData->next = phys_addrof(sCryptoData->keyDataList[i]); } prevData = phys_addrof(sCryptoData->keyDataList[i]); if (!firstData) { firstData = prevData; } } return firstData; } static Error getKeyObjectSize(ObjectType objectType, SubObjectType subObjectType, uint32_t *outKeySize) { struct ObjectSize { ObjectType objectType; SubObjectType subObjectType; uint32_t size; }; static const ObjectSize kObjectSizes[] = { { ObjectType::SecretKey, SubObjectType::Aes128, 16 }, { ObjectType::SecretKey, SubObjectType::Mac, 20 }, { ObjectType::SecretKey, SubObjectType::Ecc233, 30 }, { ObjectType::SecretKey, SubObjectType::Unknown0x7, 64 }, { ObjectType::PublicKey, SubObjectType::Rsa2048, 256 }, { ObjectType::PublicKey, SubObjectType::Rsa4096, 512 }, { ObjectType::PublicKey, SubObjectType::Ecc233, 60 }, { ObjectType::Unknown0x2, SubObjectType::Ecc233, 90 }, { ObjectType::Data, SubObjectType::Data, 0 }, { ObjectType::Data, SubObjectType::Version, 0 }, }; for (const auto &objectSize : kObjectSizes) { if (objectSize.objectType == objectType && objectSize.subObjectType == subObjectType) { *outKeySize = objectSize.size; return Error::OK; } } return static_cast<Error>(-2005); } static Error registerSystemKey(KeyId keyId, ObjectType objectType, SubObjectType subObjectType, phys_ptr<void> data, uint32_t size, phys_ptr<uint32_t> idData) { Error error; uint32_t expectedKeySize = 0; error = getKeyObjectSize(objectType, subObjectType, &expectedKeySize); if (error != Error::OK) { return error; } if (size != expectedKeySize) { return static_cast<Error>(-2014); } phys_ptr<KeyData> keyData = allocateKeyData(size); if (size > 0 && !keyData) { return static_cast<Error>(-2013); } phys_ptr<KeyHandle> keyHandle = phys_addrof(sCryptoData->keyHandleList[static_cast<uint32_t>(keyId)]); if (keyHandle->used) { return static_cast<Error>(-2001); } keyHandle->used = TRUE; keyHandle->objectType = objectType; keyHandle->subObjectType = subObjectType; keyHandle->data = keyData; keyHandle->permission = 0u; keyHandle->idData = idData ? static_cast<uint32_t>(*idData) : 0u; uint32_t bytesCopied = 0; while (bytesCopied < size) { uint32_t copySize = std::min(size - bytesCopied, keyData->data.size()); memcpy(std::addressof(keyData->data), static_cast<uint8_t *>(data.get()) + bytesCopied, copySize); bytesCopied += copySize; keyData = keyData->next; } return Error::OK; } static Error setSystemKeyPermission(KeyId keyId, uint32_t permission) { phys_ptr<KeyHandle> keyHandle = phys_addrof(sCryptoData->keyHandleList[static_cast<uint32_t>(keyId)]); if (!keyHandle->used) { return static_cast<Error>(-2002); } keyHandle->permission = permission; return Error::OK; } static Error readOtpKeys() { StackArray<uint8_t, 64> buffer; IOS_ReadOTP(OtpFieldIndex::NgId, phys_addrof(sCryptoData->otpKeys.ngId), sizeof(sCryptoData->otpKeys.ngId)); IOS_ReadOTP(OtpFieldIndex::NgPrivateKey, phys_addrof(sCryptoData->otpKeys.ngPrivateKey), sizeof(sCryptoData->otpKeys.ngPrivateKey)); IOS_ReadOTP(OtpFieldIndex::NssPrivateKey, phys_addrof(sCryptoData->otpKeys.nssPrivateKey), sizeof(sCryptoData->otpKeys.nssPrivateKey)); IOS_ReadOTP(OtpFieldIndex::WiiNssPrivateKey, phys_addrof(sCryptoData->otpKeys.wiiNssPrivateKey), sizeof(sCryptoData->otpKeys.wiiNssPrivateKey)); IOS_ReadOTP(OtpFieldIndex::WiiCommonKey, phys_addrof(sCryptoData->otpKeys.wiiCommonKey), sizeof(sCryptoData->otpKeys.wiiCommonKey)); if (sCryptoData->otpKeys.ngId != 0) { IOS_ReadOTP(OtpFieldIndex::WiiNandHmac, phys_addrof(sCryptoData->otpKeys.wiiNandHmac), sizeof(sCryptoData->otpKeys.wiiNandHmac)); IOS_ReadOTP(OtpFieldIndex::WiiNandKey, phys_addrof(sCryptoData->otpKeys.wiiNandKey), sizeof(sCryptoData->otpKeys.wiiNandKey)); } IOS_ReadOTP(OtpFieldIndex::SlcKey, phys_addrof(sCryptoData->otpKeys.slcKey), sizeof(sCryptoData->otpKeys.slcKey)); IOS_ReadOTP(OtpFieldIndex::SlcHmac, phys_addrof(sCryptoData->otpKeys.slcHmac), sizeof(sCryptoData->otpKeys.slcHmac)); IOS_ReadOTP(OtpFieldIndex::RngKey, phys_addrof(sCryptoData->otpKeys.rngKey), sizeof(sCryptoData->otpKeys.rngKey)); IOS_ReadOTP(OtpFieldIndex::StarbuckAncastKey, phys_addrof(sCryptoData->otpKeys.starbuckAncastKey), sizeof(sCryptoData->otpKeys.starbuckAncastKey)); IOS_ReadOTP(OtpFieldIndex::SeepromKey, phys_addrof(sCryptoData->otpKeys.seepromKey), sizeof(sCryptoData->otpKeys.seepromKey)); IOS_ReadOTP(OtpFieldIndex::MlcKey, phys_addrof(sCryptoData->otpKeys.mlcKey), sizeof(sCryptoData->otpKeys.mlcKey)); IOS_ReadOTP(OtpFieldIndex::DrhWlanKey, phys_addrof(sCryptoData->otpKeys.drhWlanKey), sizeof(sCryptoData->otpKeys.drhWlanKey)); IOS_ReadOTP(OtpFieldIndex::VwiiCommonKey, phys_addrof(sCryptoData->otpKeys.vwiiCommonKey), sizeof(sCryptoData->otpKeys.vwiiCommonKey)); IOS_ReadOTP(OtpFieldIndex::CommonKey, phys_addrof(sCryptoData->otpKeys.commonKey), sizeof(sCryptoData->otpKeys.commonKey)); IOS_ReadOTP(OtpFieldIndex::WiiKoreanKey, phys_addrof(sCryptoData->otpKeys.wiiKoreanKey), sizeof(sCryptoData->otpKeys.wiiKoreanKey)); IOS_ReadOTP(OtpFieldIndex::SslRsaKey, phys_addrof(sCryptoData->otpKeys.sslRsaKey), sizeof(sCryptoData->otpKeys.sslRsaKey)); IOS_ReadOTP(OtpFieldIndex::NssPrivateKey, phys_addrof(sCryptoData->otpKeys.nssPrivateKey), sizeof(sCryptoData->otpKeys.nssPrivateKey)); IOS_ReadOTP(OtpFieldIndex::XorKey, phys_addrof(sCryptoData->otpKeys.xorKey), sizeof(sCryptoData->otpKeys.xorKey)); IOS_ReadOTP(OtpFieldIndex::Unknown0x50, phys_addrof(sCryptoData->otpKeys.unknown0x50), sizeof(sCryptoData->otpKeys.unknown0x50)); registerSystemKey(KeyId::NgId, ObjectType::Data, SubObjectType::Data, nullptr, 0, phys_addrof(sCryptoData->otpKeys.ngId)); registerSystemKey(KeyId::NgPrivateKey, ObjectType::SecretKey, SubObjectType::Ecc233, phys_addrof(sCryptoData->otpKeys.ngPrivateKey), 30, nullptr); registerSystemKey(KeyId::NssPrivateKey, ObjectType::SecretKey, SubObjectType::Ecc233, phys_addrof(sCryptoData->otpKeys.nssPrivateKey), 30, nullptr); registerSystemKey(KeyId::WiiNssPrivateKey, ObjectType::SecretKey, SubObjectType::Ecc233, phys_addrof(sCryptoData->otpKeys.wiiNssPrivateKey), 30, nullptr); registerSystemKey(KeyId::SlcKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.slcKey), 16, nullptr); registerSystemKey(KeyId::SlcHmac, ObjectType::SecretKey, SubObjectType::Mac, phys_addrof(sCryptoData->otpKeys.slcHmac), 20, nullptr); registerSystemKey(KeyId::WiiCommonKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.wiiCommonKey), 16, nullptr); registerSystemKey(KeyId::RngKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.rngKey), 16, nullptr); static std::array<uint8_t, 16> wiiSdKey = { 0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08, 0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d, }; memcpy(buffer.get(), wiiSdKey.data(), wiiSdKey.size()); registerSystemKey(KeyId::WiiSdKey, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); registerSystemKey(KeyId::WiiKoreanKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.wiiKoreanKey), 16, nullptr); registerSystemKey(KeyId::StarbuckAncastKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.starbuckAncastKey), 16, nullptr); registerSystemKey(KeyId::CommonKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.commonKey), 16, nullptr); registerSystemKey(KeyId::VwiiCommonKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.vwiiCommonKey), 16, nullptr); registerSystemKey(KeyId::WiiNandKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.wiiNandKey), 16, nullptr); registerSystemKey(KeyId::WiiNandHmac, ObjectType::SecretKey, SubObjectType::Mac, phys_addrof(sCryptoData->otpKeys.wiiNandHmac), 20, nullptr); /* registerSystemKey(KeyId::StarbuckAncastModulus, ObjectType::PublicKey, SubObjectType::Rsa2048, data, 0x100, id); registerSystemKey(KeyId::Boot1AncastModulus, ObjectType::PublicKey, SubObjectType::Rsa2048, data, 0x100, id); */ registerSystemKey(KeyId::SeepromKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.seepromKey), 16, nullptr); registerSystemKey(KeyId::MlcKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.mlcKey), 16, nullptr); registerSystemKey(KeyId::DrhWlanKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.drhWlanKey), 16, nullptr); registerSystemKey(KeyId::Unknown0x1A, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.unknown0x50), 16, nullptr); registerSystemKey(KeyId::SslRsaKey, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.sslRsaKey), 16, nullptr); registerSystemKey(KeyId::NssPrivateKey2, ObjectType::SecretKey, SubObjectType::Aes128, phys_addrof(sCryptoData->otpKeys.nssPrivateKey), 16, nullptr); static std::array<uint8_t, 16> spotpassXorKey = { 0x33, 0xAC, 0x6D, 0x15, 0xC2, 0x26, 0x0A, 0x91, 0x3B, 0xBF, 0x73, 0xC3, 0x55, 0xD8, 0x66, 0x04, }; for (size_t i = 0u; i < spotpassXorKey.size(); ++i) { buffer[i] = spotpassXorKey[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::SpotPassKey, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); static std::array<uint8_t, 64> spotpassXorUnknown0x20 = { 0x7F, 0xA5, 0x48, 0x15, 0xD3, 0x6E, 0x4E, 0xBC, 0x38, 0x87, 0x6A, 0x82, 0x16, 0xF2, 0x59, 0x24, 0x39, 0x98, 0x0F, 0x13, 0xD8, 0x26, 0x0E, 0xA7, 0x3B, 0x94, 0x42, 0xCB, 0x41, 0xFB, 0x7C, 0x3C, 0x45, 0xE9, 0x6F, 0x12, 0xAA, 0x39, 0x5D, 0x9C, 0x42, 0x9A, 0x47, 0xC6, 0x49, 0xF7, 0x61, 0x25, 0x69, 0x9F, 0x0E, 0x62, 0xE3, 0x6E, 0x06, 0xF8, 0x59, 0xA6, 0x4C, 0xB0, 0x41, 0xCF, 0x26, 0x48, }; for (size_t i = 0u; i < spotpassXorUnknown0x20.size(); ++i) { buffer[i] = spotpassXorUnknown0x20[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::SpotPassUnknown0x20, ObjectType::SecretKey, SubObjectType::Unknown0x7, buffer, 64, nullptr); // This key is 64 bytes with first 32 bytes of data and last 32 bytes zero static std::array<uint8_t, 32> xorUnknown0x21 = { 0x24, 0xF0, 0x31, 0xEE, 0x47, 0x4B, 0xCE, 0x34, 0x80, 0x18, 0x1B, 0x0F, 0x11, 0xDB, 0xE5, 0xC6, 0x69, 0x16, 0x77, 0xE8, 0x89, 0x89, 0x2E, 0x62, 0x61, 0xBE, 0xE4, 0xDC, 0x46, 0xD7, 0x3C, 0x30, }; for (size_t i = 0u; i < xorUnknown0x21.size(); ++i) { buffer[i] = xorUnknown0x21[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } memset(buffer.get() + 32, 0, 32); registerSystemKey(KeyId::Unknown0x21, ObjectType::SecretKey, SubObjectType::Unknown0x7, buffer, 64, nullptr); setSystemKeyPermission(KeyId::NgId, 0xfffffff); setSystemKeyPermission(KeyId::NgPrivateKey, 2); setSystemKeyPermission(KeyId::NssPrivateKey, 0x200); setSystemKeyPermission(KeyId::WiiNssPrivateKey, 0x200); setSystemKeyPermission(KeyId::SlcKey, 0x20); setSystemKeyPermission(KeyId::SlcHmac, 0x20); setSystemKeyPermission(KeyId::WiiCommonKey, 2); setSystemKeyPermission(KeyId::RngKey, 2); setSystemKeyPermission(KeyId::WiiSdKey, 0xfffffff); setSystemKeyPermission(KeyId::WiiKoreanKey, 2); setSystemKeyPermission(KeyId::CommonKey, 2); setSystemKeyPermission(KeyId::VwiiCommonKey, 2); setSystemKeyPermission(KeyId::WiiNandKey, 0x20); setSystemKeyPermission(KeyId::WiiNandHmac, 0x20); setSystemKeyPermission(KeyId::StarbuckAncastKey, 2); setSystemKeyPermission(KeyId::StarbuckAncastModulus, 2); setSystemKeyPermission(KeyId::Boot1AncastModulus, 2); setSystemKeyPermission(KeyId::SeepromKey, 2); setSystemKeyPermission(KeyId::MlcKey, 0x20); setSystemKeyPermission(KeyId::DrhWlanKey, 0x40); setSystemKeyPermission(KeyId::Unknown0x1A, 0x20); setSystemKeyPermission(KeyId::SslRsaKey, 0x200); setSystemKeyPermission(KeyId::NssPrivateKey2, 0x200); static std::array<uint8_t, 16> udsLocalWlanCcmpKey = { 0x34, 0x0a, 0xcf, 0xef, 0xb6, 0x95, 0x42, 0x9d, 0x69, 0xae, 0x09, 0x44, 0xec, 0x37, 0x38, 0x7d, }; for (size_t i = 0u; i < udsLocalWlanCcmpKey.size(); ++i) { buffer[i] = udsLocalWlanCcmpKey[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::UdsLocalWlanCcmpKey, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); static std::array<uint8_t, 16> dlpKey = { 0x83, 0x7b, 0xe4, 0x1b, 0xcc, 0xb6, 0x5e, 0xaa, 0x77, 0x29, 0x4c, 0x7f, 0x1e, 0xee, 0xef, 0xdc, }; for (size_t i = 0u; i < dlpKey.size(); ++i) { buffer[i] = dlpKey[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::DlpKey, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); static std::array<uint8_t, 16> aptWrapKey = { 0x53, 0x20, 0xbb, 0x5e, 0xfe, 0x10, 0xd4, 0xa8, 0x9c, 0xca, 0x72, 0xd3, 0xcd, 0x3f, 0xd8, 0x22, }; for (size_t i = 0u; i < aptWrapKey.size(); ++i) { buffer[i] = aptWrapKey[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::AptWrapKey, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); setSystemKeyPermission(KeyId::AptWrapKey, 0xfffffff); setSystemKeyPermission(KeyId::SpotPassKey, 0x800); setSystemKeyPermission(KeyId::SpotPassUnknown0x20, 0x800); setSystemKeyPermission(KeyId::Unknown0x21, 0x800); static std::array<uint8_t, 16> pushmoreKey = { 0x3b, 0x66, 0x3d, 0x64, 0x3d, 0xd5, 0x3f, 0xc6, 0x7b, 0xc4, 0xb7, 0x39, 0x8e, 0x23, 0x80, 0x92, }; for (size_t i = 0u; i < pushmoreKey.size(); ++i) { buffer[i] = pushmoreKey[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::PushmoreKey, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); setSystemKeyPermission(KeyId::PushmoreKey, 0x800); // This key is 64 bytes with first 16 bytes of data and last 48 bytes zero static std::array<uint8_t, 16> unknown0x23 = { 0x17, 0xca, 0x71, 0x17, 0xc1, 0x24, 0x9b, 0x9e, 0x24, 0x47, 0x14, 0x97, 0x92, 0x21, 0xd4, 0x75, }; for (size_t i = 0u; i < unknown0x23.size(); ++i) { buffer[i] = unknown0x23[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } memset(buffer.get() + 16, 0, 48); registerSystemKey(KeyId::Unknown0x23, ObjectType::SecretKey, SubObjectType::Unknown0x7, buffer, 64, nullptr); setSystemKeyPermission(KeyId::Unknown0x23, 0x40); // This key is 64 bytes with first 16 bytes of data and last 48 bytes zero static std::array<uint8_t, 16> unknown0x22 = { 0x75, 0xa9, 0x17, 0x08, 0xe9, 0xf4, 0x3e, 0xde, 0xf2, 0x06, 0x55, 0xf6, 0x51, 0x12, 0x5d, 0x1d, }; for (size_t i = 0u; i < unknown0x22.size(); ++i) { buffer[i] = unknown0x22[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } memset(buffer.get() + 16, 0, 48); registerSystemKey(KeyId::Unknown0x22, ObjectType::SecretKey, SubObjectType::Unknown0x7, buffer, 64, nullptr); setSystemKeyPermission(KeyId::Unknown0x22, 0x40); static std::array<uint8_t, 16> unknown0x24 = { 0xcb, 0xf7, 0x3d, 0x30, 0x4d, 0x7a, 0xd5, 0x94, 0x4f, 0xb7, 0xbe, 0xb0, 0xc7, 0x48, 0xc4, 0x54, }; for (size_t i = 0u; i < unknown0x24.size(); ++i) { buffer[i] = unknown0x24[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::Unknown0x24, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); setSystemKeyPermission(KeyId::Unknown0x24, 0x40); static std::array<uint8_t, 16> unknown0x25 = { 0x5f, 0x02, 0x0b, 0x8e, 0x5b, 0x27, 0x0d, 0xee, 0xe7, 0xc1, 0xb3, 0x49, 0xd8, 0xa7, 0x13, 0x0b, }; for (size_t i = 0u; i < unknown0x25.size(); ++i) { buffer[i] = unknown0x25[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::Unknown0x25, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); setSystemKeyPermission(KeyId::Unknown0x25, 0x40); static std::array<uint8_t, 16> unknown0x26 = { 0x2c, 0x77, 0x3e, 0x7a, 0xbd, 0xe9, 0xea, 0x54, 0x69, 0x46, 0x76, 0x3b, 0xe8, 0x09, 0x89, 0xda, }; for (size_t i = 0u; i < unknown0x26.size(); ++i) { buffer[i] = unknown0x26[i] ^ sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()]; } registerSystemKey(KeyId::Unknown0x26, ObjectType::SecretKey, SubObjectType::Aes128, buffer, 16, nullptr); setSystemKeyPermission(KeyId::Unknown0x26, 0x20); return Error::OK; } static Error initialiseCrypto() { if (sCryptoData->initialised) { return Error::OK; } Error error = IOS_CreateHeap(sCryptoHeap, kCryptoHeapSize); if (error < Error::OK) { internal::cryptoLog->error( "initialiseCrypto: Failed to create crypto, error = {}.", error); return error; } sCryptoData->cryptoHeapId = static_cast<HeapId>(error); internal::cryptoLog->info("IOSC Initialize"); readOtpKeys(); sCryptoData->initialised = TRUE; return Error::OK; } static IOSCError ioscDecrypt(phys_ptr<ResourceRequest> resourceRequest, phys_ptr<IOSCRequestDecrypt> decryptRequest, phys_ptr<void> iv, uint32_t ivSize, phys_ptr<void> input, uint32_t inputSize, phys_ptr<void> output, uint32_t outputSize) { auto cryptoDevice = getCryptoDevice(resourceRequest->requestData.handle); if (!cryptoDevice) { return static_cast<IOSCError>(Error::Invalid); } if (cryptoDevice->processId > ProcessId::COSKERNEL) { return IOSCError::InvalidParam; } auto cryptoKey = getCryptoKey(decryptRequest->keyHandle); if (!cryptoKey) { return static_cast<IOSCError>(Error::Invalid); } if (!(cryptoKey->permission & (1 << static_cast<int>(cryptoDevice->processId)))) { return IOSCError::Permission; } EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); auto _ = gsl::finally([&]() { EVP_CIPHER_CTX_free(ctx); }); if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, reinterpret_cast<unsigned char *>(cryptoKey->data.get()), reinterpret_cast<unsigned char *>(iv.get()))) { return IOSCError::CryptoError; } EVP_CIPHER_CTX_set_key_length(ctx, 16); EVP_CIPHER_CTX_set_padding(ctx, 0); int updateLength = static_cast<int>(0); int finalLength = static_cast<int>(0); if (!EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(output.get()), &updateLength, reinterpret_cast<unsigned char *>(input.get()), static_cast<int>(inputSize))) { return IOSCError::CryptoError; } if (!EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(output.get()) + updateLength, &finalLength)) { return IOSCError::CryptoError; } return IOSCError::OK; } static Error cryptoIoctlv(phys_ptr<ResourceRequest> resourceRequest) { auto error = Error::OK; switch (static_cast<IOSCCommand>(resourceRequest->requestData.args.ioctlv.request)) { case IOSCCommand::Decrypt: { if (resourceRequest->requestData.args.ioctlv.numVecIn != 3) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.numVecOut != 1) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[0].paddr || resourceRequest->requestData.args.ioctlv.vecs[0].len != sizeof(IOSCRequestDecrypt)) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[1].paddr || resourceRequest->requestData.args.ioctlv.vecs[1].len != 16) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[2].paddr || !resourceRequest->requestData.args.ioctlv.vecs[2].len) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[3].paddr || !resourceRequest->requestData.args.ioctlv.vecs[3].len) { return Error::InvalidArg; } auto decryptRequest = phys_cast<IOSCRequestDecrypt *>( resourceRequest->requestData.args.ioctlv.vecs[0].paddr); error = static_cast<Error>( ioscDecrypt(resourceRequest, decryptRequest, phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr), resourceRequest->requestData.args.ioctlv.vecs[1].len, phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr), resourceRequest->requestData.args.ioctlv.vecs[2].len, phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr), resourceRequest->requestData.args.ioctlv.vecs[3].len)); break; } default: error = Error::InvalidArg; } return error; } } // namespace internal Error processEntryPoint(phys_ptr<void> /* context */) { StackObject<Message> message; // Initialise process static data internal::initialiseStaticData(); // Initialise logger internal::cryptoLog = decaf::makeLogger("IOS_CRYPTO"); // Initialise process heaps auto error = IOS_CreateCrossProcessHeap(kCrossHeapSize); if (error < Error::OK) { internal::cryptoLog->error( "processEntryPoint: Failed to create cross process heap, error = {}.", error); return error; } // Setup /dev/crypto error = IOS_CreateMessageQueue(phys_addrof(sCryptoData->messages), sCryptoData->messages.size()); if (error < Error::OK) { internal::cryptoLog->error( "processEntryPoint: IOS_CreateMessageQueue failed with error = {}", error); return error; } sCryptoData->messageQueueId = static_cast<MessageQueueId>(error); error = MCP_RegisterResourceManager("/dev/crypto", sCryptoData->messageQueueId); if (error < Error::OK) { internal::cryptoLog->error( "processEntryPoint: MCP_RegisterResourceManager failed for /dev/crypto with error = {}", error); return error; } // Run /dev/crypto while (true) { error = IOS_ReceiveMessage(sCryptoData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { internal::cryptoLog->error( "processEntryPoint: IOS_ReceiveMessage failed with error = {}", error); break; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: error = Error::NoResource; for (auto i = 0u; i < sCryptoData->handles.size(); ++i) { if (!sCryptoData->handles[i].used) { sCryptoData->handles[i].used = TRUE; sCryptoData->handles[i].processId = request->requestData.processId; error = static_cast<Error>(i); break; } } IOS_ResourceReply(request, error); break; case Command::Close: if (request->requestData.handle < static_cast<Handle>(sCryptoData->handles.size())) { sCryptoData->handles[request->requestData.handle].used = FALSE; } IOS_ResourceReply(request, Error::OK); break; case Command::Suspend: IOS_ResourceReply(request, Error::OK); break; case Command::Resume: internal::initialiseCrypto(); IOS_ResourceReply(request, Error::OK); break; case Command::Ioctlv: IOS_ResourceReply(request, internal::cryptoIoctlv(request)); break; default: IOS_ResourceReply(request, Error::Invalid); } } return Error::OK; } } // namespace ios::crypto ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::crypto { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::crypto ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto_enum.h ================================================ #ifndef IOS_CRYPTO_ENUM_H #define IOS_CRYPTO_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(crypto) ENUM_BEG(IOSCError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Permission, -2000) ENUM_VALUE(InvalidParam, -2002) ENUM_VALUE(CryptoError, -2012) ENUM_END(IOSCError) ENUM_BEG(IOSCCommand, uint32_t) ENUM_VALUE(Decrypt, 0xE) ENUM_END(IOSCCommand) ENUM_BEG(KeyId, uint32_t) ENUM_VALUE(NgPrivateKey, 0x0) ENUM_VALUE(NgId, 0x1) ENUM_VALUE(SlcKey, 0x2) ENUM_VALUE(SlcHmac, 0x3) ENUM_VALUE(WiiCommonKey, 0x4) ENUM_VALUE(RngKey, 0x5) ENUM_VALUE(WiiSdKey, 0x6) ENUM_VALUE(SeepromKey, 0x7) ENUM_VALUE(WiiKoreanKey, 0xB) ENUM_VALUE(DriveKey, 0xC) ENUM_VALUE(StarbuckAncastKey, 0xD) ENUM_VALUE(StarbuckAncastModulus, 0xE) ENUM_VALUE(Boot1AncastModulus, 0xF) ENUM_VALUE(CommonKey, 0x10) ENUM_VALUE(MlcKey, 0x11) ENUM_VALUE(WiiNandKey, 0x13) ENUM_VALUE(WiiNandHmac, 0x14) ENUM_VALUE(VwiiCommonKey, 0x15) ENUM_VALUE(DrhWlanKey, 0x16) ENUM_VALUE(UdsLocalWlanCcmpKey, 0x17) ENUM_VALUE(DlpKey, 0x18) ENUM_VALUE(AptWrapKey, 0x19) ENUM_VALUE(Unknown0x1A, 0x1A) ENUM_VALUE(SslRsaKey, 0x1B) ENUM_VALUE(NssPrivateKey, 0x1C) ENUM_VALUE(WiiNssPrivateKey, 0x1D) ENUM_VALUE(NssPrivateKey2, 0x1E) ENUM_VALUE(SpotPassKey, 0x1F) ENUM_VALUE(SpotPassUnknown0x20, 0x20) ENUM_VALUE(Unknown0x21, 0x21) ENUM_VALUE(Unknown0x22, 0x22) ENUM_VALUE(Unknown0x23, 0x23) ENUM_VALUE(Unknown0x24, 0x24) ENUM_VALUE(Unknown0x25, 0x25) ENUM_VALUE(Unknown0x26, 0x26) ENUM_VALUE(PushmoreKey, 0x27) ENUM_END(KeyId) ENUM_BEG(ObjectType, uint8_t) ENUM_VALUE(SecretKey, 0) ENUM_VALUE(PublicKey, 1) ENUM_VALUE(Unknown0x2, 2) ENUM_VALUE(Data, 3) ENUM_VALUE(Invalid, 0xFF) ENUM_END(ObjectType) ENUM_BEG(SubObjectType, uint8_t) ENUM_VALUE(Aes128, 0) ENUM_VALUE(Mac, 1) ENUM_VALUE(Rsa2048, 2) ENUM_VALUE(Rsa4096, 3) ENUM_VALUE(Ecc233, 4) ENUM_VALUE(Data, 5) ENUM_VALUE(Version, 6) ENUM_VALUE(Unknown0x7, 7) ENUM_END(SubObjectType) ENUM_NAMESPACE_EXIT(crypto) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_CRYPTO_ENUM_H ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto_ipc.cpp ================================================ #include "ios_crypto_ipc.h" #include "ios_crypto_request.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/ios_stackobject.h" namespace ios::crypto { using namespace kernel; static phys_ptr<void> allocIpcData(uint32_t size) { auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size); if (buffer) { std::memset(buffer.get(), 0, size); } return buffer; } static void freeIpcData(phys_ptr<void> data) { IOS_HeapFree(CrossProcessHeapId, data); } Error IOSC_Open() { return IOS_Open("/dev/crypto", OpenMode::None); } Error IOSC_Close(IOSCHandle handle) { return IOS_Close(handle); } IOSCError IOSC_Decrypt(IOSCHandle handle, IOSCKeyHandle keyHandle, phys_ptr<const void> ivData, uint32_t ivSize, phys_ptr<const void> inputData, uint32_t inputSize, phys_ptr<void> outputData, uint32_t outputSize) { if (!align_check(inputData.get(), 0x10u) || !align_check(outputData.get(), 0x10u)) { return IOSCError::InvalidParam; } auto request = phys_cast<IOSCRequestDecrypt *>( allocIpcData(sizeof(IOSCRequestDecrypt))); request->unknown0x00 = 0u; request->unknown0x04 = 1u; request->keyHandle = keyHandle; auto vecs = phys_cast<IoctlVec *>(allocIpcData(sizeof(IoctlVec) * 4)); vecs[0].paddr = phys_cast<phys_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(IOSCRequestDecrypt)); vecs[1].paddr = phys_cast<phys_addr>(ivData); vecs[1].len = ivSize; vecs[2].paddr = phys_cast<phys_addr>(inputData); vecs[2].len = inputSize; vecs[3].paddr = phys_cast<phys_addr>(outputData); vecs[3].len = outputSize; auto error = IOS_Ioctlv(handle, IOSCCommand::Decrypt, 3u, 1u, vecs); freeIpcData(request); freeIpcData(vecs); return static_cast<IOSCError>(error); } } // namespace ios::crypto ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto_ipc.h ================================================ #pragma once #include "ios_crypto_enum.h" #include "ios_crypto_types.h" #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_resourcemanager.h" namespace ios::crypto { using IOSCHandle = kernel::ResourceHandleId; Error IOSC_Open(); Error IOSC_Close(IOSCHandle handle); IOSCError IOSC_Decrypt(IOSCHandle handle, IOSCKeyHandle keyHandle, phys_ptr<const void> ivData, uint32_t ivSize, phys_ptr<const void> inputData, uint32_t inputSize, phys_ptr<void> outputData, uint32_t outputSize); } // namespace ios::crypto ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto_log.h ================================================ #pragma once #include <common/log.h> namespace ios::crypto::internal { extern Logger cryptoLog; } // namespace ios::crypto::internal ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto_request.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios_crypto_enum.h" #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::crypto { /** * \ingroup ios_crypto * @{ */ #pragma pack(push, 1) using IOSCKeyHandle = uint32_t; struct IOSCRequestDecrypt { be2_val<uint32_t> unknown0x00; be2_val<uint32_t> unknown0x04; be2_val<IOSCKeyHandle> keyHandle; PADDING(4); }; CHECK_OFFSET(IOSCRequestDecrypt, 0x00, unknown0x00); CHECK_OFFSET(IOSCRequestDecrypt, 0x04, unknown0x04); CHECK_OFFSET(IOSCRequestDecrypt, 0x08, keyHandle); CHECK_SIZE(IOSCRequestDecrypt, 0x10); #pragma pack(pop) /** @} */ } // namespace ios::crypto ================================================ FILE: src/libdecaf/src/ios/crypto/ios_crypto_types.h ================================================ #pragma once #include <cstdint> namespace ios::crypto { using IOSCKeyHandle = uint32_t; } // namespace ios::crypto ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd.cpp ================================================ #include "ios_fpd.h" #include "ios_fpd_log.h" #include "ios_fpd_act_server.h" #include "ios_fpd_act_accountdata.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_heap.h" namespace ios::fpd { using namespace ios::kernel; constexpr auto LocalHeapSize = 0xA0000u; constexpr auto CrossHeapSize = 0xC0000u; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger fpdLog = { }; void initialiseStaticData() { sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } } // namespace internal Error processEntryPoint(phys_ptr<void> /* context */) { auto error = Error::OK; // Initialise logger internal::fpdLog = decaf::makeLogger("IOS_FPD"); internal::initialiseStaticData(); internal::initialiseStaticActServerData(); internal::initialiseStaticAccountData(); // Initialise process heaps error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { internal::fpdLog->error( "processEntryPoint: IOS_CreateLocalProcessHeap failed with error = {}", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { internal::fpdLog->error( "processEntryPoint: IOS_CreateCrossProcessHeap failed with error = {}", error); return error; } error = internal::startActServer(); if (error < Error::OK) { internal::fpdLog->error( "processEntryPoint: startActServer failed with error = {}", error); return error; } // TODO: Start /dev/fpd thread, Join /dev/fpd thread return error; } } // namespace ios::fpd ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::fpd { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::fpd ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_accountdata.cpp ================================================ #include "ios_fpd_act_accountdata.h" #include "ios_fpd_act_server.h" #include "ios_fpd_log.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_process.h" #include "nn/ffl/nn_ffl_miidata.h" #include "nn/act/nn_act_result.h" #include <common/platform_time.h> #include <algorithm> #include <array> #include <charconv> #include <chrono> #include <common/platform_time.h> #include <cstring> #include <string_view> using namespace nn::act; using namespace nn::ffl; using namespace ios::fs; using namespace ios::kernel; namespace ios::fpd::internal { //! TODO: This hash is actually read from /dev/mcp ioctl 0xD3 static constexpr std::array<uint8_t, 4> UnknownHash = { 0xDE, 0xCA, 0xF0, 0x0D }; //! TODO: Device Hash is actually calculated from UnkownHash using some sort of sha256 static constexpr std::array<uint8_t, 8> DeviceHash = { 0xDE, 0xCA, 0xFD, 0xEC, 0xAF, 0xDE, 0xCA, 0xF0 }; struct AccountData { be2_struct<UuidManager> uuidManager; be2_struct<TransferableIdManager> transferableIdManager; be2_struct<PersistentIdManager> persistentIdManager; be2_struct<AccountManager> accountManager; be2_array<AccountInstance, NumSlots> accounts; be2_array<bool, NumSlots> accountUsed; be2_phys_ptr<AccountInstance> currentAccount; be2_phys_ptr<AccountInstance> defaultAccount; }; static phys_ptr<AccountData> sAccountData = nullptr; phys_ptr<TransferableIdManager> getTransferableIdManager() { return phys_addrof(sAccountData->transferableIdManager); } phys_ptr<UuidManager> getUuidManager() { return phys_addrof(sAccountData->uuidManager); } phys_ptr<PersistentIdManager> getPersistentIdManager() { return phys_addrof(sAccountData->persistentIdManager); } phys_ptr<AccountManager> getAccountManager() { return phys_addrof(sAccountData->accountManager); } phys_ptr<AccountInstance> getCurrentAccount() { return sAccountData->currentAccount; } void setCurrentAccount(phys_ptr<AccountInstance> account) { sAccountData->currentAccount = account; } phys_ptr<AccountInstance> getDefaultAccount() { return sAccountData->defaultAccount; } std::array<uint8_t, 8> getDeviceHash() { return DeviceHash; } uint8_t getNumAccounts() { auto count = uint8_t { 0 }; for (auto used : sAccountData->accountUsed) { if (used) { count++; } } return count; } SlotNo getSlotNoForAccount(phys_ptr<AccountInstance> account) { auto index = account - phys_addrof(sAccountData->accounts[0]); if (index < 0 || index >= sAccountData->accounts.size()) { return InvalidSlot; } return static_cast<SlotNo>(index + 1); } phys_ptr<AccountInstance> getAccountBySlotNo(SlotNo slot) { if (slot == CurrentUserSlot) { return sAccountData->currentAccount; } else if (slot == InvalidSlot) { return nullptr; } auto index = static_cast<unsigned>(slot - 1); if (index >= sAccountData->accounts.size()) { return nullptr; } if (!sAccountData->accountUsed[index]) { return nullptr; } return phys_addrof(sAccountData->accounts[index]); } phys_ptr<AccountInstance> getAccountByPersistentId(PersistentId id) { for (auto i = 0u; i < sAccountData->accounts.size(); ++i) { if (sAccountData->accountUsed[i] && sAccountData->accounts[i].persistentId == id) { return phys_addrof(sAccountData->accounts[i]); } } return nullptr; } static uint64_t sub_E30BD3A0(uint32_t a, uint32_t b) { uint32_t v2 = (b << 8) & 0xFF0000 | (b << 24) | (b >> 8) & 0xFF00 | (b >> 24); uint32_t v3 = (a << 8) & 0xFF0000 | (a << 24) | (a >> 8) & 0xFF00 | (a >> 24); uint32_t v4 = v2 ^ (v2 ^ (v2 >> 7)) & 0xAA00AA ^ (((v2 ^ (v2 >> 7)) & 0xAA00AA) << 7); uint32_t v5 = v3 ^ (v3 ^ (v3 >> 7)) & 0xAA00AA ^ (((v3 ^ (v3 >> 7)) & 0xAA00AA) << 7); uint32_t v6 = v5 ^ (v5 ^ (v5 >> 14)) & 0xCCCC ^ (((v5 ^ (v5 >> 14)) & 0xCCCC) << 14); uint32_t v7 = v4 ^ (v4 ^ (v4 >> 14)) & 0xCCCC ^ (((v4 ^ (v4 >> 14)) & 0xCCCC) << 14); uint32_t resultLo = ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) << 8) & 0xFF0000 | ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) << 24) | ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) >> 8) & 0xFF00 | ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) >> 24); uint32_t resultHi = ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) << 8) & 0xFF0000 | ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) << 24) | ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) >> 8) & 0xFF00 | ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) >> 24); return static_cast<uint64_t>(resultLo) | (static_cast<uint64_t>(resultHi) << 32); } TransferrableId calculateTransferableId(uint64_t transferableIdBase, uint16_t a3) { uint32_t a1_hi = static_cast<uint32_t>(transferableIdBase >> 32); uint32_t a1_lo = static_cast<uint32_t>(transferableIdBase); uint32_t v3 = (a3 << 6) | (a1_hi); uint32_t v4 = ((v3 >> 16) & 0x1C0) | ((a1_lo << 3) & 0x38) | ((v3 >> 6) & 7); return sub_E30BD3A0( v3 ^ (((v4 << 31) | (v4 << 22) | (v4 >> 5) | (v4 >> 23) | (v4 >> 14) | (v4 << 4) | (v4 << 13)) & 0xFFFFFE3F), a1_lo ^ ((v4 << 27) | (v4 << 9) | (v4 << 18) | v4)); } static uint16_t FFLiGetCRC16(phys_ptr<const uint8_t> bytes, uint32_t length) { auto crc = uint32_t { 0 }; for (auto byteIndex = 0u; byteIndex < length; byteIndex++) { for (auto bitIndex = 7; bitIndex >= 0; bitIndex--) { crc = (((crc << 1) | ((bytes[byteIndex] >> bitIndex) & 0x1)) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0)); } } for (auto counter = 16; counter > 0u; counter--) { crc = ((crc << 1) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0)); } return static_cast<uint16_t>(crc & 0xFFFF); } static void FFLiSetAuthorID(uint64_t *authorId) { *authorId = calculateTransferableId( sAccountData->accountManager.commonTransferableIdBase, 0x4A0); } static void FFLiSetCreateID(FFLCreateID *createId) { static constexpr tm MiiEpoch = { 0, 0, 0, 1, 0, 2010 - 1900, 0, 0, 0 }; auto epoch = std::chrono::system_clock::from_time_t(platform::make_gm_time(MiiEpoch)); auto secondsSinceMiiEpoch = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now() - epoch); createId->flags = FFLCreateIDFlags::IsNormalMii | FFLCreateIDFlags::IsWiiUMii; createId->timestamp = static_cast<uint32_t>(secondsSinceMiiEpoch.count() & 0x0FFFFFFF); std::memcpy(createId->deviceHash, DeviceHash.data(), 6); } static long long getUuidTime() { auto start = std::chrono::system_clock::from_time_t(-12219292800); auto diff = std::chrono::system_clock::now() - start; auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(diff).count(); return ns / 100; } static std::array<uint8_t, UuidSize> generateUuid() { auto time = getUuidTime(); sAccountData->uuidManager.lastTime = time; auto time_low = static_cast<uint32_t>(time); auto time_mid = static_cast<uint16_t>((time >> 32) & 0xFFFF); auto time_hi_and_version = static_cast<uint16_t>( static_cast<uint16_t>((time >> 48) & 0x0FFF) | 0x1000); auto clock_seq = static_cast<uint16_t>( (++sAccountData->uuidManager.clockSequence & 0x3FFF) | 0x8000); if (sAccountData->uuidManager.clockSequence >= 0x4000) { sAccountData->uuidManager.clockSequence = 0; } auto node = std::array<uint8_t, 6> { }; node.fill(0); node[0] = 1; node[1] = 1; std::memcpy(node.data() + 2, UnknownHash.data(), UnknownHash.size()); auto uuid = std::array<uint8_t, UuidSize> { }; std::memcpy(uuid.data() + 0, &time_low, 4); std::memcpy(uuid.data() + 4, &time_mid, 2); std::memcpy(uuid.data() + 6, &time_hi_and_version, 2); std::memcpy(uuid.data() + 8, &clock_seq, 2); std::memcpy(uuid.data() + 10, node.data(), 6); return uuid; } static PersistentId generatePersistentId() { return ++sAccountData->persistentIdManager.persistentIdHead; } static TransferrableId generateTransferrableIdBase() { auto valueLo = *reinterpret_cast<const uint32_t *>(UnknownHash.data()); auto valueHi = (sAccountData->transferableIdManager.counter << 22) | 4; sAccountData->transferableIdManager.counter = static_cast<uint32_t>((sAccountData->transferableIdManager.counter + 1) & 0x3FF); return static_cast<uint64_t>(valueLo) | (static_cast<uint64_t>(valueHi) << 32); } static phys_ptr<AccountInstance> allocateAccount() { for (auto i = 0u; i < sAccountData->accounts.size(); ++i) { if (!sAccountData->accountUsed[i]) { sAccountData->accountUsed[i] = true; return phys_addrof(sAccountData->accounts[i]); } } return nullptr; } phys_ptr<AccountInstance> createAccount() { auto account = allocateAccount(); if (!account) { return nullptr; } std::memset(account.get(), 0, sizeof(AccountInstance)); account->persistentId = generatePersistentId(); account->parentalControlSlotNo = uint8_t { 1u }; account->principalId = 1u; account->simpleAddressId = 1u; account->transferableIdBase = generateTransferrableIdBase(); account->accountId = "DonaldTrump420"; account->nfsPassword = "NfsPassword"; account->birthDay = uint8_t { 4 }; account->birthMonth = uint8_t { 6 }; account->birthYear = uint16_t { 1989 }; account->gender = uint8_t { 1 }; account->uuid = generateUuid(); account->isCommitted = uint8_t { 0 }; // Default Mii from IOS static uint8_t DefaultMii[] = { 0x00, 0x01, 0x00, 0x40, 0x80, 0xF3, 0x41, 0x80, 0x02, 0x65, 0xA0, 0x92, 0xD2, 0x3B, 0x13, 0x36, 0xA4, 0xC0, 0xE1, 0xF8, 0x2D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x21, 0x01, 0x02, 0x68, 0x44, 0x18, 0x26, 0x34, 0x46, 0x14, 0x81, 0x12, 0x17, 0x68, 0x0D, 0x00, 0x00, 0x29, 0x00, 0x52, 0x48, 0x50, 0x00, 0x00, 0x61, 0x00, 0x72, 0x00, 0x61, 0x00, 0x68, 0x00, 0x00, 0x00, 0x67, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6A, 0x00, 0x00, 0x00, 0xE4, 0x62, }; static_assert(sizeof(DefaultMii) == sizeof(account->miiData)); std::memcpy(std::addressof(account->miiData), DefaultMii, sizeof(DefaultMii)); FFLiSetAuthorID(&account->miiData.author_id); FFLiSetCreateID(&account->miiData.mii_id); account->miiData.checksum = FFLiGetCRC16(phys_cast<uint8_t *>(phys_addrof(account->miiData)), sizeof(FFLStoreData) - 2); account->miiName = u"???"; return account; } template<typename ReadKeyValueCallback> static FSAStatus readPropertyFile(std::string_view path, std::string_view typeName, ReadKeyValueCallback readKeyValueCallback) { auto fsaHandle = getActFsaHandle(); auto fileHandle = FSAFileHandle { -1 }; auto result = FSAOpenFile(fsaHandle, path, "r", &fileHandle); if (result < 0) { internal::fpdLog->debug("Could not open {}", path); return result; } auto fileBufferSize = 1024u; auto fileBuffer = phys_cast<char *>(IOS_HeapAllocAligned(CrossProcessHeapId, fileBufferSize, 0x40)); if (!fileBuffer) { internal::fpdLog->debug("Could not allocate file buffer"); FSACloseFile(fsaHandle, fileHandle); return FSAStatus::OutOfResources; } auto lineStartPos = 0u; auto readBufferOffset = 0u; auto readFileHeader = false; while (true) { result = FSAReadFile(fsaHandle, fileBuffer + readBufferOffset, 1, fileBufferSize - readBufferOffset, fileHandle, FSAReadFlag::None); if (result < 0) { internal::fpdLog->debug("FSAReadFile failed"); IOS_HeapFree(CrossProcessHeapId, fileBuffer); FSACloseFile(fsaHandle, fileHandle); return result; } auto bytesRead = static_cast<uint32_t>(result); auto bufferPos = readBufferOffset; auto eof = bytesRead < (fileBufferSize - readBufferOffset); if (bytesRead == 0) { // Hit exactly eof with the previous read break; } while (bufferPos < bytesRead) { if (fileBuffer[bufferPos] == '\n') { auto line = std::string_view { fileBuffer.get() + lineStartPos, bufferPos - lineStartPos }; if (!readFileHeader) { // Check that line starts with typeName if (line.substr(0, typeName.size()).compare(typeName) != 0) { result = FSAStatus::DataCorrupted; eof = true; break; } readFileHeader = true; } else { // Should be a Key=Value line auto separator = line.find_first_of('='); if (separator == std::string_view::npos) { result = FSAStatus::DataCorrupted; eof = true; break; } auto key = line.substr(0, separator); auto value = line.substr(separator + 1); if (key.empty()) { result = FSAStatus::DataCorrupted; eof = true; break; } readKeyValueCallback(key, value); } lineStartPos = bufferPos + 1; } ++bufferPos; } if (eof) { // Reached end of file with last read break; } if (lineStartPos == 0u) { // Could not fit whole line in our buffer, must be a bad file result = FSAStatus::DataCorrupted; break; } if (bufferPos == bytesRead) { std::memmove(fileBuffer.get(), fileBuffer.get() + lineStartPos, sizeof(bytesRead - lineStartPos)); readBufferOffset = bytesRead - lineStartPos; lineStartPos = 0u; } } if (result > 0) { result = FSAStatus::OK; } FSACloseFile(fsaHandle, fileHandle); IOS_HeapFree(CrossProcessHeapId, fileBuffer); return result; } static void parseBoolProperty(std::string_view value, be2_val<uint8_t> &out) { int temporary = 0; std::from_chars(value.data(), value.data() + value.size(), temporary, 16); out = temporary != 0; } template<typename Type> static void parseIntegerProperty(std::string_view value, be2_val<Type> &out) { Type result { 0 }; std::from_chars(value.data(), value.data() + value.size(), result, 16); out = result; } static void parseHexString(std::string_view value, phys_ptr<uint8_t> out, size_t size) { std::memset(out.get(), 0, sizeof(size)); for (auto i = 0; i < value.size() && i / 2 < size; ++i) { auto nibble = uint8_t { 0 }; if (value[i] >= 'a' && value[i] <= 'f') { nibble = (value[i] - 'a') + 0xa; } else if (value[i] >= 'A' && value[i] <= 'F') { nibble = (value[i] - 'A') + 0xA; } else if (value[i] >= '0' && value[i] <= '9') { nibble = (value[i] - '0') + 0x0; } out[i / 2] |= static_cast<uint8_t>((i % 2) ? nibble : (nibble << 4)); } } template<uint32_t Size> static void parseStringProperty(std::string_view value, be2_array<char, Size> &out) { out.fill(0); std::memcpy(phys_addrof(out).get(), value.data(), std::min<std::size_t>(value.size(), out.size())); if (value.size() == out.size()) { out.back() = '\0'; } } static bool loadTransferableIdManager(phys_ptr<TransferableIdManager> transferableIdManager) { auto result = readPropertyFile( "/vol/storage_mlc01/usr/save/system/act/transid.dat", "TransferableIdManager", [transferableIdManager=transferableIdManager.get()] (std::string_view key, std::string_view value) { if (key == "Counter") { parseIntegerProperty(value, transferableIdManager->counter); } }); if (result == FSAStatus::OK) { return true; } transferableIdManager->counter = 0u; return false; } static bool loadUuidManager(phys_ptr<UuidManager> uuidManager) { auto result = readPropertyFile( "/vol/storage_mlc01/usr/save/system/act/uuid.dat", "UuidManager", [uuidManager=uuidManager.get()] (std::string_view key, std::string_view value) { if (key == "ClockSequence") { parseIntegerProperty(value, uuidManager->clockSequence); } else if (key == "LastTime") { parseIntegerProperty(value, uuidManager->lastTime); } }); if (result == FSAStatus::OK) { return true; } uuidManager->lastTime = 0ll; uuidManager->clockSequence = 0; return false; } static bool loadPersistentIdManager(phys_ptr<PersistentIdManager> persistentIdManager) { auto result = readPropertyFile( "/vol/storage_mlc01/usr/save/system/act/persisid.dat", "PersistentIdManager", [persistentIdManager=persistentIdManager.get()] (std::string_view key, std::string_view value) { if (key == "PersistentIdHead") { parseIntegerProperty(value, persistentIdManager->persistentIdHead); } }); if (result == FSAStatus::OK) { return true; } persistentIdManager->persistentIdHead = 0x80000000; return false; } static void parseNnasSubDomain(std::string_view subDomain, be2_val<uint32_t> &outNnasType) { if (subDomain.compare("game-dev.") == 0) { outNnasType = 1u; } else if (subDomain.compare("system-dev.") == 0) { outNnasType = 2u; } else if (subDomain.compare("library-dev.") == 0) { outNnasType = 3u; } else if (subDomain.compare("staging.") == 0) { outNnasType = 4u; } else { outNnasType = 0u; } } static void parseNnasNfsEnv(std::string_view nfsEnv, be2_val<uint32_t> &outNfsType, be2_val<uint8_t> &outNfsNo) { switch (nfsEnv[0]) { case 'D': outNfsType = 1u; break; case 'J': outNfsType = 4u; break; case 'L': outNfsType = 0u; break; case 'S': outNfsType = 2u; break; case 'T': outNfsType = 3u; break; default: outNfsNo = uint8_t { 1 }; outNfsType = 0u; return; } outNfsNo = std::clamp<uint8_t>(nfsEnv[1] - '0', 1, 9); } static bool loadAccountManager(phys_ptr<AccountManager> accountManager) { auto result = readPropertyFile( "/vol/storage_mlc01/usr/save/system/act/common.dat", "AccountManager", [accountManager](std::string_view key, std::string_view value) { if (key == "PersistentIdList") { auto start = size_t { 0 }; auto end = value.find("\\0"); auto index = 0; accountManager->persistentIdList.fill(0); while (end != std::string_view::npos) { auto persistentId = value.substr(start, end - start); parseIntegerProperty(persistentId, accountManager->persistentIdList[index++]); start = end + 2; end = value.find("\\0", start); } } else if (key == "DefaultAccountPersistentId") { parseIntegerProperty(value, accountManager->defaultAccountPersistentId); } else if (key == "CommonTransferableIdBase") { parseIntegerProperty(value, accountManager->commonTransferableIdBase); } else if (key == "CommonUuid") { parseHexString(value, phys_addrof(accountManager->commonUuid), accountManager->commonUuid.size()); } else if (key == "IsApplicationUpdateRequired") { parseBoolProperty(value, accountManager->isApplicationUpdateRequired); } else if (key == "DefaultNnasType") { parseIntegerProperty(value, accountManager->defaultNnasType); } else if (key == "DefaultNfsType") { parseIntegerProperty(value, accountManager->defaultNfsType); } else if (key == "DefaultNfsNo") { parseIntegerProperty(value, accountManager->defaultNfsNo); } else if (key == "DefaultNnasSubDomain") { parseStringProperty(value, accountManager->defaultNnasSubDomain); } else if (key == "DefaultNnasNfsEnv") { parseStringProperty(value, accountManager->defaultNnasNfsEnv); } }); if (result == FSAStatus::OK) { return true; } accountManager->persistentIdList.fill(0); accountManager->defaultAccountPersistentId = 0u; accountManager->commonTransferableIdBase = 0u; accountManager->commonUuid.fill(0); accountManager->isApplicationUpdateRequired = false; accountManager->defaultNnasSubDomain.fill(0); parseNnasSubDomain(phys_addrof(accountManager->defaultNnasSubDomain).get(), accountManager->defaultNnasType); accountManager->defaultNnasNfsEnv.fill(0); accountManager->defaultNnasNfsEnv = "L1"; parseNnasNfsEnv(phys_addrof(accountManager->defaultNnasNfsEnv).get(), accountManager->defaultNfsType, accountManager->defaultNfsNo); return false; } static bool loadAccountInstance(phys_ptr<AccountInstance> accountInstance, PersistentId persistentId) { auto path = fmt::format("/vol/storage_mlc01/usr/save/system/act/{:08x}/account.dat", persistentId); auto result = readPropertyFile( path, "AccountInstance", [accountInstance](std::string_view key, std::string_view value) { if (key == "PersistentId") { parseIntegerProperty(value, accountInstance->persistentId); } else if (key == "TransferableIdBase") { parseIntegerProperty(value, accountInstance->transferableIdBase); } else if (key == "Uuid") { parseHexString(value, phys_addrof(accountInstance->uuid), accountInstance->uuid.size()); } else if (key == "ParentalControlSlotNo") { parseIntegerProperty(value, accountInstance->parentalControlSlotNo); } else if (key == "MiiData") { parseHexString(value, phys_cast<uint8_t *>(phys_addrof(accountInstance->miiData)), sizeof(accountInstance->miiData)); } else if (key == "MiiName") { parseHexString(value, phys_cast<uint8_t *>(phys_addrof(accountInstance->miiName)), accountInstance->miiName.size() * 2); } else if (key == "IsMiiUpdated") { parseBoolProperty(value, accountInstance->isMiiUpdated); } else if (key == "AccountId") { parseStringProperty(value, accountInstance->accountId); } else if (key == "BirthYear") { parseIntegerProperty(value, accountInstance->birthYear); } else if (key == "BirthMonth") { parseIntegerProperty(value, accountInstance->birthMonth); } else if (key == "BirthDay") { parseIntegerProperty(value, accountInstance->birthDay); } else if (key == "Gender") { parseIntegerProperty(value, accountInstance->gender); } else if (key == "IsMailAddressValidated") { parseBoolProperty(value, accountInstance->isMailAddressValidated); } else if (key == "EmailAddress") { parseStringProperty(value, accountInstance->emailAddress); } else if (key == "Country") { parseIntegerProperty(value, accountInstance->country); } else if (key == "SimpleAddressId") { parseIntegerProperty(value, accountInstance->simpleAddressId); } else if (key == "TimeZoneId") { parseStringProperty(value, accountInstance->timeZoneId); } else if (key == "UtcOffset") { parseIntegerProperty(value, accountInstance->utcOffset); } else if (key == "PrincipalId") { parseIntegerProperty(value, accountInstance->principalId); } else if (key == "NfsPassword") { parseStringProperty(value, accountInstance->nfsPassword); } else if (key == "EciVirtualAccount") { parseStringProperty(value, accountInstance->eciVirtualAccount); } else if (key == "NeedsToDownloadMiiImage") { parseBoolProperty(value, accountInstance->needsToDownloadMiiImage); } else if (key == "MiiImageUrl") { parseStringProperty(value, accountInstance->miiImageUrl); } else if (key == "AccountPasswordHash") { parseHexString(value, phys_addrof(accountInstance->accountPasswordHash), accountInstance->accountPasswordHash.size()); } else if (key == "IsPasswordCacheEnabled") { parseBoolProperty(value, accountInstance->isPasswordCacheEnabled); } else if (key == "AccountPasswordCache") { parseHexString(value, phys_addrof(accountInstance->accountPasswordCache), accountInstance->accountPasswordCache.size()); } else if (key == "NnasType") { parseIntegerProperty(value, accountInstance->nnasType); } else if (key == "NfsType") { parseIntegerProperty(value, accountInstance->nfsType); } else if (key == "NfsNo") { parseIntegerProperty(value, accountInstance->nfsNo); } else if (key == "NnasSubDomain") { parseStringProperty(value, accountInstance->nnasSubDomain); } else if (key == "NnasNfsEnv") { parseStringProperty(value, accountInstance->nnasNfsEnv); } else if (key == "IsPersistentIdUploaded") { parseBoolProperty(value, accountInstance->isPersistentIdUploaded); } else if (key == "IsConsoleAccountInfoUploaded") { parseBoolProperty(value, accountInstance->isConsoleAccountInfoUploaded); } else if (key == "LastAuthenticationResult") { parseIntegerProperty(value, accountInstance->lastAuthenticationResult); } else if (key == "StickyAccountId") { parseStringProperty(value, accountInstance->stickyAccountId); } else if (key == "NextAccountId") { parseStringProperty(value, accountInstance->nextAccountId); } else if (key == "StickyPrincipalId") { parseIntegerProperty(value, accountInstance->stickyPrincipalId); } else if (key == "IsServerAccountDeleted") { parseBoolProperty(value, accountInstance->isServerAccountDeleted); } else if (key == "ServerAccountStatus") { parseIntegerProperty(value, accountInstance->serverAccountStatus); } else if (key == "MiiImageLastModifiedDate") { parseStringProperty(value, accountInstance->miiImageLastModifiedDate); } else if (key == "IsCommitted") { parseBoolProperty(value, accountInstance->isCommitted); } }); if (result == FSAStatus::OK) { return true; } std::memset(accountInstance.get(), 0, sizeof(AccountInstance)); return false; } void initialiseAccounts() { loadTransferableIdManager(phys_addrof(sAccountData->transferableIdManager)); loadUuidManager(phys_addrof(sAccountData->uuidManager)); loadPersistentIdManager(phys_addrof(sAccountData->persistentIdManager)); loadAccountManager(phys_addrof(sAccountData->accountManager)); if (!sAccountData->accountManager.commonUuid[0]) { sAccountData->accountManager.commonUuid = generateUuid(); } if (!sAccountData->accountManager.commonTransferableIdBase) { sAccountData->accountManager.commonTransferableIdBase = generateTransferrableIdBase(); } for (auto slot = 1u; slot <= sAccountData->accountManager.persistentIdList.size(); ++slot) { auto persistentId = sAccountData->accountManager.persistentIdList[slot - 1]; if (persistentId) { sAccountData->accountUsed[slot - 1] = loadAccountInstance(phys_addrof(sAccountData->accounts[slot - 1]), persistentId); } } // Try find the default account auto account = getAccountByPersistentId(sAccountData->accountManager.defaultAccountPersistentId); if (!account) { // Try find any account for (auto i = 0u; i < sAccountData->accounts.size(); ++i) { if (sAccountData->accountUsed[i]) { account = phys_addrof(sAccountData->accounts[i]); break; } } if (!account) { // No account found, we can create one instead! account = createAccount(); account->isCommitted = uint8_t { 1u }; sAccountData->accountManager.persistentIdList[getSlotNoForAccount(account)] = account->persistentId; sAccountData->accountManager.defaultAccountPersistentId = account->persistentId; } } sAccountData->currentAccount = account; sAccountData->defaultAccount = account; } void initialiseStaticAccountData() { sAccountData = kernel::allocProcessStatic<AccountData>(); } } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_accountdata.h ================================================ #pragma once #include "nn/act/nn_act_types.h" #include "nn/ffl/nn_ffl_miidata.h" #include <cstdint> #include <libcpu/be2_struct.h> namespace ios::fpd::internal { using nn::act::SlotNo; using nn::act::LocalFriendCode; using nn::act::PersistentId; using nn::act::PrincipalId; using nn::act::SimpleAddressId; using nn::act::TransferrableId; using nn::act::Uuid; using nn::act::NumSlots; using nn::act::AccountIdSize; using nn::act::NfsPasswordSize; using nn::act::MiiNameSize; using nn::act::UuidSize; struct TransferableIdManager { be2_val<uint32_t> counter; }; struct UuidManager { be2_val<int32_t> clockSequence; be2_val<int64_t> lastTime; }; struct PersistentIdManager { be2_val<uint32_t> persistentIdHead; }; struct AccountManager { be2_array<PersistentId, NumSlots> persistentIdList; be2_val<PersistentId> defaultAccountPersistentId; be2_val<TransferrableId> commonTransferableIdBase; be2_array<uint8_t, UuidSize> commonUuid; be2_val<uint8_t> isApplicationUpdateRequired; be2_val<uint32_t> defaultNnasType; be2_val<uint32_t> defaultNfsType; be2_val<uint8_t> defaultNfsNo; be2_array<char, 33> defaultNnasSubDomain; be2_array<char, 3> defaultNnasNfsEnv; }; struct AccountInstance { be2_val<PersistentId> persistentId; be2_val<TransferrableId> transferableIdBase; be2_array<uint8_t, UuidSize> uuid; be2_val<SlotNo> parentalControlSlotNo; be2_struct<nn::ffl::FFLStoreData> miiData; be2_array<char16_t, MiiNameSize> miiName; be2_val<uint8_t> isMiiUpdated; be2_array<char, AccountIdSize> accountId; be2_val<uint16_t> birthYear; be2_val<uint8_t> birthMonth; be2_val<uint8_t> birthDay; be2_val<uint32_t> gender; be2_val<uint8_t> isMailAddressValidated; be2_array<char, 257> emailAddress; be2_val<uint32_t> country; be2_val<SimpleAddressId> simpleAddressId; be2_array<char, 65> timeZoneId; be2_val<uint64_t> utcOffset; // TODO: Seconds? Nanoseconds? etc be2_val<PrincipalId> principalId; be2_array<char, 17> nfsPassword; be2_array<char, 32> eciVirtualAccount; be2_val<uint8_t> needsToDownloadMiiImage; be2_array<char, 257> miiImageUrl; be2_array<uint8_t, 32> accountPasswordHash; be2_val<uint8_t> isPasswordCacheEnabled; be2_array<uint8_t, 32> accountPasswordCache; be2_val<uint32_t> nnasType; be2_val<uint32_t> nfsType; be2_val<uint8_t> nfsNo; be2_array<char, 33> nnasSubDomain; be2_array<char, 3> nnasNfsEnv; be2_val<uint8_t> isPersistentIdUploaded; be2_val<uint8_t> isConsoleAccountInfoUploaded; be2_val<uint32_t> lastAuthenticationResult; be2_array<char, AccountIdSize> stickyAccountId; be2_array<char, AccountIdSize> nextAccountId; be2_val<PrincipalId> stickyPrincipalId; be2_val<uint8_t> isServerAccountDeleted; be2_val<uint32_t> serverAccountStatus; be2_array<char, 31> miiImageLastModifiedDate; be2_val<uint8_t> isCommitted; }; phys_ptr<TransferableIdManager> getTransferableIdManager(); phys_ptr<UuidManager> getUuidManager(); phys_ptr<PersistentIdManager> getPersistentIdManager(); phys_ptr<AccountManager> getAccountManager(); phys_ptr<AccountInstance> getCurrentAccount(); void setCurrentAccount(phys_ptr<AccountInstance> account); phys_ptr<AccountInstance> getDefaultAccount(); std::array<uint8_t, 8> getDeviceHash(); uint8_t getNumAccounts(); SlotNo getSlotNoForAccount(phys_ptr<AccountInstance> account); phys_ptr<AccountInstance> getAccountBySlotNo(SlotNo slot); phys_ptr<AccountInstance> getAccountByPersistentId(PersistentId id); TransferrableId calculateTransferableId(uint64_t transferableIdBase, uint16_t a3); phys_ptr<AccountInstance> createAccount(); void initialiseAccounts(); void initialiseStaticAccountData(); } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_accountloaderservice.cpp ================================================ #include "ios_fpd_act_accountloaderservice.h" #include "ios_fpd_act_accountdata.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/act/nn_act_result.h" #include "nn/ipc/nn_ipc_result.h" #include <common/decaf_assert.h> using namespace nn::act; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::InBuffer; using nn::ipc::ServerCommand; namespace ios::fpd::internal { static nn::Result loadConsoleAccount(CommandHandlerArgs &args) { auto command = ServerCommand<ActAccountLoaderService::LoadConsoleAccount> { args }; auto slotNo = SlotNo { 0 }; auto loadOption = ACTLoadOption { }; auto unkInBuffer = InBuffer<const char> { }; auto unkBool = bool { false }; command.ReadRequest(slotNo, loadOption, unkInBuffer, unkBool); auto account = getAccountBySlotNo(slotNo); if (!account) { return ResultAccountNotFound; } /* TODO: nn::act::LoadConsoleAccount Not really sure what this does tbh. */ setCurrentAccount(account); return nn::ResultSuccess; } nn::Result ActAccountLoaderService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case LoadConsoleAccount::command: return loadConsoleAccount(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_accountloaderservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/act/nn_act_accountloaderservice.h" namespace ios::fpd::internal { struct ActAccountLoaderService : ::nn::act::services::AccountLoaderService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_accountmanagerservice.cpp ================================================ #include "ios_fpd_act_accountdata.h" #include "ios_fpd_act_accountmanagerservice.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/act/nn_act_result.h" #include "nn/ipc/nn_ipc_result.h" using namespace nn::act; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::fpd::internal { static nn::Result createConsoleAccount(CommandHandlerArgs &args) { auto command = ServerCommand<ActAccountManagerService::CreateConsoleAccount> { args }; auto account = createAccount(); if (!account) { return ResultSlotsFull; } account->isCommitted = uint8_t { 0 }; return nn::ResultSuccess; } nn::Result ActAccountManagerService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case CreateConsoleAccount::command: return createConsoleAccount(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_accountmanagerservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/act/nn_act_accountmanagerservice.h" namespace ios::fpd::internal { struct ActAccountManagerService : ::nn::act::services::AccountManagerService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_clientstandardservice.cpp ================================================ #include "ios_fpd_act_accountdata.h" #include "ios_fpd_act_clientstandardservice.h" #include "ios_fpd_act_server.h" #include "ios_fpd_log.h" #include "ios/ios_stackobject.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_timer.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/act/nn_act_result.h" #include "nn/ffl/nn_ffl_miidata.h" #include "nn/ipc/nn_ipc_result.h" #include <array> #include <charconv> #include <chrono> #include <cstring> #include <memory> #include <string> using namespace nn::act; using namespace nn::ffl; using namespace ios::fs; using namespace ios::kernel; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::fpd::internal { static nn::Result getCommonInfo(CommandHandlerArgs &args) { auto command = ServerCommand<ActClientStandardService::GetCommonInfo> { args }; auto buffer = OutBuffer<void> { }; auto type = InfoType { 0 }; command.ReadRequest(buffer, type); switch (type) { case InfoType::NumOfAccounts: { if (buffer.totalSize() < sizeof(uint8_t)) { return ResultInvalidSize; } uint8_t numAccounts = getNumAccounts(); buffer.writeOutput(&numAccounts, sizeof(uint8_t)); break; } case InfoType::SlotNo: { if (buffer.totalSize() < sizeof(SlotNo)) { return ResultInvalidSize; } SlotNo slotNo = getSlotNoForAccount(getCurrentAccount()); buffer.writeOutput(&slotNo, sizeof(slotNo)); break; } case InfoType::DefaultAccount: { if (buffer.totalSize() < sizeof(SlotNo)) { return ResultInvalidSize; } SlotNo slotNo = getSlotNoForAccount(getDefaultAccount()); buffer.writeOutput(&slotNo, sizeof(slotNo)); break; } case InfoType::NetworkTimeDifference: return ResultNotImplemented; case InfoType::LocalFriendCode: { auto accountManager = getAccountManager(); if (buffer.totalSize() < sizeof(accountManager->commonTransferableIdBase)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(accountManager->commonTransferableIdBase), sizeof(accountManager->commonTransferableIdBase)); break; } case InfoType::ApplicationUpdateRequired: { auto accountManager = getAccountManager(); if (buffer.totalSize() < sizeof(accountManager->isApplicationUpdateRequired)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(accountManager->isApplicationUpdateRequired), sizeof(accountManager->isApplicationUpdateRequired)); break; } case InfoType::DefaultHostServerSettings: return ResultNotImplemented; case InfoType::DefaultHostServerSettingsEx: return ResultNotImplemented; case InfoType::DeviceHash: { auto hash = getDeviceHash(); if (buffer.totalSize() < hash.size()) { return ResultInvalidSize; } buffer.writeOutput(hash.data(), hash.size()); break; } case InfoType::NetworkTime: return ResultNotImplemented; default: return ResultInvalidValue; } return nn::ResultSuccess; } static nn::Result getAccountInfo(CommandHandlerArgs &args) { auto command = ServerCommand<ActClientStandardService::GetAccountInfo> { args }; auto slotNo = InvalidSlot; auto buffer = OutBuffer<void> { }; auto type = InfoType { 0 }; command.ReadRequest(slotNo, buffer, type); auto account = getAccountBySlotNo(slotNo); if (!account) { return ResultAccountNotFound; } switch (type) { case InfoType::PersistentId: if (buffer.totalSize() < sizeof(account->persistentId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->persistentId), sizeof(account->persistentId)); break; case InfoType::LocalFriendCode: if (buffer.totalSize() < sizeof(account->transferableIdBase)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->transferableIdBase), sizeof(account->transferableIdBase)); break; case InfoType::Mii: if (buffer.totalSize() < sizeof(account->miiData)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->miiData), sizeof(account->miiData)); break; case InfoType::AccountId: if (buffer.totalSize() < sizeof(account->accountId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->accountId), sizeof(account->accountId)); break; case InfoType::EmailAddress: if (buffer.totalSize() < sizeof(account->emailAddress)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->emailAddress), sizeof(account->emailAddress)); break; case InfoType::Birthday: { StackObject<Birthday> birthday; if (buffer.totalSize() < sizeof(*birthday)) { return ResultInvalidSize; } birthday->year = account->birthYear; birthday->month = account->birthMonth; birthday->day = account->birthDay; buffer.writeOutput(birthday, sizeof(*birthday)); break; } case InfoType::Country: return ResultNotImplemented; case InfoType::PrincipalId: if (buffer.totalSize() < sizeof(account->principalId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->principalId), sizeof(account->principalId)); break; case InfoType::IsPasswordCacheEnabled: if (buffer.totalSize() < sizeof(account->isPasswordCacheEnabled)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->isPasswordCacheEnabled), sizeof(account->isPasswordCacheEnabled)); break; case InfoType::AccountPasswordCache: if (buffer.totalSize() < sizeof(account->accountPasswordCache)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->accountPasswordCache), sizeof(account->accountPasswordCache)); break; case InfoType::AccountInfo: case InfoType::HostServerSettings: return ResultNotImplemented; case InfoType::Gender: if (buffer.totalSize() < sizeof(account->gender)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->gender), sizeof(account->gender)); break; case InfoType::LastAuthenticationResult: if (buffer.totalSize() < sizeof(account->lastAuthenticationResult)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->lastAuthenticationResult), sizeof(account->lastAuthenticationResult)); break; case InfoType::StickyAccountId: if (buffer.totalSize() < sizeof(account->stickyAccountId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->stickyAccountId), sizeof(account->stickyAccountId)); break; case InfoType::ParentalControlSlot: if (buffer.totalSize() < sizeof(account->parentalControlSlotNo)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->parentalControlSlotNo), sizeof(account->parentalControlSlotNo)); break; case InfoType::SimpleAddressId: if (buffer.totalSize() < sizeof(account->simpleAddressId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->simpleAddressId), sizeof(account->simpleAddressId)); break; case static_cast<InfoType>(25): return ResultNotImplemented; case InfoType::IsCommitted: if (buffer.totalSize() < sizeof(account->isCommitted)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->isCommitted), sizeof(account->isCommitted)); break; case InfoType::MiiName: if (buffer.totalSize() < sizeof(account->miiName)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->miiName), sizeof(account->miiName)); break; case InfoType::NfsPassword: if (buffer.totalSize() < sizeof(account->nfsPassword)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->nfsPassword), sizeof(account->nfsPassword)); break; case InfoType::HasEciVirtualAccount: { StackObject<uint8_t> hasEciVirtualAccount; if (buffer.totalSize() < sizeof(*hasEciVirtualAccount)) { return ResultInvalidSize; } *hasEciVirtualAccount = account->eciVirtualAccount[0] ? 1 : 0; buffer.writeOutput(hasEciVirtualAccount, sizeof(*hasEciVirtualAccount)); break; } case InfoType::TimeZoneId: if (buffer.totalSize() < sizeof(account->timeZoneId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->timeZoneId), sizeof(account->timeZoneId)); break; case InfoType::IsMiiUpdated: if (buffer.totalSize() < sizeof(account->isMiiUpdated)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->isMiiUpdated), sizeof(account->isMiiUpdated)); break; case InfoType::IsMailAddressValidated: if (buffer.totalSize() < sizeof(account->isMailAddressValidated)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->isMailAddressValidated), sizeof(account->isMailAddressValidated)); break; case InfoType::NextAccountId: if (buffer.totalSize() < sizeof(account->nextAccountId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->nextAccountId), sizeof(account->nextAccountId)); break; case InfoType::Unk34: return ResultNotImplemented; case InfoType::IsServerAccountDeleted: if (buffer.totalSize() < sizeof(account->isServerAccountDeleted)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->isServerAccountDeleted), sizeof(account->isServerAccountDeleted)); break; case InfoType::MiiImageUrl: if (buffer.totalSize() < sizeof(account->miiImageUrl)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->miiImageUrl), sizeof(account->miiImageUrl)); break; case InfoType::StickyPrincipalId: if (buffer.totalSize() < sizeof(account->stickyPrincipalId)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->stickyPrincipalId), sizeof(account->stickyPrincipalId)); break; case InfoType::Unk40: case InfoType::Unk41: return ResultNotImplemented; case InfoType::ServerAccountStatus: if (buffer.totalSize() < sizeof(account->serverAccountStatus)) { return ResultInvalidSize; } buffer.writeOutput(phys_addrof(account->serverAccountStatus), sizeof(account->serverAccountStatus)); break; default: return ResultInvalidValue; } return nn::ResultSuccess; } static nn::Result getTransferableId(CommandHandlerArgs &args) { auto command = ServerCommand<ActClientStandardService::GetTransferableId> { args }; auto slotNo = InvalidSlot; auto unkArg2 = uint32_t { 0 }; command.ReadRequest(slotNo, unkArg2); auto transferableIdBase = TransferrableId { 0 }; if (slotNo == SystemSlot) { transferableIdBase = getAccountManager()->commonTransferableIdBase; } else { auto account = getAccountBySlotNo(slotNo); if (!account) { return ResultAccountNotFound; } transferableIdBase = account->transferableIdBase; } command.WriteResponse(calculateTransferableId(transferableIdBase, static_cast<uint16_t>(unkArg2))); return nn::ResultSuccess; } static nn::Result getMiiImage(CommandHandlerArgs &args) { auto command = ServerCommand<ActClientStandardService::GetMiiImage> { args }; auto slotNo = InvalidSlot; auto buffer = OutBuffer<void> { }; auto imageType = MiiImageType { 0 }; command.ReadRequest(slotNo, buffer, imageType); auto account = getAccountBySlotNo(slotNo); if (!account) { return ResultAccountNotFound; } auto path = fmt::format( "/vol/storage_mlc01/usr/save/system/act/{:08x}/miiimg{:02}.dat", static_cast<uint32_t>(account->persistentId), static_cast<int>(imageType)); // Check that the given buffer is large enough to read file into auto stat = StackObject<FSAStat> { }; auto status = FSAGetInfoByQuery(getActFsaHandle(), path, FSAQueryInfoType::Stat, stat); if (status < FSAStatus::OK) { if (status == FSAStatus::NotFound) { return ResultFileNotFound; } else { return ResultFileIoError; } } if (buffer.totalSize() < stat->size) { return ResultInvalidSize; } // Read file split across buffer auto fileHandle = FSAFileHandle { -1 }; status = FSAOpenFile(getActFsaHandle(), path, "r", &fileHandle); if (status < FSAStatus::OK) { if (status == FSAStatus::NotFound) { return ResultFileNotFound; } else { return ResultFileIoError; } } auto bytesRead = 0u; if (bytesRead < stat->size && buffer.unalignedBeforeBufferSize > 0) { auto readSize = std::min<uint32_t>(buffer.unalignedBeforeBufferSize, stat->size - bytesRead); status = FSAReadFile(getActFsaHandle(), buffer.unalignedBeforeBuffer, 1, readSize, fileHandle, FSAReadFlag::None); if (status < FSAStatus::OK || static_cast<uint32_t>(status) != readSize) { FSACloseFile(getActFsaHandle(), fileHandle); return ResultFileIoError; } bytesRead += readSize; } if (bytesRead < stat->size && buffer.alignedBufferSize > 0) { auto readSize = std::min<uint32_t>(buffer.alignedBufferSize, stat->size - bytesRead); status = FSAReadFile(getActFsaHandle(), buffer.alignedBuffer, 1, readSize, fileHandle, FSAReadFlag::None); if (status < FSAStatus::OK || static_cast<uint32_t>(status) != readSize) { FSACloseFile(getActFsaHandle(), fileHandle); return ResultFileIoError; } bytesRead += readSize; } if (bytesRead < stat->size && buffer.unalignedAfterBufferSize > 0) { auto readSize = std::min<uint32_t>(buffer.unalignedAfterBufferSize, stat->size - bytesRead); status = FSAReadFile(getActFsaHandle(), buffer.unalignedAfterBuffer, 1, readSize, fileHandle, FSAReadFlag::None); if (status < FSAStatus::OK || static_cast<uint32_t>(status) != readSize) { FSACloseFile(getActFsaHandle(), fileHandle); return ResultFileIoError; } bytesRead += readSize; } FSACloseFile(getActFsaHandle(), fileHandle); command.WriteResponse(bytesRead); return nn::ResultSuccess; } static nn::Result getUuid(CommandHandlerArgs &args) { auto command = ServerCommand<ActClientStandardService::GetUuid> { args }; auto slotNo = InvalidSlot; auto buffer = OutBuffer<Uuid> { }; auto unkArg3 = int32_t { 0 }; command.ReadRequest(slotNo, buffer, unkArg3); if (slotNo == SystemSlot) { auto uuid = Uuid { }; uuid.fill('b'); uuid[0] = 'd'; uuid[1] = 'e'; uuid[2] = 'c'; uuid[3] = 'a'; uuid[4] = 'f'; buffer.writeOutput(uuid.data(), uuid.size()); return nn::ResultSuccess; } auto account = getAccountBySlotNo(slotNo); if (!account) { return ResultAccountNotFound; } buffer.writeOutput(phys_addrof(account->uuid), account->uuid.size()); return nn::ResultSuccess; } nn::Result ActClientStandardService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case GetCommonInfo::command: return getCommonInfo(args); case GetAccountInfo::command: return getAccountInfo(args); case GetTransferableId::command: return getTransferableId(args); case GetMiiImage::command: return getMiiImage(args); case GetUuid::command: return getUuid(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_clientstandardservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/act/nn_act_clientstandardservice.h" namespace ios::fpd::internal { struct ActClientStandardService : ::nn::act::services::ClientStandardService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_server.cpp ================================================ #include "ios_fpd_log.h" #include "ios_fpd_act_accountdata.h" #include "ios_fpd_act_accountloaderservice.h" #include "ios_fpd_act_accountmanagerservice.h" #include "ios_fpd_act_clientstandardservice.h" #include "ios_fpd_act_server.h" #include "ios_fpd_act_serverstandardservice.h" #include "ios/auxil/ios_auxil_usr_cfg_ipc.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" #include "ios/fs/ios_fs_fsa_ipc.h" namespace ios::fpd::internal { using namespace kernel; constexpr auto ActNumMessages = 100u; constexpr auto ActThreadStackSize = 0x18000u; constexpr auto ActThreadPriority = 50u; class ActServer : public nn::ipc::Server { public: ActServer() : nn::ipc::Server(true) { } void intialiseServer() override { auto error = fs::FSAOpen(); if (error < Error::OK) { internal::fpdLog->error("ActServer::intialiseServer: FSAOpen failed with error = {}", error); } else { mFsaHandle = static_cast<Handle>(error); } error = auxil::UCOpen(); if (error < Error::OK) { internal::fpdLog->error("ActServer::intialiseServer: UCOpen failed with error = {}", error); } else { mUserConfigHandle = static_cast<Handle>(error); } initialiseAccounts(); } void finaliseServer() override { if (mFsaHandle >= 0) { fs::FSAClose(mFsaHandle); } } Handle getFsaHandle() { return mFsaHandle; } Handle getUserConfigHandle() { return mUserConfigHandle; } private: be2_val<Handle> mFsaHandle = -1; be2_val<Handle> mUserConfigHandle = -1; }; struct StaticActServerData { be2_struct<ActServer> server; be2_array<Message, ActNumMessages> messageBuffer; be2_array<uint8_t, ActThreadStackSize> threadStack; }; static phys_ptr<StaticActServerData> sActServerData = nullptr; Error startActServer() { auto &server = sActServerData->server; auto result = server.initialise("/dev/act", phys_addrof(sActServerData->messageBuffer), static_cast<uint32_t>(sActServerData->messageBuffer.size())); if (result.failed()) { internal::fpdLog->error( "startActServer: Server initialisation failed for /dev/act, result = {}", result.code()); return Error::FailInternal; } server.registerService<ActClientStandardService>(); server.registerService<ActServerStandardService>(); server.registerService<ActAccountManagerService>(); server.registerService<ActAccountLoaderService>(); // TODO: Register services 4, 5, 6 result = server.start(phys_addrof(sActServerData->threadStack) + sActServerData->threadStack.size(), static_cast<uint32_t>(sActServerData->threadStack.size()), ActThreadPriority); if (result.failed()) { internal::fpdLog->error( "startActServer: Server start failed for /dev/act, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } Handle getActFsaHandle() { return sActServerData->server.getFsaHandle(); } Handle getActUserConfigHandle() { return sActServerData->server.getUserConfigHandle(); } void initialiseStaticActServerData() { sActServerData = allocProcessStatic<StaticActServerData>(); } } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_server.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/ios_ipc.h" namespace ios::fpd::internal { Error startActServer(); Handle getActFsaHandle(); Handle getActUserConfigHandle(); void initialiseStaticActServerData(); } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_serverstandardservice.cpp ================================================ #include "ios_fpd_act_serverstandardservice.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/act/nn_act_result.h" #include "nn/ipc/nn_ipc_result.h" using namespace nn::act; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::fpd::internal { static nn::Result acquireNexServiceToken(CommandHandlerArgs &args) { return ResultUcError; }; static nn::Result cancel(CommandHandlerArgs &args) { auto command = ServerCommand<ActServerStandardService::Cancel> { args }; return nn::ResultSuccess; } nn::Result ActServerStandardService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case Cancel::command: return cancel(args); case AcquireNexServiceToken::command: return acquireNexServiceToken(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_act_serverstandardservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/act/nn_act_serverstandardservice.h" namespace ios::fpd::internal { struct ActServerStandardService : ::nn::act::services::ServerStandardService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fpd/ios_fpd_log.h ================================================ #pragma once #include <common/log.h> namespace ios::fpd::internal { extern Logger fpdLog; } // namespace ios::fpd::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs.cpp ================================================ #include "ios_fs.h" #include "ios_fs_log.h" #include "ios_fs_fsa_async_task.h" #include "ios_fs_fsa_thread.h" #include "ios_fs_service_thread.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_thread.h" #include <common/log.h> namespace ios::fs { constexpr auto LocalHeapSize = 0x56000u; constexpr auto CrossHeapSize = 0x1D80000u; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger fsLog = { }; static void initialiseStaticData() { sLocalHeapBuffer = kernel::allocProcessLocalHeap(LocalHeapSize); } } // namespace internal Error processEntryPoint(phys_ptr<void> context) { // Initialise logger internal::fsLog = decaf::makeLogger("IOS_FS"); // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticFsaAsyncTaskData(); internal::initialiseStaticFsaThreadData(); internal::initialiseStaticServiceThreadData(); // Initialise process heaps auto error = kernel::IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { gLog->error("Failed to create local process heap, error = {}.", error); return error; } error = kernel::IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { gLog->error("Failed to create cross process heap, error = {}.", error); return error; } // TODO: Get clock info from bsp // TODO: Start dk thread // TODO: Start odm thread // Start FSA async task thread. error = internal::startFsaAsyncTaskThread(); if (error < Error::OK) { gLog->error("Failed to start FSA async task thread"); return error; } // Start FSA thread. error = internal::startFsaThread(); if (error < Error::OK) { gLog->error("Failed to start FSA thread"); return error; } // Start service thread. error = internal::startServiceThread(); if (error < Error::OK) { gLog->error("Failed to start Service thread"); return error; } // TODO: Start ramdisk service thread. // Suspend current thread. error = kernel::IOS_GetCurrentThreadId(); if (error < Error::OK) { gLog->error("Failed to get current thread id"); return error; } auto threadId = static_cast<kernel::ThreadId>(error); error = kernel::IOS_SuspendThread(threadId); if (error < Error::OK) { gLog->error("Failed to suspend root fs thread"); return error; } return Error::OK; } } // namespace ios ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs.h ================================================ #pragma once #include "ios/ios_enum.h" #include <libcpu/be2_struct.h> namespace ios::fs { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_enum.h ================================================ #ifndef IOS_FS_ENUM_H #define IOS_FS_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(fs) ENUM_BEG(FSACommand, uint32_t) ENUM_VALUE(Invalid, 0x0) ENUM_VALUE(Mount, 0x1) ENUM_VALUE(Unmount, 0x2) ENUM_VALUE(GetVolumeInfo, 0x3) ENUM_VALUE(GetAttach, 0x4) ENUM_VALUE(ChangeDir, 0x5) ENUM_VALUE(GetCwd, 0x6) ENUM_VALUE(MakeDir, 0x7) ENUM_VALUE(Remove, 0x8) ENUM_VALUE(Rename, 0x9) ENUM_VALUE(OpenDir, 0xA) ENUM_VALUE(ReadDir, 0xB) ENUM_VALUE(RewindDir, 0xC) ENUM_VALUE(CloseDir, 0xD) ENUM_VALUE(OpenFile, 0xE) ENUM_VALUE(ReadFile, 0xF) ENUM_VALUE(WriteFile, 0x10) ENUM_VALUE(GetPosFile, 0x11) ENUM_VALUE(SetPosFile, 0x12) ENUM_VALUE(IsEof, 0x13) ENUM_VALUE(StatFile, 0x14) ENUM_VALUE(CloseFile, 0x15) ENUM_VALUE(GetError, 0x16) ENUM_VALUE(FlushFile, 0x17) ENUM_VALUE(GetInfoByQuery, 0x18) ENUM_VALUE(AppendFile, 0x19) ENUM_VALUE(TruncateFile, 0x1A) ENUM_VALUE(FlushVolume, 0x1B) ENUM_VALUE(RollbackVolume, 0x1C) ENUM_VALUE(MakeQuota, 0x1D) ENUM_VALUE(FlushQuota, 0x1E) ENUM_VALUE(RollbackQuota, 0x1F) ENUM_VALUE(ChangeMode, 0x20) ENUM_VALUE(OpenFileByStat, 0x21) ENUM_VALUE(RegisterFlushQuota, 0x22) ENUM_VALUE(FlushMultiQuota, 0x23) ENUM_VALUE(GetFileBlockAddress, 0x25) ENUM_VALUE(AddUserProcess, 0x65) ENUM_VALUE(DelUserProcess, 0x66) ENUM_VALUE(MountWithProcess, 0x67) ENUM_VALUE(UnmountWithProcess, 0x68) ENUM_VALUE(Format, 0x69) ENUM_VALUE(RawOpen, 0x6A) ENUM_VALUE(RawRead, 0x6B) ENUM_VALUE(RawWrite, 0x6C) ENUM_VALUE(RawClose, 0x6D) ENUM_VALUE(GetLastFailedVolume, 0x6E) ENUM_VALUE(GetVolumeExistence, 0x6F) ENUM_VALUE(ChangeOwner, 0x70) ENUM_VALUE(CancelGetAttach, 0x71) ENUM_VALUE(RemoveQuota, 0x72) ENUM_VALUE(SetClientPriority, 0x73) ENUM_VALUE(ApplyMemoryCache, 0x74) ENUM_VALUE(MakeLink, 0x75) ENUM_VALUE(XferParams, 0x76) ENUM_VALUE(ExecDebugProc, 0x78) ENUM_VALUE(DebugSetTitleId, 0x79) ENUM_VALUE(DebugSetCapability, 0x7A) ENUM_VALUE(SetProcessConfig, 0x82) ENUM_VALUE(ConfigSetMemoryCache, 0x83) ENUM_VALUE(ConfigUnsetMemoryCache, 0x84) ENUM_VALUE(ConfigSetPrf2CharCode, 0x85) ENUM_VALUE(GetProcResourceUsage, 0x8C) ENUM_VALUE(GetAllResourceUsage, 0x8D) ENUM_VALUE(SendProfileCmd, 0x8E) ENUM_END(FSACommand) ENUM_BEG(FSAMediaState, uint32_t) ENUM_VALUE(Ready, 0) ENUM_VALUE(NoMedia, 1) ENUM_VALUE(InvalidMedia, 2) ENUM_VALUE(DirtyMedia, 3) ENUM_VALUE(MediaError, 4) ENUM_END(FSAMediaState) ENUM_BEG(FSAMountPriority, uint32_t) ENUM_VALUE(Base, 1) ENUM_VALUE(RamDiskCache, 4) ENUM_VALUE(TitleUpdate, 9) ENUM_VALUE(UnmountAll, 0x80000000) ENUM_END(FSAMountPriority) ENUM_BEG(FSAQueryInfoType, uint32_t) ENUM_VALUE(FreeSpaceSize, 0) ENUM_VALUE(DirSize, 1) ENUM_VALUE(EntryNum, 2) ENUM_VALUE(FileSystemInfo, 3) ENUM_VALUE(DeviceInfo, 4) ENUM_VALUE(Stat, 5) ENUM_VALUE(BadBlockInfo, 6) ENUM_VALUE(JournalFreeSpaceSize, 7) ENUM_VALUE(FragmentBlockInfo, 8) ENUM_END(FSAQueryInfoType) FLAGS_BEG(FSAReadFlag, uint32_t) FLAGS_VALUE(None, 0x0) FLAGS_VALUE(ReadWithPos, 1 << 0) FLAGS_END(FSAReadFlag) ENUM_BEG(FSAStatFlags, uint32_t) ENUM_VALUE(Quota, 0x40000000) ENUM_VALUE(Directory, 0x80000000) ENUM_END(FSAStatFlags) ENUM_BEG(FSAStatus, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(NotInit, -0x30001) ENUM_VALUE(Busy, -0x30002) ENUM_VALUE(Cancelled, -0x30003) ENUM_VALUE(EndOfDir, -0x30004) ENUM_VALUE(EndOfFile, -0x30005) ENUM_VALUE(MaxMountpoints, -0x30010) ENUM_VALUE(MaxVolumes, -0x30011) ENUM_VALUE(MaxClients, -0x30012) ENUM_VALUE(MaxFiles, -0x30013) ENUM_VALUE(MaxDirs, -0x30014) ENUM_VALUE(AlreadyOpen, -0x30015) ENUM_VALUE(AlreadyExists, -0x30016) ENUM_VALUE(NotFound, -0x30017) ENUM_VALUE(NotEmpty, -0x30018) ENUM_VALUE(AccessError, -0x30019) ENUM_VALUE(PermissionError, -0x3001a) ENUM_VALUE(DataCorrupted, -0x3001b) ENUM_VALUE(StorageFull, -0x3001c) ENUM_VALUE(JournalFull, -0x3001d) ENUM_VALUE(LinkEntry, -0x3001e) ENUM_VALUE(UnavailableCmd, -0x3001f) ENUM_VALUE(UnsupportedCmd, -0x30020) ENUM_VALUE(InvalidParam, -0x30021) ENUM_VALUE(InvalidPath, -0x30022) ENUM_VALUE(InvalidBuffer, -0x30023) ENUM_VALUE(InvalidAlignment, -0x30024) ENUM_VALUE(InvalidClientHandle, -0x30025) ENUM_VALUE(InvalidFileHandle, -0x30026) ENUM_VALUE(InvalidDirHandle, -0x30027) ENUM_VALUE(NotFile, -0x30028) ENUM_VALUE(NotDir, -0x30029) ENUM_VALUE(FileTooBig, -0x3002a) ENUM_VALUE(OutOfRange, -0x3002b) ENUM_VALUE(OutOfResources, -0x3002c) ENUM_VALUE(MediaNotReady, -0x30040) ENUM_VALUE(MediaError, -0x30041) ENUM_VALUE(WriteProtected, -0x30042) ENUM_VALUE(InvalidMedia, -0x30043) ENUM_END(FSAStatus) FLAGS_BEG(FSAWriteFlag, uint32_t) FLAGS_VALUE(None, 0x0) FLAGS_VALUE(WriteWithPos, 0x1) FLAGS_END(FSAWriteFlag) ENUM_BEG(FSResourcePermissions, uint32_t) ENUM_VALUE(SdCardRead, 0x90000) ENUM_VALUE(SdCardWrite, 0xA0000) ENUM_END(FSResourcePermissions) ENUM_NAMESPACE_EXIT(fs) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_FS_ENUM_H ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa.h ================================================ #pragma once #include "ios_fs_enum.h" #include "ios_fs_fsa_request.h" #include "ios_fs_fsa_response.h" #include "ios_fs_fsa_types.h" ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_async_task.cpp ================================================ #include "ios_fs_fsa_device.h" #include "ios_fs_mutex.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/ios_enum.h" #include "ios/ios_handlemanager.h" #include "ios/ios_ipc.h" #include "ios/ios_stackobject.h" #include <mutex> #include <vector> using namespace ios::kernel; using ios::kernel::internal::setInterruptAhbAll; namespace ios::fs::internal { struct CompletedTask { phys_ptr<ResourceRequest> resourceRequest; FSAStatus status; }; struct StaticFsaAsyncData { be2_val<MessageQueueId> messageQueue; be2_array<Message, 64> messageBuffer; be2_val<ThreadId> thread; be2_array<uint8_t, 0x1000> threadStack; }; static std::mutex sCompletedTasksMutex; static std::vector<CompletedTask> sCompletedTasks; static phys_ptr<StaticFsaAsyncData> sFsaAsyncData = nullptr; static void fsaTaskCompleteHandler() { auto completedTasks = std::vector<CompletedTask> { }; sCompletedTasksMutex.lock(); completedTasks.swap(sCompletedTasks); sCompletedTasksMutex.unlock(); for (auto &task : completedTasks) { IOS_ResourceReply(task.resourceRequest, static_cast<Error>(task.status)); } IOS_ClearAndEnable(DeviceId::Sata); } void fsaAsyncTaskComplete(phys_ptr<ResourceRequest> resourceRequest, FSAStatus result) { sCompletedTasksMutex.lock(); sCompletedTasks.push_back({ resourceRequest, result }); sCompletedTasksMutex.unlock(); setInterruptAhbAll(AHBALL::get(0).Sata(true)); } static Error fsaAsyncTaskThread(phys_ptr<void> /*unused*/) { StackObject<Message> message; auto error = IOS_HandleEvent(DeviceId::Sata, sFsaAsyncData->messageQueue, Message { 1 }); if (error < Error::OK) { return error; } error = IOS_ClearAndEnable(DeviceId::Sata); if (error < Error::OK) { return error; } while (true) { error = IOS_ReceiveMessage(sFsaAsyncData->messageQueue, message, MessageFlags::None); if (error < Error::OK) { return error; } switch (*message) { case 1: fsaTaskCompleteHandler(); break; } } } Error startFsaAsyncTaskThread() { auto error = IOS_CreateMessageQueue(phys_addrof(sFsaAsyncData->messageBuffer), static_cast<uint32_t>(sFsaAsyncData->messageBuffer.size())); if (error < Error::OK) { return error; } sFsaAsyncData->messageQueue = static_cast<MessageQueueId>(error); error = IOS_CreateThread(fsaAsyncTaskThread, nullptr, phys_addrof(sFsaAsyncData->threadStack) + sFsaAsyncData->threadStack.size(), static_cast<uint32_t>(sFsaAsyncData->threadStack.size()), 85, ThreadFlags::Detached); if (error < Error::OK) { return error; } sFsaAsyncData->thread = static_cast<ThreadId>(error); kernel::internal::setThreadName(sFsaAsyncData->thread, "FsaAsyncTaskThread"); error = IOS_StartThread(sFsaAsyncData->thread); if (error < Error::OK) { return error; } IOS_YieldCurrentThread(); return Error::OK; } void initialiseStaticFsaAsyncTaskData() { sFsaAsyncData = allocProcessStatic<StaticFsaAsyncData>(); } } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_async_task.h ================================================ #pragma once #include "ios_fs_enum.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/ios_enum.h" namespace ios::fs::internal { Error startFsaAsyncTaskThread(); void initialiseStaticFsaAsyncTaskData(); void fsaAsyncTaskComplete(phys_ptr<ios::kernel::ResourceRequest> resourceRequest, FSAStatus result); } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_device.cpp ================================================ #include "ios_fs_fsa_device.h" #include "ios_fs_log.h" #include "ios/ios.h" #include "vfs/vfs_error.h" #include "vfs/vfs_filehandle.h" #include "vfs/vfs_virtual_device.h" #include <common/strutils.h> #include <libcpu/cpu_formatters.h> namespace ios::fs::internal { FSADevice::FSADevice() : mFS(ios::getFileSystem()) { } FSAStatus FSADevice::translateError(vfs::Error error) const { switch (error) { case vfs::Error::Success: return FSAStatus::OK; case vfs::Error::EndOfDirectory: return FSAStatus::EndOfDir; case vfs::Error::EndOfFile: return FSAStatus::EndOfFile; case vfs::Error::AlreadyExists: return FSAStatus::AlreadyExists; case vfs::Error::InvalidPath: return FSAStatus::InvalidPath; case vfs::Error::InvalidSeekDirection: case vfs::Error::InvalidSeekPosition: case vfs::Error::InvalidTruncatePosition: return FSAStatus::InvalidParam; case vfs::Error::NotDirectory: return FSAStatus::NotDir; case vfs::Error::NotFile: return FSAStatus::NotFile; case vfs::Error::NotFound: return FSAStatus::NotFound; case vfs::Error::NotOpen: return FSAStatus::InvalidFileHandle; case vfs::Error::OperationNotSupported: return FSAStatus::UnsupportedCmd; case vfs::Error::ExecutePermission: case vfs::Error::ReadOnly: case vfs::Error::Permission: return FSAStatus::PermissionError; default: return FSAStatus::MediaError; } } vfs::Path FSADevice::translatePath(phys_ptr<const char> path) const { if (path[0] == '/') { return { path.get() }; } else { return mWorkingPath / path.get(); } } vfs::FileHandle::Mode FSADevice::translateMode(phys_ptr<const char> mode) const { auto result = 0u; if (std::strchr(mode.get(), 'r')) { result |= vfs::FileHandle::Read; } if (std::strchr(mode.get(), 'w')) { result |= vfs::FileHandle::Write; } if (std::strchr(mode.get(), 'a')) { result |= vfs::FileHandle::Append; } if (std::strchr(mode.get(), '+')) { result |= vfs::FileHandle::Update; } return static_cast<vfs::FileHandle::Mode>(result); } void FSADevice::translateStat(const vfs::Status &entry, phys_ptr<FSAStat> stat) const { std::memset(stat.get(), 0, sizeof(FSAStat)); if (entry.flags == vfs::Status::IsDirectory) { stat->flags |= FSAStatFlags::Directory; } if (entry.flags == vfs::Status::HasSize) { stat->size = static_cast<uint32_t>(entry.size); } // TODO: Fill out this. stat->permission = 0x660u; stat->owner = 0u; stat->group = 0u; stat->entryId = 0u; stat->created = 0; stat->modified = 0; } int32_t FSADevice::mapHandle(std::unique_ptr<vfs::FileHandle> file) { auto index = 0; for (index = 0; index < mHandles.size(); ++index) { if (mHandles[index].type == Handle::Unused) { mHandles[index].type = Handle::File; mHandles[index].file = std::move(file); break; } } if (index == mHandles.size()) { mHandles.emplace_back(std::move(file)); } return index + 1; } int32_t FSADevice::mapHandle(vfs::DirectoryIterator folder) { auto index = 0; for (index = 0; index < mHandles.size(); ++index) { if (mHandles[index].type == Handle::Unused) { mHandles[index].type = Handle::Directory; mHandles[index].directory = std::move(folder); break; } } if (index == mHandles.size()) { mHandles.emplace_back(std::move(folder)); } return index + 1; } FSAStatus FSADevice::mapFileHandle(int32_t index, Handle *&handle) { if (index <= 0 || index > mHandles.size()) { return FSAStatus::InvalidFileHandle; } if (mHandles[index - 1].type != Handle::File) { return FSAStatus::NotFile; } handle = &mHandles[index - 1]; return FSAStatus::OK; } FSAStatus FSADevice::mapFolderHandle(int32_t index, Handle *&handle) { if (index <= 0 || index > mHandles.size()) { return FSAStatus::InvalidDirHandle; } if (mHandles[index - 1].type != Handle::Directory) { return FSAStatus::NotFile; } handle = &mHandles[index - 1]; return FSAStatus::OK; } FSAStatus FSADevice::removeHandle(int32_t index, Handle::Type type) { if (index <= 0 || index > mHandles.size()) { if (type == Handle::File) { return FSAStatus::InvalidFileHandle; } else { return FSAStatus::InvalidDirHandle; } } auto &handle = mHandles[index - 1]; if (handle.type != type) { if (type == Handle::File) { return FSAStatus::NotFile; } else { return FSAStatus::NotDir; } } handle.type = Handle::Unused; handle.file = {}; handle.directory = {}; return FSAStatus::OK; } FSAStatus FSADevice::appendFile(vfs::User user, phys_ptr<FSARequestAppendFile> request) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::appendFile[{}] mapFileHandle failed with status {}", request->handle, status); return status; } fsLog->trace("FSADevice::appendFile[{}] count: {} size: {}", request->handle, request->count, request->size); // Seek to (count*size - 1) past end of file then write 1 byte. char emptyByte = 0; auto seekResult = handle->file->seek(vfs::FileHandle::SeekEnd, (request->count * request->size) - 1); if (seekResult != vfs::Error::Success) { fsLog->warn("FSADevice::appendFile[{}] unexpected seekResult {}", request->handle, seekResult); return translateError(seekResult); } auto writeResult = handle->file->write(&emptyByte, 1, 1); if (!writeResult) { fsLog->warn("FSADevice::appendFile[{}] unexpected writeResult {}", request->handle, seekResult); return translateError(writeResult.error()); } return static_cast<FSAStatus>(request->count); } FSAStatus FSADevice::changeDir(vfs::User user, phys_ptr<FSARequestChangeDir> request) { mWorkingPath = translatePath(phys_addrof(request->path)); fsLog->debug("FSADevice::changeDir path: {}, new working path is: {}", phys_addrof(request->path).get(), mWorkingPath.path()); return FSAStatus::OK; } FSAStatus FSADevice::changeMode(vfs::User user, phys_ptr<FSARequestChangeMode> request) { auto path = translatePath(phys_addrof(request->path)); fsLog->debug("FSADevice::changeMode path: {} mode1: {} mode2: {}", path.path(), request->mode1, request->mode2); // TODO: Call mFS->setPermissions return FSAStatus::OK; } FSAStatus FSADevice::closeDir(vfs::User user, phys_ptr<FSARequestCloseDir> request) { fsLog->trace("FSADevice::closeDir[{}]", request->handle); return removeHandle(request->handle, Handle::Directory); } FSAStatus FSADevice::closeFile(vfs::User user, phys_ptr<FSARequestCloseFile> request) { fsLog->trace("FSADevice::closeFile[{}]", request->handle); return removeHandle(request->handle, Handle::File); } FSAStatus FSADevice::flushFile(vfs::User user, phys_ptr<FSARequestFlushFile> request) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::fluseFile[{}] mapFileHandle failed with status {}", request->handle, status); return status; } fsLog->trace("FSADevice::flushFile[{}] success", request->handle); handle->file->flush(); return FSAStatus::OK; } FSAStatus FSADevice::flushQuota(vfs::User user, phys_ptr<FSARequestFlushQuota> request) { auto path = translatePath(phys_addrof(request->path)); fsLog->trace("FSADevice::flushQuota path: {} success", path.path()); return FSAStatus::OK; } FSAStatus FSADevice::getCwd(vfs::User user, phys_ptr<FSAResponseGetCwd> response) { auto cwd = mWorkingPath.path(); if (cwd.empty() || cwd.back() != '/') { cwd.push_back('/'); } string_copy(phys_addrof(response->path).get(), cwd.c_str(), response->path.size()); response->path[response->path.size() - 1] = char { 0 }; fsLog->trace("FSADevice::getCwd cwd: {}", cwd); return FSAStatus::OK; } FSAStatus FSADevice::getInfoByQuery(vfs::User user, phys_ptr<FSARequestGetInfoByQuery> request, phys_ptr<FSAResponseGetInfoByQuery> response) { auto path = translatePath(phys_addrof(request->path)); switch (request->type) { case FSAQueryInfoType::FreeSpaceSize: { if (auto result = mFS->status(user, path); !result) { fsLog->debug("FSADevice::getInfoByQuery cmd: FreeSpaceSize path: {} error: {}", path.path(), result.error()); response->freeSpaceSize = 0ull; } else { fsLog->debug("FSADevice::getInfoByQuery cmd: FreeSpaceSize path: {}", path.path()); response->freeSpaceSize = 4096ull * 1024 * 1024; } break; } case FSAQueryInfoType::Stat: { auto result = mFS->status(user, path); if (!result) { fsLog->debug("FSADevice::getInfoByQuery cmd: Stat path: {} error: {}", path.path(), result.error()); return translateError(result.error()); } fsLog->debug("FSADevice::getInfoByQuery cmd: Stat path: {}", path.path()); translateStat(*result, phys_addrof(response->stat)); break; } default: fsLog->warn("FSADevice::getInfoByQuery unsupported cmd: {} with path: {}", request->type, path.path()); return FSAStatus::UnsupportedCmd; } return FSAStatus::OK; } FSAStatus FSADevice::getPosFile(vfs::User user, phys_ptr<FSARequestGetPosFile> request, phys_ptr<FSAResponseGetPosFile> response) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::getPosFile[{}] mapFileHandle failed with status {}", request->handle, status); return status; } auto result = handle->file->tell(); if (!result) { fsLog->warn("FSADevice::getPosFile[{}] failed with error {}", request->handle, result.error()); return translateError(result.error()); } response->pos = static_cast<uint32_t>(*result); fsLog->trace("FSADevice::getPosFile[{}] pos: {}", request->handle, response->pos); return FSAStatus::OK; } FSAStatus FSADevice::isEof(vfs::User user, phys_ptr<FSARequestIsEof> request) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::isEof[{}] mapFileHandle failed with status {}", request->handle, status); return status; } auto result = handle->file->eof(); if (!result) { fsLog->warn("FSADevice::isEof[{}] eof failed with error {}", request->handle, result.error()); return translateError(result.error()); } if (*result) { status = FSAStatus::EndOfFile; } else { status = FSAStatus::OK; } fsLog->trace("FSADevice::isEof[{}] eof: {}", request->handle, *result ? "true" : "false"); return status; } FSAStatus FSADevice::makeDir(vfs::User user, phys_ptr<FSARequestMakeDir> request) { auto path = translatePath(phys_addrof(request->path)); auto error = mFS->makeFolder(user, path); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::makeDir path: {} permission: {} failed with " "error {}", path.path(), request->permission, error); return translateError(error); } fsLog->debug("FSADevice::makeDir path: {} permission: {} success", path.path(), request->permission); return FSAStatus::OK; } FSAStatus FSADevice::makeQuota(vfs::User user, phys_ptr<FSARequestMakeQuota> request) { auto path = translatePath(phys_addrof(request->path)); auto error = mFS->makeFolder(user, path); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::makeQuota path: {} mode: {} size: {} " "failed with error {}", path.path(), request->mode, request->size, error); return translateError(error); } fsLog->debug("FSADevice::makeQuota path: {} mode: {} size: {} success", path.path(), request->mode, request->size); return FSAStatus::OK; } FSAStatus FSADevice::mount(vfs::User user, phys_ptr<FSARequestMount> request) { auto sourcePath = translatePath(phys_addrof(request->path)); auto targetPath = translatePath(phys_addrof(request->target)); auto linkDevice = mFS->getLinkDevice(user, sourcePath); if (!linkDevice) { fsLog->debug("FSADevice::mount source: {} target: {} getLinkDevice " "failed with error {}", sourcePath.path(), targetPath.path(), linkDevice.error()); return translateError(linkDevice.error()); } auto error = mFS->mountDevice(user, targetPath, std::static_pointer_cast<vfs::Device>(*linkDevice)); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::mount source: {} target: {} mountDevice " "failed with error {}", sourcePath.path(), targetPath.path(), error); return translateError(error); } fsLog->debug("FSADevice::mount source: {} target: {} success", sourcePath.path(), targetPath.path()); return FSAStatus::OK; } FSAStatus FSADevice::mountWithProcess(vfs::User user, phys_ptr<FSARequestMountWithProcess> request) { auto sourcePath = translatePath(phys_addrof(request->path)); auto targetPath = translatePath(phys_addrof(request->target)); auto linkDevice = mFS->getLinkDevice(user, sourcePath); if (!linkDevice) { fsLog->debug("FSADevice::mountWithProcess source: {} target: {} " "priority: {} getLinkDevice failed with error {}", sourcePath.path(), targetPath.path(), request->priority, linkDevice.error()); return translateError(linkDevice.error()); } auto error = mFS->mountOverlayDevice( user, static_cast<vfs::OverlayPriority>(request->priority), targetPath, std::static_pointer_cast<vfs::Device>(*linkDevice)); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::mountWithProcess source: {} target: {} " "priority: {} mountOverlayDevice failed with error {}", sourcePath.path(), targetPath.path(), request->priority, linkDevice.error()); return translateError(error); } fsLog->debug("FSADevice::mountWithProcess source: {} target: {} " "priority: {} success", sourcePath.path(), targetPath.path(), request->priority); return FSAStatus::OK; } FSAStatus FSADevice::openDir(vfs::User user, phys_ptr<FSARequestOpenDir> request, phys_ptr<FSAResponseOpenDir> response) { auto path = translatePath(phys_addrof(request->path)); auto result = mFS->openDirectory(user, path); if (!result) { fsLog->debug("FSADevice::openDir path: {} failed with error {}", path.path(), result.error()); return translateError(result.error()); } response->handle = mapHandle(*result); fsLog->debug("FSADevice::openDir[{}] path: {} handle: {}", response->handle, path.path(), response->handle); return FSAStatus::OK; } FSAStatus FSADevice::openFile(vfs::User user, phys_ptr<FSARequestOpenFile> request, phys_ptr<FSAResponseOpenFile> response) { auto path = translatePath(phys_addrof(request->path)); auto mode = translateMode(phys_addrof(request->mode)); auto result = mFS->openFile(user, path, mode); if (!result) { fsLog->debug("FSADevice::openFile path: {} mode: {} failed with error {}", path.path(), mode, result.error()); return translateError(result.error()); } response->handle = mapHandle(std::move(*result)); fsLog->debug("FSADevice::openFile[{}] path: {} mode: {} handle: {}", response->handle, path.path(), mode, response->handle); return FSAStatus::OK; } FSAStatus FSADevice::readDir(vfs::User user, phys_ptr<FSARequestReadDir> request, phys_ptr<FSAResponseReadDir> response) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFolderHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::readDir[{}] mapFolderHandle failed with status {}", request->handle, status); return status; } auto result = handle->directory.readEntry(); if (!result) { fsLog->debug("FSADevice::readDir[{}] readEntry failed with error {}", request->handle, result.error()); return translateError(result.error()); } translateStat(*result, phys_addrof(response->entry.stat)); string_copy(phys_addrof(response->entry.name).get(), response->entry.name.size(), result->name.c_str(), result->name.size()); fsLog->debug("FSADevice::readDir[{}] name: {}", request->handle, result->name); return FSAStatus::OK; } FSAStatus FSADevice::readFile(vfs::User user, phys_ptr<FSARequestReadFile> request, phys_ptr<uint8_t> buffer, uint32_t bufferLen) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::readFile[{}] mapFileHandle failed with status {}", request->handle, status); return status; } if (request->readFlags & FSAReadFlag::ReadWithPos) { handle->file->seek(vfs::FileHandle::SeekStart, request->pos); } auto result = handle->file->read(buffer.get(), request->size, request->count); if (!result) { if (request->readFlags & FSAReadFlag::ReadWithPos) { fsLog->debug("FSADevice::readFile[{}] size: {} count: {} withPos: {} " "failed with error {}", request->handle, request->size, request->count, request->pos, result.error()); } else { fsLog->debug("FSADevice::readFile[{}] size: {} count: {} failed with " "error {}", request->handle, request->size, request->count, result.error()); } return translateError(result.error()); } auto bytesRead = (*result) * request->size; if (request->readFlags & FSAReadFlag::ReadWithPos) { fsLog->trace("FSADevice::readFile[{}] size: {} count: {} withPos: {} " "read bytes: {}", request->handle, request->size, request->count, request->pos, bytesRead); } else { fsLog->trace("FSADevice::readFile[{}] size: {} count: {} read bytes: {}", request->handle, request->size, request->count, bytesRead); } return static_cast<FSAStatus>(bytesRead); } FSAStatus FSADevice::remove(vfs::User user, phys_ptr<FSARequestRemove> request) { auto path = translatePath(phys_addrof(request->path)); auto error = mFS->remove(user, path); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::remove path: {} failed with error {}", path.path(), error); } else { fsLog->debug("FSADevice::remove path: {} success", path.path()); } return translateError(error); } FSAStatus FSADevice::rename(vfs::User user, phys_ptr<FSARequestRename> request) { auto src = translatePath(phys_addrof(request->oldPath)); auto dst = translatePath(phys_addrof(request->newPath)); auto error = mFS->rename(user, src, dst); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::rename source: {} target: {} failed with error {}", src.path(), dst.path(), error); } else { fsLog->debug("FSADevice::rename source: {} target: {} success", src.path(), dst.path()); } return translateError(error); } FSAStatus FSADevice::rewindDir(vfs::User user, phys_ptr<FSARequestRewindDir> request) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFolderHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::rewindDir[{}] mapFolderHandle failed with status {}", request->handle, status); return status; } auto error = handle->directory.rewind(); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::rewindDir[{}] failed with error {}", error, request->handle); } else { fsLog->debug("FSADevice::rewindDir[{}] success", request->handle); } return translateError(error); } FSAStatus FSADevice::setPosFile(vfs::User user, phys_ptr<FSARequestSetPosFile> request) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::setPosFile[{}] mapFileHandle failed with error {}", request->handle, status); return status; } handle->file->seek(vfs::FileHandle::SeekStart, request->pos); fsLog->trace("FSADevice::setPosFile[{}] pos: {}", request->handle, request->pos); return FSAStatus::OK; } FSAStatus FSADevice::statFile(vfs::User user, phys_ptr<FSARequestStatFile> request, phys_ptr<FSAResponseStatFile> response) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::statFile[{}] mapFileHandle failed with status {}", request->handle, status); return status; } std::memset(phys_addrof(response->stat).get(), 0, sizeof(response->stat)); response->stat.flags = static_cast<FSAStatFlags>(0); response->stat.permission = 0x660u; response->stat.owner = 0u; response->stat.group = 0u; response->stat.entryId = 0u; response->stat.created = 0; response->stat.modified = 0; auto result = handle->file->size(); if (result) { response->stat.size = static_cast<uint32_t>(*result); } fsLog->trace("FSADevice::statFile[{}] size: {}", request->handle, response->stat.size); return FSAStatus::OK; } FSAStatus FSADevice::truncateFile(vfs::User user, phys_ptr<FSARequestTruncateFile> request) { auto handle = static_cast<Handle *>(nullptr); auto status = mapFileHandle(request->handle, handle); if (status < 0) { fsLog->info("FSADevice::truncateFile[{}] mapFileHandle failed with status {}", request->handle, status); return status; } auto result = handle->file->truncate(); if (!result) { fsLog->info("FSADevice::truncateFile[{}] truncate failed with error {}", request->handle, result.error()); return translateError(result.error()); } fsLog->trace("FSADevice::truncateFile[{}] success", request->handle); return FSAStatus::OK; } FSAStatus FSADevice::unmount(vfs::User user, phys_ptr<FSARequestUnmount> request) { auto path = translatePath(phys_addrof(request->path)); auto error = mFS->unmountDevice(user, path); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::unmount path: {} failed with error {}", path.path(), error); } else { fsLog->debug("FSADevice::unmount path: {} success", path.path()); } return translateError(error); } FSAStatus FSADevice::unmountWithProcess(vfs::User user, phys_ptr<FSARequestUnmountWithProcess> request) { auto path = translatePath(phys_addrof(request->path)); if (request->priority == FSAMountPriority::UnmountAll) { // unmountDevice will unmount the base overlay device, thus unmounting // all overlay devices at path. auto error = mFS->unmountDevice(user, path); if (error != vfs::Error::Success) { fsLog->debug("FSADevice::unmountWithProcess path: {} priority: {} " "failed with error {}", path.path(), request->priority, error); } else { fsLog->debug("FSADevice::unmountWithProcess path: {} priority: {} ", "success", path.path(), request->priority); } return translateError(error); } else { fsLog->warn("FSADevice::unmountWithProcess path: {} priority: {} " "unsupported", path.path(), request->priority); // TODO: unmount overlay device with priority request->priority return FSAStatus::UnsupportedCmd; } } FSAStatus FSADevice::writeFile(vfs::User user, phys_ptr<FSARequestWriteFile> request, phys_ptr<const uint8_t> buffer, uint32_t bufferLen) { auto handle = static_cast<Handle *>(nullptr); auto error = mapFileHandle(request->handle, handle); if (error < 0) { fsLog->info("FSADevice::writeFile[{}] mapFileHandle failed with error {}", request->handle, error); return error; } if (request->writeFlags & FSAWriteFlag::WriteWithPos) { handle->file->seek(vfs::FileHandle::SeekStart, request->pos); } auto result = handle->file->write(buffer.get(), request->size, request->count); if (!result) { if (request->writeFlags & FSAWriteFlag::WriteWithPos) { fsLog->debug("FSADevice::writeFile[{}] size: {} count: {} withPos: {} " "failed with error {}", request->handle, request->size, request->count, request->pos, result.error()); } else { fsLog->debug("FSADevice::writeFile[{}] size: {} count: {} failed with " "error {}", request->handle, request->size, request->count, result.error()); } return translateError(result.error()); } auto bytesWritten = (*result) * request->size; if (request->writeFlags & FSAWriteFlag::WriteWithPos) { fsLog->trace("FSADevice::writeFile[{}] size: {} count: {} withPos: {} " "write bytes: {}", request->handle, request->size, request->count, request->pos, bytesWritten); } else { fsLog->trace("FSADevice::writeFile[{}] size: {} count: {} write bytes: {}", request->handle, request->size, request->count, bytesWritten); } return static_cast<FSAStatus>(bytesWritten); } } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_device.h ================================================ #pragma once #include "ios_fs_enum.h" #include "ios_fs_fsa_request.h" #include "ios_fs_fsa_response.h" #include "ios_fs_fsa_types.h" #include "ios/ios_device.h" #include "vfs/vfs_device.h" #include "vfs/vfs_permissions.h" #include <common/structsize.h> #include <vector> #include <libcpu/be2_struct.h> #include <memory> namespace ios::fs::internal { /** * \ingroup ios_fs * @{ */ class FSADevice { struct Handle { enum Type { Unused, File, Directory }; Handle() : type(Unused) { } Handle(std::unique_ptr<vfs::FileHandle> file) : type(File), file(std::move(file)) { } Handle(vfs::DirectoryIterator itr) : type(Directory), directory(std::move(itr)) { } Type type; std::unique_ptr<vfs::FileHandle> file; vfs::DirectoryIterator directory; }; public: FSADevice(); FSAStatus appendFile(vfs::User user, phys_ptr<FSARequestAppendFile> request); FSAStatus changeDir(vfs::User user, phys_ptr<FSARequestChangeDir> request); FSAStatus changeMode(vfs::User user, phys_ptr<FSARequestChangeMode> request); FSAStatus closeDir(vfs::User user, phys_ptr<FSARequestCloseDir> request); FSAStatus closeFile(vfs::User user, phys_ptr<FSARequestCloseFile> request); FSAStatus flushFile(vfs::User user, phys_ptr<FSARequestFlushFile> request); FSAStatus flushQuota(vfs::User user, phys_ptr<FSARequestFlushQuota> request); FSAStatus getCwd(vfs::User user, phys_ptr<FSAResponseGetCwd> response); FSAStatus getInfoByQuery(vfs::User user, phys_ptr<FSARequestGetInfoByQuery> request, phys_ptr<FSAResponseGetInfoByQuery> response); FSAStatus getPosFile(vfs::User user, phys_ptr<FSARequestGetPosFile> request, phys_ptr<FSAResponseGetPosFile> response); FSAStatus isEof(vfs::User user, phys_ptr<FSARequestIsEof> request); FSAStatus makeDir(vfs::User user, phys_ptr<FSARequestMakeDir> request); FSAStatus makeQuota(vfs::User user, phys_ptr<FSARequestMakeQuota> request); FSAStatus mount(vfs::User user, phys_ptr<FSARequestMount> request); FSAStatus mountWithProcess(vfs::User user, phys_ptr<FSARequestMountWithProcess> request); FSAStatus openDir(vfs::User user, phys_ptr<FSARequestOpenDir> request, phys_ptr<FSAResponseOpenDir> response); FSAStatus openFile(vfs::User user, phys_ptr<FSARequestOpenFile> request, phys_ptr<FSAResponseOpenFile> response); FSAStatus readDir(vfs::User user, phys_ptr<FSARequestReadDir> request, phys_ptr<FSAResponseReadDir> response); FSAStatus readFile(vfs::User user, phys_ptr<FSARequestReadFile> request, phys_ptr<uint8_t> buffer, uint32_t bufferLen); FSAStatus remove(vfs::User user, phys_ptr<FSARequestRemove> request); FSAStatus rename(vfs::User user, phys_ptr<FSARequestRename> request); FSAStatus rewindDir(vfs::User user, phys_ptr<FSARequestRewindDir> request); FSAStatus setPosFile(vfs::User user, phys_ptr<FSARequestSetPosFile> request); FSAStatus statFile(vfs::User user, phys_ptr<FSARequestStatFile> request, phys_ptr<FSAResponseStatFile> response); FSAStatus truncateFile(vfs::User user, phys_ptr<FSARequestTruncateFile> request); FSAStatus unmount(vfs::User user, phys_ptr<FSARequestUnmount> request); FSAStatus unmountWithProcess(vfs::User user, phys_ptr<FSARequestUnmountWithProcess> request); FSAStatus writeFile(vfs::User user, phys_ptr<FSARequestWriteFile> request, phys_ptr<const uint8_t> buffer, uint32_t bufferLen); private: FSAStatus translateError(vfs::Error error) const; vfs::Path translatePath(phys_ptr<const char> path) const; vfs::FileHandle::Mode translateMode(phys_ptr<const char> mode) const; void translateStat(const vfs::Status &entry, phys_ptr<FSAStat> stat) const; int32_t mapHandle(std::unique_ptr<vfs::FileHandle> file); int32_t mapHandle(vfs::DirectoryIterator folder); FSAStatus mapFileHandle(int32_t handle, Handle *&file); FSAStatus mapFolderHandle(int32_t handle, Handle *&folder); FSAStatus removeHandle(int32_t index, Handle::Type type); private: std::shared_ptr<vfs::Device> mFS; vfs::Path mWorkingPath = "/"; std::vector<Handle> mHandles; }; /** @} */ } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_ipc.cpp ================================================ #include "ios_fs_fsa_ipc.h" #include "ios_fs_fsa_request.h" #include "ios_fs_fsa_response.h" #include "ios_fs_fsa_types.h" #include "ios/ios_stackobject.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" #include <common/strutils.h> namespace ios::fs { using namespace kernel; #pragma pack(push, 1) struct FSAIpcData { be2_struct<FSARequest> request; be2_struct<FSAResponse> response; be2_array<IoctlVec, 4> vecs; be2_val<FSACommand> command; be2_val<ResourceHandleId> resourceHandle; UNKNOWN(0x828 - 0x7EB); }; CHECK_SIZE(FSAIpcData, 0x828); #pragma pack(pop) static FSAStatus allocFsaIpcData(phys_ptr<FSAIpcData> *outIpcData) { auto buffer = IOS_HeapAlloc(CrossProcessHeapId, sizeof(FSAIpcData)); if (!buffer) { return FSAStatus::OutOfResources; } std::memset(buffer.get(), 0, sizeof(FSAIpcData)); *outIpcData = phys_cast<FSAIpcData *>(buffer); return FSAStatus::OK; } static void freeFsaIpcData(phys_ptr<FSAIpcData> ipcData) { IOS_HeapFree(CrossProcessHeapId, ipcData); } Error FSAOpen() { return IOS_Open("/dev/fsa", OpenMode::None); } Error FSAClose(FSAHandle handle) { return IOS_Close(handle); } FSAStatus FSAOpenDir(FSAHandle handle, std::string_view name, FSADirHandle *outHandle) { phys_ptr<FSAIpcData> ipcData; if (name.size() == 0) { return FSAStatus::InvalidPath; } if (!outHandle) { return FSAStatus::InvalidBuffer; } auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::OpenDir; ipcData->resourceHandle = handle; auto request = phys_addrof(ipcData->request); string_copy(phys_addrof(request->openDir.path).get(), name.data(), request->openDir.path.size()); auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); auto response = phys_addrof(ipcData->response); *outHandle = response->openDir.handle; freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSACloseDir(FSAHandle handle, FSADirHandle dirHandle) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::CloseDir; ipcData->resourceHandle = handle; auto request = phys_addrof(ipcData->request); request->closeDir.handle = dirHandle; auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAOpenFile(FSAHandle handle, std::string_view name, std::string_view mode, FSAFileHandle *outHandle) { phys_ptr<FSAIpcData> ipcData; if (name.size() == 0) { return FSAStatus::InvalidPath; } if (mode.size() == 0) { return FSAStatus::InvalidParam; } if (!outHandle) { return FSAStatus::InvalidBuffer; } auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::OpenFile; ipcData->resourceHandle = handle; auto request = phys_addrof(ipcData->request); string_copy(phys_addrof(request->openFile.path).get(), name.data(), request->openFile.path.size()); string_copy(phys_addrof(request->openFile.mode).get(), mode.data(), request->openFile.mode.size()); request->openFile.unk0x290 = 0x60000u; auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); auto response = phys_addrof(ipcData->response); *outHandle = response->openFile.handle; freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSACloseFile(FSAHandle handle, FSAFileHandle fileHandle) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::CloseFile; ipcData->resourceHandle = handle; auto request = phys_addrof(ipcData->request); request->closeFile.handle = fileHandle; auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } static FSAStatus readFile(FSAHandle handle, phys_ptr<void> buffer, uint32_t size, uint32_t count, uint32_t pos, FSAFileHandle fileHandle, FSAReadFlag readFlags) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::ReadFile; ipcData->resourceHandle = handle; auto request = phys_addrof(ipcData->request); request->readFile.handle = fileHandle; request->readFile.size = size; request->readFile.count = count; request->readFile.pos = pos; request->readFile.readFlags = readFlags; auto &vecs = ipcData->vecs; vecs[0].paddr = phys_cast<phys_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest)); vecs[1].paddr = phys_cast<phys_addr>(buffer); vecs[1].len = size * count; vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response)); vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse)); auto error = IOS_Ioctlv(ipcData->resourceHandle, ipcData->command, 1u, 2u, phys_addrof(ipcData->vecs)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAReadFile(FSAHandle handle, phys_ptr<void> buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAReadFlag readFlags) { return readFile(handle, buffer, size, count, 0, fileHandle, readFlags); } FSAStatus FSAReadFileWithPos(FSAHandle handle, phys_ptr<void> buffer, uint32_t size, uint32_t count, uint32_t pos, FSAFileHandle fileHandle, FSAReadFlag readFlags) { return readFile(handle, buffer, size, count, pos, fileHandle, readFlags | FSAReadFlag::ReadWithPos); } FSAStatus FSAWriteFile(FSAHandle handle, phys_ptr<const void> buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAWriteFlag writeFlags) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::WriteFile; ipcData->resourceHandle = handle; auto request = phys_addrof(ipcData->request); request->writeFile.handle = fileHandle; request->writeFile.size = size; request->writeFile.count = count; request->writeFile.writeFlags = writeFlags; auto &vecs = ipcData->vecs; vecs[0].paddr = phys_cast<phys_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest)); vecs[1].paddr = phys_cast<phys_addr>(buffer); vecs[1].len = size * count; vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response)); vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse)); auto error = IOS_Ioctlv(ipcData->resourceHandle, ipcData->command, 2u, 1u, phys_addrof(ipcData->vecs)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAStatFile(FSAHandle handle, FSAFileHandle fileHandle, phys_ptr<FSAStat> stat) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::StatFile; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); request->statFile.handle = fileHandle; // Perform ioctl auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); // Copy FSAStat auto response = phys_addrof(ipcData->response); std::memcpy(stat.get(), phys_addrof(response->statFile.stat).get(), sizeof(FSAStat)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSARemove(FSAHandle handle, std::string_view name) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::Remove; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); string_copy(phys_addrof(request->remove.path).get(), name.data(), request->remove.path.size()); // Perform ioctl auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAMakeDir(FSAHandle handle, std::string_view name, uint32_t mode) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::MakeDir; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); request->makeDir.permission = mode; string_copy(phys_addrof(request->makeDir.path).get(), name.data(), request->makeDir.path.size()); // Perform ioctl auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAMakeQuota(FSAHandle handle, std::string_view name, uint32_t mode, uint64_t quota) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::MakeQuota; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); request->makeQuota.mode = mode; request->makeQuota.size = quota; string_copy(phys_addrof(request->makeQuota.path).get(), name.data(), request->makeQuota.path.size()); // Perform ioctl auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAMount(FSAHandle handle, std::string_view src, std::string_view dst, uint32_t unk0x500, phys_ptr<void> unkBuf, uint32_t unkBufLen) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::Mount; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); string_copy(phys_addrof(request->mount.path).get(), src.data(), request->mount.path.size()); string_copy(phys_addrof(request->mount.target).get(), dst.data(), request->mount.target.size()); request->mount.unk0x500 = unk0x500; request->mount.unkBuf = nullptr; request->mount.unkBufLen = unkBufLen; // Perform ioctlv auto &vecs = ipcData->vecs; vecs[0].paddr = phys_cast<phys_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest)); vecs[1].paddr = phys_cast<phys_addr>(unkBuf); vecs[1].len = unkBufLen; vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response)); vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse)); auto error = IOS_Ioctlv(ipcData->resourceHandle, ipcData->command, 2u, 1u, phys_addrof(ipcData->vecs)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAMountWithProcess(FSAHandle handle, std::string_view src, std::string_view dst, FSAMountPriority priority, FSAProcessInfo *process, phys_ptr<void> unkBuf, uint32_t unkBufLen) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::MountWithProcess; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); string_copy(phys_addrof(request->mountWithProcess.path).get(), src.data(), request->mountWithProcess.path.size()); string_copy(phys_addrof(request->mountWithProcess.target).get(), dst.data(), request->mountWithProcess.target.size()); request->mountWithProcess.process = *process; request->mountWithProcess.priority = priority; request->mountWithProcess.unkBuf = nullptr; request->mountWithProcess.unkBufLen = unkBufLen; // Perform ioctlv auto &vecs = ipcData->vecs; vecs[0].paddr = phys_cast<phys_addr>(request); vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest)); vecs[1].paddr = phys_cast<phys_addr>(unkBuf); vecs[1].len = unkBufLen; vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response)); vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse)); auto error = IOS_Ioctlv(ipcData->resourceHandle, ipcData->command, 2u, 1u, phys_addrof(ipcData->vecs)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAUnmountWithProcess(FSAHandle handle, std::string_view path, FSAMountPriority priority, FSAProcessInfo *process) { phys_ptr<FSAIpcData> ipcData; auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::UnmountWithProcess; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); request->unmountWithProcess.path = path; request->unmountWithProcess.priority = priority; request->unmountWithProcess.process = *process; // Perform ioctl auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAGetInfoByQuery(FSAHandle handle, std::string_view name, FSAQueryInfoType query, phys_ptr<void> output) { phys_ptr<FSAIpcData> ipcData; if (name.empty()) { return FSAStatus::InvalidPath; } if (query > FSAQueryInfoType::FragmentBlockInfo) { return FSAStatus::InvalidParam; } if (!output) { return FSAStatus::InvalidBuffer; } auto status = allocFsaIpcData(&ipcData); if (status < FSAStatus::OK) { return status; } ipcData->command = FSACommand::GetInfoByQuery; ipcData->resourceHandle = handle; // Setup request auto request = phys_addrof(ipcData->request); request->getInfoByQuery.type = query; string_copy(phys_addrof(request->getInfoByQuery.path).get(), name.data(), request->getInfoByQuery.path.size()); // Perform ioctl auto error = IOS_Ioctl(ipcData->resourceHandle, ipcData->command, phys_addrof(ipcData->request), sizeof(FSARequest), phys_addrof(ipcData->response), sizeof(FSAResponse)); if (error >= Error::OK) { auto &response = ipcData->response.getInfoByQuery; switch (query) { case FSAQueryInfoType::FreeSpaceSize: std::memcpy(output.get(), phys_addrof(response.freeSpaceSize).get(), sizeof(response.freeSpaceSize)); break; case FSAQueryInfoType::DirSize: std::memcpy(output.get(), phys_addrof(response.dirSize).get(), sizeof(response.dirSize)); break; case FSAQueryInfoType::EntryNum: std::memcpy(output.get(), phys_addrof(response.entryNum).get(), sizeof(response.entryNum)); break; case FSAQueryInfoType::FileSystemInfo: std::memcpy(output.get(), phys_addrof(response.fileSystemInfo).get(), sizeof(response.fileSystemInfo)); break; case FSAQueryInfoType::DeviceInfo: std::memcpy(output.get(), phys_addrof(response.deviceInfo).get(), sizeof(response.deviceInfo)); break; case FSAQueryInfoType::Stat: std::memcpy(output.get(), phys_addrof(response.stat).get(), sizeof(response.stat)); break; case FSAQueryInfoType::BadBlockInfo: std::memcpy(output.get(), phys_addrof(response.badBlockInfo).get(), sizeof(response.badBlockInfo)); break; case FSAQueryInfoType::JournalFreeSpaceSize: std::memcpy(output.get(), phys_addrof(response.journalFreeSpaceSize).get(), sizeof(response.journalFreeSpaceSize)); break; case FSAQueryInfoType::FragmentBlockInfo: std::memcpy(output.get(), phys_addrof(response.fragmentBlockInfo).get(), sizeof(response.fragmentBlockInfo)); break; } } freeFsaIpcData(ipcData); return static_cast<FSAStatus>(error); } FSAStatus FSAGetStat(FSAHandle handle, std::string_view name, phys_ptr<FSAStat> output) { return FSAGetInfoByQuery(handle, name, FSAQueryInfoType::Stat, output); } FSAStatus FSAReadFileIntoCrossProcessHeap(FSAHandle fsaHandle, std::string_view filename, uint32_t *outBytesRead, phys_ptr<uint8_t> *outBuffer) { StackObject<FSAStat> stat; FSAFileHandle fileHandle; *outBytesRead = 0u; *outBuffer = nullptr; auto status = FSAOpenFile(fsaHandle, filename, "r", &fileHandle); if (status < FSAStatus::OK) { return status; } status = FSAStatFile(fsaHandle, fileHandle, stat); if (status < FSAStatus::OK) { FSACloseFile(fsaHandle, fileHandle); return status; } auto fileData = IOS_HeapAllocAligned(CrossProcessHeapId, stat->size, 0x40u); if (!fileData) { FSACloseFile(fsaHandle, fileHandle); return FSAStatus::OutOfResources; } status = FSAReadFile(fsaHandle, fileData, stat->size, 1, fileHandle, FSAReadFlag::None); if (status < FSAStatus::OK) { IOS_HeapFree(CrossProcessHeapId, fileData); FSACloseFile(fsaHandle, fileHandle); return status; } status = FSACloseFile(fsaHandle, fileHandle); if (status < FSAStatus::OK) { IOS_HeapFree(CrossProcessHeapId, fileData); return status; } *outBytesRead = stat->size; *outBuffer = phys_cast<uint8_t *>(fileData); return status; } } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_ipc.h ================================================ #pragma once #include "ios_fs_fsa_types.h" #include "ios/kernel/ios_kernel_ipc.h" namespace ios::fs { using FSAHandle = kernel::ResourceHandleId; Error FSAOpen(); Error FSAClose(FSAHandle handle); FSAStatus FSAOpenDir(FSAHandle handle, std::string_view name, FSADirHandle *outHandle); FSAStatus FSACloseDir(FSAHandle handle, FSADirHandle dirHandle); FSAStatus FSAOpenFile(FSAHandle handle, std::string_view name, std::string_view mode, FSAFileHandle *outHandle); FSAStatus FSACloseFile(FSAHandle handle, FSAFileHandle fileHandle); FSAStatus FSAReadFile(FSAHandle handle, phys_ptr<void> buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAReadFlag readFlags); FSAStatus FSAReadFileWithPos(FSAHandle handle, phys_ptr<void> buffer, uint32_t size, uint32_t count, uint32_t pos, FSAFileHandle fileHandle, FSAReadFlag readFlags); FSAStatus FSAWriteFile(FSAHandle handle, phys_ptr<const void> buffer, uint32_t size, uint32_t count, FSAFileHandle fileHandle, FSAWriteFlag writeFlags); FSAStatus FSAStatFile(FSAHandle handle, FSAFileHandle fileHandle, phys_ptr<FSAStat> stat); FSAStatus FSARemove(FSAHandle handle, std::string_view name); FSAStatus FSAMakeDir(FSAHandle handle, std::string_view name, uint32_t mode); FSAStatus FSAMakeQuota(FSAHandle handle, std::string_view name, uint32_t mode, uint64_t quota); FSAStatus FSAMount(FSAHandle handle, std::string_view src, std::string_view dst, uint32_t unk0x500, phys_ptr<void> unkBuf, uint32_t unkBufLen); FSAStatus FSAMountWithProcess(FSAHandle handle, std::string_view src, std::string_view dst, FSAMountPriority priority, FSAProcessInfo *process, phys_ptr<void> unkBuf, uint32_t unkBufLen); FSAStatus FSAUnmountWithProcess(FSAHandle handle, std::string_view path, FSAMountPriority priority, FSAProcessInfo *process); FSAStatus FSAGetInfoByQuery(FSAHandle handle, std::string_view name, FSAQueryInfoType query, phys_ptr<void> output); FSAStatus FSAGetStat(FSAHandle handle, std::string_view name, phys_ptr<FSAStat> output); FSAStatus FSAReadFileIntoCrossProcessHeap(FSAHandle handle, std::string_view name, uint32_t *outBytesRead, phys_ptr<uint8_t> *outBuffer); } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_request.h ================================================ #pragma once #include "ios_fs_enum.h" #include "ios_fs_fsa_types.h" #include <cstdint> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::fs { /** * \ingroup ios_fs * @{ */ #pragma pack(push, 1) /** * Request data for Command::AppendFile */ struct FSARequestAppendFile { be2_val<uint32_t> size; be2_val<uint32_t> count; be2_val<FSAFileHandle> handle; be2_val<uint32_t> unk0x0C; }; CHECK_OFFSET(FSARequestAppendFile, 0x0, size); CHECK_OFFSET(FSARequestAppendFile, 0x4, count); CHECK_OFFSET(FSARequestAppendFile, 0x8, handle); CHECK_OFFSET(FSARequestAppendFile, 0xC, unk0x0C); CHECK_SIZE(FSARequestAppendFile, 0x10); /** * Request data for Command::ChangeDir */ struct FSARequestChangeDir { be2_array<char, FSAPathLength + 1> path; }; CHECK_OFFSET(FSARequestChangeDir, 0x0, path); CHECK_SIZE(FSARequestChangeDir, 0x280); /** * Request data for Command::ChangeMode */ struct FSARequestChangeMode { be2_array<char, FSAPathLength + 1> path; be2_val<uint32_t> mode1; be2_val<uint32_t> mode2; }; CHECK_OFFSET(FSARequestChangeMode, 0x0, path); CHECK_OFFSET(FSARequestChangeMode, 0x280, mode1); CHECK_OFFSET(FSARequestChangeMode, 0x284, mode2); CHECK_SIZE(FSARequestChangeMode, 0x288); /** * Request data for Command::CloseDir */ struct FSARequestCloseDir { be2_val<FSADirHandle> handle; }; CHECK_OFFSET(FSARequestCloseDir, 0x0, handle); CHECK_SIZE(FSARequestCloseDir, 0x4); /** * Request data for Command::CloseFile */ struct FSARequestCloseFile { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSARequestCloseFile, 0x0, handle); CHECK_SIZE(FSARequestCloseFile, 0x4); /** * Request data for Command::FlushFile */ struct FSARequestFlushFile { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSARequestFlushFile, 0x0, handle); CHECK_SIZE(FSARequestFlushFile, 0x4); /** * Request data for Command::FlushQuota */ struct FSARequestFlushQuota { be2_array<char, FSAPathLength + 1> path; }; CHECK_OFFSET(FSARequestFlushQuota, 0x0, path); CHECK_SIZE(FSARequestFlushQuota, 0x280); /** * Request data for Command::GetInfoByQuery */ struct FSARequestGetInfoByQuery { be2_array<char, FSAPathLength + 1> path; be2_val<FSAQueryInfoType> type; }; CHECK_OFFSET(FSARequestGetInfoByQuery, 0x0, path); CHECK_OFFSET(FSARequestGetInfoByQuery, 0x280, type); CHECK_SIZE(FSARequestGetInfoByQuery, 0x284); /** * Request data for Command::GetPosFile */ struct FSARequestGetPosFile { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSARequestGetPosFile, 0x0, handle); CHECK_SIZE(FSARequestGetPosFile, 0x4); /** * Request data for Command::IsEof */ struct FSARequestIsEof { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSARequestIsEof, 0x0, handle); CHECK_SIZE(FSARequestIsEof, 0x4); /** * Request data for Command::MakeDir */ struct FSARequestMakeDir { be2_array<char, FSAPathLength + 1> path; be2_val<uint32_t> permission; }; CHECK_OFFSET(FSARequestMakeDir, 0x0, path); CHECK_OFFSET(FSARequestMakeDir, 0x280, permission); CHECK_SIZE(FSARequestMakeDir, 0x284); /** * Request data for Command::MakeQuota */ struct FSARequestMakeQuota { be2_array<char, FSAPathLength + 1> path; be2_val<uint32_t> mode; be2_val<uint64_t> size; }; CHECK_OFFSET(FSARequestMakeQuota, 0x0, path); CHECK_OFFSET(FSARequestMakeQuota, 0x280, mode); CHECK_OFFSET(FSARequestMakeQuota, 0x284, size); CHECK_SIZE(FSARequestMakeQuota, 0x28C); /** * Request data for Command::Mount */ struct FSARequestMount { be2_array<char, FSAPathLength + 1> path; be2_array<char, FSAPathLength + 1> target; be2_val<uint32_t> unk0x500; be2_virt_ptr<void> unkBuf; be2_val<uint32_t> unkBufLen; }; CHECK_OFFSET(FSARequestMount, 0x0, path); CHECK_OFFSET(FSARequestMount, 0x280, target); CHECK_OFFSET(FSARequestMount, 0x500, unk0x500); CHECK_OFFSET(FSARequestMount, 0x504, unkBuf); CHECK_OFFSET(FSARequestMount, 0x508, unkBufLen); CHECK_SIZE(FSARequestMount, 0x50C); /** * Request data for Command::MountWithProcess */ struct FSARequestMountWithProcess { be2_array<char, FSAPathLength + 1> path; be2_array<char, FSAPathLength + 1> target; be2_val<FSAMountPriority> priority; be2_struct<FSAProcessInfo> process; be2_virt_ptr<void> unkBuf; be2_val<uint32_t> unkBufLen; }; CHECK_OFFSET(FSARequestMountWithProcess, 0x0, path); CHECK_OFFSET(FSARequestMountWithProcess, 0x280, target); CHECK_OFFSET(FSARequestMountWithProcess, 0x500, priority); CHECK_OFFSET(FSARequestMountWithProcess, 0x504, process); CHECK_OFFSET(FSARequestMountWithProcess, 0x514, unkBuf); CHECK_OFFSET(FSARequestMountWithProcess, 0x518, unkBufLen); CHECK_SIZE(FSARequestMountWithProcess, 0x51C); /** * Request data for Command::OpenDir */ struct FSARequestOpenDir { be2_array<char, FSAPathLength + 1> path; }; CHECK_OFFSET(FSARequestOpenDir, 0x0, path); CHECK_SIZE(FSARequestOpenDir, 0x280); /** * Request data for Command::OpenFile */ struct FSARequestOpenFile { be2_array<char, FSAPathLength + 1> path; be2_array<char, FSAModeLength> mode; be2_val<uint32_t> unk0x290; be2_val<uint32_t> unk0x294; be2_val<uint32_t> unk0x298; }; CHECK_OFFSET(FSARequestOpenFile, 0x0, path); CHECK_OFFSET(FSARequestOpenFile, 0x280, mode); CHECK_OFFSET(FSARequestOpenFile, 0x290, unk0x290); CHECK_OFFSET(FSARequestOpenFile, 0x294, unk0x294); CHECK_OFFSET(FSARequestOpenFile, 0x298, unk0x298); CHECK_SIZE(FSARequestOpenFile, 0x29C); /** * Request data for Command::ReadDir */ struct FSARequestReadDir { be2_val<FSADirHandle> handle; }; CHECK_OFFSET(FSARequestReadDir, 0x0, handle); CHECK_SIZE(FSARequestReadDir, 0x4); /** * Request data for Command::ReadFile */ struct FSARequestReadFile { //! Virtual pointer used only by Cafe, for IOS we should use ioctlv.vecs[1] be2_virt_ptr<uint8_t> buffer; be2_val<uint32_t> size; be2_val<uint32_t> count; be2_val<FSAFilePosition> pos; be2_val<FSAFileHandle> handle; be2_val<FSAReadFlag> readFlags; }; CHECK_OFFSET(FSARequestReadFile, 0x00, buffer); CHECK_OFFSET(FSARequestReadFile, 0x04, size); CHECK_OFFSET(FSARequestReadFile, 0x08, count); CHECK_OFFSET(FSARequestReadFile, 0x0C, pos); CHECK_OFFSET(FSARequestReadFile, 0x10, handle); CHECK_OFFSET(FSARequestReadFile, 0x14, readFlags); CHECK_SIZE(FSARequestReadFile, 0x18); /** * Request data for Command::Remove */ struct FSARequestRemove { be2_array<char, FSAPathLength + 1> path; }; CHECK_OFFSET(FSARequestRemove, 0x0, path); CHECK_SIZE(FSARequestRemove, 0x280); /** * Request data for Command::Rename */ struct FSARequestRename { be2_array<char, FSAPathLength + 1> oldPath; be2_array<char, FSAPathLength + 1> newPath; }; CHECK_OFFSET(FSARequestRename, 0x0, oldPath); CHECK_OFFSET(FSARequestRename, 0x280, newPath); CHECK_SIZE(FSARequestRename, 0x500); /** * Request data for Command::RewindDir */ struct FSARequestRewindDir { be2_val<FSADirHandle> handle; }; CHECK_OFFSET(FSARequestRewindDir, 0x0, handle); CHECK_SIZE(FSARequestRewindDir, 0x4); /** * Request data for Command::SetPosFile */ struct FSARequestSetPosFile { be2_val<FSAFileHandle> handle; be2_val<FSAFilePosition> pos; }; CHECK_OFFSET(FSARequestSetPosFile, 0x0, handle); CHECK_OFFSET(FSARequestSetPosFile, 0x4, pos); CHECK_SIZE(FSARequestSetPosFile, 0x8); /** * Request data for Command::StatFile */ struct FSARequestStatFile { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSARequestStatFile, 0x0, handle); CHECK_SIZE(FSARequestStatFile, 0x4); /** * Request data for Command::TruncateFile */ struct FSARequestTruncateFile { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSARequestTruncateFile, 0x0, handle); CHECK_SIZE(FSARequestTruncateFile, 0x4); /** * Request data for Command::Unmount */ struct FSARequestUnmount { be2_array<char, FSAPathLength + 1> path; be2_val<uint32_t> unk0x280; }; CHECK_OFFSET(FSARequestUnmount, 0x0, path); CHECK_OFFSET(FSARequestUnmount, 0x280, unk0x280); CHECK_SIZE(FSARequestUnmount, 0x284); /** * Request data for Command::UnmountWithProcess */ struct FSARequestUnmountWithProcess { be2_array<char, FSAPathLength + 1> path; be2_val<FSAMountPriority> priority; be2_struct<FSAProcessInfo> process; }; CHECK_OFFSET(FSARequestUnmountWithProcess, 0x0, path); CHECK_OFFSET(FSARequestUnmountWithProcess, 0x280, priority); CHECK_OFFSET(FSARequestUnmountWithProcess, 0x284, process); CHECK_SIZE(FSARequestUnmountWithProcess, 0x294); /** * Request data for Command::WriteFile */ struct FSARequestWriteFile { //! Virtual pointer used only by Cafe, for IOS we should use ioctlv.vecs[1] be2_virt_ptr<const uint8_t> buffer; be2_val<uint32_t> size; be2_val<uint32_t> count; be2_val<FSAFilePosition> pos; be2_val<FSAFileHandle> handle; be2_val<FSAWriteFlag> writeFlags; }; CHECK_OFFSET(FSARequestWriteFile, 0x00, buffer); CHECK_OFFSET(FSARequestWriteFile, 0x04, size); CHECK_OFFSET(FSARequestWriteFile, 0x08, count); CHECK_OFFSET(FSARequestWriteFile, 0x0C, pos); CHECK_OFFSET(FSARequestWriteFile, 0x10, handle); CHECK_OFFSET(FSARequestWriteFile, 0x14, writeFlags); CHECK_SIZE(FSARequestWriteFile, 0x18); /** * IPC request data for FSA device. */ struct FSARequest { be2_val<FSAStatus> emulatedError; union { be2_struct<FSARequestAppendFile> appendFile; be2_struct<FSARequestChangeDir> changeDir; be2_struct<FSARequestChangeMode> changeMode; be2_struct<FSARequestCloseDir> closeDir; be2_struct<FSARequestCloseFile> closeFile; be2_struct<FSARequestFlushFile> flushFile; be2_struct<FSARequestFlushQuota> flushQuota; be2_struct<FSARequestGetInfoByQuery> getInfoByQuery; be2_struct<FSARequestGetPosFile> getPosFile; be2_struct<FSARequestIsEof> isEof; be2_struct<FSARequestMakeDir> makeDir; be2_struct<FSARequestMakeQuota> makeQuota; be2_struct<FSARequestMount> mount; be2_struct<FSARequestMountWithProcess> mountWithProcess; be2_struct<FSARequestOpenDir> openDir; be2_struct<FSARequestOpenFile> openFile; be2_struct<FSARequestReadDir> readDir; be2_struct<FSARequestReadFile> readFile; be2_struct<FSARequestRemove> remove; be2_struct<FSARequestRename> rename; be2_struct<FSARequestRewindDir> rewindDir; be2_struct<FSARequestSetPosFile> setPosFile; be2_struct<FSARequestStatFile> statFile; be2_struct<FSARequestTruncateFile> truncateFile; be2_struct<FSARequestUnmount> unmount; be2_struct<FSARequestUnmountWithProcess> unmountWithProcess; be2_struct<FSARequestWriteFile> writeFile; UNKNOWN(0x51C); }; }; CHECK_OFFSET(FSARequest, 0x00, emulatedError); CHECK_SIZE(FSARequest, 0x520); #pragma pack(pop) /** @} */ } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_response.h ================================================ #pragma once #include "ios_fs_enum.h" #include "ios_fs_fsa_types.h" #include <cstdint> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::fs { /** * \ingroup ios_fs * @{ */ #pragma pack(push, 1) struct FSAResponseGetCwd { be2_array<char, FSAPathLength + 1> path; }; CHECK_OFFSET(FSAResponseGetCwd, 0x0, path); CHECK_SIZE(FSAResponseGetCwd, 0x280); struct FSAResponseGetFileBlockAddress { be2_val<uint32_t> address; }; CHECK_OFFSET(FSAResponseGetFileBlockAddress, 0x0, address); CHECK_SIZE(FSAResponseGetFileBlockAddress, 0x4); struct FSAResponseGetPosFile { be2_val<FSAFilePosition> pos; }; CHECK_OFFSET(FSAResponseGetPosFile, 0x0, pos); CHECK_SIZE(FSAResponseGetPosFile, 0x4); struct FSAResponseGetVolumeInfo { be2_struct<FSAVolumeInfo> volumeInfo; }; CHECK_OFFSET(FSAResponseGetVolumeInfo, 0x0, volumeInfo); CHECK_SIZE(FSAResponseGetVolumeInfo, 0x1BC); struct FSAResponseGetInfoByQuery { union { be2_struct<FSABlockInfo> badBlockInfo; be2_struct<FSADeviceInfo> deviceInfo; be2_val<uint64_t> dirSize; be2_val<FSAEntryNum> entryNum; be2_struct<FSAFileSystemInfo> fileSystemInfo; be2_struct<FSABlockInfo> fragmentBlockInfo; be2_val<uint64_t> freeSpaceSize; be2_val<uint64_t> journalFreeSpaceSize; be2_struct<FSAStat> stat; }; }; CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, badBlockInfo); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, deviceInfo); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, dirSize); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, entryNum); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, fragmentBlockInfo); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, freeSpaceSize); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, fileSystemInfo); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, journalFreeSpaceSize); CHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, stat); CHECK_SIZE(FSAResponseGetInfoByQuery, 0x64); struct FSAResponseOpenFile { be2_val<FSAFileHandle> handle; }; CHECK_OFFSET(FSAResponseOpenFile, 0x0, handle); CHECK_SIZE(FSAResponseOpenFile, 0x4); struct FSAResponseOpenDir { be2_val<FSADirHandle> handle; }; CHECK_OFFSET(FSAResponseOpenDir, 0x0, handle); CHECK_SIZE(FSAResponseOpenDir, 0x4); struct FSAResponseReadDir { be2_struct<FSADirEntry> entry; }; CHECK_OFFSET(FSAResponseReadDir, 0x0, entry); CHECK_SIZE(FSAResponseReadDir, 0x164); struct FSAResponseStatFile { be2_struct<FSAStat> stat; }; CHECK_OFFSET(FSAResponseStatFile, 0x0, stat); CHECK_SIZE(FSAResponseStatFile, 0x64); struct FSAResponse { be2_val<uint32_t> word0; union { be2_struct<FSAResponseGetCwd> getCwd; be2_struct<FSAResponseGetFileBlockAddress> getFileBlockAddress; be2_struct<FSAResponseGetPosFile> getPosFile; be2_struct<FSAResponseGetVolumeInfo> getVolumeInfo; be2_struct<FSAResponseGetInfoByQuery> getInfoByQuery; be2_struct<FSAResponseOpenDir> openDir; be2_struct<FSAResponseOpenFile> openFile; be2_struct<FSAResponseReadDir> readDir; be2_struct<FSAResponseStatFile> statFile; UNKNOWN(0x28F); }; }; CHECK_OFFSET(FSAResponse, 0x0, word0); CHECK_OFFSET(FSAResponse, 0x4, getFileBlockAddress); CHECK_OFFSET(FSAResponse, 0x4, getPosFile); CHECK_OFFSET(FSAResponse, 0x4, getVolumeInfo); CHECK_OFFSET(FSAResponse, 0x4, openDir); CHECK_OFFSET(FSAResponse, 0x4, openFile); CHECK_OFFSET(FSAResponse, 0x4, readDir); CHECK_OFFSET(FSAResponse, 0x4, statFile); CHECK_SIZE(FSAResponse, 0x293); #pragma pack(pop) /** @} */ } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_thread.cpp ================================================ #include "ios_fs_fsa_async_task.h" #include "ios_fs_fsa_device.h" #include "ios_fs_mutex.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/ios_enum.h" #include "ios/ios_handlemanager.h" #include "ios/ios_ipc.h" #include "ios/ios_stackobject.h" #include "ios/ios_worker_thread.h" using namespace ios::kernel; using ios::internal::submitWorkerTask; namespace ios::fs::internal { using FSADeviceHandle = int32_t; constexpr auto FSAMaxClients = 0x270; struct RequestOrigin { be2_val<TitleId> titleId; be2_val<ProcessId> processId; be2_val<GroupId> groupId; }; struct StaticFsaThreadData { be2_val<MessageQueueId> fsaMessageQueue; be2_array<Message, 0x160> fsaMessageBuffer; be2_val<ThreadId> fsaThread; be2_array<uint8_t, 0x4000> fsaThreadStack; }; static phys_ptr<StaticFsaThreadData> sData = nullptr; static HandleManager<FSADevice, FSADeviceHandle, FSAMaxClients> sDevices; FSAStatus getDevice(FSADeviceHandle handle, FSADevice **outDevice) { auto error = sDevices.get(handle, outDevice); if (error < Error::OK) { return FSAStatus::InvalidClientHandle; } return FSAStatus::OK; } static FSAStatus fsaDeviceOpen(phys_ptr<RequestOrigin> origin, FSADeviceHandle *outHandle, ClientCapabilityMask clientCapabilityMask) { auto error = sDevices.open(); if(error < Error::OK) { return FSAStatus::MaxClients; } *outHandle = static_cast<FSADeviceHandle>(error); return FSAStatus::OK; } static FSAStatus fsaDeviceClose(FSADeviceHandle handle, phys_ptr<ResourceRequest> resourceRequest) { FSADevice *device = nullptr; auto status = getDevice(handle, &device); if (status < FSAStatus::OK) { return status; } // TODO: Handle cleanup of async operations sDevices.close(device); return FSAStatus::OK; } static void fsaDeviceIoctl(phys_ptr<ResourceRequest> resourceRequest, FSACommand command, be2_phys_ptr<const void> inputBuffer, be2_phys_ptr<void> outputBuffer) { FSADevice *device = nullptr; auto status = getDevice(resourceRequest->requestData.handle, &device); if (status < FSAStatus::OK) { IOS_ResourceReply(resourceRequest, static_cast<Error>(status)); return; } auto request = phys_cast<const FSARequest *>(inputBuffer); auto response = phys_cast<FSAResponse *>(outputBuffer); if (!device) { IOS_ResourceReply(resourceRequest, static_cast<Error>(FSAStatus::InvalidClientHandle)); return; } if (!inputBuffer && !outputBuffer) { IOS_ResourceReply(resourceRequest, static_cast<Error>(FSAStatus::InvalidParam)); return; } if (request->emulatedError < FSAStatus::OK) { IOS_ResourceReply(resourceRequest, static_cast<Error>(request->emulatedError)); return; } auto user = vfs::User { }; user.id = static_cast<vfs::OwnerId>(resourceRequest->requestData.titleId & 0xFFFFFFFF); user.group = static_cast<vfs::GroupId>(resourceRequest->requestData.groupId); switch (command) { case FSACommand::AppendFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->appendFile(user, phys_addrof(request->appendFile))); }); break; case FSACommand::ChangeDir: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->changeDir(user, phys_addrof(request->changeDir))); }); break; case FSACommand::ChangeMode: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->changeMode(user, phys_addrof(request->changeMode))); }); break; case FSACommand::CloseDir: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->closeDir(user, phys_addrof(request->closeDir))); }); break; case FSACommand::CloseFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->closeFile(user, phys_addrof(request->closeFile))); }); break; case FSACommand::FlushFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->flushFile(user, phys_addrof(request->flushFile))); }); break; case FSACommand::FlushQuota: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->flushQuota(user, phys_addrof(request->flushQuota))); }); break; case FSACommand::GetCwd: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->getCwd(user, phys_addrof(response->getCwd))); }); break; case FSACommand::GetInfoByQuery: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->getInfoByQuery(user, phys_addrof(request->getInfoByQuery), phys_addrof(response->getInfoByQuery))); }); break; case FSACommand::GetPosFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->getPosFile(user, phys_addrof(request->getPosFile), phys_addrof(response->getPosFile))); }); break; case FSACommand::IsEof: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->isEof(user, phys_addrof(request->isEof))); }); break; case FSACommand::MakeDir: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->makeDir(user, phys_addrof(request->makeDir))); }); break; case FSACommand::MakeQuota: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->makeQuota(user, phys_addrof(request->makeQuota))); }); break; case FSACommand::OpenDir: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->openDir(user, phys_addrof(request->openDir), phys_addrof(response->openDir))); }); break; case FSACommand::OpenFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->openFile(user, phys_addrof(request->openFile), phys_addrof(response->openFile))); }); break; case FSACommand::ReadDir: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->readDir(user, phys_addrof(request->readDir), phys_addrof(response->readDir))); }); break; case FSACommand::Remove: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->remove(user, phys_addrof(request->remove))); }); break; case FSACommand::Rename: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->rename(user, phys_addrof(request->rename))); }); break; case FSACommand::RewindDir: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->rewindDir(user, phys_addrof(request->rewindDir))); }); break; case FSACommand::SetPosFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->setPosFile(user, phys_addrof(request->setPosFile))); }); break; case FSACommand::StatFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->statFile(user, phys_addrof(request->statFile), phys_addrof(response->statFile))); }); break; case FSACommand::TruncateFile: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->truncateFile(user, phys_addrof(request->truncateFile))); }); break; case FSACommand::Unmount: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->unmount(user, phys_addrof(request->unmount))); }); break; case FSACommand::UnmountWithProcess: submitWorkerTask([=]() { fsaAsyncTaskComplete( resourceRequest, device->unmountWithProcess(user, phys_addrof(request->unmountWithProcess))); }); break; default: IOS_ResourceReply(resourceRequest, static_cast<Error>(FSAStatus::UnsupportedCmd)); } } static void fsaDeviceIoctlv(phys_ptr<ResourceRequest> resourceRequest, FSACommand command, be2_phys_ptr<IoctlVec> vecs) { auto request = phys_cast<FSARequest *>(vecs[0].paddr); if (request->emulatedError < FSAStatus::OK) { IOS_ResourceReply(resourceRequest, static_cast<Error>(request->emulatedError)); return; } FSADevice *device = nullptr; auto status = getDevice(resourceRequest->requestData.handle, &device); if (status < FSAStatus::OK) { IOS_ResourceReply(resourceRequest, static_cast<Error>(status)); return; } auto user = vfs::User{ }; user.id = static_cast<vfs::OwnerId>(resourceRequest->requestData.titleId & 0xFFFFFFFF); user.group = static_cast<vfs::GroupId>(resourceRequest->requestData.groupId); switch (command) { case FSACommand::ReadFile: { submitWorkerTask( [=]() { auto buffer = phys_cast<uint8_t *>(vecs[1].paddr); auto length = vecs[1].len; fsaAsyncTaskComplete( resourceRequest, device->readFile(user, phys_addrof(request->readFile), buffer, length)); }); break; } case FSACommand::WriteFile: { submitWorkerTask( [=]() { auto buffer = phys_cast<uint8_t *>(vecs[1].paddr); auto length = vecs[1].len; fsaAsyncTaskComplete( resourceRequest, device->writeFile(user, phys_addrof(request->writeFile), buffer, length)); }); break; } case FSACommand::Mount: { submitWorkerTask( [=]() { fsaAsyncTaskComplete( resourceRequest, device->mount(user, phys_addrof(request->mount))); }); break; } case FSACommand::MountWithProcess: { submitWorkerTask( [=]() { fsaAsyncTaskComplete( resourceRequest, device->mountWithProcess(user, phys_addrof(request->mountWithProcess))); }); break; } default: IOS_ResourceReply(resourceRequest, static_cast<Error>(FSAStatus::UnsupportedCmd)); } } static Error fsaThreadMain(phys_ptr<void> /*context*/) { StackObject<Message> message; StackObject<RequestOrigin> origin; while (true) { auto error = IOS_ReceiveMessage(sData->fsaMessageQueue, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { FSADeviceHandle fsaHandle; origin->titleId = request->requestData.titleId; origin->processId = request->requestData.processId; origin->groupId = request->requestData.groupId; auto fsaError = fsaDeviceOpen(origin, &fsaHandle, request->requestData.args.open.caps); if (fsaError >= FSAStatus::OK) { fsaError = static_cast<FSAStatus>(fsaHandle); } IOS_ResourceReply(request, static_cast<Error>(fsaError)); break; } case Command::Close: { auto fsaError = fsaDeviceClose(request->requestData.handle, request); IOS_ResourceReply(request, static_cast<Error>(fsaError)); break; } case Command::Ioctl: { fsaDeviceIoctl(request, static_cast<FSACommand>(request->requestData.args.ioctl.request), request->requestData.args.ioctl.inputBuffer, request->requestData.args.ioctl.outputBuffer); break; } case Command::Ioctlv: { fsaDeviceIoctlv(request, static_cast<FSACommand>(request->requestData.args.ioctlv.request), request->requestData.args.ioctlv.vecs); break; } default: IOS_ResourceReply(request, Error::Invalid); } } } Error startFsaThread() { auto error = IOS_CreateMessageQueue(phys_addrof(sData->fsaMessageBuffer), static_cast<uint32_t>(sData->fsaMessageBuffer.size())); if (error < Error::OK) { return error; } auto queueId = static_cast<MessageQueueId>(error); sData->fsaMessageQueue = queueId; error = IOS_RegisterResourceManager("/dev/fsa", sData->fsaMessageQueue); if (error < Error::OK) { return error; } error = IOS_AssociateResourceManager("/dev/fsa", ResourcePermissionGroup::FS); if (error < Error::OK) { return error; } error = IOS_CreateThread(fsaThreadMain, nullptr, phys_addrof(sData->fsaThreadStack) + sData->fsaThreadStack.size(), static_cast<uint32_t>(sData->fsaThreadStack.size()), 78, ThreadFlags::Detached); if (error < Error::OK) { return error; } sData->fsaThread = static_cast<ThreadId>(error); kernel::internal::setThreadName(sData->fsaThread, "FsaThread"); error = IOS_StartThread(sData->fsaThread); if (error < Error::OK) { return error; } return Error::OK; } void initialiseStaticFsaThreadData() { sData = allocProcessStatic<StaticFsaThreadData>(); sDevices.closeAll(); } } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::fs::internal { Error startFsaThread(); void initialiseStaticFsaThreadData(); } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_fsa_types.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios_fs_enum.h" #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::fs { /** * \ingroup ios_fs * @{ */ #pragma pack(push, 1) constexpr auto FSAPathLength = 639u; constexpr auto FSAModeLength = 16u; constexpr auto FSAFileNameLength = 256u; constexpr auto FSAVolumeLabelLength = 128u; constexpr auto FSAVolumeIdLength = 128u; constexpr auto FSADevicePathLength = 16u; constexpr auto FSAMountPathLength = 128u; using FSADeviceHandle = int32_t; using FSADirHandle = int32_t; using FSAEntryNum = int32_t; using FSAFileHandle = int32_t; using FSAFilePosition = uint32_t; /** * Attach information. */ struct FSAAttachInfo { UNKNOWN(0x1BC); }; CHECK_SIZE(FSAAttachInfo, 0x1BC); /** * Block information. */ struct FSABlockInfo { UNKNOWN(0x14); }; CHECK_SIZE(FSABlockInfo, 0x14); /** * Device information. */ struct FSADeviceInfo { UNKNOWN(0x28); }; CHECK_SIZE(FSADeviceInfo, 0x28); /** * File System information. */ struct FSAFileSystemInfo { UNKNOWN(0x1E); }; CHECK_SIZE(FSAFileSystemInfo, 0x1E); /** * Information about a file or directory. */ struct FSAStat { be2_val<FSAStatFlags> flags; be2_val<uint32_t> permission; be2_val<uint32_t> owner; be2_val<uint32_t> group; be2_val<uint32_t> size; UNKNOWN(0xC); be2_val<uint32_t> entryId; be2_val<int64_t> created; be2_val<int64_t> modified; UNKNOWN(0x30); }; CHECK_OFFSET(FSAStat, 0x00, flags); CHECK_OFFSET(FSAStat, 0x04, permission); CHECK_OFFSET(FSAStat, 0x08, owner); CHECK_OFFSET(FSAStat, 0x0C, group); CHECK_OFFSET(FSAStat, 0x10, size); CHECK_OFFSET(FSAStat, 0x20, entryId); CHECK_OFFSET(FSAStat, 0x24, created); CHECK_OFFSET(FSAStat, 0x2C, modified); CHECK_SIZE(FSAStat, 0x64); /** * Information about an item in a directory. */ struct FSADirEntry { //! File stat. be2_struct<FSAStat> stat; //! File name. be2_array<char, FSAFileNameLength> name; }; CHECK_OFFSET(FSADirEntry, 0x00, stat); CHECK_OFFSET(FSADirEntry, 0x64, name); CHECK_SIZE(FSADirEntry, 0x164); /** * Volume information. */ struct FSAVolumeInfo { be2_val<uint32_t> flags; be2_val<FSAMediaState> mediaState; UNKNOWN(0x4); be2_val<uint32_t> unk0x0C; be2_val<uint32_t> unk0x10; be2_val<int32_t> unk0x14; be2_val<int32_t> unk0x18; UNKNOWN(0x10); be2_array<char, FSAVolumeLabelLength> volumeLabel; be2_array<char, FSAVolumeIdLength> volumeId; be2_array<char, FSADevicePathLength> devicePath; be2_array<char, FSAMountPathLength> mountPath; }; CHECK_OFFSET(FSAVolumeInfo, 0x00, flags); CHECK_OFFSET(FSAVolumeInfo, 0x04, mediaState); CHECK_OFFSET(FSAVolumeInfo, 0x0C, unk0x0C); CHECK_OFFSET(FSAVolumeInfo, 0x10, unk0x10); CHECK_OFFSET(FSAVolumeInfo, 0x14, unk0x14); CHECK_OFFSET(FSAVolumeInfo, 0x18, unk0x18); CHECK_OFFSET(FSAVolumeInfo, 0x2C, volumeLabel); CHECK_OFFSET(FSAVolumeInfo, 0xAC, volumeId); CHECK_OFFSET(FSAVolumeInfo, 0x12C, devicePath); CHECK_OFFSET(FSAVolumeInfo, 0x13C, mountPath); CHECK_SIZE(FSAVolumeInfo, 0x1BC); /** * Process information. */ struct FSAProcessInfo { be2_val<uint64_t> titleId; be2_val<ProcessId> processId; be2_val<uint32_t> groupId; }; CHECK_OFFSET(FSAProcessInfo, 0x00, titleId); CHECK_OFFSET(FSAProcessInfo, 0x08, processId); CHECK_OFFSET(FSAProcessInfo, 0x0C, groupId); CHECK_SIZE(FSAProcessInfo, 0x10); #pragma pack(pop) /** @} */ } // namespace ios::fs ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_log.h ================================================ #pragma once #include <common/log.h> namespace ios::fs::internal { extern Logger fsLog; } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_mutex.cpp ================================================ #include "ios_fs_mutex.h" namespace ios::fs::internal { Error initMutex(phys_ptr<Mutex> mutex) { auto error = kernel::IOS_CreateSemaphore(1, 1); mutex->semaphore = static_cast<kernel::SemaphoreId>(error); return error; } Error destroyMutex(phys_ptr<Mutex> mutex) { auto semaphore = mutex->semaphore; if (mutex->semaphore < 0) { return Error::OK; } mutex->semaphore = static_cast<kernel::SemaphoreId>(Error::Invalid); return kernel::IOS_DestroySempahore(mutex->semaphore); } Error lockMutex(phys_ptr<Mutex> mutex) { return kernel::IOS_WaitSemaphore(mutex->semaphore, FALSE); } Error unlockMutex(phys_ptr<Mutex> mutex) { return kernel::IOS_SignalSempahore(mutex->semaphore); } } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_mutex.h ================================================ #pragma once #include "ios/kernel/ios_kernel_semaphore.h" #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::fs::internal { struct Mutex { be2_val<kernel::SemaphoreId> semaphore; }; CHECK_OFFSET(Mutex, 0x0, semaphore); CHECK_SIZE(Mutex, 0x4); Error initMutex(phys_ptr<Mutex> mutex); Error destroyMutex(phys_ptr<Mutex> mutex); Error lockMutex(phys_ptr<Mutex> mutex); Error unlockMutex(phys_ptr<Mutex> mutex); } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_service_thread.cpp ================================================ #include "ios_fs_service_thread.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/ios_enum.h" #include "ios/ios_handlemanager.h" #include "ios/ios_ipc.h" #include "ios/ios_stackobject.h" using namespace ios::kernel; using namespace ios::mcp; namespace ios::fs::internal { constexpr auto ServiceThreadStackSize = 0x4000u; constexpr auto ServiceThreadPriority = 85u; struct StaticServiceThreadData { be2_val<ThreadId> threadId; be2_array<uint8_t, ServiceThreadStackSize> threadStack; be2_val<MessageQueueId> messageQueue; be2_array<Message, 0x160> messageBuffer; }; static phys_ptr<StaticServiceThreadData> sData = nullptr; struct ServiceDevice { std::string_view name; bool open = false; bool resumed = false; }; static std::array<ServiceDevice, 16> sServiceDevices = { ServiceDevice { "/dev/df" }, ServiceDevice { "/dev/atfs" }, ServiceDevice { "/dev/isfs" }, ServiceDevice { "/dev/wfs" }, ServiceDevice { "/dev/pcfs" }, ServiceDevice { "/dev/rbfs" }, ServiceDevice { "/dev/fat" }, ServiceDevice { "/dev/fla" }, ServiceDevice { "/dev/ums" }, ServiceDevice { "/dev/ahcimgr" }, ServiceDevice { "/dev/shdd" }, ServiceDevice { "/dev/md" }, ServiceDevice { "/dev/scfm" }, ServiceDevice { "/dev/mmc" }, ServiceDevice { "/dev/timetrace" }, ServiceDevice { "/dev/tcp_pcfs" }, }; static Error serviceThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { return error; } auto queueId = static_cast<MessageQueueId>(error); sData->messageQueue = queueId; for (auto &device : sServiceDevices) { error = MCP_RegisterResourceManager(device.name, sData->messageQueue); if (error >= Error::OK) { IOS_AssociateResourceManager(device.name, ResourcePermissionGroup::FS); } } while (true) { error = IOS_ReceiveMessage(sData->messageQueue, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { error = Error::Invalid; for (auto i = 0u; i < sServiceDevices.size(); ++i) { auto &device = sServiceDevices[i]; if (device.name.compare(request->requestData.args.open.name.get()) == 0) { device.open = true; error = static_cast<Error>(i); break; } } IOS_ResourceReply(request, static_cast<Error>(error)); break; } case Command::Close: { auto deviceIdx = request->requestData.handle; if (deviceIdx < 0 || deviceIdx >= sServiceDevices.size()) { error = Error::Invalid; } else { sServiceDevices[deviceIdx].open = false; } IOS_ResourceReply(request, static_cast<Error>(error)); break; } case Command::Resume: { auto deviceIdx = request->requestData.handle; if (deviceIdx < 0 || deviceIdx >= sServiceDevices.size()) { error = Error::Invalid; } else { sServiceDevices[deviceIdx].resumed = true; } IOS_ResourceReply(request, static_cast<Error>(error)); break; } case Command::Suspend: { auto deviceIdx = request->requestData.handle; if (deviceIdx < 0 || deviceIdx >= sServiceDevices.size()) { error = Error::Invalid; } else { sServiceDevices[deviceIdx].resumed = false; } IOS_ResourceReply(request, static_cast<Error>(error)); break; } default: IOS_ResourceReply(request, Error::Invalid); } } } Error startServiceThread() { auto error = IOS_CreateThread(&serviceThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), static_cast<uint32_t>(sData->threadStack.size()), ServiceThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } sData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sData->threadId, "FsServiceThread"); error = IOS_StartThread(sData->threadId); if (error < Error::OK) { return error; } return Error::OK; } void initialiseStaticServiceThreadData() { sData = allocProcessStatic<StaticServiceThreadData>(); for (auto &device : sServiceDevices) { device.open = false; device.resumed = false; } } } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/fs/ios_fs_service_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::fs::internal { Error startServiceThread(); void initialiseStaticServiceThreadData(); } // namespace ios::fs::internal ================================================ FILE: src/libdecaf/src/ios/ios.cpp ================================================ #include "ios.h" #include "ios_alarm_thread.h" #include "ios_network_thread.h" #include "ios_worker_thread.h" #include "ios/kernel/ios_kernel.h" #include "vfs/vfs_virtual_device.h" #include <memory> namespace ios { static std::shared_ptr<vfs::VirtualDevice> sFileSystem; void start() { internal::startAlarmThread(); internal::startNetworkTaskThread(); internal::startWorkerThread(); kernel::start(); } void join() { kernel::join(); internal::stopWorkerThread(); internal::stopNetworkTaskThread(); internal::stopAlarmThread(); } void stop() { kernel::stop(); internal::stopWorkerThread(); internal::stopNetworkTaskThread(); internal::stopAlarmThread(); } void setFileSystem(std::shared_ptr<vfs::VirtualDevice> root) { sFileSystem = std::move(root); } std::shared_ptr<vfs::VirtualDevice> getFileSystem() { return sFileSystem; } } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios.h ================================================ #pragma once #include <memory> namespace vfs { class VirtualDevice; } namespace ios { void start(); void join(); void stop(); void setFileSystem(std::shared_ptr<vfs::VirtualDevice> fs); std::shared_ptr<vfs::VirtualDevice> getFileSystem(); } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios_alarm_thread.cpp ================================================ #include "ios_alarm_thread.h" #include "ios/kernel/ios_kernel_hardware.h" #include <atomic> #include <condition_variable> #include <functional> #include <mutex> #include <thread> namespace ios::internal { static std::thread sAlarmThread; static std::mutex sAlarmMutex; static std::condition_variable sAlarmCondition; static std::atomic_bool sAlarmThreadRunning = false; static std::chrono::steady_clock::time_point sNextAlarm = std::chrono::steady_clock::time_point::max(); static void alarmThread() { while (sAlarmThreadRunning.load()) { std::unique_lock<std::mutex> lock { sAlarmMutex }; auto now = std::chrono::steady_clock::now(); if (now >= sNextAlarm) { sNextAlarm = std::chrono::steady_clock::time_point::max(); lock.unlock(); kernel::internal::setInterruptAhbAll(kernel::AHBALL::get(0).Timer(true)); lock.lock(); } if (sNextAlarm != std::chrono::steady_clock::time_point::max()) { sAlarmCondition.wait_until(lock, sNextAlarm); } else { sAlarmCondition.wait(lock); } } } void startAlarmThread() { if (!sAlarmThreadRunning.load()) { sAlarmThreadRunning.store(true); sAlarmThread = std::thread { alarmThread }; } } void stopAlarmThread() { if (sAlarmThreadRunning.load()) { sAlarmThreadRunning.store(false); sAlarmCondition.notify_all(); sAlarmThread.join(); } } void setNextAlarm(std::chrono::steady_clock::time_point time) { std::unique_lock<std::mutex> lock { sAlarmMutex }; sNextAlarm = time; sAlarmCondition.notify_all(); } } // namespace ios::internal ================================================ FILE: src/libdecaf/src/ios/ios_alarm_thread.h ================================================ #pragma once #include <chrono> namespace ios::internal { void startAlarmThread(); void stopAlarmThread(); void setNextAlarm(std::chrono::steady_clock::time_point time); } // namespace ios::internal ================================================ FILE: src/libdecaf/src/ios/ios_device.h ================================================ #pragma once #include "ios_enum.h" #include "ios_ipc.h" #include "kernel/ios_kernel_resourcemanager.h" #include <libcpu/be2_struct.h> namespace ios { class Device { public: virtual ~Device() = 0; virtual Error read(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<void> buffer, uint32_t length) = 0; virtual Error write(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const void> buffer, uint32_t length) = 0; virtual Error seek(phys_ptr<kernel::ResourceRequest> resourceRequest, uint32_t offset, SeekOrigin origin) = 0; virtual Error ioctl(phys_ptr<kernel::ResourceRequest> resourceRequest, uint32_t ioctlRequest, phys_ptr<void> inputBuffer, uint32_t inputLength, phys_ptr<void> outputBuffer, uint32_t outputLength) = 0; virtual Error ioctlv(phys_ptr<kernel::ResourceRequest> resourceRequest, uint32_t ioctlRequest, uint32_t numVecsIn, uint32_t numVecsOut, phys_ptr<IoctlVec> vecs) = 0; }; } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios_enum.h ================================================ #ifndef IOS_ENUM_H #define IOS_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) FLAGS_BEG(CoreInterruptFlags, uint32_t) FLAGS_VALUE(Shutdown, 1 << 0) FLAGS_VALUE(Scheduler, 1 << 1) FLAGS_END(CoreInterruptFlags) FLAGS_BEG(InterruptFlags, uint32_t) FLAGS_VALUE(Ahb, 1 << 0) FLAGS_END(InterruptFlags) ENUM_BEG(Command, int32_t) ENUM_VALUE(Invalid, 0) ENUM_VALUE(Open, 1) ENUM_VALUE(Close, 2) ENUM_VALUE(Read, 3) ENUM_VALUE(Write, 4) ENUM_VALUE(Seek, 5) ENUM_VALUE(Ioctl, 6) ENUM_VALUE(Ioctlv, 7) ENUM_VALUE(Reply, 8) ENUM_VALUE(IpcMsg0, 9) ENUM_VALUE(IpcMsg1, 10) ENUM_VALUE(IpcMsg2, 11) ENUM_VALUE(Suspend, 12) ENUM_VALUE(Resume, 13) ENUM_VALUE(SvcMsg, 14) ENUM_END(Command) ENUM_BEG(CpuId, uint32_t) ENUM_VALUE(ARM, 0) ENUM_VALUE(PPC0, 1) ENUM_VALUE(PPC1, 2) ENUM_VALUE(PPC2, 3) ENUM_END(CpuId) ENUM_BEG(ErrorCategory, uint32_t) ENUM_VALUE(Kernel, 0) ENUM_VALUE(FSA, 3) ENUM_VALUE(MCP, 4) ENUM_VALUE(Unknown7, 7) ENUM_VALUE(Unknown8, 8) ENUM_VALUE(Socket, 10) ENUM_VALUE(ODM, 14) ENUM_VALUE(Unknown15, 15) ENUM_VALUE(Unknown19, 19) ENUM_VALUE(Unknown30, 30) ENUM_VALUE(Unknown45, 45) ENUM_END(ErrorCategory) ENUM_BEG(Error, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Access, -1) ENUM_VALUE(Exists, -2) ENUM_VALUE(Intr, -3) ENUM_VALUE(Invalid, -4) ENUM_VALUE(Max, -5) ENUM_VALUE(NoExists, -6) ENUM_VALUE(QEmpty, -7) ENUM_VALUE(QFull, -8) ENUM_VALUE(Unknown, -9) ENUM_VALUE(NotReady, -10) ENUM_VALUE(Ecc, -11) ENUM_VALUE(EccCrit, -12) ENUM_VALUE(BadBlock, -13) ENUM_VALUE(InvalidObjType, -14) ENUM_VALUE(InvalidRNG, -15) ENUM_VALUE(InvalidFlag, -16) ENUM_VALUE(InvalidFormat, -17) ENUM_VALUE(InvalidVersion, -18) ENUM_VALUE(InvalidSigner, -19) ENUM_VALUE(FailCheckValue, -20) ENUM_VALUE(FailInternal, -21) ENUM_VALUE(FailAlloc, -22) ENUM_VALUE(InvalidSize, -23) ENUM_VALUE(NoLink, -24) ENUM_VALUE(ANFailed, -25) ENUM_VALUE(MaxSemCount, -26) ENUM_VALUE(SemUnavailable, -27) ENUM_VALUE(InvalidHandle, -28) ENUM_VALUE(InvalidArg, -29) ENUM_VALUE(NoResource, -30) ENUM_VALUE(Busy, -31) ENUM_VALUE(Timeout, -32) ENUM_VALUE(Alignment, -33) ENUM_VALUE(BSP, -34) ENUM_VALUE(DataPending, -35) ENUM_VALUE(Expired, -36) ENUM_VALUE(NoReadAccess, -37) ENUM_VALUE(NoWriteAccess, -38) ENUM_VALUE(NoReadWriteAccess, -39) ENUM_VALUE(ClientTxnLimit, -40) ENUM_VALUE(StaleHandle, -41) ENUM_VALUE(UnknownValue, -42) ENUM_VALUE(MaxKernelError, -0x400) ENUM_END(Error) ENUM_BEG(OpenMode, int32_t) ENUM_VALUE(None, 0) ENUM_VALUE(Read, 1) ENUM_VALUE(Write, 2) ENUM_VALUE(ReadWrite, Read | Write) ENUM_END(OpenMode) ENUM_BEG(ProcessId, int32_t) ENUM_VALUE(Invalid, -4) ENUM_VALUE(KERNEL, 0) ENUM_VALUE(MCP, 1) ENUM_VALUE(BSP, 2) ENUM_VALUE(CRYPTO, 3) ENUM_VALUE(USB, 4) ENUM_VALUE(FS, 5) ENUM_VALUE(PAD, 6) ENUM_VALUE(NET, 7) ENUM_VALUE(ACP, 8) ENUM_VALUE(NSEC, 9) ENUM_VALUE(AUXIL, 10) ENUM_VALUE(NIM, 11) ENUM_VALUE(FPD, 12) ENUM_VALUE(TEST, 13) ENUM_VALUE(COSKERNEL, 14) ENUM_VALUE(COSROOT, 15) ENUM_VALUE(COS02, 16) ENUM_VALUE(COS03, 17) ENUM_VALUE(COSOVERLAY, 18) ENUM_VALUE(COSHBM, 19) ENUM_VALUE(COSERROR, 20) ENUM_VALUE(COSMASTER, 21) ENUM_VALUE(Max, 22) ENUM_END(ProcessId) ENUM_BEG(SeekOrigin, uint32_t) ENUM_VALUE(Beg, 0) ENUM_VALUE(Cur, 1) ENUM_VALUE(End, 2) ENUM_END(SeekOrigin) ENUM_BEG(SyscallId, uint32_t) ENUM_VALUE(CreateThread, 0x00) ENUM_VALUE(JoinThread, 0x01) ENUM_VALUE(CancelThread, 0x02) ENUM_VALUE(GetCurrentThreadId, 0x03) ENUM_VALUE(GetCurrentProcessId, 0x05) ENUM_VALUE(GetCurrentProcessName, 0x06) ENUM_VALUE(StartThread, 0x07) ENUM_VALUE(SuspendThread, 0x08) ENUM_VALUE(YieldThread, 0x09) ENUM_VALUE(GetThreadPriority, 0x0A) ENUM_VALUE(SetThreadPriority, 0x0B) ENUM_VALUE(CreateMessageQueue, 0x0C) ENUM_VALUE(DestroyMessageQueue, 0x0D) ENUM_VALUE(SendMessage, 0x0E) ENUM_VALUE(JamMessage, 0x0F) ENUM_VALUE(ReceiveMessage, 0x10) ENUM_VALUE(HandleEvent, 0x11) ENUM_VALUE(UnhandleEvent, 0x12) ENUM_VALUE(CreateTimer, 0x13) ENUM_VALUE(RestartTimer, 0x14) ENUM_VALUE(StopTimer, 0x15) ENUM_VALUE(DestroyTimer, 0x16) ENUM_VALUE(GetUpTimeStruct, 0x19) ENUM_VALUE(GetUpTime64, 0x1A) ENUM_VALUE(GetAbsTimeCalendar, 0x1C) ENUM_VALUE(GetAbsTime64, 0x1D) ENUM_VALUE(GetAbsTimeStruct, 0x1E) ENUM_VALUE(CreateLocalProcessHeap, 0x24) ENUM_VALUE(CreateCrossProcessHeap, 0x25) ENUM_VALUE(Alloc, 0x27) ENUM_VALUE(AllocAligned, 0x28) ENUM_VALUE(Free, 0x29) ENUM_VALUE(FreeAndZero, 0x2A) ENUM_VALUE(Realloc, 0x2B) ENUM_VALUE(RegisterResourceManager, 0x2C) ENUM_VALUE(Open, 0x33) ENUM_VALUE(Close, 0x34) ENUM_VALUE(Read, 0x35) ENUM_VALUE(Write, 0x36) ENUM_VALUE(Seek, 0x37) ENUM_VALUE(Ioctl, 0x38) ENUM_VALUE(Ioctlv, 0x39) ENUM_VALUE(OpenAsync, 0x3A) ENUM_VALUE(CloseAsync, 0x3B) ENUM_VALUE(ReadAsync, 0x3C) ENUM_VALUE(WriteAsync, 0x3D) ENUM_VALUE(SeekAsync, 0x3E) ENUM_VALUE(IoctlAsync, 0x3F) ENUM_VALUE(IoctlvAsync, 0x40) ENUM_VALUE(ResourceReply, 0x49) ENUM_VALUE(ClearAndEnable, 0x50) ENUM_VALUE(InvalidateDCache, 0x51) ENUM_END(SyscallId) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_ENUM_H ================================================ FILE: src/libdecaf/src/ios/ios_enum_string.cpp ================================================ #include "ios_enum_string.h" #undef IOS_ENUM_H #include <common/enum_string_define.inl> #include "ios_enum.h" #include <common/enum_end.inl> ================================================ FILE: src/libdecaf/src/ios/ios_enum_string.h ================================================ #pragma once #include <cstdint> #include <string> #include "ios_enum.h" #undef IOS_ENUM_H #include <common/enum_string_declare.inl> #include "ios_enum.h" #include <common/enum_end.inl> ================================================ FILE: src/libdecaf/src/ios/ios_error.h ================================================ #pragma once #include "ios_enum.h" namespace ios { constexpr ErrorCategory getErrorCategory(Error error) { return static_cast<ErrorCategory>(((~error) >> 16) & 0x3FF); } constexpr int32_t getErrorCode(Error error) { return (error & 0x8000) ? (error | 0xFFFF0000) : (error & 0xFFFF); } constexpr bool isKernelError(int32_t error) { return error > Error::MaxKernelError; } constexpr Error makeError(ErrorCategory category, int32_t code) { return static_cast<Error>(code >= 0 ? code : ((~category) << 16) | (code & 0xFFFF)); } } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios_handlemanager.h ================================================ #pragma once #include "ios_enum.h" #include <array> #include <memory> #include <type_traits> namespace ios { template<typename ValueType, typename HandleType, size_t MaxNumHandles> class HandleManager { static_assert(MaxNumHandles < 0xFFFF); struct Handle { uint16_t instanceNum = 0; std::unique_ptr<ValueType> value = nullptr; }; public: Error open() { auto index = -1; for (auto i = 0u; i < mHandles.size(); ++i) { if (!mHandles[i].value) { index = static_cast<int>(i); break; } } if (index < 0) { return Error::Max; } ++mHandles[index].instanceNum; mHandles[index].value = std::make_unique<ValueType>(); return static_cast<Error>((mHandles[index].instanceNum << 16) | index); } Error close(HandleType handle) { auto index = handle & 0xFFFF; auto instanceNum = (handle >> 16) & 0xFFFF; if constexpr (std::is_signed<HandleType>::value) { if (index < 0) { return Error::InvalidHandle; } } if (index >= mHandles.size()) { return Error::InvalidHandle; } if (!mHandles[index].value || mHandles[index].instanceNum != instanceNum) { return Error::StaleHandle; } mHandles[index].value = nullptr; return Error::OK; } Error close(ValueType *value) { for (auto i = 0u; i < mHandles.size(); ++i) { if (mHandles[i].value.get() == value) { mHandles[i].value = nullptr; return Error::OK; } } return Error::InvalidHandle; } Error closeAll() { for (auto &handle : mHandles) { handle.value = nullptr; } return Error::OK; } Error get(HandleType handle, ValueType **outData) { auto index = handle & 0xFFFF; auto instanceNum = (handle >> 16) & 0xFFFF; if constexpr (std::is_signed<HandleType>::value) { if (index < 0) { return Error::InvalidHandle; } } if (index >= mHandles.size()) { return Error::InvalidHandle; } if (!mHandles[index].value || mHandles[index].instanceNum != instanceNum) { return Error::StaleHandle; } *outData = mHandles[index].value.get(); return Error::OK; } private: std::array<Handle, MaxNumHandles> mHandles; }; } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios_ipc.h ================================================ #pragma once #include "ios_enum.h" #include <common/structsize.h> #include <cstddef> #include <libcpu/be2_struct.h> namespace ios { #pragma pack(push, 1) using GroupId = uint32_t; using Handle = int32_t; using TitleId = uint64_t; static constexpr uint32_t IoctlVecAlign = 0x40u; /** * Structure used for ioctlv arguments. */ struct IoctlVec { //! Virtual address of buffer. be2_val<virt_addr> vaddr; //! Length of buffer. be2_val<uint32_t> len; //! Physical address of buffer. be2_val<phys_addr> paddr; }; CHECK_OFFSET(IoctlVec, 0x00, vaddr); CHECK_OFFSET(IoctlVec, 0x04, len); CHECK_OFFSET(IoctlVec, 0x08, paddr); CHECK_SIZE(IoctlVec, 0x0C); struct IpcRequestArgsOpen { be2_phys_ptr<const char> name; be2_val<uint32_t> nameLen; be2_val<OpenMode> mode; be2_val<uint64_t> caps; }; CHECK_OFFSET(IpcRequestArgsOpen, 0x00, name); CHECK_OFFSET(IpcRequestArgsOpen, 0x04, nameLen); CHECK_OFFSET(IpcRequestArgsOpen, 0x08, mode); CHECK_OFFSET(IpcRequestArgsOpen, 0x0C, caps); CHECK_SIZE(IpcRequestArgsOpen, 0x14); struct IpcRequestArgsClose { be2_val<uint32_t> unkArg0; }; CHECK_OFFSET(IpcRequestArgsClose, 0x00, unkArg0); CHECK_SIZE(IpcRequestArgsClose, 0x04); struct IpcRequestArgsRead { be2_phys_ptr<void> data; be2_val<uint32_t> length; }; CHECK_OFFSET(IpcRequestArgsRead, 0x00, data); CHECK_OFFSET(IpcRequestArgsRead, 0x04, length); CHECK_SIZE(IpcRequestArgsRead, 0x08); struct IpcRequestArgsWrite { be2_phys_ptr<const void> data; be2_val<uint32_t> length; }; CHECK_OFFSET(IpcRequestArgsWrite, 0x00, data); CHECK_OFFSET(IpcRequestArgsWrite, 0x04, length); CHECK_SIZE(IpcRequestArgsWrite, 0x08); struct IpcRequestArgsSeek { be2_val<uint32_t> offset; be2_val<SeekOrigin> origin; }; CHECK_OFFSET(IpcRequestArgsSeek, 0x00, offset); CHECK_OFFSET(IpcRequestArgsSeek, 0x04, origin); CHECK_SIZE(IpcRequestArgsSeek, 0x08); struct IpcRequestArgsIoctl { be2_val<uint32_t> request; be2_phys_ptr<const void> inputBuffer; be2_val<uint32_t> inputLength; be2_phys_ptr<void> outputBuffer; be2_val<uint32_t> outputLength; }; CHECK_OFFSET(IpcRequestArgsIoctl, 0x00, request); CHECK_OFFSET(IpcRequestArgsIoctl, 0x04, inputBuffer); CHECK_OFFSET(IpcRequestArgsIoctl, 0x08, inputLength); CHECK_OFFSET(IpcRequestArgsIoctl, 0x0C, outputBuffer); CHECK_OFFSET(IpcRequestArgsIoctl, 0x10, outputLength); CHECK_SIZE(IpcRequestArgsIoctl, 0x14); struct IpcRequestArgsIoctlv { be2_val<uint32_t> request; be2_val<uint32_t> numVecIn; be2_val<uint32_t> numVecOut; be2_phys_ptr<IoctlVec> vecs; }; CHECK_OFFSET(IpcRequestArgsIoctlv, 0x00, request); CHECK_OFFSET(IpcRequestArgsIoctlv, 0x04, numVecIn); CHECK_OFFSET(IpcRequestArgsIoctlv, 0x08, numVecOut); CHECK_OFFSET(IpcRequestArgsIoctlv, 0x0C, vecs); CHECK_SIZE(IpcRequestArgsIoctlv, 0x10); struct IpcRequestArgsResume { be2_val<uint32_t> unkArg0; be2_val<uint32_t> unkArg1; }; CHECK_OFFSET(IpcRequestArgsResume, 0x00, unkArg0); CHECK_OFFSET(IpcRequestArgsResume, 0x04, unkArg1); CHECK_SIZE(IpcRequestArgsResume, 0x08); struct IpcRequestArgsSuspend { be2_val<uint32_t> unkArg0; be2_val<uint32_t> unkArg1; }; CHECK_OFFSET(IpcRequestArgsSuspend, 0x00, unkArg0); CHECK_OFFSET(IpcRequestArgsSuspend, 0x04, unkArg1); CHECK_SIZE(IpcRequestArgsSuspend, 0x08); struct IpcRequestArgsSvcMsg { be2_val<uint32_t> command; be2_val<uint32_t> unkArg1; be2_val<uint32_t> unkArg2; be2_val<uint32_t> unkArg3; }; CHECK_OFFSET(IpcRequestArgsSvcMsg, 0x00, command); CHECK_OFFSET(IpcRequestArgsSvcMsg, 0x04, unkArg1); CHECK_OFFSET(IpcRequestArgsSvcMsg, 0x08, unkArg2); CHECK_OFFSET(IpcRequestArgsSvcMsg, 0x0C, unkArg3); CHECK_SIZE(IpcRequestArgsSvcMsg, 0x10); struct IpcRequestArgs { IpcRequestArgs() { } union { be2_struct<IpcRequestArgsOpen> open; be2_struct<IpcRequestArgsClose> close; be2_struct<IpcRequestArgsRead> read; be2_struct<IpcRequestArgsWrite> write; be2_struct<IpcRequestArgsSeek> seek; be2_struct<IpcRequestArgsIoctl> ioctl; be2_struct<IpcRequestArgsIoctlv> ioctlv; be2_struct<IpcRequestArgsResume> resume; be2_struct<IpcRequestArgsSuspend> suspend; be2_struct<IpcRequestArgsSvcMsg> svcMsg; be2_array<uint32_t, 5> args; }; }; CHECK_OFFSET(IpcRequestArgs, 0x00, open); CHECK_OFFSET(IpcRequestArgs, 0x00, read); CHECK_OFFSET(IpcRequestArgs, 0x00, write); CHECK_OFFSET(IpcRequestArgs, 0x00, seek); CHECK_OFFSET(IpcRequestArgs, 0x00, ioctl); CHECK_OFFSET(IpcRequestArgs, 0x00, ioctlv); CHECK_OFFSET(IpcRequestArgs, 0x00, args); CHECK_SIZE(IpcRequestArgs, 0x14); /** * The actual data which is sent as an IPC request between IOSU (ARM) and * PowerPC cores. */ struct IpcRequest { static constexpr auto ArgCount = 5; //! IOS command to execute be2_val<Command> command; //! IPC command result be2_val<Error> reply; //! Handle for the IOS resource be2_val<Handle> handle; //! Flags, always 0 be2_val<uint32_t> flags; //! CPU the request originated from be2_val<CpuId> cpuId; union { //! Cafe/PowerPC process ID the request originated from, only valid when //! receiving the request in the kernel ipc thread. be2_val<int32_t> clientPid; //! IOS ProcessId the request originated from, this is the value that //! should be used everywhere except from the kernel ipc thread. be2_val<ProcessId> processId; }; //! Title ID the request originated from be2_val<TitleId> titleId; //! Group ID be2_val<GroupId> groupId; //! IPC command args be2_struct<IpcRequestArgs> args; }; CHECK_OFFSET(IpcRequest, 0x00, command); CHECK_OFFSET(IpcRequest, 0x04, reply); CHECK_OFFSET(IpcRequest, 0x08, handle); CHECK_OFFSET(IpcRequest, 0x0C, flags); CHECK_OFFSET(IpcRequest, 0x10, cpuId); CHECK_OFFSET(IpcRequest, 0x14, clientPid); CHECK_OFFSET(IpcRequest, 0x14, processId); CHECK_OFFSET(IpcRequest, 0x18, titleId); CHECK_OFFSET(IpcRequest, 0x20, groupId); CHECK_OFFSET(IpcRequest, 0x24, args); CHECK_SIZE(IpcRequest, 0x38); #pragma pack(pop) } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios_network_thread.cpp ================================================ #include "ios_network_thread.h" #include <algorithm> #include <ares.h> #include <atomic> #include <memory> #include <mutex> #include <thread> #include <uv.h> #include <vector> namespace ios::internal { static uv_loop_t sNetworkLoop = { }; static uv_async_t sAsyncPendingNetworkEvent = { }; static uv_timer_t sAresTimer = { }; static ares_channel sAresChannel = { }; static std::atomic<bool> sNetworkTaskThreadRunning { false }; static std::thread sNetworkTaskThread; static std::mutex sPendingTasksMutex; static std::vector<NetworkTask> sPendingTasks; struct AresTask { ares_socket_t socket; uv_poll_t poll; }; static std::vector<std::unique_ptr<AresTask>> sAresTasks; static void uvAsyncCallback(uv_async_t *handle) { std::vector<NetworkTask> tasks; sPendingTasksMutex.lock(); sPendingTasks.swap(tasks); sPendingTasksMutex.unlock(); for (const auto &task : tasks) { task(); } if (!sNetworkTaskThreadRunning) { uv_stop(&sNetworkLoop); } } static void aresTimerCallback(uv_timer_t *timer) { ares_process_fd(sAresChannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); } static void aresPollCallback(uv_poll_t *watcher, int status, int events) { auto task = reinterpret_cast<AresTask *>(watcher->data); uv_timer_again(&sAresTimer); if (status < 0) { ares_process_fd(sAresChannel, task->socket, task->socket); return; } ares_process_fd(sAresChannel, (events & UV_READABLE) ? task->socket : ARES_SOCKET_BAD, (events & UV_WRITABLE) ? task->socket : ARES_SOCKET_BAD); } static void aresSockstateCallback(void *data, ares_socket_t sock, int read, int write) { auto itr = std::find_if(sAresTasks.begin(), sAresTasks.end(), [&](const auto &t) { return t->socket == sock; }); if (read || write) { if (itr == sAresTasks.end()) { auto task = std::make_unique<AresTask>(); uv_poll_init_socket(&sNetworkLoop, &task->poll, sock); task->poll.data = task.get(); task->socket = sock; itr = sAresTasks.emplace(sAresTasks.end(), std::move(task)); } uv_poll_start(&(*itr)->poll, (read ? UV_READABLE : 0) | (write ? UV_WRITABLE : 0), aresPollCallback); } else { if (itr != sAresTasks.end()) { uv_poll_stop(&(*itr)->poll); sAresTasks.erase(itr); } } } static void networkTaskThread() { ares_library_init(ARES_LIB_INIT_ALL); auto options = ares_options { 0 }; options.flags = ARES_FLAG_NOCHECKRESP; options.sock_state_cb = &aresSockstateCallback; options.sock_state_cb_data = nullptr; ares_init_options(&sAresChannel, &options, ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB); uv_loop_init(&sNetworkLoop); uv_timer_init(&sNetworkLoop, &sAresTimer); uv_timer_start(&sAresTimer, aresTimerCallback, 1000, 1000); uv_async_init(&sNetworkLoop, &sAsyncPendingNetworkEvent, &uvAsyncCallback); uv_run(&sNetworkLoop, UV_RUN_DEFAULT); uv_loop_close(&sNetworkLoop); ares_destroy(sAresChannel); ares_library_cleanup(); } void startNetworkTaskThread() { sNetworkTaskThreadRunning = true; sNetworkTaskThread = std::thread { networkTaskThread }; } void stopNetworkTaskThread() { if (sNetworkTaskThreadRunning) { sNetworkTaskThreadRunning = false; uv_async_send(&sAsyncPendingNetworkEvent); sNetworkTaskThread.join(); } } uv_loop_t * networkUvLoop() { return &sNetworkLoop; } ares_channel networkAresChannel() { return sAresChannel; } void submitNetworkTask(NetworkTask task) { sPendingTasksMutex.lock(); sPendingTasks.emplace_back(std::move(task)); sPendingTasksMutex.unlock(); uv_async_send(&sAsyncPendingNetworkEvent); } } // ios::internal ================================================ FILE: src/libdecaf/src/ios/ios_network_thread.h ================================================ #pragma once #include <ares.h> #include <functional> #include <uv.h> namespace ios::internal { using NetworkTask = std::function<void()>; void startNetworkTaskThread(); void stopNetworkTaskThread(); void submitNetworkTask(NetworkTask task); uv_loop_t * networkUvLoop(); ares_channel networkAresChannel(); } // namespace ios::internal ================================================ FILE: src/libdecaf/src/ios/ios_stackobject.h ================================================ #pragma once #include "kernel/ios_kernel_thread.h" #include <algorithm> #include <common/align.h> #include <common/decaf_assert.h> #include <libcpu/be2_struct.h> #include <memory> namespace ios { template<typename Type, size_t NumElements = 1> class StackObject : public phys_ptr<Type> { static constexpr auto AlignedSize = align_up(static_cast<uint32_t>(sizeof(Type) * NumElements), std::max<std::size_t>(alignof(Type), 4u)); public: StackObject() { auto thread = kernel::internal::getCurrentThread(); auto ptr = phys_cast<uint8_t *>(thread->userContext.stackPointer) - AlignedSize; phys_ptr<Type>::mAddress = phys_cast<phys_addr>(ptr); thread->userContext.stackPointer = ptr; std::uninitialized_default_construct_n(phys_ptr<Type>::get(), NumElements); } ~StackObject() { std::destroy_n(phys_ptr<Type>::get(), NumElements); auto thread = kernel::internal::getCurrentThread(); auto ptr = phys_cast<uint8_t *>(thread->userContext.stackPointer); decaf_check(phys_cast<phys_addr>(ptr) == phys_ptr<Type>::mAddress); thread->userContext.stackPointer = ptr + AlignedSize; } }; template<typename Type, size_t NumElements> class StackArray : public StackObject<Type, NumElements> { public: using StackObject<Type, NumElements>::StackObject; constexpr uint32_t size() const { return NumElements; } constexpr auto &operator[](std::size_t index) { return phys_ptr<Type>::get()[index]; } constexpr const auto &operator[](std::size_t index) const { return phys_ptr<Type>::get()[index]; } }; } // namespace ios ================================================ FILE: src/libdecaf/src/ios/ios_worker_thread.cpp ================================================ #include "ios_worker_thread.h" #include <atomic> #include <condition_variable> #include <functional> #include <mutex> #include <queue> #include <thread> namespace ios::internal { using WorkerTask = std::function<void()>; static std::thread sWorkerThread; static std::atomic<bool> sWorkerThreadRunning { false }; static std::condition_variable sWorkerThreadConditionVariable; static std::mutex sWorkerThreadMutex; static std::queue<WorkerTask> sWorkerThreadTasks; static void iosWorkerThread() { while (sWorkerThreadRunning) { auto lock = std::unique_lock { sWorkerThreadMutex }; if (sWorkerThreadTasks.empty()) { sWorkerThreadConditionVariable.wait(lock); if (!sWorkerThreadRunning) { break; } } auto task = std::move(sWorkerThreadTasks.front()); sWorkerThreadTasks.pop(); lock.unlock(); task(); } } void startWorkerThread() { if (!sWorkerThreadRunning) { sWorkerThreadRunning = true; sWorkerThread = std::thread { iosWorkerThread }; } } void stopWorkerThread() { if (sWorkerThreadRunning) { sWorkerThreadRunning = false; sWorkerThreadConditionVariable.notify_all(); sWorkerThread.join(); sWorkerThreadTasks = {}; } } void submitWorkerTask(WorkerTask task) { auto lock = std::unique_lock { sWorkerThreadMutex }; sWorkerThreadTasks.push(std::move(task)); sWorkerThreadConditionVariable.notify_all(); } } // namespace ios::internal ================================================ FILE: src/libdecaf/src/ios/ios_worker_thread.h ================================================ #include <functional> namespace ios::internal { using WorkerTask = std::function<void()>; void startWorkerThread(); void stopWorkerThread(); void submitWorkerTask(WorkerTask task); } // namespace ios::internal ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel.cpp ================================================ #include "ios_kernel.h" #include "ios_kernel_debug.h" #include "ios_kernel_hardware.h" #include "ios_kernel_heap.h" #include "ios_kernel_ipc_thread.h" #include "ios_kernel_otp.h" #include "ios_kernel_process.h" #include "ios_kernel_resourcemanager.h" #include "ios_kernel_semaphore.h" #include "ios_kernel_scheduler.h" #include "ios_kernel_thread.h" #include "ios_kernel_timer.h" #include "ios/acp/ios_acp.h" #include "ios/auxil/ios_auxil.h" #include "ios/bsp/ios_bsp.h" #include "ios/crypto/ios_crypto.h" #include "ios/fpd/ios_fpd.h" #include "ios/fs/ios_fs.h" #include "ios/mcp/ios_mcp.h" #include "ios/net/ios_net.h" #include "ios/nim/ios_nim.h" #include "ios/nsec/ios_nsec.h" #include "ios/pad/ios_pad.h" #include "ios/test/ios_test.h" #include "ios/usb/ios_usb.h" #include "ios/ios_enum.h" #include "ios/ios_stackobject.h" #include <common/log.h> #include <libcpu/cpu_formatters.h> #include <functional> namespace ios::kernel { using namespace std::chrono_literals; constexpr auto RootThreadNumMessages = 1u; constexpr auto RootThreadStackSize = 0x2000u; constexpr auto RootThreadPriority = 126u; struct RootThreadMessage { be2_val<RootThreadCommand> command; be2_array<uint32_t, 13> args; }; CHECK_SIZE(RootThreadMessage, 0x38); struct StaticKernelData { be2_val<ThreadId> threadId; be2_val<TimerId> timerId; be2_val<MessageQueueId> messageQueueId; be2_array<Message, RootThreadNumMessages> messageBuffer; be2_array<uint8_t, RootThreadStackSize> threadStack; be2_struct<RootThreadMessage> rootTimerMessage; be2_struct<RootThreadMessage> sysprotEventMessage; }; static phys_ptr<StaticKernelData> sData; struct ProcessInfo { ProcessId pid; ThreadEntryFn entry; int32_t priority; uint32_t stackSize; uint32_t memPermMask; const char *threadName; }; static ProcessInfo sProcessBootInfo[] = { { ProcessId::MCP, mcp::processEntryPoint, 124, 0x2000, 0xC0030, "McpProcess" }, { ProcessId::BSP, bsp::processEntryPoint, 125, 0x1000, 0x100000, "BspProcess" }, { ProcessId::CRYPTO, crypto::processEntryPoint, 123, 0x1000, 0xC0030, "CryptoProcess" }, { ProcessId::USB, usb::processEntryPoint, 107, 0x4000, 0x38600, "UsbProcess" }, { ProcessId::FS, fs::processEntryPoint, 85, 0x4000, 0x1C5870, "FsProcess" }, { ProcessId::PAD, pad::processEntryPoint, 117, 0x2000, 0x8180, "PadProcess" }, { ProcessId::NET, net::processEntryPoint, 80, 0x4000, 0x2000, "NetProcess" }, { ProcessId::NIM, nim::processEntryPoint, 50, 0x4000, 0, "NimProcess" }, { ProcessId::NSEC, nsec::processEntryPoint, 50, 0x1000, 0, "NsecProcess" }, { ProcessId::FPD, fpd::processEntryPoint, 50, 0x4000, 0, "FpdProcess" }, { ProcessId::ACP, acp::processEntryPoint, 50, 0x4000, 0, "AcpProcess" }, { ProcessId::AUXIL, auxil::processEntryPoint, 70, 0x4000, 0, "AuxilProcess" }, { ProcessId::TEST, test::processEntryPoint, 75, 0x2000, 0, "TestProcess" }, }; static Error startProcesses(bool bootOnlyBSP) { for (auto &info : sProcessBootInfo) { if (bootOnlyBSP) { if (info.pid != ProcessId::BSP) { continue; } } else if (info.pid == ProcessId::BSP) { continue; } auto stackPtr = allocProcessStatic(info.pid, info.stackSize, 0x10); auto error = IOS_CreateThread(info.entry, phys_cast<void *>(phys_addr { static_cast<uint32_t>(info.pid) }), phys_cast<uint8_t *>(stackPtr) + info.stackSize, info.stackSize, info.priority, ThreadFlags::AllocateTLS | ThreadFlags::Detached); if (error < Error::OK) { gLog->warn("Error creating process thread for pid {}, error = {}", info.pid, error); continue; } auto threadId = static_cast<ThreadId>(error); auto thread = internal::getThread(threadId); thread->pid = info.pid; internal::setThreadName(threadId, info.threadName); error = IOS_StartThread(threadId); if (error < Error::OK) { gLog->warn("Error starting process thread for pid {}, error = {}", info.pid, error); continue; } } return Error::OK; } static Error initialiseRootThread() { StackObject<Message> message; auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { gLog->error("Failed to create root thread message queue, error = {}", error); return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); error = IOS_CreateTimer(5000us, 1000000us, sData->messageQueueId, makeMessage(phys_addrof(sData->rootTimerMessage))); if (error < Error::OK) { gLog->error("Failed to create root thread timer, error = {}", error); return error; } sData->timerId = static_cast<TimerId>(error); error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { gLog->error("Failed to receive root thread timer message, error = {}", error); } error = IOS_HandleEvent(DeviceId::SysProt, sData->messageQueueId, makeMessage(phys_addrof(sData->sysprotEventMessage))); if (error < Error::OK) { gLog->error("Failed to register sysprot event handler, error = {}", error); return error; } return IOS_ClearAndEnable(DeviceId::SysProt); } static Error handleTimerEvent() { return Error::OK; } static Error handleSysprotEvent() { return Error::OK; } static Error kernelEntryPoint(phys_ptr<void> context) { StackObject<Message> message; // Start timer thread internal::startTimerThread(); // Set initial process caps internal::setSecurityLevel(SecurityLevel::Normal); internal::setClientCapability(ProcessId::KERNEL, FeatureId { 0x7FFFFFFF }, 0xFFFFFFFFu); internal::setClientCapability(ProcessId::MCP, FeatureId { 0x7FFFFFFF }, 0xFFFFFFFFu); internal::setClientCapability(ProcessId::BSP, FeatureId { 0x7FFFFFFF }, 0xFFFFFFFFu); for (auto i = +ProcessId::CRYPTO; i < NumIosProcess; ++i) { internal::setClientCapability(ProcessId { i }, FeatureId { 1 }, 0xF); } // Initialise shared heap auto error = IOS_CreateHeap(phys_cast<void *>(phys_addr { 0x1D000000 }), 0x2B00000); if (error < Error::OK) { gLog->error("Failed to create shared heap, error = {}", error); return error; } auto sharedHeapId = static_cast<HeapId>(error); if (sharedHeapId != 1) { gLog->error("Expected IOS kernel sharedHeapId to be 1, found {}", sharedHeapId); return Error::Invalid; } error = IOS_CreateCrossProcessHeap(0x20000); if (error < Error::OK) { gLog->error("Failed to create cross process heap, error = {}", error); return error; } // Start the BSP process error = startProcesses(true); if (error < Error::OK) { gLog->error("Failed to start BSP process, error = {}", error); return error; } // Wait for BSP to start IOS_SetThreadPriority(CurrentThread, 124); while (!internal::bspReady()) { IOS_YieldCurrentThread(); } IOS_SetThreadPriority(CurrentThread, 126); // Initialise root kernel thread error = initialiseRootThread(); if (error < Error::OK) { gLog->error("Failed to initialise root thread, error = {}", error); return error; } // Start the rest of the processes error = startProcesses(false); if (error < Error::OK) { gLog->error("Failed to start remaining IOS processes, error = {}", error); return error; } // Start IPC thread internal::startIpcThread(); while (true) { error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error) { return error; } auto rootThreadMessage = parseMessage<RootThreadMessage>(message); switch (rootThreadMessage->command) { case RootThreadCommand::Timer: error = handleTimerEvent(); break; case RootThreadCommand::SysprotEvent: error = handleSysprotEvent(); break; default: gLog->warn("Received unexpected message on root thread, command = {}", rootThreadMessage->command); } } } Error start() { // Initialise static memory internal::initialiseProcessStaticAllocators(); internal::initialiseStaticHardwareData(); internal::initialiseStaticHeapData(); internal::initialiseStaticSchedulerData(); internal::initialiseStaticMessageQueueData(); internal::initialiseStaticResourceManagerData(); internal::initialiseStaticSemaphoreData(); internal::initialiseStaticThreadData(); internal::initialiseStaticTimerData(); internal::initialiseOtp(); sData = allocProcessStatic<StaticKernelData>(); sData->rootTimerMessage.command = RootThreadCommand::Timer; sData->sysprotEventMessage.command = RootThreadCommand::SysprotEvent; // Create root kernel thread auto error = IOS_CreateThread(kernelEntryPoint, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), static_cast<uint32_t>(sData->threadStack.size()), RootThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } // Force start the root kernel thread. We cannot use IOS_StartThread // because it reschedules and we are not running on an IOS thread. auto threadId = static_cast<ThreadId>(error); auto thread = internal::getThread(threadId); thread->state = ThreadState::Ready; internal::setThreadName(threadId, "KernelProcess"); internal::queueThread(thread); // Send the power on interrupt internal::setInterruptAhbAll(AHBALL {}.PowerButton(true)); // Start the host hardware thread internal::startHardwareThread(); return Error::OK; } void join() { internal::joinHardwareThread(); } void stop() { internal::stopHardwareThread(); } } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::kernel { Error start(); void join(); void stop(); } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_debug.cpp ================================================ #include "ios_kernel_debug.h" #include "ios_kernel_process.h" namespace ios::kernel { static SecurityLevel sSecurityLevel = SecurityLevel::Normal; Error IOS_SetSecurityLevel(SecurityLevel level) { if (IOS_GetCurrentProcessId() != static_cast<Error>(ProcessId::MCP)) { return Error::Access; } sSecurityLevel = level; return Error::OK; } SecurityLevel IOS_GetSecurityLevel() { return sSecurityLevel; } namespace internal { void setSecurityLevel(SecurityLevel level) { sSecurityLevel = level; } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_debug.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios/ios_enum.h" namespace ios::kernel { Error IOS_SetSecurityLevel(SecurityLevel level); SecurityLevel IOS_GetSecurityLevel(); namespace internal { void setSecurityLevel(SecurityLevel level); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_enum.h ================================================ #ifndef IOS_KERNEL_ENUM_H #define IOS_KERNEL_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(kernel) ENUM_BEG(DeviceId, uint32_t) ENUM_VALUE(Timer, 0) ENUM_VALUE(NandInterfaceAHBALL, 1) ENUM_VALUE(AesEngineAHBALL, 2) ENUM_VALUE(Sha1EngineAHBALL, 3) ENUM_VALUE(UsbEhci, 4) ENUM_VALUE(UsbOhci0, 5) ENUM_VALUE(UsbOhci1, 6) ENUM_VALUE(SdHostController, 7) ENUM_VALUE(Wireless80211, 8) ENUM_VALUE(SysEvent, 9) ENUM_VALUE(SysProt, 10) ENUM_VALUE(PowerButton, 15) ENUM_VALUE(DriveInterface, 16) ENUM_VALUE(ExiRtc, 18) ENUM_VALUE(Sata, 26) ENUM_VALUE(IpcStarbuckCompat, 29) ENUM_VALUE(Unknown30, 30) ENUM_VALUE(Unknown31, 31) ENUM_VALUE(Unknown32, 32) ENUM_VALUE(Unknown33, 33) ENUM_VALUE(Drh, 34) ENUM_VALUE(Unknown35, 35) ENUM_VALUE(Unknown36, 36) ENUM_VALUE(Unknown37, 37) ENUM_VALUE(AesEngineAHBLT, 38) ENUM_VALUE(Sha1EngineAHBLT, 39) ENUM_VALUE(Unknown40, 40) ENUM_VALUE(Unknown41, 41) ENUM_VALUE(Unknown42, 42) ENUM_VALUE(I2CEspresso, 43) ENUM_VALUE(I2CStarbuck, 44) ENUM_VALUE(IpcStarbuckCore2, 45) ENUM_VALUE(IpcStarbuckCore1, 46) ENUM_VALUE(IpcStarbuckCore0, 47) ENUM_END(DeviceId) FLAGS_BEG(HeapFlags, uint32_t) FLAGS_VALUE(LocalProcessHeap, 1 << 0) FLAGS_VALUE(CrossProcessHeap, 1 << 1) FLAGS_END(HeapFlags) ENUM_BEG(HeapBlockState, uint32_t) ENUM_VALUE(Free, 0xBABE0000) ENUM_VALUE(Allocated, 0xBABE0001) ENUM_VALUE(InnerBlock, 0xBABE0002) ENUM_END(HeapBlockState) FLAGS_BEG(MessageFlags, uint8_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(NonBlocking, 1) FLAGS_END(MessageFlags) FLAGS_BEG(MessageQueueFlags, uint8_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(RegisteredEventHandler, 1) FLAGS_END(MessageQueueFlags) ENUM_BEG(OtpFieldIndex, uint32_t) ENUM_VALUE(WiiBoot1Sha1Hash, 0x1) ENUM_VALUE(WiiCommonKey, 0x5) ENUM_VALUE(WiiNgId, 0x9) ENUM_VALUE(WiiNgPrivateKey, 0xA) ENUM_VALUE(WiiNandHmac, 0x11) ENUM_VALUE(WiiNandKey, 0x16) ENUM_VALUE(WiiRngKey, 0x1A) ENUM_VALUE(SecurityLevel, 0x20) ENUM_VALUE(StarbuckAncastKey, 0x24) ENUM_VALUE(SeepromKey, 0x28) ENUM_VALUE(VwiiCommonKey, 0x34) ENUM_VALUE(CommonKey, 0x38) ENUM_VALUE(SslRsaKey, 0x48) ENUM_VALUE(UsbStorageKey, 0x4C) ENUM_VALUE(Unknown0x50, 0x50) ENUM_VALUE(XorKey, 0x54) ENUM_VALUE(RngKey, 0x58) ENUM_VALUE(SlcKey, 0x5C) ENUM_VALUE(MlcKey, 0x60) ENUM_VALUE(ShddKey, 0x64) ENUM_VALUE(DrhWlanKey, 0x68) ENUM_VALUE(SlcHmac, 0x78) ENUM_VALUE(NgId, 0x87) ENUM_VALUE(NgPrivateKey, 0x88) ENUM_VALUE(NssPrivateKey, 0x90) ENUM_VALUE(OtpRngSeed, 0x98) ENUM_VALUE(RootCertMsId, 0xA0) ENUM_VALUE(RootCertCaId, 0xA1) ENUM_VALUE(RootCertNgId, 0xC2) ENUM_VALUE(RootCertNgSig, 0xC3) ENUM_VALUE(WiiKoreanKey, 0xD2) ENUM_VALUE(WiiNssPrivateKey, 0xD8) ENUM_VALUE(Boot1Key, 0xE8) ENUM_VALUE(OtpVersion, 0xF9) ENUM_VALUE(OtpDateCode, 0xFA) ENUM_VALUE(OtpVersionName, 0xFC) ENUM_VALUE(JtagStatus, 0xFF) ENUM_END(OtpFieldIndex) ENUM_BEG(OtpFieldSize, uint32_t) ENUM_VALUE(WiiBoot1Sha1Hash, 0x14) ENUM_VALUE(WiiCommonKey, 0x10) ENUM_VALUE(WiiNgId, 0x4) ENUM_VALUE(WiiNgPrivateKey, 0x1C) ENUM_VALUE(WiiNandHmac, 0x14) ENUM_VALUE(WiiNandKey, 0x10) ENUM_VALUE(WiiRngKey, 0x10) ENUM_VALUE(SecurityLevel, 0x4) ENUM_VALUE(StarbuckAncastKey, 0x10) ENUM_VALUE(SeepromKey, 0x10) ENUM_VALUE(VwiiCommonKey, 0x10) ENUM_VALUE(CommonKey, 0x10) ENUM_VALUE(SslRsaKey, 0x10) ENUM_VALUE(UsbStorageKey, 0x10) ENUM_VALUE(Unknown0x50, 0x10) ENUM_VALUE(XorKey, 0x10) ENUM_VALUE(RngKey, 0x10) ENUM_VALUE(SlcKey, 0x10) ENUM_VALUE(MlcKey, 0x10) ENUM_VALUE(ShddKey, 0x10) ENUM_VALUE(DrhWlanKey, 0x10) ENUM_VALUE(SlcHmac, 0x14) ENUM_VALUE(NgId, 0x4) ENUM_VALUE(NgPrivateKey, 0x20) ENUM_VALUE(NssPrivateKey, 0x20) ENUM_VALUE(OtpRngSeed, 0x10) ENUM_VALUE(RootCertMsId, 0x4) ENUM_VALUE(RootCertCaId, 0x4) ENUM_VALUE(RootCertNgId, 0x4) ENUM_VALUE(RootCertNgSig, 0x3C) ENUM_VALUE(WiiKoreanKey, 0x10) ENUM_VALUE(WiiNssPrivateKey, 0x20) ENUM_VALUE(Boot1Key, 0x10) ENUM_VALUE(OtpVersion, 0x4) ENUM_VALUE(OtpDateCode, 0x8) ENUM_VALUE(OtpVersionName, 0x8) ENUM_VALUE(JtagStatus, 0x4) ENUM_END(OtpFieldSize) ENUM_BEG(RootThreadCommand, int32_t) ENUM_VALUE(Timer, 0x100) ENUM_VALUE(SysprotEvent, 0x101) ENUM_END(RootThreadCommand) ENUM_BEG(ResourceHandleState, int32_t) ENUM_VALUE(Free, 0x00) ENUM_VALUE(Opening, 0x11) ENUM_VALUE(Open, 0x22) ENUM_VALUE(Closed, 0x33) ENUM_END(ResourceHandleState) ENUM_BEG(ResourcePermissionGroup, int32_t) ENUM_VALUE(None, 0) ENUM_VALUE(BSP, 1) ENUM_VALUE(DK, 3) ENUM_VALUE(FS, 11) ENUM_VALUE(UHS, 12) ENUM_VALUE(MCP, 13) ENUM_VALUE(NIM, 14) ENUM_VALUE(ACT, 15) ENUM_VALUE(FPD, 16) ENUM_VALUE(BOSS, 17) ENUM_VALUE(ACP, 18) ENUM_VALUE(PDM, 19) ENUM_VALUE(AC, 20) ENUM_VALUE(NDM, 21) ENUM_VALUE(NSEC, 22) ENUM_VALUE(PAD, 1000) ENUM_VALUE(All, 0x7FFFFFFF) ENUM_END(ResourcePermissionGroup) ENUM_BEG(SecurityLevel, uint32_t) ENUM_VALUE(Test, 10) ENUM_VALUE(Debug, 20) ENUM_VALUE(Normal, 30) ENUM_END(SecurityLevel) FLAGS_BEG(ThreadFlags, uint32_t) FLAGS_VALUE(Detached, 1 << 0) FLAGS_VALUE(AllocateTLS, 1 << 1) FLAGS_END(ThreadFlags) ENUM_BEG(ThreadState, uint32_t) ENUM_VALUE(Available, 0x00) ENUM_VALUE(Ready, 0x01) ENUM_VALUE(Running, 0x02) ENUM_VALUE(Stopped, 0x03) ENUM_VALUE(Waiting, 0x04) ENUM_VALUE(Dead, 0x05) ENUM_VALUE(Faulted, 0x06) ENUM_VALUE(Unknown, 0x07) ENUM_END(ThreadState) ENUM_BEG(TimerState, uint32_t) ENUM_VALUE(Free, 0x00) ENUM_VALUE(Ready, 0x01) ENUM_VALUE(Running, 0x02) ENUM_VALUE(Triggered, 0x03) ENUM_VALUE(Stopped, 0x04) ENUM_END(TimerState) ENUM_NAMESPACE_EXIT(kernel) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_KERNEL_ENUM_H ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_hardware.cpp ================================================ #include "ios_kernel_hardware.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_scheduler.h" #include "ios_kernel_process.h" #include "ios_kernel_thread.h" #include <atomic> #include <condition_variable> #include <thread> #include <mutex> namespace ios::kernel { struct EventHandler { be2_phys_ptr<MessageQueue> queue; be2_val<Message> message; be2_val<ProcessId> pid; PADDING(0x4); }; CHECK_OFFSET(EventHandler, 0x00, queue); CHECK_OFFSET(EventHandler, 0x04, message); CHECK_OFFSET(EventHandler, 0x08, pid); CHECK_SIZE(EventHandler, 0x10); struct StaticHardwareData { be2_array<EventHandler, 0x30> eventHandlers; be2_val<BOOL> bspReady; }; static phys_ptr<StaticHardwareData> sHardwareData; //! Interrupt mask for ARM AHB IRQs static std::atomic<uint32_t> LT_INTMR_AHBALL_ARM { 0 }; //! Triggered interrupts for ARM AHB IRQs static std::atomic<uint32_t> LT_INTSR_AHBALL_ARM { 0 }; //! Interrupt mask for latte ARM AHB IRQs static std::atomic<uint32_t> LT_INTMR_AHBLT_ARM { 0 }; //! Triggered interrupts for latte ARM AHB IRQs static std::atomic<uint32_t> LT_INTSR_AHBLT_ARM { 0 }; static std::thread sHardwareThread; static std::condition_variable sHardwareConditionVariable; static std::mutex sHardwareMutex; static std::atomic<bool> sRunning; /** * Registers a message queue as the event handler for a device. * * Sends the given message to the message queue when an interrupt is received * for the given device. */ Error IOS_HandleEvent(DeviceId id, MessageQueueId qid, Message message) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(qid, &queue); if (error < Error::OK) { return error; } if (id >= sHardwareData->eventHandlers.size()) { return Error::Invalid; } queue->flags |= MessageQueueFlags::RegisteredEventHandler; auto &handler = sHardwareData->eventHandlers[id]; handler.message = message; handler.queue = queue; handler.pid = internal::getCurrentProcessId(); return Error::OK; } /** * Unregister an event handler previously registered by IOS_HandleEvent. */ Error IOS_UnregisterEventHandler(DeviceId id) { if (id >= sHardwareData->eventHandlers.size()) { return Error::Invalid; } auto &handler = sHardwareData->eventHandlers[id]; if (handler.queue) { handler.queue->flags &= ~MessageQueueFlags::RegisteredEventHandler; } std::memset(phys_addrof(handler).get(), 0, sizeof(handler)); return Error::OK; } /** * Clear and enable interrupts for given DeviceId. */ Error IOS_ClearAndEnable(DeviceId id) { auto thread = internal::getCurrentThread(); // We don't actually clear the signalling register so we can avoid race // conditions with signals coming from external threads. Instead we just // enable the mask, which could lead to spurious wakeups but they are much // better than not waking up at all. auto enableAhbAll = [](AHBALL mask) { LT_INTMR_AHBALL_ARM |= mask.value; }; auto enableAhbLatte = [](AHBLT mask) { LT_INTMR_AHBLT_ARM |= mask.value; }; switch (id) { case DeviceId::Timer: enableAhbAll(AHBALL::get(0).Timer(true)); break; case DeviceId::NandInterfaceAHBALL: enableAhbAll(AHBALL::get(0).NandInterface(true)); break; case DeviceId::AesEngineAHBALL: decaf_check(thread->pid == ProcessId::CRYPTO); enableAhbAll(AHBALL::get(0).AesEngine(true)); break; case DeviceId::Sha1EngineAHBALL: decaf_check(thread->pid == ProcessId::CRYPTO); enableAhbAll(AHBALL::get(0).Sha1Engine(true)); break; case DeviceId::UsbEhci: enableAhbAll(AHBALL::get(0).UsbEhci(true)); break; case DeviceId::UsbOhci0: enableAhbAll(AHBALL::get(0).UsbOhci0(true)); break; case DeviceId::UsbOhci1: enableAhbAll(AHBALL::get(0).UsbOhci1(true)); break; case DeviceId::SdHostController: enableAhbAll(AHBALL::get(0).SdHostController(true)); break; case DeviceId::Wireless80211: enableAhbAll(AHBALL::get(0).Wireless80211(true)); break; case 9: // TODO: latte gpio int flag = 1 // TODO: latte gpio int mask |= 1 break; case DeviceId::SysProt: decaf_check(thread->pid == ProcessId::KERNEL); enableAhbAll(AHBALL::get(0).SysProt(true)); break; case DeviceId::PowerButton: enableAhbAll(AHBALL::get(0).PowerButton(true)); break; case DeviceId::DriveInterface: enableAhbAll(AHBALL::get(0).DriveInterface(true)); break; case DeviceId::ExiRtc: enableAhbAll(AHBALL::get(0).ExiRtc(true)); break; case DeviceId::Sata: enableAhbAll(AHBALL::get(0).Sata(true)); break; case DeviceId::IpcStarbuckCompat: decaf_check(thread->pid == ProcessId::KERNEL); enableAhbAll(AHBALL::get(0).IpcStarbuckCompat(true)); break; case DeviceId::Unknown30: enableAhbLatte(AHBLT::get(0).SdHostController(true)); break; case DeviceId::Unknown31: enableAhbLatte(AHBLT::get(0).Unknown1(true)); break; case DeviceId::Unknown32: enableAhbLatte(AHBLT::get(0).Unknown2(true)); break; case DeviceId::Unknown33: enableAhbLatte(AHBLT::get(0).Unknown3(true)); break; case DeviceId::Drh: enableAhbLatte(AHBLT::get(0).Drh(true)); break; case DeviceId::Unknown35: enableAhbLatte(AHBLT::get(0).Unknown5(true)); break; case DeviceId::Unknown36: enableAhbLatte(AHBLT::get(0).Unknown6(true)); break; case DeviceId::Unknown37: enableAhbLatte(AHBLT::get(0).Unknown7(true)); break; case DeviceId::AesEngineAHBLT: decaf_check(thread->pid == ProcessId::CRYPTO); enableAhbLatte(AHBLT::get(0).AesEngine(true)); break; case DeviceId::Sha1EngineAHBLT: decaf_check(thread->pid == ProcessId::CRYPTO); enableAhbLatte(AHBLT::get(0).Sha1Engine(true)); break; case DeviceId::Unknown40: enableAhbLatte(AHBLT::get(0).Unknown10(true)); break; case DeviceId::Unknown41: enableAhbLatte(AHBLT::get(0).Unknown11(true)); break; case DeviceId::Unknown42: enableAhbLatte(AHBLT::get(0).Unknown12(true)); break; case DeviceId::I2CEspresso: enableAhbLatte(AHBLT::get(0).I2CEspresso(true)); break; case DeviceId::I2CStarbuck: enableAhbLatte(AHBLT::get(0).I2CStarbuck(true)); break; case DeviceId::IpcStarbuckCore2: decaf_check(thread->pid == ProcessId::KERNEL); enableAhbLatte(AHBLT::get(0).IpcStarbuckCore2(true)); break; case DeviceId::IpcStarbuckCore1: decaf_check(thread->pid == ProcessId::KERNEL); enableAhbLatte(AHBLT::get(0).IpcStarbuckCore1(true)); break; case DeviceId::IpcStarbuckCore0: decaf_check(thread->pid == ProcessId::KERNEL); enableAhbLatte(AHBLT::get(0).IpcStarbuckCore0(true)); break; default: return Error::Invalid; } return Error::OK; } /** * Set BSP ready flag used for boot. */ Error IOS_SetBspReady() { sHardwareData->bspReady = TRUE; return Error::OK; } namespace internal { /** * Get BSP ready flag. */ bool bspReady() { return !!sHardwareData->bspReady; } /** * Sends a message to the device event handler message queue. * * We cannot use IOS_SendMessage due to different scheduling requirements here. */ static void sendEventHandlerMessageNoLock(DeviceId id) { auto &handler = sHardwareData->eventHandlers[id]; auto queue = handler.queue; if (queue && queue->used < queue->size) { auto index = (queue->first + queue->used) % queue->size; queue->messages[index] = handler.message; queue->used++; internal::wakeupOneThread(phys_addrof(queue->receiveQueue), Error::OK); } } void setInterruptAhbAll(AHBALL mask) { auto lock = std::unique_lock { sHardwareMutex }; LT_INTSR_AHBALL_ARM |= mask.value; sHardwareConditionVariable.notify_all(); } void setInterruptAhbLt(AHBLT mask) { auto lock = std::unique_lock { sHardwareMutex }; LT_INTSR_AHBLT_ARM |= mask.value; sHardwareConditionVariable.notify_all(); } void unregisterEventHandlerQueue(MessageQueueId queue) { for (auto &handler : sHardwareData->eventHandlers) { if (handler.queue->uid == queue) { std::memset(phys_addrof(handler).get(), 0, sizeof(handler)); break; } } } void initialiseStaticHardwareData() { LT_INTMR_AHBALL_ARM.store(AHBALL::get(0) .Timer(true) .LatteGpioStarbuck(true) .PowerButton(true) .value); LT_INTSR_AHBALL_ARM.store(0); LT_INTMR_AHBLT_ARM.store(0); LT_INTSR_AHBLT_ARM.store(0); sHardwareData = allocProcessStatic<StaticHardwareData>(); } /** * Send a message to the queues registered with IOS_HandleEvent for the events * indicated by the AHBALL and AHBLT register values. */ static void handleEvents(AHBALL ahbAll, AHBLT ahbLatte) { if (ahbAll.Timer()) { sendEventHandlerMessageNoLock(DeviceId::Timer); } if (ahbAll.NandInterface()) { sendEventHandlerMessageNoLock(DeviceId::NandInterfaceAHBALL); } if (ahbAll.AesEngine()) { sendEventHandlerMessageNoLock(DeviceId::AesEngineAHBALL); } if (ahbAll.Sha1Engine()) { sendEventHandlerMessageNoLock(DeviceId::Sha1EngineAHBALL); } if (ahbAll.UsbEhci()) { sendEventHandlerMessageNoLock(DeviceId::UsbEhci); } if (ahbAll.UsbOhci0()) { sendEventHandlerMessageNoLock(DeviceId::UsbOhci0); } if (ahbAll.UsbOhci1()) { sendEventHandlerMessageNoLock(DeviceId::UsbOhci1); } if (ahbAll.SdHostController()) { sendEventHandlerMessageNoLock(DeviceId::SdHostController); } if (ahbAll.Wireless80211()) { sendEventHandlerMessageNoLock(DeviceId::Wireless80211); } if (ahbAll.SysProt()) { sendEventHandlerMessageNoLock(DeviceId::SysProt); } if (ahbAll.PowerButton()) { sendEventHandlerMessageNoLock(DeviceId::PowerButton); } if (ahbAll.DriveInterface()) { sendEventHandlerMessageNoLock(DeviceId::DriveInterface); } if (ahbAll.ExiRtc()) { sendEventHandlerMessageNoLock(DeviceId::ExiRtc); } if (ahbAll.Sata()) { sendEventHandlerMessageNoLock(DeviceId::Sata); } if (ahbAll.IpcStarbuckCompat()) { sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCompat); } if (ahbLatte.SdHostController()) { sendEventHandlerMessageNoLock(DeviceId::Unknown30); } if (ahbLatte.Unknown1()) { sendEventHandlerMessageNoLock(DeviceId::Unknown31); } if (ahbLatte.Unknown2()) { sendEventHandlerMessageNoLock(DeviceId::Unknown32); } if (ahbLatte.Unknown3()) { sendEventHandlerMessageNoLock(DeviceId::Unknown33); } if (ahbLatte.Drh()) { sendEventHandlerMessageNoLock(DeviceId::Drh); } if (ahbLatte.Unknown5()) { sendEventHandlerMessageNoLock(DeviceId::Unknown35); } if (ahbLatte.Unknown6()) { sendEventHandlerMessageNoLock(DeviceId::Unknown36); } if (ahbLatte.Unknown7()) { sendEventHandlerMessageNoLock(DeviceId::Unknown37); } if (ahbLatte.AesEngine()) { sendEventHandlerMessageNoLock(DeviceId::AesEngineAHBLT); } if (ahbLatte.Sha1Engine()) { sendEventHandlerMessageNoLock(DeviceId::Sha1EngineAHBLT); } if (ahbLatte.Unknown10()) { sendEventHandlerMessageNoLock(DeviceId::Unknown40); } if (ahbLatte.Unknown11()) { sendEventHandlerMessageNoLock(DeviceId::Unknown41); } if (ahbLatte.Unknown12()) { sendEventHandlerMessageNoLock(DeviceId::Unknown42); } if (ahbLatte.I2CEspresso()) { sendEventHandlerMessageNoLock(DeviceId::I2CEspresso); } if (ahbLatte.I2CStarbuck()) { sendEventHandlerMessageNoLock(DeviceId::I2CStarbuck); } if (ahbLatte.IpcStarbuckCore0()) { sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCore0); } if (ahbLatte.IpcStarbuckCore1()) { sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCore1); } if (ahbLatte.IpcStarbuckCore2()) { sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCore2); } } #if 0 /** * Check and handle any pending interrupts. * * To be called from kernel scheduler to ensure we do not miss any interrupts * in the case that we always have new kernel threads to run. */ void checkAndHandleInterrupts() { // Check for pending interrupts auto lock = std::unique_lock { sHardwareMutex }; // Read unmasked interrupts auto ahbLatte = LT_INTSR_AHBLT_ARM & LT_INTMR_AHBLT_ARM; auto ahbAll = LT_INTSR_AHBALL_ARM & LT_INTMR_AHBALL_ARM; // Clear and disable handled interrupts LT_INTMR_AHBLT_ARM &= ~ahbLatte; LT_INTSR_AHBLT_ARM &= ~ahbLatte; LT_INTMR_AHBALL_ARM &= ~ahbAll; LT_INTSR_AHBALL_ARM &= ~ahbAll; if (ahbLatte || ahbAll) { lock.unlock(); handleEvents(AHBALL::get(ahbAll), AHBLT::get(ahbLatte)); } } #endif static void hardwareThreadEntry() { setIdleFiber(); while (sRunning) { // Check for any pending threads to run reschedule(); // Check for pending interrupts auto lock = std::unique_lock { sHardwareMutex }; // Read unmasked interrupts auto ahbLatte = LT_INTSR_AHBLT_ARM & LT_INTMR_AHBLT_ARM; auto ahbAll = LT_INTSR_AHBALL_ARM & LT_INTMR_AHBALL_ARM; // Clear and disable handled interrupts LT_INTMR_AHBLT_ARM &= ~ahbLatte; LT_INTSR_AHBLT_ARM &= ~ahbLatte; LT_INTMR_AHBALL_ARM &= ~ahbAll; LT_INTSR_AHBALL_ARM &= ~ahbAll; if (ahbLatte || ahbAll) { lock.unlock(); handleEvents(AHBALL::get(ahbAll), AHBLT::get(ahbLatte)); } else if (sRunning) { sHardwareConditionVariable.wait(lock); } } } void startHardwareThread() { sRunning = true; sHardwareThread = std::thread { hardwareThreadEntry }; } void joinHardwareThread() { sHardwareThread.join(); } void stopHardwareThread() { sRunning = false; sHardwareConditionVariable.notify_all(); } } // namespace internal } // namespace ios ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_hardware.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios_kernel_messagequeue.h" #include <chrono> #include <common/bitfield.h> namespace ios::kernel { BITFIELD_BEG(AHBALL, uint32_t) BITFIELD_ENTRY(0, 1, bool, Timer) BITFIELD_ENTRY(1, 1, bool, NandInterface) BITFIELD_ENTRY(2, 1, bool, AesEngine) BITFIELD_ENTRY(3, 1, bool, Sha1Engine) BITFIELD_ENTRY(4, 1, bool, UsbEhci) BITFIELD_ENTRY(5, 1, bool, UsbOhci0) BITFIELD_ENTRY(6, 1, bool, UsbOhci1) BITFIELD_ENTRY(7, 1, bool, SdHostController) BITFIELD_ENTRY(8, 1, bool, Wireless80211) BITFIELD_ENTRY(10, 1, bool, LatteGpioEspresso) BITFIELD_ENTRY(11, 1, bool, LatteGpioStarbuck) BITFIELD_ENTRY(12, 1, bool, SysProt) BITFIELD_ENTRY(17, 1, bool, PowerButton) BITFIELD_ENTRY(18, 1, bool, DriveInterface) BITFIELD_ENTRY(20, 1, bool, ExiRtc) BITFIELD_ENTRY(28, 1, bool, Sata) BITFIELD_ENTRY(30, 1, bool, IpcEspressoCompat) BITFIELD_ENTRY(31, 1, bool, IpcStarbuckCompat) BITFIELD_END BITFIELD_BEG(AHBLT, uint32_t) BITFIELD_ENTRY(0, 1, bool, SdHostController) BITFIELD_ENTRY(1, 1, bool, Unknown1) BITFIELD_ENTRY(2, 1, bool, Unknown2) BITFIELD_ENTRY(3, 1, bool, Unknown3) BITFIELD_ENTRY(4, 1, bool, Drh) BITFIELD_ENTRY(5, 1, bool, Unknown5) BITFIELD_ENTRY(6, 1, bool, Unknown6) BITFIELD_ENTRY(7, 1, bool, Unknown7) BITFIELD_ENTRY(8, 1, bool, AesEngine) BITFIELD_ENTRY(9, 1, bool, Sha1Engine) BITFIELD_ENTRY(10, 1, bool, Unknown10) BITFIELD_ENTRY(11, 1, bool, Unknown11) BITFIELD_ENTRY(12, 1, bool, Unknown12) BITFIELD_ENTRY(13, 1, bool, I2CEspresso) BITFIELD_ENTRY(14, 1, bool, I2CStarbuck) BITFIELD_ENTRY(26, 1, bool, IpcEspressoCore2) BITFIELD_ENTRY(27, 1, bool, IpcStarbuckCore2) BITFIELD_ENTRY(28, 1, bool, IpcEspressoCore1) BITFIELD_ENTRY(29, 1, bool, IpcStarbuckCore1) BITFIELD_ENTRY(30, 1, bool, IpcEspressoCore0) BITFIELD_ENTRY(31, 1, bool, IpcStarbuckCore0) BITFIELD_END Error IOS_HandleEvent(DeviceId id, MessageQueueId queue, Message message); Error IOS_UnregisterEventHandler(DeviceId id); Error IOS_ClearAndEnable(DeviceId id); Error IOS_SetBspReady(); namespace internal { bool bspReady(); void checkAndHandleInterrupts(); void setInterruptAhbAll(AHBALL mask); void setInterruptAhbLt(AHBLT mask); void unregisterEventHandlerQueue(MessageQueueId queue); void startHardwareThread(); void joinHardwareThread(); void stopHardwareThread(); void initialiseStaticHardwareData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_heap.cpp ================================================ #include "ios_kernel_heap.h" #include "ios_kernel_process.h" #include <array> #include <common/decaf_assert.h> #include <common/teenyheap.h> namespace ios::kernel { constexpr auto MaxNumHeaps = 48u; constexpr auto MaxNumProcessHeaps = 14u; constexpr auto HeapAllocSizeAlign = 32u; constexpr auto HeapAllocAlignAlign = 32u; struct StaticHeapData { be2_array<Heap, MaxNumHeaps> heaps; be2_array<HeapId, MaxNumProcessHeaps> localProcessHeaps; be2_array<HeapId, MaxNumProcessHeaps> crossProcessHeaps; }; static phys_ptr<StaticHeapData> sHeapData; static Error getHeap(HeapId id, bool checkPermission, phys_ptr<Heap> *outHeap) { auto error = IOS_GetCurrentProcessId(); if (error < Error::OK) { return Error::Invalid; } auto pid = static_cast<ProcessId>(error); if (id == 0 || id == CrossProcessHeapId) { id = sHeapData->crossProcessHeaps[pid]; } else if (id == LocalProcessHeapId) { id = sHeapData->localProcessHeaps[pid]; } if (id >= static_cast<HeapId>(sHeapData->heaps.size())) { return Error::Invalid; } if (sHeapData->heaps[id].pid < ProcessId::KERNEL || !sHeapData->heaps[id].base) { return Error::NoExists; } if (checkPermission && id != SharedHeapId && sHeapData->heaps[id].pid != pid) { return Error::Access; } *outHeap = phys_addrof(sHeapData->heaps[id]); return Error::OK; } Error IOS_CreateHeap(phys_ptr<void> ptr, uint32_t size) { HeapId id; if (phys_cast<phys_addr>(ptr).getAddress() & 0x1F) { return Error::Alignment; } for (id = HeapId { 1 }; id < static_cast<HeapId>(sHeapData->heaps.size()); ++id) { if (!sHeapData->heaps[id].base) { break; } } if (id >= static_cast<HeapId>(sHeapData->heaps.size())) { return Error::Max; } auto error = IOS_GetCurrentProcessId(); if (error < Error::OK) { return error; } auto pid = static_cast<ProcessId>(error); // Initialise first heap block auto firstBlock = phys_cast<HeapBlock *>(ptr); firstBlock->state = HeapBlockState::Free; firstBlock->size = static_cast<uint32_t>(size - sizeof(HeapBlock)); firstBlock->next = nullptr; firstBlock->prev = nullptr; // Initialise heap auto &heap = sHeapData->heaps[id]; heap.id = id; heap.base = ptr; heap.size = size; heap.firstFreeBlock = firstBlock; heap.pid = pid; return static_cast<Error>(id); } Error IOS_CreateLocalProcessHeap(phys_ptr<void> ptr, uint32_t size) { phys_ptr<Heap> heap; auto error = IOS_GetCurrentProcessId(); if (error < Error::OK) { return error; } auto pid = static_cast<ProcessId>(error); if (pid >= static_cast<ProcessId>(sHeapData->localProcessHeaps.size())) { return Error::Invalid; } if (sHeapData->localProcessHeaps[pid] >= 0) { return static_cast<Error>(sHeapData->localProcessHeaps[pid]); } error = IOS_CreateHeap(ptr, size); if (error < Error::OK) { return error; } auto heapId = static_cast<HeapId>(error); error = getHeap(heapId, true, &heap); if (error < Error::OK) { return error; } heap->flags |= HeapFlags::LocalProcessHeap; sHeapData->localProcessHeaps[pid] = heapId; return static_cast<Error>(heapId); } Error IOS_CreateCrossProcessHeap(uint32_t size) { phys_ptr<Heap> heap; auto error = IOS_GetCurrentProcessId(); if (error < Error::OK) { return error; } auto pid = static_cast<ProcessId>(error); if (pid >= static_cast<ProcessId>(sHeapData->crossProcessHeaps.size())) { return Error::Invalid; } if (sHeapData->crossProcessHeaps[pid] >= 0) { return static_cast<Error>(sHeapData->crossProcessHeaps[pid]); } auto ptr = IOS_HeapAlloc(SharedHeapId, size); if (!ptr) { return Error::NoResource; } error = IOS_CreateHeap(ptr, size); if (error < Error::OK) { return error; } auto heapId = static_cast<HeapId>(error); error = getHeap(heapId, true, &heap); if (error < Error::OK) { return error; } heap->flags |= HeapFlags::CrossProcessHeap; sHeapData->crossProcessHeaps[pid] = heapId; return static_cast<Error>(heapId); } Error IOS_DestroyHeap(HeapId heapId) { phys_ptr<Heap> heap; if (heapId == SharedHeapId) { return Error::Invalid; } auto error = getHeap(heapId, true, &heap); if (error < Error::OK) { return Error::Invalid; } std::memset(heap.get(), 0, sizeof(Heap)); heap->pid = ProcessId::Invalid; if (heapId == LocalProcessHeapId) { sHeapData->localProcessHeaps[internal::getCurrentProcessId()] = -4; } return Error::OK; } phys_ptr<void> IOS_HeapAlloc(HeapId id, uint32_t size) { return IOS_HeapAllocAligned(id, size, 0x20); } phys_ptr<void> IOS_HeapAllocAligned(HeapId heapId, uint32_t size, uint32_t alignment) { phys_ptr<Heap> heap; auto error = getHeap(heapId, true, &heap); if (error < Error::OK || size == 0 || alignment == 0) { return nullptr; } auto allocBlock = phys_ptr<HeapBlock> { nullptr }; auto allocBase = phys_ptr<uint8_t> { nullptr }; // Both size and alignment must be aligned. size = align_up(size, HeapAllocSizeAlign); alignment = align_up(alignment, HeapAllocAlignAlign); for (auto block = phys_ptr<HeapBlock> { heap->firstFreeBlock }; block; block = block->next) { auto base = phys_cast<uint8_t *>(block) + sizeof(HeapBlock); auto baseAddr = phys_cast<phys_addr>(base); auto alignedBaseAddr = align_up(phys_cast<phys_addr>(base), alignment); auto alignOffset = static_cast<uint32_t>(alignedBaseAddr - baseAddr); decaf_check(block->state == HeapBlockState::Free); if (alignedBaseAddr == baseAddr) { // Base already aligned if (block->size >= size) { allocBlock = block; allocBase = base; break; } } else if (alignedBaseAddr - baseAddr >= sizeof(HeapBlock)) { // Requires alignment, and has space for a HeapBlock if (block->size >= size + alignOffset) { allocBlock = block; allocBase = phys_cast<uint8_t *>(alignedBaseAddr); size += alignOffset; break; } } else if (block->size >= size + alignOffset + alignment) { // Base required double align to have space for HeapBlock allocBlock = block; allocBase = phys_cast<uint8_t *>(align_up(alignedBaseAddr + 1, alignment)); size += alignOffset + alignment; break; } } if (!allocBlock) { heap->errorCountAllocOutOfMemory++; return nullptr; } auto innerBlock = phys_cast<HeapBlock *>(allocBase - sizeof(HeapBlock)); if (innerBlock != allocBlock) { // Create inner block for aligned allocations if needed auto offset = phys_cast<uint8_t *>(innerBlock) - phys_cast<uint8_t *>(allocBlock); innerBlock->state = HeapBlockState::InnerBlock; innerBlock->size = static_cast<uint32_t>(size - offset); innerBlock->prev = allocBlock; innerBlock->next = nullptr; } // Check if there is enough size remaining to make it worth creating a new // free block. if (allocBlock->size - size > 0x40) { auto allocBlockEnd = phys_cast<uint8_t *>(allocBlock) + sizeof(HeapBlock) + allocBlock->size; auto freeBlock = phys_cast<HeapBlock *>(phys_cast<uint8_t *>(allocBlock) + sizeof(HeapBlock) + size); freeBlock->state = HeapBlockState::Free; freeBlock->size = static_cast<uint32_t>(allocBlockEnd - phys_cast<uint8_t *>(freeBlock) - sizeof(HeapBlock)); freeBlock->prev = allocBlock->prev; freeBlock->next = allocBlock->next; if (allocBlock->prev) { allocBlock->prev->next = freeBlock; } if (allocBlock->next) { allocBlock->next->prev = freeBlock; } allocBlock->size = size; if (heap->firstFreeBlock == allocBlock) { heap->firstFreeBlock = freeBlock; } } else if (heap->firstFreeBlock == allocBlock) { heap->firstFreeBlock = allocBlock->next; } allocBlock->state = HeapBlockState::Allocated; allocBlock->prev = nullptr; allocBlock->next = nullptr; heap->currentAllocatedSize += allocBlock->size; heap->largestAllocatedSize = std::max(heap->largestAllocatedSize, allocBlock->size); heap->totalAllocCount++; return allocBase; } static bool heapContainsPtr(phys_ptr<Heap> heap, phys_ptr<void> ptr) { auto start = phys_cast<uint8_t *>(heap->base); auto end = start + heap->size; if (phys_cast<uint8_t *>(ptr) < start + sizeof(Heap) + sizeof(HeapBlock)) { return false; } if (phys_cast<uint8_t *>(ptr) >= end) { return false; } return true; } static void tryMergeConsecutiveBlocks(phys_ptr<HeapBlock> first, phys_ptr<HeapBlock> second) { auto firstEnd = phys_cast<uint8_t *>(first) + sizeof(HeapBlock) + first->size; if (phys_cast<phys_addr>(firstEnd) == phys_cast<phys_addr>(second)) { first->size += static_cast<uint32_t>(second->size + sizeof(HeapBlock)); first->next = second->next; if (second->next) { second->next->prev = first; } } } static Error heapFree(HeapId heapId, phys_ptr<void> ptr, bool clearMemory) { phys_ptr<Heap> heap; if (!ptr) { return Error::Invalid; } auto error = getHeap(heapId, true, &heap); if (error < Error::OK) { return error; } if (!heapContainsPtr(heap, ptr)) { heap->errorCountFreeBlockNotInHeap++; return Error::Invalid; } auto freeBlock = phys_cast<HeapBlock *>(phys_cast<uint8_t *>(ptr) - sizeof(HeapBlock)); if (freeBlock->state == HeapBlockState::InnerBlock) { freeBlock = freeBlock->prev; } if (freeBlock->state != HeapBlockState::Allocated) { heap->errorCountFreeUnallocatedBlock++; return Error::Invalid; } freeBlock->state = HeapBlockState::Free; if (clearMemory) { auto base = phys_cast<uint8_t *>(freeBlock) + sizeof(HeapBlock); auto size = freeBlock->size; std::memset(base.get(), 0, size); } heap->currentAllocatedSize -= freeBlock->size; auto nextBlock = heap->firstFreeBlock; auto prevBlock = nextBlock->prev; while (nextBlock) { if (phys_cast<phys_addr>(freeBlock) < phys_cast<phys_addr>(nextBlock)) { break; } prevBlock = nextBlock; nextBlock = nextBlock->next; } if (prevBlock) { prevBlock->next = freeBlock; } if (nextBlock) { nextBlock->prev = freeBlock; } freeBlock->prev = prevBlock; freeBlock->next = nextBlock; if (heap->firstFreeBlock == nextBlock) { heap->firstFreeBlock = freeBlock; } if (nextBlock) { tryMergeConsecutiveBlocks(freeBlock, nextBlock); } if (prevBlock) { tryMergeConsecutiveBlocks(prevBlock, freeBlock); } heap->totalFreeCount++; return Error::OK; } phys_ptr<void> IOS_HeapRealloc(HeapId heapId, phys_ptr<void> ptr, uint32_t size) { phys_ptr<Heap> heap; auto error = getHeap(heapId, true, &heap); if (error < Error::OK || !ptr) { return nullptr; } // Allocate a new block if ptr is null if (!ptr) { return IOS_HeapAllocAligned(heapId, size, HeapAllocAlignAlign); } if (!heapContainsPtr(heap, ptr)) { heap->errorCountExpandInvalidBlock++; return nullptr; } auto block = phys_cast<HeapBlock *>(phys_cast<uint8_t *>(ptr) - sizeof(HeapBlock)); if (block->state != HeapBlockState::Allocated && block->state != HeapBlockState::InnerBlock) { heap->errorCountExpandInvalidBlock++; return nullptr; } auto blockSize = block->size; size = align_up(size, HeapAllocSizeAlign); if (size == 0) { // Realloc to size 0 just means we should free the memory. heapFree(heapId, ptr, false); return nullptr; } else if (block->size == size) { // Realloc to same size, no changes required. return ptr; } // Just allocate a new block and copy the data across. auto newPtr = IOS_HeapAllocAligned(heapId, size, HeapAllocAlignAlign); if (newPtr) { std::memcpy(newPtr.get(), ptr.get(), std::min<uint32_t>(size, block->size)); heapFree(heapId, ptr, false); } return newPtr; } Error IOS_HeapFree(HeapId id, phys_ptr<void> ptr) { return heapFree(id, ptr, false); } Error IOS_HeapFreeAndZero(HeapId id, phys_ptr<void> ptr) { return heapFree(id, ptr, true); } namespace internal { void initialiseStaticHeapData() { sHeapData = allocProcessStatic<StaticHeapData>(); for (auto i = 0u; i < sHeapData->heaps.size(); ++i) { sHeapData->heaps[i].pid = ProcessId { -4 }; } for (auto i = 0u; i < sHeapData->localProcessHeaps.size(); ++i) { sHeapData->localProcessHeaps[i] = HeapId { -4 }; } for (auto i = 0u; i < sHeapData->crossProcessHeaps.size(); ++i) { sHeapData->crossProcessHeaps[i] = HeapId { -4 }; } } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_heap.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios/ios_enum.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::kernel { using HeapId = int32_t; constexpr auto SharedHeapId = HeapId { 1 }; constexpr auto LocalProcessHeapId = HeapId { 0xCAFE }; constexpr auto CrossProcessHeapId = HeapId { 0xCAFF }; struct Heap { be2_phys_ptr<void> base; be2_val<ProcessId> pid; be2_val<uint32_t> size; be2_phys_ptr<struct HeapBlock> firstFreeBlock; be2_val<uint32_t> errorCountAllocOutOfMemory; be2_val<uint32_t> errorCountFreeBlockNotInHeap; be2_val<uint32_t> errorCountExpandInvalidBlock; be2_val<uint32_t> errorCountCorruptedBlock; be2_val<HeapFlags> flags; be2_val<uint32_t> currentAllocatedSize; be2_val<uint32_t> largestAllocatedSize; be2_val<uint32_t> totalAllocCount; be2_val<uint32_t> totalFreeCount; be2_val<uint32_t> errorCountFreeUnallocatedBlock; be2_val<uint32_t> errorCountAllocInvalidHeap; be2_val<HeapId> id; }; CHECK_OFFSET(Heap, 0x00, base); CHECK_OFFSET(Heap, 0x04, pid); CHECK_OFFSET(Heap, 0x08, size); CHECK_OFFSET(Heap, 0x0C, firstFreeBlock); CHECK_OFFSET(Heap, 0x10, errorCountAllocOutOfMemory); CHECK_OFFSET(Heap, 0x14, errorCountFreeBlockNotInHeap); CHECK_OFFSET(Heap, 0x18, errorCountExpandInvalidBlock); CHECK_OFFSET(Heap, 0x1C, errorCountCorruptedBlock); CHECK_OFFSET(Heap, 0x20, flags); CHECK_OFFSET(Heap, 0x24, currentAllocatedSize); CHECK_OFFSET(Heap, 0x28, largestAllocatedSize); CHECK_OFFSET(Heap, 0x2C, totalAllocCount); CHECK_OFFSET(Heap, 0x30, totalFreeCount); CHECK_OFFSET(Heap, 0x34, errorCountFreeUnallocatedBlock); CHECK_OFFSET(Heap, 0x38, errorCountAllocInvalidHeap); CHECK_OFFSET(Heap, 0x3C, id); CHECK_SIZE(Heap, 0x40); struct HeapBlock { be2_val<HeapBlockState> state; be2_val<uint32_t> size; be2_phys_ptr<HeapBlock> prev; be2_phys_ptr<HeapBlock> next; }; CHECK_OFFSET(HeapBlock, 0x00, state); CHECK_OFFSET(HeapBlock, 0x04, size); CHECK_OFFSET(HeapBlock, 0x08, prev); CHECK_OFFSET(HeapBlock, 0x0C, next); CHECK_SIZE(HeapBlock, 0x10); Error IOS_CreateHeap(phys_ptr<void> ptr, uint32_t size); Error IOS_CreateLocalProcessHeap(phys_ptr<void> ptr, uint32_t size); Error IOS_CreateCrossProcessHeap(uint32_t size); Error IOS_DestroyHeap(HeapId id); phys_ptr<void> IOS_HeapAlloc(HeapId id, uint32_t size); phys_ptr<void> IOS_HeapAllocAligned(HeapId id, uint32_t size, uint32_t alignment); phys_ptr<void> IOS_HeapRealloc(HeapId id, phys_ptr<void> ptr, uint32_t size); Error IOS_HeapFree(HeapId id, phys_ptr<void> ptr); Error IOS_HeapFreeAndZero(HeapId id, phys_ptr<void> ptr); namespace internal { void initialiseStaticHeapData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_ipc.cpp ================================================ #include "ios_kernel_ipc.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_process.h" #include "ios_kernel_resourcemanager.h" #include "ios/ios_stackobject.h" namespace ios::kernel { namespace internal { static Error waitRequestReply(phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> request); } // namespace internal Error IOS_Open(std::string_view device, OpenMode mode) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosOpen(device, mode, queue, request, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } Error IOS_OpenAsync(std::string_view device, OpenMode mode, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue); if (error < Error::OK) { return error; } return internal::dispatchIosOpen(device, mode, queue, asyncNotifyRequest, internal::getCurrentProcessId(), CpuId::ARM); } Error IOS_Close(ResourceHandleId handle) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosClose(handle, queue, request, 0, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } Error IOS_CloseAsync(ResourceHandleId handle, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue); if (error < Error::OK) { return error; } return internal::dispatchIosClose(handle, queue, asyncNotifyRequest, 0, internal::getCurrentProcessId(), CpuId::ARM); } Error IOS_Read(ResourceHandleId handle, phys_ptr<void> buffer, uint32_t length) { // TODO: Implement IOS_Read return Error::Invalid; } Error IOS_ReadAsync(ResourceHandleId handle, phys_ptr<void> buffer, uint32_t length, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { // TODO: Implement IOS_Read return Error::Invalid; } Error IOS_Write(ResourceHandleId handle, phys_ptr<const void> buffer, uint32_t length) { // TODO: Implement IOS_Write return Error::Invalid; } Error IOS_WriteAsync(ResourceHandleId handle, phys_ptr<const void> buffer, uint32_t length, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { // TODO: Implement IOS_WriteAsync return Error::Invalid; } Error IOS_Seek(ResourceHandleId handle, uint32_t offset, uint32_t origin) { // TODO: Implement IOS_Seek return Error::Invalid; } Error IOS_SeekAsync(ResourceHandleId handle, uint32_t offset, uint32_t origin, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { // TODO: Implement IOS_SeekAsync return Error::Invalid; } Error IOS_Ioctl(ResourceHandleId handle, uint32_t ioctlRequest, phys_ptr<const void> inputBuffer, uint32_t inputBufferLength, phys_ptr<void> outputBuffer, uint32_t outputBufferLength) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosIoctl(handle, ioctlRequest, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, queue, request, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } Error IOS_IoctlAsync(ResourceHandleId handle, uint32_t ioctlRequest, phys_ptr<const void> inputBuffer, uint32_t inputBufferLength, phys_ptr<void> outputBuffer, uint32_t outputBufferLength, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue); if (error < Error::OK) { return error; } return internal::dispatchIosIoctl(handle, ioctlRequest, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, queue, asyncNotifyRequest, internal::getCurrentProcessId(), CpuId::ARM); } Error IOS_Ioctlv(ResourceHandleId handle, uint32_t ioctlRequest, uint32_t numVecIn, uint32_t numVecOut, phys_ptr<IoctlVec> vecs) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosIoctlv(handle, ioctlRequest, numVecIn, numVecOut, vecs, queue, request, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } Error IOS_IoctlvAsync(ResourceHandleId handle, uint32_t ioctlRequest, uint32_t numVecIn, uint32_t numVecOut, phys_ptr<IoctlVec> vecs, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue); if (error < Error::OK) { return error; } return internal::dispatchIosIoctlv(handle, ioctlRequest, numVecIn, numVecOut, vecs, queue, asyncNotifyRequest, internal::getCurrentProcessId(), CpuId::ARM); } Error IOS_Resume(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosResume(handle, unkArg0, unkArg1, queue, request, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } Error IOS_ResumeAsync(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue); if (error < Error::OK) { return error; } return internal::dispatchIosResume(handle, unkArg0, unkArg1, queue, asyncNotifyRequest, internal::getCurrentProcessId(), CpuId::ARM); } Error IOS_Suspend(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosSuspend(handle, unkArg0, unkArg1, queue, request, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } Error IOS_SuspendAsync(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue); if (error < Error::OK) { return error; } return internal::dispatchIosSuspend(handle, unkArg0, unkArg1, queue, asyncNotifyRequest, internal::getCurrentProcessId(), CpuId::ARM); } Error IOS_SvcMsg(ResourceHandleId handle, uint32_t command, uint32_t unkArg1, uint32_t unkArg2, uint32_t unkArg3) { StackObject<IpcRequest> request; std::memset(request.get(), 0, sizeof(IpcRequest)); auto queue = internal::getCurrentThreadMessageQueue(); auto error = internal::dispatchIosSvcMsg(handle, command, unkArg1, unkArg2, unkArg3, queue, request, internal::getCurrentProcessId(), CpuId::ARM); if (error < Error::OK) { return error; } return internal::waitRequestReply(queue, request); } namespace internal { static Error waitRequestReply(phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> request) { StackObject<Message> message; auto error = internal::receiveMessage(queue, message, MessageFlags::None); if (error < Error::OK) { return error; } auto receivedRequest = kernel::parseMessage<IpcRequest>(message); if (receivedRequest != request) { return Error::FailInternal; } return static_cast<Error>(request->reply); } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_ipc.h ================================================ #pragma once #include "ios_kernel_messagequeue.h" #include "ios_kernel_resourcemanager.h" #include "ios/ios_ipc.h" namespace ios::kernel { Error IOS_Open(std::string_view device, OpenMode mode); Error IOS_OpenAsync(std::string_view device, OpenMode mode, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Close(ResourceHandleId handle); Error IOS_CloseAsync(ResourceHandleId handle, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Read(ResourceHandleId handle, phys_ptr<void> buffer, uint32_t length); Error IOS_ReadAsync(ResourceHandleId handle, phys_ptr<void> buffer, uint32_t length, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Write(ResourceHandleId handle, phys_ptr<const void> buffer, uint32_t length); Error IOS_WriteAsync(ResourceHandleId handle, phys_ptr<const void> buffer, uint32_t length, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Seek(ResourceHandleId handle, uint32_t offset, uint32_t origin); Error IOS_SeekAsync(ResourceHandleId handle, uint32_t offset, uint32_t origin, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Ioctl(ResourceHandleId handle, uint32_t ioctlRequest, phys_ptr<const void> inputBuffer, uint32_t inputBufferLength, phys_ptr<void> outputBuffer, uint32_t outputBufferLength); Error IOS_IoctlAsync(ResourceHandleId handle, uint32_t ioctlRequest, phys_ptr<const void> inputBuffer, uint32_t inputBufferLength, phys_ptr<void> outputBuffer, uint32_t outputBufferLength, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Ioctlv(ResourceHandleId handle, uint32_t ioctlRequest, uint32_t numVecIn, uint32_t numVecOut, phys_ptr<IoctlVec> vecs); Error IOS_IoctlvAsync(ResourceHandleId handle, uint32_t request, uint32_t numVecIn, uint32_t numVecOut, phys_ptr<IoctlVec> vecs, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Resume(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1); Error IOS_ResumeAsync(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_Suspend(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1); Error IOS_SuspendAsync(ResourceHandleId handle, uint32_t unkArg0, uint32_t unkArg1, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); Error IOS_SvcMsg(ResourceHandleId handle, uint32_t command, uint32_t unkArg1, uint32_t unkArg2, uint32_t unkArg3); Error IOS_SvcMsgAsync(ResourceHandleId handle, uint32_t command, uint32_t unkArg1, uint32_t unkArg2, uint32_t unkArg3, MessageQueueId asyncNotifyQueue, phys_ptr<IpcRequest> asyncNotifyRequest); } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_ipc_thread.cpp ================================================ #include "ios_kernel_hardware.h" #include "ios_kernel_ipc_thread.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_process.h" #include "ios_kernel_resourcemanager.h" #include "ios/ios_stackobject.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include <common/atomicqueue.h> #include <common/log.h> #include <libcpu/cpu_formatters.h> #include <mutex> #include <queue> namespace ios::kernel { constexpr auto IpcThreadNumMessages = 0x100u; constexpr auto IpcThreadStackSize = 0x800u; constexpr auto IpcThreadPriority = 95u; struct StaticIpcData { be2_val<MessageQueueId> messageQueueId; be2_array<Message, IpcThreadNumMessages> messageBuffer; be2_val<ThreadId> threadId; be2_array<uint8_t, IpcThreadStackSize> threadStack; }; static phys_ptr<StaticIpcData> sData = nullptr; static AtomicQueue<phys_ptr<IpcRequest>, 128> sIpcRequestQueue; void submitIpcRequest(phys_ptr<IpcRequest> request) { decaf_check(!sIpcRequestQueue.wasFull()); sIpcRequestQueue.push(request); switch (request->cpuId) { case CpuId::PPC0: internal::setInterruptAhbLt(AHBLT::get(0).IpcStarbuckCore0(true)); break; case CpuId::PPC1: internal::setInterruptAhbLt(AHBLT::get(0).IpcStarbuckCore1(true)); break; case CpuId::PPC2: internal::setInterruptAhbLt(AHBLT::get(0).IpcStarbuckCore2(true)); break; } } namespace internal { static Error ipcThreadEntry(phys_ptr<void> context) { StackObject<Message> message; phys_ptr<MessageQueue> queue; auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { return error; } // Register interrupt handlers and enable the interrupts. auto queueId = static_cast<MessageQueueId>(error); error = internal::getMessageQueue(queueId, &queue); sData->messageQueueId = queueId; IOS_HandleEvent(DeviceId::IpcStarbuckCore0, queueId, Message { Command::IpcMsg0 }); IOS_ClearAndEnable(DeviceId::IpcStarbuckCore0); IOS_HandleEvent(DeviceId::IpcStarbuckCore1, queueId, Message { Command::IpcMsg1 }); IOS_ClearAndEnable(DeviceId::IpcStarbuckCore1); IOS_HandleEvent(DeviceId::IpcStarbuckCore2, queueId, Message { Command::IpcMsg2 }); IOS_ClearAndEnable(DeviceId::IpcStarbuckCore2); IOS_HandleEvent(DeviceId::IpcStarbuckCompat, queueId, Message { Command::IpcMsg1 }); IOS_ClearAndEnable(DeviceId::IpcStarbuckCompat); while (true) { error = IOS_ReceiveMessage(queueId, message, MessageFlags::None); if (error < Error::OK) { return error; } /* * We're kind of cheating here, we have combined all 3 IPC queues into a * single queue in order to make it easier to reduce chances of race * conditions. Also we attempt to fully empty the ipc request queue * rather than processing a single message per interrupt like on hardware. */ while (!sIpcRequestQueue.wasEmpty()) { auto request = sIpcRequestQueue.pop(); if (request->clientPid > 7) { gLog->error("Received IPC request with invalid clientPid of {}", request->clientPid); error = Error::Invalid; } else { auto pid = static_cast<ProcessId>(request->clientPid + ProcessId::COSKERNEL); switch (request->command) { case Command::Open: error = internal::dispatchIosOpen(request->args.open.name.get(), request->args.open.mode, queue, request, pid, request->cpuId); break; case Command::Close: error = internal::dispatchIosClose(request->handle, queue, request, request->args.close.unkArg0, pid, request->cpuId); break; case Command::Read: error = internal::dispatchIosRead(request->handle, request->args.read.data, request->args.read.length, queue, request, pid, request->cpuId); break; case Command::Write: error = internal::dispatchIosWrite(request->handle, request->args.write.data, request->args.write.length, queue, request, pid, request->cpuId); break; case Command::Seek: error = internal::dispatchIosSeek(request->handle, request->args.seek.offset, request->args.seek.origin, queue, request, pid, request->cpuId); break; case Command::Ioctl: error = internal::dispatchIosIoctl(request->handle, request->args.ioctl.request, request->args.ioctl.inputBuffer, request->args.ioctl.inputLength, request->args.ioctl.outputBuffer, request->args.ioctl.outputLength, queue, request, pid, request->cpuId); break; case Command::Ioctlv: error = internal::dispatchIosIoctlv(request->handle, request->args.ioctlv.request, request->args.ioctlv.numVecIn, request->args.ioctlv.numVecOut, request->args.ioctlv.vecs, queue, request, pid, request->cpuId); break; default: error = Error::Invalid; } } if (error < Error::OK) { // Reply with error! request->command = Command::Reply; request->reply = error; cafe::kernel::ipckDriverIosSubmitReply(request); continue; } } IOS_ClearAndEnable(DeviceId::IpcStarbuckCore0); IOS_ClearAndEnable(DeviceId::IpcStarbuckCore1); IOS_ClearAndEnable(DeviceId::IpcStarbuckCore2); } } MessageQueueId getIpcMessageQueueId() { return sData->messageQueueId; } Error startIpcThread() { sData = allocProcessStatic<StaticIpcData>(); // Create thread auto error = IOS_CreateThread(&ipcThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), static_cast<uint32_t>(sData->threadStack.size()), IpcThreadPriority, kernel::ThreadFlags::Detached); if (error < Error::OK) { kernel::IOS_DestroyMessageQueue(sData->messageQueueId); return error; } sData->threadId = static_cast<kernel::ThreadId>(error); internal::setThreadName(sData->threadId, "IpcThread"); return kernel::IOS_StartThread(sData->threadId); } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_ipc_thread.h ================================================ #pragma once #include "ios_kernel_messagequeue.h" #include "ios/ios_ipc.h" namespace ios::kernel { void submitIpcRequest(phys_ptr<IpcRequest> request); namespace internal { MessageQueueId getIpcMessageQueueId(); Error startIpcThread(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_messagequeue.cpp ================================================ #include "ios_kernel_hardware.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_process.h" #include "ios_kernel_scheduler.h" #include "ios_kernel_thread.h" #include <common/log.h> namespace ios::kernel { struct StaticMessageQueueData { be2_array<MessageQueue, MaxNumMessageQueues> queues; be2_val<uint32_t> numCreatedQueues; be2_array<MessageQueue, MaxNumThreads> perThreadQueues; be2_array<Message, MaxNumThreads> perThreadMesssages; }; static phys_ptr<StaticMessageQueueData> sData = nullptr; /** * Create a message queue. */ Error IOS_CreateMessageQueue(phys_ptr<Message> messages, uint32_t size) { phys_ptr<MessageQueue> queue = nullptr; for (auto i = 0u; i < sData->queues.size(); ++i) { if (sData->queues[i].size == 0) { queue = phys_addrof(sData->queues[i]); queue->uid = static_cast<int32_t>((sData->numCreatedQueues << 12) | i); break; } } if (!queue) { return Error::Max; } queue->first = 0u; queue->used = 0u; queue->size = size; queue->messages = messages; queue->flags = MessageQueueFlags::None; queue->pid = static_cast<uint8_t>(internal::getCurrentProcessId()); ThreadQueue_Initialise(phys_addrof(queue->receiveQueue)); ThreadQueue_Initialise(phys_addrof(queue->sendQueue)); sData->numCreatedQueues++; return static_cast<Error>(queue->uid); } /** * Destroy message queue. * * Interrupts any threads waiting on the receive or send queue. */ Error IOS_DestroyMessageQueue(MessageQueueId id) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(id, &queue); if (error < Error::OK) { return error; } if (queue->flags & MessageQueueFlags::RegisteredEventHandler) { internal::unregisterEventHandlerQueue(queue->uid); queue->flags &= ~MessageQueueFlags::RegisteredEventHandler; } internal::wakeupAllThreads(phys_addrof(queue->sendQueue), Error::Intr); internal::wakeupAllThreads(phys_addrof(queue->receiveQueue), Error::Intr); std::memset(queue.get(), 0, sizeof(ThreadQueue)); return Error::OK; } /** * Insert a message to the back of the message queue. */ Error IOS_SendMessage(MessageQueueId id, Message message, MessageFlags flags) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(id, &queue); if (error < Error::OK) { return error; } return internal::sendMessage(queue, message, flags); } /** * Insert a message to front of the message queue. */ Error IOS_JamMessage(MessageQueueId id, Message message, MessageFlags flags) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(id, &queue); if (error < Error::OK) { return error; } while (queue->used == queue->size) { if (flags & MessageFlags::NonBlocking) { return Error::Max; } internal::sleepThread(phys_addrof(queue->sendQueue)); internal::reschedule(); auto thread = internal::getCurrentThread(); if (thread->context.queueWaitResult != Error::OK) { return thread->context.queueWaitResult; } } if (queue->first == 0) { queue->first = queue->size - 1; } else { queue->first--; } queue->messages[queue->first] = message; queue->used++; internal::wakeupOneThread(phys_addrof(queue->receiveQueue), Error::OK); internal::reschedule(); return Error::OK; } /** * Receive a message from the front of the message queue. */ Error IOS_ReceiveMessage(MessageQueueId id, phys_ptr<Message> message, MessageFlags flags) { phys_ptr<MessageQueue> queue; auto error = internal::getMessageQueueSafe(id, &queue); if (error < Error::OK) { return error; } return internal::receiveMessage(queue, message, flags); } namespace internal { /** * Find a message queue from it's ID. */ Error getMessageQueue(MessageQueueId id, phys_ptr<MessageQueue> *outQueue) { auto idx = static_cast<size_t>(id & 0xFFF); if (idx >= sData->queues.size()) { return Error::Invalid; } auto queue = phys_addrof(sData->queues[idx]); if (outQueue) { *outQueue = queue; } return Error::OK; } /** * Find a message queue from it's ID. * * Verifies that the queue belongs to the caller process. */ Error getMessageQueueSafe(MessageQueueId id, phys_ptr<MessageQueue> *outQueue) { auto idx = static_cast<size_t>(id & 0xFFF); if (idx >= sData->queues.size()) { return Error::Invalid; } auto queue = phys_addrof(sData->queues[idx]); if (queue->pid != internal::getCurrentProcessId()) { // Can only access queues belonging to same process. return Error::Access; } if (outQueue) { *outQueue = queue; } return Error::OK; } /** * Get the message queue for this thread. * * Used for blocking requests. */ phys_ptr<MessageQueue> getCurrentThreadMessageQueue() { return phys_addrof(sData->perThreadQueues[internal::getCurrentThreadId()]); } /** * Insert message to the back of the message queue. */ Error sendMessage(phys_ptr<MessageQueue> queue, Message message, MessageFlags flags) { while (queue->used == queue->size) { if (flags & MessageFlags::NonBlocking) { return Error::Max; } internal::sleepThread(phys_addrof(queue->sendQueue)); internal::reschedule(); auto thread = internal::getCurrentThread(); if (thread->context.queueWaitResult != Error::OK) { return thread->context.queueWaitResult; } } auto index = (queue->first + queue->used) % queue->size; queue->messages[index] = message; queue->used++; internal::wakeupOneThread(phys_addrof(queue->receiveQueue), Error::OK); internal::reschedule(); return Error::OK; } /** * Receive a message from the front of the message queue. */ Error receiveMessage(phys_ptr<MessageQueue> queue, phys_ptr<Message> message, MessageFlags flags) { while (queue->used == 0) { if (flags & MessageFlags::NonBlocking) { return Error::Max; } internal::sleepThread(phys_addrof(queue->receiveQueue)); internal::reschedule(); auto thread = internal::getCurrentThread(); if (thread->context.queueWaitResult != Error::OK) { return thread->context.queueWaitResult; } } *message = queue->messages[queue->first]; queue->first = (queue->first + 1) % queue->size; queue->used--; internal::wakeupOneThread(phys_addrof(queue->sendQueue), Error::OK); internal::reschedule(); return Error::OK; } void initialiseStaticMessageQueueData() { sData = allocProcessStatic<StaticMessageQueueData>(); for (auto i = 0u; i < sData->perThreadQueues.size(); ++i) { auto &queue = sData->perThreadQueues[i]; queue.used = 0u; queue.first = 0u; queue.size = 1u; queue.messages = phys_addrof(sData->perThreadMesssages[i]); queue.uid = -4; queue.pid = uint8_t { 0 }; queue.flags = MessageQueueFlags::None; queue.unk0x1E = uint16_t { 0 }; ThreadQueue_Initialise(phys_addrof(queue.receiveQueue)); ThreadQueue_Initialise(phys_addrof(queue.sendQueue)); } } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_messagequeue.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios_kernel_threadqueue.h" #include "ios/ios_enum.h" #include <common/cbool.h> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::kernel { static constexpr auto MaxNumMessageQueues = 750u; struct Thread; using MessageQueueId = int32_t; using Message = uint32_t; struct MessageQueue { be2_struct<ThreadQueue> receiveQueue; be2_struct<ThreadQueue> sendQueue; be2_val<uint32_t> used; be2_val<uint32_t> first; be2_val<uint32_t> size; be2_phys_ptr<Message> messages; be2_val<int32_t> uid; be2_val<uint8_t> pid; be2_val<MessageQueueFlags> flags; be2_val<uint16_t> unk0x1E; }; CHECK_OFFSET(MessageQueue, 0x00, receiveQueue); CHECK_OFFSET(MessageQueue, 0x04, sendQueue); CHECK_OFFSET(MessageQueue, 0x08, used); CHECK_OFFSET(MessageQueue, 0x0C, first); CHECK_OFFSET(MessageQueue, 0x10, size); CHECK_OFFSET(MessageQueue, 0x14, messages); CHECK_OFFSET(MessageQueue, 0x18, uid); CHECK_OFFSET(MessageQueue, 0x1C, pid); CHECK_OFFSET(MessageQueue, 0x1D, flags); CHECK_OFFSET(MessageQueue, 0x1E, unk0x1E); CHECK_SIZE(MessageQueue, 0x20); Error IOS_CreateMessageQueue(phys_ptr<Message> messageBuffer, uint32_t numMessages); Error IOS_DestroyMessageQueue(MessageQueueId id); Error IOS_SendMessage(MessageQueueId id, Message message, MessageFlags flags); Error IOS_JamMessage(MessageQueueId id, Message message, MessageFlags flags); Error IOS_ReceiveMessage(MessageQueueId id, phys_ptr<Message> message, MessageFlags flags); template<typename Type> Message makeMessage(phys_ptr<Type> ptr) { return static_cast<Message>(phys_cast<phys_addr>(ptr).getAddress()); } template<typename Type> Message makeMessage(be2_phys_ptr<Type> ptr) { return static_cast<Message>(phys_cast<phys_addr>(ptr).getAddress()); } template<typename Type> phys_ptr<Type> parseMessage(phys_ptr<Message> message) { return phys_cast<Type *>(phys_addr { static_cast<Message>(*message) }); } namespace internal { Error getMessageQueue(MessageQueueId id, phys_ptr<MessageQueue> *outQueue); Error getMessageQueueSafe(MessageQueueId id, phys_ptr<MessageQueue> *outQueue); phys_ptr<MessageQueue> getCurrentThreadMessageQueue(); Error sendMessage(phys_ptr<MessageQueue> queue, Message message, MessageFlags flags); Error receiveMessage(phys_ptr<MessageQueue> queue, phys_ptr<Message> message, MessageFlags flags); void initialiseStaticMessageQueueData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_otp.cpp ================================================ #include "ios_kernel_enum.h" #include "ios_kernel_thread.h" #include "ios_kernel_otp.h" #include "decaf_config.h" #include <array> #include <cstddef> #include <common/log.h> #include <fstream> namespace ios::kernel { static bool sOtpLoaded = false; static std::array<std::byte, 0x400> sOtpData; Error IOS_ReadOTP(OtpFieldIndex fieldIndex, phys_ptr<void> buffer, uint32_t bufferSize) { auto thread = internal::getCurrentThread(); if (thread->pid != ProcessId::CRYPTO) { return Error::Access; } if (!sOtpLoaded) { return Error::NoExists; } auto offset = fieldIndex * 4; if (offset + bufferSize > sOtpData.size()) { return Error::Invalid; } std::memcpy(buffer.get(), sOtpData.data() + offset, bufferSize); return Error::OK; } namespace internal { Error initialiseOtp() { auto config = decaf::config(); if (!config->system.otp_path.empty()) { std::ifstream fh { config->system.otp_path, std::fstream::binary }; if (fh.is_open()) { fh.read(reinterpret_cast<char *>(sOtpData.data()), sOtpData.size()); if (fh) { sOtpLoaded = true; } } } if (!sOtpLoaded) { gLog->warn("Failed to load otp.bin from {}", config->system.otp_path); return Error::NoExists; } return Error::OK; } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_otp.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios/ios_error.h" #include <cstdint> #include <libcpu/be2_struct.h> namespace ios::kernel { Error IOS_ReadOTP(OtpFieldIndex fieldIndex, phys_ptr<void> buffer, uint32_t bufferSize); namespace internal { Error initialiseOtp(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_process.cpp ================================================ #include "ios_kernel_process.h" #include "ios_kernel_thread.h" #include <array> #include <cstring> #include <common/frameallocator.h> #include <common/strutils.h> namespace ios::kernel { static std::array<FrameAllocator, NumIosProcess> sProcessStaticAllocators; Error IOS_GetCurrentProcessId() { auto thread = internal::getCurrentThread(); if (!thread) { return Error::Invalid; } return static_cast<Error>(thread->pid); } Error IOS_GetProcessName(ProcessId process, char *buffer) { const char *name = nullptr; switch (process) { case ProcessId::KERNEL: name = "IOS-KERNEL"; break; case ProcessId::MCP: name = "IOS-MCP"; break; case ProcessId::BSP: name = "IOS-BSP"; break; case ProcessId::CRYPTO: name = "IOS-CRYPTO"; break; case ProcessId::USB: name = "IOS-USB"; break; case ProcessId::FS: name = "IOS-FS"; break; case ProcessId::PAD: name = "IOS-PAD"; break; case ProcessId::NET: name = "IOS-NET"; break; case ProcessId::ACP: name = "IOS-ACP"; break; case ProcessId::NSEC: name = "IOS-NSEC"; break; case ProcessId::AUXIL: name = "IOS-AUXIL"; break; case ProcessId::NIM: name = "IOS-NIM"; break; case ProcessId::FPD: name = "IOS-FPD"; break; case ProcessId::TEST: name = "IOS-TEST"; break; case ProcessId::COSKERNEL: name = "COS-KERNEL"; break; case ProcessId::COSROOT: name = "COS-ROOT"; break; case ProcessId::COS02: name = "COS-02"; break; case ProcessId::COS03: name = "COS-03"; break; case ProcessId::COSOVERLAY: name = "COS-OVERLAY"; break; case ProcessId::COSHBM: name = "COS-HBM"; break; case ProcessId::COSERROR: name = "COS-ERROR"; break; case ProcessId::COSMASTER: name = "COS-MASTER"; break; default: return Error::Invalid; } strcpy(buffer, name); return Error::OK; } phys_ptr<char> allocProcessStatic(std::string_view str) { auto buffer = phys_cast<char *>( allocProcessStatic(internal::getCurrentProcessId(), str.size() + 1, 4)); std::copy(str.begin(), str.end(), buffer.get()); buffer[str.size()] = char { 0 }; return buffer; } phys_ptr<void> allocProcessStatic(ProcessId pid, size_t size, size_t align) { decaf_check(pid < sProcessStaticAllocators.size()); auto &allocator = sProcessStaticAllocators[pid]; auto buffer = allocator.allocate(size, align); std::memset(buffer, 0, size); return phys_cast<void *>(cpu::translatePhysical(buffer)); } phys_ptr<void> allocProcessLocalHeap(size_t size) { auto pid = internal::getCurrentProcessId(); auto &allocator = sProcessStaticAllocators[pid]; auto buffer = allocator.allocate(size, 0x20); std::memset(buffer, 0, size); return phys_cast<void *>(cpu::translatePhysical(buffer)); } namespace internal { void initialiseProcessStaticAllocators() { struct PhysicalMemoryLayout { ProcessId id; phys_addr addr; uint32_t size; }; static constexpr PhysicalMemoryLayout ProcessMemoryLayout[] = { { ProcessId::KERNEL, phys_addr { 0x08120000 }, 0xA0000 }, { ProcessId::MCP, phys_addr { 0x081C0000 }, 0xC0000 }, { ProcessId::BSP, phys_addr { 0x13CC0000 }, 0xC0000 }, { ProcessId::CRYPTO, phys_addr { 0x08280000 }, 0x40000 }, { ProcessId::USB, phys_addr { 0x10100000 }, 0x600000 }, { ProcessId::FS, phys_addr { 0x10700000 }, 0x1800000 }, { ProcessId::PAD, phys_addr { 0x11F00000 }, 0x400000 }, { ProcessId::NET, phys_addr { 0x12300000 }, 0x600000 }, { ProcessId::ACP, phys_addr { 0x12900000 }, 0x2C0000 }, { ProcessId::NSEC, phys_addr { 0x12BC0000 }, 0x300000 }, { ProcessId::AUXIL, phys_addr { 0x13C00000 }, 0xC0000 }, { ProcessId::NIM, phys_addr { 0x12EC0000 }, 0x780000 }, { ProcessId::FPD, phys_addr { 0x13640000 }, 0x400000 }, { ProcessId::TEST, phys_addr { 0x13A40000 }, 0x1C0000 }, }; for (auto &layout : ProcessMemoryLayout) { sProcessStaticAllocators[layout.id] = FrameAllocator { phys_cast<uint8_t *>(layout.addr).get(), layout.size }; } } ProcessId getCurrentProcessId() { auto thread = getCurrentThread(); if (!thread) { return ProcessId::KERNEL; } return thread->pid; } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_process.h ================================================ #pragma once #include "ios/ios_enum.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <string_view> #include <utility> namespace ios::kernel { constexpr auto NumIosProcess = 14; Error IOS_GetCurrentProcessId(); Error IOS_GetProcessName(ProcessId process, char *buffer); phys_ptr<void> allocProcessStatic(ProcessId pid, size_t size, size_t align); phys_ptr<void> allocProcessLocalHeap(size_t size); phys_ptr<char> allocProcessStatic(std::string_view str); namespace internal { ProcessId getCurrentProcessId(); void initialiseProcessStaticAllocators(); } // namespace internal template<typename Type> inline phys_ptr<Type> allocProcessStatic() { auto data = allocProcessStatic(internal::getCurrentProcessId(), sizeof(Type), alignof(Type)); new (data.get()) Type { }; return phys_cast<Type *>(data); } } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_resourcemanager.cpp ================================================ #include "ios_kernel_ipc_thread.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_resourcemanager.h" #include "ios_kernel_process.h" #include "ios/ios_enum_string.h" #include "ios/ios_stackobject.h" #include "cafe/kernel/cafe_kernel_ipckdriver.h" #include <common/log.h> #include <common/strutils.h> #include <libcpu/cpu_formatters.h> #include <map> #include <string> #include <string_view> namespace ios::kernel { struct StaticResourceManagerData { be2_val<BOOL> registrationEnabled = TRUE; be2_struct<ResourceManagerList> resourceManagerList; be2_struct<ResourceRequestList> resourceRequestList; be2_array<ResourceHandleManager, ProcessId::Max> resourceHandleManagers; be2_val<uint32_t> totalOpenedHandles = 0u; }; static phys_ptr<StaticResourceManagerData> sData; namespace internal { static Error findResourceManager(std::string_view device, phys_ptr<ResourceManager> *outResourceManager); static phys_ptr<ResourceHandleManager> getResourceHandleManager(ProcessId id); static Error allocResourceRequest(phys_ptr<ResourceHandleManager> resourceHandleManager, CpuId cpuId, phys_ptr<ResourceManager> resourceManager, phys_ptr<MessageQueue> messageQueue, phys_ptr<IpcRequest> ipcRequest, phys_ptr<ResourceRequest> *outResourceRequest); static Error freeResourceRequest(phys_ptr<ResourceRequest> resourceRequest); static Error allocResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager, phys_ptr<ResourceManager> resourceManager, ResourceHandleId *outResourceHandleId); static Error freeResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager, ResourceHandleId id); static Error getResourceHandle(ResourceHandleId id, phys_ptr<ResourceHandleManager> resourceHandleManager, phys_ptr<ResourceHandle> *outResourceHandle); static Error dispatchResourceReply(phys_ptr<ResourceRequest> resourceRequest, Error reply, bool freeRequest); static Error dispatchRequest(phys_ptr<ResourceRequest> request); } // namespace internal /** * Enable or disable any further resource manager registrations. * * \returns Error:Access * Registration can only be disabled or enabled from the MCP process. * * \returns Error::Busy * For some reason this function returns Error::Busy on success. */ Error IOS_SetResourceManagerRegistrationDisabled(bool disable) { auto pid = internal::getCurrentProcessId(); if (pid != ProcessId::MCP) { return Error::Access; } sData->registrationEnabled = disable ? FALSE : TRUE; return Error::Busy; } /** * Register a message queue to receive messages for a device. */ Error IOS_RegisterResourceManager(std::string_view device, MessageQueueId queue) { auto pid = internal::getCurrentProcessId(); auto resourceHandleManager = internal::getResourceHandleManager(pid); if (!resourceHandleManager) { return Error::Invalid; } auto error = internal::findResourceManager(device, nullptr); if (error >= Error::OK) { return Error::Exists; } if (!sData->registrationEnabled) { return Error::NotReady; } auto &resourceManagerList = sData->resourceManagerList; auto resourceManagerIdx = resourceManagerList.firstFreeIdx; if (resourceHandleManager->numResourceManagers >= resourceHandleManager->maxResourceManagers || resourceManagerIdx < 0) { return Error::FailAlloc; } auto &resourceManager = resourceManagerList.resourceManagers[resourceManagerIdx]; auto nextFreeIdx = resourceManager.nextResourceManagerIdx; if (nextFreeIdx < 0) { resourceManagerList.firstFreeIdx = int16_t { -1 }; resourceManagerList.lastFreeIdx = int16_t { -1 }; } else { auto &nextFreeResourceManager = resourceManagerList.resourceManagers[nextFreeIdx]; nextFreeResourceManager.prevResourceManagerIdx = int16_t { -1 }; resourceManagerList.firstFreeIdx = nextFreeIdx; } resourceManager.queueId = queue; string_copy(phys_addrof(resourceManager.device).get(), device.data(), resourceManager.device.size()); resourceManager.deviceLen = static_cast<uint16_t>(device.size()); resourceManager.numRequests = uint16_t { 0u }; resourceManager.firstRequestIdx = int16_t { -1 }; resourceManager.lastRequestIdx = int16_t { -1 }; resourceManager.prevResourceManagerIdx = int16_t { -1 }; resourceManager.nextResourceManagerIdx = int16_t { -1 }; resourceManager.numHandles = uint16_t { 0u }; resourceManager.unk0x3C = uint16_t { 8u }; resourceManagerList.numRegistered++; if (resourceManagerList.numRegistered > resourceManagerList.mostRegistered) { resourceManagerList.mostRegistered = resourceManagerList.numRegistered; } resourceHandleManager->numResourceManagers++; resourceManager.resourceHandleManager = resourceHandleManager; if (resourceManagerList.firstRegisteredIdx < 0) { resourceManagerList.firstRegisteredIdx = resourceManagerIdx; resourceManagerList.lastRegisteredIdx = resourceManagerIdx; } else { auto insertBeforeIndex = resourceManagerList.firstRegisteredIdx; while (insertBeforeIndex >= 0) { auto &other = resourceManagerList.resourceManagers[insertBeforeIndex]; if (device.compare(phys_addrof(other.device).get()) < 0) { break; } insertBeforeIndex = other.nextResourceManagerIdx; } if (insertBeforeIndex == resourceManagerList.firstRegisteredIdx) { // Insert at front auto &insertBefore = resourceManagerList.resourceManagers[insertBeforeIndex]; insertBefore.prevResourceManagerIdx = resourceManagerIdx; resourceManager.nextResourceManagerIdx = insertBeforeIndex; resourceManagerList.firstRegisteredIdx = resourceManagerIdx; } else if (insertBeforeIndex < 0) { // Insert at back auto insertAfterIndex = resourceManagerList.lastRegisteredIdx; auto &insertAfter = resourceManagerList.resourceManagers[insertAfterIndex]; insertAfter.nextResourceManagerIdx = resourceManagerIdx; resourceManager.prevResourceManagerIdx = insertAfterIndex; resourceManagerList.lastRegisteredIdx = resourceManagerIdx; } else { // Insert in middle auto &insertBefore = resourceManagerList.resourceManagers[insertBeforeIndex]; auto insertAfterIndex = insertBefore.prevResourceManagerIdx; auto &insertAfter = resourceManagerList.resourceManagers[insertAfterIndex]; insertAfter.nextResourceManagerIdx = resourceManagerIdx; insertBefore.prevResourceManagerIdx = resourceManagerIdx; resourceManager.nextResourceManagerIdx = insertBeforeIndex; resourceManager.prevResourceManagerIdx = insertAfterIndex; } } return Error::OK; } /** * Associate a resource manager with a permission group (matching cos.xml) */ Error IOS_AssociateResourceManager(std::string_view device, ResourcePermissionGroup group) { auto resourceManager = phys_ptr<ResourceManager> { nullptr }; auto error = internal::findResourceManager(device, &resourceManager); if (error < Error::OK) { return error; } auto pid = internal::getCurrentProcessId(); if (pid != resourceManager->resourceHandleManager->processId) { return Error::Access; } resourceManager->permissionGroup = group; return Error::OK; } /** * Send a reply to a resource request. */ Error IOS_ResourceReply(phys_ptr<ResourceRequest> resourceRequest, Error reply) { // Get the resource handle manager for the current process auto pid = internal::getCurrentProcessId(); auto resourceHandleManager = internal::getResourceHandleManager(pid); if (!resourceHandleManager) { resourceHandleManager->failedResourceReplies++; return Error::Invalid; } // Calculate the request index auto &resourceRequestList = sData->resourceRequestList; auto resourceRequestIndex = resourceRequest - phys_addrof(resourceRequestList.resourceRequests[0]); if (resourceRequestIndex < 0 || resourceRequestIndex >= resourceRequestList.resourceRequests.size()) { resourceHandleManager->failedResourceReplies++; return Error::Invalid; } if (resourceRequest != phys_addrof(resourceRequestList.resourceRequests[resourceRequestIndex]) || resourceHandleManager != resourceRequest->resourceManager->resourceHandleManager) { resourceHandleManager->failedResourceReplies++; return Error::Invalid; } auto resourceHandle = phys_ptr<ResourceHandle> { nullptr }; auto requestHandleManager = resourceRequest->resourceHandleManager; auto error = internal::getResourceHandle(resourceRequest->resourceHandleId, requestHandleManager, &resourceHandle); if (error < Error::OK) { gLog->warn("IOS_ResourceReply({}, {}) passed invalid resource request.", phys_cast<phys_addr>(resourceRequest), reply); resourceHandleManager->failedResourceReplies++; } else if (resourceRequest->requestData.command == Command::Open) { if (reply < Error::OK) { // Resource open failed, free the resource handle. internal::freeResourceHandle(requestHandleManager, resourceRequest->resourceHandleId); } else { // Resource open succeeded, save the resource handle. resourceHandle->handle = static_cast<int32_t>(reply); resourceHandle->state = ResourceHandleState::Open; reply = static_cast<Error>(resourceHandle->id); } } else if (resourceRequest->requestData.command == Command::Close) { // Resource closed, close the resource handle. internal::freeResourceHandle(requestHandleManager, resourceRequest->resourceHandleId); } return internal::dispatchResourceReply(resourceRequest, reply, true); } /** * Set the client capability mask for a specific process & feature ID. * * Can only be set by the MCP process. */ Error IOS_SetClientCapabilities(ProcessId pid, FeatureId featureId, phys_ptr<uint64_t> mask) { if (internal::getCurrentProcessId() != ProcessId::MCP) { return Error::Access; } return internal::setClientCapability(pid, featureId, *mask); } Error IOS_SetProcessTitle(ProcessId process, TitleId title, GroupId group) { if (internal::getCurrentProcessId() != ProcessId::MCP) { return Error::Access; } auto resourceHandleManager = internal::getResourceHandleManager(process); if (!resourceHandleManager) { return Error::InvalidArg; } resourceHandleManager->titleId = title; resourceHandleManager->groupId = group; return Error::OK; } namespace internal { /** * Find a registered ResourceManager for the given device name. */ static Error findResourceManager(std::string_view device, phys_ptr<ResourceManager> *outResourceManager) { auto index = sData->resourceManagerList.firstRegisteredIdx; while (index >= 0) { auto &resourceManager = sData->resourceManagerList.resourceManagers[index]; auto resourceManagerDevice = std::string_view { phys_addrof(resourceManager.device).get(), resourceManager.deviceLen }; if (device.compare(resourceManagerDevice) == 0) { if (outResourceManager) { *outResourceManager = phys_addrof(resourceManager); } return Error::OK; } index = resourceManager.nextResourceManagerIdx; } return Error::NoExists; } /** * Get the ResourceHandleManager for the specified process. */ static phys_ptr<ResourceHandleManager> getResourceHandleManager(ProcessId id) { if (id >= ProcessId::Max) { return nullptr; } return phys_addrof(sData->resourceHandleManagers[id]); } /** * Allocate a ResourceRequest. */ static Error allocResourceRequest(phys_ptr<ResourceHandleManager> resourceHandleManager, CpuId cpuId, phys_ptr<ResourceManager> resourceManager, phys_ptr<MessageQueue> messageQueue, phys_ptr<IpcRequest> ipcRequest, phys_ptr<ResourceRequest> *outResourceRequest) { if (resourceHandleManager->numResourceRequests >= resourceHandleManager->maxResourceRequests) { return Error::ClientTxnLimit; } // Find a free resource request to allocate. auto &resourceRequestList = sData->resourceRequestList; if (resourceRequestList.firstFreeIdx < 0) { return Error::FailAlloc; } auto resourceRequestIdx = resourceRequestList.firstFreeIdx; auto resourceRequest = phys_addrof(resourceRequestList.resourceRequests[resourceRequestIdx]); auto nextFreeIdx = resourceRequest->nextIdx; if (nextFreeIdx < 0) { resourceRequestList.firstFreeIdx = int16_t { -1 }; resourceRequestList.lastFreeIdx = int16_t { -1 }; } else { auto &nextFreeResourceRequest = resourceRequestList.resourceRequests[nextFreeIdx]; nextFreeResourceRequest.prevIdx = int16_t { -1 }; resourceRequestList.firstFreeIdx = nextFreeIdx; } resourceRequest->resourceHandleId = static_cast<ResourceHandleId>(Error::Invalid); resourceRequest->messageQueueId = messageQueue->uid; resourceRequest->messageQueue = messageQueue; resourceRequest->resourceHandleManager = resourceHandleManager; resourceRequest->resourceManager = resourceManager; resourceRequest->ipcRequest = ipcRequest; resourceRequest->requestData.cpuId = cpuId; resourceRequest->requestData.processId = resourceHandleManager->processId; resourceRequest->requestData.titleId = resourceHandleManager->titleId; resourceRequest->requestData.groupId = resourceHandleManager->groupId; // Insert into the allocated request list. resourceRequest->nextIdx = int16_t { -1 }; resourceRequest->prevIdx = resourceManager->lastRequestIdx; if (resourceManager->lastRequestIdx < 0) { resourceManager->firstRequestIdx = resourceRequestIdx; resourceManager->lastRequestIdx = resourceRequestIdx; } else { auto &lastRequest = resourceRequestList.resourceRequests[resourceManager->lastRequestIdx]; lastRequest.nextIdx = resourceRequestIdx; resourceManager->lastRequestIdx = resourceRequestIdx; } // Increment our counters! resourceManager->numRequests++; resourceRequestList.numRegistered++; if (resourceRequestList.numRegistered > resourceRequestList.mostRegistered) { resourceRequestList.mostRegistered = resourceRequestList.numRegistered; } resourceHandleManager->numResourceRequests++; if (resourceHandleManager->numResourceRequests > resourceHandleManager->mostResourceRequests) { resourceHandleManager->mostResourceRequests = resourceHandleManager->numResourceRequests; } *outResourceRequest = resourceRequest; return Error::OK; } /** * Free a ResourceRequest. */ static Error freeResourceRequest(phys_ptr<ResourceRequest> resourceRequest) { auto &resourceRequestList = sData->resourceRequestList; auto resourceManager = resourceRequest->resourceManager; if (!resourceManager) { return Error::Invalid; } // Remove from the request list auto resourceRequestIndex = static_cast<int16_t>(resourceRequest - phys_addrof(resourceRequestList.resourceRequests[0])); auto lastFreeIdx = resourceRequestList.lastFreeIdx; auto nextIdx = resourceRequest->nextIdx; auto prevIdx = resourceRequest->prevIdx; if (nextIdx >= 0) { auto &nextResourceRequest = resourceRequestList.resourceRequests[nextIdx]; nextResourceRequest.prevIdx = prevIdx; } if (prevIdx >= 0) { auto &prevResourceRequest = resourceRequestList.resourceRequests[prevIdx]; prevResourceRequest.nextIdx = nextIdx; } if (resourceManager->firstRequestIdx == resourceRequestIndex) { resourceManager->firstRequestIdx = nextIdx; } if (resourceManager->lastRequestIdx == resourceRequestIndex) { resourceManager->lastRequestIdx = prevIdx; } // Decrement our counters! auto resourceHandleManager = resourceRequest->resourceHandleManager; resourceHandleManager->numResourceRequests--; resourceRequestList.numRegistered--; resourceManager->numRequests--; // Reset the resource request std::memset(resourceRequest.get(), 0, sizeof(ResourceRequest)); resourceRequest->prevIdx = lastFreeIdx; resourceRequest->nextIdx = int16_t { -1 }; // Reinsert into free list resourceRequestList.lastFreeIdx = resourceRequestIndex; if (lastFreeIdx < 0) { resourceRequestList.firstFreeIdx = resourceRequestIndex; } else { auto &lastFreeResourceRequest = resourceRequestList.resourceRequests[lastFreeIdx]; lastFreeResourceRequest.nextIdx = resourceRequestIndex; } return Error::OK; } /** * Allocate a ResourceHandle. */ static Error allocResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager, phys_ptr<ResourceManager> resourceManager, ResourceHandleId *outResourceHandleId) { // Check if we have a free resource handle to register. if (resourceHandleManager->numResourceHandles >= resourceHandleManager->maxResourceHandles) { return Error::Max; } // Find a free resource handle phys_ptr<ResourceHandle> resourceHandle = nullptr; auto resourceHandleIdx = 0u; for (auto i = 0u; i < resourceHandleManager->handles.size(); ++i) { if (resourceHandleManager->handles[i].state == ResourceHandleState::Free) { resourceHandle = phys_addrof(resourceHandleManager->handles[i]); resourceHandleIdx = i; break; } } // Double check we have one... should never happen really. if (!resourceHandle) { return Error::Max; } resourceHandle->id = static_cast<ResourceHandleId>(resourceHandleIdx | ((sData->totalOpenedHandles << 12) & 0x7FFFFFFF)); resourceHandle->resourceManager = resourceManager; resourceHandle->state = ResourceHandleState::Opening; resourceHandle->handle = -10; *outResourceHandleId = resourceHandle->id; return Error::OK; } /** * Free a ResourceHandle. */ static Error freeResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager, ResourceHandleId id) { phys_ptr<ResourceHandle> resourceHandle; auto error = getResourceHandle(id, resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } auto resourceManager = resourceHandle->resourceManager; resourceHandle->handle = -4; resourceHandle->resourceManager = nullptr; resourceHandle->state = ResourceHandleState::Free; resourceHandle->id = static_cast<ResourceHandleId>(Error::Invalid); resourceHandleManager->numResourceHandles--; resourceManager->numHandles--; return Error::OK; } /** * Get ResourceHandle by id. */ static Error getResourceHandle(ResourceHandleId id, phys_ptr<ResourceHandleManager> resourceHandleManager, phys_ptr<ResourceHandle> *outResourceHandle) { auto index = id & 0xFFF; if (id < 0 || index >= static_cast<ResourceHandleId>(resourceHandleManager->handles.size())) { return Error::Invalid; } if (resourceHandleManager->handles[index].id != id) { return Error::NoExists; } *outResourceHandle = phys_addrof(resourceHandleManager->handles[index]); return Error::OK; } /** * Lookup an open resource handle. */ static Error getOpenResource(ProcessId pid, ResourceHandleId id, phys_ptr<ResourceHandleManager> *outResourceHandleManager, phys_ptr<ResourceHandle> *outResourceHandle) { phys_ptr<ResourceHandle> resourceHandle; // Try get the resource handle manager for this process. auto resourceHandleManager = getResourceHandleManager(pid); if (!resourceHandleManager) { return Error::Invalid; } auto error = getResourceHandle(id, resourceHandleManager, &resourceHandle); if (error) { return error; } if (resourceHandle->state == ResourceHandleState::Closed) { return Error::StaleHandle; } else if (resourceHandle->state != ResourceHandleState::Open) { return Error::Invalid; } *outResourceHandle = resourceHandle; *outResourceHandleManager = resourceHandleManager; return Error::OK; } /** * Dispatch a reply to a ResourceRequest. */ static Error dispatchResourceReply(phys_ptr<ResourceRequest> resourceRequest, Error reply, bool freeRequest) { auto error = Error::Invalid; phys_ptr<IpcRequest> ipcRequest = resourceRequest->ipcRequest; ipcRequest->command = Command::Reply; ipcRequest->reply = reply; if (resourceRequest->messageQueueId == getIpcMessageQueueId()) { cafe::kernel::ipckDriverIosSubmitReply(ipcRequest); error = Error::OK; } else { phys_ptr<MessageQueue> queue = nullptr; if (resourceRequest->messageQueueId < 0) { queue = resourceRequest->messageQueue; } else { error = getMessageQueue(resourceRequest->messageQueueId, &queue); } if (queue) { error = sendMessage(queue, makeMessage(ipcRequest), MessageFlags::NonBlocking); } } if (freeRequest) { freeResourceRequest(resourceRequest); } return error; } /** * Dispatch a request to it's message queue. */ static Error dispatchRequest(phys_ptr<ResourceRequest> request) { phys_ptr<MessageQueue> queue; auto error = getMessageQueue(request->resourceManager->queueId, &queue); if (error < Error::OK) { return error; } return sendMessage(queue, makeMessage(request), MessageFlags::NonBlocking); } /** * Dispatch an IOS_Open request. */ Error dispatchIosOpen(std::string_view device, OpenMode mode, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ClientCapability> clientCapability; phys_ptr<ResourceManager> resourceManager; phys_ptr<ResourceRequest> resourceRequest; ResourceHandleId resourceHandleId; // Try get the resource handle manager for this process. auto resourceHandleManager = internal::getResourceHandleManager(pid); if (!resourceHandleManager) { return Error::Invalid; } // Try find the resource manager for this device name. auto error = internal::findResourceManager(device, &resourceManager); if (error < Error::OK) { return error; } // Try get the cap bits error = getClientCapability(resourceHandleManager, resourceManager->permissionGroup, &clientCapability); if (error < Error::OK) { return Error::Access; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Open; // Set the IOS_Open arguments resourceRequest->requestData.args.open.name = phys_addrof(resourceRequest->openNameBuffer); resourceRequest->requestData.args.open.mode = mode; resourceRequest->requestData.args.open.caps = clientCapability->mask; string_copy(phys_addrof(resourceRequest->openNameBuffer).get(), device.data(), resourceRequest->openNameBuffer.size()); // Try allocate a resource handle. error = allocResourceHandle(resourceHandleManager, resourceManager, &resourceHandleId); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } resourceRequest->resourceHandleId = resourceHandleId; // Increment our counters! sData->totalOpenedHandles++; resourceManager->numHandles++; resourceHandleManager->numResourceHandles++; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceHandle(resourceHandleManager, resourceHandleId); freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Close request. */ Error dispatchIosClose(ResourceHandleId resourceHandleId, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, uint32_t unkArg0, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceRequest> resourceRequest; // Try get the resource handle manager for this process. auto resourceHandleManager = getResourceHandleManager(pid); if (!resourceHandleManager) { return Error::Invalid; } // Try lookup the resource handle. auto error = getResourceHandle(resourceHandleId, resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } // If the handle no longer has a resource manager associated with it then // we need to hack in a resource request. auto resourceManager = resourceHandle->resourceManager; if (!resourceManager) { ios::StackObject<ResourceRequest> directResourceRequest; std::memset(directResourceRequest.get(), 0, sizeof(ResourceRequest)); directResourceRequest->requestData.command = Command::Close; directResourceRequest->requestData.cpuId = cpuId; directResourceRequest->requestData.processId = pid; directResourceRequest->requestData.handle = resourceHandleId; directResourceRequest->ipcRequest = ipcRequest; directResourceRequest->messageQueue = queue; directResourceRequest->messageQueueId = queue->uid; freeResourceHandle(resourceHandleManager, resourceHandleId); return dispatchRequest(directResourceRequest); } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Close; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.close.unkArg0 = unkArg0; auto previousResourceHandleState = resourceHandle->state; resourceHandle->state = ResourceHandleState::Closed; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { resourceHandle->state = previousResourceHandleState; freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Read request. */ Error dispatchIosRead(ResourceHandleId resourceHandleId, phys_ptr<void> buffer, uint32_t length, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } if (length && !buffer) { return Error::Access; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Read; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.read.data = buffer; resourceRequest->requestData.args.read.length = length; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Write request. */ Error dispatchIosWrite(ResourceHandleId resourceHandleId, phys_ptr<const void> buffer, uint32_t length, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } if (length && !buffer) { return Error::Access; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Write; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.write.data = buffer; resourceRequest->requestData.args.write.length = length; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Seek request. */ Error dispatchIosSeek(ResourceHandleId resourceHandleId, uint32_t offset, SeekOrigin origin, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Seek; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.seek.offset = offset; resourceRequest->requestData.args.seek.origin = origin; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Ioctl request. */ Error dispatchIosIoctl(ResourceHandleId resourceHandleId, uint32_t ioctlRequest, phys_ptr<const void> inputBuffer, uint32_t inputLength, phys_ptr<void> outputBuffer, uint32_t outputLength, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } if ((inputLength && !inputBuffer) || (outputLength && !outputBuffer)) { return Error::Access; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Ioctl; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.ioctl.request = ioctlRequest; resourceRequest->requestData.args.ioctl.inputBuffer = inputBuffer; resourceRequest->requestData.args.ioctl.inputLength = inputLength; resourceRequest->requestData.args.ioctl.outputBuffer = outputBuffer; resourceRequest->requestData.args.ioctl.outputLength = outputLength; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Ioctlv request. */ Error dispatchIosIoctlv(ResourceHandleId resourceHandleId, uint32_t ioctlRequest, uint32_t numVecIn, uint32_t numVecOut, phys_ptr<IoctlVec> vecs, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } if (numVecIn + numVecOut > 0 && !vecs) { return Error::Access; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Ioctlv; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.ioctlv.request = ioctlRequest; resourceRequest->requestData.args.ioctlv.numVecIn = numVecIn; resourceRequest->requestData.args.ioctlv.numVecOut = numVecOut; resourceRequest->requestData.args.ioctlv.vecs = vecs; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Resume request. */ Error dispatchIosResume(ResourceHandleId resourceHandleId, uint32_t unkArg0, uint32_t unkArg1, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Resume; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.resume.unkArg0 = unkArg0; resourceRequest->requestData.args.resume.unkArg1 = unkArg1; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_Suspend request. */ Error dispatchIosSuspend(ResourceHandleId resourceHandleId, uint32_t unkArg0, uint32_t unkArg1, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::Suspend; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.suspend.unkArg0 = unkArg0; resourceRequest->requestData.args.suspend.unkArg1 = unkArg1; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Dispatch an IOS_SvcMsg request. */ Error dispatchIosSvcMsg(ResourceHandleId resourceHandleId, uint32_t command, uint32_t unkArg1, uint32_t unkArg2, uint32_t unkArg3, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId) { phys_ptr<ResourceHandle> resourceHandle; phys_ptr<ResourceHandleManager> resourceHandleManager; phys_ptr<ResourceRequest> resourceRequest; auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle); if (error < Error::OK) { return error; } // Try allocate a resource request. error = allocResourceRequest(resourceHandleManager, cpuId, resourceHandle->resourceManager, queue, ipcRequest, &resourceRequest); if (error < Error::OK) { return error; } resourceRequest->requestData.command = Command::SvcMsg; resourceRequest->resourceHandleId = resourceHandleId; resourceRequest->requestData.handle = resourceHandle->handle; resourceRequest->requestData.args.svcMsg.command = command; resourceRequest->requestData.args.svcMsg.unkArg1 = unkArg1; resourceRequest->requestData.args.svcMsg.unkArg2 = unkArg2; resourceRequest->requestData.args.svcMsg.unkArg3 = unkArg3; // Try dispatch the request to the relevant resource manager. error = dispatchRequest(resourceRequest); if (error < Error::OK) { freeResourceRequest(resourceRequest); return error; } return Error::OK; } /** * Find the ClientCapability structure for a specific feature ID. */ Error getClientCapability(phys_ptr<ResourceHandleManager> resourceHandleManager, FeatureId featureId, phys_ptr<ClientCapability> *outClientCapability) { for (auto i = 0u; i < resourceHandleManager->clientCapabilities.size(); ++i) { auto caps = phys_addrof(resourceHandleManager->clientCapabilities[i]); if (caps->featureId == ResourcePermissionGroup::All || caps->featureId == featureId) { if (outClientCapability) { *outClientCapability = caps; } return Error::OK; } } return Error::NoExists; } /** * Set the client capability mask for a specific process & feature ID. */ Error setClientCapability(ProcessId pid, FeatureId featureId, uint64_t mask) { auto clientCapability = phys_ptr<ClientCapability> { nullptr }; auto resourceHandleManager = getResourceHandleManager(pid); if (!resourceHandleManager) { return Error::InvalidArg; } auto error = getClientCapability(resourceHandleManager, featureId, &clientCapability); if (error >= Error::OK) { if (mask == 0) { // Delete client cap clientCapability->featureId = -1; clientCapability->mask = 0ull; return Error::OK; } // Update client cap clientCapability->mask = mask; return Error::OK; } if (mask == 0) { return Error::OK; } // Add new client cap clientCapability = nullptr; for (auto i = 0u; i < resourceHandleManager->clientCapabilities.size(); ++i) { auto cap = phys_addrof(resourceHandleManager->clientCapabilities[i]); if (cap->featureId == -1) { clientCapability = cap; break; } } if (!clientCapability) { return Error::FailAlloc; } clientCapability->featureId = featureId; clientCapability->mask = mask; return Error::OK; } void initialiseStaticResourceManagerData() { sData = allocProcessStatic<StaticResourceManagerData>(); // Initialise resourceManagerList auto &resourceManagerList = sData->resourceManagerList; resourceManagerList.firstRegisteredIdx = int16_t { -1 }; resourceManagerList.lastRegisteredIdx = int16_t { -1 }; resourceManagerList.firstFreeIdx = int16_t { 0 }; resourceManagerList.lastFreeIdx = static_cast<int16_t>(MaxNumResourceManagers - 1); for (auto i = 0; i < MaxNumResourceManagers; ++i) { auto &resourceManager = resourceManagerList.resourceManagers[i]; resourceManager.prevResourceManagerIdx = static_cast<int16_t>(i - 1); resourceManager.nextResourceManagerIdx = static_cast<int16_t>(i + 1); } resourceManagerList.resourceManagers[resourceManagerList.firstFreeIdx].prevResourceManagerIdx = int16_t { -1 }; resourceManagerList.resourceManagers[resourceManagerList.lastFreeIdx].nextResourceManagerIdx = int16_t { -1 }; // Initialise resourceRequestList auto &resourceRequestList = sData->resourceRequestList; resourceRequestList.firstFreeIdx = int16_t { 0 }; resourceRequestList.lastFreeIdx = static_cast<int16_t>(MaxNumResourceRequests - 1); for (auto i = 0; i < MaxNumResourceRequests; ++i) { auto &resourceRequest = resourceRequestList.resourceRequests[i]; resourceRequest.prevIdx = static_cast<int16_t>(i - 1); resourceRequest.nextIdx = static_cast<int16_t>(i + 1); } resourceRequestList.resourceRequests[resourceRequestList.firstFreeIdx].prevIdx = int16_t { -1 }; resourceRequestList.resourceRequests[resourceRequestList.lastFreeIdx].nextIdx = int16_t { -1 }; // Initialise resourceHandleManagers auto &resourceHandleManagers = sData->resourceHandleManagers; for (auto i = 0u; i < resourceHandleManagers.size(); ++i) { auto &resourceHandleManager = resourceHandleManagers[i]; resourceHandleManager.processId = static_cast<ProcessId>(i); resourceHandleManager.maxResourceHandles = MaxNumResourceHandlesPerProcess; resourceHandleManager.maxResourceRequests = MaxNumResourceRequestsPerProcess; if (resourceHandleManager.processId >= ProcessId::COSKERNEL) { resourceHandleManager.maxResourceManagers = 0u; } else { resourceHandleManager.maxResourceManagers = MaxNumResourceManagersPerProcess; } for (auto j = 0u; j < MaxNumResourceHandlesPerProcess; ++j) { auto &handle = resourceHandleManager.handles[j]; handle.handle = -4; handle.id = -4; handle.resourceManager = nullptr; handle.state = ResourceHandleState::Free; } for (auto j = 0u; j < MaxNumClientCapabilitiesPerProcess; ++j) { auto &caps = resourceHandleManager.clientCapabilities[j]; caps.featureId = -1; caps.mask = 0ull; } setClientCapability(resourceHandleManager.processId, 0, 0xFFFFFFFFFFFFFFFFull); } } } // namespace internal } // namespace ios ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_resourcemanager.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios_kernel_messagequeue.h" #include "ios/ios_enum.h" #include "ios/ios_ipc.h" #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::kernel { static constexpr auto MaxNumResourceManagers = 96u; static constexpr auto MaxNumResourceRequests = 256u; static constexpr auto MaxNumResourceHandlesPerProcess = 96u; static constexpr auto MaxNumResourceManagersPerProcess = 32u; static constexpr auto MaxNumResourceRequestsPerProcess = 32u; static constexpr auto MaxNumClientCapabilitiesPerProcess = 20u; using ResourceHandleId = int32_t; using FeatureId = int32_t; using ClientCapabilityMask = uint64_t; struct ResourceManager; struct ResourceManagerList; struct ResourceHandle; struct ResourceHandleManager; struct ResourceRequest; struct ResourceRequestList; #pragma pack(push, 1) struct ResourceManager { //! Name of the device this resource manager manages. be2_array<char, 32> device; //! ID of the message queue associated with this resource. be2_val<MessageQueueId> queueId; //! Pointer to the resource handle manager for this resource manager. be2_phys_ptr<ResourceHandleManager> resourceHandleManager; //! Permission Group this resource belongs to, this matches values in cos.xml. be2_val<ResourcePermissionGroup> permissionGroup; //! Length of the string in this->device be2_val<uint16_t> deviceLen; //! Index of the first resource request for this resource in the global //! resource request list. be2_val<int16_t> firstRequestIdx; //! Index of the last resource request for this resource in the global //! resource request list. be2_val<int16_t> lastRequestIdx; //! Number of resource requests active for this resource. be2_val<uint16_t> numRequests; //! Number of resource handles active for this resource. be2_val<uint16_t> numHandles; //! Index of the next active resource manager. be2_val<int16_t> nextResourceManagerIdx; //! Index of the previous active resource manager. be2_val<int16_t> prevResourceManagerIdx; be2_val<uint16_t> unk0x3A; be2_val<uint16_t> unk0x3C; be2_val<uint16_t> unk0x3E; }; CHECK_OFFSET(ResourceManager, 0x00, device); CHECK_OFFSET(ResourceManager, 0x20, queueId); CHECK_OFFSET(ResourceManager, 0x24, resourceHandleManager); CHECK_OFFSET(ResourceManager, 0x28, permissionGroup); CHECK_OFFSET(ResourceManager, 0x2C, deviceLen); CHECK_OFFSET(ResourceManager, 0x2E, firstRequestIdx); CHECK_OFFSET(ResourceManager, 0x30, lastRequestIdx); CHECK_OFFSET(ResourceManager, 0x32, numRequests); CHECK_OFFSET(ResourceManager, 0x34, numHandles); CHECK_OFFSET(ResourceManager, 0x36, nextResourceManagerIdx); CHECK_OFFSET(ResourceManager, 0x38, prevResourceManagerIdx); CHECK_OFFSET(ResourceManager, 0x3A, unk0x3A); CHECK_OFFSET(ResourceManager, 0x3C, unk0x3C); CHECK_OFFSET(ResourceManager, 0x3E, unk0x3E); CHECK_SIZE(ResourceManager, 0x40); struct ResourceManagerList { //! Number of registered resource managers. be2_val<uint16_t> numRegistered; //! The highest number of registered resource managers at one time. be2_val<uint16_t> mostRegistered; //! Index of the first registered resource manager. be2_val<int16_t> firstRegisteredIdx; //! Index of the last registered resource manager. be2_val<int16_t> lastRegisteredIdx; //! Index of the first free resource manager. be2_val<int16_t> firstFreeIdx; //! Index of the last free resource manager. be2_val<int16_t> lastFreeIdx; //! List of resource managers. be2_array<ResourceManager, MaxNumResourceManagers> resourceManagers; }; CHECK_OFFSET(ResourceManagerList, 0x00, numRegistered); CHECK_OFFSET(ResourceManagerList, 0x02, mostRegistered); CHECK_OFFSET(ResourceManagerList, 0x04, firstRegisteredIdx); CHECK_OFFSET(ResourceManagerList, 0x06, lastRegisteredIdx); CHECK_OFFSET(ResourceManagerList, 0x08, firstFreeIdx); CHECK_OFFSET(ResourceManagerList, 0x0A, lastFreeIdx); CHECK_OFFSET(ResourceManagerList, 0x0C, resourceManagers); CHECK_SIZE(ResourceManagerList, 0x180C); struct ResourceHandle { //! The internal process handle returned by a successful IOS_Open request. be2_val<int32_t> handle; //! The unique id of this resource handle. be2_val<ResourceHandleId> id; //! The resource manager this handle belongs to. be2_phys_ptr<ResourceManager> resourceManager; //! The state of this resource handle. be2_val<ResourceHandleState> state; }; CHECK_OFFSET(ResourceHandle, 0x00, handle); CHECK_OFFSET(ResourceHandle, 0x04, id); CHECK_OFFSET(ResourceHandle, 0x08, resourceManager); CHECK_OFFSET(ResourceHandle, 0x0C, state); CHECK_SIZE(ResourceHandle, 0x10); struct ClientCapability { be2_val<FeatureId> featureId; be2_val<ClientCapabilityMask> mask; }; CHECK_OFFSET(ClientCapability, 0x00, featureId); CHECK_OFFSET(ClientCapability, 0x04, mask); CHECK_SIZE(ClientCapability, 0x0C); /** * A per process structure to manage resource handles. */ struct ResourceHandleManager { //! Title ID this resource handle manager belongs to. be2_val<TitleId> titleId; //! Group ID this resource handle manager belongs to. be2_val<GroupId> groupId; //! Process this resource handle manager belongs to. be2_val<ProcessId> processId; //! Number of current resource handles. be2_val<uint32_t> numResourceHandles; //! Highest number of resource handles at one time. be2_val<uint32_t> mostResourceHandles; //! Maximum number of resource handles, aka size of handles array. be2_val<uint32_t> maxResourceHandles; //! List of resource handles. be2_array<ResourceHandle, MaxNumResourceHandlesPerProcess> handles; //! Number of resource requests. be2_val<uint32_t> numResourceRequests; //! Highest number of resource requests at once. be2_val<uint32_t> mostResourceRequests; //! Number of times registerIpcRequest failed due to max number of resource requests. be2_val<uint32_t> failedRegisterMaxResourceRequests; //! Maxmimum allowed number of resource requests per process. be2_val<uint32_t> maxResourceRequests; //! Client Capabilities be2_array<ClientCapability, MaxNumClientCapabilitiesPerProcess> clientCapabilities; //! Number of resource managers for this process. be2_val<uint32_t> numResourceManagers; //! Maximum allowed number of resource managers for this process. be2_val<uint32_t> maxResourceManagers; //! Number of times IOS_ResourceReply failed be2_val<uint32_t> failedResourceReplies; }; CHECK_OFFSET(ResourceHandleManager, 0x00, titleId); CHECK_OFFSET(ResourceHandleManager, 0x08, groupId); CHECK_OFFSET(ResourceHandleManager, 0x0C, processId); CHECK_OFFSET(ResourceHandleManager, 0x10, numResourceHandles); CHECK_OFFSET(ResourceHandleManager, 0x14, mostResourceHandles); CHECK_OFFSET(ResourceHandleManager, 0x18, maxResourceHandles); CHECK_OFFSET(ResourceHandleManager, 0x1C, handles); CHECK_OFFSET(ResourceHandleManager, 0x61C, numResourceRequests); CHECK_OFFSET(ResourceHandleManager, 0x620, mostResourceRequests); CHECK_OFFSET(ResourceHandleManager, 0x624, failedRegisterMaxResourceRequests); CHECK_OFFSET(ResourceHandleManager, 0x628, maxResourceRequests); CHECK_OFFSET(ResourceHandleManager, 0x62C, clientCapabilities); CHECK_OFFSET(ResourceHandleManager, 0x71C, numResourceManagers); CHECK_OFFSET(ResourceHandleManager, 0x720, maxResourceManagers); CHECK_OFFSET(ResourceHandleManager, 0x724, failedResourceReplies); CHECK_SIZE(ResourceHandleManager, 0x728); struct ResourceRequest { //! Data store for the actual request. be2_struct<IpcRequest> requestData; //! Message queue this resource request came from. be2_phys_ptr<MessageQueue> messageQueue; //! Message queue id, why store this and message queue, who knows..? be2_val<MessageQueueId> messageQueueId; //! Pointer to the IpcRequest that this resource request originated from. be2_phys_ptr<IpcRequest> ipcRequest; //! Pointer to the resource handle manager for this request. be2_phys_ptr<ResourceHandleManager> resourceHandleManager; //! Pointer to the resource manager for this request. be2_phys_ptr<ResourceManager> resourceManager; //! ID of the resource handle associated with this request. be2_val<ResourceHandleId> resourceHandleId; //! Index of the next resource request, used for either free or registered //! list in ResourceRequestList. be2_val<int16_t> nextIdx; //! Index of the previous resource request, used for either free or registered //! list in ResourceRequestList. be2_val<int16_t> prevIdx; //! Buffer to copy the device name to for IOS_Open calls. be2_array<char, 32> openNameBuffer; UNKNOWN(0xB4 - 0x74); }; CHECK_OFFSET(ResourceRequest, 0x00, requestData); CHECK_OFFSET(ResourceRequest, 0x38, messageQueue); CHECK_OFFSET(ResourceRequest, 0x3C, messageQueueId); CHECK_OFFSET(ResourceRequest, 0x40, ipcRequest); CHECK_OFFSET(ResourceRequest, 0x44, resourceHandleManager); CHECK_OFFSET(ResourceRequest, 0x48, resourceManager); CHECK_OFFSET(ResourceRequest, 0x4C, resourceHandleId); CHECK_OFFSET(ResourceRequest, 0x50, nextIdx); CHECK_OFFSET(ResourceRequest, 0x52, prevIdx); CHECK_OFFSET(ResourceRequest, 0x54, openNameBuffer); CHECK_SIZE(ResourceRequest, 0xB4); /** * Storage for all resource requests. */ struct ResourceRequestList { //! Number of registered resource requests. be2_val<uint16_t> numRegistered; //! Highest number of registered resource requests. be2_val<uint16_t> mostRegistered; be2_val<uint16_t> unk0x04; //! Index of first free element in resourceRequests. be2_val<int16_t> firstFreeIdx; //! Index of last free element in resourceRequests. be2_val<int16_t> lastFreeIdx; be2_val<uint16_t> unk0x0A; //! Resource Request storage. be2_array<ResourceRequest, MaxNumResourceRequests> resourceRequests; }; CHECK_OFFSET(ResourceRequestList, 0x00, numRegistered); CHECK_OFFSET(ResourceRequestList, 0x02, mostRegistered); CHECK_OFFSET(ResourceRequestList, 0x04, unk0x04); CHECK_OFFSET(ResourceRequestList, 0x06, firstFreeIdx); CHECK_OFFSET(ResourceRequestList, 0x08, lastFreeIdx); CHECK_OFFSET(ResourceRequestList, 0x0A, unk0x0A); CHECK_OFFSET(ResourceRequestList, 0x0C, resourceRequests); CHECK_SIZE(ResourceRequestList, 0xB40C); #pragma pack(pop) Error IOS_SetResourceManagerRegistrationDisabled(bool enable); Error IOS_RegisterResourceManager(std::string_view device, MessageQueueId queue); Error IOS_AssociateResourceManager(std::string_view device, ResourcePermissionGroup group); Error IOS_ResourceReply(phys_ptr<ResourceRequest> request, Error reply); Error IOS_SetClientCapabilities(ProcessId pid, FeatureId featureId, phys_ptr<uint64_t> mask); Error IOS_SetProcessTitle(ProcessId process, TitleId title, GroupId group); namespace internal { Error dispatchIosOpen(std::string_view name, OpenMode mode, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosClose(ResourceHandleId resourceHandleId, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, uint32_t unkArg0, ProcessId pid, CpuId cpuId); Error dispatchIosRead(ResourceHandleId resourceHandleId, phys_ptr<void> buffer, uint32_t length, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosWrite(ResourceHandleId resourceHandleId, phys_ptr<const void> buffer, uint32_t length, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosSeek(ResourceHandleId resourceHandleId, uint32_t offset, SeekOrigin origin, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosIoctl(ResourceHandleId resourceHandleId, uint32_t ioctlRequest, phys_ptr<const void> inputBuffer, uint32_t inputLength, phys_ptr<void> outputBuffer, uint32_t outputLength, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosIoctlv(ResourceHandleId resourceHandleId, uint32_t ioctlRequest, uint32_t numVecIn, uint32_t numVecOut, phys_ptr<IoctlVec> vecs, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosResume(ResourceHandleId resourceHandleId, uint32_t unkArg0, uint32_t unkArg1, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosSuspend(ResourceHandleId resourceHandleId, uint32_t unkArg0, uint32_t unkArg1, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error dispatchIosSvcMsg(ResourceHandleId resourceHandleId, uint32_t command, uint32_t unkArg1, uint32_t unkArg2, uint32_t unkArg3, phys_ptr<MessageQueue> queue, phys_ptr<IpcRequest> ipcRequest, ProcessId pid, CpuId cpuId); Error getClientCapability(phys_ptr<ResourceHandleManager> resourceHandleManager, FeatureId featureId, phys_ptr<ClientCapability> *outClientCapability); Error setClientCapability(ProcessId pid, FeatureId featureId, uint64_t mask); void initialiseStaticResourceManagerData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_scheduler.cpp ================================================ #include "ios_kernel_hardware.h" #include "ios_kernel_scheduler.h" #include "ios_kernel_thread.h" #include "ios_kernel_threadqueue.h" #include "ios_kernel_process.h" #include <fmt/format.h> #include <common/log.h> #include <common/platform_fiber.h> #include <libcpu/cpu_formatters.h> #include <iterator> namespace ios::kernel::internal { struct StaticSchedulerData { be2_struct<ThreadQueue> runQueue; }; static phys_ptr<StaticSchedulerData> sData = nullptr; static phys_ptr<Thread> sCurrentThreadContext = nullptr; static platform::Fiber * sIdleFiber = nullptr; phys_ptr<Thread> getCurrentThread() { return sCurrentThreadContext; } ThreadId getCurrentThreadId() { return sCurrentThreadContext->id; } void sleepThread(phys_ptr<ThreadQueue> queue) { auto thread = sCurrentThreadContext; thread->state = ThreadState::Waiting; ThreadQueue_PushThread(queue, thread); } void wakeupOneThread(phys_ptr<ThreadQueue> queue, Error waitResult) { if (auto thread = ThreadQueue_PopThread(queue)) { thread->state = ThreadState::Ready; thread->context.queueWaitResult = waitResult; queueThread(thread); } } void wakeupAllThreads(phys_ptr<ThreadQueue> queue, Error waitResult) { while (queue->first) { wakeupOneThread(queue, waitResult); } } void queueThread(phys_ptr<Thread> thread) { ThreadQueue_PushThread(phys_addrof(sData->runQueue), thread); } bool isThreadInRunQueue(phys_ptr<Thread> thread) { return thread->threadQueue == phys_addrof(sData->runQueue); } void reschedule(bool yielding) { auto currentThread = sCurrentThreadContext; auto nextThread = ThreadQueue_PeekThread(phys_addrof(sData->runQueue)); if (currentThread && currentThread->state == ThreadState::Running) { if (!nextThread) { // No other threads to run, we're stuck with this one! return; } if (currentThread->priority > nextThread->priority) { // Next thread has lower priority, keep running current. return; } if (!yielding && currentThread->priority == nextThread->priority) { // Next thread has same priority, but we are not yielding. return; } currentThread->state = ThreadState::Ready; decaf_check(ThreadQueue_PopThread(phys_addrof(sData->runQueue)) == nextThread); queueThread(currentThread); } else { decaf_check(ThreadQueue_PopThread(phys_addrof(sData->runQueue)) == nextThread); } // Trace log the thread switch if (gLog->should_log(Logger::Level::trace)) { fmt::memory_buffer out; fmt::format_to(std::back_inserter(out), "IOS leaving"); if (currentThread) { fmt::format_to(std::back_inserter(out), " thread {}", currentThread->id); if (currentThread->context.threadName) { fmt::format_to(std::back_inserter(out), " [{}]", currentThread->context.threadName); } } else { fmt::format_to(std::back_inserter(out), " idle"); } fmt::format_to(std::back_inserter(out), " to"); if (nextThread) { fmt::format_to(std::back_inserter(out), " thread {}", nextThread->id); if (nextThread->context.threadName) { fmt::format_to(std::back_inserter(out), " [{}]", nextThread->context.threadName); } } else { fmt::format_to(std::back_inserter(out), " idle"); } gLog->trace("{}", std::string_view { out.data(), out.size() }); } sCurrentThreadContext = nextThread; if (nextThread) { nextThread->state = ThreadState::Running; } auto fiberSrc = currentThread ? currentThread->context.fiber : sIdleFiber; auto fiberDst = nextThread ? nextThread->context.fiber : sIdleFiber; if (fiberSrc != fiberDst) { platform::swapToFiber(fiberSrc, fiberDst); } } void setIdleFiber() { sIdleFiber = platform::getThreadFiber(); } void initialiseStaticSchedulerData() { sData = allocProcessStatic<StaticSchedulerData>(); } } // namespace ios::kernel::internal ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_scheduler.h ================================================ #pragma once #include "ios/ios_enum.h" #include <libcpu/be2_struct.h> namespace ios::kernel { struct Thread; struct ThreadQueue; namespace internal { void sleepThread(phys_ptr<ThreadQueue> queue); void wakeupOneThread(phys_ptr<ThreadQueue> queue, Error waitResult); void wakeupAllThreads(phys_ptr<ThreadQueue> queue, Error waitResult); void queueThread(phys_ptr<Thread> thread); bool isThreadInRunQueue(phys_ptr<Thread> thread); void reschedule(bool yielding = false); void setIdleFiber(); void initialiseStaticSchedulerData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_semaphore.cpp ================================================ #include "ios_kernel_process.h" #include "ios_kernel_semaphore.h" #include "ios_kernel_scheduler.h" #include "ios_kernel_thread.h" #include <array> #include <mutex> namespace ios::kernel { struct StaticSemaphoreData { be2_val<uint32_t> numCreatedSemaphores = uint32_t { 0 }; be2_val<int16_t> firstFreeSemaphoreIndex = int16_t { 0 }; be2_val<int16_t> lastFreeSemaphoreIndex = int16_t { 0 }; be2_array<Semaphore, 750> semaphores; }; static phys_ptr<StaticSemaphoreData> sData; static phys_ptr<Semaphore> getSemaphore(SemaphoreId id) { auto idx = static_cast<size_t>(id & 0xFFF); if (idx >= sData->semaphores.size()) { return nullptr; } auto semaphore = phys_addrof(sData->semaphores[idx]); if (semaphore->pid != internal::getCurrentProcessId()) { // Can only access semaphores belonging to same process. return nullptr; } return semaphore; } Error IOS_CreateSemaphore(int32_t maxCount, int32_t initialCount) { if (sData->firstFreeSemaphoreIndex < 0) { return Error::Max; } auto semaphore = phys_addrof(sData->semaphores[sData->firstFreeSemaphoreIndex]); auto semaphoreId = sData->firstFreeSemaphoreIndex; // Remove semaphore from free semaphore linked list sData->firstFreeSemaphoreIndex = semaphore->nextFreeSemaphoreIndex; if (semaphore->nextFreeSemaphoreIndex >= 0) { sData->semaphores[semaphore->nextFreeSemaphoreIndex].prevFreeSemaphoreIndex = int16_t { -1 }; } else { sData->lastFreeSemaphoreIndex = int16_t { -1 }; } semaphore->nextFreeSemaphoreIndex = int16_t { -1 }; semaphore->prevFreeSemaphoreIndex = int16_t { -1 }; sData->numCreatedSemaphores++; semaphore->id = static_cast<SemaphoreId>(semaphoreId | (sData->numCreatedSemaphores << 12)); semaphore->count = initialCount; semaphore->maxCount = maxCount; semaphore->pid = internal::getCurrentProcessId(); semaphore->unknown0x04 = nullptr; ThreadQueue_Initialise(phys_addrof(semaphore->waitThreadQueue)); return static_cast<Error>(semaphore->id); } Error IOS_DestroySempahore(SemaphoreId id) { auto semaphore = getSemaphore(id); if (!semaphore) { return Error::Invalid; } semaphore->count = 0; semaphore->maxCount = 0; // Add semaphore to the free semaphore linked list. auto index = static_cast<int16_t>(semaphore - phys_addrof(sData->semaphores)); auto prevSemaphoreIndex = sData->lastFreeSemaphoreIndex; if (prevSemaphoreIndex >= 0) { auto prevSemaphore = phys_addrof(sData->semaphores[sData->lastFreeSemaphoreIndex]); prevSemaphore->nextFreeSemaphoreIndex = index; } semaphore->prevFreeSemaphoreIndex = prevSemaphoreIndex; semaphore->nextFreeSemaphoreIndex = int16_t { -1 }; sData->lastFreeSemaphoreIndex = index; if (sData->firstFreeSemaphoreIndex < 0) { sData->firstFreeSemaphoreIndex = index; } internal::wakeupAllThreads(phys_addrof(semaphore->waitThreadQueue), Error::Intr); internal::reschedule(); return Error::Invalid; } Error IOS_WaitSemaphore(SemaphoreId id, BOOL tryWait) { auto semaphore = getSemaphore(id); if (!semaphore) { return Error::Invalid; } if (semaphore->count <= 0 && tryWait) { return Error::SemUnavailable; } while (semaphore->count <= 0) { internal::sleepThread(phys_addrof(semaphore->waitThreadQueue)); internal::reschedule(); auto thread = internal::getCurrentThread(); if (thread->context.queueWaitResult != Error::OK) { return thread->context.queueWaitResult; } } semaphore->count -= 1; return Error::OK; } Error IOS_SignalSempahore(SemaphoreId id) { auto semaphore = getSemaphore(id); if (!semaphore) { return Error::Invalid; } if (semaphore->count < semaphore->maxCount) { semaphore->count += 1; } internal::wakeupOneThread(phys_addrof(semaphore->waitThreadQueue), Error::OK); internal::reschedule(); return Error::OK; } namespace internal { void initialiseStaticSemaphoreData() { sData = allocProcessStatic<StaticSemaphoreData>(); } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_semaphore.h ================================================ #pragma once #include "ios_kernel_threadqueue.h" #include "ios/ios_enum.h" #include <common/cbool.h> #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::kernel { // Max num semaphores: 750 // SemaphoreId seems to be (id | (something << 12)); using SemaphoreId = int32_t; struct Thread; struct Semaphore { //! List of threads waiting on this semaphore, ordered by priority. be2_struct<ThreadQueue> waitThreadQueue; //! Unknown list of threads. be2_phys_ptr<Thread> unknown0x04; //! Unique semaphore identifier. be2_val<SemaphoreId> id; //! Process this semaphore belongs to. be2_val<ProcessId> pid; //! Current semaphore signal count. be2_val<int32_t> count; //! Maximum semaphore signal count. be2_val<int32_t> maxCount; //! Previous free semaphore, linked list. be2_val<int16_t> prevFreeSemaphoreIndex; //! Next free semaphore, linked list. be2_val<int16_t> nextFreeSemaphoreIndex; }; CHECK_OFFSET(Semaphore, 0x00, waitThreadQueue); CHECK_OFFSET(Semaphore, 0x04, unknown0x04); CHECK_OFFSET(Semaphore, 0x08, id); CHECK_OFFSET(Semaphore, 0x0C, pid); CHECK_OFFSET(Semaphore, 0x10, count); CHECK_OFFSET(Semaphore, 0x14, maxCount); CHECK_OFFSET(Semaphore, 0x18, prevFreeSemaphoreIndex); CHECK_OFFSET(Semaphore, 0x1A, nextFreeSemaphoreIndex); CHECK_SIZE(Semaphore, 0x1C); Error IOS_CreateSemaphore(int32_t maxCount, int32_t initialCount); Error IOS_DestroySempahore(SemaphoreId id); Error IOS_WaitSemaphore(SemaphoreId id, BOOL tryWait); Error IOS_SignalSempahore(SemaphoreId id); namespace internal { void initialiseStaticSemaphoreData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_thread.cpp ================================================ #include "ios_kernel_thread.h" #include "ios_kernel_threadqueue.h" #include "ios_kernel_process.h" #include "ios_kernel_scheduler.h" #include <array> #include <mutex> namespace ios::kernel { struct StaticThreadData { be2_array<Thread, MaxNumThreads> threads; be2_val<uint32_t> numActiveThreads = uint32_t { 0 }; }; static phys_ptr<StaticThreadData> sData = nullptr; namespace internal { static void memset32(phys_ptr<void> ptr, uint32_t value, uint32_t bytes); static void iosFiberEntryPoint(void *context); } // namespace internal Error IOS_CreateThread(ThreadEntryFn entry, phys_ptr<void> context, phys_ptr<uint8_t> stackTop, uint32_t stackSize, int priority, ThreadFlags flags) { phys_ptr<Thread> thread = nullptr; if (priority > MaxThreadPriority) { return Error::Invalid; } // We cannot create thread with priority higher than current thread's max // priority auto currentThread = internal::getCurrentThread(); if (currentThread && priority > currentThread->maxPriority) { return Error::Invalid; } // Check stack pointer and alignment if (!stackTop || (phys_cast<phys_addr>(stackTop) & 3)) { return Error::Invalid; } // Check stack size and alignment if (stackSize < 0x68 || (stackSize & 3)) { return Error::Invalid; } // Find a free thread for (auto i = 0u; i < sData->threads.size(); ++i) { if (sData->threads[i].state == ThreadState::Available) { thread = phys_addrof(sData->threads[i]); thread->id = i; break; } } if (!thread) { // Maximum number of threads running return Error::Max; } if (currentThread) { thread->pid = currentThread->pid; } else { thread->pid = ProcessId::KERNEL; } thread->state = ThreadState::Stopped; thread->maxPriority = priority; thread->priority = priority; thread->userStackAddr = stackTop; thread->userStackSize = stackSize; // TODO: thread->sysStackAddr = systemStack + thread->id * 0x400; thread->userContext.stackPointer = stackTop - 0x10; internal::memset32(stackTop - stackSize, 0xF5A5A5A5, stackSize); if (flags & ThreadFlags::AllocateTLS) { stackTop -= sizeof(ThreadLocalStorage); thread->threadLocalStorage = phys_cast<ThreadLocalStorage *>(stackTop); std::memset(thread->threadLocalStorage.get(), 0, 0x24); } else { thread->threadLocalStorage = nullptr; } sData->numActiveThreads++; thread->flags = flags; thread->threadQueueNext = nullptr; thread->threadQueue = nullptr; ThreadQueue_Initialise(phys_addrof(thread->joinQueue)); #ifdef IOS_EMULATE_ARM // We are not doing ARM emulation... thread->context.gpr[0] = phys_addr { context }.getAddress(); thread->context.gpr[13] = phys_addr { stackTop - 0x20 }.getAddress(); thread->context.pc = entry; if (entry & 1) { // Disable both FIQ and IRQ interrupts thread->context.cpsr = 0x30; } else { // Only disable IRQ interrupts thread->context.cpsr = 0x10; } thread->context.lr = 0xDEADC0DE; #else thread->context.entryPoint = entry; thread->context.entryPointArg = context; thread->context.fiber = platform::createFiber(internal::iosFiberEntryPoint, thread.get()); #endif return static_cast<Error>(thread->id); } Error IOS_JoinThread(ThreadId id, phys_ptr<Error> returnedValue) { auto currentThread = internal::getCurrentThread(); if (!id) { id = currentThread->id; } else if (id >= sData->threads.size()) { return Error::Invalid; } auto thread = phys_addrof(sData->threads[id]); if (thread->pid != currentThread->pid) { // Can only join threads belonging to the same process. return Error::Invalid; } if (thread == currentThread) { // Can't join self. return Error::Invalid; } if (thread->flags & ThreadFlags::Detached) { // Can't join a detached thread. return Error::Invalid; } /* if (thread->unknown0x64 != dword_81430D4) { return Error::Invalid; } */ if (thread->state != ThreadState::Dead) { internal::sleepThread(phys_addrof(thread->joinQueue)); internal::reschedule(); } if (returnedValue) { *returnedValue = thread->exitValue; } thread->state = ThreadState::Available; return Error::OK; } Error IOS_CancelThread(ThreadId id, Error exitValue) { auto currentThread = internal::getCurrentThread(); if (!id) { id = currentThread->id; } else if (id >= sData->threads.size()) { return Error::Invalid; } auto thread = phys_addrof(sData->threads[id]); if (thread->pid != currentThread->pid) { // Can only cancel threads belonging to the same process. return Error::Invalid; } if (thread->state != ThreadState::Stopped) { ThreadQueue_RemoveThread(thread->threadQueue, thread); } thread->exitValue = exitValue; sData->numActiveThreads--; if (thread->flags & ThreadFlags::Detached) { thread->state = ThreadState::Available; } else { thread->state = ThreadState::Dead; internal::wakeupAllThreads(phys_addrof(thread->joinQueue), Error::OK); } if (thread == currentThread) { internal::reschedule(); } return Error::Invalid; } Error IOS_StartThread(ThreadId id) { auto currentThread = internal::getCurrentThread(); if (!id) { id = currentThread->id; } else if (id >= sData->threads.size()) { return Error::Invalid; } auto thread = phys_addrof(sData->threads[id]); if (currentThread->pid != ProcessId::KERNEL && thread->pid != currentThread->pid) { // Can only start threads belonging to the same process. // Unless we're the kernel of course. return Error::Invalid; } if (thread->state != ThreadState::Stopped) { // Can only start a stopped thread. return Error::Invalid; } if (thread->threadQueue && !internal::isThreadInRunQueue(thread)) { thread->state = ThreadState::Waiting; ThreadQueue_PushThread(thread->threadQueue, thread); } else { thread->state = ThreadState::Ready; internal::queueThread(thread); } internal::reschedule(); return Error::OK; } Error IOS_SuspendThread(ThreadId id) { auto currentThread = internal::getCurrentThread(); if (!id) { id = currentThread->id; } else if (id >= sData->threads.size()) { return Error::Invalid; } auto thread = phys_addrof(sData->threads[id]); if (thread->pid != currentThread->pid) { // Can only suspend threads belonging to the same process. return Error::Invalid; } if (thread->state == ThreadState::Running) { thread->state = ThreadState::Stopped; } else if (thread->state != ThreadState::Waiting && thread->state != ThreadState::Ready) { // Cannot suspend a thread which is not Running, Ready or Waiting. return Error::Invalid; } else { thread->state = ThreadState::Stopped; ThreadQueue_RemoveThread(thread->threadQueue, thread); } if (thread == currentThread) { internal::reschedule(); } return Error::OK; } Error IOS_YieldCurrentThread() { internal::reschedule(true); return Error::Invalid; } Error IOS_GetCurrentThreadId() { auto thread = internal::getCurrentThread(); if (!thread) { return Error::Invalid; } return static_cast<Error>(thread->id); } phys_ptr<ThreadLocalStorage> IOS_GetCurrentThreadLocalStorage() { auto thread = internal::getCurrentThread(); if (!thread) { return nullptr; } return thread->threadLocalStorage; } Error IOS_GetThreadPriority(ThreadId id) { auto currentThread = internal::getCurrentThread(); if (!id) { id = currentThread->id; } else if (id >= sData->threads.size()) { return Error::Invalid; } auto thread = phys_addrof(sData->threads[id]); if (thread->pid != currentThread->pid) { // Can only access threads in same process. return Error::Invalid; } return static_cast<Error>(thread->priority); } Error IOS_SetThreadPriority(ThreadId id, ThreadPriority priority) { auto currentThread = internal::getCurrentThread(); if (!id) { id = currentThread->id; } else if (id >= sData->threads.size()) { return Error::Invalid; } auto thread = phys_addrof(sData->threads[id]); if (thread->pid != currentThread->pid) { // Can only access threads in same process. return Error::Invalid; } if (priority > MaxThreadPriority || priority > thread->maxPriority) { return Error::Invalid; } if (thread->priority == priority) { return Error::OK; } thread->priority = priority; if (thread != currentThread) { ThreadQueue_RemoveThread(thread->threadQueue, thread); ThreadQueue_PushThread(thread->threadQueue, thread); } internal::reschedule(); return Error::OK; } namespace internal { static void memset32(phys_ptr<void> ptr, uint32_t value, uint32_t bytes) { auto ptr32 = phys_cast<uint32_t *>(ptr); for (auto i = 0u; i < bytes / 4; ++i) { ptr32[i] = value; } } static void iosFiberEntryPoint(void *context) { auto thread = reinterpret_cast<Thread *>(context); #ifndef IOS_EMULATE_ARM auto exitValue = thread->context.entryPoint(thread->context.entryPointArg); #else decaf_abort("iosFiberEntryPoint for IOS_EMULATE_ARM unimplemented"); #endif if (thread->flags & ThreadFlags::Detached) { IOS_CancelThread(thread->id, exitValue); } else { thread->exitValue = exitValue; IOS_SuspendThread(thread->id); } } phys_ptr<Thread> getThread(ThreadId id) { auto thread = phys_addrof(sData->threads[id]); if (thread->id == id) { return thread; } return nullptr; } void setThreadName(ThreadId id, const char *name) { if (auto thread = getThread(id)) { thread->context.threadName = name; } } void initialiseStaticThreadData() { sData = allocProcessStatic<StaticThreadData>(); } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_thread.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios_kernel_threadqueue.h" #include "ios/ios_enum.h" #include <cstdint> #include <common/structsize.h> #include <common/platform_fiber.h> #include <libcpu/be2_struct.h> namespace ios::kernel { static constexpr auto MaxNumThreads = 180u; static constexpr auto MaxThreadPriority = 127; #pragma pack(push, 1) using ThreadId = uint32_t; using ThreadPriority = int32_t; using ThreadEntryFn = Error(*)(phys_ptr<void>); constexpr auto CurrentThread = ThreadId { 0 }; struct ContextLLE { be2_val<uint32_t> cpsr; be2_val<uint32_t> gpr[13]; be2_phys_ptr<void> stackPointer; be2_val<uint32_t> lr; be2_val<uint32_t> pc; }; CHECK_OFFSET(ContextLLE, 0x00, cpsr); CHECK_OFFSET(ContextLLE, 0x04, gpr); CHECK_OFFSET(ContextLLE, 0x38, stackPointer); CHECK_OFFSET(ContextLLE, 0x3C, lr); CHECK_OFFSET(ContextLLE, 0x40, pc); CHECK_SIZE(ContextLLE, 0x44); struct ContextHLE { ThreadEntryFn entryPoint; phys_ptr<void> entryPointArg; platform::Fiber *fiber; Error queueWaitResult; const char *threadName; PADDING(0x24); }; CHECK_SIZE(ContextHLE, 0x44); struct ThreadLocalStorage { be2_array<uint32_t, 9> storage; }; CHECK_OFFSET(ThreadLocalStorage, 0, storage); CHECK_SIZE(ThreadLocalStorage, 0x24); struct Thread { be2_struct<ContextHLE> context; //! Link to next item in the thread queue. be2_phys_ptr<Thread> threadQueueNext; be2_val<ThreadPriority> maxPriority; be2_val<ThreadPriority> priority; be2_val<ThreadState> state; be2_val<ProcessId> pid; be2_val<ThreadId> id; be2_val<ThreadFlags> flags; be2_val<Error> exitValue; //! Queue of threads waiting to join this thread. be2_struct<ThreadQueue> joinQueue; //! The thread queue this therad is currently in. be2_phys_ptr<ThreadQueue> threadQueue; be2_struct<ContextLLE> userContext; be2_phys_ptr<void> sysStackAddr; be2_phys_ptr<void> userStackAddr; be2_val<uint32_t> userStackSize; be2_phys_ptr<ThreadLocalStorage> threadLocalStorage; be2_val<uint32_t> profileCount; be2_val<uint32_t> profileTime; }; CHECK_OFFSET(Thread, 0, context); CHECK_OFFSET(Thread, 0x44, threadQueueNext); CHECK_OFFSET(Thread, 0x48, maxPriority); CHECK_OFFSET(Thread, 0x4C, priority); CHECK_OFFSET(Thread, 0x50, state); CHECK_OFFSET(Thread, 0x54, pid); CHECK_OFFSET(Thread, 0x58, id); CHECK_OFFSET(Thread, 0x5C, flags); CHECK_OFFSET(Thread, 0x60, exitValue); CHECK_OFFSET(Thread, 0x64, joinQueue); CHECK_OFFSET(Thread, 0x68, threadQueue); CHECK_OFFSET(Thread, 0x6C, userContext); CHECK_OFFSET(Thread, 0xB0, sysStackAddr); CHECK_OFFSET(Thread, 0xB4, userStackAddr); CHECK_OFFSET(Thread, 0xB8, userStackSize); CHECK_OFFSET(Thread, 0xBC, threadLocalStorage); CHECK_OFFSET(Thread, 0xC0, profileCount); CHECK_OFFSET(Thread, 0xC4, profileTime); CHECK_SIZE(Thread, 0xC8); #pragma pack(pop) Error IOS_CreateThread(ThreadEntryFn entry, phys_ptr<void> context, phys_ptr<uint8_t> stackTop, uint32_t stackSize, int priority, ThreadFlags flags); Error IOS_JoinThread(ThreadId id, phys_ptr<Error> returnedValue); Error IOS_CancelThread(ThreadId id, Error exitValue); Error IOS_StartThread(ThreadId id); Error IOS_SuspendThread(ThreadId id); Error IOS_YieldCurrentThread(); Error IOS_GetCurrentThreadId(); phys_ptr<ThreadLocalStorage> IOS_GetCurrentThreadLocalStorage(); Error IOS_GetThreadPriority(ThreadId id); Error IOS_SetThreadPriority(ThreadId id, ThreadPriority priority); namespace internal { phys_ptr<Thread> getCurrentThread(); ThreadId getCurrentThreadId(); phys_ptr<Thread> getThread(ThreadId id); void setThreadName(ThreadId id, const char *name); void initialiseStaticThreadData(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_threadqueue.cpp ================================================ #include "ios_kernel_thread.h" #include "ios_kernel_threadqueue.h" namespace ios::kernel { void ThreadQueue_Initialise(phys_ptr<ThreadQueue> queue) { queue->first = nullptr; } void ThreadQueue_PushThread(phys_ptr<ThreadQueue> queue, phys_ptr<Thread> thread) { if (!queue || !thread) { return; } auto insertAt = phys_addrof(queue->first); auto next = queue->first; for (auto itr = queue->first; itr; itr = itr->threadQueueNext) { if (thread->priority > itr->priority) { break; } insertAt = phys_addrof(itr->threadQueueNext); next = itr->threadQueueNext; } *insertAt = thread; thread->threadQueue = queue; thread->threadQueueNext = next; } phys_ptr<Thread> ThreadQueue_PeekThread(phys_ptr<ThreadQueue> queue) { return queue->first; } phys_ptr<Thread> ThreadQueue_PopThread(phys_ptr<ThreadQueue> queue) { auto thread = queue->first; if (thread) { queue->first = thread->threadQueueNext; thread->threadQueue = nullptr; thread->threadQueueNext = nullptr; } else { queue->first = nullptr; } return thread; } void ThreadQueue_RemoveThread(phys_ptr<ThreadQueue> queue, phys_ptr<Thread> removeThread) { if (!queue || !removeThread) { return; } auto thread = queue->first; auto removeAt = phys_addrof(queue->first); while (thread) { if (thread == removeThread) { *removeAt = removeThread->threadQueueNext; break; } removeAt = phys_addrof(thread->threadQueueNext); thread = thread->threadQueueNext; } } } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_threadqueue.h ================================================ #pragma once #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::kernel { struct Thread; struct ThreadQueue { //! Linked list on thread->threadQueueNext. be2_phys_ptr<Thread> first; }; CHECK_OFFSET(ThreadQueue, 0x0, first); CHECK_SIZE(ThreadQueue, 0x4); void ThreadQueue_Initialise(phys_ptr<ThreadQueue> queue); void ThreadQueue_PushThread(phys_ptr<ThreadQueue> queue, phys_ptr<Thread> thread); phys_ptr<Thread> ThreadQueue_PeekThread(phys_ptr<ThreadQueue> queue); phys_ptr<Thread> ThreadQueue_PopThread(phys_ptr<ThreadQueue> queue); void ThreadQueue_RemoveThread(phys_ptr<ThreadQueue> queue, phys_ptr<Thread> removeThread); } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_timer.cpp ================================================ #include "ios_kernel_timer.h" #include "ios_kernel_hardware.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_process.h" #include "ios_kernel_thread.h" #include "ios/ios_alarm_thread.h" #include "ios/ios_stackobject.h" #include <chrono> namespace ios::kernel { constexpr auto TimerThreadNumMessages = 1u; constexpr auto TimerThreadStackSize = 0x400u; constexpr auto TimerThreadPriority = 125u; struct StaticTimerData { be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_array<Message, TimerThreadNumMessages> messageBuffer; be2_array<uint8_t, TimerThreadStackSize> threadStack; be2_struct<TimerManager> timerManager; }; static phys_ptr<StaticTimerData> sData; static std::chrono::time_point<std::chrono::steady_clock> sStartupTime; namespace internal { uint32_t startTimer(phys_ptr<Timer> timer); void setAlarm(TimerTicks when); } // namespace internal Error IOS_GetUpTime64(phys_ptr<TimerTicks> outTime) { *outTime = internal::getUpTime64(); return Error::OK; } Error IOS_CreateTimer(std::chrono::microseconds delay, std::chrono::microseconds period, MessageQueueId queue, Message message) { auto &timerManager = sData->timerManager; // Verify the message queue exists. auto error = internal::getMessageQueueSafe(queue, nullptr); if (error < Error::OK) { return error; } // Check the process has not exceeded its maximum number of processes. auto pid = internal::getCurrentProcessId(); if (timerManager.numProcessTimers[pid] >= MaxNumTimersPerProcess) { return Error::Max; } if (timerManager.firstFreeIdx < 0) { return Error::FailAlloc; } auto timerIdx = timerManager.firstFreeIdx; auto &timer = timerManager.timers[timerIdx]; auto nextFreeIdx = timer.nextTimerIdx; if (nextFreeIdx < 0) { timerManager.firstFreeIdx = int16_t { -1 }; timerManager.lastFreeIdx = int16_t { -1 }; } else { auto &nextTimer = timerManager.timers[nextFreeIdx]; nextTimer.prevTimerIdx = int16_t { -1 }; timerManager.firstFreeIdx = nextFreeIdx; } timerManager.totalCreatedTimers++; timer.uid = timerIdx | static_cast<int32_t>((timerManager.totalCreatedTimers << 12) & 0x7FFFFFFF); timer.state = TimerState::Ready; timer.nextTriggerTime = TimerTicks { 0 }; timer.period = static_cast<TimeMicroseconds32>(period.count()); timer.queueId = queue; timer.message = message; timer.processId = pid; timer.prevTimerIdx = int16_t { -1 }; timer.nextTimerIdx = int16_t { -1 }; timerManager.numRegistered++; if (timerManager.numRegistered > timerManager.mostRegistered) { timerManager.mostRegistered = timerManager.numRegistered; } if (delay.count() || period.count()) { timer.nextTriggerTime = internal::getUpTime64() + internal::durationToTicks(delay); if (internal::startTimer(phys_addrof(timer)) == 0) { internal::setAlarm(timer.nextTriggerTime); } } return static_cast<Error>(timer.uid); } Error IOS_DestroyTimer(TimerId timerId) { phys_ptr<Timer> timer; auto &timerManager = sData->timerManager; auto error = internal::getTimer(timerId, &timer); if (error < Error::OK) { return error; } if (timer->state == TimerState::Running) { error = internal::stopTimer(timer); if (error < Error::OK) { return error; } } auto pid = internal::getCurrentProcessId(); timerManager.numProcessTimers[pid]--; timer->state = TimerState::Free; timer->uid = TimerId { -4 }; timer->processId = ProcessId { -4 }; timer->queueId = MessageQueueId { -4 }; timer->message = Message { 0 }; timer->period = 0u; timer->nextTriggerTime = TimerTicks{ 0 }; if (timerManager.lastFreeIdx < 0) { timerManager.firstFreeIdx = timer->index; timerManager.lastFreeIdx = timer->index; timer->nextTimerIdx = int16_t { -1 }; timer->prevTimerIdx = int16_t { -1 }; } else { auto &lastFreeTimer = timerManager.timers[timerManager.lastFreeIdx]; lastFreeTimer.nextTimerIdx = timer->index; timer->prevTimerIdx = timerManager.lastFreeIdx; timer->nextTimerIdx = int16_t { -1 }; timerManager.lastFreeIdx = timer->index; } timerManager.numRegistered--; return Error::OK; } Error IOS_StopTimer(TimerId timerId) { phys_ptr<Timer> timer; auto error = internal::getTimer(timerId, &timer); if (error < Error::OK) { return error; } if (timer->state == TimerState::Running) { error = internal::stopTimer(timer); } else { timer->state = TimerState::Stopped; error = Error::Expired; } return error; } Error IOS_RestartTimer(TimerId timerId, std::chrono::microseconds delay, std::chrono::microseconds period) { phys_ptr<Timer> timer; auto error = internal::getTimer(timerId, &timer); if (error < Error::OK) { return error; } if (timer->state == TimerState::Running) { error = internal::stopTimer(timer); if (error < Error::OK) { return error; } } timer->nextTriggerTime = TimerTicks { 0 }; timer->period = static_cast<TimeMicroseconds32>(period.count()); if (delay.count() || period.count()) { timer->nextTriggerTime = internal::getUpTime64() + internal::durationToTicks(delay); if (internal::startTimer(timer) == 0) { internal::setAlarm(timer->nextTriggerTime); } } return error; } namespace internal { static Error timerThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; auto &timerManager = sData->timerManager; while (true) { auto error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto now = internal::getUpTime64(); while (timerManager.firstRunningTimerIdx >= 0) { auto &timer = timerManager.timers[timerManager.firstRunningTimerIdx]; auto queue = phys_ptr<MessageQueue>(nullptr); if (timer.nextTriggerTime >= now) { // No more timers to run break; } // Send message to notify any waiters on the timer error = internal::getMessageQueue(timer.queueId, &queue); if (error >= 0) { internal::sendMessage(queue, timer.message, MessageFlags::NonBlocking); } // Remove timer from queue timerManager.firstRunningTimerIdx = timer.nextTimerIdx; if (timerManager.firstRunningTimerIdx < 0) { timerManager.lastRunningTimerIdx = int16_t { -1 }; } else { timerManager.timers[timerManager.firstRunningTimerIdx].prevTimerIdx = int16_t { -1 }; } // Update timer timer.prevTimerIdx = int16_t { -1 }; timer.nextTimerIdx = int16_t { -1 }; timer.state = TimerState::Triggered; if (!timer.period) { continue; } // Setup periodic timer next trigger timer.nextTriggerTime += timer.period; startTimer(phys_addrof(timer)); } } } void setAlarm(TimerTicks when) { auto nextAlarm = std::chrono::nanoseconds { when } + sStartupTime; ios::internal::setNextAlarm(nextAlarm); } TimerTicks timeToTicks(std::chrono::steady_clock::time_point time) { auto dt = time - sStartupTime; return std::chrono::duration_cast<std::chrono::nanoseconds>(dt).count(); } Error getTimer(TimerId id, phys_ptr<Timer> *outTimer) { auto index = id & 0xFFF; if (id < 0 || index >= static_cast<TimerId>(sData->timerManager.timers.size())) { return Error::Invalid; } if (sData->timerManager.timers[index].uid != id) { return Error::NoExists; } *outTimer = phys_addrof(sData->timerManager.timers[index]); return Error::OK; } /** * Put the timer into the running timer list. * * \return * Returns the position of the timer in the queue, if the position is 0 then * the new timer will be the first one to trigger in which case the caller * is responsbile for updating the next interrupt time. */ uint32_t startTimer(phys_ptr<Timer> timer) { auto &timerManager = sData->timerManager; auto timerQueuePosition = 0u; // Insert timer into the running timer list ordered by nextTriggerTime. if (timerManager.firstRunningTimerIdx < 0) { timerManager.firstRunningTimerIdx = timer->index; timerManager.lastRunningTimerIdx = timer->index; timer->prevTimerIdx = int16_t { -1 }; timer->nextTimerIdx = int16_t { -1 }; } else { auto prevTimerIdx = int16_t { -1 }; auto nextTimerIdx = int16_t { -1 }; auto itrTimerIdx = timerManager.firstRunningTimerIdx; while (itrTimerIdx >= 0) { const auto &itrTimer = timerManager.timers[itrTimerIdx]; if (itrTimer.nextTriggerTime >= timer->nextTriggerTime) { break; } itrTimerIdx = itrTimer.nextTimerIdx; timerQueuePosition++; } if (prevTimerIdx < 0) { nextTimerIdx = timerManager.firstRunningTimerIdx; timerManager.firstRunningTimerIdx = timer->index; } else { auto &prevTimer = timerManager.timers[prevTimerIdx]; nextTimerIdx = prevTimer.nextTimerIdx; prevTimer.nextTimerIdx = timer->index; } if (nextTimerIdx < 0) { decaf_check(timerManager.lastRunningTimerIdx == prevTimerIdx); timerManager.lastRunningTimerIdx = timer->index; } else { auto &nextTimer = timerManager.timers[nextTimerIdx]; nextTimer.prevTimerIdx = timer->index; } timer->prevTimerIdx = prevTimerIdx; timer->nextTimerIdx = nextTimerIdx; } timer->state = TimerState::Running; timerManager.numRunningTimers++; return timerQueuePosition; } Error stopTimer(phys_ptr<Timer> timer) { auto &timerManager = sData->timerManager; auto prevTimerIdx = timer->prevTimerIdx; auto nextTimerIdx = timer->nextTimerIdx; if (prevTimerIdx < 0) { decaf_check(timerManager.firstRunningTimerIdx == timer->index); timerManager.firstRunningTimerIdx = nextTimerIdx; } else { auto &prevTimer = timerManager.timers[prevTimerIdx]; prevTimer.nextTimerIdx = nextTimerIdx; } if (nextTimerIdx < 0) { decaf_check(timerManager.lastRunningTimerIdx == timer->index); timerManager.lastRunningTimerIdx = prevTimerIdx; } else { auto &nextTimer = timerManager.timers[nextTimerIdx]; nextTimer.prevTimerIdx = prevTimerIdx; } timer->state = TimerState::Stopped; timer->prevTimerIdx = int16_t { -1 }; timer->nextTimerIdx = int16_t { -1 }; timerManager.numRunningTimers--; return Error::OK; } Error startTimerThread() { // Create message queue auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), static_cast<uint32_t>(sData->messageBuffer.size())); if (error < Error::OK) { return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); // Set timer event handler error = IOS_HandleEvent(DeviceId::Timer, sData->messageQueueId, 0); if (error < Error::OK) { return error; } // Create thread error = IOS_CreateThread(&timerThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), static_cast<uint32_t>(sData->threadStack.size()), TimerThreadPriority, kernel::ThreadFlags::Detached); if (error < Error::OK) { kernel::IOS_DestroyMessageQueue(sData->messageQueueId); return error; } sData->threadId = static_cast<kernel::ThreadId>(error); internal::setThreadName(sData->threadId, "TimerThread"); return kernel::IOS_StartThread(sData->threadId); } void initialiseStaticTimerData() { sStartupTime = std::chrono::steady_clock::now(); sData = allocProcessStatic<StaticTimerData>(); for (auto i = 0u; i < sData->timerManager.timers.size(); ++i) { auto &timer = sData->timerManager.timers[i]; timer.uid = TimerId { -4 }; timer.processId = ProcessId { -4 }; timer.state = TimerState::Free; timer.prevTimerIdx = static_cast<int16_t>(i - 1); timer.index = static_cast<int16_t>(i); timer.nextTimerIdx = static_cast<int16_t>(i + 1); } sData->timerManager.timers[sData->timerManager.timers.size() - 1].nextTimerIdx = int16_t { -1 }; sData->timerManager.firstFreeIdx = int16_t { 0 }; sData->timerManager.lastFreeIdx = int16_t { 255 }; sData->timerManager.firstRunningTimerIdx = int16_t { -1 }; sData->timerManager.lastRunningTimerIdx = int16_t { -1 }; } TimerTicks getUpTime64() { return internal::timeToTicks(std::chrono::steady_clock::now()); } } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/kernel/ios_kernel_timer.h ================================================ #pragma once #include "ios_kernel_enum.h" #include "ios_kernel_messagequeue.h" #include "ios_kernel_process.h" #include "ios/ios_enum.h" #include <chrono> #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::kernel { #pragma pack(push, 1) static constexpr auto MaxNumTimers = 256u; static constexpr auto MaxNumTimersPerProcess = 64u; using TimerId = int32_t; using TimerTicks = uint64_t; using TimeMicroseconds32 = uint32_t; using TimeMicroseconds64 = uint64_t; struct Timer { be2_val<TimerId> uid; be2_val<TimerState> state; be2_val<TimerTicks> nextTriggerTime; be2_val<TimeMicroseconds32> period; be2_val<MessageQueueId> queueId; be2_val<Message> message; be2_val<ProcessId> processId; //! This timers index in the TimerManager timers list. be2_val<int16_t> index; //! If state == Free this is the index of the next free timer. //! If state == Running this is the index of the next running timer. be2_val<int16_t> nextTimerIdx; //! If state == Free this is the index of the previous free timer. //! If state == Running this is the index of the previous running timer. be2_val<int16_t> prevTimerIdx; be2_val<uint16_t> unk0x26; }; CHECK_OFFSET(Timer, 0x00, uid); CHECK_OFFSET(Timer, 0x04, state); CHECK_OFFSET(Timer, 0x08, nextTriggerTime); CHECK_OFFSET(Timer, 0x10, period); CHECK_OFFSET(Timer, 0x14, queueId); CHECK_OFFSET(Timer, 0x18, message); CHECK_OFFSET(Timer, 0x1C, processId); CHECK_OFFSET(Timer, 0x20, index); CHECK_OFFSET(Timer, 0x22, nextTimerIdx); CHECK_OFFSET(Timer, 0x24, prevTimerIdx); CHECK_OFFSET(Timer, 0x26, unk0x26); CHECK_SIZE(Timer, 0x28); struct TimerManager { //! Total number of timers that have been created. be2_val<uint32_t> totalCreatedTimers; //! Number of timers each process has be2_array<uint16_t, NumIosProcess> numProcessTimers; //! Index of the first running Timer, ordered by nextTriggerTime. be2_val<int16_t> firstRunningTimerIdx; //! Index of the last running Timer, ordered by nextTriggerTime. be2_val<int16_t> lastRunningTimerIdx; //! Number of actively running timers. be2_val<uint16_t> numRunningTimers; //! Index of the first free Timer. be2_val<int16_t> firstFreeIdx; //! Index of the last free Timer. be2_val<int16_t> lastFreeIdx; //! Number of registered Timers. be2_val<uint16_t> numRegistered; //! Highest number of registered Timers at one time. be2_val<uint16_t> mostRegistered; be2_val<uint16_t> unk0x2E; be2_array<Timer, MaxNumTimers> timers; }; CHECK_OFFSET(TimerManager, 0x00, totalCreatedTimers); CHECK_OFFSET(TimerManager, 0x04, numProcessTimers); CHECK_OFFSET(TimerManager, 0x20, firstRunningTimerIdx); CHECK_OFFSET(TimerManager, 0x22, lastRunningTimerIdx); CHECK_OFFSET(TimerManager, 0x24, numRunningTimers); CHECK_OFFSET(TimerManager, 0x26, firstFreeIdx); CHECK_OFFSET(TimerManager, 0x28, lastFreeIdx); CHECK_OFFSET(TimerManager, 0x2A, numRegistered); CHECK_OFFSET(TimerManager, 0x2C, mostRegistered); CHECK_OFFSET(TimerManager, 0x2E, unk0x2E); CHECK_OFFSET(TimerManager, 0x30, timers); #pragma pack(pop) Error IOS_GetUpTime64(phys_ptr<TimerTicks> outTime); Error IOS_CreateTimer(std::chrono::microseconds delay, std::chrono::microseconds period, MessageQueueId queue, Message message); Error IOS_DestroyTimer(TimerId timerId); Error IOS_StopTimer(TimerId timerId); Error IOS_RestartTimer(TimerId timerId, std::chrono::microseconds delay, std::chrono::microseconds period); namespace internal { Error getTimer(TimerId id, phys_ptr<Timer> *outTimer); Error stopTimer(phys_ptr<Timer> timer); TimerTicks timeToTicks(std::chrono::steady_clock::time_point time); template<class Rep, class Period> TimerTicks durationToTicks(std::chrono::duration<Rep, Period> duration) { return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count(); } Error startTimerThread(); void initialiseStaticTimerData(); TimerTicks getUpTime64(); } // namespace internal } // namespace ios::kernel ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp.cpp ================================================ #include "ios_mcp.h" #include "ios_mcp_config.h" #include "ios_mcp_enum.h" #include "ios_mcp_mcp_thread.h" #include "ios_mcp_pm_thread.h" #include "ios_mcp_ppc_thread.h" #include "ios_mcp_title.h" #include "ios/kernel/ios_kernel_debug.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/ios_stackobject.h" #include <common/log.h> using namespace ios::kernel; namespace ios::mcp { constexpr auto LocalHeapSize = 0x8000u; constexpr auto CrossHeapSize = 0x31E000u; constexpr auto MainThreadNumMessages = 30u; struct StaticMcpData { be2_val<uint32_t> bootFlags; be2_val<uint32_t> systemModeFlags; be2_val<SystemFileSys> systemFileSys; be2_struct<IpcRequest> sysEventMsg; be2_val<MessageQueueId> messageQueueId; be2_array<Message, MainThreadNumMessages> messageBuffer; }; static phys_ptr<StaticMcpData> sData = nullptr; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { static void initialiseStaticData() { sData = allocProcessStatic<StaticMcpData>(); sData->sysEventMsg.command = static_cast<Command>(MainThreadCommand::SysEvent); sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } static void initialiseClientCaps() { StackObject<uint64_t> mask; struct { ProcessId pid; FeatureId fid; uint64_t mask; } caps[] = { { ProcessId::MCP, 0x7FFFFFFF, 0xFFFFFFFFFFFFFFFF }, { ProcessId::CRYPTO, 1, 0xFF }, { ProcessId::USB, 1, 0xF }, { ProcessId::USB, 0xC, 0xFFFFFFFFFFFFFFFF }, { ProcessId::USB, 9, 0xFFFFFFFFFFFFFFFF }, { ProcessId::USB, 0xB, 0x3300300 }, { ProcessId::FS, 1, 0xF }, { ProcessId::FS, 3, 3 }, { ProcessId::FS, 9, 1 }, { ProcessId::FS, 0xB, 0xFFFFFFFFFFFFFFFF }, { ProcessId::FS, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::FS, 0xC, 1 }, { ProcessId::PAD, 0xB, 0x101000 }, { ProcessId::PAD, 1, 0xF }, { ProcessId::PAD, 0xD, 1 }, { ProcessId::PAD, 0x18, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NET, 1, 0xF }, { ProcessId::NET, 8, 1 }, { ProcessId::NET, 0xC, 1 }, { ProcessId::NET, 0x1A, 0xFFFFFFFFFFFFFFFF }, }; for (auto &cap : caps) { *mask = cap.mask; IOS_SetClientCapabilities(cap.pid, cap.fid, mask); } } static Error initialiseProcessTitles() { return static_cast<Error>( IOS_SetProcessTitle(ProcessId::MCP, 0, 0) | IOS_SetProcessTitle(ProcessId::BSP, 0x100000F8ull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::CRYPTO, 0x100000F9ull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::USB, 0x100000FAull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::FS, 0x100000F1ull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::PAD, 0x100000F2ull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::NET, 0x100000FBull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::ACP, 0x100000F6ull, 0x400u) | IOS_SetProcessTitle(ProcessId::NSEC, 0x100000F4ull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::AUXIL, 0x100000F5ull, 0x100000FF) | IOS_SetProcessTitle(ProcessId::NIM, 0x100000F3ull, 0x400u) | IOS_SetProcessTitle(ProcessId::FPD, 0x100000F7ull, 0x400u) | IOS_SetProcessTitle(ProcessId::TEST, 0x100000FCull, 0x100000FF)); } static Error mainThreadLoop() { StackObject<Message> message; while (true) { auto error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); /* TODO: Handle commands switch (request->requestData.command) { default: } */ IOS_ResourceReply(request, Error::InvalidArg); } } uint32_t getBootFlags() { return sData->bootFlags; } uint32_t getSystemModeFlags() { return sData->systemModeFlags; } SystemFileSys getSystemFileSys() { return sData->systemFileSys; } } // namespace internal Error processEntryPoint(phys_ptr<void> /* context */) { // Initialise process static data internal::initialiseStaticData(); internal::initialiseStaticConfigData(); internal::initialiseStaticMcpThreadData(); internal::initialiseStaticPmThreadData(); internal::initialiseStaticPpcThreadData(); internal::initialiseTitleStaticData(); // Initialise process heaps auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { gLog->error("Failed to create local process heap, error = {}.", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { gLog->error("Failed to create cross process heap, error = {}.", error); return error; } // Set normal system flags sData->bootFlags = 0xC200000u; sData->systemModeFlags = 0x100000u; sData->systemFileSys = SystemFileSys::Nand; IOS_SetSecurityLevel(SecurityLevel::Normal); // Start pm thread error = internal::startPmThread(); if (error < Error::OK) { gLog->error("Failed to start pm thread, error = {}.", error); return error; } // Create main thread message queue error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), sData->messageBuffer.size()); if (error < Error::OK) { gLog->error("Failed to create main thread message buffer, error = {}.", error); return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); // Set process client caps internal::initialiseClientCaps(); internal::initialiseProcessTitles(); // Stat mcp thread internal::startMcpThread(); // Start PPC thread internal::startPpcThread(); // Register main thread as SysEvent handler error = IOS_HandleEvent(DeviceId::SysEvent, sData->messageQueueId, makeMessage(phys_addrof(sData->sysEventMsg))); if (error < Error::OK) { gLog->error("Failed to register SysEvent event handler, error = {}.", error); return error; } // Handle all pending resource manager registrations error = internal::handleResourceManagerRegistrations(sData->systemModeFlags, sData->bootFlags); if (error < Error::OK) { gLog->error("Failed to handle resource manager registrations, error = {}.", error); return error; } return internal::mainThreadLoop(); } } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp.h ================================================ #pragma once #include "ios_mcp_enum.h" #include "ios/kernel/ios_kernel_process.h" #include <cstdint> namespace ios::mcp { Error processEntryPoint(phys_ptr<void> context); namespace internal { uint32_t getBootFlags(); uint32_t getSystemModeFlags(); SystemFileSys getSystemFileSys(); } // namespace internal } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_config.cpp ================================================ #include "ios_mcp_config.h" #include "ios/kernel/ios_kernel_debug.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/ios_stackobject.h" #include "decaf_config.h" #include <array> #include <libcpu/be2_struct.h> #include <string_view> using namespace ios::auxil; using namespace ios::kernel; namespace ios::mcp::internal { struct StaticConfigData { be2_struct<RtcConfig> rtcConfig; be2_struct<SystemConfig> systemConfig; be2_struct<SysProdConfig> sysProdConfig; }; static phys_ptr<StaticConfigData> sData = nullptr; static std::array<const char *, 5> sValidRootKeys = { "app", "eco", "rtc", "system", "sys_prod" }; static bool isValidRootKey(std::string_view key) { auto securityLevel = IOS_GetSecurityLevel(); if (securityLevel != ios::kernel::SecurityLevel::Debug && securityLevel != ios::kernel::SecurityLevel::Test) { return true; } for (auto validKey : sValidRootKeys) { if (key.compare(validKey) == 0) { return true; } } return false; } static std::string_view getFileSysPath(UCFileSys fileSys) { switch (fileSys) { case UCFileSys::Sys: return "/vol/system/config/"; case UCFileSys::Slc: return "/vol/system_slc/config/"; case UCFileSys::Ram: return "/vol/system_ram/config/"; default: return "*error*"; } } MCPError translateUCError(UCError error) { switch (error) { case UCError::OK: return MCPError::OK; case UCError::Other: return MCPError::Invalid; case UCError::System: return MCPError::System; case UCError::Alloc: return MCPError::Alloc; case UCError::Opcode: return MCPError::Opcode; case UCError::InvalidParam: return MCPError::InvalidParam; case UCError::InvalidType: return MCPError::InvalidType; case UCError::Unsupported: return MCPError::Unsupported; case UCError::NonLeafNode: return MCPError::NonLeafNode; case UCError::KeyNotFound: return MCPError::KeyNotFound; case UCError::Modify: return MCPError::Modify; case UCError::StringTooLong: return MCPError::StringTooLong; case UCError::RootKeysDiffer: return MCPError::RootKeysDiffer; case UCError::InvalidLocation: return MCPError::InvalidLocation; case UCError::BadComment: return MCPError::BadComment; case UCError::ReadAccess: return MCPError::ReadAccess; case UCError::WriteAccess: return MCPError::WriteAccess; case UCError::CreateAccess: return MCPError::CreateAccess; case UCError::FileSysName: return MCPError::FileSysName; case UCError::FileSysInit: return MCPError::FileSysInit; case UCError::FileSysMount: return MCPError::FileSysMount; case UCError::FileOpen: return MCPError::FileOpen; case UCError::FileStat: return MCPError::FileStat; case UCError::FileRead: return MCPError::FileRead; case UCError::FileWrite: return MCPError::FileWrite; case UCError::FileTooBig: return MCPError::FileTooBig; case UCError::FileRemove: return MCPError::FileRemove; case UCError::FileRename: return MCPError::FileRename; case UCError::FileClose: return MCPError::FileClose; case UCError::FileSeek: return MCPError::FileSeek; case UCError::MalformedXML: return MCPError::MalformedXML; case UCError::Version: return MCPError::Version; case UCError::NoIPCBuffers: return MCPError::NoIpcBuffers; case UCError::FileLockNeeded: return MCPError::FileLockNeeded; case UCError::SysProt: return MCPError::SysProt; default: return MCPError::Invalid; } } MCPError readConfigItems(phys_ptr<UCItem> items, uint32_t count) { if (count == 0) { return MCPError::OK; } auto name = std::string_view { phys_addrof(items[0].name).get() }; auto fileSys = getFileSys(name); if (fileSys == UCFileSys::Invalid) { return MCPError::InvalidLocation; } auto rootKey = getRootKey(name); if (!isValidRootKey(rootKey)) { return MCPError::FileSysName; } return translateUCError(readItems(getFileSysPath(fileSys), items, count, nullptr)); } MCPError writeConfigItems(phys_ptr<UCItem> items, uint32_t count) { if (count == 0) { return MCPError::OK; } auto name = std::string_view { phys_addrof(items[0].name).get() }; auto fileSys = getFileSys(name); if (fileSys == UCFileSys::Invalid) { return MCPError::InvalidLocation; } auto rootKey = getRootKey(name); if (!isValidRootKey(rootKey)) { return MCPError::FileSysName; } return translateUCError(writeItems(getFileSysPath(fileSys), items, count, nullptr)); } MCPError deleteConfigItems(phys_ptr<UCItem> items, uint32_t count) { if (count == 0) { return MCPError::OK; } auto name = std::string_view { phys_addrof(items[0].name).get() }; auto fileSys = getFileSys(name); if (fileSys == UCFileSys::Invalid) { return MCPError::InvalidLocation; } auto rootKey = getRootKey(name); if (!isValidRootKey(rootKey)) { return MCPError::FileSysName; } return translateUCError(deleteItems(getFileSysPath(fileSys), items, count)); } MCPError loadRtcConfig() { StackArray<UCItem, 3> items; auto config = phys_addrof(sData->rtcConfig); items[0].name = "slc:rtc"; items[0].access = 0x777u; items[0].dataType = UCDataType::Complex; items[1].name = "slc:rtc.version"; items[1].dataType = UCDataType::UnsignedInt; items[1].dataSize = 4u; items[1].data = phys_addrof(config->version); items[2].name = "slc:rtc.rtc_offset"; items[2].dataType = UCDataType::UnsignedInt; items[2].dataSize = 4u; items[2].data = phys_addrof(config->rtc_offset); auto error = readConfigItems(items, items.size()); if (error < MCPError::OK || config->version < 21) { // Factory reset items std::memset(config.get(), 0, sizeof(RtcConfig)); config->version = 21u; config->rtc_offset = 0x4EFFA200u; // Try save them to file deleteConfigItems(items, 1); writeConfigItems(items, items.size()); } return MCPError::OK; } MCPError loadSystemConfig() { StackArray<UCItem, 20> items; auto config = phys_addrof(sData->systemConfig); items[0].name = "system"; items[0].access = 0x777u; items[0].dataType = UCDataType::Complex; items[1].name = "system.version"; items[1].dataType = UCDataType::UnsignedInt; items[1].dataSize = 4u; items[1].data = phys_addrof(config->version); items[2].name = "system.cmdFlags"; items[2].dataType = UCDataType::UnsignedInt; items[2].dataSize = 4u; items[2].data = phys_addrof(config->cmdFlags); items[3].name = "system.default_os_id"; items[3].dataType = UCDataType::HexBinary; items[3].dataSize = 8u; items[3].data = phys_addrof(config->default_os_id); items[4].name = "system.default_title_id"; items[4].dataType = UCDataType::HexBinary; items[4].dataSize = 8u; items[4].data = phys_addrof(config->default_title_id); items[5].name = "system.log.enable"; items[5].dataType = UCDataType::UnsignedInt; items[5].dataSize = 4u; items[5].data = phys_addrof(config->log.enable); items[6].name = "system.log.max_size"; items[6].dataType = UCDataType::UnsignedInt; items[6].dataSize = 4u; items[6].data = phys_addrof(config->log.max_size); items[7].name = "system.standby.enable"; items[7].dataType = UCDataType::UnsignedInt; items[7].dataSize = 4u; items[7].data = phys_addrof(config->standby.enable); items[8].name = "system.ramdisk.cache_user_code"; items[8].dataType = UCDataType::UnsignedInt; items[8].dataSize = 4u; items[8].data = phys_addrof(config->ramdisk.cache_user_code); items[9].name = "system.ramdisk.max_file_size"; items[9].dataType = UCDataType::UnsignedInt; items[9].dataSize = 4u; items[9].data = phys_addrof(config->ramdisk.max_file_size); items[10].name = "system.ramdisk.cache_delay_ms"; items[10].dataType = UCDataType::UnsignedInt; items[10].dataSize = 4u; items[10].data = phys_addrof(config->ramdisk.cache_delay_ms); items[11].name = "system.simulated_ppc_mem2_size"; items[11].dataType = UCDataType::UnsignedInt; items[11].dataSize = 4u; items[11].data = phys_addrof(config->simulated_ppc_mem2_size); items[12].name = "system.dev_mode"; items[12].dataType = UCDataType::UnsignedInt; items[12].dataSize = 4u; items[12].data = phys_addrof(config->dev_mode); items[13].name = "system.prev_title_id"; items[13].dataType = UCDataType::HexBinary; items[13].dataSize = 8u; items[13].data = phys_addrof(config->prev_title_id); items[14].name = "system.prev_os_id"; items[14].dataType = UCDataType::HexBinary; items[14].dataSize = 8u; items[14].data = phys_addrof(config->prev_os_id); items[15].name = "system.default_app_type"; items[15].dataType = UCDataType::HexBinary; items[15].dataSize = 4u; items[15].error = static_cast<UCError>(0x90000001); // whyy??? items[15].data = phys_addrof(config->default_app_type); items[16].name = "system.default_device_type"; items[16].dataType = UCDataType::String; items[16].dataSize = 16u; items[16].data = phys_addrof(config->default_device_type); items[17].name = "system.default_device_index"; items[17].dataType = UCDataType::UnsignedInt; items[17].dataSize = 4u; items[17].data = phys_addrof(config->default_device_index); items[18].name = "system.fast_relaunch_value"; items[18].dataType = UCDataType::UnsignedInt; items[18].dataSize = 4u; items[18].data = phys_addrof(config->fast_relaunch_value); items[19].name = "system.default_eco_title_id"; items[19].dataType = UCDataType::HexBinary; items[19].dataSize = 8u; items[19].data = phys_addrof(config->default_eco_title_id); auto error = readConfigItems(items, items.size()); if (error < MCPError::OK || config->version < 21) { // Factory reset items std::memset(config.get(), 0, sizeof(SystemConfig)); config->version = 21u; config->dev_mode = 0u; config->default_eco_title_id = 0x0005001010066000ull; config->standby.enable = 0u; config->ramdisk.max_file_size = 0xA00000u; config->simulated_ppc_mem2_size = 0u; config->default_app_type = 0x90000001u; // Try save them to file deleteConfigItems(items, 1); writeConfigItems(items, items.size()); } return MCPError::OK; } MCPError loadSysProdConfig() { StackArray<UCItem, 11> items; auto config = phys_addrof(sData->sysProdConfig); items[0].name = "slc:sys_prod"; items[0].access = 0x510u; items[0].dataType = UCDataType::Complex; items[1].name = "slc:sys_prod.version"; items[1].access = 0x510u; items[1].dataType = UCDataType::UnsignedInt; items[1].dataSize = 4u; items[1].data = phys_addrof(config->version); items[2].name = "slc:sys_prod.eeprom_version"; items[2].access = 0x510u; items[2].dataType = UCDataType::UnsignedShort; items[2].dataSize = 2u; items[2].data = phys_addrof(config->eeprom_version); items[3].name = "slc:sys_prod.product_area"; items[3].access = 0x510u; items[3].dataType = UCDataType::UnsignedInt; items[3].dataSize = 4u; items[3].data = phys_addrof(config->product_area); items[4].name = "slc:sys_prod.game_region"; items[4].access = 0x510u; items[4].dataType = UCDataType::UnsignedInt; items[4].dataSize = 4u; items[4].data = phys_addrof(config->game_region); items[5].name = "slc:sys_prod.ntsc_pal"; items[5].access = 0x510u; items[5].dataType = UCDataType::String; items[5].dataSize = 5u; items[5].data = phys_addrof(config->ntsc_pal); items[6].name = "slc:sys_prod.5ghz_country_code"; items[6].access = 0x510u; items[6].dataType = UCDataType::String; items[6].dataSize = 4u; items[6].data = phys_addrof(config->wifi_5ghz_country_code); items[7].name = "slc:sys_prod.5ghz_country_code_revision"; items[7].access = 0x510u; items[7].dataType = UCDataType::UnsignedByte; items[7].dataSize = 1u; items[7].data = phys_addrof(config->wifi_5ghz_country_code_revision); items[8].name = "slc:sys_prod.code_id"; items[8].access = 0x510u; items[8].dataType = UCDataType::String; items[8].dataSize = 8u; items[8].data = phys_addrof(config->code_id); items[9].name = "slc:sys_prod.serial_id"; items[9].access = 0x510u; items[9].dataType = UCDataType::String; items[9].dataSize = 12u; items[9].data = phys_addrof(config->serial_id); items[10].name = "slc:sys_prod.model_number"; items[10].access = 0x510u; items[10].dataType = UCDataType::String; items[10].dataSize = 16u; items[10].data = phys_addrof(config->model_number); auto error = readConfigItems(items, items.size()); if (error < MCPError::OK || config->version <= 4) { // Factory reset items std::memset(config.get(), 0, sizeof(SysProdConfig)); config->version = 5u; config->eeprom_version = uint16_t { 1 }; config->code_id = "FEM"; config->serial_id = std::to_string(100000000 | 0xDECAF); config->model_number = "WUP-101(03)"; // This probably varies per region but I do not know the values config->wifi_5ghz_country_code_revision = uint8_t { 24 }; config->ntsc_pal = "NTSC"; switch (decaf::config()->system.region) { case decaf::SystemRegion::Japan: config->game_region = MCPRegion::Japan; config->product_area = MCPRegion::Japan; config->wifi_5ghz_country_code = "JP3"; break; case decaf::SystemRegion::USA: config->game_region = MCPRegion::USA; config->product_area = MCPRegion::USA; config->wifi_5ghz_country_code = "US"; break; case decaf::SystemRegion::China: config->game_region = MCPRegion::China; config->product_area = MCPRegion::China; config->wifi_5ghz_country_code = "CN"; break; case decaf::SystemRegion::Korea: config->game_region = MCPRegion::Korea; config->product_area = MCPRegion::Korea; config->wifi_5ghz_country_code = "KR"; break; case decaf::SystemRegion::Taiwan: config->game_region = MCPRegion::Taiwan; config->product_area = MCPRegion::Taiwan; config->wifi_5ghz_country_code = "TW"; break; case decaf::SystemRegion::Europe: default: config->game_region = MCPRegion::Europe; config->product_area = MCPRegion::Europe; config->wifi_5ghz_country_code = "EU"; config->ntsc_pal = "PAL"; break; } // Try save them to file deleteConfigItems(items, 1); writeConfigItems(items, items.size()); } return MCPError::OK; } phys_ptr<RtcConfig> getRtcConfig() { return phys_addrof(sData->rtcConfig); } phys_ptr<SystemConfig> getSystemConfig() { return phys_addrof(sData->systemConfig); } phys_ptr<SysProdConfig> getSysProdConfig() { return phys_addrof(sData->sysProdConfig); } void initialiseStaticConfigData() { sData = allocProcessStatic<StaticConfigData>(); } } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_config.h ================================================ #pragma once #include "ios_mcp_enum.h" #include "ios/auxil/ios_auxil_config.h" #include <common/structsize.h> #include <libcpu/be2_struct.h> namespace ios::mcp::internal { #pragma pack(push, 1) struct RtcConfig { be2_val<uint32_t> version; be2_val<uint32_t> rtc_offset; }; CHECK_OFFSET(RtcConfig, 0x00, version); CHECK_OFFSET(RtcConfig, 0x04, rtc_offset); CHECK_SIZE(RtcConfig, 0x08); struct SystemConfig { be2_val<uint32_t> version; be2_val<uint32_t> cmdFlags; be2_val<uint64_t> default_os_id; be2_val<uint64_t> default_title_id; struct { be2_val<uint32_t> enable; be2_val<uint32_t> max_size; } log; struct { be2_val<uint32_t> enable; } standby; struct { be2_val<uint32_t> cache_user_code; be2_val<uint32_t> max_file_size; be2_val<uint32_t> cache_delay_ms; } ramdisk; be2_val<uint32_t> simulated_ppc_mem2_size; be2_val<uint32_t> dev_mode; be2_val<uint64_t> prev_title_id; be2_val<uint64_t> prev_os_id; be2_val<uint32_t> default_app_type; be2_array<char, 16> default_device_type; be2_val<uint32_t> default_device_index; be2_val<uint32_t> fast_relaunch_value; be2_val<uint64_t> default_eco_title_id; }; CHECK_OFFSET(SystemConfig, 0x00, version); CHECK_OFFSET(SystemConfig, 0x04, cmdFlags); CHECK_OFFSET(SystemConfig, 0x08, default_os_id); CHECK_OFFSET(SystemConfig, 0x10, default_title_id); CHECK_OFFSET(SystemConfig, 0x18, log.enable); CHECK_OFFSET(SystemConfig, 0x1C, log.max_size); CHECK_OFFSET(SystemConfig, 0x20, standby.enable); CHECK_OFFSET(SystemConfig, 0x24, ramdisk.cache_user_code); CHECK_OFFSET(SystemConfig, 0x28, ramdisk.max_file_size); CHECK_OFFSET(SystemConfig, 0x2C, ramdisk.cache_delay_ms); CHECK_OFFSET(SystemConfig, 0x30, simulated_ppc_mem2_size); CHECK_OFFSET(SystemConfig, 0x34, dev_mode); CHECK_OFFSET(SystemConfig, 0x38, prev_title_id); CHECK_OFFSET(SystemConfig, 0x40, prev_os_id); CHECK_OFFSET(SystemConfig, 0x48, default_app_type); CHECK_OFFSET(SystemConfig, 0x4C, default_device_type); CHECK_OFFSET(SystemConfig, 0x5C, default_device_index); CHECK_OFFSET(SystemConfig, 0x60, fast_relaunch_value); CHECK_OFFSET(SystemConfig, 0x64, default_eco_title_id); CHECK_SIZE(SystemConfig, 0x6C); struct SysProdConfig { be2_val<MCPRegion> product_area; be2_val<uint16_t> eeprom_version; PADDING(2); be2_val<MCPRegion> game_region; UNKNOWN(4); be2_array<char, 5> ntsc_pal; //! Actually 5ghz_country_code, but can't start a variable with a number!! be2_array<char, 4> wifi_5ghz_country_code; //! Actually 5ghz_country_code_revision, but can't start a variable with a number!! be2_val<uint8_t> wifi_5ghz_country_code_revision; be2_array<char, 8> code_id; be2_array<char, 12> serial_id; UNKNOWN(4); be2_array<char, 16> model_number; be2_val<uint32_t> version; }; CHECK_OFFSET(SysProdConfig, 0x00, product_area); CHECK_OFFSET(SysProdConfig, 0x04, eeprom_version); CHECK_OFFSET(SysProdConfig, 0x08, game_region); CHECK_OFFSET(SysProdConfig, 0x10, ntsc_pal); CHECK_OFFSET(SysProdConfig, 0x15, wifi_5ghz_country_code); CHECK_OFFSET(SysProdConfig, 0x19, wifi_5ghz_country_code_revision); CHECK_OFFSET(SysProdConfig, 0x1A, code_id); CHECK_OFFSET(SysProdConfig, 0x22, serial_id); CHECK_OFFSET(SysProdConfig, 0x32, model_number); CHECK_OFFSET(SysProdConfig, 0x42, version); CHECK_SIZE(SysProdConfig, 0x46); #pragma pack(pop) MCPError translateUCError(auxil::UCError error); MCPError readConfigItems(phys_ptr<auxil::UCItem> items, uint32_t count); MCPError writeConfigItems(phys_ptr<auxil::UCItem> items, uint32_t count); MCPError deleteConfigItems(phys_ptr<auxil::UCItem> items, uint32_t count); MCPError loadRtcConfig(); MCPError loadSystemConfig(); MCPError loadSysProdConfig(); phys_ptr<RtcConfig> getRtcConfig(); phys_ptr<SystemConfig> getSystemConfig(); phys_ptr<SysProdConfig> getSysProdConfig(); void initialiseStaticConfigData(); } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_enum.h ================================================ #ifndef IOS_MCP_ENUM_H #define IOS_MCP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(mcp) ENUM_BEG(MainThreadCommand, uint32_t) ENUM_VALUE(SysEvent, 0x101) ENUM_END(MainThreadCommand) ENUM_BEG(MCPAppType, uint32_t) ENUM_VALUE(Unk0x0800000E, 0x0800000E) ENUM_END(MCPAppType) ENUM_BEG(MCPCommand, uint32_t) ENUM_VALUE(GetEnvironmentVariable, 0x20) ENUM_VALUE(GetSysProdSettings, 0x40) ENUM_VALUE(SetSysProdSettings, 0x41) ENUM_VALUE(GetOwnTitleInfo, 0x4C) ENUM_VALUE(TitleCount, 0x4D) ENUM_VALUE(DeviceList, 0x4E) ENUM_VALUE(CloseTitle, 0x50) ENUM_VALUE(SwitchTitle, 0x51) ENUM_VALUE(PrepareTitle0x52, 0x52) ENUM_VALUE(LoadFile, 0x53) ENUM_VALUE(GetFileLength, 0x57) ENUM_VALUE(SearchTitleList, 0x58) ENUM_VALUE(PrepareTitle0x59, 0x59) ENUM_VALUE(GetLaunchParameters, 0x5A) ENUM_VALUE(GetTitleId, 0x5B) ENUM_VALUE(UpdateGetProgress, 0x92) ENUM_VALUE(UpdateCheckResume, 0x96) ENUM_VALUE(UpdateCheckContext, 0x99) ENUM_END(MCPCommand) ENUM_BEG(MCPCountryCode, uint32_t) ENUM_VALUE(USA, 0x31) ENUM_VALUE(UnitedKingdom, 0x63) ENUM_END(MCPCountryCode) FLAGS_BEG(MCPDeviceFlags, uint32_t) FLAGS_VALUE(Unk1, 1 << 0) FLAGS_VALUE(Unk2, 1 << 1) FLAGS_VALUE(Unk4, 1 << 2) FLAGS_VALUE(Unk8, 1 << 3) FLAGS_END(MCPDeviceFlags) ENUM_BEG(MCPError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Invalid, -0x40001) ENUM_VALUE(System, -0x40002) ENUM_VALUE(Alloc, -0x40003) ENUM_VALUE(Opcode, -0x40004) ENUM_VALUE(InvalidParam, -0x40005) ENUM_VALUE(InvalidType, -0x40006) ENUM_VALUE(Unsupported, -0x40007) ENUM_VALUE(NonLeafNode, -0x4000A) ENUM_VALUE(KeyNotFound, -0x4000B) ENUM_VALUE(Modify, -0x4000C) ENUM_VALUE(StringTooLong, -0x4000D) ENUM_VALUE(RootKeysDiffer, -0x4000E) ENUM_VALUE(InvalidLocation, -0x4000F) ENUM_VALUE(BadComment, -0x40010) ENUM_VALUE(ReadAccess, -0x40011) ENUM_VALUE(WriteAccess, -0x40012) ENUM_VALUE(CreateAccess, -0x40013) ENUM_VALUE(FileSysName, -0x40014) ENUM_VALUE(FileSysInit, -0x40015) ENUM_VALUE(FileSysMount, -0x40016) ENUM_VALUE(FileOpen, -0x40017) ENUM_VALUE(FileStat, -0x40018) ENUM_VALUE(FileRead, -0x40019) ENUM_VALUE(FileWrite, -0x4001A) ENUM_VALUE(FileTooBig, -0x4001B) ENUM_VALUE(FileRemove, -0x4001C) ENUM_VALUE(FileRename, -0x4001D) ENUM_VALUE(FileClose, -0x4001E) ENUM_VALUE(FileSeek, -0x4001F) ENUM_VALUE(MalformedXML, -0x40022) ENUM_VALUE(Version, -0x40023) ENUM_VALUE(NoIpcBuffers, -0x40024) ENUM_VALUE(FileLockNeeded, -0x40026) ENUM_VALUE(SysProt, -0x40032) ENUM_VALUE(AlreadyOpen, -0x40054) ENUM_VALUE(StorageFull, -0x40055) ENUM_VALUE(WriteProtected, -0x40056) ENUM_VALUE(DataCorrupted, -0x40057) ENUM_VALUE(KernelErrorBase, -0x403E8) ENUM_END(MCPError) ENUM_BEG(MCPFileType, uint32_t) //! Load from the process's code directory (process title id)/code/%s ENUM_VALUE(ProcessCode, 0x00) //! Load from the CafeOS directory (00050010-1000400A)/code/%s ENUM_VALUE(CafeOS, 0x01) //! Load from the shared data content directory (0005001B-10042400)/content/%s ENUM_VALUE(SharedDataContent, 0x02) //! Load from the shared data code directory (0005001B-10042400)/code/%s ENUM_VALUE(SharedDataCode, 0x03) ENUM_END(MCPFileType) ENUM_BEG(MCPResourcePermissions, uint32_t) ENUM_VALUE(AddOnContent, 0x80) ENUM_VALUE(SciErrorLog, 0x200) ENUM_END(MCPResourcePermissions) ENUM_BEG(MCPRegion, uint32_t) ENUM_VALUE(Japan, 0x01) ENUM_VALUE(USA, 0x02) ENUM_VALUE(Europe, 0x04) ENUM_VALUE(Unknown8, 0x08) ENUM_VALUE(China, 0x10) ENUM_VALUE(Korea, 0x20) ENUM_VALUE(Taiwan, 0x40) ENUM_END(MCPRegion) FLAGS_BEG(MCPTitleListSearchFlags, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(TitleId, 1 << 0) FLAGS_VALUE(AppType, 1 << 2) FLAGS_VALUE(IndexedDevice, 1 << 6) FLAGS_VALUE(Unk0x60, 1 << 8) FLAGS_VALUE(Path, 1 << 9) FLAGS_VALUE(UniqueId, 1 << 10) FLAGS_END(MCPTitleListSearchFlags) ENUM_BEG(PMCommand, uint32_t) ENUM_VALUE(GetResourceManagerId, 0xE0) ENUM_VALUE(RegisterResourceManager, 0xE1) ENUM_END(PMCommand) ENUM_BEG(PPCAppCommand, uint32_t) ENUM_VALUE(StartupEvent, 0xB0) ENUM_VALUE(PowerOff, 0xB2) ENUM_VALUE(UnrecoverableError, 0xB3) ENUM_END(PPCAppCommand) ENUM_BEG(ResourceManagerCommand, int32_t) ENUM_VALUE(Timeout, -32) // ios::Error::Timeout ENUM_VALUE(ResumeReply, 8) // ios::Command::Reply ENUM_VALUE(Register, 16) ENUM_END(ResourceManagerCommand) ENUM_BEG(ResourceManagerRegistrationState, uint32_t) ENUM_VALUE(Invalid, 0) ENUM_VALUE(Registered, 1) ENUM_VALUE(NotRegistered, 2) ENUM_VALUE(Resumed, 3) ENUM_VALUE(Suspended, 4) ENUM_VALUE(Pending, 5) ENUM_VALUE(Failed, 6) ENUM_END(ResourceManagerRegistrationState) ENUM_BEG(SystemFileSys, uint32_t) ENUM_VALUE(Nand, 1) ENUM_VALUE(Pcfs, 2) ENUM_VALUE(SdCard, 3) ENUM_END(SystemFileSys) ENUM_NAMESPACE_EXIT(mcp) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_MCP_ENUM_H ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_ipc.cpp ================================================ #include "ios_mcp_ipc.h" #include "ios_mcp_enum.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/ios_stackobject.h" namespace ios::mcp { constexpr auto DeviceNameLength = 32u; using namespace kernel; static phys_ptr<void> allocIpcData(uint32_t size) { auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size); if (buffer) { std::memset(buffer.get(), 0, size); } return buffer; } static void freeIpcData(phys_ptr<void> data) { IOS_HeapFree(CrossProcessHeapId, data); } Error MCP_Open() { return IOS_Open("/dev/mcp", OpenMode::None); } Error MCP_Close(MCPHandle handle) { return IOS_Close(handle); } Error MCP_RegisterResourceManager(std::string_view device, kernel::MessageQueueId queue) { StackObject<uint32_t> resourceManagerId; auto error = IOS_Open("/dev/pm", OpenMode::None); if (error < Error::OK) { return error; } // Alloc a cross process buffer to copy the name to auto handle = static_cast<ResourceHandleId>(error); auto nameBuffer = phys_cast<char *>(allocIpcData(DeviceNameLength + 1)); if (!nameBuffer) { error = Error::FailAlloc; goto out; } // Ensure device name does not exceed DeviceNameLength if (device.size() >= 32) { device.remove_suffix(device.size() - DeviceNameLength); } // Copy device to ipc buffer std::copy(device.begin(), device.end(), nameBuffer.get()); nameBuffer[device.size()] = char { 0 }; // Send ioctl to get the manager id error = IOS_Ioctl(handle, PMCommand::GetResourceManagerId, nameBuffer, DeviceNameLength + 1, nullptr, 0); if (error < Error::OK) { goto out; } *resourceManagerId = static_cast<uint32_t>(error); // Register resource manager with kernel error = IOS_RegisterResourceManager(device, queue); if (error < Error::OK) { goto out; } // Register resource manager with MCP error = IOS_Ioctl(handle, PMCommand::RegisterResourceManager, resourceManagerId, sizeof(uint32_t), nullptr, 0); out: if (nameBuffer) { freeIpcData(nameBuffer); } IOS_Close(handle); return error; } } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_ipc.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_resourcemanager.h" namespace ios::mcp { using MCPHandle = kernel::ResourceHandleId; Error MCP_Open(); Error MCP_Close(MCPHandle handle); Error MCP_RegisterResourceManager(std::string_view device, kernel::MessageQueueId messageQueueId); } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp.h ================================================ #pragma once #include "ios_mcp_enum.h" #include "ios_mcp_mcp_request.h" #include "ios_mcp_mcp_response.h" #include "ios_mcp_mcp_types.h" ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_device.cpp ================================================ #include "ios_mcp_config.h" #include "ios_mcp_enum.h" #include "ios_mcp_mcp_device.h" #include "ios_mcp_mcp_types.h" #include "ios_mcp_mcp_request.h" #include "ios_mcp_mcp_response.h" #include "ios_mcp_mcp_thread.h" #include "ios_mcp_title.h" #include "cafe/libraries/cafe_hle.h" #include "decaf_config.h" #include "ios/ios.h" #include "ios/ios_stackobject.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "vfs/vfs_host_device.h" #include "vfs/vfs_virtual_device.h" #include <common/log.h> namespace ios::mcp::internal { using namespace ios::fs; using namespace ios::kernel; static std::string sCurrentLoadFilePath = { }; static FSAHandle sCurrentLoadFileHandle = { }; static size_t sCurrentLoadFileSize = 0u; MCPError mcpDeviceList(phys_ptr<const MCPRequestDeviceList> request, phys_ptr<MCPDevice> deviceList, uint32_t deviceListSizeBytes) { auto deviceListCount = deviceListSizeBytes / sizeof(MCPDevice); auto devicesAdded = 0; auto config = decaf::config(); auto deviceId = 0u; /* Example device list from my console: path type fs flags index uid /vol/storage_usb01, usb, wfs, 0xF, 1 7 /vol/storage_bt00, bt, "", 0x3, 0 6 /vol/storage_drh00, drh, "", 0x3, 0 5 /vol/storage_slccmpt01, slccmpt, isfs, 0x7, 1 4 /vol/storage_slc01, slc, isfs, 0xF, 1 3 /vol/storage_sdcard01, sdcard, fat, 0x7, 1 2 /vol/storage_ramdisk01, ramdisk, wfs, 0x7, 1 1 /vol/storage_mlc01, mlc, wfs, 0xF, 1 0 Only mlc/ramdisk/usb had unk0x08 set to some sort of 7 character unique identifier looking string. */ if (devicesAdded < deviceListCount && (request->flags & MCPDeviceFlags::Unk1) && !config->system.mlc_path.empty()) { auto &device = deviceList[devicesAdded++]; std::memset(std::addressof(device), 0, sizeof(MCPDevice)); device.type = "mlc"; device.filesystem = "wfs"; device.flags = MCPDeviceFlags::Unk1 | MCPDeviceFlags::Unk2 | MCPDeviceFlags::Unk4 | MCPDeviceFlags::Unk8; device.index = 1u; device.path = "/vol/storage_mlc01"; device.uid = deviceId++; } if (devicesAdded < deviceListCount && (request->flags & MCPDeviceFlags::Unk1) && !config->system.slc_path.empty()) { auto &device = deviceList[devicesAdded++]; std::memset(std::addressof(device), 0, sizeof(MCPDevice)); device.type = "slc"; device.filesystem = "isfs"; device.flags = MCPDeviceFlags::Unk1 | MCPDeviceFlags::Unk2 | MCPDeviceFlags::Unk4 | MCPDeviceFlags::Unk8; device.index = 1u; device.path = "/vol/storage_slc01"; device.uid = deviceId++; } return static_cast<MCPError>(devicesAdded); } MCPError mcpGetFileLength(phys_ptr<const MCPRequestGetFileLength> request) { auto path = std::string { }; auto name = std::string_view { phys_addrof(request->name).get() }; if (request->fileType == MCPFileType::CafeOS) { if (std::find(decaf::config()->system.lle_modules.begin(), decaf::config()->system.lle_modules.end(), name) == decaf::config()->system.lle_modules.end()) { auto library = cafe::hle::getLibrary(name); if (library) { auto &rpl = library->getGeneratedRpl(); return static_cast<MCPError>(rpl.size()); } } } switch (request->fileType) { case MCPFileType::ProcessCode: path = fmt::format("/vol/code/{}", name); break; case MCPFileType::CafeOS: path = fmt::format("/vol/storage_mlc01/sys/title/00050010/1000400A/code/{}", name); break; case MCPFileType::SharedDataCode: path = fmt::format("/vol/storage_mlc01/sys/title/0005001B/10042400/code/{}", name); break; case MCPFileType::SharedDataContent: path = fmt::format("/vol/storage_mlc01/sys/title/0005001B/10042400/content/{}", name); break; default: return static_cast<MCPError>(Error::InvalidArg); } StackObject<FSAStat> stat; auto error = FSAGetInfoByQuery(getFsaHandle(), path, FSAQueryInfoType::Stat, stat); if (error < FSAStatus::OK) { return static_cast<MCPError>(error); } return static_cast<MCPError>(stat->size); } MCPError mcpGetSysProdSettings(phys_ptr<MCPResponseGetSysProdSettings> response) { std::memcpy(phys_addrof(response->settings).get(), getSysProdConfig().get(), sizeof(MCPSysProdSettings)); return MCPError::OK; } MCPError mcpGetTitleId(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<MCPResponseGetTitleId> response) { response->titleId = resourceRequest->requestData.titleId; return MCPError::OK; } MCPError mcpLoadFile(phys_ptr<const MCPRequestLoadFile> request, phys_ptr<void> outputBuffer, uint32_t outputBufferLength) { auto path = std::string { }; auto name = std::string_view { phys_addrof(request->name).get() }; if (request->fileType == MCPFileType::CafeOS) { if (std::find(decaf::config()->system.lle_modules.begin(), decaf::config()->system.lle_modules.end(), name) == decaf::config()->system.lle_modules.end()) { auto library = cafe::hle::getLibrary(name); if (library) { auto &rpl = library->getGeneratedRpl(); auto bytesRead = std::min<uint32_t>(static_cast<uint32_t>(rpl.size() - request->pos), outputBufferLength); std::memcpy(outputBuffer.get(), rpl.data() + request->pos, bytesRead); return static_cast<MCPError>(bytesRead); } } } switch (request->fileType) { case MCPFileType::ProcessCode: path = fmt::format("/vol/code/{}", name); break; case MCPFileType::CafeOS: path = fmt::format("/vol/system_slc/title/00050010/1000400A/code/{}", name); break; case MCPFileType::SharedDataCode: path = fmt::format("/vol/storage_mlc01/sys/title/0005001B/10042400/code/{}", name); break; case MCPFileType::SharedDataContent: path = fmt::format("/vol/storage_mlc01/sys/title/0005001B/10042400/content/{}", name); break; default: return static_cast<MCPError>(Error::InvalidArg); } auto fsaHandle = getFsaHandle(); if (path != sCurrentLoadFilePath) { auto fileHandle = FSAHandle { }; // Open a new file auto error = FSAOpenFile(fsaHandle, path, "r", &fileHandle); if (error < 0) { return static_cast<MCPError>(error); } StackObject<FSAStat> stat; error = FSAStatFile(fsaHandle, fileHandle, stat); if (error < 0) { FSACloseFile(fsaHandle, fileHandle); return static_cast<MCPError>(error); } sCurrentLoadFileSize = stat->size; sCurrentLoadFileHandle = fileHandle; sCurrentLoadFilePath = path; } auto error = FSAReadFileWithPos(fsaHandle, outputBuffer, 1, outputBufferLength, request->pos, sCurrentLoadFileHandle, FSAReadFlag::None); if (error < 0 || request->pos + error >= sCurrentLoadFileSize) { FSACloseFile(fsaHandle, sCurrentLoadFileHandle); sCurrentLoadFileSize = 0u; sCurrentLoadFileHandle = static_cast<FSAHandle>(Error::Invalid); sCurrentLoadFilePath.clear(); } return static_cast<MCPError>(error); } static bool checkExistenceUsingOpenDir(std::string_view path) { auto fsaHandle = getFsaHandle(); auto dirHandle = FSADirHandle { -1 }; auto result = FSAOpenDir(fsaHandle, path, &dirHandle); if (result == FSAStatus::OK) { FSACloseDir(fsaHandle, dirHandle); return true; } if (result == FSAStatus::NotDir) { return true; } return false; } static bool checkExistence(std::string_view path) { StackObject<FSAStat> stat; auto result = FSAGetStat(getFsaHandle(), path, stat); if (result == FSAStatus::OK) { return true; } if (result == FSAStatus::PermissionError) { return checkExistenceUsingOpenDir(path); } return false; } MCPError mcpPrepareTitle52(phys_ptr<const MCPRequestPrepareTitle> request, phys_ptr<MCPResponsePrepareTitle> response) { auto titleInfoBuffer = getPrepareTitleInfoBuffer(); auto titleId = request->titleId; if (titleId == DefaultTitleId) { StackObject<MCPTitleAppXml> appXml; if (auto error = readTitleAppXml(appXml); error < MCPError::OK) { titleInfoBuffer = getPrepareTitleInfoBuffer(); std::memset(titleInfoBuffer.get(), 0x0, sizeof(MCPPPrepareTitleInfo)); titleInfoBuffer->permissions[0].group = static_cast<uint32_t>(ResourcePermissionGroup::All); titleInfoBuffer->permissions[0].mask = 0xFFFFFFFFFFFFFFFFull; return error; } titleInfoBuffer->titleId = appXml->title_id; titleInfoBuffer->groupId = appXml->group_id; titleId = appXml->title_id; } // TODO: When we have title switching we will need to read the title id and // mount the correct title to /vol - until then libdecaf already mounted it. auto error = readTitleCosXml(titleInfoBuffer); if (error < MCPError::OK) { // If there is no cos.xml then let's grant full permissions std::memset(titleInfoBuffer.get(), 0x0, sizeof(MCPPPrepareTitleInfo)); titleInfoBuffer->permissions[0].group = static_cast<uint32_t>(ResourcePermissionGroup::All); titleInfoBuffer->permissions[0].mask = 0xFFFFFFFFFFFFFFFFull; } else { // If there is cos.xml but it doesn't have any permissions then grant full permissions bool havePermissions = false; for (auto i = 0u; i <= 18; ++i) { if (titleInfoBuffer->permissions[i].group != 0 || titleInfoBuffer->permissions[i].mask != 0) { havePermissions = true; break; } } if (!havePermissions) { titleInfoBuffer->permissions[0].group = static_cast<uint32_t>(ResourcePermissionGroup::All); titleInfoBuffer->permissions[0].mask = 0xFFFFFFFFFFFFFFFFull; } } // Try mount updates for the title auto titleIdLo = titleId & 0xFFFFFFFF; auto titleUpdatePath = fmt::format("/vol/storage_mlc01/usr/title/0005000e/{:08x}", titleIdLo); if (checkExistence(titleUpdatePath)) { gLog->info("Title update found at {}", titleUpdatePath); auto processInfo = FSAProcessInfo { }; processInfo.groupId = titleInfoBuffer->groupId; processInfo.titleId = titleInfoBuffer->titleId; processInfo.processId = ios::ProcessId::COSKERNEL; // TODO: Use correct process id. auto fsaHandle = getFsaHandle(); auto mountResult = FSAMountWithProcess(fsaHandle, titleUpdatePath + "/code", "/vol/code", FSAMountPriority::TitleUpdate, &processInfo, nullptr, 0); if (mountResult) { gLog->warn("Error mounting update path {}/code to /vol/code", titleUpdatePath); } else { gLog->info("Mounted update {}/code to /vol/code", titleUpdatePath); } mountResult = FSAMountWithProcess(fsaHandle, titleUpdatePath + "/content", "/vol/content", FSAMountPriority::TitleUpdate, &processInfo, nullptr, 0); if (mountResult) { gLog->warn("Error mounting update path {}/content to /vol/content", titleUpdatePath + "/content"); } else { gLog->info("Mounted update {}/content to /vol/content", titleUpdatePath); } mountResult = FSAMountWithProcess(fsaHandle, titleUpdatePath + "/meta", "/vol/meta", FSAMountPriority::TitleUpdate, &processInfo, nullptr, 0); if (mountResult) { gLog->warn("Error mounting update path {}/meta to /vol/meta", titleUpdatePath + "/meta"); } else { gLog->info("Mounted update {}/meta to /vol/meta", titleUpdatePath); } } else { gLog->info("No title update found at {}", titleUpdatePath); } // Return result std::memcpy(phys_addrof(response->titleInfo).get(), titleInfoBuffer.get(), sizeof(MCPPPrepareTitleInfo)); std::memset(phys_addrof(response->titleInfo.permissions).get(), 0, sizeof(response->titleInfo.permissions)); return MCPError::OK; } MCPError mcpSwitchTitle(phys_ptr<const MCPRequestSwitchTitle> request) { auto titleInfoBuffer = getPrepareTitleInfoBuffer(); auto processId = static_cast<ProcessId>(ProcessId::COSKERNEL + request->cafeProcessId); auto sdCardPermissions = vfs::NoPermissions; // Apply title permissions for (auto &permission : titleInfoBuffer->permissions) { if (!permission.group) { break; } IOS_SetClientCapabilities(processId, permission.group, phys_addrof(permission.mask)); if (permission.group == ResourcePermissionGroup::FS || permission.group == ResourcePermissionGroup::All) { if (permission.mask & FSResourcePermissions::SdCardRead) { sdCardPermissions = sdCardPermissions | vfs::OtherRead; } if (permission.mask & FSResourcePermissions::SdCardWrite) { sdCardPermissions = sdCardPermissions | vfs::OtherWrite; } } } IOS_SetProcessTitle(processId, titleInfoBuffer->titleId, titleInfoBuffer->groupId); // Mount sdcard if title has permissions if (sdCardPermissions != vfs::NoPermissions) { auto filesystem = ios::getFileSystem(); filesystem->mountDevice({}, "/dev/sdcard01", std::make_shared<vfs::HostDevice>(decaf::config()->system.sdcard_path)); filesystem->setPermissions({}, "/dev/sdcard01", sdCardPermissions); } std::memset(titleInfoBuffer.get(), 0xFF, sizeof(MCPPPrepareTitleInfo)); return MCPError::OK; } MCPError mcpUpdateCheckContext(phys_ptr<MCPResponseUpdateCheckContext> response) { // TODO: Implement mcpUpdateCheckContext std::memset(response.get(), 0, sizeof(MCPResponseUpdateCheckContext)); return MCPError::OK; } MCPError mcpUpdateCheckResume(phys_ptr<MCPResponseUpdateCheckResume> response) { // TODO: Implement mcpUpdateCheckResume std::memset(response.get(), 0, sizeof(MCPResponseUpdateCheckResume)); return MCPError::OK; } MCPError mcpUpdateGetProgress(phys_ptr<MCPResponseUpdateProgress> response) { // TODO: Implement mcpUpdateGetProgress std::memset(response.get(), 0, sizeof(MCPResponseUpdateProgress)); return MCPError::OK; } } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_device.h ================================================ #pragma once #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios_mcp_enum.h" #include "ios_mcp_mcp_response.h" #include "ios_mcp_mcp_request.h" namespace ios::mcp::internal { MCPError mcpDeviceList(phys_ptr<const MCPRequestDeviceList> request, phys_ptr<MCPDevice> deviceList, uint32_t deviceListSizeBytes); MCPError mcpGetFileLength(phys_ptr<const MCPRequestGetFileLength> request); MCPError mcpGetSysProdSettings(phys_ptr<MCPResponseGetSysProdSettings> response); MCPError mcpGetTitleId(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<MCPResponseGetTitleId> response); MCPError mcpLoadFile(phys_ptr<const MCPRequestLoadFile> request, phys_ptr<void> outputBuffer, uint32_t outputBufferLength); MCPError mcpPrepareTitle52(phys_ptr<const MCPRequestPrepareTitle> request, phys_ptr<MCPResponsePrepareTitle> response); MCPError mcpSwitchTitle(phys_ptr<const MCPRequestSwitchTitle> request); MCPError mcpUpdateCheckContext(phys_ptr<MCPResponseUpdateCheckContext> response); MCPError mcpUpdateCheckResume(phys_ptr<MCPResponseUpdateCheckResume> response); MCPError mcpUpdateGetProgress(phys_ptr<MCPResponseUpdateProgress> response); } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_request.h ================================================ #pragma once #include "ios_mcp_enum.h" #include "ios_mcp_mcp_types.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::mcp { /** * \ingroup ios_dev_mcp * @{ */ #pragma pack(push, 1) struct MCPRequestDeviceList { be2_val<uint32_t> flags; }; CHECK_OFFSET(MCPRequestDeviceList, 0x00, flags); CHECK_SIZE(MCPRequestDeviceList, 0x04); struct MCPRequestGetFileLength { UNKNOWN(0x14); be2_val<MCPFileType> fileType; be2_val<uint32_t> unk0x18; UNKNOWN(0x28 - 0x1C); be2_array<char, 64> name; PADDING(0x12D8 - 0x68); }; CHECK_OFFSET(MCPRequestGetFileLength, 0x14, fileType); CHECK_OFFSET(MCPRequestGetFileLength, 0x18, unk0x18); CHECK_OFFSET(MCPRequestGetFileLength, 0x28, name); CHECK_SIZE(MCPRequestGetFileLength, 0x12D8); struct MCPRequestGetOwnTitleInfo { be2_val<uint32_t> unk0x00; }; CHECK_OFFSET(MCPRequestGetOwnTitleInfo, 0x00, unk0x00); CHECK_SIZE(MCPRequestGetOwnTitleInfo, 0x04); struct MCPRequestLoadFile { UNKNOWN(0x10); be2_val<uint32_t> pos; be2_val<MCPFileType> fileType; be2_val<uint32_t> cafeProcessId; UNKNOWN(0xC); be2_array<char, 0x40> name; UNKNOWN(0x12D8 - 0x68); }; CHECK_OFFSET(MCPRequestLoadFile, 0x10, pos); CHECK_OFFSET(MCPRequestLoadFile, 0x14, fileType); CHECK_OFFSET(MCPRequestLoadFile, 0x18, cafeProcessId); CHECK_OFFSET(MCPRequestLoadFile, 0x28, name); CHECK_SIZE(MCPRequestLoadFile, 0x12D8); struct MCPRequestPrepareTitle { be2_val<MCPTitleId> titleId; UNKNOWN(0x60); be2_array<char, 4096> argvStr; PADDING(0x12D8 - 0x1068); }; CHECK_OFFSET(MCPRequestPrepareTitle, 0x00, titleId); CHECK_OFFSET(MCPRequestPrepareTitle, 0x68, argvStr); CHECK_SIZE(MCPRequestPrepareTitle, 0x12D8); struct MCPRequestSearchTitleList { be2_struct<MCPTitleListType> searchTitle; be2_val<MCPTitleListSearchFlags> searchFlags; }; CHECK_OFFSET(MCPRequestSearchTitleList, 0x00, searchTitle); CHECK_OFFSET(MCPRequestSearchTitleList, 0x61, searchFlags); CHECK_SIZE(MCPRequestSearchTitleList, 0x65); struct MCPRequestSwitchTitle { UNKNOWN(0x18); be2_val<uint32_t> cafeProcessId; be2_val<phys_addr> dataStart; be2_val<phys_addr> codeEnd; be2_val<phys_addr> codeGenStart; PADDING(0x12D8 - 0x28); }; CHECK_OFFSET(MCPRequestSwitchTitle, 0x18, cafeProcessId); CHECK_OFFSET(MCPRequestSwitchTitle, 0x1C, dataStart); CHECK_OFFSET(MCPRequestSwitchTitle, 0x20, codeEnd); CHECK_OFFSET(MCPRequestSwitchTitle, 0x24, codeGenStart); CHECK_SIZE(MCPRequestSwitchTitle, 0x12D8); #pragma pack(pop) /** @} */ } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_response.h ================================================ #pragma once #include "ios_mcp_enum.h" #include "ios_mcp_mcp_types.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::mcp { /** * \ingroup ios_mcp * @{ */ #pragma pack(push, 1) struct MCPResponseGetTitleId { be2_val<uint64_t> titleId; }; CHECK_OFFSET(MCPResponseGetTitleId, 0x00, titleId); CHECK_SIZE(MCPResponseGetTitleId, 8); struct MCPResponseGetOwnTitleInfo { be2_struct<MCPTitleListType> titleInfo; }; CHECK_OFFSET(MCPResponseGetOwnTitleInfo, 0x00, titleInfo); CHECK_SIZE(MCPResponseGetOwnTitleInfo, 0x61); struct MCPResponseGetSysProdSettings { be2_struct<MCPSysProdSettings> settings; }; CHECK_OFFSET(MCPResponseGetSysProdSettings, 0x00, settings); CHECK_SIZE(MCPResponseGetSysProdSettings, 0x46); struct MCPResponsePrepareTitle { UNKNOWN(0x68); be2_struct<MCPPPrepareTitleInfo> titleInfo; }; CHECK_OFFSET(MCPResponsePrepareTitle, 0x68, titleInfo); CHECK_SIZE(MCPResponsePrepareTitle, 0x12D8); struct MCPResponseUpdateProgress { be2_struct<MCPUpdateProgress> progress; }; CHECK_SIZE(MCPResponseUpdateProgress, 0x38); struct MCPResponseUpdateCheckContext { be2_val<uint32_t> result; }; CHECK_OFFSET(MCPResponseUpdateCheckContext, 0x00, result); CHECK_SIZE(MCPResponseUpdateCheckContext, 4); struct MCPResponseUpdateCheckResume { be2_val<uint32_t> result; }; CHECK_OFFSET(MCPResponseUpdateCheckResume, 0x00, result); CHECK_SIZE(MCPResponseUpdateCheckResume, 4); #pragma pack(pop) /** @} */ } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_thread.cpp ================================================ #include "ios_mcp.h" #include "ios_mcp_config.h" #include "ios_mcp_enum.h" #include "ios_mcp_mcp_device.h" #include "ios_mcp_mcp_thread.h" #include "ios_mcp_mcp_request.h" #include "ios_mcp_mcp_response.h" #include "ios_mcp_pm_thread.h" #include "ios/ios_stackobject.h" #include "ios/auxil/ios_auxil_config.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_debug.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include <common/log.h> namespace ios::mcp::internal { constexpr auto McpThreadStackSize = 0x8000u; constexpr auto McpThreadPriority = 123u; constexpr auto MaxNumMessages = 200u; constexpr auto MaxNumMcpHandles = 256u; using MCPHandle = int32_t; using namespace fs; using namespace kernel; struct StaticMcpThreadData { be2_val<uint32_t> systemMode; be2_val<FSAHandle> fsaHandle; be2_val<ThreadId> threadId; be2_array<uint8_t, McpThreadStackSize> threadStack; be2_array<Message, MaxNumMessages> messageBuffer; be2_array<uint8_t, MaxNumMcpHandles / 8> handleOpenBitset; be2_array<char, 0x100> cafeTitlePath; }; static phys_ptr<StaticMcpThreadData> sData; ios::Handle getFsaHandle() { return sData->fsaHandle; } static MCPError mcpIoctl(phys_ptr<ResourceRequest> request) { auto error = MCPError::OK; auto &ioctl = request->requestData.args.ioctl; switch (static_cast<MCPCommand>(request->requestData.args.ioctl.request)) { case MCPCommand::DeviceList: if (ioctl.inputBuffer && ioctl.inputLength == sizeof(MCPRequestDeviceList) && ioctl.outputBuffer && ioctl.outputLength >= sizeof(MCPDevice)) { error = mcpDeviceList(phys_cast<const MCPRequestDeviceList *>(ioctl.inputBuffer), phys_cast<MCPDevice *>(ioctl.outputBuffer), ioctl.outputLength); } else { error = MCPError::InvalidParam; } break; case MCPCommand::GetFileLength: if (ioctl.inputBuffer && ioctl.inputLength == sizeof(MCPRequestGetFileLength) && !ioctl.outputBuffer && !ioctl.outputLength) { error = mcpGetFileLength(phys_cast<const MCPRequestGetFileLength *>(ioctl.inputBuffer)); } else { error = MCPError::InvalidParam; } break; case MCPCommand::GetTitleId: if (ioctl.outputLength == sizeof(MCPResponseGetTitleId)) { error = mcpGetTitleId(request, phys_cast<MCPResponseGetTitleId *>(ioctl.outputBuffer)); } else { error = MCPError::InvalidParam; } break; case MCPCommand::LoadFile: if (ioctl.inputLength >= sizeof(MCPResponseGetTitleId) && ioctl.outputLength) { error = mcpLoadFile(phys_cast<const MCPRequestLoadFile *>(ioctl.inputBuffer), ioctl.outputBuffer, ioctl.outputLength); } else { error = MCPError::InvalidParam; } break; case MCPCommand::PrepareTitle0x52: if (ioctl.inputBuffer && ioctl.inputLength == sizeof(MCPRequestPrepareTitle) && ioctl.outputBuffer && ioctl.outputLength == sizeof(MCPResponsePrepareTitle)) { error = mcpPrepareTitle52(phys_cast<const MCPRequestPrepareTitle *>(ioctl.inputBuffer), phys_cast<MCPResponsePrepareTitle *>(ioctl.outputBuffer)); } else { error = MCPError::InvalidParam; } break; case MCPCommand::SwitchTitle: if (ioctl.inputBuffer && ioctl.inputLength == sizeof(MCPRequestSwitchTitle) && !ioctl.outputBuffer && !ioctl.outputLength) { error = mcpSwitchTitle(phys_cast<const MCPRequestSwitchTitle *>(ioctl.inputBuffer)); } else { error = MCPError::InvalidParam; } break; case MCPCommand::UpdateCheckContext: if (ioctl.outputBuffer && ioctl.outputLength == sizeof(MCPResponseUpdateCheckContext)) { error = mcpUpdateCheckContext(phys_cast<MCPResponseUpdateCheckContext *>(ioctl.outputBuffer)); } else { error = MCPError::InvalidParam; } break; case MCPCommand::UpdateCheckResume: if (ioctl.outputBuffer && ioctl.outputLength == sizeof(MCPResponseUpdateCheckResume)) { error = mcpUpdateCheckResume(phys_cast<MCPResponseUpdateCheckResume *>(ioctl.outputBuffer)); } else { error = MCPError::InvalidParam; } break; default: error = MCPError::Opcode; } return error; } static MCPError mcpIoctlv(phys_ptr<ResourceRequest> request) { auto error = MCPError::OK; auto &ioctlv = request->requestData.args.ioctlv; switch (static_cast<MCPCommand>(ioctlv.request)) { case MCPCommand::GetSysProdSettings: if (ioctlv.numVecIn == 0 && ioctlv.numVecOut == 1 && ioctlv.vecs[0].paddr && ioctlv.vecs[0].len == sizeof(MCPResponseGetSysProdSettings)) { error = mcpGetSysProdSettings(phys_cast<MCPResponseGetSysProdSettings *>(ioctlv.vecs[0].paddr)); } else { error = MCPError::InvalidParam; } break; case MCPCommand::UpdateGetProgress: if (ioctlv.numVecIn == 0 && ioctlv.numVecOut == 1 && ioctlv.vecs[0].paddr && ioctlv.vecs[0].len == sizeof(MCPResponseUpdateProgress)) { error = mcpUpdateGetProgress(phys_cast<MCPResponseUpdateProgress *>(ioctlv.vecs[0].paddr)); } else { error = MCPError::InvalidParam; } break; default: error = MCPError::Opcode; } return error; } static bool isOpenHandle(MCPHandle handle) { if (handle < 0) { return false; } auto handleIndex = handle >> 16; if (handleIndex >= MaxNumMcpHandles) { return false; } auto byteIndex = handleIndex / 8; auto bitIndex = handleIndex % 8; return sData->handleOpenBitset[byteIndex] & (1 << bitIndex); } static Error mcpOpen(ClientCapabilityMask caps) { auto handleIndex = -1; auto byteIndex = 0; auto bitIndex = 0; for (auto i = 0u; i < MaxNumMcpHandles; ++i) { byteIndex = i / 8; bitIndex = i % 8; if ((sData->handleOpenBitset[byteIndex] & (1 << bitIndex)) == 0) { handleIndex = i; break; } } if (handleIndex < 0) { return Error::Max; } // Set the open bit sData->handleOpenBitset[byteIndex] |= (1 << bitIndex); auto handle = (handleIndex << 16) | static_cast<int32_t>(caps & 0xFFFF); return static_cast<Error>(handle); } static Error mcpClose(MCPHandle handle) { auto handleIndex = handle >> 16; if (handleIndex >= MaxNumMcpHandles) { return Error::InvalidHandle; } // Clear the open bit auto byteIndex = handleIndex / 8; auto bitIndex = handleIndex % 8; sData->handleOpenBitset[byteIndex] &= ~(1 << bitIndex); return Error::OK; } static MCPError initialiseClientCaps() { StackObject<uint64_t> mask; struct { ProcessId pid; FeatureId fid; uint64_t mask; } caps[] = { { ProcessId::CRYPTO, 1, 0xFF }, { ProcessId::USB, 1, 0xF }, { ProcessId::USB, 0xC, 0xFFFFFFFFFFFFFFFF }, { ProcessId::USB, 9, 0xFFFFFFFFFFFFFFFF }, { ProcessId::USB, 0xB, 0x3300300 }, { ProcessId::FS, 0xB, 0x400000000F00 }, { ProcessId::FS, 0xD, 1 }, { ProcessId::FS, 0xC, 1 }, { ProcessId::PAD, 0xB, 0x101000 }, { ProcessId::PAD, 1, 0xF }, { ProcessId::PAD, 0xD, 0x11 }, { ProcessId::PAD, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::PAD, 0x18, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NET, 1, 0xF }, { ProcessId::NET, 8, 1 }, { ProcessId::NET, 0xB, 0x101B1001 }, { ProcessId::NET, 3, 3 }, { ProcessId::NET, 0xE, 0x10 }, { ProcessId::NET, 0x10, 0x800 }, { ProcessId::NET, 0x11, 8 }, { ProcessId::NET, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NET, 0x12, 0xF }, { ProcessId::NET, 0x14, 0xF }, { ProcessId::NET, 0xC, 1 }, { ProcessId::NET, 0x1A, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NET, 0xD, 0x11 }, { ProcessId::ACP, 0xB, 0xFFFFFFFFF33F3091 }, { ProcessId::ACP, 0xD, 0x11 }, { ProcessId::ACP, 1, 0xF }, { ProcessId::ACP, 0x12, 0xF }, { ProcessId::ACP, 0xF, 1 }, { ProcessId::ACP, 0x10, 0xFF }, { ProcessId::ACP, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::ACP, 0xE, 0x27CB }, { ProcessId::ACP, 0x11, 0xA }, { ProcessId::ACP, 0x14, 0xF }, { ProcessId::ACP, 9, 1 }, { ProcessId::NSEC, 0xB, 0x303300 }, { ProcessId::NSEC, 0xD, 1 }, { ProcessId::NSEC, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::FPD, 0xB, 0x3303000 }, { ProcessId::FPD, 0xD, 0x11 }, { ProcessId::FPD, 0x12, 0xF }, { ProcessId::FPD, 0xF, 3 }, { ProcessId::FPD, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::FPD, 0x14, 0xF }, { ProcessId::FPD, 0x16, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NIM, 0xB, 0x200303B3000 }, { ProcessId::NIM, 1, 0xF }, { ProcessId::NIM, 0xD, 0x15 }, { ProcessId::NIM, 0x12, 0x13 }, { ProcessId::NIM, 0xF, 3 }, { ProcessId::NIM, 0x11, 3 }, { ProcessId::NIM, 2, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NIM, 0x13, 0xFFFFFFFFFFFFFFFF }, { ProcessId::NIM, 0x14, 0xF }, { ProcessId::NIM, 0x16, 0xFFFFFFFFFFFFFFFF }, { ProcessId::AUXIL, 0xB, 0x3300300 }, { ProcessId::TEST, 1, 0xF }, { ProcessId::TEST, 3, 3 }, { ProcessId::TEST, 0xB, 0xFFFFFFFFF03FFF00 }, { ProcessId::TEST, 0xD, 1 }, { ProcessId::TEST, 0x16, 1 }, { ProcessId::COSKERNEL, 1, 0xFF00 }, { ProcessId::COSKERNEL, 3, 6 }, { ProcessId::COSKERNEL, 0xD, 1 }, { ProcessId::COSROOT, 1, 0xF00 }, { ProcessId::COSROOT, 3, 6 }, { ProcessId::COSROOT, 0xD, 0x11 }, }; for (auto &cap : caps) { *mask = cap.mask; IOS_SetClientCapabilities(cap.pid, cap.fid, mask); } return MCPError::OK; } static FSAStatus initialiseDirectories() { static const struct { const char *path; uint32_t changeOwnerArg0; uint32_t changeOwnerArg1; uint32_t changeOwnerArg3; ProcessId changeOwnerArg2; uint32_t mode; uint32_t sysModePcfs; uint32_t sysModeNand; uint32_t sysModeSdcard; int32_t makeQuotaSizeHi; uint32_t makeQuotaSizeLo; uint32_t flushBit; } sDirs[] = { { "/vol/system_slc/logs", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x1C4000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/rights", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/rights/ticket", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/title", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/import", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/security", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/config", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/proc", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/proc/acp", 0, 0x100000F6, 0, ProcessId::ACP, 0x600, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/proc/prefs", 0, 0x100000F5, 0, ProcessId::AUXIL, 0x600, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/system_slc/proc/usb", 0, 0x100000FA, 0, ProcessId::USB, 0x600, 0x1E4000, 0x1C4000, 0x40000, -1, 0xFFFFFFFC, 0 }, { "/vol/system_slc/tmp", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 2 }, { "/vol/storage_mlc01/sys", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, 0, 0xC0000000, 1 }, { "/vol/storage_mlc01/usr", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/sys/title", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/sys/config", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x144000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/sys/import", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/sys/update", 0, 0x100000F3, 0, ProcessId::NIM, 0x600, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/usr/tmp", 0, 0, 0, ProcessId::MCP, 0x666, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/usr/title", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/usr/import", 0, 0, 0, ProcessId::MCP, 0, 0x164000, 0x144000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/usr/save", 0, 0x100000F6, 0, ProcessId::ACP, 0x600, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/usr/boss", 0, 0x100000F6, 0, ProcessId::ACP, 0x600, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/storage_mlc01/usr/nsec", 0, 0x100000F4, 0, ProcessId::NSEC, 0x600, 0x164000, 0x144000, 0x40000, 0, 0xA00000, 1 }, { "/vol/storage_mlc01/usr/packages", 0, 0x100000F3, 0, ProcessId::NIM, 0x600, 0x164000, 0x44000, 0x40000, -1, 0xFFFFFFFC, 1 }, { "/vol/system_ram/cache", 0, 0, 0, ProcessId::MCP, 0, 0x1A4000, 0x184000, 0x140000, 0, 0x6400000, 4 }, { "/vol/system_ram/es", 0, 0, 0, ProcessId::MCP, 0, 0x1E4000, 0x1C4000, 0x140000, 0, 0x300000, 4 }, { "/vol/system_ram/config", 0, 0, 0, ProcessId::MCP, 0, 0x1A4000, 0x184000, 0x140000, -1, 0xFFFFFFFC, 4 }, { "/vol/system_ram/proc", 0, 0, 0, ProcessId::MCP, 0, 0x1A4000, 0x184000, 0x140000, -1, 0xFFFFFFFC, 4 }, { "/vol/system_ram/proc/cache", 0, 0, 0, ProcessId::MCP, 0x600, 0x1A0000, 0x180000, 0x140000, 0, 0xA00000, 4 }, { "/vol/system_ram/proc/fpd", 0, 0x100000F7, 0x400, ProcessId::FPD, 0x600, 0x1A4000, 0x184000, 0x140000, 0, 0x40000, 4 }, { "/vol/system_ram/proc/prefs", 0, 0x100000F5, 0, ProcessId::AUXIL, 0x600, 0x1A4000, 0x184000, 0x140000, 0, 0x100000, 4 }, { "/vol/system_ram/proc/usb", 0, 0x100000FA, 0, ProcessId::USB, 0x600, 0x1A4000, 0x184000, 0x140000, 0, 0x40000, 4 }, { "/vol/system_hfio/config", 0, 0, 0, ProcessId::MCP, 0, 0x1A0000, 0, 0, -1, 0xFFFFFFFC, 0 }, { "/vol/system_hfio/proc", 0, 0, 0, ProcessId::MCP, 0, 0x1A0000, 0, 0, -1, 0xFFFFFFFC, 0 }, { "/vol/system_hfio/proc/acp", 0, 0x100000F6, 0, ProcessId::ACP, 0x600, 0x1A0000, 0, 0, -1, 0xFFFFFFFC, 0 }, { "/vol/system_hfio/proc/usb", 0, 0x100000FA, 0, ProcessId::USB, 0x600, 0x1A0000, 0, 0, -1, 0xFFFFFFFC, 0 }, { "/vol/system_hfio/proc/prefs", 0, 0x100000F5, 0, ProcessId::AUXIL, 0x600, 0x1A0000, 0, 0, -1, 0xFFFFFFFC, 0 }, { "/vol/system_hfio/logs", 0, 0, 0, ProcessId::MCP, 0, 0x120000, 0, 0, -1, 0xFFFFFFFC, 0 }, }; StackObject<FSAStat> stat; auto flushBits = 0u; auto systemFileSys = getSystemFileSys(); auto systemModeFlags = getSystemModeFlags(); // TODO: Remove this hack which force creates all directories systemModeFlags |= 0xFFFFFFFF; for (auto &dir : sDirs) { auto dirSystemMode = dir.sysModeNand; if (systemFileSys == SystemFileSys::Pcfs) { dirSystemMode = dir.sysModePcfs; } else if (systemFileSys == SystemFileSys::SdCard) { dirSystemMode = dir.sysModeSdcard; } if (!(systemModeFlags & dirSystemMode)) { continue; } auto error = FSAGetInfoByQuery(sData->fsaHandle, dir.path, FSAQueryInfoType::Stat, stat); if (error == FSAStatus::NotFound) { if (dir.makeQuotaSizeHi == -1) { error = FSAMakeDir(sData->fsaHandle, dir.path, dir.mode); } else { auto quota = (static_cast<uint64_t>(dir.makeQuotaSizeHi) << 32) | dir.makeQuotaSizeLo; error = FSAMakeQuota(sData->fsaHandle, dir.path, dir.mode, quota); } if (error == FSAStatus::OK) { // TODO: FSAChangeOwner } } else { // TODO: FSAChangeMode } if (error < FSAStatus::OK) { return error; } flushBits |= dir.flushBit; } if (flushBits & 1) { // TODO: FSAFlushVolume "/vol/storage_mlc01" } if (flushBits & 2) { // TODO: FSAFlushVolume "/vol/system_slc" } if (flushBits & 4) { // TODO: FSAFlushVolume "/vol/system_ram" } return FSAStatus::OK; } static Error cleanTmpDirectories() { // TODO: Clean dirs in /vol/system_slc/tmp // TODO: Clean dirs in /vol/storage_mlc01/usr/tmp return Error::OK; } static MCPError mcpResume() { auto iosError = FSAOpen(); if (iosError < Error::OK) { gLog->error("Failed to open FSA handle"); return MCPError::Invalid; } sData->fsaHandle = static_cast<FSAHandle>(iosError); // Mount the system devices auto fsaStatus = FSAMount(sData->fsaHandle, "/dev/slc01", "/vol/system_slc", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /dev/slc01 to /vol/system_slc"); return MCPError::Invalid; } fsaStatus = FSAMount(sData->fsaHandle, "/dev/mlc01", "/vol/storage_mlc01", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /dev/mlc01 to /vol/storage_mlc01"); return MCPError::Invalid; } fsaStatus = FSAMount(sData->fsaHandle, "/dev/ramdisk01", "/vol/system_ram", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /dev/ramdisk01 to /vol/system_ram"); return MCPError::Invalid; } auto securityLevel = IOS_GetSecurityLevel(); if (getSystemFileSys() == SystemFileSys::Pcfs && securityLevel != SecurityLevel::Normal) { fsaStatus = FSAMount(sData->fsaHandle, "/dev/hfio01", "/vol/system_hfio", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /dev/hfio01 to /vol/system_hfio"); return MCPError::Invalid; } } // Cleanup tmp directories auto error = cleanTmpDirectories(); if (error < Error::OK) { return MCPError::Invalid; } // Initialise the default directories initialiseDirectories(); // Mount /vol/system if (getSystemFileSys() == SystemFileSys::Pcfs) { fsaStatus = FSAMount(sData->fsaHandle, "/vol/system_hfio", "/vol/system", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /vol/system_hfio to /vol/system"); return MCPError::Invalid; } } else { fsaStatus = FSAMount(sData->fsaHandle, "/vol/system_slc", "/vol/system", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /vol/system_slc to /vol/system"); return MCPError::Invalid; } } // Mount /vol/sys/proc fsaStatus = FSAMount(sData->fsaHandle, "/vol/system/proc", "/vol/sys/proc", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /vol/system/proc to /vol/sys/proc"); return MCPError::Invalid; } fsaStatus = FSAMount(sData->fsaHandle, "/vol/system_slc/proc", "/vol/sys/proc_slc", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /vol/system_slc/proc to /vol/sys/proc_slc"); return MCPError::Invalid; } fsaStatus = FSAMount(sData->fsaHandle, "/vol/system_ram/proc", "/vol/sys/proc_ram", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /vol/system_ram/proc to /vol/sys/proc_ram"); return MCPError::Invalid; } // Open FSA handle for config iosError = auxil::openFsaHandle(); if (iosError < Error::OK) { gLog->error("Failed to open config FSA handle"); return MCPError::Invalid; } // Load configs auto mcpError = loadRtcConfig(); if (mcpError < MCPError::OK) { gLog->error("Failed to initialise rtc config"); return mcpError; } mcpError = loadSystemConfig(); if (mcpError < MCPError::OK) { gLog->error("Failed to initialise system config"); return mcpError; } mcpError = loadSysProdConfig(); if (mcpError < MCPError::OK) { gLog->error("Failed to initialise sys_prod config"); return mcpError; } // Format the Cafe OS title path auto default_os_id = getSystemConfig()->default_os_id; sData->cafeTitlePath = fmt::format("/vol/system/title/{:08x}/{:08x}/code", default_os_id >> 32, default_os_id & 0xFFFFFFFF); // Init client caps mcpError = initialiseClientCaps(); if (mcpError < MCPError::OK) { gLog->error("Failed to initialise client capabilities"); return mcpError; } if (securityLevel == SecurityLevel::Debug || securityLevel == SecurityLevel::Test) { fsaStatus = FSAMount(sData->fsaHandle, "/dev/hfio01", "/vol/storage_hfiomlc01", 0, nullptr, 0); if (fsaStatus < FSAStatus::OK) { gLog->error("Failed to mount /dev/hfio01 to /vol/storage_hfiomlc01"); return MCPError::Invalid; } } if (getSystemFileSys() == SystemFileSys::Nand) { getSystemConfig()->ramdisk.cache_user_code = 1u; } return MCPError::OK; } static MCPError mcpSuspend() { // TODO: mcp suspend return MCPError::OK; } static Error mcpThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), sData->messageBuffer.size()); if (error < Error::OK) { return error; } auto messageQueueId = static_cast<MessageQueueId>(error); error = registerResourceManager("/dev/mcp", messageQueueId); if (error < Error::OK) { return error; } error = IOS_AssociateResourceManager("/dev/mcp", ResourcePermissionGroup::MCP); if (error < Error::OK) { return error; } while (true) { error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None); if (error < Error::OK) { break; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: if (!(request->requestData.args.open.caps & 1)) { IOS_ResourceReply(request, Error::Access); } else { error = mcpOpen(request->requestData.args.open.caps); IOS_ResourceReply(request, error); } break; case Command::Close: IOS_ResourceReply(request, mcpClose(request->requestData.handle)); break; case Command::Ioctl: if (!isOpenHandle(request->requestData.handle)) { IOS_ResourceReply(request, Error::InvalidHandle); } else { IOS_ResourceReply(request, static_cast<Error>(mcpIoctl(request))); } break; case Command::Ioctlv: if (!isOpenHandle(request->requestData.handle)) { IOS_ResourceReply(request, Error::InvalidHandle); } else { IOS_ResourceReply(request, static_cast<Error>(mcpIoctlv(request))); } break; case Command::Suspend: IOS_ResourceReply(request, static_cast<Error>(mcpSuspend())); break; case Command::Resume: IOS_ResourceReply(request, static_cast<Error>(mcpResume())); break; default: IOS_ResourceReply(request, Error::InvalidArg); } } return error; } Error startMcpThread() { auto error = IOS_CreateThread(&mcpThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), sData->threadStack.size(), McpThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } auto threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(threadId, "McpThread"); return IOS_StartThread(threadId); } void initialiseStaticMcpThreadData() { sData = allocProcessStatic<StaticMcpThreadData>(); } } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::mcp::internal { Error startMcpThread(); void initialiseStaticMcpThreadData(); ios::Handle getFsaHandle(); } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_mcp_types.h ================================================ #pragma once #include "ios_mcp_config.h" #include "ios_mcp_enum.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::mcp { /** * \ingroup ios_mcp * @{ */ #pragma pack(push, 1) using MCPSysProdSettings = internal::SysProdConfig; using MCPTitleId = uint64_t; constexpr auto DefaultTitleId = static_cast<MCPTitleId>(-3); struct MCPDevice { //! Device type e.g. mlc, slc, usb be2_array<char, 8> type; //! This seems to be some 7 character unique identifier which is only set //! for mlc, ramdisk, usb be2_array<char, 128> unk0x08; //! Filesystem e.g. wfs, fat, isfs be2_array<char, 8> filesystem; //! Path, e.g. /vol/storage_mlc01 be2_array<char, 0x27F> path; be2_val<MCPDeviceFlags> flags; //! Unique index - incrementing id in order of device added to system be2_val<uint32_t> uid; //! Device type index, e.g. 3 for /vol/storage_mlc03 be2_val<uint32_t> index; }; CHECK_OFFSET(MCPDevice, 0x00, type); CHECK_OFFSET(MCPDevice, 0x08, unk0x08); CHECK_OFFSET(MCPDevice, 0x88, filesystem); CHECK_OFFSET(MCPDevice, 0x90, path); CHECK_OFFSET(MCPDevice, 0x30F, flags); CHECK_OFFSET(MCPDevice, 0x313, uid); CHECK_OFFSET(MCPDevice, 0x317, index); CHECK_SIZE(MCPDevice, 0x31B); // Offsets of MCPTitleAppXml not verified. struct MCPTitleAppXml { be2_val<uint32_t> version; be2_val<uint64_t> os_version; be2_val<uint64_t> title_id; be2_val<uint16_t> title_version; be2_val<uint32_t> sdk_version; be2_val<uint32_t> app_type; be2_val<uint32_t> group_id; be2_array<uint8_t, 32> os_mask; be2_val<uint64_t> common_id; }; struct MCPPPrepareTitleInfo { struct Permission { be2_val<uint32_t> group; be2_val<uint64_t> mask; }; be2_val<uint32_t> version; UNKNOWN(8); be2_val<MCPTitleId> titleId; be2_val<uint32_t> groupId; be2_val<uint32_t> cmdFlags; be2_array<char, 4096> argstr; be2_array<virt_ptr<char>, 64> argv; be2_val<uint32_t> max_size; be2_val<uint32_t> avail_size; be2_val<uint32_t> codegen_size; be2_val<uint32_t> codegen_core; be2_val<uint32_t> max_codesize; be2_val<uint32_t> overlay_arena; be2_val<uint32_t> num_workarea_heap_blocks; be2_val<uint32_t> num_codearea_heap_blocks; be2_array<Permission, 19> permissions; be2_val<uint32_t> default_stack0_size; be2_val<uint32_t> default_stack1_size; be2_val<uint32_t> default_stack2_size; be2_val<uint32_t> default_redzone0_size; be2_val<uint32_t> default_redzone1_size; be2_val<uint32_t> default_redzone2_size; be2_val<uint32_t> exception_stack0_size; be2_val<uint32_t> exception_stack1_size; be2_val<uint32_t> exception_stack2_size; be2_val<uint32_t> sdkVersion; be2_val<uint32_t> titleVersion; UNKNOWN(0x1270 - 0x124C); }; CHECK_OFFSET(MCPPPrepareTitleInfo, 0x00, version); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x0C, titleId); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x14, groupId); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x18, cmdFlags); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1C, argstr); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x101C, argv); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x111C, max_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1120, avail_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1124, codegen_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1128, codegen_core); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x112C, max_codesize); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1130, overlay_arena); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1134, num_workarea_heap_blocks); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1138, num_codearea_heap_blocks); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1220, default_stack0_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1224, default_stack1_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1228, default_stack2_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x122C, default_redzone0_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1230, default_redzone1_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1234, default_redzone2_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1238, exception_stack0_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x123C, exception_stack1_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1240, exception_stack2_size); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1244, sdkVersion); CHECK_OFFSET(MCPPPrepareTitleInfo, 0x1248, titleVersion); CHECK_SIZE(MCPPPrepareTitleInfo, 0x1270); struct MCPTitleListType { be2_val<uint64_t> titleId; UNKNOWN(4); be2_array<char, 56> path; be2_val<MCPAppType> appType; UNKNOWN(0x54 - 0x48); be2_val<uint8_t> device; UNKNOWN(1); be2_array<char, 10> indexedDevice; be2_val<uint8_t> unk0x60; }; CHECK_OFFSET(MCPTitleListType, 0x00, titleId); CHECK_OFFSET(MCPTitleListType, 0x0C, path); CHECK_OFFSET(MCPTitleListType, 0x44, appType); CHECK_OFFSET(MCPTitleListType, 0x54, device); CHECK_OFFSET(MCPTitleListType, 0x56, indexedDevice); CHECK_OFFSET(MCPTitleListType, 0x60, unk0x60); CHECK_SIZE(MCPTitleListType, 0x61); struct MCPUpdateProgress { UNKNOWN(0x38); }; CHECK_SIZE(MCPUpdateProgress, 0x38); #pragma pack(pop) /** @} */ } // namespace ios::mcp ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_pm_thread.cpp ================================================ #include "ios_mcp_enum.h" #include "ios_mcp_pm_thread.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_timer.h" #include "ios/ios_stackobject.h" #include <array> #include <common/log.h> #include <libcpu/cpu_formatters.h> #include <memory> namespace ios::mcp::internal { using namespace kernel; using RegisteredResourceManagerId = int32_t; constexpr auto MaxNumRmQueueMessages = 0x80u; constexpr auto MaxNumResourceManagers = 0x56u; constexpr auto PmThreadStackSize = 0x2000u; constexpr auto PmThreadPriority = 124u; struct ResourceManagerRegistration { struct Data // Why another struct? Who knows... { be2_val<BOOL> isDummyRM; be2_val<ResourceHandleId> resourceHandle; be2_val<Error> error; be2_phys_ptr<IpcRequest> messageBuffer; be2_val<ResourceManagerRegistrationState> state; be2_val<uint32_t> systemModeFlags; be2_val<ProcessId> processId; be2_val<uint32_t> unk0x1C; be2_val<TimeMicroseconds64> timeResumeStart; be2_val<TimeMicroseconds64> timeResumeFinished; be2_val<TimeMicroseconds64> timeSuspendStart; be2_val<TimeMicroseconds64> timeSuspendFinished; be2_val<TimeMicroseconds64> timeOpenStart; be2_val<TimeMicroseconds64> timeOpenFinished; }; be2_phys_ptr<char> name; be2_val<uint32_t> unk0x04; Data data; }; CHECK_OFFSET(ResourceManagerRegistration, 0x00, name); CHECK_OFFSET(ResourceManagerRegistration, 0x04, unk0x04); CHECK_OFFSET(ResourceManagerRegistration, 0x08, data); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x00, isDummyRM); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x04, resourceHandle); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x08, error); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x0C, messageBuffer); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x10, state); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x14, systemModeFlags); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x18, processId); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x1C, unk0x1C); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x20, timeResumeStart); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x28, timeResumeFinished); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x30, timeSuspendStart); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x38, timeSuspendFinished); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x40, timeOpenStart); CHECK_OFFSET(ResourceManagerRegistration::Data, 0x48, timeOpenFinished); CHECK_SIZE(ResourceManagerRegistration::Data, 0x50); CHECK_SIZE(ResourceManagerRegistration, 0x58); struct StaticPmThreadData { be2_val<ThreadId> threadId; be2_array<uint8_t, PmThreadStackSize> threadStack; be2_array<ResourceManagerRegistration, MaxNumResourceManagers> resourceManagers; be2_val<MessageQueueId> resourceManagerMessageQueueId; be2_val<TimerId> resourceManagerTimerId; be2_struct<IpcRequest> resourceManagerTimeoutMessage; be2_array<Message, MaxNumRmQueueMessages> resourceManagerMessageBuffer; }; static phys_ptr<StaticPmThreadData> sPmThreadData; static Error getResourceManagerId(std::string_view name) { for (auto i = 0u; i < sPmThreadData->resourceManagers.size(); ++i) { auto &resourceManager = sPmThreadData->resourceManagers[i]; if (resourceManager.data.isDummyRM || !resourceManager.name) { continue; } if (name.compare(resourceManager.name.get()) == 0) { return static_cast<Error>(i); } } return Error::NoResource; } static Error sendRegisterResourceManagerMessage(RegisteredResourceManagerId id) { if (id < 0 || id >= static_cast<RegisteredResourceManagerId>(sPmThreadData->resourceManagers.size())) { return Error::InvalidArg; } auto &resourceManager = sPmThreadData->resourceManagers[id]; if (resourceManager.data.isDummyRM) { return Error::InvalidArg; } if (resourceManager.data.state != ResourceManagerRegistrationState::NotRegistered) { return Error::InvalidArg; } resourceManager.data.messageBuffer->command = static_cast<Command>(ResourceManagerCommand::Register); resourceManager.data.messageBuffer->handle = id; return IOS_SendMessage(sPmThreadData->resourceManagerMessageQueueId, makeMessage(resourceManager.data.messageBuffer), MessageFlags::None); } static Error pmIoctl(PMCommand command, phys_ptr<const void> inputBuffer, uint32_t inputLength, phys_ptr<void> outputBuffer, uint32_t outputLength) { auto error = Error::OK; switch (command) { case PMCommand::GetResourceManagerId: error = getResourceManagerId(phys_cast<const char *>(inputBuffer).get()); break; case PMCommand::RegisterResourceManager: error = sendRegisterResourceManagerMessage(*phys_cast<const RegisteredResourceManagerId *>(inputBuffer)); break; default: error = Error::InvalidArg; } return error; } static Error pmThreadEntry(phys_ptr<void> /*context*/) { StackArray<Message, 0x80u> messageBuffer; StackObject<Message> message; // Create message queue auto error = IOS_CreateMessageQueue(messageBuffer, 0x80u); if (error < Error::OK) { return error; } auto messageQueueId = static_cast<MessageQueueId>(error); error = IOS_RegisterResourceManager("/dev/pm", messageQueueId); if (error < Error::OK) { return error; } while (true) { error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None); if (error < Error::OK) { break; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: case Command::Close: IOS_ResourceReply(request, Error::OK); break; case Command::Ioctl: { error = pmIoctl(static_cast<PMCommand>(request->requestData.args.ioctl.request), request->requestData.args.ioctl.inputBuffer, request->requestData.args.ioctl.inputLength, request->requestData.args.ioctl.outputBuffer, request->requestData.args.ioctl.outputLength); IOS_ResourceReply(request, error); break; } default: IOS_ResourceReply(request, Error::InvalidArg); } } return error; } Error registerResourceManager(std::string_view device, MessageQueueId queue) { auto error = getResourceManagerId(device); if (error < Error::OK) { return error; } auto resourceManagerId = static_cast<RegisteredResourceManagerId>(error); error = IOS_RegisterResourceManager(device, queue); if (error < Error::OK) { return error; } return sendRegisterResourceManagerMessage(resourceManagerId); } Error startPmThread() { // Create resource manager message queue auto error = IOS_CreateMessageQueue(phys_addrof(sPmThreadData->resourceManagerMessageBuffer), sPmThreadData->resourceManagerMessageBuffer.size()); if (error < Error::OK) { return error; } sPmThreadData->resourceManagerMessageQueueId = static_cast<MessageQueueId>(error); // Create resource manager timeout timer error = IOS_CreateTimer(std::chrono::microseconds { 0 }, std::chrono::microseconds { 0 }, sPmThreadData->resourceManagerMessageQueueId, makeMessage(phys_addrof(sPmThreadData->resourceManagerTimeoutMessage))); if (error < Error::OK) { return error; } sPmThreadData->resourceManagerTimerId = static_cast<TimerId>(error); // Initialise resource manager registrations for (auto i = 0u; i < sPmThreadData->resourceManagers.size(); ++i) { auto &rm = sPmThreadData->resourceManagers[i]; rm.data.state = ResourceManagerRegistrationState::NotRegistered; rm.data.resourceHandle = static_cast<ResourceHandleId>(Error::Invalid); rm.data.error = Error::Invalid; if (!rm.data.isDummyRM) { auto buffer = IOS_HeapAlloc(CrossProcessHeapId, static_cast<uint32_t>(sizeof(IpcRequest))); if (!buffer) { return error; } rm.data.messageBuffer = phys_cast<IpcRequest *>(buffer); } } // Create thread error = IOS_CreateThread(&pmThreadEntry, nullptr, phys_addrof(sPmThreadData->threadStack) + sPmThreadData->threadStack.size(), static_cast<uint32_t>(sPmThreadData->threadStack.size()), PmThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } sPmThreadData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sPmThreadData->threadId, "PmThread"); return IOS_StartThread(sPmThreadData->threadId); } static Error waitAsyncReplyWithTimeout(MessageQueueId queue, phys_ptr<Message> message, TimeMicroseconds32 timeout) { if (timeout == -1) { return IOS_ReceiveMessage(queue, message, MessageFlags::None); } auto error = IOS_RestartTimer(sPmThreadData->resourceManagerTimerId, std::chrono::microseconds { timeout }, std::chrono::microseconds { 0 }); if (error < Error::OK) { return error; } error = IOS_ReceiveMessage(queue, message, MessageFlags::None); IOS_StopTimer(sPmThreadData->resourceManagerTimerId); return error; } /** * Handle all pending resource manager registrations. * * This code is needlessly complex, thanks Nint*ndo. I think I know what you * were trying to do - not overfill the message queue with pending messages * whilst at the same time trying to have multiple devices registering * asynchronously. Still - it's not great a way to do that is it. * * A basic summary is that we try to transition states for the RM as: * NotRegistered -> Registered -> Pending -> Resumed. * * The non-trivial logic is to handle all incoming messages on the message * queue whilst at the same time ensuring every resource manager gets * registered and resumed. */ Error handleResourceManagerRegistrations(uint32_t systemModeFlags, uint32_t bootFlags) { StackObject<Message> message; auto id = 0u; auto numPendingResumes = 0; auto error = Error::OK; while (id < sPmThreadData->resourceManagers.size()) { if (sPmThreadData->resourceManagers[id].data.isDummyRM) { auto &dummyRm = sPmThreadData->resourceManagers[id]; if (numPendingResumes == 0) { // We've processed all our resumes, on to the next resource manager id! IOS_GetUpTime64(phys_addrof(dummyRm.data.timeResumeStart)); ++id; continue; } } else { if (!sPmThreadData->resourceManagers[id].name) { // Skip this unimplemented resource. ++id; continue; } if ((sPmThreadData->resourceManagers[id].data.systemModeFlags & systemModeFlags) == 0) { // Skip this device if it is not enable for the current system mode. ++id; continue; } if (sPmThreadData->resourceManagers[id].data.state == ResourceManagerRegistrationState::Registered) { auto &rm = sPmThreadData->resourceManagers[id]; // Send a resume request - transition from Registered to Pending. IOS_GetUpTime64(phys_addrof(rm.data.timeResumeStart)); rm.data.state = ResourceManagerRegistrationState::Pending; rm.data.error = Error::Invalid; error = IOS_ResumeAsync(rm.data.resourceHandle, systemModeFlags, bootFlags, sPmThreadData->resourceManagerMessageQueueId, rm.data.messageBuffer); if (error < Error::OK) { gLog->error("Unexpected error for IOS_ResumeAsync on resource manager {}, error = {}", rm.name.get(), error); return error; } // Increase the number of pending resumes so we wait for the reply on // the next dummy manager. ++numPendingResumes; // Move onto the next resource manager ++id; continue; } } // Check for any pending messages error = waitAsyncReplyWithTimeout(sPmThreadData->resourceManagerMessageQueueId, message, 10000); if (error < Error::OK) { gLog->error("Unexpected error for waitAsyncReplyWithTimeout, error = {}", error); return error; } auto request = parseMessage<IpcRequest>(message); auto command = static_cast<ResourceManagerCommand>(request->command); if (command == ResourceManagerCommand::Timeout) { gLog->error("Unexpected timeout whilst waiting for resource manager message"); return Error::Timeout; } else if (command == ResourceManagerCommand::Register) { auto &rm = sPmThreadData->resourceManagers[request->handle]; // Open a resource handle IOS_GetUpTime64(phys_addrof(rm.data.timeOpenStart)); error = IOS_Open(rm.name.get(), static_cast<OpenMode>(0x80000000)); IOS_GetUpTime64(phys_addrof(rm.data.timeOpenFinished)); if (error < Error::OK) { gLog->error("Unexpected error for IOS_Open on resource manager {}, error = {}", rm.name.get(), error); return error; } // Transition from NotRegistered to Registered. decaf_check(rm.data.state == ResourceManagerRegistrationState::NotRegistered); rm.data.state = ResourceManagerRegistrationState::Registered; rm.data.resourceHandle = static_cast<ResourceHandleId>(error); } else if (command == ResourceManagerCommand::ResumeReply) { // This is a reply to our resume request - transition from Pending to Resumed. auto resumeId = request->handle; auto &resumeRm = sPmThreadData->resourceManagers[resumeId]; decaf_check(resumeRm.data.state == ResourceManagerRegistrationState::Pending); resumeRm.data.error = request->reply; resumeRm.data.state = ResourceManagerRegistrationState::Resumed; IOS_GetUpTime64(phys_addrof(resumeRm.data.timeResumeFinished)); if (request->reply < Error::OK) { resumeRm.data.state = ResourceManagerRegistrationState::Failed; gLog->error("Unexpected reply from IOS_ResumeAsync for resource manager {}, error = {}", resumeRm.name.get(), request->reply); return request->reply; } --numPendingResumes; decaf_check(numPendingResumes >= 0); } } return Error::OK; } void initialiseStaticPmThreadData() { sPmThreadData = allocProcessStatic<StaticPmThreadData>(); sPmThreadData->resourceManagerTimeoutMessage.command = static_cast<Command>(Error::Timeout); auto dummyRM = ResourceManagerRegistration { nullptr, 0u, ResourceManagerRegistration::Data { TRUE, ResourceHandleId { 0 }, Error::OK, nullptr, ResourceManagerRegistrationState::Invalid, 0u, ProcessId { 0 }, 0u, 0ull, 0ull, 0ull, 0ull, 0ull, 0ull } }; auto ss = [](const char *str) { return allocProcessStatic(str); }; auto rm = [](uint32_t systemModeFlags, ProcessId pid, uint32_t unk0x1C) { return ResourceManagerRegistration::Data { FALSE, ResourceHandleId { 0 }, Error::OK, nullptr, ResourceManagerRegistrationState::Invalid, systemModeFlags, pid, unk0x1C, 0ull, 0ull, 0ull, 0ull, 0ull, 0ull }; }; // This table was taken from firmware 5.5.1 sPmThreadData->resourceManagers = std::array<ResourceManagerRegistration, 86> { dummyRM, { ss("/dev/crypto"), 1u, rm(0x1E8000, ProcessId::CRYPTO, 0) }, { ss("/dev/ahcimgr"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, //{ ss("/dev/usbproc1"), 1u, rm(0x1C0000, ProcessId::USB, 0) }, dummyRM, //{ ss("/dev/usb_cdc"), 1u, rm(0x1C0000, ProcessId::USB, 0) }, dummyRM, //{ ss("/dev/testproc1"), 1u, rm(0x1C0000, ProcessId::TEST, 0) }, dummyRM, //{ ss("/dev/usb_syslog"), 0u, rm(0x1E8000, ProcessId::MCP, 0) }, { ss("/dev/mmc"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, //{ ss("/dev/odm"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, { ss("/dev/shdd"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, { ss("/dev/fla"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, //{ ss("/dev/dk"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, //{ ss("/dev/ramdisk_svc"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, //{ ss("/dev/dk_syslog"), 0u, rm(0x1E8000, ProcessId::MCP, 0) }, { ss("/dev/df"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, { ss("/dev/atfs"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, { ss("/dev/isfs"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, { ss("/dev/wfs"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, { ss("/dev/fat"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, { ss("/dev/rbfs"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, { ss("/dev/scfm"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, { ss("/dev/md"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, { ss("/dev/pcfs"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, { ss("/dev/mcp"), 1u, rm(0x1A8000, ProcessId::MCP, 0) }, //{ ss("/dev/mcp_recovery"), 0u, rm( 0x40000, ProcessId::MCP, 0) }, dummyRM, //{ ss("/dev/usbproc2"), 1u, rm(0x1C0000, ProcessId::USB, 0) }, { ss("/dev/usr_cfg"), 1u, rm(0x180000, ProcessId::AUXIL, 0) }, //{ ss("/dev/usb_hid"), 1u, rm(0x100000, ProcessId::USB, 0) }, //{ ss("/dev/usb_uac"), 1u, rm(0x100000, ProcessId::USB, 0) }, //{ ss("/dev/usb_midi"), 1u, rm(0x100000, ProcessId::USB, 0) }, dummyRM, { ss("/dev/ppc_kernel"), 1u, rm(0x180000, ProcessId::MCP, 0) }, //{ ss("/dev/ccr_io"), 1u, rm(0x1C8000, ProcessId::PAD, 0) }, { ss("/dev/usb/early_btrm"), 0u, rm(0x1C0000, ProcessId::PAD, 3) }, //{ ss("/dev/testproc2"), 1u, rm(0x1C0000, ProcessId::TEST, 0) }, dummyRM, { ss("/dev/ums"), 1u, rm(0x1C0000, ProcessId::USB, 0) }, // WTF?? Should be FS surely? //{ ss("/dev/wifi24"), 0u, rm(0x188000, ProcessId::PAD, 0) }, // WTF?? Should be NET surely? dummyRM, { ss("/dev/auxilproc"), 1u, rm(0x100000, ProcessId::AUXIL, 1) }, { ss("/dev/network"), 1u, rm(0x180000, ProcessId::NET, 0) }, dummyRM, { ss("/dev/nsec"), 1u, rm(0x180000, ProcessId::NET, 0) }, { ss("/dev/usb/btrm"), 0u, rm(0x1C0000, ProcessId::PAD, 1) }, { ss("/dev/acpproc"), 1u, rm(0x188000, ProcessId::ACP, 0) }, dummyRM, //{ ss("/dev/ifuds"), 1u, rm(0x100000, ProcessId::PAD, 0) }, // WTF?? Should be NET surely? //{ ss("/dev/udscntrl"), 1u, rm(0x100000, ProcessId::PAD, 0) }, // WTF?? Should be NET surely? dummyRM, { ss("/dev/nnsm"), 1u, rm(0x180000, ProcessId::ACP, 0) }, dummyRM, //{ ss("/dev/dlp"), 1u, rm(0x100000, ProcessId::NET, 0) }, dummyRM, { ss("/dev/ac_main"), 1u, rm(0x180000, ProcessId::NET, 1) }, dummyRM, { ss("/dev/tcp_pcfs"), 1u, rm(0x1E8000, ProcessId::FS, 0) }, dummyRM, { ss("/dev/act"), 1u, rm(0x180000, ProcessId::FPD, 1) }, dummyRM, //{ ss("/dev/fpd"), 1u, rm(0x180000, ProcessId::FPD, 1) }, dummyRM, { ss("/dev/acp_main"), 1u, rm(0x180000, ProcessId::ACP, 1) }, dummyRM, { ss("/dev/pdm"), 1u, rm(0x180000, ProcessId::ACP, 1) }, dummyRM, { ss("/dev/boss"), 1u, rm(0x180000, ProcessId::NIM, 1) }, dummyRM, { ss("/dev/nim"), 1u, rm(0x180000, ProcessId::NIM, 1) }, dummyRM, { ss("/dev/ndm"), 1u, rm(0x180000, ProcessId::NET, 1) }, dummyRM, //{ ss("/dev/emd"), 1u, rm(0x180000, ProcessId::ACP, 1) }, dummyRM, { ss("/dev/ppc_app"), 1u, rm(0x180000, ProcessId::MCP, 2) }, dummyRM, }; } } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_pm_thread.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include <string_view> namespace ios::mcp::internal { Error startPmThread(); Error registerResourceManager(std::string_view device, kernel::MessageQueueId queue); Error handleResourceManagerRegistrations(uint32_t systemModeFlags, uint32_t bootFlags); void initialiseStaticPmThreadData(); } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_ppc_thread.cpp ================================================ #include "ios_mcp_enum.h" #include "ios_mcp_pm_thread.h" #include "ios_mcp_ppc_thread.h" #include "cafe/kernel/cafe_kernel.h" #include "ios/ios_stackobject.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" namespace ios::mcp::internal { using namespace kernel; using RegisteredResourceManagerId = int32_t; constexpr auto MaxNumPpcMessages = 10u; constexpr auto PpcThreadStackSize = 0x2000u; constexpr auto PpcThreadPriority = 123u; constexpr auto PpcAppHandle = 0x707061; // 'ppa' constexpr auto PpcKernelHandle = 0x6E726B; // 'nrk' struct StaticPpcThreadData { be2_val<ThreadId> threadId; be2_array<uint8_t, PpcThreadStackSize> threadStack; }; static phys_ptr<StaticPpcThreadData> sPpcThreadData; static Error mcpPpcIoctl(MCPCommand command, phys_ptr<const void> inputBuffer, uint32_t inputLength, phys_ptr<void> outputBuffer, uint32_t outputLength) { auto error = Error::OK; auto ppcAppCommand = static_cast<PPCAppCommand>(command); switch (ppcAppCommand) { case PPCAppCommand::PowerOff: ios::kernel::internal::stopHardwareThread(); break; default: error = Error::InvalidArg; } return error; } static Error ppcThreadEntry(phys_ptr<void> /*context*/) { StackArray<Message, MaxNumPpcMessages> messageBuffer; StackObject<Message> message; // Create message queue auto error = IOS_CreateMessageQueue(messageBuffer, messageBuffer.size()); if (error < Error::OK) { return error; } auto messageQueueId = static_cast<MessageQueueId>(error); // Register devices error = registerResourceManager("/dev/ppc_app", messageQueueId); if (error < Error::OK) { return error; } error = registerResourceManager("/dev/ppc_kernel", messageQueueId); if (error < Error::OK) { return error; } while (true) { error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None); if (error < Error::OK) { break; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { auto name = std::string_view { request->requestData.args.open.name.get() }; error = Error::InvalidArg; if (name.compare("/dev/ppc_kernel") == 0) { error = static_cast<Error>(PpcKernelHandle); } else if (name.compare("/dev/ppc_app") == 0) { error = static_cast<Error>(PpcAppHandle); } IOS_ResourceReply(request, error); break; } case Command::Close: { IOS_ResourceReply(request, Error::OK); break; } case Command::Resume: { if (request->requestData.handle == PpcKernelHandle) { // TODO: Until we have proper permission initialisation in IOS for // CafeOS kernel let's just force all permission StackObject<uint64_t> mask; *mask = 0xFFFFFFFFFFFFFFFFull; IOS_SetClientCapabilities(ProcessId::COSKERNEL, ResourcePermissionGroup::All, mask); // Boot the PPC kernel! cafe::kernel::start(); } IOS_ResourceReply(request, Error::OK); break; } case Command::Ioctl: { error = mcpPpcIoctl(static_cast<MCPCommand>(request->requestData.args.ioctl.request), request->requestData.args.ioctl.inputBuffer, request->requestData.args.ioctl.inputLength, request->requestData.args.ioctl.outputBuffer, request->requestData.args.ioctl.outputLength); IOS_ResourceReply(request, error); } default: IOS_ResourceReply(request, Error::InvalidArg); } } return error; } Error startPpcThread() { // Create thread auto error = IOS_CreateThread(&ppcThreadEntry, nullptr, phys_addrof(sPpcThreadData->threadStack) + sPpcThreadData->threadStack.size(), static_cast<uint32_t>(sPpcThreadData->threadStack.size()), PpcThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } sPpcThreadData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sPpcThreadData->threadId, "PpcThread"); return IOS_StartThread(sPpcThreadData->threadId); } void initialiseStaticPpcThreadData() { sPpcThreadData = allocProcessStatic<StaticPpcThreadData>(); } } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_ppc_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::mcp::internal { Error startPpcThread(); void initialiseStaticPpcThreadData(); } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_title.cpp ================================================ #include "ios_mcp_config.h" #include "ios_mcp_title.h" #include "ios/ios_stackobject.h" #include "ios/auxil/ios_auxil_config.h" #include "ios/kernel/ios_kernel_process.h" #include <fmt/format.h> using namespace ios::auxil; using namespace ios::kernel; namespace ios::mcp::internal { struct StaticTitleData { be2_struct<MCPPPrepareTitleInfo> prepareTitleInfoBuffer; }; static phys_ptr<StaticTitleData> sTitleData = nullptr; static MCPError readTitleConfigItems(std::string_view path, phys_ptr<UCItem> items, uint32_t count) { return translateUCError(readItemsFromFile(path, items, count, nullptr)); } phys_ptr<MCPPPrepareTitleInfo> getPrepareTitleInfoBuffer() { return phys_addrof(sTitleData->prepareTitleInfoBuffer); } MCPError readTitleAppXml(phys_ptr<MCPTitleAppXml> titleInfo) { StackArray<UCItem, 10> items; items[0].name = "app"; items[0].access = 0x777u; items[0].dataType = UCDataType::Complex; items[1].name = "app.version"; items[1].dataType = UCDataType::UnsignedInt; items[1].dataSize = 4u; items[1].data = phys_addrof(titleInfo->version); items[2].name = "app.os_version"; items[2].dataType = UCDataType::HexBinary; items[2].dataSize = 8u; items[2].data = phys_addrof(titleInfo->os_version); items[3].name = "app.title_id"; items[3].dataType = UCDataType::HexBinary; items[3].dataSize = 8u; items[3].data = phys_addrof(titleInfo->title_id); items[4].name = "app.title_version"; items[4].dataType = UCDataType::HexBinary; items[4].dataSize = 2u; items[4].data = phys_addrof(titleInfo->title_version); items[5].name = "app.sdk_version"; items[5].dataType = UCDataType::UnsignedInt; items[5].dataSize = 4u; items[5].data = phys_addrof(titleInfo->sdk_version); items[6].name = "app.app_type"; items[6].dataType = UCDataType::UnsignedInt; items[6].dataSize = 4u; items[6].data = phys_addrof(titleInfo->app_type); items[7].name = "app.group_id"; items[7].dataType = UCDataType::UnsignedInt; items[7].dataSize = 4u; items[7].data = phys_addrof(titleInfo->group_id); items[8].name = "app.os_mask"; items[8].dataType = UCDataType::HexBinary; items[8].dataSize = 32u; items[8].data = phys_addrof(titleInfo->os_mask); items[9].name = "app.common_id"; items[9].dataType = UCDataType::HexBinary; items[9].dataSize = 8u; items[9].data = phys_addrof(titleInfo->common_id); auto error = readTitleConfigItems("/vol/code/app.xml", items, items.size()); if (error < MCPError::OK && error != MCPError::KeyNotFound) { // KeyNotFound is allowed because not all fields are required in xml return error; } return MCPError::OK; } MCPError readTitleCosXml(phys_ptr<MCPPPrepareTitleInfo> titleInfo) { StackArray<UCItem, 60> items; items[0].name = "app"; items[0].access = 0x777u; items[0].dataType = UCDataType::Complex; items[1].name = "app.version"; items[1].dataType = UCDataType::UnsignedInt; items[1].dataSize = 4u; items[1].data = phys_addrof(titleInfo->version); items[2].name = "app.cmdFlags"; items[2].dataType = UCDataType::UnsignedInt; items[2].dataSize = 4u; items[2].data = phys_addrof(titleInfo->cmdFlags); items[3].name = "app.argstr"; items[3].dataType = UCDataType::String; items[3].dataSize = 4096u; items[3].data = phys_addrof(titleInfo->argstr); items[4].name = "app.max_size"; items[4].dataType = UCDataType::HexBinary; items[4].dataSize = 4u; items[4].data = phys_addrof(titleInfo->max_size); items[5].name = "app.avail_size"; items[5].dataType = UCDataType::HexBinary; items[5].dataSize = 4u; items[5].data = phys_addrof(titleInfo->avail_size); items[6].name = "app.codegen_size"; items[6].dataType = UCDataType::HexBinary; items[6].dataSize = 4u; items[6].data = phys_addrof(titleInfo->codegen_size); items[7].name = "app.codegen_core"; items[7].dataType = UCDataType::HexBinary; items[7].dataSize = 4u; items[7].data = phys_addrof(titleInfo->codegen_core); items[8].name = "app.max_codesize"; items[8].dataType = UCDataType::HexBinary; items[8].dataSize = 4u; items[8].data = phys_addrof(titleInfo->max_codesize); items[9].name = "app.overlay_arena"; items[9].dataType = UCDataType::HexBinary; items[9].dataSize = 4u; items[9].data = phys_addrof(titleInfo->overlay_arena); items[10].name = "app.num_workarea_heap_blocks"; items[10].dataType = UCDataType::UnsignedInt; items[10].dataSize = 4u; items[10].data = phys_addrof(titleInfo->num_workarea_heap_blocks); items[11].name = "app.num_codearea_heap_blocks"; items[11].dataType = UCDataType::UnsignedInt; items[11].dataSize = 4u; items[11].data = phys_addrof(titleInfo->num_codearea_heap_blocks); items[12].name = "app.permissions"; items[12].dataType = UCDataType::Complex; for (auto i = 0u; i <= 18; ++i) { auto &mask = items[13 + (i * 2)]; *fmt::format_to(phys_addrof(mask.name).get(), "app.permissions.p{}.mask", i) = char { 0 }; mask.dataType = UCDataType::HexBinary; mask.dataSize = 8u; mask.data = phys_addrof(titleInfo->permissions[i].mask); auto &group = items[14 + (i * 2)]; *fmt::format_to(phys_addrof(group.name).get(), "app.permissions.p{}.group", i) = char { 0 }; group.dataType = UCDataType::UnsignedInt; group.dataSize = 4u; group.data = phys_addrof(titleInfo->permissions[i].group); } items[51].name = "app.default_stack0_size"; items[51].dataType = UCDataType::HexBinary; items[51].dataSize = 4u; items[51].data = phys_addrof(titleInfo->default_stack0_size); items[52].name = "app.default_stack1_size"; items[52].dataType = UCDataType::HexBinary; items[52].dataSize = 4u; items[52].data = phys_addrof(titleInfo->default_stack1_size); items[53].name = "app.default_stack2_size"; items[53].dataType = UCDataType::HexBinary; items[53].dataSize = 4u; items[53].data = phys_addrof(titleInfo->default_stack2_size); items[54].name = "app.default_redzone0_size"; items[54].dataType = UCDataType::HexBinary; items[54].dataSize = 4u; items[54].data = phys_addrof(titleInfo->default_redzone0_size); items[55].name = "app.default_redzone1_size"; items[55].dataType = UCDataType::HexBinary; items[55].dataSize = 4u; items[55].data = phys_addrof(titleInfo->default_redzone1_size); items[56].name = "app.default_redzone2_size"; items[56].dataType = UCDataType::HexBinary; items[56].dataSize = 4u; items[56].data = phys_addrof(titleInfo->default_redzone2_size); items[57].name = "app.exception_stack0_size"; items[57].dataType = UCDataType::HexBinary; items[57].dataSize = 4u; items[57].data = phys_addrof(titleInfo->exception_stack0_size); items[58].name = "app.exception_stack1_size"; items[58].dataType = UCDataType::HexBinary; items[58].dataSize = 4u; items[58].data = phys_addrof(titleInfo->exception_stack1_size); items[59].name = "app.exception_stack2_size"; items[59].dataType = UCDataType::HexBinary; items[59].dataSize = 4u; items[59].data = phys_addrof(titleInfo->exception_stack2_size); auto error = readTitleConfigItems("/vol/code/cos.xml", items, items.size()); if (error < MCPError::OK && error != MCPError::KeyNotFound) { // KeyNotFound is allowed because not all fields are required in xml return error; } return MCPError::OK; } void initialiseTitleStaticData() { sTitleData = allocProcessStatic<StaticTitleData>(); } } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/mcp/ios_mcp_title.h ================================================ #pragma once #include "ios_mcp_enum.h" #include "ios_mcp_mcp_types.h" namespace ios::mcp::internal { phys_ptr<MCPPPrepareTitleInfo> getPrepareTitleInfoBuffer(); MCPError readTitleAppXml(phys_ptr<MCPTitleAppXml> titleInfo); MCPError readTitleCosXml(phys_ptr<MCPPPrepareTitleInfo> titleInfo); void initialiseTitleStaticData(); } // namespace ios::mcp::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net.cpp ================================================ #include "ios_net.h" #include "ios_net_ac_main_server.h" #include "ios_net_log.h" #include "ios_net_ndm_server.h" #include "ios_net_subsys.h" #include "ios_net_socket_async_task.h" #include "ios_net_socket_thread.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/ios_stackobject.h" #include <common/log.h> using namespace ios::kernel; namespace ios::net { using namespace kernel; using namespace mcp; constexpr auto LocalHeapSize = 0x40000u; constexpr auto CrossHeapSize = 0xC0000u; constexpr auto NumNetworkMessages = 10u; struct StaticNetData { be2_val<bool> subsysStarted; }; static phys_ptr<StaticNetData> sData = nullptr; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger netLog = { }; static void initialiseStaticData() { sData = allocProcessStatic<StaticNetData>(); sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } static Error networkLoop() { StackArray<Message, NumNetworkMessages> messageBuffer; StackObject<Message> message; // Create message queue auto error = IOS_CreateMessageQueue(messageBuffer, messageBuffer.size()); if (error < Error::OK) { netLog->error("NET: Failed to create message queue, error = {}.", error); return error; } auto messageQueueId = static_cast<MessageQueueId>(error); // Register resource manager error = MCP_RegisterResourceManager("/dev/network", messageQueueId); if (error < Error::OK) { netLog->error("NET: Failed to register resource manager for /dev/network, error = {}.", error); return error; } while (true) { error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: case Command::Close: IOS_ResourceReply(request, Error::OK); break; case Command::Resume: if (!sData->subsysStarted) { error = internal::startSubsys(); } else { error = Error::OK; } IOS_ResourceReply(request, Error::OK); break; case Command::Suspend: if (sData->subsysStarted) { error = internal::stopSubsys(); } else { error = Error::OK; } IOS_ResourceReply(request, Error::OK); break; default: IOS_ResourceReply(request, Error::InvalidArg); } } IOS_DestroyMessageQueue(messageQueueId); return Error::OK; } } // namespace internal Error processEntryPoint(phys_ptr<void> /* context */) { // Initialise logger internal::netLog = decaf::makeLogger("IOS_NET"); // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticAcMainServerData(); internal::initialiseStaticNdmServerData(); internal::initialiseStaticSocketData(); internal::initialiseStaticSocketAsyncTaskData(); internal::initialiseStaticSubsysData(); // Initialise process heaps auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { internal::netLog->error("NET: Failed to create local process heap, error = {}.", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { internal::netLog->error("NET: Failed to create cross process heap, error = {}.", error); return error; } // TODO: bspGetClockInfo // TODO: initIoBuf // TODO: start wifi24 thread (/dev/wifi24) // TODO: start uds threads (/dev/ifuds /dev/udscntrl) // TODO: start nn servers for /dev/dlp error = internal::startNdmServer(); if (error < Error::OK) { internal::netLog->error("NET: Failed to start ndm server, error = {}.", error); return error; } error = internal::startAcMainServer(); if (error < Error::OK) { internal::netLog->error("NET: Failed to start ac_main server, error = {}.", error); return error; } error = internal::startSocketAsyncTaskThread(); if (error < Error::OK) { internal::netLog->error("NET: Failed to initialise socket async task thread, error = {}.", error); return error; } error = internal::initSubsys(); if (error < Error::OK) { internal::netLog->error("NET: Failed to initialise subsystem, error = {}.", error); return error; } error = internal::networkLoop(); if (error < Error::OK) { internal::netLog->error("NET: networkLoop returned error = {}.", error); return error; } error = internal::joinAcMainServer(); if (error < Error::OK) { internal::netLog->error("NET: Failed to join ac_main server, error = {}.", error); return error; } return IOS_SuspendThread(IOS_GetCurrentThreadId()); } } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::net { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_ac_main_server.cpp ================================================ #include "ios_net_log.h" #include "ios_net_ac_main_server.h" #include "ios_net_ac_service.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" namespace ios::net::internal { using namespace kernel; constexpr auto AcMainNumMessages = 0x64u; constexpr auto AcMainThreadStackSize = 0x2000u; constexpr auto AcMainThreadPriority = 50u; class AcMainServer : public nn::ipc::Server { public: AcMainServer() : nn::ipc::Server(true) { } }; struct StaticAcMainServerData { be2_struct<AcMainServer> server; be2_array<Message, AcMainNumMessages> messageBuffer; be2_array<uint8_t, AcMainThreadStackSize> threadStack; }; static phys_ptr<StaticAcMainServerData> sAcMainServerData = nullptr; Error startAcMainServer() { auto &server = sAcMainServerData->server; auto result = server.initialise("/dev/ac_main", phys_addrof(sAcMainServerData->messageBuffer), static_cast<uint32_t>(sAcMainServerData->messageBuffer.size())); if (result.failed()) { netLog->error( "startAcMainServer: Server initialisation failed for /dev/ac_main, result = {}", result.code()); return Error::FailInternal; } server.registerService<AcService>(); result = server.start(phys_addrof(sAcMainServerData->threadStack) + sAcMainServerData->threadStack.size(), static_cast<uint32_t>(sAcMainServerData->threadStack.size()), AcMainThreadPriority); if (result.failed()) { netLog->error( "startAcMainServer: Server start failed for /dev/ac_main, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } Error joinAcMainServer() { sAcMainServerData->server.join(); return Error::OK; } void initialiseStaticAcMainServerData() { sAcMainServerData = allocProcessStatic<StaticAcMainServerData>(); } } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_ac_main_server.h ================================================ #pragma once #include "ios/ios_error.h" namespace ios::net::internal { Error startAcMainServer(); Error joinAcMainServer(); void initialiseStaticAcMainServerData(); } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_ac_service.cpp ================================================ #include "ios_net_ac_service.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/ac/nn_ac_result.h" #include "nn/ipc/nn_ipc_result.h" #include <chrono> #include <ctime> #include <uv.h> using namespace nn::ac; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::net::internal { static nn::Result initialise(CommandHandlerArgs &args) { auto command = ServerCommand<AcService::Initialise> { args }; return nn::ResultSuccess; } static nn::Result finalise(CommandHandlerArgs &args) { auto command = ServerCommand<AcService::Finalise> { args }; return nn::ResultSuccess; } static nn::Result getAssignedAddress(CommandHandlerArgs &args) { auto command = ServerCommand<AcService::GetAssignedAddress> { args }; auto unkParamater = uint32_t {}; command.ReadRequest(unkParamater); auto ipAddress = static_cast<uint32_t>((127u << 24) | (0u << 16) | (0u << 8) | 1u); command.WriteResponse(ipAddress); return nn::ResultSuccess; } nn::Result AcService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case Initialise::command: return initialise(args); case Finalise::command: return finalise(args); case GetAssignedAddress::command: return getAssignedAddress(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_ac_service.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/ac/nn_ac_service.h" namespace ios::net::internal { struct AcService : ::nn::ac::services::AcService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_enum.h ================================================ #ifndef IOS_NET_ENUM_H #define IOS_NET_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(net) ENUM_BEG(SocketCommand, uint32_t) ENUM_VALUE(Accept, 0x1) ENUM_VALUE(Bind, 0x2) ENUM_VALUE(Close, 0x3) ENUM_VALUE(Connect, 0x4) ENUM_VALUE(GetPeerName, 0x6) ENUM_VALUE(GetSockName, 0x7) ENUM_VALUE(GetSockOpt, 0x8) ENUM_VALUE(SetSockOpt, 0x9) ENUM_VALUE(Listen, 0xA) ENUM_VALUE(Recv, 0xC) ENUM_VALUE(RecvFrom, 0xD) ENUM_VALUE(Send, 0xE) ENUM_VALUE(SendTo, 0xF) ENUM_VALUE(Shutdown, 0x10) ENUM_VALUE(Socket, 0x11) ENUM_VALUE(DnsQuery, 0x26) ENUM_VALUE(Select, 0x27) ENUM_VALUE(SimplePing, 0x28) ENUM_VALUE(SimplePingResult, 0x29) ENUM_VALUE(GetProcessSocketHandle, 0x2A) ENUM_VALUE(CloseAll, 0x2D) ENUM_VALUE(GetProxyConfig, 0x2E) ENUM_VALUE(GetOpt, 0x2F) ENUM_VALUE(SetOpt, 0x30) ENUM_VALUE(ClearResolverCache, 0x32) ENUM_VALUE(SendToMulti, 0x33) ENUM_VALUE(RecvFromMulti, 0x35) ENUM_VALUE(SendToMultiEx, 0x36) ENUM_VALUE(RecvFromEx, 0x38) ENUM_END(SocketCommand) ENUM_BEG(SocketDnsQueryType, uint8_t) ENUM_VALUE(GetHostByName, 1) ENUM_END(SocketDnsQueryType) ENUM_BEG(SocketError, uint32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(NoBufs, 1) ENUM_VALUE(TimedOut, 2) ENUM_VALUE(IsConn, 3) ENUM_VALUE(OpNotSupp, 4) ENUM_VALUE(ConnAborted, 5) ENUM_VALUE(WouldBlock, 6) ENUM_VALUE(ConnRefused, 7) ENUM_VALUE(ConnReset, 8) ENUM_VALUE(NotConn, 9) ENUM_VALUE(Already, 10) ENUM_VALUE(Inval, 11) ENUM_VALUE(MsgSize, 12) ENUM_VALUE(Pipe, 13) ENUM_VALUE(DestAddrReq, 14) ENUM_VALUE(Shutdown, 15) ENUM_VALUE(NoProtoOpt, 16) ENUM_VALUE(HaveOob, 17) ENUM_VALUE(NoMem, 18) ENUM_VALUE(AddrNotAvail, 19) ENUM_VALUE(AddrInUse, 20) ENUM_VALUE(AfNoSupport, 21) ENUM_VALUE(InProgress, 22) ENUM_VALUE(Lower, 23) ENUM_VALUE(NotSock, 24) ENUM_VALUE(Eieio, 27) ENUM_VALUE(TooManyRefs, 28) ENUM_VALUE(Fault, 29) ENUM_VALUE(NetUnreach, 30) ENUM_VALUE(ProtoNoSupport, 31) ENUM_VALUE(Prototype, 32) ENUM_VALUE(GenericError, 41) ENUM_VALUE(NoLibRm, 42) ENUM_VALUE(NotInitialised, 43) ENUM_VALUE(Busy, 44) ENUM_VALUE(Unknown, 45) ENUM_VALUE(NoResources, 48) ENUM_VALUE(BadFd, 49) ENUM_VALUE(Aborted, 50) ENUM_VALUE(MFile, 51) ENUM_END(SocketError) ENUM_BEG(SocketFamily, uint32_t) ENUM_VALUE(Inet, 0x2) ENUM_END(SocketFamily) ENUM_NAMESPACE_EXIT(net) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_NET_ENUM_H ================================================ FILE: src/libdecaf/src/ios/net/ios_net_log.h ================================================ #pragma once #include <common/log.h> namespace ios::net::internal { extern Logger netLog; } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_ndm_server.cpp ================================================ #include "ios_net_log.h" #include "ios_net_ndm_server.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" namespace ios::net::internal { using namespace kernel; constexpr auto NdmNumMessages = 0x64u; constexpr auto NdmThreadStackSize = 0x2000u; constexpr auto NdmThreadPriority = 45u; class NdmServer : public nn::ipc::Server { public: NdmServer() : nn::ipc::Server(true) { } }; struct StaticNdmServerData { be2_struct<NdmServer> server; be2_array<Message, NdmNumMessages> messageBuffer; be2_array<uint8_t, NdmThreadStackSize> threadStack; }; static phys_ptr<StaticNdmServerData> sNdmServerData = nullptr; Error startNdmServer() { auto &server = sNdmServerData->server; auto result = server.initialise("/dev/ndm", phys_addrof(sNdmServerData->messageBuffer), static_cast<uint32_t>(sNdmServerData->messageBuffer.size())); if (result.failed()) { netLog->error( "startNdmServer: Server initialisation failed for /dev/ndm, result = {}", result.code()); return Error::FailInternal; } // TODO: Register service 0, 1, 2 result = server.start(phys_addrof(sNdmServerData->threadStack) + sNdmServerData->threadStack.size(), static_cast<uint32_t>(sNdmServerData->threadStack.size()), NdmThreadPriority); if (result.failed()) { netLog->error( "startNdmServer: Server start failed for /dev/ndm, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } Error joinNdmServer() { sNdmServerData->server.join(); return Error::OK; } void initialiseStaticNdmServerData() { sNdmServerData = allocProcessStatic<StaticNdmServerData>(); } } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_ndm_server.h ================================================ #pragma once #include "ios/ios_error.h" namespace ios::net::internal { Error startNdmServer(); Error joinNdmServer(); void initialiseStaticNdmServerData(); } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket.h ================================================ #pragma once #include "ios_net_enum.h" #include "ios_net_socket_request.h" #include "ios_net_socket_response.h" #include "ios_net_socket_types.h" ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_async_task.cpp ================================================ #include "ios_net_socket_async_task.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/ios_enum.h" #include "ios/ios_handlemanager.h" #include "ios/ios_ipc.h" #include "ios/ios_stackobject.h" #include <mutex> #include <optional> #include <vector> using namespace ios::kernel; using ios::kernel::internal::setInterruptAhbAll; namespace ios::net::internal { struct CompletedTask { phys_ptr<ResourceRequest> resourceRequest; Error status; }; struct StaticSocketAsyncTaskData { be2_val<MessageQueueId> messageQueue; be2_array<Message, 64> messageBuffer; be2_val<ThreadId> thread; be2_array<uint8_t, 0x1000> threadStack; }; static std::mutex sCompletedTasksMutex; static std::vector<CompletedTask> sCompletedTasks; static phys_ptr<StaticSocketAsyncTaskData> sSocketAsyncTaskData = nullptr; void completeSocketTask(phys_ptr<ResourceRequest> resourceRequest, std::optional<Error> result) { if (result.has_value()) { sCompletedTasksMutex.lock(); sCompletedTasks.push_back({ resourceRequest, result.value() }); sCompletedTasksMutex.unlock(); setInterruptAhbAll(AHBALL::get(0).Wireless80211(true)); } } static Error socketAsyncTaskThread(phys_ptr<void> /*unused*/) { StackObject<Message> message; auto error = IOS_HandleEvent(DeviceId::Wireless80211, sSocketAsyncTaskData->messageQueue, Message { 1 }); if (error < Error::OK) { return error; } error = IOS_ClearAndEnable(DeviceId::Wireless80211); if (error < Error::OK) { return error; } while (true) { error = IOS_ReceiveMessage(sSocketAsyncTaskData->messageQueue, message, MessageFlags::None); if (error < Error::OK) { return error; } if (*message == 1) { auto completedTasks = std::vector<CompletedTask> { }; sCompletedTasksMutex.lock(); completedTasks.swap(sCompletedTasks); sCompletedTasksMutex.unlock(); for (auto &task : completedTasks) { IOS_ResourceReply(task.resourceRequest, static_cast<Error>(task.status)); } IOS_ClearAndEnable(DeviceId::Wireless80211); } } } Error startSocketAsyncTaskThread() { auto error = IOS_CreateMessageQueue(phys_addrof(sSocketAsyncTaskData->messageBuffer), static_cast<uint32_t>(sSocketAsyncTaskData->messageBuffer.size())); if (error < Error::OK) { return error; } sSocketAsyncTaskData->messageQueue = static_cast<MessageQueueId>(error); error = IOS_CreateThread(socketAsyncTaskThread, nullptr, phys_addrof(sSocketAsyncTaskData->threadStack) + sSocketAsyncTaskData->threadStack.size(), static_cast<uint32_t>(sSocketAsyncTaskData->threadStack.size()), 80, ThreadFlags::Detached); if (error < Error::OK) { return error; } sSocketAsyncTaskData->thread = static_cast<ThreadId>(error); kernel::internal::setThreadName(sSocketAsyncTaskData->thread, "SocketAsyncTaskThread"); error = IOS_StartThread(sSocketAsyncTaskData->thread); if (error < Error::OK) { return error; } IOS_YieldCurrentThread(); return Error::OK; } void initialiseStaticSocketAsyncTaskData() { sSocketAsyncTaskData = allocProcessStatic<StaticSocketAsyncTaskData>(); } } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_async_task.h ================================================ #pragma once #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/ios_enum.h" #include <optional> namespace ios::net::internal { Error startSocketAsyncTaskThread(); void completeSocketTask(phys_ptr<kernel::ResourceRequest> resourceRequest, std::optional<Error> result); void initialiseStaticSocketAsyncTaskData(); } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_device.cpp ================================================ #include "ios_net_log.h" #include "ios_net_socket_async_task.h" #include "ios_net_socket_device.h" #include "ios/kernel/ios_kernel_hardware.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/ios_error.h" #include "ios/ios_network_thread.h" #include <atomic> #include <ares.h> #include <common/platform.h> #include <common/strutils.h> #include <libcpu/cpu_formatters.h> #include <functional> #include <mutex> #include <uv.h> #include <vector> using namespace ios::kernel; using ios::kernel::internal::setInterruptAhbAll; using ios::internal::networkUvLoop; namespace ios::net::internal { static constexpr int SO_AF_UNSPEC = 0; static constexpr int SO_AF_INET = 2; static constexpr int SO_SOCK_STREAM = 1; static constexpr int SO_SOCK_DGRAM = 2; static constexpr int SO_IPPROTO_IP = 0; static constexpr int SO_IPPROTO_TCP = 6; static constexpr int SO_IPPROTO_UDP = 17; static constexpr int SO_MSG_PEEK = 0x2; static constexpr int SO_MSG_DONTWAIT = 0x20; static void * resourceRequestToHandleData(phys_ptr<ResourceRequest> resourceRequest) { return reinterpret_cast<void *>( static_cast<uintptr_t>( static_cast<uint32_t>( phys_cast<phys_addr>(resourceRequest)))); } static phys_ptr<ResourceRequest> handleDataToResourceRequest(void *data) { return phys_cast<ResourceRequest *>( static_cast<phys_addr>( static_cast<uint32_t>( reinterpret_cast<uintptr_t>(data)))); } std::optional<Error> SocketDevice::accept(phys_ptr<ResourceRequest> resourceRequest, SocketHandle fd, phys_ptr<SocketAddrIn> sockAddr, int32_t sockAddrLen) { return makeError(ErrorCategory::Socket, SocketError::Inval); } std::optional<Error> SocketDevice::bind(phys_ptr<ResourceRequest> resourceRequest, SocketHandle fd, phys_ptr<SocketAddrIn> sockAddr, int32_t sockAddrLen) { auto socket = getSocket(fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } auto addr = sockaddr_in { 0 }; addr.sin_addr.s_addr = sockAddr->sin_addr.s_addr_; addr.sin_family = sockAddr->sin_family; addr.sin_port = htons(sockAddr->sin_port); auto result = 0; if (socket->type == Socket::Tcp) { result = uv_tcp_bind(reinterpret_cast<uv_tcp_t *>(socket->handle.get()), reinterpret_cast<sockaddr *>(&addr), 0); } else if (socket->type == Socket::Udp) { result = uv_udp_bind(reinterpret_cast<uv_udp_t *>(socket->handle.get()), reinterpret_cast<sockaddr *>(&addr), 0); } if (result != 0) { return makeError(ErrorCategory::Socket, SocketError::GenericError); } return Error::OK; } std::optional<Error> SocketDevice::createSocket(phys_ptr<ResourceRequest> resourceRequest, int32_t family, int32_t type, int32_t proto) { auto fd = SocketHandle { -1 }; for (auto i = 0u; i < mSockets.size(); ++i) { if (!mSockets[i].type) { fd = i + 1; break; } } if (fd == -1) { return makeError(ErrorCategory::Socket, SocketError::MFile); } if (family != SO_AF_INET) { return makeError(ErrorCategory::Socket, SocketError::AfNoSupport); } if (type == SO_SOCK_STREAM) { if (proto != SO_IPPROTO_IP && proto != SO_IPPROTO_TCP) { return makeError(ErrorCategory::Socket, SocketError::ProtoNoSupport); } auto handle = std::make_unique<uv_tcp_t>(); if (uv_tcp_init(networkUvLoop(), handle.get()) != 0) { return makeError(ErrorCategory::Socket, SocketError::GenericError); } auto &socket = mSockets[fd - 1]; socket.device = this; socket.type = Socket::Tcp; handle->data = &socket; socket.handle.reset(reinterpret_cast<uv_handle_t *>(handle.release())); } else if (type == SO_SOCK_DGRAM) { if (proto != SO_IPPROTO_IP && proto != SO_IPPROTO_UDP) { return makeError(ErrorCategory::Socket, SocketError::ProtoNoSupport); } auto handle = std::make_unique<uv_udp_t>(); if (uv_udp_init(networkUvLoop(), handle.get()) != 0) { return makeError(ErrorCategory::Socket, SocketError::GenericError); } auto &socket = mSockets[fd - 1]; socket.device = this; socket.type = Socket::Udp; handle->data = &socket; socket.handle.reset(reinterpret_cast<uv_handle_t *>(handle.release())); } else { return makeError(ErrorCategory::Socket, SocketError::ProtoNoSupport); } return static_cast<Error>(fd); } void SocketDevice::uvCloseSocketCallback(uv_handle_t *handle) { auto socket = reinterpret_cast<SocketDevice::Socket *>(handle->data); // Abort any pending read requests for (auto readRequest : socket->pendingReads) { completeSocketTask(readRequest, makeError(ErrorCategory::Socket, SocketError::Aborted)); } socket->pendingReads.clear(); // Complete the close request completeSocketTask(socket->closeRequest, makeError(ErrorCategory::Socket, SocketError::OK)); socket->closeRequest = nullptr; // Ensure that we never dangle any unanswered ResourceRequests! decaf_check(socket->connectRequest == nullptr); decaf_check(socket->closeRequest == nullptr); decaf_check(socket->pendingReads.empty()); decaf_check(socket->pendingWrites.empty()); *socket = SocketDevice::Socket {}; } std::optional<Error> SocketDevice::closeSocket(phys_ptr<ResourceRequest> resourceRequest, SocketHandle fd) { auto socket = getSocket(fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } socket->closeRequest = resourceRequest; uv_close(socket->handle.get(), uvCloseSocketCallback); return {}; } static void uvReadAllocCallback(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { buf->base = new char[suggested_size]; buf->len = static_cast<decltype(buf->len)>(suggested_size); } static void uvReadCallback(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { auto socket = reinterpret_cast<SocketDevice::Socket *>(stream->data); if (nread >= 0) { socket->readBuffer.insert(socket->readBuffer.end(), buf->base, buf->base + buf->len); } else if (nread < 0) { socket->except = makeError(ErrorCategory::Socket, SocketError::GenericError); } delete buf->base; socket->device->checkPendingReads(socket); socket->device->checkPendingSelects(); } static void uvConnectCallback(uv_connect_t *req, int status) { auto socket = reinterpret_cast<SocketDevice::Socket *>(req->data); if (status) { if (socket->connectRequest) { completeSocketTask(socket->connectRequest, makeError(ErrorCategory::Socket, SocketError::ConnRefused)); } socket->connect.reset(); return; } // Immediately start reading data so we can fill our incoming read buffer // in order to be able to emulate selects on read fds uv_read_start(reinterpret_cast<uv_stream_t *>(socket->handle.get()), uvReadAllocCallback, uvReadCallback); if (socket->connectRequest) { completeSocketTask(socket->connectRequest, Error::OK); } socket->connected = true; socket->device->checkPendingSelects(); } std::optional<Error> SocketDevice::connect(phys_ptr<ResourceRequest> resourceRequest, SocketHandle fd, phys_ptr<SocketAddrIn> sockAddr, int32_t sockAddrLen) { auto socket = getSocket(fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } if (socket->connect) { return makeError(ErrorCategory::Socket, SocketError::IsConn); } if (socket->type != Socket::Tcp) { return makeError(ErrorCategory::Socket, SocketError::Prototype); } auto addr = sockaddr_in { 0 }; addr.sin_addr.s_addr = sockAddr->sin_addr.s_addr_; addr.sin_family = sockAddr->sin_family; addr.sin_port = htons(sockAddr->sin_port); socket->connect = std::make_unique<uv_connect_t>(); socket->connect->data = socket; auto error = uv_tcp_connect(socket->connect.get(), reinterpret_cast<uv_tcp_t *>(socket->handle.get()), reinterpret_cast<const sockaddr *>(&addr), &uvConnectCallback); if (error) { socket->connect.reset(); return makeError(ErrorCategory::Socket, SocketError::ConnAborted); } if (socket->nonBlocking) { socket->connectRequest = nullptr; return makeError(ErrorCategory::Socket, SocketError::InProgress); } socket->connectRequest = resourceRequest; return {}; } static void getHostByNameCallback(void *arg, int status, int timeouts, struct hostent *hostent) { auto resourceRequest = handleDataToResourceRequest(arg); auto response = phys_cast<SocketDnsQueryResponse *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr); response->hostent.h_name = phys_addrof(response->dnsNames); string_copy(response->hostent.h_name.get(), hostent->h_name, response->dnsNames.size()); response->hostent.h_addrtype = hostent->h_addrtype; response->hostent.h_length = hostent->h_length; // From testing on my Wii U it seems no aliases are returned. response->aliases[0] = 0u; response->ipaddrs = 0u; for (auto i = 0u; hostent->h_addr_list[i] && i < response->ipaddrList.size(); ++i) { response->ipaddrList[i] = *reinterpret_cast<uint32_t *>(hostent->h_addr_list[i]); response->ipaddrs++; } response->selfPointerOffset = static_cast<uint32_t>(phys_cast<phys_addr>(response)); completeSocketTask(resourceRequest, Error::OK); } std::optional<Error> SocketDevice::dnsQuery(phys_ptr<ResourceRequest> resourceRequest, phys_ptr<SocketDnsQueryRequest> request, phys_ptr<SocketDnsQueryResponse> response) { if (request->queryType == SocketDnsQueryType::GetHostByName) { ares_gethostbyname(ios::internal::networkAresChannel(), phys_addrof(request->name).get(), AF_INET, getHostByNameCallback, resourceRequestToHandleData(resourceRequest)); return {}; } else { return makeError(ErrorCategory::Socket, SocketError::Inval); } } std::optional<Error> SocketDevice::getpeername(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketGetPeerNameRequest> request, phys_ptr<SocketGetPeerNameResponse> response) { auto socket = getSocket(request->fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } if (!socket->connected) { return makeError(ErrorCategory::Socket, SocketError::NotConn); } auto addr = sockaddr_in { }; auto addrlen = static_cast<int>(sizeof(sockaddr_in)); auto error = uv_tcp_getpeername(reinterpret_cast<uv_tcp_t *>(socket->handle.get()), reinterpret_cast<struct sockaddr *>(&addr), &addrlen); if (error) { return makeError(ErrorCategory::Socket, SocketError::NotConn); } response->addr.sin_family = addr.sin_family; response->addr.sin_port = ntohs(addr.sin_port); response->addr.sin_addr.s_addr_ = addr.sin_addr.s_addr; response->addrlen = static_cast<int32_t>(sizeof(SocketAddrIn)); return Error::OK; } std::optional<Error> SocketDevice::getsockname(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketGetSockNameRequest> request, phys_ptr<SocketGetSockNameResponse> response) { auto socket = getSocket(request->fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } if (!socket->connected) { return makeError(ErrorCategory::Socket, SocketError::NotConn); } auto addr = sockaddr_in { }; auto addrlen = static_cast<int>(sizeof(sockaddr_in)); auto error = uv_tcp_getsockname(reinterpret_cast<uv_tcp_t *>(socket->handle.get()), reinterpret_cast<struct sockaddr *>(&addr), &addrlen); if (error) { return makeError(ErrorCategory::Socket, SocketError::NotConn); } response->addr.sin_family = addr.sin_family; response->addr.sin_port = ntohs(addr.sin_port); response->addr.sin_addr.s_addr_ = addr.sin_addr.s_addr; response->addrlen = static_cast<int32_t>(sizeof(SocketAddrIn)); return Error::OK; } std::optional<Error> SocketDevice::checkRecv(phys_ptr<kernel::ResourceRequest> resourceRequest) { auto request = phys_cast<const SocketRecvRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr); auto socket = getSocket(request->fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } auto alignedBeforeBuffer = phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr); auto alignedBeforeLength = resourceRequest->requestData.args.ioctlv.vecs[1].len; auto alignedBuffer = phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr); auto alignedLength = resourceRequest->requestData.args.ioctlv.vecs[2].len; auto alignedAfterBuffer = phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr); auto alignedAfterLength = resourceRequest->requestData.args.ioctlv.vecs[3].len; if (socket->readBuffer.empty()) { auto nonBlocking = socket->nonBlocking || !!(request->flags & SO_MSG_DONTWAIT); if (!nonBlocking) { return makeError(ErrorCategory::Socket, SocketError::WouldBlock); } return {}; } auto readBytes = size_t { 0u }; if (alignedBeforeBuffer && alignedBeforeLength) { auto len = std::min<size_t>(socket->readBuffer.size() - readBytes, alignedBeforeLength); std::memcpy(alignedBeforeBuffer.get(), socket->readBuffer.data() + readBytes, len); readBytes += len; } if (alignedBuffer && alignedLength) { auto len = std::min<size_t>(socket->readBuffer.size() - readBytes, alignedLength); std::memcpy(alignedBuffer.get(), socket->readBuffer.data() + readBytes, len); readBytes += len; } if (alignedAfterBuffer && alignedAfterLength) { auto len = std::min<size_t>(socket->readBuffer.size() - readBytes, alignedAfterLength); std::memcpy(alignedAfterBuffer.get(), socket->readBuffer.data() + readBytes, len); readBytes += len; } if (!(request->flags & SO_MSG_PEEK)) { if (readBytes == socket->readBuffer.size()) { socket->readBuffer.clear(); } else { std::memmove(socket->readBuffer.data(), socket->readBuffer.data() + readBytes, socket->readBuffer.size() - readBytes); socket->readBuffer.resize(socket->readBuffer.size() - readBytes); } } return static_cast<Error>(readBytes); } std::optional<Error> SocketDevice::recv(phys_ptr<ResourceRequest> resourceRequest, phys_ptr<const SocketRecvRequest> request, phys_ptr<char> alignedBeforeBuffer, uint32_t alignedBeforeLength, phys_ptr<char> alignedBuffer, uint32_t alignedLength, phys_ptr<char> alignedAfterBuffer, uint32_t alignedAfterLength) { auto socket = getSocket(request->fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } if (!socket->connected) { return makeError(ErrorCategory::Socket, SocketError::NotConn); } auto result = checkRecv(resourceRequest); if (result.has_value()) { return result; } socket->pendingReads.push_back(resourceRequest); return {}; } void SocketDevice::uvWriteCallback(uv_write_t *req, int32_t status) { auto write = reinterpret_cast<SocketDevice::PendingWrite *>(req->data); auto socket = write->socket; for (auto itr = socket->pendingWrites.begin(); itr != socket->pendingWrites.end(); ++itr) { if (itr->get() == write) { if (status != 0) { completeSocketTask((*itr)->resourceRequest, makeError(ErrorCategory::Socket, SocketError::GenericError)); } else { completeSocketTask((*itr)->resourceRequest, static_cast<Error>((*itr)->sendBytes)); } socket->pendingWrites.erase(itr); break; } } } std::optional<Error> SocketDevice::send(phys_ptr<ResourceRequest> resourceRequest, phys_ptr<const SocketSendRequest> request, phys_ptr<char> alignedBeforeBuffer, uint32_t alignedBeforeLength, phys_ptr<char> alignedBuffer, uint32_t alignedLength, phys_ptr<char> alignedAfterBuffer, uint32_t alignedAfterLength) { auto socket = getSocket(request->fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } if (!socket->connected) { return makeError(ErrorCategory::Socket, SocketError::NotConn); } auto buffers = std::array<uv_buf_t, 3> { }; auto numBuffers = 0; auto sendBytes = 0u; if (alignedBeforeBuffer && alignedBeforeLength) { buffers[numBuffers].len = alignedBeforeLength; buffers[numBuffers].base = alignedBeforeBuffer.get(); numBuffers++; sendBytes += alignedBeforeLength; } if (alignedBuffer && alignedLength) { buffers[numBuffers].len = alignedLength; buffers[numBuffers].base = alignedBuffer.get(); numBuffers++; sendBytes += alignedLength; } if (alignedAfterBuffer && alignedAfterLength) { buffers[numBuffers].len = alignedAfterLength; buffers[numBuffers].base = alignedAfterBuffer.get(); numBuffers++; sendBytes += alignedAfterLength; } auto write = std::make_unique<PendingWrite>(); write->socket = socket; write->handle.data = write.get(); write->resourceRequest = resourceRequest; write->sendBytes = sendBytes; auto error = uv_write(&write->handle, reinterpret_cast<uv_stream_t *>(socket->handle.get()), buffers.data(), numBuffers, &uvWriteCallback); if (error) { return makeError(ErrorCategory::Socket, SocketError::GenericError); } socket->pendingWrites.push_back(std::move(write)); return {}; } std::optional<Error> SocketDevice::setsockopt(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketSetSockOptRequest> request, phys_ptr<void> optval, uint32_t optlen) { auto socket = getSocket(request->fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } if (request->optname == 4118) { if (!optval || optlen != sizeof(uint32_t)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } socket->nonBlocking = !!*phys_cast<uint32_t *>(optval); return Error::OK; } netLog->warn("Unimplemented setsockopt for optname {}", request->optname); return Error::OK; } std::optional<Error> SocketDevice::checkSelect(phys_ptr<kernel::ResourceRequest> resourceRequest) { auto request = phys_cast<const SocketSelectRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer); auto response = phys_cast<SocketSelectResponse *>(resourceRequest->requestData.args.ioctl.outputBuffer); auto readyReadFds = uint32_t { 0 }; auto readyWriteFds = uint32_t { 0 }; auto readyExceptFds = uint32_t { 0 }; auto numFds = 0; for (auto i = 0; i < request->nfds; ++i) { if ((request->readfds >> i) & 1) { auto socket = getSocket(i); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } // Check if socket is ready to read if (!socket->readBuffer.empty()) { readyReadFds |= 1u << i; ++numFds; } } } for (auto i = 0; i < request->nfds; ++i) { if ((request->writefds >> i) & 1) { auto socket = getSocket(i); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } // Our sockets are always ready to write once they have connected if (socket->connected) { readyWriteFds |= 1u << i; ++numFds; } } } for (auto i = 0; i < request->nfds; ++i) { if ((request->exceptfds >> i) & 1) { auto socket = getSocket(i); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } // Check if socket has errored if (socket->except != Error::OK) { response->exceptfds |= 1u << i; ++numFds; } } } if (numFds) { response->readfds = readyReadFds; response->writefds = readyWriteFds; response->exceptfds = readyExceptFds; return static_cast<Error>(numFds); } return {}; } void SocketDevice::checkPendingReads(Socket *socket) { auto itr = socket->pendingReads.begin(); while (itr != socket->pendingReads.end()) { auto result = checkRecv(*itr); if (result.has_value()) { completeSocketTask(*itr, result); itr = socket->pendingReads.erase(itr); } else { ++itr; } } } void SocketDevice::checkPendingSelects() { auto now = std::chrono::system_clock::now(); auto itr = mPendingSelects.begin(); while (itr != mPendingSelects.end()) { auto result = checkSelect((*itr)->resourceRequest); if (result.has_value()) { completeSocketTask((*itr)->resourceRequest, result); itr = mPendingSelects.erase(itr); } else { ++itr; } } } void SocketDevice::uvExpirePendingSelectCallback(uv_timer_t *timer) { auto pending = reinterpret_cast<PendingSelect *>(timer->data); completeSocketTask(pending->resourceRequest, makeError(ErrorCategory::Socket, SocketError::TimedOut)); for (auto itr = pending->device->mPendingSelects.begin(); itr != pending->device->mPendingSelects.end(); ++itr) { if (itr->get() == pending) { pending->device->mPendingSelects.erase(itr); } } } std::optional<Error> SocketDevice::select(phys_ptr<ResourceRequest> resourceRequest, phys_ptr<const SocketSelectRequest> request, phys_ptr<SocketSelectResponse> response) { auto result = checkSelect(resourceRequest); if (result.has_value()) { return result; } if (request->hasTimeout) { if (request->timeout.tv_sec == 0 && request->timeout.tv_usec == 0) { return makeError(ErrorCategory::Socket, SocketError::TimedOut); } auto pending = std::make_unique<PendingSelect>(); uv_timer_init(networkUvLoop(), &pending->timer); pending->timer.data = resourceRequestToHandleData(resourceRequest); pending->resourceRequest = resourceRequest; pending->device = this; auto expireMS = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::seconds(request->timeout.tv_sec) + std::chrono::microseconds(request->timeout.tv_usec) + (std::chrono::milliseconds(1) - std::chrono::microseconds(1))); uv_timer_start(&pending->timer, &uvExpirePendingSelectCallback, static_cast<uint64_t>(expireMS.count()), 0ull); mPendingSelects.push_back(std::move(pending)); return {}; } return static_cast<Error>(0); } #if 0 static void uvListenConnectionCallback(uv_stream_t *server, int status) { auto socket = reinterpret_cast<SocketDevice::Socket *>(server->data); } #endif std::optional<Error> SocketDevice::listen(phys_ptr<ResourceRequest> resourceRequest, SocketHandle fd, int32_t backlog) { auto socket = getSocket(fd); if (!socket) { return makeError(ErrorCategory::Socket, SocketError::BadFd); } return makeError(ErrorCategory::Socket, SocketError::Inval); #if 0 auto error = uv_listen(reinterpret_cast<uv_stream_t *>(socket->handle.get()), backlog, uvListenConnectionCallback); if (error != 0) { } return {}; #endif } SocketDevice::Socket * SocketDevice::getSocket(SocketHandle fd) { if (fd <= 0) { return nullptr; } if (fd > mSockets.size()) { return nullptr; } if (!mSockets[fd - 1].type) { return nullptr; } return &mSockets[fd - 1]; } } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_device.h ================================================ #pragma once #include "ios_net_enum.h" #include "ios_net_socket_request.h" #include "ios_net_socket_response.h" #include "ios_net_socket_types.h" #include "ios/ios_enum.h" #include <array> #include <chrono> #include <memory> #include <optional> #include <vector> #include <uv.h> namespace ios::kernel { struct ResourceRequest; }; namespace ios::net::internal { /** * \ingroup ios_net * @{ */ class SocketDevice { public: struct Socket; struct PendingWrite { Socket *socket; phys_ptr<kernel::ResourceRequest> resourceRequest; uv_write_t handle; uint32_t sendBytes; }; struct PendingSelect { ~PendingSelect() { uv_timer_stop(&timer); } SocketDevice *device; phys_ptr<kernel::ResourceRequest> resourceRequest; uv_timer_t timer; }; struct Socket { enum Type { Unused, Tcp, Udp, }; Type type = Unused; SocketDevice *device = nullptr; bool nonBlocking = false; std::unique_ptr<uv_handle_t> handle; std::unique_ptr<uv_connect_t> connect; phys_ptr<kernel::ResourceRequest> connectRequest = nullptr; bool connected = false; Error except = Error::OK; std::vector<char> readBuffer; std::vector<phys_ptr<kernel::ResourceRequest>> pendingReads; std::vector<std::unique_ptr<PendingWrite>> pendingWrites; phys_ptr<kernel::ResourceRequest> closeRequest = nullptr; }; public: std::optional<Error> accept(phys_ptr<kernel::ResourceRequest> resourceRequest, SocketHandle fd, phys_ptr<SocketAddrIn> sockAddr, int32_t sockAddrLen); std::optional<Error> bind(phys_ptr<kernel::ResourceRequest> resourceRequest, SocketHandle fd, phys_ptr<SocketAddrIn> sockAddr, int32_t sockAddrLen); std::optional<Error> createSocket(phys_ptr<kernel::ResourceRequest> resourceRequest, int32_t family, int32_t type, int32_t proto); std::optional<Error> closeSocket(phys_ptr<kernel::ResourceRequest> resourceRequest, SocketHandle fd); std::optional<Error> connect(phys_ptr<kernel::ResourceRequest> resourceRequest, SocketHandle fd, phys_ptr<SocketAddrIn> sockAddr, int32_t sockAddrLen); std::optional<Error> dnsQuery(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<SocketDnsQueryRequest> request, phys_ptr<SocketDnsQueryResponse> response); std::optional<Error> getpeername(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketGetPeerNameRequest> request, phys_ptr<SocketGetPeerNameResponse> response); std::optional<Error> getsockname(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketGetSockNameRequest> request, phys_ptr<SocketGetSockNameResponse> response); std::optional<Error> recv(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketRecvRequest> request, phys_ptr<char> alignedBeforeBuffer, uint32_t alignedBeforeLength, phys_ptr<char> alignedBuffer, uint32_t alignedLength, phys_ptr<char> alignedAfterBuffer, uint32_t alignedAfterLength); std::optional<Error> send(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketSendRequest> request, phys_ptr<char> alignedBeforeBuffer, uint32_t alignedBeforeLength, phys_ptr<char> alignedBuffer, uint32_t alignedLength, phys_ptr<char> alignedAfterBuffer, uint32_t alignedAfterLength); std::optional<Error> setsockopt(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketSetSockOptRequest> request, phys_ptr<void> optval, uint32_t optlen); std::optional<Error> select(phys_ptr<kernel::ResourceRequest> resourceRequest, phys_ptr<const SocketSelectRequest> request, phys_ptr<SocketSelectResponse> response); std::optional<Error> listen(phys_ptr<kernel::ResourceRequest> resourceRequest, SocketHandle fd, int32_t backlog); void checkPendingSelects(); void checkPendingReads(Socket *socket); protected: Socket *getSocket(int sockfd); std::optional<Error> checkSelect(phys_ptr<kernel::ResourceRequest> resourceRequest); std::optional<Error> checkRecv(phys_ptr<kernel::ResourceRequest> resourceRequest); static void uvCloseSocketCallback(uv_handle_t *handle); static void uvExpirePendingSelectCallback(uv_timer_t *timer); static void uvWriteCallback(uv_write_t *req, int32_t status); private: std::array<Socket, 64> mSockets; std::vector<std::unique_ptr<PendingSelect>> mPendingSelects; }; /** @} */ } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_request.h ================================================ #pragma once #include "ios_net_enum.h" #include "ios_net_socket_types.h" #include "ios/ios_ipc.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::net { /** * \ingroup ios_net * @{ */ #pragma pack(push, 1) struct SocketAcceptRequest { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketAcceptRequest, 0x00, fd); CHECK_OFFSET(SocketAcceptRequest, 0x04, addr); CHECK_OFFSET(SocketAcceptRequest, 0x14, addrlen); CHECK_SIZE(SocketAcceptRequest, 0x18); struct SocketBindRequest { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketBindRequest, 0x00, fd); CHECK_OFFSET(SocketBindRequest, 0x04, addr); CHECK_OFFSET(SocketBindRequest, 0x14, addrlen); CHECK_SIZE(SocketBindRequest, 0x18); struct SocketCloseRequest { be2_val<SocketHandle> fd; }; CHECK_OFFSET(SocketCloseRequest, 0x00, fd); CHECK_SIZE(SocketCloseRequest, 0x04); struct SocketConnectRequest { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketConnectRequest, 0x00, fd); CHECK_OFFSET(SocketConnectRequest, 0x04, addr); CHECK_OFFSET(SocketConnectRequest, 0x14, addrlen); CHECK_SIZE(SocketConnectRequest, 0x18); struct SocketDnsQueryRequest { be2_array<char, 0x80> name; be2_val<SocketDnsQueryType> queryType; be2_val<uint8_t> isAsync; PADDING(2); UNKNOWN(4); be2_val<uint32_t> unk0x88; be2_val<uint32_t> unk0x8C; }; CHECK_OFFSET(SocketDnsQueryRequest, 0x00, name); CHECK_OFFSET(SocketDnsQueryRequest, 0x80, queryType); CHECK_OFFSET(SocketDnsQueryRequest, 0x81, isAsync); CHECK_OFFSET(SocketDnsQueryRequest, 0x88, unk0x88); CHECK_OFFSET(SocketDnsQueryRequest, 0x8C, unk0x8C); CHECK_SIZE(SocketDnsQueryRequest, 0x90); struct SocketGetPeerNameRequest { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketGetPeerNameRequest, 0x00, fd); CHECK_OFFSET(SocketGetPeerNameRequest, 0x04, addr); CHECK_OFFSET(SocketGetPeerNameRequest, 0x14, addrlen); CHECK_SIZE(SocketGetPeerNameRequest, 0x18); struct SocketGetProcessSocketHandle { be2_val<ios::TitleId> titleId; be2_val<ios::ProcessId> processId; }; CHECK_OFFSET(SocketGetProcessSocketHandle, 0x00, titleId); CHECK_OFFSET(SocketGetProcessSocketHandle, 0x08, processId); CHECK_SIZE(SocketGetProcessSocketHandle, 0x0C); struct SocketGetSockNameRequest { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketGetSockNameRequest, 0x00, fd); CHECK_OFFSET(SocketGetSockNameRequest, 0x04, addr); CHECK_OFFSET(SocketGetSockNameRequest, 0x14, addrlen); CHECK_SIZE(SocketGetSockNameRequest, 0x18); struct SocketListenRequest { be2_val<SocketHandle> fd; be2_val<int32_t> backlog; }; CHECK_OFFSET(SocketListenRequest, 0x00, fd); CHECK_OFFSET(SocketListenRequest, 0x04, backlog); CHECK_SIZE(SocketListenRequest, 0x08); struct SocketRecvRequest { be2_val<SocketHandle> fd; be2_val<int32_t> flags; }; CHECK_OFFSET(SocketRecvRequest, 0x00, fd); CHECK_OFFSET(SocketRecvRequest, 0x04, flags); CHECK_SIZE(SocketRecvRequest, 0x08); struct SocketSendRequest { be2_val<SocketHandle> fd; be2_val<int32_t> flags; }; CHECK_OFFSET(SocketSendRequest, 0x00, fd); CHECK_OFFSET(SocketSendRequest, 0x04, flags); CHECK_SIZE(SocketSendRequest, 0x08); struct SocketSelectRequest { be2_val<int32_t> nfds; be2_val<SocketFdSet> readfds; be2_val<SocketFdSet> writefds; be2_val<SocketFdSet> exceptfds; be2_struct<SocketTimeval> timeout; be2_val<int32_t> hasTimeout; }; CHECK_OFFSET(SocketSelectRequest, 0x00, nfds); CHECK_OFFSET(SocketSelectRequest, 0x04, readfds); CHECK_OFFSET(SocketSelectRequest, 0x08, writefds); CHECK_OFFSET(SocketSelectRequest, 0x0C, exceptfds); CHECK_OFFSET(SocketSelectRequest, 0x10, timeout); CHECK_OFFSET(SocketSelectRequest, 0x18, hasTimeout); CHECK_SIZE(SocketSelectRequest, 0x1C); struct SocketSetSockOptRequest { // For some reason this structure overlaps the ioctlv vecs... PADDING(0xC * 2); be2_val<SocketHandle> fd; be2_val<int32_t> level; be2_val<int32_t> optname; }; CHECK_OFFSET(SocketSetSockOptRequest, 0x18, fd); CHECK_OFFSET(SocketSetSockOptRequest, 0x1C, level); CHECK_OFFSET(SocketSetSockOptRequest, 0x20, optname); CHECK_SIZE(SocketSetSockOptRequest, 0x24); struct SocketSocketRequest { be2_val<int32_t> family; be2_val<int32_t> type; be2_val<int32_t> proto; }; CHECK_OFFSET(SocketSocketRequest, 0x00, family); CHECK_OFFSET(SocketSocketRequest, 0x04, type); CHECK_OFFSET(SocketSocketRequest, 0x08, proto); CHECK_SIZE(SocketSocketRequest, 0x0C); struct SocketRequest { union { be2_struct<SocketAcceptRequest> accept; be2_struct<SocketBindRequest> bind; be2_struct<SocketCloseRequest> close; be2_struct<SocketConnectRequest> connect; be2_struct<SocketDnsQueryRequest> dnsQuery; be2_struct<SocketGetPeerNameRequest> getpeername; be2_struct<SocketGetSockNameRequest> getsockname; be2_struct<SocketListenRequest> listen; be2_struct<SocketRecvRequest> recv; be2_struct<SocketSendRequest> send; be2_struct<SocketSelectRequest> select; be2_struct<SocketSetSockOptRequest> setsockopt; be2_struct<SocketSocketRequest> socket; be2_struct<SocketGetProcessSocketHandle> getProcessSocketHandle; }; }; #pragma pack(pop) /** @} */ } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_response.h ================================================ #pragma once #include "ios_net_enum.h" #include "ios_net_socket_types.h" #include "ios/ios_ipc.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::net { /** * \ingroup ios_net * @{ */ #pragma pack(push, 1) struct SocketAcceptResponse { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketAcceptResponse, 0x00, fd); CHECK_OFFSET(SocketAcceptResponse, 0x04, addr); CHECK_OFFSET(SocketAcceptResponse, 0x14, addrlen); CHECK_SIZE(SocketAcceptResponse, 0x18); struct SocketDnsQueryResponse { be2_val<uint32_t> unk0x00; be2_val<uint32_t> unk0x04; be2_val<uint32_t> sendTime; be2_val<uint32_t> expireTime; be2_val<uint16_t> tries; be2_val<uint16_t> lport; be2_val<uint16_t> id; UNKNOWN(0x2); be2_val<uint32_t> unk0x18; be2_val<uint32_t> replies; be2_val<uint32_t> ipaddrs; be2_array<uint32_t, 10> ipaddrList; be2_array<uint32_t, 10> hostentIpaddrList; be2_val<uint32_t> err; be2_val<uint32_t> rcode; be2_array<char, 256> dnsNames; be2_array<char, 129> unk0x17C; UNKNOWN(0x27C - 0x1FD); be2_val<uint32_t> authsIp; be2_array<uint32_t, 2> aliases; UNKNOWN(0x290 - 0x288); be2_struct<SocketHostEnt> hostent; be2_val<SocketDnsQueryType> queryType; be2_array<uint8_t, 2> unk0x2A5; UNKNOWN(0x2B0 - 0x2A7); be2_val<uint32_t> dnsReq; be2_val<uint32_t> next; //! Used to adjust pointers in hostent be2_val<uint32_t> selfPointerOffset; }; CHECK_OFFSET(SocketDnsQueryResponse, 0x00, unk0x00); CHECK_OFFSET(SocketDnsQueryResponse, 0x04, unk0x04); CHECK_OFFSET(SocketDnsQueryResponse, 0x08, sendTime); CHECK_OFFSET(SocketDnsQueryResponse, 0x0C, expireTime); CHECK_OFFSET(SocketDnsQueryResponse, 0x10, tries); CHECK_OFFSET(SocketDnsQueryResponse, 0x12, lport); CHECK_OFFSET(SocketDnsQueryResponse, 0x14, id); CHECK_OFFSET(SocketDnsQueryResponse, 0x18, unk0x18); CHECK_OFFSET(SocketDnsQueryResponse, 0x1C, replies); CHECK_OFFSET(SocketDnsQueryResponse, 0x20, ipaddrs); CHECK_OFFSET(SocketDnsQueryResponse, 0x24, ipaddrList); CHECK_OFFSET(SocketDnsQueryResponse, 0x4C, hostentIpaddrList); CHECK_OFFSET(SocketDnsQueryResponse, 0x74, err); CHECK_OFFSET(SocketDnsQueryResponse, 0x78, rcode); CHECK_OFFSET(SocketDnsQueryResponse, 0x7C, dnsNames); CHECK_OFFSET(SocketDnsQueryResponse, 0x17C, unk0x17C); CHECK_OFFSET(SocketDnsQueryResponse, 0x27C, authsIp); CHECK_OFFSET(SocketDnsQueryResponse, 0x280, aliases); CHECK_OFFSET(SocketDnsQueryResponse, 0x290, hostent); CHECK_OFFSET(SocketDnsQueryResponse, 0x2A4, queryType); CHECK_OFFSET(SocketDnsQueryResponse, 0x2A5, unk0x2A5); CHECK_OFFSET(SocketDnsQueryResponse, 0x2B0, dnsReq); CHECK_OFFSET(SocketDnsQueryResponse, 0x2B4, next); CHECK_OFFSET(SocketDnsQueryResponse, 0x2B8, selfPointerOffset); CHECK_SIZE(SocketDnsQueryResponse, 0x2BC); struct SocketGetPeerNameResponse { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketGetPeerNameResponse, 0x00, fd); CHECK_OFFSET(SocketGetPeerNameResponse, 0x04, addr); CHECK_OFFSET(SocketGetPeerNameResponse, 0x14, addrlen); CHECK_SIZE(SocketGetPeerNameResponse, 0x18); struct SocketGetSockNameResponse { be2_val<SocketHandle> fd; be2_struct<SocketAddrIn> addr; be2_val<int32_t> addrlen; }; CHECK_OFFSET(SocketGetSockNameResponse, 0x00, fd); CHECK_OFFSET(SocketGetSockNameResponse, 0x04, addr); CHECK_OFFSET(SocketGetSockNameResponse, 0x14, addrlen); CHECK_SIZE(SocketGetSockNameResponse, 0x18); struct SocketSelectResponse { be2_val<int32_t> nfds; be2_val<SocketFdSet> readfds; be2_val<SocketFdSet> writefds; be2_val<SocketFdSet> exceptfds; be2_struct<SocketTimeval> timeout; be2_val<int32_t> hasTimeout; }; CHECK_OFFSET(SocketSelectResponse, 0x00, nfds); CHECK_OFFSET(SocketSelectResponse, 0x04, readfds); CHECK_OFFSET(SocketSelectResponse, 0x08, writefds); CHECK_OFFSET(SocketSelectResponse, 0x0C, exceptfds); CHECK_OFFSET(SocketSelectResponse, 0x10, timeout); CHECK_OFFSET(SocketSelectResponse, 0x18, hasTimeout); CHECK_SIZE(SocketSelectResponse, 0x1C); struct SocketResponse { union { be2_struct<SocketAcceptResponse> accept; be2_struct<SocketDnsQueryResponse> dnsQuery; be2_struct<SocketGetPeerNameResponse> getpeername; be2_struct<SocketGetSockNameResponse> getsockname; be2_struct<SocketSelectResponse> select; }; }; #pragma pack(pop) /** @} */ } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_thread.cpp ================================================ #include "ios_net_socket_async_task.h" #include "ios_net_socket_device.h" #include "ios_net_socket_response.h" #include "ios_net_socket_request.h" #include "ios_net_socket_thread.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/ios_error.h" #include "ios/ios_stackobject.h" #include "ios/ios_network_thread.h" #include <array> #include <optional> using ios::internal::submitNetworkTask; namespace ios::net::internal { using SocketDeviceHandle = int32_t; using namespace kernel; constexpr auto NumSocketMessages = 40u; constexpr auto SocketThreadStackSize = 0x4000u; constexpr auto SocketThreadPriority = 69u; struct StaticSocketThreadData { be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_struct<IpcRequest> stopMessage; be2_array<Message, NumSocketMessages> messageBuffer; be2_array<uint8_t, SocketThreadStackSize> threadStack; }; static phys_ptr<StaticSocketThreadData> sData = nullptr; static std::array<std::unique_ptr<SocketDevice>, ProcessId::Max> sDevices; static SocketDevice * getDevice(SocketDeviceHandle handle) { if (handle < 0 || handle >= sDevices.size()) { return nullptr; } return sDevices[handle].get(); } static Error socketOpen(phys_ptr<ResourceRequest> request) { // There is one socket device per process auto pid = request->requestData.clientPid; auto idx = static_cast<size_t>(pid); if (!sDevices[idx]) { sDevices[idx] = std::make_unique<SocketDevice>(); } return static_cast<Error>(idx); } static Error socketClose(phys_ptr<ResourceRequest> request) { auto pid = request->requestData.clientPid; auto idx = static_cast<size_t>(pid); if (idx != request->requestData.handle) { return Error::Exists; } sDevices[idx] = nullptr; return Error::OK; } static std::optional<Error> socketIoctl(phys_ptr<ResourceRequest> resourceRequest) { auto device = getDevice(resourceRequest->requestData.handle); if (!device) { return Error::InvalidHandle; } auto request = phys_cast<const SocketRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer); auto response = phys_cast<SocketResponse *>(resourceRequest->requestData.args.ioctl.outputBuffer); switch (static_cast<SocketCommand>(resourceRequest->requestData.args.ioctl.request)) { case SocketCommand::Accept: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketAcceptRequest)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->accept(resourceRequest, request->accept.fd, phys_addrof(request->accept.addr), request->accept.addrlen)); }); break; case SocketCommand::Bind: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketBindRequest)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->bind(resourceRequest, request->bind.fd, phys_addrof(request->bind.addr), request->bind.addrlen)); }); break; case SocketCommand::Close: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketCloseRequest)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->closeSocket(resourceRequest, request->close.fd)); }); break; case SocketCommand::Connect: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketConnectRequest)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->connect(resourceRequest, request->connect.fd, phys_addrof(request->connect.addr), request->connect.addrlen)); }); break; case SocketCommand::GetPeerName: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketGetPeerNameRequest) || resourceRequest->requestData.args.ioctl.outputLength != sizeof(SocketGetPeerNameResponse)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->getpeername(resourceRequest, phys_addrof(request->getpeername), phys_addrof(response->getpeername))); }); break; case SocketCommand::GetSockName: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketGetSockNameRequest) || resourceRequest->requestData.args.ioctl.outputLength != sizeof(SocketGetSockNameResponse)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->getsockname(resourceRequest, phys_addrof(request->getsockname), phys_addrof(response->getsockname))); }); break; case SocketCommand::Listen: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketListenRequest)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->listen(resourceRequest, request->listen.fd, request->listen.backlog)); }); break; case SocketCommand::Select: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketSelectRequest) || resourceRequest->requestData.args.ioctl.outputLength != sizeof(SocketSelectResponse)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->select(resourceRequest, phys_addrof(request->select), phys_addrof(response->select))); }); break; case SocketCommand::Socket: if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketSocketRequest)) { return makeError(ErrorCategory::Socket, SocketError::Inval); } submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->createSocket(resourceRequest, request->socket.family, request->socket.type, request->socket.proto)); }); break; case SocketCommand::GetProcessSocketHandle: if (static_cast<size_t>(request->getProcessSocketHandle.processId) < sDevices.size() && sDevices[request->getProcessSocketHandle.processId]) { return static_cast<Error>(request->getProcessSocketHandle.processId); } else { return makeError(ErrorCategory::Socket, SocketError::Inval); } break; default: return Error::Invalid; } return {}; // async request } static std::optional<Error> socketIoctlv(phys_ptr<ResourceRequest> resourceRequest) { auto device = getDevice(resourceRequest->requestData.handle); if (!device) { return Error::InvalidHandle; } switch (static_cast<SocketCommand>(resourceRequest->requestData.args.ioctlv.request)) { case SocketCommand::DnsQuery: if (resourceRequest->requestData.args.ioctlv.numVecIn == 1 && resourceRequest->requestData.args.ioctlv.numVecOut == 1 && resourceRequest->requestData.args.ioctlv.vecs[0].len == sizeof(SocketDnsQueryRequest) && resourceRequest->requestData.args.ioctlv.vecs[1].len == sizeof(SocketDnsQueryResponse)) { submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->dnsQuery(resourceRequest, phys_cast<SocketDnsQueryRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr), phys_cast<SocketDnsQueryResponse *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr))); }); } else { return makeError(ErrorCategory::Socket, SocketError::Inval); } break; case SocketCommand::Recv: if (resourceRequest->requestData.args.ioctlv.numVecIn == 1 && resourceRequest->requestData.args.ioctlv.numVecOut == 3 && resourceRequest->requestData.args.ioctlv.vecs[0].len == sizeof(SocketRecvRequest)) { submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->recv(resourceRequest, phys_cast<const SocketRecvRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr), phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr), resourceRequest->requestData.args.ioctlv.vecs[1].len, phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr), resourceRequest->requestData.args.ioctlv.vecs[2].len, phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr), resourceRequest->requestData.args.ioctlv.vecs[3].len)); }); } else { return makeError(ErrorCategory::Socket, SocketError::Inval); } break; case SocketCommand::SetSockOpt: if (resourceRequest->requestData.args.ioctlv.numVecIn == 2 && resourceRequest->requestData.args.ioctlv.numVecOut == 0 && resourceRequest->requestData.args.ioctlv.vecs[1].len == sizeof(SocketSetSockOptRequest)) { submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->setsockopt(resourceRequest, phys_cast<SocketSetSockOptRequest *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr), phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr), resourceRequest->requestData.args.ioctlv.vecs[0].len)); }); } else { return makeError(ErrorCategory::Socket, SocketError::Inval); } break; case SocketCommand::Send: if (resourceRequest->requestData.args.ioctlv.numVecIn == 4 && resourceRequest->requestData.args.ioctlv.numVecOut == 0 && resourceRequest->requestData.args.ioctlv.vecs[0].len == sizeof(SocketSendRequest)) { submitNetworkTask([=]() { completeSocketTask( resourceRequest, device->send(resourceRequest, phys_cast<const SocketSendRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr), phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr), resourceRequest->requestData.args.ioctlv.vecs[1].len, phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr), resourceRequest->requestData.args.ioctlv.vecs[2].len, phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr), resourceRequest->requestData.args.ioctlv.vecs[3].len)); }); } else { return makeError(ErrorCategory::Socket, SocketError::Inval); } break; default: return Error::Invalid; } return {}; // async request } static Error socketThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; while (true) { auto error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: IOS_ResourceReply(request, socketOpen(request)); break; case Command::Close: IOS_ResourceReply(request, socketClose(request)); break; case Command::Ioctl: if (auto result = socketIoctl(request); result.has_value()) { IOS_ResourceReply(request, result.value()); } break; case Command::Ioctlv: if (auto result = socketIoctlv(request); result.has_value()) { IOS_ResourceReply(request, result.value()); } break; case Command::Suspend: // TODO: Do any necessary cleanup! return Error::OK; default: IOS_ResourceReply(request, Error::InvalidArg); } } } Error registerSocketResourceManager() { auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer), sData->messageBuffer.size()); if (error < Error::OK) { return error; } sData->messageQueueId = static_cast<MessageQueueId>(error); return IOS_RegisterResourceManager("/dev/socket", sData->messageQueueId); } Error startSocketThread() { auto error = IOS_CreateThread(&socketThreadEntry, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), sData->threadStack.size(), SocketThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } sData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sData->threadId, "SocketThread"); return IOS_StartThread(sData->threadId); } Error stopSocketThread() { return IOS_JamMessage(sData->messageQueueId, makeMessage(phys_addrof(sData->stopMessage)), MessageFlags::NonBlocking); } void initialiseStaticSocketData() { sData = allocProcessStatic<StaticSocketThreadData>(); sData->stopMessage.command = Command::Suspend; } } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::net::internal { Error registerSocketResourceManager(); Error startSocketThread(); Error stopSocketThread(); void initialiseStaticSocketData(); } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_socket_types.h ================================================ #pragma once #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::net { /** * \ingroup ios_net * @{ */ #pragma pack(push, 1) using SocketHandle = int32_t; using SocketFdSet = uint32_t; struct SocketAddr { be2_val<uint16_t> sa_family; be2_array<uint8_t, 14> sa_data; }; CHECK_OFFSET(SocketAddr, 0x00, sa_family); CHECK_OFFSET(SocketAddr, 0x02, sa_data); CHECK_SIZE(SocketAddr, 0x10); struct SocketInAddr { be2_val<uint32_t> s_addr_; }; CHECK_OFFSET(SocketInAddr, 0x00, s_addr_); CHECK_SIZE(SocketInAddr, 0x04); struct SocketHostEnt { be2_phys_ptr<char> h_name; be2_phys_ptr<char> h_aliases; be2_val<int32_t> h_addrtype; be2_val<int32_t> h_length; be2_phys_ptr<char> h_addr_list; }; CHECK_OFFSET(SocketHostEnt, 0x00, h_name); CHECK_OFFSET(SocketHostEnt, 0x04, h_aliases); CHECK_OFFSET(SocketHostEnt, 0x08, h_addrtype); CHECK_OFFSET(SocketHostEnt, 0x0C, h_length); CHECK_OFFSET(SocketHostEnt, 0x10, h_addr_list); CHECK_SIZE(SocketHostEnt, 0x14); struct SocketAddrIn { be2_val<uint16_t> sin_family; be2_val<uint16_t> sin_port; be2_struct<SocketInAddr> sin_addr; be2_array<uint8_t, 8> sin_zero; }; CHECK_OFFSET(SocketAddrIn, 0x00, sin_family); CHECK_OFFSET(SocketAddrIn, 0x02, sin_port); CHECK_OFFSET(SocketAddrIn, 0x04, sin_addr); CHECK_OFFSET(SocketAddrIn, 0x08, sin_zero); CHECK_SIZE(SocketAddrIn, 0x10); struct SocketTimeval { be2_val<int32_t> tv_sec; be2_val<int32_t> tv_usec; }; CHECK_OFFSET(SocketTimeval, 0x00, tv_sec); CHECK_OFFSET(SocketTimeval, 0x04, tv_usec); CHECK_SIZE(SocketTimeval, 0x08); #pragma pack(pop) /** @} */ } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_soshim.cpp ================================================ #include "ios_net_soshim.h" #include "ios_net_socket_request.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/ios_stackobject.h" namespace ios::net { using namespace kernel; static phys_ptr<void> allocIpcData(uint32_t size) { auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size); if (buffer) { std::memset(buffer.get(), 0, size); } return buffer; } static void freeIpcData(phys_ptr<void> data) { IOS_HeapFree(CrossProcessHeapId, data); } Error SOShim_Open() { return IOS_Open("/dev/socket", OpenMode::None); } Error SOShim_Close(SOShimHandle handle) { return IOS_Close(handle); } Error SOShim_GetProcessSocketHandle(SOShimHandle handle, TitleId titleId, ProcessId processId) { auto request = phys_cast<SocketGetProcessSocketHandle *>(allocIpcData(sizeof(SocketGetProcessSocketHandle))); if (!request) { return Error::Access; } request->titleId = titleId; request->processId = processId; auto error = IOS_Ioctl(handle, SocketCommand::GetProcessSocketHandle, request, sizeof(SocketGetProcessSocketHandle), nullptr, 0); freeIpcData(request); return static_cast<Error>(error); } } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_soshim.h ================================================ #pragma once #include "ios/ios_enum.h" #include "ios/kernel/ios_kernel_resourcemanager.h" namespace ios::net { using SOShimHandle = kernel::ResourceHandleId; Error SOShim_Open(); Error SOShim_Close(SOShimHandle handle); Error SOShim_GetProcessSocketHandle(SOShimHandle handle, TitleId titleId, ProcessId processId); } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/net/ios_net_subsys.cpp ================================================ #include "ios_net_subsys.h" #include "ios_net_socket_thread.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_thread.h" namespace ios::net::internal { constexpr auto SubsysHeapSize = 0x23A000u; constexpr auto InitThreadStackSize = 0x4000u; constexpr auto InitThreadPriority = 69u; using namespace kernel; struct StaticSubsysData { be2_val<HeapId> heap; be2_array<uint8_t, InitThreadStackSize> threadStack; }; static phys_ptr<StaticSubsysData> sData; static phys_ptr<void> sSubsysHeapBuffer; static Error subsysInitThread(phys_ptr<void> /*context*/) { // Read network config // Start ifmgr thread // Start socket thread auto error = startSocketThread(); if (error < Error::OK) { return error; } // Start ifnet thread // Create some weird timer thread return Error::OK; } Error initSubsys() { // Init ifmgr resource manager // Init socket resource manager auto error = registerSocketResourceManager(); if (error < Error::OK) { return error; } // Init ifnet resource manager return Error::OK; } Error startSubsys() { auto error = IOS_CreateHeap(sSubsysHeapBuffer, SubsysHeapSize); if (error < Error::OK) { return error; } error = IOS_CreateThread(&subsysInitThread, nullptr, phys_addrof(sData->threadStack) + sData->threadStack.size(), sData->threadStack.size(), InitThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { return error; } auto threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(threadId, "NetSubsysThread"); return IOS_StartThread(threadId); } Error stopSubsys() { stopSocketThread(); return Error::OK; } void initialiseStaticSubsysData() { sData = allocProcessStatic<StaticSubsysData>(); sSubsysHeapBuffer = allocProcessLocalHeap(SubsysHeapSize); } } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/net/ios_net_subsys.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::net::internal { Error initSubsys(); Error startSubsys(); Error stopSubsys(); void initialiseStaticSubsysData(); } // namespace ios::net::internal ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim.cpp ================================================ #include "ios_nim.h" #include "ios_nim_log.h" #include "ios_nim_boss_server.h" #include "ios_nim_nim_server.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn.h" using namespace ios::kernel; namespace ios::nim { constexpr auto LocalHeapSize = 0x20000u; constexpr auto CrossHeapSize = 0x24E400u; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger nimLog = { }; void initialiseStaticData() { sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } } // namespace internal Error processEntryPoint(phys_ptr<void> /* context */) { auto error = Error::OK; // Initialise logger internal::nimLog = decaf::makeLogger("IOS_NIM"); // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticBossServerData(); internal::initialiseStaticNimServerData(); // Initialise nn for current process nn::initialiseProcess(); // Initialise process heaps error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { internal::nimLog->error( "processEntryPoint: IOS_CreateLocalProcessHeap failed with error = {}", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { internal::nimLog->error( "processEntryPoint: IOS_CreateCrossProcessHeap failed with error = {}", error); return error; } // Start /dev/boss server error = internal::startBossServer(); if (error < Error::OK) { internal::nimLog->error("Failed to start boss server"); return error; } // Start /dev/nim server error = internal::startNimServer(); if (error < Error::OK) { internal::nimLog->error("Failed to start nim server"); return error; } internal::joinNimServer(); internal::joinBossServer(); return Error::OK; } } // namespace ios::nim ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::nim { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::nim ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_boss_privilegedservice.cpp ================================================ #include "ios_nim_boss_privilegedservice.h" #include "ios/nn/ios_nn_ipc_server_command.h" #include "nn/boss/nn_boss_result.h" #include "nn/ipc/nn_ipc_result.h" using namespace nn::boss; using nn::ipc::CommandHandlerArgs; using nn::ipc::CommandId; using nn::ipc::OutBuffer; using nn::ipc::ServerCommand; namespace ios::nim::internal { static nn::Result addAccount(CommandHandlerArgs &args) { auto command = ServerCommand<PrivilegedService::AddAccount> { args }; auto persistentId = PersistentId { 0 }; command.ReadRequest(persistentId); // TODO: Implement nn::boss::PrivilegedService::AddAccount return ResultSuccess; } nn::Result PrivilegedService::commandHandler(uint32_t unk1, CommandId command, CommandHandlerArgs &args) { switch (command) { case AddAccount::command: return addAccount(args); default: return nn::ipc::ResultInvalidMethodTag; } } } // namespace ios::nim::internal ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_boss_privilegedservice.h ================================================ #pragma once #include "ios/nn/ios_nn_ipc_server.h" #include "nn/boss/nn_boss_privileged_service.h" namespace ios::nim::internal { struct PrivilegedService : ::nn::boss::services::PrivilegedService { static nn::Result commandHandler(uint32_t unk1, nn::ipc::CommandId command, nn::ipc::CommandHandlerArgs &args); }; } // namespace ios::acp ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_boss_server.cpp ================================================ #include "ios_nim_log.h" #include "ios_nim_boss_server.h" #include "ios_nim_boss_privilegedservice.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" namespace ios::nim::internal { using namespace kernel; constexpr auto BossNumMessages = 0x64u; constexpr auto BossThreadStackSize = 0x4000u; constexpr auto BossThreadPriority = 50u; class BossServer : public nn::ipc::Server { public: BossServer() : nn::ipc::Server(true) { } }; struct StaticBossServerData { be2_struct<BossServer> server; be2_array<Message, BossNumMessages> messageBuffer; be2_array<uint8_t, BossThreadStackSize> threadStack; }; static phys_ptr<StaticBossServerData> sBossServerData = nullptr; Error startBossServer() { auto &server = sBossServerData->server; auto result = server.initialise("/dev/boss", phys_addrof(sBossServerData->messageBuffer), static_cast<uint32_t>(sBossServerData->messageBuffer.size())); if (result.failed()) { internal::nimLog->error( "startBossServer: Server initialisation failed for /dev/boss, result = {}", result.code()); return Error::FailInternal; } // TODO: Services 0, 2, 3, 4 server.registerService<PrivilegedService>(); result = server.start(phys_addrof(sBossServerData->threadStack) + sBossServerData->threadStack.size(), static_cast<uint32_t>(sBossServerData->threadStack.size()), BossThreadPriority); if (result.failed()) { internal::nimLog->error( "startBossServer: Server start failed for /dev/boss, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } Error joinBossServer() { sBossServerData->server.join(); return Error::OK; } void initialiseStaticBossServerData() { sBossServerData = allocProcessStatic<StaticBossServerData>(); } } // namespace ios::nim::internal ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_boss_server.h ================================================ #include "ios/ios_error.h" namespace ios::nim::internal { Error startBossServer(); Error joinBossServer(); void initialiseStaticBossServerData(); } // namespace ios::nim::internal ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_log.h ================================================ #pragma once #include <common/log.h> namespace ios::nim::internal { extern Logger nimLog; } // namespace ios::nim::internal ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_nim_server.cpp ================================================ #include "ios_nim_log.h" #include "ios_nim_nim_server.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/nn/ios_nn_ipc_server.h" namespace ios::nim::internal { using namespace kernel; constexpr auto NimNumMessages = 0x64u; constexpr auto NimThreadStackSize = 0x8000u; constexpr auto NimThreadPriority = 50u; class NimServer : public nn::ipc::Server { public: NimServer() : nn::ipc::Server(true) { } }; struct StaticNimServerData { be2_struct<NimServer> server; be2_array<Message, NimNumMessages> messageBuffer; be2_array<uint8_t, NimThreadStackSize> threadStack; }; static phys_ptr<StaticNimServerData> sNimServerData = nullptr; Error startNimServer() { auto &server = sNimServerData->server; auto result = server.initialise("/dev/nim", phys_addrof(sNimServerData->messageBuffer), static_cast<uint32_t>(sNimServerData->messageBuffer.size())); if (result.failed()) { internal::nimLog->error( "startMainServer: Server initialisation failed for /dev/nim, result = {}", result.code()); return Error::FailInternal; } // TODO: Services 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 result = server.start(phys_addrof(sNimServerData->threadStack) + sNimServerData->threadStack.size(), static_cast<uint32_t>(sNimServerData->threadStack.size()), NimThreadPriority); if (result.failed()) { internal::nimLog->error( "startMainServer: Server start failed for /dev/nim, result = {}", result.code()); return Error::FailInternal; } return Error::OK; } Error joinNimServer() { sNimServerData->server.join(); return Error::OK; } void initialiseStaticNimServerData() { sNimServerData = allocProcessStatic<StaticNimServerData>(); } } // namespace ios::nim::internal ================================================ FILE: src/libdecaf/src/ios/nim/ios_nim_nim_server.h ================================================ #pragma once #include "ios/ios_error.h" namespace ios::nim::internal { Error startNimServer(); Error joinNimServer(); void initialiseStaticNimServerData(); } // namespace ios::nim::internal ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn.cpp ================================================ #include "ios_nn.h" #include "ios_nn_criticalsection.h" namespace nn { void initialiseProcess() { internal::initialiseProcessCriticalSectionData(); } void uninitialiseProcess() { internal::freeProcessCriticalSectionData(); } } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn.h ================================================ #pragma once namespace nn { void initialiseProcess(); void uninitialiseProcess(); } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_criticalsection.cpp ================================================ #include "ios_nn_criticalsection.h" #include "ios_nn_tls.h" #include "ios/kernel/ios_kernel_semaphore.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_process.h" #include <array> #include <common/decaf_assert.h> using namespace ios::kernel; /** * nn implements N critical sections for a process using a few semaphores: * - 1 semaphore per process for critical section data members. * - 1 semaphore per process for condition variable data members. * - 1 semaphore per thread, allocated in TLS. */ namespace nn { class CriticalSection; struct CriticalSectionWaiter { SemaphoreId semaphore; CriticalSection *waitObject; CriticalSectionWaiter *prev; CriticalSectionWaiter *next; }; struct PerProcessCriticalSectionData { SemaphoreId criticalSectionSemaphore { -1 }; SemaphoreId conditionVariableSemaphore { -1 }; TlsEntry threadSemaphoreEntry { }; CriticalSectionWaiter *waiters { nullptr }; }; static std::array<PerProcessCriticalSectionData, NumIosProcess> sPerProcessCriticalSectionData { }; static PerProcessCriticalSectionData & getProcessCriticalSectionData() { auto error = IOS_GetCurrentProcessId(); decaf_check(error >= ::ios::Error::OK); return sPerProcessCriticalSectionData[static_cast<size_t>(error)]; } static SemaphoreId getCriticalSectionProcessSemaphore() { return getProcessCriticalSectionData().criticalSectionSemaphore; } static SemaphoreId getConditionVariableThreadSemaphore() { auto tls = tlsGetEntry(getProcessCriticalSectionData().threadSemaphoreEntry); return *phys_cast<SemaphoreId *>(tls); } bool CriticalSection::try_lock() { auto semaphore = getCriticalSectionProcessSemaphore(); auto result = false; IOS_WaitSemaphore(semaphore, FALSE); if (!mEntered) { result = true; mEntered = true; } IOS_SignalSempahore(semaphore); return result; } void CriticalSection::lock() { auto processData = getProcessCriticalSectionData(); // Fast path, try lock with no contention IOS_WaitSemaphore(processData.criticalSectionSemaphore, FALSE); if (!mEntered) { mEntered = true; IOS_SignalSempahore(processData.criticalSectionSemaphore); return; } mWaiters++; IOS_SignalSempahore(processData.criticalSectionSemaphore); // Contention path, wait on condition variable while (true) { waitWaiterConditionVariable(); IOS_WaitSemaphore(processData.criticalSectionSemaphore, FALSE); if (!mEntered) { --mWaiters; IOS_SignalSempahore(processData.criticalSectionSemaphore); return; } IOS_SignalSempahore(processData.criticalSectionSemaphore); } } void CriticalSection::unlock() { auto processData = getProcessCriticalSectionData(); auto wakeWaiters = false; IOS_WaitSemaphore(processData.criticalSectionSemaphore, FALSE); mEntered = false; if (mWaiters) { wakeWaiters = true; } IOS_SignalSempahore(processData.criticalSectionSemaphore); if (wakeWaiters) { signalWaiterConditionVariable(1); } } void CriticalSection::waitWaiterConditionVariable() { auto processData = getProcessCriticalSectionData(); IOS_WaitSemaphore(processData.conditionVariableSemaphore, FALSE); if (!mEntered) { auto waiter = CriticalSectionWaiter { getConditionVariableThreadSemaphore(), this, nullptr, nullptr }; if (processData.waiters) { waiter.next = processData.waiters->next; waiter.prev = processData.waiters; processData.waiters->next->prev = &waiter; processData.waiters->next = &waiter; } else { waiter.next = &waiter; waiter.prev = &waiter; processData.waiters = &waiter; } IOS_SignalSempahore(processData.conditionVariableSemaphore); IOS_WaitSemaphore(waiter.semaphore, FALSE); decaf_check(!waiter.next && !waiter.prev); IOS_WaitSemaphore(processData.conditionVariableSemaphore, FALSE); } IOS_SignalSempahore(processData.conditionVariableSemaphore); } void CriticalSection::signalWaiterConditionVariable(int wakeCount) { auto processData = getProcessCriticalSectionData(); IOS_WaitSemaphore(processData.conditionVariableSemaphore, FALSE); while (wakeCount) { if (!processData.waiters) { // No waiters break; } // Find a waiter for this wait object auto waiter = static_cast<CriticalSectionWaiter *>(nullptr); auto start = processData.waiters; auto end = processData.waiters->next; for (auto itr = start; itr && itr != end; itr = itr->prev) { decaf_check(itr->next && itr->prev); if (itr->waitObject == this) { waiter = itr; break; } } if (!waiter) { // No waiters for this wait object break; } // Remove waiter from queue if (waiter->next == waiter) { // This was the only item in queue processData.waiters = nullptr; } else { auto prev = waiter->prev; auto next = waiter->next; prev->next = next; next->prev = prev; } waiter->prev = nullptr; waiter->next = nullptr; --wakeCount; IOS_SignalSempahore(waiter->semaphore); } IOS_SignalSempahore(processData.conditionVariableSemaphore); } namespace internal { void initialiseProcessCriticalSectionData() { auto &data = getProcessCriticalSectionData(); data.criticalSectionSemaphore = IOS_CreateSemaphore(1, 1); data.conditionVariableSemaphore = IOS_CreateSemaphore(1, 1); tlsAllocateEntry(data.threadSemaphoreEntry, [](TlsEntryEvent event, phys_ptr<void> dst, phys_ptr<void> copySrc) -> uint32_t { auto data = phys_cast<SemaphoreId *>(dst); auto copyData = phys_cast<SemaphoreId *>(copySrc); switch (event) { case TlsEntryEvent::Create: *data = IOS_CreateSemaphore(1, 0); break; case TlsEntryEvent::Destroy: IOS_DestroySempahore(*data); break; case TlsEntryEvent::Copy: *data = *copyData; return 0u; case TlsEntryEvent::GetSize: return sizeof(SemaphoreId); } return sizeof(SemaphoreId); }); } void freeProcessCriticalSectionData() { auto &data = getProcessCriticalSectionData(); IOS_DestroySempahore(data.criticalSectionSemaphore); IOS_DestroySempahore(data.conditionVariableSemaphore); } } // namespace internal } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_criticalsection.h ================================================ #pragma once namespace nn { class CriticalSection { public: void lock(); bool try_lock(); void unlock(); private: void waitWaiterConditionVariable(); void signalWaiterConditionVariable(int wakeCount); private: bool mEntered = false; int mWaiters = 0; }; namespace internal { void initialiseProcessCriticalSectionData(); void freeProcessCriticalSectionData(); } // namespace internal } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_ipc_server.cpp ================================================ #include "ios_nn_ipc_server.h" #include "ios_nn_tls.h" #include "ios/ios_stackobject.h" #include "ios/acp/ios_acp_nnsm_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_timer.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/mcp/ios_mcp_ipc.h" #include "nn/ios/nn_ios_error.h" #include "nn/ipc/nn_ipc_format.h" using namespace ios; using namespace ios::acp; using namespace ios::kernel; using namespace ios::mcp; namespace nn::ipc { Result Server::initialise(std::string_view deviceName, phys_ptr<Message> messageBuffer, uint32_t numMessages) { auto error = IOS_CreateMessageQueue(messageBuffer, numMessages); if (error < ::ios::Error::OK) { return ::nn::ios::convertError(error); } mQueueId = static_cast<MessageQueueId>(error); error = IOS_CreateTimer(std::chrono::microseconds(0), std::chrono::microseconds(0), mQueueId, mTimerMessage); if (error < ::ios::Error::OK) { return ::nn::ios::convertError(error); } mTimer = static_cast<TimerId>(error); if (mIsMcpResourceManager) { error = MCP_RegisterResourceManager(deviceName, mQueueId); } else { error = IOS_RegisterResourceManager(deviceName, mQueueId); } mDeviceName = deviceName; return ::nn::ios::convertError(error); } void Server::registerService(ServiceId serviceId, CommandHandler commandHandler) { for (auto &service : mServices) { if (!service.handler) { service.id = serviceId; service.handler = commandHandler; break; } } } Error Server::threadEntryWrapper(phys_ptr<void> ptr) { auto self = phys_cast<Server *>(ptr); return static_cast<Error>(self->threadEntry().code()); } Result Server::waitForResume() { StackObject<Message> message; // Read Open auto error = IOS_ReceiveMessage(mQueueId, message, MessageFlags::None); if (error < Error::OK) { return ::nn::ios::convertError(error); } auto request = parseMessage<ResourceRequest>(message); if (request->requestData.command != ::ios::Command::Open) { return ::nn::ios::convertError(Error::FailInternal); } if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) { return ::nn::ios::convertError(error); } // Read Resume error = IOS_ReceiveMessage(mQueueId, message, MessageFlags::None); if (error < Error::OK) { return ::nn::ios::convertError(error); } request = parseMessage<ResourceRequest>(message); if (request->requestData.command != ::ios::Command::Resume) { return ::nn::ios::convertError(Error::FailInternal); } mResumeArgs = request->ipcRequest->args.resume; if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) { return ::nn::ios::convertError(error); } return ::nn::ios::convertError(Error::OK); } Error Server::openDeviceHandle(uint64_t caps, ProcessId processId) { for (auto &deviceHandle : mDeviceHandles) { if (deviceHandle.open) { continue; } deviceHandle.open = true; deviceHandle.caps = caps; deviceHandle.processId = processId; auto index = &deviceHandle - &mDeviceHandles[0]; return static_cast<Error>(index); } return Error::Max; } Error Server::closeDeviceHandle(DeviceHandleId handleId) { if (handleId >= mDeviceHandles.size()) { return Error::InvalidHandle; } auto &deviceHandle = mDeviceHandles[handleId]; if (!deviceHandle.open) { return Error::InvalidHandle; } deviceHandle.open = false; deviceHandle.processId = ProcessId::Invalid; deviceHandle.caps = 0ull; return Error::OK; } Error Server::handleMessage(phys_ptr<ResourceRequest> request) { auto &ioctlv = request->requestData.args.ioctlv; if (!ioctlv.numVecOut || ioctlv.vecs[ioctlv.numVecIn].len < sizeof(RequestHeader) || ioctlv.vecs[0].len < sizeof(ResponseHeader)) { return Error::InvalidArg; } auto requestHeader = phys_cast<RequestHeader *>(ioctlv.vecs[ioctlv.numVecIn].paddr); auto responseHeader = phys_cast<ResponseHeader *>(ioctlv.vecs[0].paddr); CommandHandlerArgs args; args.resourceRequest = request; args.requestBuffer = requestHeader + 1; args.requestBufferSize = ioctlv.vecs[ioctlv.numVecIn].len - sizeof(RequestHeader); args.responseBuffer = responseHeader + 1; args.responseBufferSize = ioctlv.vecs[0].len - sizeof(ResponseHeader); args.numVecsIn = ioctlv.numVecIn; args.numVecsOut = ioctlv.numVecOut; args.vecs = ioctlv.vecs; for (auto &service : mServices) { if (service.id != static_cast<ServiceId>(requestHeader->service)) { continue; } auto result = service.handler(requestHeader->unk0x08, requestHeader->command, args); responseHeader->result = result.code(); return Error::OK; } return Error::InvalidArg; } Result Server::runMessageLoop() { StackObject<Message> message; auto error = Error::OK; while (true) { mMutex.lock(); error = IOS_ReceiveMessage(mQueueId, message, MessageFlags::None); auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case ::ios::Command::Open: { error = openDeviceHandle(request->requestData.args.open.caps, request->requestData.processId); IOS_ResourceReply(request, error); break; } case ::ios::Command::Close: error = closeDeviceHandle(static_cast<DeviceHandleId>(request->requestData.handle)); IOS_ResourceReply(request, error); break; case ::ios::Command::Ioctlv: error = handleMessage(request); IOS_ResourceReply(request, error); break; default: IOS_ResourceReply(request, Error::Invalid); } mMutex.unlock(); } return error; } Result Server::resolvePendingMessages() { return Error::OK; } Result Server::threadEntry() { if (mResourcePermissionGroup != ResourcePermissionGroup::None) { IOS_AssociateResourceManager(mDeviceName, mResourcePermissionGroup); } if (auto result = waitForResume(); result.failed()) { return result; } NNSM_RegisterServer(mDeviceName); intialiseServer(); auto result = runMessageLoop(); finaliseServer(); resolvePendingMessages(); NNSM_UnregisterServer(mDeviceName); return result; } Result Server::start(phys_ptr<uint8_t> stackTop, uint32_t stackSize, kernel::ThreadPriority priority) { return mThread.start(Server::threadEntryWrapper, phys_this(this), stackTop, stackSize, priority); } void Server::join() { mThread.join(); } } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_ipc_server.h ================================================ #pragma once #include "ios_nn_recursivemutex.h" #include "ios_nn_thread.h" #include "ios/ios_enum.h" #include "ios/ios_ipc.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_timer.h" #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_service.h" #include "nn/nn_result.h" #include <array> #include <cstdint> #include <string> #include <string_view> #include <vector> namespace nn::ipc { struct CommandHandlerArgs { phys_ptr<ios::kernel::ResourceRequest> resourceRequest; phys_ptr<void> requestBuffer; uint32_t requestBufferSize; phys_ptr<void> responseBuffer; uint32_t responseBufferSize; uint32_t numVecsIn; uint32_t numVecsOut; phys_ptr<::ios::IoctlVec> vecs; }; using DeviceHandleId = uint32_t; using CommandHandler = Result(*)(uint32_t unk1, CommandId command, CommandHandlerArgs &args); class Server { static constexpr auto MaxNumDeviceHandles = 32u; static constexpr auto MaxNumServices = 32u; public: struct DeviceHandle { bool open = false; uint64_t caps; ::ios::ProcessId processId; RecursiveMutex mutex; }; struct RegisteredService { ServiceId id; CommandHandler handler = nullptr; }; Server(bool isMcpResourceManager = false, ::ios::kernel::ResourcePermissionGroup group = ::ios::kernel::ResourcePermissionGroup::None) : mIsMcpResourceManager(isMcpResourceManager), mResourcePermissionGroup(group) { } Result initialise(std::string_view deviceName, phys_ptr<::ios::kernel::Message> messageBuffer, uint32_t numMessages); Result start(phys_ptr<uint8_t> stackTop, uint32_t stackSize, ::ios::kernel::ThreadPriority priority); void join(); void registerService(ServiceId serviceId, CommandHandler commandHandler); template<typename ServiceType> void registerService() { registerService(ServiceType::id, ServiceType::commandHandler); } protected: static ::ios::Error threadEntryWrapper(phys_ptr<void> ptr); Result threadEntry(); Result waitForResume(); Result runMessageLoop(); Result resolvePendingMessages(); virtual void intialiseServer() { } virtual void finaliseServer() { } ::ios::Error openDeviceHandle(uint64_t caps, ::ios::ProcessId processId); ::ios::Error closeDeviceHandle(DeviceHandleId handleId); ::ios::Error handleMessage(phys_ptr<::ios::kernel::ResourceRequest> request); protected: bool mIsMcpResourceManager; ::ios::kernel::MessageQueueId mQueueId = -1; ::ios::kernel::TimerId mTimer = -1; ::ios::kernel::Message mTimerMessage = static_cast<::ios::kernel::Message>(-32); Thread mThread; std::string mDeviceName; std::array<DeviceHandle, MaxNumDeviceHandles> mDeviceHandles; ::ios::kernel::ResourcePermissionGroup mResourcePermissionGroup; RecursiveMutex mMutex; std::array<RegisteredService, MaxNumServices> mServices; ::ios::IpcRequestArgsResume mResumeArgs; }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_ipc_server_command.h ================================================ #pragma once #include "ios_nn_ipc_server.h" #include "nn/ipc/nn_ipc_format.h" #include "nn/ipc/nn_ipc_managedbuffer.h" namespace nn::ipc { namespace detail { template<typename Type> struct IpcDeserialiser { static void read(const CommandHandlerArgs &args, size_t &offset, Type &value) { auto ptr = phys_cast<Type *>(phys_cast<phys_addr>(args.requestBuffer) + offset); value = *ptr; offset += sizeof(Type); } }; template<> struct IpcDeserialiser<ManagedBuffer> { static void read(const CommandHandlerArgs &args, size_t &offset, ManagedBuffer &value) { auto managedBuffer = phys_cast<ManagedBufferParameter *>( phys_cast<phys_addr>(args.requestBuffer) + offset); auto alignedBufferIndex = 1 + managedBuffer->alignedBufferIndex; auto unalignedBufferIndex = 1 + managedBuffer->unalignedBufferIndex; value.alignedBuffer = phys_cast<void *>(args.vecs[alignedBufferIndex].paddr); value.alignedBufferSize = managedBuffer->alignedBufferSize; value.unalignedBeforeBuffer = phys_cast<void *>(args.vecs[unalignedBufferIndex].paddr); value.unalignedBeforeBufferSize = managedBuffer->unalignedBeforeBufferSize; value.unalignedAfterBuffer = phys_cast<void *>(args.vecs[unalignedBufferIndex].paddr + managedBuffer->unalignedBeforeBufferSize); value.unalignedAfterBufferSize = managedBuffer->unalignedAfterBufferSize; offset += sizeof(ManagedBufferParameter); } }; template<typename Type> struct IpcDeserialiser<ipc::InBuffer<Type>> { static void read(const CommandHandlerArgs &args, size_t &offset, ipc::InBuffer<Type> &value) { IpcDeserialiser<ManagedBuffer>::read(args, offset, value); } }; template<typename Type> struct IpcDeserialiser<ipc::InOutBuffer<Type>> { static void read(const CommandHandlerArgs &args, size_t &offset, ipc::InOutBuffer<Type> &value) { IpcDeserialiser<ManagedBuffer>::read(args, offset, value); } }; template<typename Type> struct IpcDeserialiser<ipc::OutBuffer<Type>> { static void read(const CommandHandlerArgs &args, size_t &offset, ipc::OutBuffer<Type> &value) { IpcDeserialiser<ManagedBuffer>::read(args, offset, value); } }; template<typename Type> struct IpcSerialiser { static void write(const CommandHandlerArgs &args, size_t &offset, const Type &value) { auto ptr = phys_cast<Type *>(phys_cast<phys_addr>(args.responseBuffer) + offset); *ptr = value; offset += sizeof(Type); } }; template<int, int, typename... Types> struct ServerCommandHelper; template<int ServiceId, int CommandId, typename... ParameterTypes, typename... ResponseTypes> struct ServerCommandHelper<ServiceId, CommandId, std::tuple<ParameterTypes...>, std::tuple<ResponseTypes...>> { ServerCommandHelper(const CommandHandlerArgs &args) : mArgs(args) { } void ReadRequest(ParameterTypes &... parameters) { auto offset = size_t { 0 }; (IpcDeserialiser<ParameterTypes>::read(mArgs, offset, parameters), ...); } void WriteResponse(const ResponseTypes &... responses) { auto offset = size_t { 0 }; (IpcSerialiser<ResponseTypes>::write(mArgs, offset, responses), ...); } const CommandHandlerArgs &mArgs; }; } // namespace detail template<typename CommandType> struct ServerCommand; template<typename CommandType> struct ServerCommand : detail::ServerCommandHelper<CommandType::service, CommandType::command, typename CommandType::parameters, typename CommandType::response> { ServerCommand(const CommandHandlerArgs &args) : detail::ServerCommandHelper<CommandType::service, CommandType::command, typename CommandType::parameters, typename CommandType::response>(args) { } }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_recursivemutex.cpp ================================================ #include "ios_nn_recursivemutex.h" #include "ios/kernel/ios_kernel_thread.h" #include <common/decaf_assert.h> using namespace ios::kernel; namespace nn { void RecursiveMutex::lock() { auto threadId = IOS_GetCurrentThreadId(); if (mRecursionCount == 0) { mOwnerThread = threadId; } if (mOwnerThread != static_cast<ThreadId>(threadId)) { mCriticalSection.lock(); mOwnerThread = threadId; decaf_check(mRecursionCount == 0); } ++mRecursionCount; } bool RecursiveMutex::try_lock() { auto threadId = IOS_GetCurrentThreadId(); if (mRecursionCount == 0) { mOwnerThread = threadId; } if (mOwnerThread != static_cast<ThreadId>(threadId)) { return false; } ++mRecursionCount; return true; } void RecursiveMutex::unlock() { decaf_check(locked()); --mRecursionCount; if (mRecursionCount == 0) { mOwnerThread = static_cast<ThreadId>(-1); mCriticalSection.unlock(); } } bool RecursiveMutex::locked() { return mRecursionCount > 0 && mOwnerThread == static_cast<ThreadId>(IOS_GetCurrentThreadId()); } } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_recursivemutex.h ================================================ #pragma once #include "ios_nn_criticalsection.h" #include "ios/kernel/ios_kernel_thread.h" namespace nn { class RecursiveMutex { public: void lock(); bool try_lock(); void unlock(); bool locked(); private: int mRecursionCount = 0; ios::kernel::ThreadId mOwnerThread = static_cast<ios::kernel::ThreadId>(-1); CriticalSection mCriticalSection; }; } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_thread.cpp ================================================ #include "ios_nn_thread.h" #include "ios_nn_tls.h" #include "ios/kernel/ios_kernel_thread.h" #include "nn/ios/nn_ios_error.h" #include <common/decaf_assert.h> using namespace ios::kernel; namespace nn { Thread::Thread(Thread &&other) noexcept { mThreadId = other.mThreadId; mJoined = other.mJoined; other.mThreadId = static_cast<native_handle_type>(-1); other.mJoined = true; } Thread::~Thread() { decaf_check(!joinable()); } struct ThreadStartData { ThreadEntryFn entry; phys_ptr<void> context; phys_ptr<void> tlsData; }; static ::ios::Error threadEntryPoint(phys_ptr<void> arg) { auto startData = phys_cast<ThreadStartData *>(arg); tlsInitialiseThread(startData->tlsData); auto result = startData->entry(startData->context); tlsDestroyData(startData->tlsData); return result; } Result Thread::start(ThreadEntryFn entry, phys_ptr<void> context, phys_ptr<uint8_t> stackTop, uint32_t stackSize, ThreadPriority priority) { auto tlsDataSize = tlsGetDataSize(); auto userStackTop = stackTop; // Allocate TLS data from stack stackTop = align_down(stackTop - tlsDataSize, 8); auto tlsData = phys_cast<void *>(stackTop); tlsInitialiseData(tlsData); // Allocate thread start context from stack stackTop = align_down(stackTop - sizeof(ThreadStartData), 8); auto threadStartData = phys_cast<ThreadStartData *>(stackTop); threadStartData->entry = entry; threadStartData->context = context; threadStartData->tlsData = tlsData; // Calculate new stack size stackSize -= static_cast<uint32_t>(userStackTop - stackTop); auto error = IOS_CreateThread(threadEntryPoint, threadStartData, stackTop, stackSize, priority, ThreadFlags::AllocateTLS); if (error < ::ios::Error::OK) { return ios::convertError(error); } mThreadId = static_cast<ThreadId>(error); error = IOS_StartThread(mThreadId); if (error < ::ios::Error::OK) { return ios::convertError(error); } mJoined = false; return ios::ResultOK; } void Thread::join() { if (joinable()) { IOS_JoinThread(mThreadId, nullptr); mJoined = true; } } void Thread::detach() { mThreadId = static_cast<native_handle_type>(-1); mJoined = true; } void Thread::swap(Thread &other) noexcept { std::swap(mThreadId, other.mThreadId); std::swap(mJoined, other.mJoined); } Thread::id Thread::get_id() const noexcept { return mThreadId; } Thread::native_handle_type Thread::native_handle() const noexcept { return mThreadId; } bool Thread::joinable() const noexcept { return mThreadId != -1; } } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_thread.h ================================================ #pragma once #include "ios/kernel/ios_kernel_thread.h" #include "nn/nn_result.h" namespace nn { class Thread { public: using id = ::ios::kernel::ThreadId; using native_handle_type = ::ios::kernel::ThreadId; Thread() noexcept = default; Thread(const Thread &) = delete; Thread(Thread &&other) noexcept; ~Thread(); Result start(::ios::kernel::ThreadEntryFn entry, phys_ptr<void> context, phys_ptr<uint8_t> stackTop, uint32_t stackSize, ::ios::kernel::ThreadPriority priority); void join(); void detach(); void swap(Thread &other) noexcept; id get_id() const noexcept; native_handle_type native_handle() const noexcept; bool joinable() const noexcept; static unsigned int hardware_concurrency() noexcept { return 1; } private: native_handle_type mThreadId = static_cast<native_handle_type>(-1); bool mJoined = true; }; } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_tls.cpp ================================================ #include "ios_nn_tls.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_thread.h" #include <array> #include <libcpu/be2_struct.h> using namespace ios::kernel; namespace nn { struct NnThreadLocalStorage { static constexpr auto Magic = 0x94826575u; UNKNOWN(0x4); be2_phys_ptr<void> tlsData; be2_val<uint32_t> magic; UNKNOWN(0x24 - 0x0C); }; CHECK_OFFSET(NnThreadLocalStorage, 0x04, tlsData); CHECK_OFFSET(NnThreadLocalStorage, 0x08, magic); CHECK_SIZE(NnThreadLocalStorage, 0x24); struct TlsTable { TlsEntry *entries = nullptr; uint32_t dataSize = 0u; }; static std::array<TlsTable, NumIosProcess> sProcessTlsTables; static TlsTable * getTlsTable() { auto error = IOS_GetCurrentProcessId(); if (error < ::ios::Error::OK) { return nullptr; } return &sProcessTlsTables[static_cast<size_t>(error)]; } /** * Allocate a TLS entry for the current process. */ void tlsAllocateEntry(TlsEntry &entry, TlsEntryEventFn eventCallback, bool supportsCopy) { auto table = getTlsTable(); // Initialise entry entry.eventFn = eventCallback; entry.supportsCopy = supportsCopy; // Assign data position auto size = eventCallback(TlsEntryEvent::GetSize, nullptr, nullptr); entry.dataOffset = table->dataSize; table->dataSize += size; // Insert into table entry.next = table->entries; table->entries = &entry; } /** * Get the data size of all TLS entries in the current process. * * This should be the size of the data passed to tlsInitialiseData. */ uint32_t tlsGetDataSize() { return getTlsTable()->dataSize; } /** * Initialise a TLS data block for the current process. */ void tlsInitialiseData(phys_ptr<void> data, phys_ptr<void> copySrc) { auto table = getTlsTable(); if (copySrc) { decaf_abort("TODO: Implement tlsInitialiseData for copies"); } auto dataAddr = phys_cast<phys_addr>(data); std::memset(data.get(), 0, table->dataSize); for (auto entry = table->entries; entry; entry = entry->next) { entry->eventFn(TlsEntryEvent::Create, phys_cast<void *>(dataAddr + entry->dataOffset), nullptr); } } /** * Clean up a TLS data block for the current process. */ void tlsDestroyData(phys_ptr<void> data) { auto table = getTlsTable(); auto dataAddr = phys_cast<phys_addr>(data); for (auto entry = table->entries; entry; entry = entry->next) { entry->eventFn(TlsEntryEvent::Destroy, phys_cast<void *>(dataAddr + entry->dataOffset), nullptr); } } /** * Initialise the current thread's local storage. */ void tlsInitialiseThread(phys_ptr<void> data) { auto tls = phys_cast<NnThreadLocalStorage *>(IOS_GetCurrentThreadLocalStorage()); tls->magic = NnThreadLocalStorage::Magic; tls->tlsData = data; } /** * Get the data pointer for the current thread's TLS entry. */ phys_ptr<void> tlsGetEntry(TlsEntry &entry) { auto tls = phys_cast<NnThreadLocalStorage *>(IOS_GetCurrentThreadLocalStorage()); if (!tls || !tls->tlsData) { return nullptr; } return phys_cast<void *>(phys_cast<phys_addr>(tls->tlsData) + entry.dataOffset); } } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nn/ios_nn_tls.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace nn { enum class TlsEntryEvent { GetSize = 0, Create = 1, Destroy = 2, Copy = 3, }; using TlsEntryEventFn = uint32_t(*)(TlsEntryEvent event, phys_ptr<void> dst, phys_ptr<void> copySrc); struct TlsEntry { uint32_t dataOffset; TlsEntry *next; TlsEntryEventFn eventFn = nullptr; bool supportsCopy; }; /* CHECK_OFFSET(TlsEntry, 0x0, dataOffset); CHECK_OFFSET(TlsEntry, 0x4, next); CHECK_OFFSET(TlsEntry, 0x8, entryEventFn); CHECK_OFFSET(TlsEntry, 0xC, unk0x0c); // I think this is maybe a can copy flag CHECK_SIZE(TlsEntry, 0x10); */ void tlsAllocateEntry(TlsEntry &entry, TlsEntryEventFn eventCallback, bool supportsCopy = false); uint32_t tlsGetDataSize(); void tlsInitialiseData(phys_ptr<void> data, phys_ptr<void> copySrc = nullptr); void tlsDestroyData(phys_ptr<void> data); void tlsInitialiseThread(phys_ptr<void> data); phys_ptr<void> tlsGetEntry(TlsEntry &entry); } // namespace nn ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec.cpp ================================================ #include "ios_nsec.h" #include "ios_nsec_log.h" #include "ios_nsec_nssl_certstore.h" #include "ios_nsec_nssl_thread.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/ios_enum.h" #include "ios/ios_stackobject.h" #include <common/log.h> #include <libcpu/be2_struct.h> using namespace ios::kernel; namespace ios::nsec { constexpr auto LocalHeapSize = 0x140000u; constexpr auto CrossHeapSize = 0x200000u; constexpr auto InitThreadStackSize = 0x1000u; constexpr auto InitThreadPriority = 50u; struct StaticNsecData { be2_val<BOOL> started; be2_array<uint8_t, InitThreadStackSize> threadStack; be2_val<ThreadId> initThreadId; }; static phys_ptr<StaticNsecData> sNsecData = nullptr; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger nsecLog = { }; static void initialiseStaticData() { sNsecData = allocProcessStatic<StaticNsecData>(); sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize); } static Error initThread(phys_ptr<void> /*context*/) { // Some FSA stuff // Some cert stuff // Start nssl thread auto error = startNsslThread(); if (error < Error::OK) { return error; } // Start nss thread return Error::OK; } } // namespace internal Error processEntryPoint(phys_ptr<void> context) { StackArray<Message, 10> messageBuffer; StackObject<Message> message; // Initialise logger internal::nsecLog = decaf::makeLogger("IOS_NSEC"); // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticNsslData(); internal::initialiseStaticCertStoreData(); // Initialise process heaps auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { internal::nsecLog->error("NSEC: Failed to create local process heap, error = {}.", error); return error; } error = IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { internal::nsecLog->error("NSEC: Failed to create cross process heap, error = {}.", error); return error; } error = internal::registerNsslResourceManager(); if (error < Error::OK) { internal::nsecLog->error("NSEC: Failed to register NSSL resource manager, error = {}.", error); return error; } // TODO: registerNssResourceManager // Setup nsec error = IOS_CreateMessageQueue(messageBuffer, messageBuffer.size()); if (error < Error::OK) { internal::nsecLog->error("NSEC: Failed to create nsec proc message queue, error = {}.", error); return error; } auto messageQueueId = static_cast<MessageQueueId>(error); error = mcp::MCP_RegisterResourceManager("/dev/nsec", messageQueueId); if (error < Error::OK) { internal::nsecLog->error("NSEC: Failed to register /dev/nsec, error = {}.", error); return error; } // Run nsec while (true) { error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { IOS_ResourceReply(request, Error::OK); break; } case Command::Suspend: { if (sNsecData->started) { internal::stopNsslThread(); // internal::stopNssThread(); sNsecData->started = FALSE; } IOS_ResourceReply(request, Error::OK); break; } case Command::Resume: { if (sNsecData->started) { IOS_ResourceReply(request, Error::OK); } else { error = IOS_CreateThread(&internal::initThread, nullptr, phys_addrof(sNsecData->threadStack) + sNsecData->threadStack.size(), sNsecData->threadStack.size(), InitThreadPriority, ThreadFlags::Detached); if (error >= 0) { sNsecData->initThreadId = static_cast<ThreadId>(error); error = IOS_StartThread(sNsecData->initThreadId); } if (error >= 0) { IOS_ResourceReply(request, Error::OK); sNsecData->started = TRUE; } else { IOS_ResourceReply(request, Error::Access); } } break; } default: IOS_ResourceReply(request, Error::Invalid); } } } } // namespace ios::nsec ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::nsec { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::nsec ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_enum.h ================================================ #ifndef IOS_NSEC_ENUM_H #define IOS_NSEC_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(nsec) FLAGS_BEG(NSSLCertProperties, uint32_t) FLAGS_VALUE(HasSecondPath, 1 << 0) FLAGS_VALUE(Exportable, 1 << 1) FLAGS_VALUE(Encrypted, 1 << 2) FLAGS_END(NSSLCertProperties) ENUM_BEG(NSSLCertType, int32_t) ENUM_VALUE(Unknown0, 0) ENUM_END(NSSLCertType) ENUM_BEG(NSSLCertEncoding, int32_t) ENUM_VALUE(Unknown1, 1) ENUM_END(NSSLCertEncoding) ENUM_BEG(NSSLCommand, uint32_t) ENUM_VALUE(CreateContext, 1) ENUM_VALUE(DestroyContext, 2) ENUM_VALUE(SetClientPKI, 3) ENUM_VALUE(SetClientPKIExternal, 4) ENUM_VALUE(AddServerPKI, 5) ENUM_VALUE(AddServerPKIGroups, 6) ENUM_VALUE(AddServerPKIExternal, 7) ENUM_VALUE(CreateConnection, 8) ENUM_VALUE(DestroyConnection, 9) ENUM_VALUE(DoHandshake, 10) ENUM_VALUE(Read, 11) ENUM_VALUE(Write, 12) ENUM_VALUE(GetSession, 13) ENUM_VALUE(SetSession, 14) ENUM_VALUE(FreeSession, 15) ENUM_VALUE(GetPending, 16) ENUM_VALUE(GetPeerCertSize, 17) ENUM_VALUE(GetPeerCert, 18) ENUM_VALUE(GetCipherInfo, 19) ENUM_VALUE(RemoveSession, 20) ENUM_VALUE(NSECEncrypt, 21) ENUM_VALUE(ExportInternalServerCertificate, 22) ENUM_VALUE(ExportInternalClientCertificate, 23) ENUM_END(NSSLCommand) ENUM_BEG(NSSLError, int32_t) ENUM_VALUE(OK, 0) ENUM_VALUE(Generic, -1) ENUM_VALUE(InvalidNsslContext, -2621441) ENUM_VALUE(InvalidCertID, -2621442) ENUM_VALUE(CertLimit, -2621443) ENUM_VALUE(InvalidNsslConnection, -2621444) ENUM_VALUE(InvalidCert, -2621445) ENUM_VALUE(ZeroReturn, -2621446) ENUM_VALUE(WantRead, -2621447) ENUM_VALUE(WantWrite, -2621448) ENUM_VALUE(IoError, -2621449) ENUM_VALUE(NsslLibError, -2621450) ENUM_VALUE(Unknown, -2621451) ENUM_VALUE(OutOfMemory, -2621452) ENUM_VALUE(InvalidState, -2621453) ENUM_VALUE(HandshakeError, -2621454) ENUM_VALUE(NoCert, -2621455) ENUM_VALUE(InvalidFd, -2621456) ENUM_VALUE(LibNotReady, -2621457) ENUM_VALUE(IpcError, -2621458) ENUM_VALUE(ResourceLimit, -2621459) ENUM_VALUE(InvalidHandle, -2621460) ENUM_VALUE(InvalidCertType, -2621461) ENUM_VALUE(InvalidKeyType, -2621462) ENUM_VALUE(InvalidSize, -2621463) ENUM_VALUE(NoPeerCert, -2621464) ENUM_VALUE(InsufficientSize, -2621465) ENUM_VALUE(NoCipher, -2621466) ENUM_VALUE(InvalidArg, -2621467) ENUM_VALUE(InvalidNsslSession, -2621468) ENUM_VALUE(NoSession, -2621469) ENUM_VALUE(SslShutdownError, -2621470) ENUM_VALUE(CertSizeLimit, -2621471) ENUM_VALUE(CertNoAccess, -2621472) ENUM_VALUE(InvalidCertId2, -2621473) ENUM_VALUE(CertReadError, -2621474) ENUM_VALUE(CertStoreInitFailure, -2621475) ENUM_VALUE(InvalidCertEncoding, -2621476) ENUM_VALUE(CertStoreError, -2621477) ENUM_VALUE(PrivateKeyReadError, -2621478) ENUM_VALUE(InvalidPrivateKey, -2621479) ENUM_VALUE(NotReady, -2621480) ENUM_VALUE(EncryptionError, -2621481) ENUM_VALUE(NoCertStore, -2621482) ENUM_VALUE(PrivateKeySizeLimit, -2621483) ENUM_VALUE(ProcessMaxExtCerts, -2621484) ENUM_VALUE(ProcessMaxContexts, -2621485) ENUM_VALUE(ProcessMaxConnections, -2621486) ENUM_VALUE(CertNotExportable, -2621487) ENUM_VALUE(InvalidCertSize, -2621488) ENUM_VALUE(InvalidKeySize, -2621489) ENUM_END(NSSLError) ENUM_BEG(NSSLPrivateKeyType, int32_t) ENUM_VALUE(Unknown0, 0) ENUM_END(NSSLPrivateKeyType) ENUM_BEG(NSSLVersion, uint32_t) ENUM_VALUE(Auto, 0) ENUM_END(NSSLVersion) ENUM_NAMESPACE_EXIT(nsec) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_NSEC_ENUM_H ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_log.h ================================================ #pragma once #include <common/log.h> namespace ios::nsec::internal { extern Logger nsecLog; } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl.h ================================================ #pragma once #include "ios_nsec_enum.h" #include "ios_nsec_nssl_request.h" #include "ios_nsec_nssl_response.h" #include "ios_nsec_nssl_types.h" ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_certstore.cpp ================================================ #include "ios_nsec_nssl_certstore.h" #include "ios_nsec_log.h" #include "ios/fs/ios_fs_fsa_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/ios_error.h" #include "ios/ios_stackobject.h" #include <array> #include <charconv> #include <common/strutils.h> #include <gsl/gsl-lite.hpp> #include <string_view> using namespace ios::fs; using namespace ios::kernel; namespace ios::nsec::internal { struct StaticCertStoreData { be2_val<FSAHandle> fsaHandle; be2_array<CertStoreMetaData, MaxNumCertificates> metaData; }; static phys_ptr<StaticCertStoreData> sCertStoreData = nullptr; bool checkCertPermission(phys_ptr<CertStoreMetaData> certMetaData, TitleId titleId, ProcessId processId, uint64_t caps) { if (caps & certMetaData->capabilityMask) { return true; } for (auto tid : certMetaData->titleIds) { if (tid == -1) { break; } if (tid == titleId) { return true; } } for (auto pid : certMetaData->processIds) { if (pid == static_cast<ProcessId>(-1)) { break; } else if (pid == static_cast<ProcessId>(-4)) { return true; } else if (pid == static_cast<ProcessId>(-2) && processId < ProcessId::TEST) { return true; } else if (pid == static_cast<ProcessId>(-3) && processId >= ProcessId::COSKERNEL) { return true; } else if (processId == ProcessId::TEST) { return true; } else if (processId == pid) { return true; } } return false; } bool checkCertExportable(phys_ptr<CertStoreMetaData> certMetaData) { return !!(certMetaData->properties & NSSLCertProperties::Exportable); } std::optional<uint32_t> getCertFileSize(phys_ptr<CertStoreMetaData> certMetaData, int32_t pathIndex) { if (pathIndex >= certMetaData->numPaths) { return {}; } if (pathIndex > 0 && !(certMetaData->properties & NSSLCertProperties::HasSecondPath)) { return {}; } auto path = std::string { "/vol/storage_mlc01/sys/title/0005001b/10054000/content/" }; if (pathIndex == 0) { path += phys_addrof(certMetaData->path1).get(); } else if (pathIndex == 1) { path += phys_addrof(certMetaData->path2).get(); } auto stat = StackObject<FSAStat> { }; auto error = FSAGetStat(sCertStoreData->fsaHandle, path, stat); if (error < FSAStatus::OK) { return {}; } return static_cast<uint32_t>(stat->size); } std::optional<uint32_t> getCertFileData(phys_ptr<CertStoreMetaData> certMetaData, int32_t pathIndex, phys_ptr<void> certBuffer, uint32_t certBufferSize) { // Read file data 4096 bytes at a time into allocated buffer copying into certbuiffer auto heapBufferSize = std::min(4096u, certBufferSize); auto heapBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, heapBufferSize, 0x40u); if (!heapBuffer) { return {}; } auto _ = gsl::finally([&]() { IOS_HeapFree(CrossProcessHeapId, heapBuffer); }); auto path = std::string { "/vol/storage_mlc01/sys/title/0005001b/10054000/content/" }; if (pathIndex == 0) { path += phys_addrof(certMetaData->path1).get(); } else if (pathIndex == 1) { path += phys_addrof(certMetaData->path2).get(); } auto fileHandle = FSAFileHandle { -1 }; auto error = FSAOpenFile(sCertStoreData->fsaHandle, path, "r", &fileHandle); if (error < FSAStatus::OK) { return {}; } auto _2 = gsl::finally([&]() { FSACloseFile(sCertStoreData->fsaHandle, fileHandle); }); auto certSize = 0; while (true) { error = FSAReadFile(sCertStoreData->fsaHandle, heapBuffer, 1, heapBufferSize, fileHandle, FSAReadFlag::None); if (error < 0) { break; } auto bytesRead = static_cast<uint32_t>(error); std::memcpy(reinterpret_cast<uint8_t *>(certBuffer.get()) + certSize, heapBuffer.get(), error); certSize += bytesRead; if (bytesRead < heapBufferSize) { // Reached EOF break; } } if (error < FSAStatus::OK && error != FSAStatus::EndOfFile) { return {}; } return certSize; } phys_ptr<CertStoreMetaData> lookupCertMetaData(NSSLCertID id) { for (auto &cert : sCertStoreData->metaData) { if (cert.id == id) { return phys_addrof(cert); } else if (cert.id == -1) { break; } } return nullptr; } Error loadCertstoreMetadata() { auto openError = FSAOpen(); if (openError < Error::OK) { nsecLog->warn("loadCertstoreMetadata: FSAOpen failed with error {}", openError); return openError; } sCertStoreData->fsaHandle = static_cast<FSAHandle>(openError); // TODO: Use MCP_SearchTitle to find title path auto path = "/vol/storage_mlc01/sys/title/0005001b/10054000/content/certstore_metadata.txt"; auto stat = StackObject<FSAStat> { }; auto error = FSAGetStat(sCertStoreData->fsaHandle, path, stat); if (error < FSAStatus::OK) { nsecLog->warn("loadCertstoreMetadata: FSAStat failed with error {} on {}", error, path); return static_cast<Error>(error); } auto size = stat->size ? stat->size.value() : 10240u; auto fileBuffer = IOS_HeapAlloc(LocalProcessHeapId, size); if (!fileBuffer) { nsecLog->warn("loadCertstoreMetadata: Failed to allocate file buffer of size {}", size); return Error::QFull; } auto _ = gsl::finally([&]() { IOS_HeapFree(CrossProcessHeapId, fileBuffer); }); auto fileHandle = FSAFileHandle { -1 }; error = FSAOpenFile(sCertStoreData->fsaHandle, path, "r", &fileHandle); if (error < FSAStatus::OK) { nsecLog->warn("loadCertstoreMetadata: FSAOpenFile failed with error {} on {}", error, path); return static_cast<Error>(error); } error = FSAReadFile(sCertStoreData->fsaHandle, fileBuffer, 1, size, fileHandle, FSAReadFlag::None); FSACloseFile(sCertStoreData->fsaHandle, fileHandle); if (error < FSAStatus::OK) { nsecLog->warn("loadCertstoreMetadata: FSAReadFile failed with error {}", error); return static_cast<Error>(error); } size = static_cast<uint32_t>(error); // Now parse fileBuffer, size bytes for metadata auto contents = std::string_view { phys_cast<char *>(fileBuffer).get(), size }; auto position = size_t { 0 }; auto certIndex = 0u; auto lineIndex = 1u; // Iteratre through lines of fileBuffer while (position < size) { auto lineEndPosition = contents.find_first_of('\n', position); if (lineEndPosition == std::string_view::npos) { lineEndPosition = size; } auto line = trim_view(contents.substr(position, lineEndPosition - position)); if (!line.empty() && line[0] != '#') { auto linePosition = size_t { 0 }; struct { bool hasId = false; bool hasType = false; bool hasEncoding = false; bool hasProperties = false; bool hasCapabilityMask = false; bool hasProcessIds = false; bool hasTitleIds = false; bool hasPaths = false; bool hasGroups = false; bool hasRawE0Size = false; bool hasRawE1Size = false; bool hasChecksum = false; long id = 0; long type = 0; long encoding = 0; long properties = 0; unsigned long long capabilityMask = 0; std::array<long, 33> processIds; std::array<long long, 33> titleIds; long numPaths = 0; std::string_view path1; std::string_view path2; long groups = 0; long rawE0Size = 0; long rawE1Size = 0; } certData; certData.titleIds[0] = -1; certData.processIds[0] = -1; // Iterator through the line splitting by ; while (linePosition < line.size()) { auto kvEndPosition = std::min(line.find_first_of(';', linePosition), line.size()); auto kvPair = line.substr(linePosition, kvEndPosition - linePosition); // Split the key=value pair auto eqPos = kvPair.find_first_of('='); if (eqPos != std::string_view::npos && eqPos + 1 < kvPair.size()) { auto key = trim_view(kvPair.substr(0, eqPos)); auto value = trim_view(kvPair.substr(eqPos + 1)); if (iequals(key, "ID")) { certData.id = std::stol(std::string(value), nullptr, 0); certData.hasId = true; } else if (iequals(key, "TYPE")) { certData.type = std::stol(std::string(value), nullptr, 0); certData.hasType = true; } else if (iequals(key, "ENCODING")) { certData.encoding = std::stol(std::string(value), nullptr, 0); certData.hasEncoding = true; } else if (iequals(key, "PROPERTIES")) { certData.properties = std::stol(std::string(value), nullptr, 0); certData.hasProperties = true; } else if (iequals(key, "CAPABILITY_MASK")) { certData.capabilityMask = std::stol(std::string(value), nullptr, 0); certData.hasCapabilityMask = true; } else if (iequals(key, "PID")) { auto pidPosition = size_t { 0 }; auto pidIndex = 0u; while (pidPosition < value.size() && pidIndex < certData.processIds.size() - 1) { auto pidEndPosition = std::min(value.find_first_of(','), value.size()); auto pid = value.substr(pidPosition, pidEndPosition - pidPosition); certData.processIds[pidIndex++] = std::stol(std::string(pid), nullptr, 0); pidPosition = pidEndPosition + 1; } certData.processIds[pidIndex] = -1; certData.hasProcessIds = true; } else if (iequals(key, "TID")) { auto tidPosition = size_t { 0 }; auto tidIndex = 0u; while (tidPosition < value.size() && tidIndex < certData.titleIds.size() - 1) { auto tidEndPosition = std::min(value.find_first_of(','), value.size()); auto tid = value.substr(tidPosition, tidEndPosition - tidPosition); certData.titleIds[tidIndex++] = std::stol(std::string(tid), nullptr, 0); tidPosition = tidEndPosition + 1; } certData.titleIds[tidIndex] = -1; certData.hasTitleIds = true; } else if (iequals(key, "PATHS")) { auto path2Position = std::min(value.find_first_of(','), value.size()); certData.path1 = value.substr(0, path2Position); if (path2Position + 1 < value.size()) { certData.path2 = value.substr(path2Position + 1); } if (!certData.path1.empty()) { certData.numPaths += 1; } if (!certData.path2.empty()) { certData.numPaths += 1; } certData.hasPaths = true; } else if (iequals(key, "GROUPS")) { certData.groups = std::stol(std::string(value), nullptr, 0); certData.hasGroups = true; } else if (iequals(key, "RAW_E0_SIZE") || iequals(key, "RAW_SIZE")) { certData.rawE0Size = std::stol(std::string(value), nullptr, 0); certData.hasRawE0Size = true; } else if (iequals(key, "RAW_E1_SIZE") || iequals(key, "KEYSIZEDER")) { certData.rawE1Size = std::stol(std::string(value), nullptr, 0); certData.hasRawE1Size = true; } else { nsecLog->warn("CERTSTORE: Unrecognized property name '{}' on line {}", key, lineIndex); } } linePosition = kvEndPosition + 1; } if (!certData.hasId) { nsecLog->warn("CERTSTORE: ID not specified on line {}", lineIndex); } else if (!certData.hasType) { nsecLog->warn("CERTSTORE: TYPE not specified on line {}", lineIndex); } else if (!certData.hasEncoding) { nsecLog->warn("CERTSTORE: ENCODING not specified on line {}", lineIndex); } else if (!certData.hasProperties) { nsecLog->warn("CERTSTORE: PROPERTIES not specified on line {}", lineIndex); } else if (!certData.hasCapabilityMask) { nsecLog->warn("CERTSTORE: CAPABILITY_MASK not specified on line {}", lineIndex); } else if (!certData.hasProcessIds && !certData.hasTitleIds) { nsecLog->warn("CERTSTORE: Neither PID nor TID specified on line {}", lineIndex); } else if (!certData.hasPaths) { nsecLog->warn("CERTSTORE: PATHS not specified on line {}", lineIndex); } else if (!certData.hasGroups) { nsecLog->warn("CERTSTORE: GROUPS not specified on line {}", lineIndex); } else if (!certData.hasRawE0Size && (certData.properties & 4)) { nsecLog->warn("CERTSTORE: RAW_E0_SIZE not specified on line {} for encrypted entity", lineIndex); } else if (!certData.hasRawE1Size && (certData.properties & 1)) { nsecLog->warn("CERTSTORE: KEYSIZEDER/RAW_E1_SIZE not specified on line {}", lineIndex); } else { auto &cert = sCertStoreData->metaData[certIndex++]; cert.id = static_cast<int32_t>(certData.id); cert.type = static_cast<int32_t>(certData.type); cert.encoding = static_cast<NSSLCertEncoding>(certData.encoding); cert.properties = static_cast<NSSLCertProperties>(certData.properties); cert.groups = static_cast<int32_t>(certData.groups); cert.capabilityMask = static_cast<uint64_t>(certData.capabilityMask); cert.numPaths = static_cast<int32_t>(certData.numPaths); cert.path1 = certData.path1; cert.path2 = certData.path2; cert.rawE0Size = static_cast<int32_t>(certData.rawE0Size); cert.rawE1Size = static_cast<int32_t>(certData.rawE1Size); for (auto i = 0u; i < certData.processIds.size(); ++i) { cert.processIds[i] = static_cast<ProcessId>(certData.processIds[i]); } for (auto i = 0u; i < certData.titleIds.size(); ++i) { cert.titleIds[i] = static_cast<TitleId>(certData.titleIds[i]); } } } if (certIndex == sCertStoreData->metaData.size()) { nsecLog->warn("CERTSTORE: Maximum Entity Limit ({}) Reached !", sCertStoreData->metaData.size()); break; } position = lineEndPosition + 1; ++lineIndex; } if (certIndex < sCertStoreData->metaData.size()) { sCertStoreData->metaData[certIndex].id = -1; } return Error::OK; } void initialiseStaticCertStoreData() { sCertStoreData = allocProcessStatic<StaticCertStoreData>(); } } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_certstore.h ================================================ #pragma once #include "ios_nsec_enum.h" #include "ios_nsec_nssl_types.h" #include "ios/ios_enum.h" #include "ios/ios_error.h" #include "ios/ios_ipc.h" #include <cstdint> #include <common/structsize.h> #include <libcpu/be2_struct.h> #include <optional> namespace ios::nsec::internal { #pragma pack(push, 1) // Reversing IOS seems to indicate this may be 1000, but lets just set to 100 // to save memory. constexpr auto MaxNumCertificates = 100u; struct CertStoreMetaData { be2_val<NSSLCertID> id; be2_val<int32_t> type; // This is NOT an NSSLCertType be2_val<NSSLCertEncoding> encoding; be2_val<NSSLCertProperties> properties; be2_val<int32_t> groups; be2_val<uint64_t> capabilityMask; be2_array<ProcessId, 33> processIds; be2_array<TitleId, 33> titleIds; be2_val<int32_t> numPaths; be2_array<char, 128> path1; be2_array<char, 128> path2; be2_val<int32_t> rawE0Size; be2_val<int32_t> rawE1Size; }; CHECK_OFFSET(CertStoreMetaData, 0x00, id); CHECK_OFFSET(CertStoreMetaData, 0x04, type); CHECK_OFFSET(CertStoreMetaData, 0x08, encoding); CHECK_OFFSET(CertStoreMetaData, 0x0C, properties); CHECK_OFFSET(CertStoreMetaData, 0x10, groups); CHECK_OFFSET(CertStoreMetaData, 0x14, capabilityMask); CHECK_OFFSET(CertStoreMetaData, 0x1C, processIds); CHECK_OFFSET(CertStoreMetaData, 0xA0, titleIds); CHECK_OFFSET(CertStoreMetaData, 0x1A8, numPaths); CHECK_OFFSET(CertStoreMetaData, 0x1AC, path1); CHECK_OFFSET(CertStoreMetaData, 0x22C, path2); CHECK_OFFSET(CertStoreMetaData, 0x2AC, rawE0Size); CHECK_OFFSET(CertStoreMetaData, 0x2B0, rawE1Size); CHECK_SIZE(CertStoreMetaData, 0x2B4); #pragma pack(pop) bool checkCertPermission(phys_ptr<CertStoreMetaData> certMetaData, TitleId titleId, ProcessId processId, uint64_t caps); bool checkCertExportable(phys_ptr<CertStoreMetaData> certMetaData); std::optional<uint32_t> getCertFileSize(phys_ptr<CertStoreMetaData> certMetaData, int32_t pathIndex); std::optional<uint32_t> getCertFileData(phys_ptr<CertStoreMetaData> certMetaData, int32_t pathIndex, phys_ptr<void> certBuffer, uint32_t certBufferSize); phys_ptr<CertStoreMetaData> lookupCertMetaData(NSSLCertID id); Error loadCertstoreMetadata(); void initialiseStaticCertStoreData(); } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_device.cpp ================================================ #include "ios_nsec_nssl_certstore.h" #include "ios_nsec_nssl_device.h" #include "ios/crypto/ios_crypto_ipc.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/ios_stackobject.h" #include <gsl/gsl-lite.hpp> using namespace ::ios::kernel; namespace ios::nsec::internal { NSSLError NSSLDevice::createContext(NSSLVersion version) { for (auto i = 0u; i < mContexts.size(); ++i) { auto &context = mContexts[i]; if (context.initialised) { continue; } context.initialised = true; context.version = version; return static_cast<NSSLError>(i); } return NSSLError::ProcessMaxContexts; } NSSLError NSSLDevice::addServerPKI(NSSLContextHandle context, NSSLCertID cert) { return NSSLError::OK; } NSSLError NSSLDevice::addServerPKIExternal(NSSLContextHandle context, phys_ptr<uint8_t> cert, uint32_t certSize, NSSLCertType certType) { return NSSLError::OK; } NSSLError NSSLDevice::exportInternalClientCertificate( phys_ptr<NSSLExportInternalClientCertificateRequest> request, phys_ptr<NSSLExportInternalClientCertificateResponse> response, phys_ptr<uint8_t> certBuffer, uint32_t certBufferSize, phys_ptr<uint8_t> privateKeyBuffer, uint32_t privateKeyBufferSize) { auto certMetaData = lookupCertMetaData(request->certId); if (!certMetaData) { return NSSLError::InvalidCertId2; } if (!checkCertPermission(certMetaData, mTitleId, mProcessId, mCapabilities)) { return NSSLError::CertNoAccess; } if (!checkCertExportable(certMetaData)) { return NSSLError::CertNotExportable; } if (certMetaData->type != 2) { return NSSLError::InvalidCertType; } if (certMetaData->encoding != NSSLCertEncoding::Unknown1) { return NSSLError::InvalidCertEncoding; } auto fileSize = getCertFileSize(certMetaData, 1); if (!fileSize.has_value()) { return NSSLError::InvalidCertSize; } if (!certBuffer || !certBufferSize) { response->certSize = *fileSize; response->certType = NSSLCertType::Unknown0; return NSSLError::OK; } if (certBufferSize < fileSize) { return NSSLError::InsufficientSize; } auto encryptedCertBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, certBufferSize, 0x10u); auto decryptedCertBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, certBufferSize, 0x10u); auto _ = gsl::finally([&]() { IOS_HeapFree(CrossProcessHeapId, encryptedCertBuffer); IOS_HeapFree(CrossProcessHeapId, decryptedCertBuffer); }); fileSize = getCertFileData(certMetaData, 1, encryptedCertBuffer, certBufferSize); if (!fileSize.has_value()) { return NSSLError::CertReadError; } StackArray<uint8_t, 16> iv; memset(iv.get(), 0, iv.size()); auto ioscHandle = static_cast<crypto::IOSCHandle>(crypto::IOSC_Open()); auto error = crypto::IOSC_Decrypt(ioscHandle, crypto::KeyId::SslRsaKey, iv, iv.size(), encryptedCertBuffer, certBufferSize, decryptedCertBuffer, certBufferSize); if (error != Error::OK) { return NSSLError::CertReadError; } response->certSize = *fileSize; response->certType = NSSLCertType::Unknown0; memcpy(certBuffer.get(), decryptedCertBuffer.get(), certBufferSize); return NSSLError::OK; } NSSLError NSSLDevice::exportInternalServerCertificate( phys_ptr<NSSLExportInternalServerCertificateRequest> request, phys_ptr<NSSLExportInternalServerCertificateResponse> response, phys_ptr<uint8_t> certBuffer, uint32_t certBufferSize) { auto certMetaData = lookupCertMetaData(request->certId); if (!certMetaData) { return NSSLError::InvalidCertId2; } if (!checkCertPermission(certMetaData, mTitleId, mProcessId, mCapabilities)) { return NSSLError::CertNoAccess; } if (!checkCertExportable(certMetaData)) { return NSSLError::CertNotExportable; } if (certMetaData->type != 1) { return NSSLError::InvalidCertType; } if (certMetaData->encoding != NSSLCertEncoding::Unknown1) { return NSSLError::InvalidCertEncoding; } auto fileSize = getCertFileSize(certMetaData, 0); if (!fileSize.has_value()) { return NSSLError::InvalidCertSize; } if (!certBuffer || !certBufferSize) { response->certSize = *fileSize; response->certType = NSSLCertType::Unknown0; return NSSLError::OK; } if (certBufferSize < fileSize) { return NSSLError::InsufficientSize; } fileSize = getCertFileData(certMetaData, 0, certBuffer, certBufferSize); if (!fileSize.has_value()) { return NSSLError::CertReadError; } response->certSize = *fileSize; response->certType = NSSLCertType::Unknown0; return NSSLError::OK; } } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_device.h ================================================ #pragma once #include "ios_nsec_enum.h" #include "ios_nsec_nssl_types.h" #include "ios_nsec_nssl_request.h" #include "ios_nsec_nssl_response.h" #include "ios/ios_ipc.h" #include <array> #include <libcpu/be2_struct.h> namespace ios::nsec::internal { class NSSLDevice { static constexpr const size_t MaxNumContexts = 32; struct Context { bool initialised = false; NSSLVersion version = NSSLVersion::Auto; }; public: NSSLDevice(TitleId titleId, ProcessId processId, uint64_t caps, Handle socketHandle) : mTitleId(titleId), mProcessId(processId), mCapabilities(caps), mSocketHandle(socketHandle) { } NSSLError createContext(NSSLVersion version); NSSLError addServerPKI(NSSLContextHandle context, NSSLCertID cert); NSSLError addServerPKIExternal(NSSLContextHandle context, phys_ptr<uint8_t> cert, uint32_t certSize, NSSLCertType certType); NSSLError exportInternalClientCertificate( phys_ptr<NSSLExportInternalClientCertificateRequest> request, phys_ptr<NSSLExportInternalClientCertificateResponse> response, phys_ptr<uint8_t> certBuffer, uint32_t certBufferSize, phys_ptr<uint8_t> privateKeyBuffer, uint32_t privateKeyBufferSize); NSSLError exportInternalServerCertificate( phys_ptr<NSSLExportInternalServerCertificateRequest> request, phys_ptr<NSSLExportInternalServerCertificateResponse> response, phys_ptr<uint8_t> certBuffer, uint32_t certBufferSize); private: TitleId mTitleId; ProcessId mProcessId; uint64_t mCapabilities; Handle mSocketHandle; std::array<Context, MaxNumContexts> mContexts; }; } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_request.h ================================================ #pragma once #include "ios_nsec_enum.h" #include "ios_nsec_nssl_types.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::nsec { /** * \ingroup ios_nsec * @{ */ #pragma pack(push, 1) struct NSSLCreateContextRequest { be2_val<NSSLVersion> version; }; CHECK_OFFSET(NSSLCreateContextRequest, 0x00, version); CHECK_SIZE(NSSLCreateContextRequest, 0x04); struct NSSLDestroyContextRequest { be2_val<NSSLContextHandle> context; }; CHECK_OFFSET(NSSLDestroyContextRequest, 0x00, context); CHECK_SIZE(NSSLDestroyContextRequest, 0x04); struct NSSLAddServerPKIRequest { be2_val<NSSLContextHandle> context; be2_val<NSSLCertID> cert; }; CHECK_OFFSET(NSSLAddServerPKIRequest, 0x00, context); CHECK_OFFSET(NSSLAddServerPKIRequest, 0x04, cert); CHECK_SIZE(NSSLAddServerPKIRequest, 0x08); struct NSSLAddServerPKIExternalRequest { be2_val<NSSLContextHandle> context; be2_val<NSSLCertType> certType; }; CHECK_OFFSET(NSSLAddServerPKIExternalRequest, 0x00, context); CHECK_OFFSET(NSSLAddServerPKIExternalRequest, 0x04, certType); CHECK_SIZE(NSSLAddServerPKIExternalRequest, 0x08); struct NSSLExportInternalClientCertificateRequest { be2_val<NSSLCertID> certId; }; CHECK_OFFSET(NSSLExportInternalClientCertificateRequest, 0x00, certId); CHECK_SIZE(NSSLExportInternalClientCertificateRequest, 0x04); struct NSSLExportInternalServerCertificateRequest { be2_val<NSSLCertID> certId; }; CHECK_OFFSET(NSSLExportInternalServerCertificateRequest, 0x00, certId); CHECK_SIZE(NSSLExportInternalServerCertificateRequest, 0x04); #pragma pack(pop) /** @} */ } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_response.h ================================================ #pragma once #include "ios_nsec_enum.h" #include "ios_nsec_nssl_types.h" #include <cstdint> #include <libcpu/be2_struct.h> #include <common/structsize.h> namespace ios::nsec { /** * \ingroup ios_nsec * @{ */ #pragma pack(push, 1) struct NSSLExportInternalClientCertificateResponse { be2_val<NSSLCertType> certType; be2_val<uint32_t> certSize; be2_val<NSSLPrivateKeyType> privateKeyType; be2_val<uint32_t> privateKeySize; }; CHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x00, certType); CHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x04, certSize); CHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x08, privateKeyType); CHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x0C, privateKeySize); CHECK_SIZE(NSSLExportInternalClientCertificateResponse, 0x10); struct NSSLExportInternalServerCertificateResponse { be2_val<NSSLCertType> certType; be2_val<uint32_t> certSize; }; CHECK_OFFSET(NSSLExportInternalServerCertificateResponse, 0x00, certType); CHECK_OFFSET(NSSLExportInternalServerCertificateResponse, 0x04, certSize); CHECK_SIZE(NSSLExportInternalServerCertificateResponse, 0x08); #pragma pack(pop) /** @} */ } // namespace ios::net ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_thread.cpp ================================================ #include "ios_nsec_log.h" #include "ios_nsec_nssl_certstore.h" #include "ios_nsec_nssl_device.h" #include "ios_nsec_nssl_thread.h" #include "ios_nsec_nssl_request.h" #include "ios_nsec_nssl_response.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_process.h" #include "ios/kernel/ios_kernel_resourcemanager.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/kernel/ios_kernel_ipc.h" #include "ios/net/ios_net_soshim.h" #include "ios/ios_error.h" #include "ios/ios_stackobject.h" namespace ios::nsec::internal { using SocketDeviceHandle = int32_t; using namespace kernel; using namespace net; constexpr auto NumNsslMessages = 32u; constexpr auto NsslThreadStackSize = 0x4000u; constexpr auto NsslThreadPriority = 47u; struct StaticNsslThreadData { be2_val<BOOL> suspended; be2_val<ThreadId> threadId; be2_val<MessageQueueId> messageQueueId; be2_struct<IpcRequest> stopMessage; be2_array<Message, NumNsslMessages> messageBuffer; be2_array<uint8_t, NsslThreadStackSize> threadStack; be2_val<BOOL> openedSocketHandle; be2_val<Handle> socketHandle; }; static phys_ptr<StaticNsslThreadData> sNsslThreadData = nullptr; static std::array<std::unique_ptr<NSSLDevice>, ProcessId::Max> sDevices; static NSSLDevice * getDevice(SocketDeviceHandle handle) { if (handle < 0 || handle >= sDevices.size()) { return nullptr; } return sDevices[handle].get(); } static Error nsslOpen(phys_ptr<ResourceRequest> request) { // Send GetProcessSocketHandle to /dev/socket if (!sNsslThreadData->openedSocketHandle) { auto error = SOShim_Open(); if (error < 0) { return static_cast<Error>(NSSLError::IoError); } sNsslThreadData->socketHandle = static_cast<Handle>(error); sNsslThreadData->openedSocketHandle = TRUE; } auto error = SOShim_GetProcessSocketHandle(sNsslThreadData->socketHandle, request->requestData.titleId, request->requestData.processId); if (error < 0) { return static_cast<Error>(NSSLError::IoError); } // There is one NSSL device per process auto pid = request->requestData.processId; auto idx = static_cast<size_t>(pid); if (!sDevices[idx]) { sDevices[idx] = std::make_unique<NSSLDevice>( request->requestData.titleId, request->requestData.processId, request->requestData.args.open.caps, static_cast<Handle>(error)); } return static_cast<Error>(idx); } static Error nsslClose(phys_ptr<ResourceRequest> request) { auto pid = request->requestData.clientPid; auto idx = static_cast<size_t>(pid); if (idx != request->requestData.handle) { return Error::Exists; } sDevices[idx] = nullptr; return Error::OK; } static Error nsslIoctl(phys_ptr<ResourceRequest> resourceRequest) { auto error = Error::OK; auto device = getDevice(resourceRequest->requestData.handle); if (!device) { return static_cast<Error>(NSSLError::InvalidHandle); } switch (static_cast<NSSLCommand>(resourceRequest->requestData.args.ioctl.request)) { case NSSLCommand::CreateContext: { if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(NSSLCreateContextRequest)) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctl.inputBuffer) { return Error::InvalidArg; } auto request = phys_cast<const NSSLCreateContextRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer); error = static_cast<Error>(device->createContext(request->version)); break; } case NSSLCommand::AddServerPKI: { if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(NSSLAddServerPKIRequest)) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctl.inputBuffer) { return Error::InvalidArg; } auto request = phys_cast<const NSSLAddServerPKIRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer); error = static_cast<Error>(device->addServerPKI(request->context, request->cert)); break; } default: error = Error::InvalidArg; } return error; } static Error nsslIoctlv(phys_ptr<ResourceRequest> resourceRequest) { auto error = Error::OK; auto device = getDevice(resourceRequest->requestData.handle); if (!device) { return static_cast<Error>(NSSLError::InvalidHandle); } switch (static_cast<NSSLCommand>(resourceRequest->requestData.args.ioctlv.request)) { case NSSLCommand::AddServerPKIExternal: { if (resourceRequest->requestData.args.ioctlv.numVecIn != 2) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.numVecOut != 0) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.vecs[1].len != sizeof(NSSLAddServerPKIExternalRequest)) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[1].paddr) { return Error::InvalidArg; } auto request = phys_cast<NSSLAddServerPKIExternalRequest *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr); auto cert = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr); auto certSize = resourceRequest->requestData.args.ioctlv.vecs[0].len; error = static_cast<Error>( device->addServerPKIExternal(request->context, cert, certSize, request->certType)); break; } case NSSLCommand::ExportInternalClientCertificate: { if (resourceRequest->requestData.args.ioctlv.numVecIn != 1) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.numVecOut != 3) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.vecs[0].len != sizeof(NSSLExportInternalClientCertificateRequest) || resourceRequest->requestData.args.ioctlv.vecs[3].len != sizeof(NSSLExportInternalClientCertificateResponse)) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[0].paddr || !resourceRequest->requestData.args.ioctlv.vecs[3].paddr) { return Error::InvalidArg; } auto request = phys_cast<NSSLExportInternalClientCertificateRequest *>( resourceRequest->requestData.args.ioctlv.vecs[0].paddr); auto certBuffer = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr); auto certBufferSize = resourceRequest->requestData.args.ioctlv.vecs[1].len; auto privateKeyBuffer = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr); auto privateKeyBufferSize = resourceRequest->requestData.args.ioctlv.vecs[2].len; auto response = phys_cast<NSSLExportInternalClientCertificateResponse *>( resourceRequest->requestData.args.ioctlv.vecs[3].paddr); error = static_cast<Error>(device->exportInternalClientCertificate( request, response, certBuffer, certBufferSize, privateKeyBuffer, privateKeyBufferSize)); break; } case NSSLCommand::ExportInternalServerCertificate: { if (resourceRequest->requestData.args.ioctlv.numVecIn != 1) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.numVecOut != 2) { return Error::InvalidArg; } if (resourceRequest->requestData.args.ioctlv.vecs[0].len != sizeof(NSSLExportInternalServerCertificateRequest) || resourceRequest->requestData.args.ioctlv.vecs[2].len != sizeof(NSSLExportInternalServerCertificateResponse)) { return Error::InvalidArg; } if (!resourceRequest->requestData.args.ioctlv.vecs[0].paddr || !resourceRequest->requestData.args.ioctlv.vecs[2].paddr) { return Error::InvalidArg; } auto request = phys_cast<NSSLExportInternalServerCertificateRequest *>( resourceRequest->requestData.args.ioctlv.vecs[0].paddr); auto certBuffer = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr); auto certBufferSize = resourceRequest->requestData.args.ioctlv.vecs[1].len; auto response = phys_cast<NSSLExportInternalServerCertificateResponse *>( resourceRequest->requestData.args.ioctlv.vecs[2].paddr); error = static_cast<Error>(device->exportInternalServerCertificate( request, response, certBuffer, certBufferSize)); break; } default: error = Error::InvalidArg; } return error; } static Error nsslThreadEntry(phys_ptr<void> /*context*/) { StackObject<Message> message; while (true) { auto error = IOS_ReceiveMessage(sNsslThreadData->messageQueueId, message, MessageFlags::None); if (error < Error::OK) { break; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: IOS_ResourceReply(request, nsslOpen(request)); break; case Command::Close: IOS_ResourceReply(request, nsslClose(request)); break; case Command::Ioctl: IOS_ResourceReply(request, nsslIoctl(request)); break; case Command::Ioctlv: IOS_ResourceReply(request, nsslIoctlv(request)); break; case Command::Suspend: // TODO: /dev/nsec/nssl Suspend return Error::OK; case Command::Resume: // TODO: /dev/nsec/nssl Resume return Error::OK; default: IOS_ResourceReply(request, Error::InvalidArg); } } if (sNsslThreadData->messageQueueId > 0) { IOS_DestroyMessageQueue(sNsslThreadData->messageQueueId); sNsslThreadData->messageQueueId = Error::Invalid; } IOS_SuspendThread(IOS_GetCurrentThreadId()); return Error::OK; } Error registerNsslResourceManager() { auto error = IOS_CreateMessageQueue(phys_addrof(sNsslThreadData->messageBuffer), sNsslThreadData->messageBuffer.size()); if (error < Error::OK) { return error; } sNsslThreadData->messageQueueId = static_cast<MessageQueueId>(error); error = IOS_RegisterResourceManager("/dev/nsec/nssl", sNsslThreadData->messageQueueId); if (error < Error::OK) { nsecLog->error("registerNsslResourceManager: IOS_RegisterResourceManager returned {}", error); IOS_DestroyMessageQueue(sNsslThreadData->messageQueueId); sNsslThreadData->messageQueueId = Error::Invalid; return error; } error = IOS_AssociateResourceManager("/dev/nsec/nssl", ResourcePermissionGroup::NSEC); if (error < Error::OK) { nsecLog->error("registerNsslResourceManager: IOS_AssociateResourceManager returned {}", error); IOS_DestroyMessageQueue(sNsslThreadData->messageQueueId); sNsslThreadData->messageQueueId = Error::Invalid; return error; } return Error::OK; } Error startNsslThread() { auto error = loadCertstoreMetadata(); if (error < Error::OK) { nsecLog->warn("startNsslThread: Failed to load certstore metadata, error {}", error); } error = IOS_CreateThread(&nsslThreadEntry, nullptr, phys_addrof(sNsslThreadData->threadStack) + sNsslThreadData->threadStack.size(), sNsslThreadData->threadStack.size(), NsslThreadPriority, ThreadFlags::Detached); if (error < Error::OK) { nsecLog->error("startNsslThread: IOS_CreateThread returned {}", error); return error; } sNsslThreadData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sNsslThreadData->threadId, "NsslThread"); return IOS_StartThread(sNsslThreadData->threadId); } Error stopNsslThread() { return IOS_JamMessage(sNsslThreadData->messageQueueId, makeMessage(phys_addrof(sNsslThreadData->stopMessage)), MessageFlags::NonBlocking); } void initialiseStaticNsslData() { sNsslThreadData = allocProcessStatic<StaticNsslThreadData>(); sNsslThreadData->stopMessage.command = Command::Suspend; } } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_thread.h ================================================ #pragma once #include "ios/ios_enum.h" namespace ios::nsec::internal { Error registerNsslResourceManager(); Error startNsslThread(); Error stopNsslThread(); void initialiseStaticNsslData(); } // namespace ios::nsec::internal ================================================ FILE: src/libdecaf/src/ios/nsec/ios_nsec_nssl_types.h ================================================ #pragma once #include <cstdint> namespace ios::nsec { /** * \ingroup ios_nesc * @{ */ #pragma pack(push, 1) using NSSLCertID = int32_t; using NSSLContextHandle = int32_t; #pragma pack(pop) /** @} */ } // namespace ios::nsec ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad.cpp ================================================ #include "ios_pad.h" #include "ios_pad_btrm_device.h" #include "ios_pad_log.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_heap.h" #include "ios/kernel/ios_kernel_messagequeue.h" using namespace ios::kernel; namespace ios::pad { constexpr auto LocalHeapSize = 0x100000u; constexpr auto CrossHeapSize = 0x20000u; static phys_ptr<void> sLocalHeapBuffer = nullptr; namespace internal { Logger padLog = { }; static void initialiseStaticData() { sLocalHeapBuffer = kernel::allocProcessLocalHeap(LocalHeapSize); } } // namespace internal Error processEntryPoint(phys_ptr<void> /* context */) { // Initialise logger internal::padLog = decaf::makeLogger("IOS_PAD"); // Initialise static memory internal::initialiseStaticData(); internal::initialiseStaticBtrmData(); // Initialise process heaps auto error = kernel::IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize); if (error < Error::OK) { internal::padLog->error("Failed to create local process heap, error = {}.", error); return error; } error = kernel::IOS_CreateCrossProcessHeap(CrossHeapSize); if (error < Error::OK) { internal::padLog->error("Failed to create cross process heap, error = {}.", error); return error; } // TODO: Create /dev/ccr_io thread error = internal::startBtrmDeviceThread(); if (error < Error::OK) { internal::padLog->error("Failed to start btrm device thread, error = {}.", error); return error; } return Error::OK; } } // namespace ios::pad ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::pad { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::pad ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad_btrm_device.cpp ================================================ #include "ios_pad.h" #include "ios_pad_btrm_device.h" #include "ios_pad_btrm_request.h" #include "ios_pad_btrm_response.h" #include "ios_pad_log.h" #include "decaf_log.h" #include "ios/kernel/ios_kernel_messagequeue.h" #include "ios/kernel/ios_kernel_thread.h" #include "ios/mcp/ios_mcp_ipc.h" #include "ios/ios_stackobject.h" #include <cstring> using namespace ios::kernel; using namespace ios::mcp; namespace ios::pad::internal { struct StaticBtrmData { be2_array<Message, 0x40> messageBuffer; be2_val<MessageQueueId> messageQueue; be2_array<Message, 0x10> transMessageBuffer; be2_val<MessageQueueId> transMessageQueue; be2_val<ThreadId> threadId; be2_array<uint8_t, 0x4000> threadStack; }; static phys_ptr<StaticBtrmData> sBtrmData = nullptr; static phys_ptr<void> sLocalHeapBuffer = nullptr; static Error btrmIoctlv(phys_ptr<ResourceRequest> resourceRequest) { if (resourceRequest->requestData.args.ioctlv.numVecIn != 1 || resourceRequest->requestData.args.ioctlv.numVecOut != 1 || !resourceRequest->requestData.args.ioctlv.vecs[0].paddr || resourceRequest->requestData.args.ioctlv.vecs[0].len != sizeof(BtrmRequest) || !resourceRequest->requestData.args.ioctlv.vecs[1].paddr || resourceRequest->requestData.args.ioctlv.vecs[1].len != sizeof(BtrmResponse)) { return Error::Invalid; } auto request = phys_cast<BtrmRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr); auto response = phys_cast<BtrmResponse *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr); auto result = Error::OK; switch (request->command) { case BtrmCommand::PpcInitDone: std::memset(phys_addrof(response->ppcInitDone).get(), 0, sizeof(response->ppcInitDone)); response->ppcInitDone.btChipId = uint8_t { 63 }; response->ppcInitDone.btChipBuildNumber = uint16_t { 517 }; result = Error::OK; break; case BtrmCommand::Wud: { switch (request->subcommand) { case BtrmSubCommand::UpdateBTDevSize: result = Error::OK; break; default: result = Error::Invalid; } } default: result = Error::Invalid; } return result; } static Error btrmThreadMain(phys_ptr<void> /*context*/) { StackObject<Message> message; while (true) { auto error = IOS_ReceiveMessage(sBtrmData->messageQueue, message, MessageFlags::None); if (error < Error::OK) { return error; } auto request = parseMessage<ResourceRequest>(message); switch (request->requestData.command) { case Command::Open: { auto device = std::string_view { phys_addrof(request->openNameBuffer).get() }; if (device == "/dev/usb/btrm") { IOS_ResourceReply(request, static_cast<Error>(1)); } else if (device == "/dev/usb/early_btrm") { IOS_ResourceReply(request, static_cast<Error>(2)); } else { IOS_ResourceReply(request, static_cast<Error>(3)); } break; } case Command::Close: IOS_ResourceReply(request, Error::OK); break; case Command::Ioctlv: { IOS_ResourceReply(request, btrmIoctlv(request)); break; } case Command::Resume: case Command::Suspend: IOS_ResourceReply(request, Error::OK); break; default: IOS_ResourceReply(request, Error::Invalid); } } } Error startBtrmDeviceThread() { auto error = Error::OK; error = IOS_CreateMessageQueue(phys_addrof(sBtrmData->messageBuffer), static_cast<uint32_t>(sBtrmData->messageBuffer.size())); if (error < Error::OK) { padLog->error("Failed to create message queue, error = {}.", error); return error; } sBtrmData->messageQueue = static_cast<MessageQueueId>(error); error = MCP_RegisterResourceManager("/dev/usb/btrm", sBtrmData->messageQueue); if (error < Error::OK) { padLog->error("Failed to register resource manager for btrm, error = {}.", error); return error; } error = MCP_RegisterResourceManager("/dev/usb/early_btrm", sBtrmData->messageQueue); if (error < Error::OK) { padLog->error("Failed to register resource manager for early_btrm, error = {}.", error); return error; } error = IOS_AssociateResourceManager("/dev/usb/early_btrm", ResourcePermissionGroup::PAD); if (error < Error::OK) { padLog->error("Failed to associate resource manager for early_btrm, error = {}.", error); return error; } error = IOS_CreateMessageQueue(phys_addrof(sBtrmData->transMessageBuffer), static_cast<uint32_t>(sBtrmData->transMessageBuffer.size())); if (error < Error::OK) { padLog->error("Failed to create message queue, error = {}.", error); return error; } sBtrmData->transMessageQueue = static_cast<MessageQueueId>(error); error = IOS_CreateThread(btrmThreadMain, nullptr, phys_addrof(sBtrmData->threadStack) + sBtrmData->threadStack.size(), static_cast<uint32_t>(sBtrmData->threadStack.size()), IOS_GetThreadPriority(IOS_GetCurrentThreadId()), ThreadFlags::Detached); if (error < Error::OK) { padLog->error("Failed to create btrm thread, error = {}.", error); return error; } sBtrmData->threadId = static_cast<ThreadId>(error); kernel::internal::setThreadName(sBtrmData->threadId, "BtrmThread"); error = IOS_StartThread(sBtrmData->threadId); if (error < Error::OK) { padLog->error("Failed to start btrm thread, error = {}.", error); return error; } return Error::OK; } void initialiseStaticBtrmData() { sBtrmData = allocProcessStatic<StaticBtrmData>(); } } // namespace ios::pad ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad_btrm_device.h ================================================ #pragma once #include "ios/ios_error.h" namespace ios::pad::internal { Error startBtrmDeviceThread(); void initialiseStaticBtrmData(); } // namespace ios::pad::internal ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad_btrm_request.h ================================================ #pragma once #include "ios_pad_enum.h" #include <libcpu/be2_struct.h> namespace ios::pad { /** * \ingroup ios_pad * @{ */ #pragma pack(push, 1) struct BtrmRequest { union { be2_array<uint8_t, 0x1000> data; }; be2_val<BtrmCommand> command; be2_val<BtrmSubCommand> subcommand; be2_val<uint16_t> unk0x1002; be2_val<uint32_t> unk0x1004; }; CHECK_OFFSET(BtrmRequest, 0x1000, command); CHECK_OFFSET(BtrmRequest, 0x1001, subcommand); CHECK_OFFSET(BtrmRequest, 0x1002, unk0x1002); CHECK_OFFSET(BtrmRequest, 0x1004, unk0x1004); CHECK_SIZE(BtrmRequest, 0x1008); #pragma pack(pop) /** @} */ } // namespace ios::pad ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad_btrm_response.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace ios::pad { /** * \ingroup ios_pad * @{ */ #pragma pack(push, 1) struct BtrmResponsePpcInitDone { be2_val<uint32_t> unk0x00; be2_array<char, 6> unk0x04; be2_val<uint8_t> btChipId; be2_val<uint16_t> unk0x0B; be2_val<uint16_t> btChipBuildNumber; be2_val<uint8_t> unk0x0F; }; CHECK_OFFSET(BtrmResponsePpcInitDone, 0x00, unk0x00); CHECK_OFFSET(BtrmResponsePpcInitDone, 0x04, unk0x04); CHECK_OFFSET(BtrmResponsePpcInitDone, 0x0A, btChipId); CHECK_OFFSET(BtrmResponsePpcInitDone, 0x0B, unk0x0B); CHECK_OFFSET(BtrmResponsePpcInitDone, 0x0D, btChipBuildNumber); CHECK_OFFSET(BtrmResponsePpcInitDone, 0x0F, unk0x0F); struct BtrmResponse { union { be2_array<uint8_t, 0x1000> data; be2_struct<BtrmResponsePpcInitDone> ppcInitDone; }; UNKNOWN(0xC); }; CHECK_SIZE(BtrmResponse, 0x100C); #pragma pack(pop) /** @} */ } // namespace ios::pad ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad_enum.h ================================================ #ifndef IOS_PAD_ENUM_H #define IOS_PAD_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(ios) ENUM_NAMESPACE_ENTER(pad) ENUM_BEG(BtrmCommand, uint8_t) ENUM_VALUE(PpcInitDone, 1) ENUM_VALUE(Wud, 3) ENUM_VALUE(Bte, 4) ENUM_END(BtrmCommand) ENUM_BEG(BtrmSubCommand, uint8_t) // Category 3 ENUM_VALUE(UpdateBTDevSize, 29) ENUM_END(BtrmSubCommand) ENUM_NAMESPACE_EXIT(pad) ENUM_NAMESPACE_EXIT(ios) #include <common/enum_end.inl> #endif // ifdef IOS_PAD_ENUM_H ================================================ FILE: src/libdecaf/src/ios/pad/ios_pad_log.h ================================================ #pragma once #include <common/log.h> namespace ios::pad::internal { extern Logger padLog; } // namespace ios::pad::internal ================================================ FILE: src/libdecaf/src/ios/test/ios_test.cpp ================================================ #include "ios_test.h" namespace ios::test { Error processEntryPoint(phys_ptr<void> /* context */) { return Error::OK; } } // namespace ios::test ================================================ FILE: src/libdecaf/src/ios/test/ios_test.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::test { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::test ================================================ FILE: src/libdecaf/src/ios/usb/ios_usb.cpp ================================================ #include "ios_usb.h" namespace ios::usb { Error processEntryPoint(phys_ptr<void> /* context */) { return Error::OK; } } // namespace ios::usb ================================================ FILE: src/libdecaf/src/ios/usb/ios_usb.h ================================================ #pragma once #include "ios/kernel/ios_kernel_process.h" namespace ios::usb { Error processEntryPoint(phys_ptr<void> context); } // namespace ios::usb ================================================ FILE: src/libdecaf/src/nn/ac/nn_ac_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::ac { static constexpr Result ResultSuccess { nn::Result::MODULE_NN_AC, nn::Result::LEVEL_SUCCESS, 128 }; static constexpr Result ResultInvalidArgument { nn::Result::MODULE_NN_AC, nn::Result::LEVEL_USAGE, 0xC980 }; static constexpr Result ResultLibraryNotInitialiased { nn::Result::MODULE_NN_AC, nn::Result::LEVEL_USAGE, 0xCC00 }; static constexpr Result ResultConnectFailed { nn::Result::MODULE_NN_AC, nn::Result::LEVEL_STATUS, 0xFF80 }; } // namespace nn::ac ================================================ FILE: src/libdecaf/src/nn/ac/nn_ac_service.h ================================================ #pragma once #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_managedbuffer.h" #include "nn/ipc/nn_ipc_service.h" #include <cstdint> namespace nn::ac::services { struct AcService : ipc::Service<0> { using Initialise = ipc::Command<AcService, 80> ::Parameters<> ::Response<>; using Finalise = ipc::Command<AcService, 81> ::Parameters<> ::Response<>; using GetAssignedAddress = ipc::Command<AcService, 0x610> ::Parameters<uint32_t> ::Response<uint32_t>; }; } // namespace nn::ac::services ================================================ FILE: src/libdecaf/src/nn/acp/nn_acp_enum.h ================================================ #ifndef NN_ACP_ENUM_H #define NN_ACP_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(nn) ENUM_NAMESPACE_ENTER(acp) ENUM_BEG(ACPDeviceType, int32_t) ENUM_VALUE(Unknown1, 1) ENUM_END(ACPDeviceType) ENUM_NAMESPACE_EXIT(acp) ENUM_NAMESPACE_EXIT(nn) #include <common/enum_end.inl> #endif // ifdef NN_ACP_ENUM_H ================================================ FILE: src/libdecaf/src/nn/acp/nn_acp_miscservice.h ================================================ #pragma once #include "nn_acp_types.h" #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_managedbuffer.h" #include "nn/ipc/nn_ipc_service.h" #include <cstdint> namespace nn::acp::services { struct MiscService : ipc::Service<0> { using GetNetworkTime = ipc::Command<MiscService, 101> ::Parameters<> ::Response<int64_t, uint32_t>; using GetTitleIdOfMainApplication = ipc::Command<MiscService, 201> ::Parameters<> ::Response<ACPTitleId>; using GetTitleMetaXml = ipc::Command<MiscService, 205> ::Parameters<ipc::OutBuffer<ACPMetaXml>, ACPTitleId>; }; } // namespace nn::acp::services ================================================ FILE: src/libdecaf/src/nn/acp/nn_acp_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::acp { static constexpr Result ResultSuccess { nn::Result::MODULE_NN_ACP, nn::Result::LEVEL_SUCCESS, 128 }; static constexpr const auto ResultInvalid = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6400, 0x9600>(); static constexpr const auto ResultInvalidParameter = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6480, 0x6500>(); static constexpr const auto ResultInvalidFile = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6500, 0x6580>(); static constexpr const auto ResultInvalidXmlFile = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6580, 0x6600>(); static constexpr const auto ResultFileAccessMode = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6600, 0x6680>(); static constexpr const auto ResultInvalidNetworkTime = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6680, 0x6700>(); static constexpr const auto ResultNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFA00, 0x12C00>(); static constexpr const auto ResultFileNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFA80, 0xFB00>(); static constexpr const auto ResultDirNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFB00, 0xFB80>(); static constexpr const auto ResultDeviceNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFB80, 0xFC00>(); static constexpr const auto ResultTitleNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFC00, 0xFC80>(); static constexpr const auto ResultApplicationNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFC80, 0xFD00>(); static constexpr const auto ResultSystemConfigNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFD00, 0xFD80>(); static constexpr const auto ResultXmlItemNotFound = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFD80, 0xFE00>(); static constexpr const auto ResultAlreadyExists = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x12C00, 0x15E00>(); static constexpr const auto ResultFileAlreadyExists = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x12C80, 0x12D00>(); static constexpr const auto ResultDirAlreadyExists = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x12D00, 0x12D80>(); static constexpr const auto ResultAlreadyDone = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x15E00, 0x19000>(); static constexpr const auto ResultAuthentication = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F400, 0x22600>(); static constexpr const auto ResultInvalidRegion = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F480, 0x1F500>(); static constexpr const auto ResultRestrictedRating = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F500, 0x1F580>(); static constexpr const auto ResultNotPresentRating = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F580, 0x1F600>(); static constexpr const auto ResultPendingRating = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F600, 0x1F680>(); static constexpr const auto ResultNetSettingRequired = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F680, 0x1F700>(); static constexpr const auto ResultNetAccountRequired = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F700, 0x1F780>(); static constexpr const auto ResultNetAccountError = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F780, 0x1F800>(); static constexpr const auto ResultBrowserRequired = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F800, 0x1F880>(); static constexpr const auto ResultOlvRequired = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F880, 0x1F900>(); static constexpr const auto ResultPincodeRequired = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F900, 0x1F980>(); static constexpr const auto ResultIncorrectPincode = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F980, 0x1FA00>(); static constexpr const auto ResultInvalidLogo = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1FA00, 0x1FA80>(); static constexpr const auto ResultDemoExpiredNumber = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1FA80, 0x1FB00>(); static constexpr const auto ResultDrcRequired = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1FB00, 0x1FB80>(); static constexpr const auto ResultNoPermission = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x22600, 0x25800>(); static constexpr const auto ResultNoFilePermission = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x22680, 0x22700>(); static constexpr const auto ResultNoDirPermission = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x22700, 0x22780>(); static constexpr const auto ResultBusy = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x28A00, 0x2BC00>(); static constexpr const auto ResultUsbStorageNotReady = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x28A80, 0x28B00>(); static constexpr const auto ResultCancelled = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2BC00, 0x2EE00>(); static constexpr const auto ResultResource = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EE00, 0x32000>(); static constexpr const auto ResultDeviceFull = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EE80, 0x2EF00>(); static constexpr const auto ResultJournalFull = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EF00, 0x2EF80>(); static constexpr const auto ResultSystemMemory = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EF80, 0x2F000>(); static constexpr const auto ResultFsResource = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2F000, 0x2F080>(); static constexpr const auto ResultIpcResource = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2F080, 0x2F100>(); static constexpr const auto ResultNotInitialised = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x32000, 0x35200>(); static constexpr const auto ResultAccountError = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x35200, 0x38400>(); static constexpr const auto ResultUnsupported = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x38400, 0x3B600>(); static constexpr const auto ResultDevice = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E800, 0x41A00>(); static constexpr const auto ResultDataCorrupted = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E880, 0x3E900>(); static constexpr const auto ResultSlcDataCorrupted = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E900, 0x3E980>(); static constexpr const auto ResultMlcDataCorrupted = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E980, 0x3EA00>(); static constexpr const auto ResultUsbDataCorrupted = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3EA00, 0x3EA80>(); static constexpr const auto ResultMedia = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41A00, 0x44C00>(); static constexpr const auto ResultMediaNotReady = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41A80, 0x41B00>(); static constexpr const auto ResultMediaBroken = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41B00, 0x41B80>(); static constexpr const auto ResultOddMediaNotReady = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41B80, 0x41C00>(); static constexpr const auto ResultOddMediaBroken = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41C00, 0x41C80>(); static constexpr const auto ResultUsbMediaNotReady = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41C80, 0x41D00>(); static constexpr const auto ResultUsbMediaBroken = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41D00, 0x41D80>(); static constexpr const auto ResultMediaWriteProtected = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41D80, 0x41E00>(); static constexpr const auto ResultUsbWriteProtected = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41E00, 0x41E80>(); static constexpr const auto ResultMii = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x44C00, 0x47E00>(); static constexpr const auto ResultEncryptionError = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x44C80, 0x44D00>(); static constexpr const auto ResultFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D000, 0x80000>(); static constexpr const auto ResultFsaFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D080, 0x7D100>(); static constexpr const auto ResultFsaAddClientFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D100, 0x7D180>(); static constexpr const auto ResultMcpTitleFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D180, 0x7D200>(); static constexpr const auto ResultMcpPatchFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D200, 0x7D280>(); static constexpr const auto ResultMcpFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D280, 0x7D300>(); static constexpr const auto ResultSaveFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D300, 0x7D380>(); static constexpr const auto ResultUcFatal = ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D380, 0x7D400>(); } // namespace nn::acp ================================================ FILE: src/libdecaf/src/nn/acp/nn_acp_saveservice.h ================================================ #pragma once #include "nn_acp_enum.h" #include "nn_acp_types.h" #include "nn/ipc/nn_ipc_service.h" #include <cstdint> namespace nn::acp::services { struct SaveService : ipc::Service<2> { using MountSaveDir = ipc::Command<SaveService, 101> ::Parameters<>; using UnmountSaveDir = ipc::Command<SaveService, 102> ::Parameters<>; using CreateSaveDir = ipc::Command<SaveService, 103> ::Parameters<uint32_t, ACPDeviceType>; using CreateSaveDirEx = ipc::Command<SaveService, 108> ::Parameters<uint32_t, ACPTitleId, ACPDeviceType>; using RepairSaveMetaDir = ipc::Command<SaveService, 125> ::Parameters<>; using MountExternalStorage = ipc::Command<SaveService, 150> ::Parameters<>; using UnmountExternalStorage = ipc::Command<SaveService, 151> ::Parameters<>; using IsExternalStorageRequired = ipc::Command<SaveService, 152> ::Parameters<> ::Response<int32_t>; }; } // namespace nn::acp::services ================================================ FILE: src/libdecaf/src/nn/acp/nn_acp_types.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace nn::acp { using ACPTitleId = uint64_t; struct ACPMetaXml { be2_val<uint64_t> title_id; be2_val<uint64_t> boss_id; be2_val<uint64_t> os_version; be2_val<uint64_t> app_size; be2_val<uint64_t> common_save_size; be2_val<uint64_t> account_save_size; be2_val<uint64_t> common_boss_size; be2_val<uint64_t> account_boss_size; be2_val<uint64_t> join_game_mode_mask; be2_val<uint32_t> version; be2_array<char, 32> product_code; be2_array<char, 32> content_platform; be2_array<char, 8> company_code; be2_array<char, 32> mastering_date; be2_val<uint32_t> logo_type; be2_val<uint32_t> app_launch_type; be2_val<uint32_t> invisible_flag; be2_val<uint32_t> no_managed_flag; be2_val<uint32_t> no_event_log; be2_val<uint32_t> no_icon_database; be2_val<uint32_t> launching_flag; be2_val<uint32_t> install_flag; be2_val<uint32_t> closing_msg; be2_val<uint32_t> title_version; be2_val<uint32_t> group_id; be2_val<uint32_t> save_no_rollback; be2_val<uint32_t> bg_daemon_enable; be2_val<uint32_t> join_game_id; be2_val<uint32_t> olv_accesskey; be2_val<uint32_t> wood_tin; be2_val<uint32_t> e_manual; be2_val<uint32_t> e_manual_version; be2_val<uint32_t> region; be2_val<uint32_t> pc_cero; be2_val<uint32_t> pc_esrb; be2_val<uint32_t> pc_bbfc; be2_val<uint32_t> pc_usk; be2_val<uint32_t> pc_pegi_gen; be2_val<uint32_t> pc_pegi_fin; be2_val<uint32_t> pc_pegi_prt; be2_val<uint32_t> pc_pegi_bbfc; be2_val<uint32_t> pc_cob; be2_val<uint32_t> pc_grb; be2_val<uint32_t> pc_cgsrr; be2_val<uint32_t> pc_oflc; be2_val<uint32_t> pc_reserved0; be2_val<uint32_t> pc_reserved1; be2_val<uint32_t> pc_reserved2; be2_val<uint32_t> pc_reserved3; be2_val<uint32_t> ext_dev_nunchaku; be2_val<uint32_t> ext_dev_classic; be2_val<uint32_t> ext_dev_urcc; be2_val<uint32_t> ext_dev_board; be2_val<uint32_t> ext_dev_usb_keyboard; be2_val<uint32_t> ext_dev_etc; be2_array<char, 512> ext_dev_etc_name; be2_val<uint32_t> eula_version; be2_val<uint32_t> drc_use; be2_val<uint32_t> network_use; be2_val<uint32_t> online_account_use; be2_val<uint32_t> direct_boot; be2_array<uint32_t, 8> reserved_flags; be2_array<char, 512> longname_ja; be2_array<char, 512> longname_en; be2_array<char, 512> longname_fr; be2_array<char, 512> longname_de; be2_array<char, 512> longname_it; be2_array<char, 512> longname_es; be2_array<char, 512> longname_zhs; be2_array<char, 512> longname_ko; be2_array<char, 512> longname_nl; be2_array<char, 512> longname_pt; be2_array<char, 512> longname_ru; be2_array<char, 512> longname_zht; be2_array<char, 256> shortname_ja; be2_array<char, 256> shortname_en; be2_array<char, 256> shortname_fr; be2_array<char, 256> shortname_de; be2_array<char, 256> shortname_it; be2_array<char, 256> shortname_es; be2_array<char, 256> shortname_zhs; be2_array<char, 256> shortname_ko; be2_array<char, 256> shortname_nl; be2_array<char, 256> shortname_pt; be2_array<char, 256> shortname_ru; be2_array<char, 256> shortname_zht; be2_array<char, 256> publisher_ja; be2_array<char, 256> publisher_en; be2_array<char, 256> publisher_fr; be2_array<char, 256> publisher_de; be2_array<char, 256> publisher_it; be2_array<char, 256> publisher_es; be2_array<char, 256> publisher_zhs; be2_array<char, 256> publisher_ko; be2_array<char, 256> publisher_nl; be2_array<char, 256> publisher_pt; be2_array<char, 256> publisher_ru; be2_array<char, 256> publisher_zht; be2_array<uint32_t, 32> add_on_unique_ids; UNKNOWN(52); }; CHECK_OFFSET(ACPMetaXml, 0x0000, title_id); CHECK_OFFSET(ACPMetaXml, 0x0008, boss_id); CHECK_OFFSET(ACPMetaXml, 0x0010, os_version); CHECK_OFFSET(ACPMetaXml, 0x0018, app_size); CHECK_OFFSET(ACPMetaXml, 0x0020, common_save_size); CHECK_OFFSET(ACPMetaXml, 0x0028, account_save_size); CHECK_OFFSET(ACPMetaXml, 0x0030, common_boss_size); CHECK_OFFSET(ACPMetaXml, 0x0038, account_boss_size); CHECK_OFFSET(ACPMetaXml, 0x0040, join_game_mode_mask); CHECK_OFFSET(ACPMetaXml, 0x0048, version); CHECK_OFFSET(ACPMetaXml, 0x004C, product_code); CHECK_OFFSET(ACPMetaXml, 0x006C, content_platform); CHECK_OFFSET(ACPMetaXml, 0x008C, company_code); CHECK_OFFSET(ACPMetaXml, 0x0094, mastering_date); CHECK_OFFSET(ACPMetaXml, 0x00B4, logo_type); CHECK_OFFSET(ACPMetaXml, 0x00B8, app_launch_type); CHECK_OFFSET(ACPMetaXml, 0x00BC, invisible_flag); CHECK_OFFSET(ACPMetaXml, 0x00C0, no_managed_flag); CHECK_OFFSET(ACPMetaXml, 0x00C4, no_event_log); CHECK_OFFSET(ACPMetaXml, 0x00C8, no_icon_database); CHECK_OFFSET(ACPMetaXml, 0x00CC, launching_flag); CHECK_OFFSET(ACPMetaXml, 0x00D0, install_flag); CHECK_OFFSET(ACPMetaXml, 0x00D4, closing_msg); CHECK_OFFSET(ACPMetaXml, 0x00D8, title_version); CHECK_OFFSET(ACPMetaXml, 0x00DC, group_id); CHECK_OFFSET(ACPMetaXml, 0x00E0, save_no_rollback); CHECK_OFFSET(ACPMetaXml, 0x00E4, bg_daemon_enable); CHECK_OFFSET(ACPMetaXml, 0x00E8, join_game_id); CHECK_OFFSET(ACPMetaXml, 0x00EC, olv_accesskey); CHECK_OFFSET(ACPMetaXml, 0x00F0, wood_tin); CHECK_OFFSET(ACPMetaXml, 0x00F4, e_manual); CHECK_OFFSET(ACPMetaXml, 0x00F8, e_manual_version); CHECK_OFFSET(ACPMetaXml, 0x00FC, region); CHECK_OFFSET(ACPMetaXml, 0x0100, pc_cero); CHECK_OFFSET(ACPMetaXml, 0x0104, pc_esrb); CHECK_OFFSET(ACPMetaXml, 0x0108, pc_bbfc); CHECK_OFFSET(ACPMetaXml, 0x010C, pc_usk); CHECK_OFFSET(ACPMetaXml, 0x0110, pc_pegi_gen); CHECK_OFFSET(ACPMetaXml, 0x0114, pc_pegi_fin); CHECK_OFFSET(ACPMetaXml, 0x0118, pc_pegi_prt); CHECK_OFFSET(ACPMetaXml, 0x011C, pc_pegi_bbfc); CHECK_OFFSET(ACPMetaXml, 0x0120, pc_cob); CHECK_OFFSET(ACPMetaXml, 0x0124, pc_grb); CHECK_OFFSET(ACPMetaXml, 0x0128, pc_cgsrr); CHECK_OFFSET(ACPMetaXml, 0x012C, pc_oflc); CHECK_OFFSET(ACPMetaXml, 0x0130, pc_reserved0); CHECK_OFFSET(ACPMetaXml, 0x0134, pc_reserved1); CHECK_OFFSET(ACPMetaXml, 0x0138, pc_reserved2); CHECK_OFFSET(ACPMetaXml, 0x013C, pc_reserved3); CHECK_OFFSET(ACPMetaXml, 0x0140, ext_dev_nunchaku); CHECK_OFFSET(ACPMetaXml, 0x0144, ext_dev_classic); CHECK_OFFSET(ACPMetaXml, 0x0148, ext_dev_urcc); CHECK_OFFSET(ACPMetaXml, 0x014C, ext_dev_board); CHECK_OFFSET(ACPMetaXml, 0x0150, ext_dev_usb_keyboard); CHECK_OFFSET(ACPMetaXml, 0x0154, ext_dev_etc); CHECK_OFFSET(ACPMetaXml, 0x0158, ext_dev_etc_name); CHECK_OFFSET(ACPMetaXml, 0x0358, eula_version); CHECK_OFFSET(ACPMetaXml, 0x035C, drc_use); CHECK_OFFSET(ACPMetaXml, 0x0360, network_use); CHECK_OFFSET(ACPMetaXml, 0x0364, online_account_use); CHECK_OFFSET(ACPMetaXml, 0x0368, direct_boot); CHECK_OFFSET(ACPMetaXml, 0x036C, reserved_flags); CHECK_OFFSET(ACPMetaXml, 0x038C, longname_ja); CHECK_OFFSET(ACPMetaXml, 0x058C, longname_en); CHECK_OFFSET(ACPMetaXml, 0x078C, longname_fr); CHECK_OFFSET(ACPMetaXml, 0x098C, longname_de); CHECK_OFFSET(ACPMetaXml, 0x0B8C, longname_it); CHECK_OFFSET(ACPMetaXml, 0x0D8C, longname_es); CHECK_OFFSET(ACPMetaXml, 0x0F8C, longname_zhs); CHECK_OFFSET(ACPMetaXml, 0x118C, longname_ko); CHECK_OFFSET(ACPMetaXml, 0x138C, longname_nl); CHECK_OFFSET(ACPMetaXml, 0x158C, longname_pt); CHECK_OFFSET(ACPMetaXml, 0x178C, longname_ru); CHECK_OFFSET(ACPMetaXml, 0x198C, longname_zht); CHECK_OFFSET(ACPMetaXml, 0x1B8C, shortname_ja); CHECK_OFFSET(ACPMetaXml, 0x1C8C, shortname_en); CHECK_OFFSET(ACPMetaXml, 0x1D8C, shortname_fr); CHECK_OFFSET(ACPMetaXml, 0x1E8C, shortname_de); CHECK_OFFSET(ACPMetaXml, 0x1F8C, shortname_it); CHECK_OFFSET(ACPMetaXml, 0x208C, shortname_es); CHECK_OFFSET(ACPMetaXml, 0x218C, shortname_zhs); CHECK_OFFSET(ACPMetaXml, 0x228C, shortname_ko); CHECK_OFFSET(ACPMetaXml, 0x238C, shortname_nl); CHECK_OFFSET(ACPMetaXml, 0x248C, shortname_pt); CHECK_OFFSET(ACPMetaXml, 0x258C, shortname_ru); CHECK_OFFSET(ACPMetaXml, 0x268C, shortname_zht); CHECK_OFFSET(ACPMetaXml, 0x278C, publisher_ja); CHECK_OFFSET(ACPMetaXml, 0x288C, publisher_en); CHECK_OFFSET(ACPMetaXml, 0x298C, publisher_fr); CHECK_OFFSET(ACPMetaXml, 0x2A8C, publisher_de); CHECK_OFFSET(ACPMetaXml, 0x2B8C, publisher_it); CHECK_OFFSET(ACPMetaXml, 0x2C8C, publisher_es); CHECK_OFFSET(ACPMetaXml, 0x2D8C, publisher_zhs); CHECK_OFFSET(ACPMetaXml, 0x2E8C, publisher_ko); CHECK_OFFSET(ACPMetaXml, 0x2F8C, publisher_nl); CHECK_OFFSET(ACPMetaXml, 0x308C, publisher_pt); CHECK_OFFSET(ACPMetaXml, 0x318C, publisher_ru); CHECK_OFFSET(ACPMetaXml, 0x328C, publisher_zht); CHECK_OFFSET(ACPMetaXml, 0x338C, add_on_unique_ids); CHECK_SIZE(ACPMetaXml, 0x3440); } // namespace nn::acp ================================================ FILE: src/libdecaf/src/nn/act/nn_act_accountloaderservice.h ================================================ #pragma once #include "nn_act_enum.h" #include "nn_act_types.h" #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_service.h" #include "nn/ipc/nn_ipc_managedbuffer.h" namespace nn::act::services { struct AccountLoaderService : ipc::Service<3> { using LoadConsoleAccount = ipc::Command<AccountLoaderService, 1> ::Parameters<SlotNo, ACTLoadOption, ipc::InBuffer<const char>, bool>; }; } // namespace nn::act::services ================================================ FILE: src/libdecaf/src/nn/act/nn_act_accountmanagerservice.h ================================================ #pragma once #include "nn_act_enum.h" #include "nn_act_types.h" #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_service.h" #include "nn/ipc/nn_ipc_managedbuffer.h" namespace nn::act::services { struct AccountManagerService : ipc::Service<2> { using CreateConsoleAccount = ipc::Command<AccountManagerService, 1> ::Parameters<>; }; } // namespace nn::act::services ================================================ FILE: src/libdecaf/src/nn/act/nn_act_clientstandardservice.h ================================================ #pragma once #include "nn_act_enum.h" #include "nn_act_types.h" #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_service.h" #include "nn/ipc/nn_ipc_managedbuffer.h" #include <array> #include <cstdint> namespace nn::act::services { struct ClientStandardService : ipc::Service<0> { using GetCommonInfo = ipc::Command<ClientStandardService, 1> ::Parameters<ipc::OutBuffer<void>, InfoType>; using GetAccountInfo = ipc::Command<ClientStandardService, 2> ::Parameters<SlotNo, ipc::OutBuffer<void>, InfoType>; using GetTransferableId = ipc::Command<ClientStandardService, 4> ::Parameters<SlotNo, uint32_t> ::Response<TransferrableId>; using GetMiiImage = ipc::Command<ClientStandardService, 6> ::Parameters<SlotNo, ipc::OutBuffer<void>, MiiImageType> ::Response<uint32_t>; using GetUuid = ipc::Command<ClientStandardService, 22> ::Parameters<SlotNo, ipc::OutBuffer<Uuid>, int32_t>; using FindSlotNoByUuid = ipc::Command<ClientStandardService, 23> ::Parameters<Uuid, int32_t> ::Response<uint8_t>; }; } // namespace nn::act::services ================================================ FILE: src/libdecaf/src/nn/act/nn_act_enum.h ================================================ #ifndef NN_ACT_ENUM_H #define NN_ACT_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(nn) ENUM_NAMESPACE_ENTER(act) ENUM_BEG(ACTLoadOption, int32_t) ENUM_END(ACTLoadOption) ENUM_BEG(InfoType, int32_t) ENUM_VALUE(NumOfAccounts, 1) ENUM_VALUE(SlotNo, 2) ENUM_VALUE(DefaultAccount, 3) ENUM_VALUE(NetworkTimeDifference, 4) ENUM_VALUE(PersistentId, 5) ENUM_VALUE(LocalFriendCode, 6) ENUM_VALUE(Mii, 7) ENUM_VALUE(AccountId, 8) ENUM_VALUE(EmailAddress, 9) ENUM_VALUE(Birthday, 10) ENUM_VALUE(Country, 11) ENUM_VALUE(PrincipalId, 12) ENUM_VALUE(IsPasswordCacheEnabled, 14) ENUM_VALUE(AccountPasswordCache, 15) ENUM_VALUE(AccountInfo, 17) ENUM_VALUE(HostServerSettings, 18) ENUM_VALUE(Gender, 19) ENUM_VALUE(LastAuthenticationResult, 20) ENUM_VALUE(StickyAccountId, 21) ENUM_VALUE(ParentalControlSlot, 22) ENUM_VALUE(SimpleAddressId, 23) ENUM_VALUE(UtcOffset, 25) ENUM_VALUE(IsCommitted, 26) ENUM_VALUE(MiiName, 27) ENUM_VALUE(NfsPassword, 28) ENUM_VALUE(HasEciVirtualAccount, 29) ENUM_VALUE(TimeZoneId, 30) ENUM_VALUE(IsMiiUpdated, 31) ENUM_VALUE(IsMailAddressValidated, 32) ENUM_VALUE(NextAccountId, 33) ENUM_VALUE(Unk34, 34) ENUM_VALUE(ApplicationUpdateRequired, 35) ENUM_VALUE(DefaultHostServerSettings, 36) ENUM_VALUE(IsServerAccountDeleted, 37) ENUM_VALUE(MiiImageUrl, 38) ENUM_VALUE(StickyPrincipalId, 39) ENUM_VALUE(Unk40, 40) ENUM_VALUE(Unk41, 41) ENUM_VALUE(DefaultHostServerSettingsEx, 42) ENUM_VALUE(DeviceHash, 43) ENUM_VALUE(ServerAccountStatus, 44) ENUM_VALUE(NetworkTime, 46) ENUM_END(InfoType) ENUM_BEG(MiiImageType, int32_t) ENUM_END(MiiImageType) ENUM_NAMESPACE_EXIT(act) ENUM_NAMESPACE_EXIT(nn) #include <common/enum_end.inl> #endif // ifdef NN_ACT_ENUM_H ================================================ FILE: src/libdecaf/src/nn/act/nn_act_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::act { static constexpr Result ResultMailAddressNotConfirmed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x80 }; static constexpr Result ResultLibraryError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFA00 }; static constexpr Result ResultNotInitialised { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFA80 }; static constexpr Result ResultAlreadyInitialised { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFB00 }; static constexpr Result ResultBusy { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFF80 }; static constexpr Result ResultNotImplemented { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12780 }; static constexpr Result ResultDeprecated { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12800 }; static constexpr Result ResultDevelopmentOnly { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12880 }; static constexpr Result ResultInvalidArgument { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12C00 }; static constexpr Result ResultInvalidPointer { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12C80 }; static constexpr Result ResultOutOfRange { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12D00 }; static constexpr Result ResultInvalidSize { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12D80 }; static constexpr Result ResultInvalidFormat { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12E00 }; static constexpr Result ResultInvalidHandle { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12E80 }; static constexpr Result ResultInvalidValue { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12F00 }; static constexpr Result ResultInternalError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x15E00 }; static constexpr Result ResultEndOfStream { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x15E80 }; static constexpr Result ResultFileError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16300 }; static constexpr Result ResultFileNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x16380 }; static constexpr Result ResultFileVersionMismatch { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16400 }; static constexpr Result ResultFileIoError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_FATAL, 0x16480 }; static constexpr Result ResultFileTypeMismatch { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16500 }; static constexpr Result ResultOutOfResource { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16D00 }; static constexpr Result ResultShortOfBuffer { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16D80 }; static constexpr Result ResultOutOfMemory { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17200 }; static constexpr Result ResultOutOfGlobalHeap { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17280 }; static constexpr Result ResultOutOfCrossProcessHeap { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17300 }; static constexpr Result ResultOutOfProcessLocalHeap { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17380 }; static constexpr Result ResultOutOfMxmlHeap { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17400 }; static constexpr Result ResultUcError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19000 }; static constexpr Result ResultUcReadSysConfigError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19080 }; static constexpr Result ResultMcpError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19500 }; static constexpr Result ResultMcpOpenError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19580 }; static constexpr Result ResultMcpGetInfoError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19600 }; static constexpr Result ResultIsoError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19A00 }; static constexpr Result ResultIsoInitFailure { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19A80 }; static constexpr Result ResultIsoGetCountryCodeFailure { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19B00 }; static constexpr Result ResultIsoGetLanguageCodeFailure { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19B80 }; static constexpr Result ResultMxmlError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1a900 }; static constexpr Result ResultIosError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1c200 }; static constexpr Result ResultIosOpenError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1c280 }; static constexpr Result ResultAccountManagementError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f400 }; static constexpr Result ResultAccountNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f480 }; static constexpr Result ResultSlotsFull { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f500 }; static constexpr Result ResultAccountNotLoaded { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f980 }; static constexpr Result ResultAccountAlreadyLoaded { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1fa00 }; static constexpr Result ResultAccountLocked { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1fa80 }; static constexpr Result ResultNotNetworkAccount { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1fe80 }; static constexpr Result ResultNotLocalAccount { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1ff00 }; static constexpr Result ResultAccountNotCommitted { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1ff80 }; static constexpr Result ResultNetworkClockInvalid { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x22680 }; static constexpr Result ResultAuthenticationError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x3e800 }; static constexpr Result ResultHttpError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41a00 }; static constexpr Result ResultHttpUnsupportedProtocol { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41a80 }; static constexpr Result ResultHttpFailedInit { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41b00 }; static constexpr Result ResultHttpURLMalformat { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41b80 }; static constexpr Result ResultHttpNotBuiltIn { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41c00 }; static constexpr Result ResultHttpCouldntResolveProxy { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41c80 }; static constexpr Result ResultHttpCouldntResolveHost { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41d00 }; static constexpr Result ResultHttpCouldntConnect { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41d80 }; static constexpr Result ResultHttpFtpWeirdServerReply { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41e00 }; static constexpr Result ResultHttpRemoteAccessDenied { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41e80 }; static constexpr Result ResultHttpObsolete10 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41f00 }; static constexpr Result ResultHttpFtpWeirdPassReply { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41f80 }; static constexpr Result ResultHttpObsolete12 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42000 }; static constexpr Result ResultHttpFtpWeirdPasvReply { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42080 }; static constexpr Result ResultHttpFtpWeird227Format { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42100 }; static constexpr Result ResultHttpFtpCantGetHost { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42180 }; static constexpr Result ResultHttpObsolete16 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42200 }; static constexpr Result ResultHttpFtpCouldntSetType { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42280 }; static constexpr Result ResultHttpPartialFile { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42300 }; static constexpr Result ResultHttpFtpCouldntRetrFile { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42380 }; static constexpr Result ResultHttpObsolete20 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42400 }; static constexpr Result ResultHttpQuoteError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42480 }; static constexpr Result ResultHttpHttpReturnedError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42500 }; static constexpr Result ResultHttpWriteError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42580 }; static constexpr Result ResultHttpObsolete24 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42600 }; static constexpr Result ResultHttpUploadFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42680 }; static constexpr Result ResultHttpReadError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42700 }; static constexpr Result ResultHttpOutOfMemory { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42780 }; static constexpr Result ResultHttpOperationTimedout { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42800 }; static constexpr Result ResultHttpObsolete29 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42880 }; static constexpr Result ResultHttpFtpPortFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42900 }; static constexpr Result ResultHttpFtpCouldntUseRest { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42980 }; static constexpr Result ResultHttpObsolete32 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42a00 }; static constexpr Result ResultHttpRangeError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42a80 }; static constexpr Result ResultHttpHttpPostError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42b00 }; static constexpr Result ResultHttpSslConnectError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42b80 }; static constexpr Result ResultHttpBadDownloadResume { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42c00 }; static constexpr Result ResultHttpFileCouldntReadFile { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42c80 }; static constexpr Result ResultHttpLdapCannotBind { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42d00 }; static constexpr Result ResultHttpLdapSearchFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42d80 }; static constexpr Result ResultHttpObsolete40 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42e00 }; static constexpr Result ResultHttpFunctionNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42e80 }; static constexpr Result ResultHttpAbortedByCallback { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42f00 }; static constexpr Result ResultHttpBadFunctionArgument { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42f80 }; static constexpr Result ResultHttpObsolete44 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43000 }; static constexpr Result ResultHttpInterfaceFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43080 }; static constexpr Result ResultHttpObsolete46 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43100 }; static constexpr Result ResultHttpTooManyRedirects { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43180 }; static constexpr Result ResultHttpUnknownOption { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43200 }; static constexpr Result ResultHttpTelnetOptionSyntax { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43280 }; static constexpr Result ResultHttpObsolete50 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43300 }; static constexpr Result ResultHttpPeerFailedVerification { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43380 }; static constexpr Result ResultHttpGotNothing { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43400 }; static constexpr Result ResultHttpSslEngineNotfound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43480 }; static constexpr Result ResultHttpSslEngineSetfailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43500 }; static constexpr Result ResultHttpSendError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43580 }; static constexpr Result ResultHttpRecvError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43600 }; static constexpr Result ResultHttpObsolete57 { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43680 }; static constexpr Result ResultHttpSslCertproblem { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43700 }; static constexpr Result ResultHttpSslCipher { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43780 }; static constexpr Result ResultHttpSslCacert { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43800 }; static constexpr Result ResultHttpBadContentEncoding { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43880 }; static constexpr Result ResultHttpLdapInvalidURL { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43900 }; static constexpr Result ResultHttpFilesizeExceeded { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43980 }; static constexpr Result ResultHttpUseSslFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43a00 }; static constexpr Result ResultHttpSendFailRewind { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43a80 }; static constexpr Result ResultHttpSslEngineInitfailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43b00 }; static constexpr Result ResultHttpLoginDenied { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43b80 }; static constexpr Result ResultHttpTftpNotfound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43c00 }; static constexpr Result ResultHttpTftpPerm { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43c80 }; static constexpr Result ResultHttpRemoteDiskFull { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43d00 }; static constexpr Result ResultHttpTftpIllegal { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43d80 }; static constexpr Result ResultHttpTftpUnknownid { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43e00 }; static constexpr Result ResultHttpRemoteFileExists { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43e80 }; static constexpr Result ResultHttpTftpNosuchuser { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43f00 }; static constexpr Result ResultHttpConvFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43f80 }; static constexpr Result ResultHttpConvReqd { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44000 }; static constexpr Result ResultHttpSslCacertBadfile { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44080 }; static constexpr Result ResultHttpRemoteFileNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44100 }; static constexpr Result ResultHttpSsh { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44180 }; static constexpr Result ResultHttpSslShutdownFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44200 }; static constexpr Result ResultHttpAgain { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44280 }; static constexpr Result ResultHttpSslCrlBadfile { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44300 }; static constexpr Result ResultHttpSslIssuerError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44380 }; static constexpr Result ResultHttpFtpPretFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44400 }; static constexpr Result ResultHttpRtspCseqError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44480 }; static constexpr Result ResultHttpRtspSessionError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44500 }; static constexpr Result ResultHttpFtpBadFileList { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44580 }; static constexpr Result ResultHttpChunkFailed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44600 }; static constexpr Result ResultHttpNsslNoCtx { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44680 }; static constexpr Result ResultSoError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x45100 }; static constexpr Result ResultSoSelectError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x45180 }; static constexpr Result ResultRequestError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b000 }; static constexpr Result ResultBadFormatParameter { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b080 }; static constexpr Result ResultBadFormatRequest { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b100 }; static constexpr Result ResultRequestParameterMissing { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b180 }; static constexpr Result ResultWrongHttpMethod { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b200 }; static constexpr Result ResultResponseError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ba00 }; static constexpr Result ResultBadFormatResponse { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ba80 }; static constexpr Result ResultResponseItemMissing { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4bb00 }; static constexpr Result ResultResponseTooLarge { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4bb80 }; static constexpr Result ResultNotModified { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4c480 }; static constexpr Result ResultInvalidCommonParameter { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4c900 }; static constexpr Result ResultInvalidPlatformID { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4c980 }; static constexpr Result ResultUnauthorizedDevice { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ca00 }; static constexpr Result ResultInvalidSerialID { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ca80 }; static constexpr Result ResultInvalidMacAddress { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cb00 }; static constexpr Result ResultInvalidRegion { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cb80 }; static constexpr Result ResultInvalidCountry { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cc00 }; static constexpr Result ResultInvalidLanguage { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cc80 }; static constexpr Result ResultUnauthorizedClient { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cd00 }; static constexpr Result ResultDeviceIDEmpty { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cd80 }; static constexpr Result ResultSerialIDEmpty { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ce00 }; static constexpr Result ResultPlatformIDEmpty { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ce80 }; static constexpr Result ResultInvalidUniqueID { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d380 }; static constexpr Result ResultInvalidClientID { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d400 }; static constexpr Result ResultInvalidClientKey { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d480 }; static constexpr Result ResultInvalidNexClientID { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d880 }; static constexpr Result ResultInvalidGameServerID { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d900 }; static constexpr Result ResultGameServerIDEnvironmentNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d980 }; static constexpr Result ResultGameServerIDUniqueIDNotLinked { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4da00 }; static constexpr Result ResultClientIDUniqueIDNotLinked { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4da80 }; static constexpr Result ResultDeviceMismatch { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e280 }; static constexpr Result ResultCountryMismatch { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e300 }; static constexpr Result ResultEulaNotAccepted { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e380 }; static constexpr Result ResultUpdateRequired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e700 }; static constexpr Result ResultSystemUpdateRequired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e780 }; static constexpr Result ResultApplicationUpdateRequired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e800 }; static constexpr Result ResultUnauthorizedRequest { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ec00 }; static constexpr Result ResultRequestForbidden { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ed00 }; static constexpr Result ResultResourceNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f100 }; static constexpr Result ResultPidNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f180 }; static constexpr Result ResultNexAccountNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f200 }; static constexpr Result ResultGenerateTokenFailure { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f280 }; static constexpr Result ResultRequestNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f300 }; static constexpr Result ResultMasterPinNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f380 }; static constexpr Result ResultMailTextNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f400 }; static constexpr Result ResultSendMailFailure { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f480 }; static constexpr Result ResultApprovalIDNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f500 }; static constexpr Result ResultInvalidEulaParameter { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f600 }; static constexpr Result ResultInvalidEulaCountry { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f680 }; static constexpr Result ResultInvalidEulaCountryAndVersion { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f700 }; static constexpr Result ResultEulaNotFound { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f780 }; static constexpr Result ResultPhraseNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50500 }; static constexpr Result ResultAccountIDAlreadyExists { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50580 }; static constexpr Result ResultAccountIDNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50600 }; static constexpr Result ResultAccountPasswordNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50680 }; static constexpr Result ResultMiiNameNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50700 }; static constexpr Result ResultMailAddressNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50780 }; static constexpr Result ResultAccountIDFormatInvalid { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50800 }; static constexpr Result ResultAccountIDPasswordSame { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50880 }; static constexpr Result ResultAccountIDCharNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50900 }; static constexpr Result ResultAccountIDSuccessiveSymbol { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50980 }; static constexpr Result ResultAccountIDSymbolPositionNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50a00 }; static constexpr Result ResultAccountIDTooManyDigit { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50a80 }; static constexpr Result ResultAccountPasswordCharNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50b00 }; static constexpr Result ResultAccountPasswordTooFewCharTypes { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50b80 }; static constexpr Result ResultAccountPasswordSuccessiveSameChar { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50c00 }; static constexpr Result ResultMailAddressDomainNameNotAcceptable { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50c80 }; static constexpr Result ResultMailAddressDomainNameNotResolved { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50d00 }; static constexpr Result ResultReachedAssociationLimit { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50f80 }; static constexpr Result ResultReachedRegistrationLimit { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51000 }; static constexpr Result ResultCoppaNotAccepted { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51080 }; static constexpr Result ResultParentalControlsRequired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51100 }; static constexpr Result ResultMiiNotRegistered { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51180 }; static constexpr Result ResultDeviceEulaCountryMismatch { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51200 }; static constexpr Result ResultPendingMigration { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51280 }; static constexpr Result ResultWrongUserInput { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51900 }; static constexpr Result ResultWrongAccountPassword { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51980 }; static constexpr Result ResultWrongMailAddress { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51a00 }; static constexpr Result ResultWrongAccountPasswordOrMailAddress { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51a80 }; static constexpr Result ResultWrongConfirmationCode { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51b00 }; static constexpr Result ResultWrongBirthDateOrMailAddress { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51b80 }; static constexpr Result ResultWrongAccountMail { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51c00 }; static constexpr Result ResultAccountAlreadyDeleted { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52380 }; static constexpr Result ResultAccountIDChanged { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52400 }; static constexpr Result ResultAuthenticationLocked { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52480 }; static constexpr Result ResultDeviceInactive { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52500 }; static constexpr Result ResultCoppaAgreementCanceled { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52580 }; static constexpr Result ResultDomainAccountAlreadyExists { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52600 }; static constexpr Result ResultAccountTokenExpired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52880 }; static constexpr Result ResultInvalidAccountToken { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52900 }; static constexpr Result ResultAuthenticationRequired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52980 }; static constexpr Result ResultConfirmationCodeExpired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52d80 }; static constexpr Result ResultMailAddressNotValidated { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53280 }; static constexpr Result ResultExcessiveMailSendRequest { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53300 }; static constexpr Result ResultCreditCardError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53700 }; static constexpr Result ResultCreditCardGeneralFailure { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53780 }; static constexpr Result ResultCreditCardDeclined { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53800 }; static constexpr Result ResultCreditCardBlacklisted { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53880 }; static constexpr Result ResultInvalidCreditCardNumber { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53900 }; static constexpr Result ResultInvalidCreditCardDate { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53980 }; static constexpr Result ResultInvalidCreditCardPin { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53a00 }; static constexpr Result ResultInvalidPostalCode { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53a80 }; static constexpr Result ResultInvalidLocation { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53b00 }; static constexpr Result ResultCreditCardDateExpired { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53b80 }; static constexpr Result ResultCreditCardNumberWrong { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53c00 }; static constexpr Result ResultCreditCardPinWrong { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53c80 }; static constexpr Result ResultBanned { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57800 }; static constexpr Result ResultBannedAccount { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57880 }; static constexpr Result ResultBannedAccountAll { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57900 }; static constexpr Result ResultBannedAccountInApplication { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57980 }; static constexpr Result ResultBannedAccountInNexService { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57a00 }; static constexpr Result ResultBannedAccountInIndependentService { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57a80 }; static constexpr Result ResultBannedDevice { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57d80 }; static constexpr Result ResultBannedDeviceAll { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57e00 }; static constexpr Result ResultBannedDeviceInApplication { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57e80 }; static constexpr Result ResultBannedDeviceInNexService { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57f00 }; static constexpr Result ResultBannedDeviceInIndependentService { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57f80 }; static constexpr Result ResultBannedAccountTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58280 }; static constexpr Result ResultBannedAccountAllTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58300 }; static constexpr Result ResultBannedAccountInApplicationTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58380 }; static constexpr Result ResultBannedAccountInNexServiceTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58400 }; static constexpr Result ResultBannedAccountInIndependentServiceTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58480 }; static constexpr Result ResultBannedDeviceTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58780 }; static constexpr Result ResultBannedDeviceAllTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58800 }; static constexpr Result ResultBannedDeviceInApplicationTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58880 }; static constexpr Result ResultBannedDeviceInNexServiceTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58900 }; static constexpr Result ResultBannedDeviceInIndependentServiceTemporarily { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58980 }; static constexpr Result ResultServiceNotProvided { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a000 }; static constexpr Result ResultUnderMaintenance { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a080 }; static constexpr Result ResultServiceClosed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a100 }; static constexpr Result ResultNintendoNetworkClosed { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a180 }; static constexpr Result ResultNotProvidedCountry { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a200 }; static constexpr Result ResultRestrictionError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5aa00 }; static constexpr Result ResultRestrictedByAge { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5aa80 }; static constexpr Result ResultRestrictedByParentalControls { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5af00 }; static constexpr Result ResultOnGameInternetCommunicationRestricted { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5af80 }; static constexpr Result ResultInternalServerError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5b980 }; static constexpr Result ResultUnknownServerError { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5ba00 }; static constexpr Result ResultUnauthenticatedAfterSalvage { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5db00 }; static constexpr Result ResultAuthenticationFailureUnknown { nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5db80 }; } // namespace nn::act ================================================ FILE: src/libdecaf/src/nn/act/nn_act_serverstandardservice.h ================================================ #pragma once #include "nn_act_enum.h" #include "nn_act_types.h" #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_service.h" #include "nn/ipc/nn_ipc_managedbuffer.h" #include <array> #include <cstdint> namespace nn::act::services { struct ServerStandardService : ipc::Service<1> { using AcquireNexServiceToken = ipc::Command<ServerStandardService, 4> ::Parameters<SlotNo, ipc::OutBuffer<NexAuthenticationResult>, uint32_t, bool>; using Cancel = ipc::Command<ServerStandardService, 100> ::Parameters<>; }; } // namespace nn::act::services ================================================ FILE: src/libdecaf/src/nn/act/nn_act_types.h ================================================ #pragma once #include <array> #include <cstdint> #include <libcpu/be2_struct.h> namespace nn::act { #pragma pack(push, 1) using SlotNo = uint8_t; using LocalFriendCode = uint64_t; using PersistentId = uint32_t; using PrincipalId = uint32_t; using SimpleAddressId = uint32_t; using TransferrableId = uint64_t; using Uuid = std::array<char, 0x10>; static constexpr SlotNo InvalidSlot = 0; static constexpr SlotNo NumSlots = 12; static constexpr SlotNo CurrentUserSlot = 254; static constexpr SlotNo SystemSlot = 255; static constexpr uint32_t AccountIdSize = 17; static constexpr uint32_t NfsPasswordSize = 17; static constexpr uint32_t MiiNameSize = 11; static constexpr uint32_t UuidSize = 16; struct Birthday { be2_val<uint16_t> year; be2_val<uint8_t> month; be2_val<uint8_t> day; }; CHECK_OFFSET(Birthday, 0x00, year); CHECK_OFFSET(Birthday, 0x02, month); CHECK_OFFSET(Birthday, 0x03, day); CHECK_SIZE(Birthday, 0x4); struct NexAuthenticationResult { be2_array<char, 513> token; PADDING(3); be2_array<char, 65> password; PADDING(3); be2_array<char, 16> host; be2_val<uint16_t> port; PADDING(2); }; CHECK_OFFSET(NexAuthenticationResult, 0x000, token); CHECK_OFFSET(NexAuthenticationResult, 0x204, password); CHECK_OFFSET(NexAuthenticationResult, 0x248, host); CHECK_OFFSET(NexAuthenticationResult, 0x258, port); CHECK_SIZE(NexAuthenticationResult, 0x25C); #pragma pack(pop) } // namespace nn::act ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_management_service.h ================================================ #pragma once #include "nn/ipc/nn_ipc_service.h" namespace nn::boss::services { struct ManagementService : ipc::Service<3> { }; } // namespace nn::boss::services ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_private_service.h ================================================ #pragma once #include "nn/ipc/nn_ipc_service.h" namespace nn::boss::services { struct PrivateService : ipc::Service<4> { }; } // namespace nn::boss::services ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_privileged_service.h ================================================ #pragma once #include "nn_boss_types.h" #include "nn/ipc/nn_ipc_service.h" namespace nn::boss::services { struct PrivilegedService : ipc::Service<1> { using AddAccount = ipc::Command<PrivilegedService, 316> ::Parameters<PersistentId>; }; } // namespace nn::boss::services ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::boss { static constexpr Result ResultSuccess { Result::MODULE_NN_BOSS, Result::LEVEL_SUCCESS, 0x00080 }; static constexpr Result ResultNotInitialised { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03200 }; static constexpr Result ResultLibraryNotInitialiased { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03280 }; static constexpr Result ResultInvalid { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03700 }; static constexpr Result ResultInvalidParameter { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03780 }; static constexpr Result ResultInvalidFormat { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03800 }; static constexpr Result ResultInvalidAccount { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03880 }; static constexpr Result ResultInvalidTitle { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03900 }; static constexpr Result ResultNoSupport { Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x04100 }; static constexpr Result ResultInitialized { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1f400 }; static constexpr Result ResultNotExist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1f900 }; static constexpr Result ResultFileNotExist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1f980 }; static constexpr Result ResultBossStorageNotExist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1fa00 }; static constexpr Result ResultDbNotExist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1fa80 }; static constexpr Result ResultRecordNotExist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1fb00 }; static constexpr Result ResultNotCompleted { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20300 }; static constexpr Result ResultNotPermitted { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20800 }; static constexpr Result ResultFull { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20d00 }; static constexpr Result ResultSizeFull { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20d80 }; static constexpr Result ResultCountFull { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20e00 }; static constexpr Result ResultFinished { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21200 }; static constexpr Result ResultServiceFinished { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21280 }; static constexpr Result ResultCanceled { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21700 }; static constexpr Result ResultStoppedByPolicylist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21c00 }; static constexpr Result ResultAlreadyExist { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x22100 }; static constexpr Result ResultCannotGetNetworkTime { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x22600 }; static constexpr Result ResultNotNetworkAccount { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x22b00 }; static constexpr Result ResultRestrictedByParentalControl { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23000 }; static constexpr Result ResultDisableUploadConsoleInformation { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23500 }; static constexpr Result ResultNotConnectNetwork { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23a00 }; static constexpr Result ResultRestrictedByParentalControlTotalEnable { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23f00 }; static constexpr Result ResultNotFound { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x24400 }; static constexpr Result ResultBossStorageNotFound { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x24480 }; static constexpr Result ResultHTTPError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x3e800 }; static constexpr Result ResultFsError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4E200 }; static constexpr Result ResultFsErrorNotInit { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e280 }; static constexpr Result ResultFsErrorBusy { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e300 }; static constexpr Result ResultFsErrorCanceled { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e380 }; static constexpr Result ResultFsErrorEndOfDirectory { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e400 }; static constexpr Result ResultFsErrorEndOfFile { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e480 }; static constexpr Result ResultFsErrorMaxMountpoints { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e500 }; static constexpr Result ResultFsErrorMaxVolumes { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e580 }; static constexpr Result ResultFsErrorMaxClients { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e600 }; static constexpr Result ResultFsErrorMaxFiles { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e680 }; static constexpr Result ResultFsErrorMaxDirs { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e700 }; static constexpr Result ResultFsErrorAlreadyOpen { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e780 }; static constexpr Result ResultFsErrorAlreadyExists { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e800 }; static constexpr Result ResultFsErrorNotFound { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e880 }; static constexpr Result ResultFsErrorNotEmpty { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e900 }; static constexpr Result ResultFsErrorAccessError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e980 }; static constexpr Result ResultFsErrorPermissionError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ea00 }; static constexpr Result ResultFsErrorDataCorrupted { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ea80 }; static constexpr Result ResultFsErrorStorageFull { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4eb00 }; static constexpr Result ResultFsErrorJournalFull { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4eb80 }; static constexpr Result ResultFsErrorUnavailableCmd { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ec00 }; static constexpr Result ResultFsErrorUnsupportedCmd { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ec80 }; static constexpr Result ResultFsErrorInvalidParam { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ed00 }; static constexpr Result ResultFsErrorInvalidPath { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ed80 }; static constexpr Result ResultFsErrorInvalidBuffer { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ee00 }; static constexpr Result ResultFsErrorInvalidAlignment { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ee80 }; static constexpr Result ResultFsErrorInvalidClientHandle { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ef00 }; static constexpr Result ResultFsErrorInvalidFileHandle { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ef80 }; static constexpr Result ResultFsErrorInvalidDirHandle { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f000 }; static constexpr Result ResultFsErrorNotFile { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f080 }; static constexpr Result ResultFsErrorNotDir { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f100 }; static constexpr Result ResultFsErrorFileTooBig { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f180 }; static constexpr Result ResultFsErrorOutOfRange { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f200 }; static constexpr Result ResultFsErrorOutOfResources { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f280 }; static constexpr Result ResultFsErrorMediaNotReady { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f300 }; static constexpr Result ResultFsErrorMediaError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f380 }; static constexpr Result ResultFsErrorWriteProtected { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f400 }; static constexpr Result ResultFsErrorUnknown { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f480 }; static constexpr Result ResultFail { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5aa00 }; static constexpr Result ResultMemoryAllocateError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5dc00 }; static constexpr Result ResultInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e100 }; static constexpr Result ResultSslInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e180 }; static constexpr Result ResultAcpInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e200 }; static constexpr Result ResultActInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e280 }; static constexpr Result ResultPdmInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e300 }; static constexpr Result ResultConfigInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e380 }; static constexpr Result ResultFsInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e400 }; static constexpr Result ResultHTTPInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e480 }; static constexpr Result ResultAcInitializeError { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e500 }; static constexpr Result ResultUnexpect { Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x7ff80 }; } // namespace nn::boss ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_service.h ================================================ #pragma once #include "nn/ipc/nn_ipc_service.h" namespace nn::boss::services { struct BossService : ipc::Service<0> { }; } // namespace nn::boss::services ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_test_service.h ================================================ #pragma once #include "nn/ipc/nn_ipc_service.h" namespace nn::boss::services { struct TestService : ipc::Service<3> { }; } // namespace nn::boss::services ================================================ FILE: src/libdecaf/src/nn/boss/nn_boss_types.h ================================================ #pragma once #include <cstdint> namespace nn::boss { using PersistentId = uint32_t; } // namespace nn::boss ================================================ FILE: src/libdecaf/src/nn/dbg/nn_dbg_result_string.cpp ================================================ #include "nn_dbg_result_string.h" #include "nn/acp/nn_acp_result.h" #include "nn/act/nn_act_result.h" namespace nn::dbg { const char * GetLevelString(nn::Result result) { if (result.isLegacyRepresentation()) { switch (result.level()) { case nn::Result::LEGACY_LEVEL_INFO: return "LEVEL_INFO"; case nn::Result::LEGACY_LEVEL_RESET: return "LEVEL_RESET"; case nn::Result::LEGACY_LEVEL_REINIT: return "LEVEL_REINIT"; case nn::Result::LEGACY_LEVEL_PERMANENT: return "LEVEL_PERMANENT"; case nn::Result::LEGACY_LEVEL_TEMPORARY: return "LEVEL_TEMPORARY"; default: return "<unknown>"; } } else { switch (result.level()) { case nn::Result::LEVEL_SUCCESS: return "LEVEL_SUCCESS"; case nn::Result::LEVEL_FATAL: return "LEVEL_FATAL"; case nn::Result::LEVEL_USAGE: return "LEVEL_USAGE"; case nn::Result::LEVEL_STATUS: return "LEVEL_STATUS"; case nn::Result::LEVEL_END: return "LEVEL_END"; default: return "<unknown>"; } } } const char * GetModuleString(nn::Result result) { if (result.isLegacyRepresentation()) { switch (result.module()) { case nn::Result::LEGACY_MODULE_COMMON: return "MODULE_COMMON"; case nn::Result::LEGACY_MODULE_NN_KERNEL: return "MODULE_NN_KERNEL"; case nn::Result::LEGACY_MODULE_NN_UTIL: return "MODULE_NN_UTIL"; case nn::Result::LEGACY_MODULE_NN_FILE_SERVER: return "MODULE_NN_FILE_SERVER"; case nn::Result::LEGACY_MODULE_NN_LOADER_SERVER: return "MODULE_NN_LOADER_SERVER"; case nn::Result::LEGACY_MODULE_NN_TCB: return "MODULE_NN_TCB"; case nn::Result::LEGACY_MODULE_NN_OS: return "MODULE_NN_OS"; case nn::Result::LEGACY_MODULE_NN_DBG: return "MODULE_NN_DBG"; case nn::Result::LEGACY_MODULE_NN_DMNT: return "MODULE_NN_DMNT"; case nn::Result::LEGACY_MODULE_NN_PDN: return "MODULE_NN_PDN"; case nn::Result::LEGACY_MODULE_NN_GX: return "MODULE_NN_GX"; case nn::Result::LEGACY_MODULE_NN_I2C: return "MODULE_NN_I2C"; case nn::Result::LEGACY_MODULE_NN_GPIO: return "MODULE_NN_GPIO"; case nn::Result::LEGACY_MODULE_NN_DD: return "MODULE_NN_DD"; case nn::Result::LEGACY_MODULE_NN_CODEC: return "MODULE_NN_CODEC"; case nn::Result::LEGACY_MODULE_NN_SPI: return "MODULE_NN_SPI"; case nn::Result::LEGACY_MODULE_NN_PXI: return "MODULE_NN_PXI"; case nn::Result::LEGACY_MODULE_NN_FS: return "MODULE_NN_FS"; case nn::Result::LEGACY_MODULE_NN_DI: return "MODULE_NN_DI"; case nn::Result::LEGACY_MODULE_NN_HID: return "MODULE_NN_HID"; case nn::Result::LEGACY_MODULE_NN_CAMERA: return "MODULE_NN_CAMERA"; case nn::Result::LEGACY_MODULE_NN_PI: return "MODULE_NN_PI"; case nn::Result::LEGACY_MODULE_NN_PM: return "MODULE_NN_PM"; case nn::Result::LEGACY_MODULE_NN_PMLOW: return "MODULE_NN_PMLOW"; case nn::Result::LEGACY_MODULE_NN_FSI: return "MODULE_NN_FSI"; case nn::Result::LEGACY_MODULE_NN_SRV: return "MODULE_NN_SRV"; case nn::Result::LEGACY_MODULE_NN_NDM: return "MODULE_NN_NDM"; case nn::Result::LEGACY_MODULE_NN_NWM: return "MODULE_NN_NWM"; case nn::Result::LEGACY_MODULE_NN_SOCKET: return "MODULE_NN_SOCKET"; case nn::Result::LEGACY_MODULE_NN_LDR: return "MODULE_NN_LDR"; case nn::Result::LEGACY_MODULE_NN_ACC: return "MODULE_NN_ACC"; case nn::Result::LEGACY_MODULE_NN_ROMFS: return "MODULE_NN_ROMFS"; case nn::Result::LEGACY_MODULE_NN_AM: return "MODULE_NN_AM"; case nn::Result::LEGACY_MODULE_NN_HIO: return "MODULE_NN_HIO"; case nn::Result::LEGACY_MODULE_NN_UPDATER: return "MODULE_NN_UPDATER"; case nn::Result::LEGACY_MODULE_NN_MIC: return "MODULE_NN_MIC"; case nn::Result::LEGACY_MODULE_NN_FND: return "MODULE_NN_FND"; case nn::Result::LEGACY_MODULE_NN_MP: return "MODULE_NN_MP"; case nn::Result::LEGACY_MODULE_NN_MPWL: return "MODULE_NN_MPWL"; case nn::Result::LEGACY_MODULE_NN_AC: return "MODULE_NN_AC"; case nn::Result::LEGACY_MODULE_NN_HTTP: return "MODULE_NN_HTTP"; case nn::Result::LEGACY_MODULE_NN_DSP: return "MODULE_NN_DSP"; case nn::Result::LEGACY_MODULE_NN_SND: return "MODULE_NN_SND"; case nn::Result::LEGACY_MODULE_NN_DLP: return "MODULE_NN_DLP"; case nn::Result::LEGACY_MODULE_NN_HIOLOW: return "MODULE_NN_HIOLOW"; case nn::Result::LEGACY_MODULE_NN_CSND: return "MODULE_NN_CSND"; case nn::Result::LEGACY_MODULE_NN_SSL: return "MODULE_NN_SSL"; case nn::Result::LEGACY_MODULE_NN_AMLOW: return "MODULE_NN_AMLOW"; case nn::Result::LEGACY_MODULE_NN_NEX: return "MODULE_NN_NEX"; case nn::Result::LEGACY_MODULE_NN_FRIENDS: return "MODULE_NN_FRIENDS"; case nn::Result::LEGACY_MODULE_NN_RDT: return "MODULE_NN_RDT"; case nn::Result::LEGACY_MODULE_NN_APPLET: return "MODULE_NN_APPLET"; case nn::Result::LEGACY_MODULE_NN_NIM: return "MODULE_NN_NIM"; case nn::Result::LEGACY_MODULE_NN_PTM: return "MODULE_NN_PTM"; case nn::Result::LEGACY_MODULE_NN_MIDI: return "MODULE_NN_MIDI"; case nn::Result::LEGACY_MODULE_NN_MC: return "MODULE_NN_MC"; case nn::Result::LEGACY_MODULE_NN_SWC: return "MODULE_NN_SWC"; case nn::Result::LEGACY_MODULE_NN_FATFS: return "MODULE_NN_FATFS"; case nn::Result::LEGACY_MODULE_NN_NGC: return "MODULE_NN_NGC"; case nn::Result::LEGACY_MODULE_NN_CARD: return "MODULE_NN_CARD"; case nn::Result::LEGACY_MODULE_NN_CARDNOR: return "MODULE_NN_CARDNOR"; case nn::Result::LEGACY_MODULE_NN_SDMC: return "MODULE_NN_SDMC"; case nn::Result::LEGACY_MODULE_NN_BOSS: return "MODULE_NN_BOSS"; case nn::Result::LEGACY_MODULE_NN_DBM: return "MODULE_NN_DBM"; case nn::Result::LEGACY_MODULE_NN_CFG: return "MODULE_NN_CFG"; case nn::Result::LEGACY_MODULE_NN_PS: return "MODULE_NN_PS"; case nn::Result::LEGACY_MODULE_NN_CEC: return "MODULE_NN_CEC"; case nn::Result::LEGACY_MODULE_NN_IR: return "MODULE_NN_IR"; case nn::Result::LEGACY_MODULE_NN_UDS: return "MODULE_NN_UDS"; case nn::Result::LEGACY_MODULE_NN_PL: return "MODULE_NN_PL"; case nn::Result::LEGACY_MODULE_NN_CUP: return "MODULE_NN_CUP"; case nn::Result::LEGACY_MODULE_NN_GYROSCOPE: return "MODULE_NN_GYROSCOPE"; case nn::Result::LEGACY_MODULE_NN_MCU: return "MODULE_NN_MCU"; case nn::Result::LEGACY_MODULE_NN_NS: return "MODULE_NN_NS"; case nn::Result::LEGACY_MODULE_NN_NEWS: return "MODULE_NN_NEWS"; case nn::Result::LEGACY_MODULE_NN_RO: return "MODULE_NN_RO"; case nn::Result::LEGACY_MODULE_NN_GD: return "MODULE_NN_GD"; case nn::Result::LEGACY_MODULE_NN_CARDSPI: return "MODULE_NN_CARDSPI"; case nn::Result::LEGACY_MODULE_NN_EC: return "MODULE_NN_EC"; case nn::Result::LEGACY_MODULE_NN_WEBBRS: return "MODULE_NN_WEBBRS"; case nn::Result::LEGACY_MODULE_NN_TEST: return "MODULE_NN_TEST"; case nn::Result::LEGACY_MODULE_NN_ENC: return "MODULE_NN_ENC"; case nn::Result::LEGACY_MODULE_NN_PIA: return "MODULE_NN_PIA"; case nn::Result::LEGACY_MODULE_APPLICATION: return "MODULE_APPLICATION"; } } else { switch (result.module()) { case nn::Result::MODULE_COMMON: return "MODULE_COMMON"; case nn::Result::MODULE_NN_IPC: return "MODULE_NN_IPC"; case nn::Result::MODULE_NN_BOSS: return "MODULE_NN_BOSS"; case nn::Result::MODULE_NN_ACP: return "MODULE_NN_ACP"; case nn::Result::MODULE_NN_IOS: return "MODULE_NN_IOS"; case nn::Result::MODULE_NN_NIM: return "MODULE_NN_NIM"; case nn::Result::MODULE_NN_PDM: return "MODULE_NN_PDM"; case nn::Result::MODULE_NN_ACT: return "MODULE_NN_ACT"; case nn::Result::MODULE_NN_NGC: return "MODULE_NN_NGC"; case nn::Result::MODULE_NN_ECA: return "MODULE_NN_ECA"; case nn::Result::MODULE_NN_NUP: return "MODULE_NN_NUP"; case nn::Result::MODULE_NN_NDM: return "MODULE_NN_NDM"; case nn::Result::MODULE_NN_FP: return "MODULE_NN_FP"; case nn::Result::MODULE_NN_AC: return "MODULE_NN_AC"; case nn::Result::MODULE_NN_CONNTEST: return "MODULE_NN_CONNTEST"; case nn::Result::MODULE_NN_DRMAPP: return "MODULE_NN_DRMAPP"; case nn::Result::MODULE_NN_TELNET: return "MODULE_NN_TELNET"; case nn::Result::MODULE_NN_OLV: return "MODULE_NN_OLV"; case nn::Result::MODULE_NN_VCTL: return "MODULE_NN_VCTL"; case nn::Result::MODULE_NN_NEIA: return "MODULE_NN_NEIA"; case nn::Result::MODULE_NN_SPM: return "MODULE_NN_SPM"; case nn::Result::MODULE_NN_EMD: return "MODULE_NN_EMD"; case nn::Result::MODULE_NN_EC: return "MODULE_NN_EC"; case nn::Result::MODULE_NN_CIA: return "MODULE_NN_CIA"; case nn::Result::MODULE_NN_SL: return "MODULE_NN_SL"; case nn::Result::MODULE_NN_ECO: return "MODULE_NN_ECO"; case nn::Result::MODULE_NN_TRIAL: return "MODULE_NN_TRIAL"; case nn::Result::MODULE_NN_NFP: return "MODULE_NN_NFP"; case nn::Result::MODULE_NN_TEST: return "MODULE_NN_TEST"; } } return "<unknown>"; } const char * GetSummaryString(nn::Result result) { if (!result.isLegacyRepresentation()) { return "SUMMARY_SUCCESS"; } switch (result.legacySummary()) { case nn::Result::LEGACY_SUMMARY_SUCCESS: return "SUMMARY_SUCCESS"; case nn::Result::LEGACY_SUMMARY_NOTHING_HAPPENED: return "SUMMARY_NOTHING_HAPPENED"; case nn::Result::LEGACY_SUMMARY_WOULD_BLOCK: return "SUMMARY_WOULD_BLOCK"; case nn::Result::LEGACY_SUMMARY_OUT_OF_RESOURCE: return "SUMMARY_OUT_OF_RESOURCE"; case nn::Result::LEGACY_SUMMARY_NOT_FOUND: return "SUMMARY_NOT_FOUND"; case nn::Result::LEGACY_SUMMARY_INVALID_STATE: return "SUMMARY_INVALID_STATE"; case nn::Result::LEGACY_SUMMARY_NOT_SUPPORTED: return "SUMMARY_NOT_SUPPORTED"; case nn::Result::LEGACY_SUMMARY_INVALID_ARGUMENT: return "SUMMARY_INVALID_ARGUMENT"; case nn::Result::LEGACY_SUMMARY_WRONG_ARGUMENT: return "SUMMARY_WRONG_ARGUMENT"; case nn::Result::LEGACY_SUMMARY_CANCELLED: return "SUMMARY_CANCELLED"; case nn::Result::LEGACY_SUMMARY_STATUS_CHANGED: return "SUMMARY_STATUS_CHANGED"; case nn::Result::LEGACY_SUMMARY_INTERNAL: return "SUMMARY_INTERNAL"; } return "<unknown>"; } static const char * GetLegacyDescriptionString(nn::Result result) { switch (result.description()) { case nn::Result::LEGACY_DESCRIPTION_SUCCESS: return "DESCRIPTION_SUCCESS"; case nn::Result::LEGACY_DESCRIPTION_TIMEOUT: return "DESCRIPTION_TIMEOUT"; case nn::Result::LEGACY_DESCRIPTION_OUT_OF_RANGE: return "DESCRIPTION_OUT_OF_RANGE"; case nn::Result::LEGACY_DESCRIPTION_ALREADY_EXISTS: return "DESCRIPTION_ALREADY_EXISTS"; case nn::Result::LEGACY_DESCRIPTION_CANCEL_REQUESTED: return "DESCRIPTION_CANCEL_REQUESTED"; case nn::Result::LEGACY_DESCRIPTION_NOT_FOUND: return "DESCRIPTION_NOT_FOUND"; case nn::Result::LEGACY_DESCRIPTION_ALREADY_INITIALIZED: return "DESCRIPTION_ALREADY_INITIALIZED"; case nn::Result::LEGACY_DESCRIPTION_NOT_INITIALIZED: return "DESCRIPTION_NOT_INITIALIZED"; case nn::Result::LEGACY_DESCRIPTION_INVALID_HANDLE: return "DESCRIPTION_INVALID_HANDLE"; case nn::Result::LEGACY_DESCRIPTION_INVALID_POINTER: return "DESCRIPTION_INVALID_POINTER"; case nn::Result::LEGACY_DESCRIPTION_INVALID_ADDRESS: return "DESCRIPTION_INVALID_ADDRESS"; case nn::Result::LEGACY_DESCRIPTION_NOT_IMPLEMENTED: return "DESCRIPTION_NOT_IMPLEMENTED"; case nn::Result::LEGACY_DESCRIPTION_OUT_OF_MEMORY: return "DESCRIPTION_OUT_OF_MEMORY"; case nn::Result::LEGACY_DESCRIPTION_MISALIGNED_SIZE: return "DESCRIPTION_MISALIGNED_SIZE"; case nn::Result::LEGACY_DESCRIPTION_MISALIGNED_ADDRESS: return "DESCRIPTION_MISALIGNED_ADDRESS"; case nn::Result::LEGACY_DESCRIPTION_BUSY: return "DESCRIPTION_BUSY"; case nn::Result::LEGACY_DESCRIPTION_NO_DATA: return "DESCRIPTION_NO_DATA"; case nn::Result::LEGACY_DESCRIPTION_INVALID_COMBINATION: return "DESCRIPTION_INVALID_COMBINATION"; case nn::Result::LEGACY_DESCRIPTION_INVALID_ENUM_VALUE: return "DESCRIPTION_INVALID_ENUM_VALUE"; case nn::Result::LEGACY_DESCRIPTION_INVALID_SIZE: return "DESCRIPTION_INVALID_SIZE"; case nn::Result::LEGACY_DESCRIPTION_ALREADY_DONE: return "DESCRIPTION_ALREADY_DONE"; case nn::Result::LEGACY_DESCRIPTION_NOT_AUTHORIZED: return "DESCRIPTION_NOT_AUTHORIZED"; case nn::Result::LEGACY_DESCRIPTION_TOO_LARGE: return "DESCRIPTION_TOO_LARGE"; case nn::Result::LEGACY_DESCRIPTION_INVALID_SELECTION: return "DESCRIPTION_INVALID_SELECTION"; default: return "<unknown>"; } } static const char * GetAcpDescriptionString(nn::Result result) { if (result == acp::ResultInvalidParameter) { return "INVALID_PARAMETER"; } else if (result == acp::ResultInvalidFile) { return "INVALID_FILE"; } else if (result == acp::ResultInvalidXmlFile) { return "INVALID_XML_FILE"; } else if (result == acp::ResultFileAccessMode) { return "FILE_ACCESS_MODE"; } else if (result == acp::ResultInvalidNetworkTime) { return "INVALID_NETWORK_TIME"; } else if (result == acp::ResultInvalid) { return "INVALID"; } if (result == acp::ResultFileNotFound) { return "FILE_NOT_FOUND"; } else if (result == acp::ResultDirNotFound) { return "DIR_NOT_FOUND"; } else if (result == acp::ResultDeviceNotFound) { return "DEVICE_NOT_FOUND"; } else if (result == acp::ResultTitleNotFound) { return "TITLE_NOT_FOUND"; } else if (result == acp::ResultApplicationNotFound) { return "APPLICATION_NOT_FOUND"; } else if (result == acp::ResultSystemConfigNotFound) { return "SYSTEM_CONFIG_NOT_FOUND"; } else if (result == acp::ResultXmlItemNotFound) { return "XML_ITEM_NOT_FOUND"; } else if (result == acp::ResultNotFound) { return "NOT_FOUND"; } if (result == acp::ResultFileAlreadyExists) { return "FILE_ALREADY_EXISTS"; } else if (result == acp::ResultDirAlreadyExists) { return "DIR_ALREADY_EXISTS"; } else if (result == acp::ResultAlreadyExists) { return "ALREADY_EXISTS"; } if (result == acp::ResultAlreadyDone) { return "ALREADY_DONE"; } if (result == acp::ResultInvalidRegion) { return "INVALID_REGION"; } else if (result == acp::ResultRestrictedRating) { return "RESTRICTED_RATING"; } else if (result == acp::ResultNotPresentRating) { return "NOT_PRESENT_RATING"; } else if (result == acp::ResultPendingRating) { return "PENDING_RATING"; } else if (result == acp::ResultNetSettingRequired) { return "NET_SETTING_REQUIRED"; } else if (result == acp::ResultNetAccountRequired) { return "NET_ACCOUNT_REQUIRED"; } else if (result == acp::ResultNetAccountError) { return "NET_ACCOUNT_ERROR"; } else if (result == acp::ResultBrowserRequired) { return "BROWSER_REQUIRED"; } else if (result == acp::ResultOlvRequired) { return "OLV_REQUIRED"; } else if (result == acp::ResultPincodeRequired) { return "PINCODE_REQUIRED"; } else if (result == acp::ResultIncorrectPincode) { return "INCORRECT_PINCODE"; } else if (result == acp::ResultInvalidLogo) { return "INVALID_LOGO"; } else if (result == acp::ResultDemoExpiredNumber) { return "DEMO_EXPIRED_NUMBER"; } else if (result == acp::ResultDrcRequired) { return "DRC_REQUIRED"; } else if (result == acp::ResultAuthentication) { return "AUTHENTICATION"; } if (result == acp::ResultNoFilePermission) { return "NO_FILE_PERMISSION"; } else if (result == acp::ResultNoDirPermission) { return "NO_DIR_PERMISSION"; } else if (result == acp::ResultNoPermission) { return "NO_PERMISSION"; } if (result == acp::ResultUsbStorageNotReady) { return "USB_STORAGE_NOT_READY"; } else if (result == acp::ResultBusy) { return "BUSY"; } if (result == acp::ResultCancelled) { return "CANCELLED"; } if (result == acp::ResultDeviceFull) { return "DEVICE_FULL"; } else if (result == acp::ResultJournalFull) { return "JOURNAL_FULL"; } else if (result == acp::ResultSystemMemory) { return "SYSTEM_MEMORY"; } else if (result == acp::ResultFsResource) { return "FS_RESOURCE"; } else if (result == acp::ResultIpcResource) { return "IPC_RESOURCE"; } else if (result == acp::ResultResource) { return "RESOURCE"; } if (result == acp::ResultNotInitialised) { return "NOT_INITIALISED"; } if (result == acp::ResultAccountError) { return "ACCOUNT_ERROR"; } if (result == acp::ResultUnsupported) { return "UNSUPPORTED"; } if (result == acp::ResultDataCorrupted) { return "DATA_CORRUPTED"; } else if (result == acp::ResultSlcDataCorrupted) { return "SLC_DATA_CORRUPTED"; } else if (result == acp::ResultMlcDataCorrupted) { return "MLC_DATA_CORRUPTED"; } else if (result == acp::ResultUsbDataCorrupted) { return "USB_DATA_CORRUPTED"; } else if (result == acp::ResultDevice) { return "DEVICE"; } if (result == acp::ResultMediaNotReady) { return "MEDIA_NOT_READY"; } else if (result == acp::ResultMediaBroken) { return "MEDIA_BROKEN"; } else if (result == acp::ResultOddMediaNotReady) { return "ODD_MEDIA_NOT_READY"; } else if (result == acp::ResultOddMediaBroken) { return "ODD_MEDIA_BROKEN"; } else if (result == acp::ResultUsbMediaNotReady) { return "USB_MEDIA_NOT_READY"; } else if (result == acp::ResultUsbMediaBroken) { return "USB_MEDIA_BROKEN"; } else if (result == acp::ResultMediaWriteProtected) { return "MEDIA_WRITE_PROTECTED"; } else if (result == acp::ResultUsbWriteProtected) { return "USB_WRITE_PROTECTED"; } else if (result == acp::ResultMedia) { return "MEDIA"; } if (result == acp::ResultEncryptionError) { return "ENCRYPTION_ERROR"; } else if (result == acp::ResultMii) { return "MII"; } if (result == acp::ResultFsaFatal) { return "FSA_FATAL"; } else if (result == acp::ResultFsaAddClientFatal) { return "FSA_ADD_CLIENT_FATAL"; } else if (result == acp::ResultMcpTitleFatal) { return "MCP_TITLE_FATAL"; } else if (result == acp::ResultMcpPatchFatal) { return "MCP_PATCH_FATAL"; } else if (result == acp::ResultMcpFatal) { return "MCP_FATAL"; } else if (result == acp::ResultSaveFatal) { return "SAVE_FATAL"; } else if (result == acp::ResultUcFatal) { return "UC_FATAL"; } else if (result == acp::ResultFatal) { return "FATAL"; } return "<unknown>"; } static const char * GetActDescriptionString(nn::Result result) { if (result == act::ResultMailAddressNotConfirmed) { return "MAIL_ADDRESS_NOT_CONFIRMED"; } else if (result == act::ResultLibraryError) { return "LIBRARY_ERROR"; } else if (result == act::ResultNotInitialised) { return "NOT_INITIALISED"; } else if (result == act::ResultAlreadyInitialised) { return "ALREADY_INITIALISED"; } else if (result == act::ResultBusy) { return "BUSY"; } else if (result == act::ResultNotImplemented) { return "NOT_IMPLEMENTED"; } else if (result == act::ResultDeprecated) { return "DEPRECATED"; } else if (result == act::ResultDevelopmentOnly) { return "DEVELOPMENT_ONLY"; } else if (result == act::ResultInvalidArgument) { return "INVALID_ARGUMENT"; } else if (result == act::ResultInvalidPointer) { return "INVALID_POINTER"; } else if (result == act::ResultOutOfRange) { return "OUT_OF_RANGE"; } else if (result == act::ResultInvalidSize) { return "INVALID_SIZE"; } else if (result == act::ResultInvalidFormat) { return "INVALID_FORMAT"; } else if (result == act::ResultInvalidHandle) { return "INVALID_HANDLE"; } else if (result == act::ResultInvalidValue) { return "INVALID_VALUE"; } if (result == act::ResultInternalError) { return "INTERNAL_ERROR"; } else if (result == act::ResultEndOfStream) { return "END_OF_STREAM"; } else if (result == act::ResultFileError) { return "FILE_ERROR"; } else if (result == act::ResultFileNotFound) { return "FILE_NOT_FOUND"; } else if (result == act::ResultFileVersionMismatch) { return "FILE_VERSION_MISMATCH"; } else if (result == act::ResultFileIoError) { return "FILE_IO_ERROR"; } else if (result == act::ResultFileTypeMismatch) { return "FILE_TYPE_MISMATCH"; } else if (result == act::ResultOutOfResource) { return "OUT_OF_RESOURCE"; } else if (result == act::ResultShortOfBuffer) { return "SHORT_OF_BUFFER"; } else if (result == act::ResultOutOfMemory) { return "OUT_OF_MEMORY"; } else if (result == act::ResultOutOfGlobalHeap) { return "OUT_OF_GLOBAL_HEAP"; } else if (result == act::ResultOutOfCrossProcessHeap) { return "OUT_OF_CROSS_PROCESS_HEAP"; } else if (result == act::ResultOutOfProcessLocalHeap) { return "OUT_OF_PROCESS_LOCAL_HEAP"; } else if (result == act::ResultOutOfMxmlHeap) { return "OUT_OF_MXML_HEAP"; } else if (result == act::ResultUcError) { return "UC_ERROR"; } else if (result == act::ResultUcReadSysConfigError) { return "UC_READ_SYS_CONFIG_ERROR"; } else if (result == act::ResultMcpError) { return "MCP_ERROR"; } else if (result == act::ResultMcpOpenError) { return "MCP_OPEN_ERROR"; } else if (result == act::ResultMcpGetInfoError) { return "MCP_GET_INFO_ERROR"; } else if (result == act::ResultIsoError) { return "ISO_ERROR"; } else if (result == act::ResultIsoInitFailure) { return "ISO_INIT_FAILURE"; } else if (result == act::ResultIsoGetCountryCodeFailure) { return "ISO_GET_COUNTRY_CODE_FAILURE"; } else if (result == act::ResultIsoGetLanguageCodeFailure) { return "ISO_GET_LANGUAGE_CODE_FAILURE"; } else if (result == act::ResultMxmlError) { return "MXML_ERROR"; } else if (result == act::ResultIosError) { return "IOS_ERROR"; } else if (result == act::ResultIosOpenError) { return "IOS_OPEN_ERROR"; } if (result == act::ResultAccountManagementError) { return "ACCOUNT_MANAGEMENT_ERROR"; } else if (result == act::ResultAccountNotFound) { return "ACCOUNT_NOT_FOUND"; } else if (result == act::ResultSlotsFull) { return "SLOTS_FULL"; } else if (result == act::ResultAccountNotLoaded) { return "ACCOUNT_NOT_LOADED"; } else if (result == act::ResultAccountAlreadyLoaded) { return "ACCOUNT_ALREADY_LOADED"; } else if (result == act::ResultAccountLocked) { return "ACCOUNT_LOCKED"; } else if (result == act::ResultNotNetworkAccount) { return "NOT_NETWORK_ACCOUNT"; } else if (result == act::ResultNotLocalAccount) { return "NOT_LOCAL_ACCOUNT"; } else if (result == act::ResultAccountNotCommitted) { return "ACCOUNT_NOT_COMMITTED"; } else if (result == act::ResultNetworkClockInvalid) { return "NETWORK_CLOCK_INVALID"; } else if (result == act::ResultAuthenticationError) { return "AUTHENTICATION_ERROR"; } if (result == act::ResultHttpError) { return "HTTP_ERROR"; } else if (result == act::ResultHttpUnsupportedProtocol) { return "HTTP_UNSUPPORTED_PROTOCOL"; } else if (result == act::ResultHttpFailedInit) { return "HTTP_FAILED_INIT"; } else if (result == act::ResultHttpURLMalformat) { return "HTTP_URL_MALFORMAT"; } else if (result == act::ResultHttpNotBuiltIn) { return "HTTP_NOT_BUILT_IN"; } else if (result == act::ResultHttpCouldntResolveProxy) { return "HTTP_COULDNT_RESOLVE_PROXY"; } else if (result == act::ResultHttpCouldntResolveHost) { return "HTTP_COULDNT_RESOLVE_HOST"; } else if (result == act::ResultHttpCouldntConnect) { return "HTTP_COULDNT_CONNECT"; } else if (result == act::ResultHttpFtpWeirdServerReply) { return "HTTP_FTP_WEIRD_SERVER_REPLY"; } else if (result == act::ResultHttpRemoteAccessDenied) { return "HTTP_REMOTE_ACCESS_DENIED"; } else if (result == act::ResultHttpObsolete10) { return "HTTP_OBSOLETE10"; } else if (result == act::ResultHttpFtpWeirdPassReply) { return "HTTP_FTP_WEIRD_PASS_REPLY"; } else if (result == act::ResultHttpObsolete12) { return "HTTP_OBSOLETE12"; } else if (result == act::ResultHttpFtpWeirdPasvReply) { return "HTTP_FTP_WEIRD_PASV_REPLY"; } else if (result == act::ResultHttpFtpWeird227Format) { return "HTTP_FTP_WEIRD_227_FORMAT"; } else if (result == act::ResultHttpFtpCantGetHost) { return "HTTP_FTP_CANT_GET_HOST"; } else if (result == act::ResultHttpObsolete16) { return "HTTP_OBSOLETE16"; } else if (result == act::ResultHttpFtpCouldntSetType) { return "HTTP_FTP_COULDNT_SET_TYPE"; } else if (result == act::ResultHttpPartialFile) { return "HTTP_PARTIAL_FILE"; } else if (result == act::ResultHttpFtpCouldntRetrFile) { return "HTTP_FTP_COULDNT_RETR_FILE"; } else if (result == act::ResultHttpObsolete20) { return "HTTP_OBSOLETE20"; } else if (result == act::ResultHttpQuoteError) { return "HTTP_QUOTE_ERROR"; } else if (result == act::ResultHttpHttpReturnedError) { return "HTTP_HTTP_RETURNED_ERROR"; } else if (result == act::ResultHttpWriteError) { return "HTTP_WRITE_ERROR"; } else if (result == act::ResultHttpObsolete24) { return "HTTP_OBSOLETE24"; } else if (result == act::ResultHttpUploadFailed) { return "HTTP_UPLOAD_FAILED"; } else if (result == act::ResultHttpReadError) { return "HTTP_READ_ERROR"; } else if (result == act::ResultHttpOutOfMemory) { return "HTTP_OUT_OF_MEMORY"; } else if (result == act::ResultHttpOperationTimedout) { return "HTTP_OPERATION_TIMEDOUT"; } else if (result == act::ResultHttpObsolete29) { return "HTTP_OBSOLETE29"; } else if (result == act::ResultHttpFtpPortFailed) { return "HTTP_FTP_PORT_FAILED"; } else if (result == act::ResultHttpFtpCouldntUseRest) { return "HTTP_FTP_COULDNT_USE_REST"; } else if (result == act::ResultHttpObsolete32) { return "HTTP_OBSOLETE32"; } else if (result == act::ResultHttpRangeError) { return "HTTP_RANGE_ERROR"; } else if (result == act::ResultHttpHttpPostError) { return "HTTP_HTTP_POST_ERROR"; } else if (result == act::ResultHttpSslConnectError) { return "HTTP_SSL_CONNECT_ERROR"; } else if (result == act::ResultHttpBadDownloadResume) { return "HTTP_BAD_DOWNLOAD_RESUME"; } else if (result == act::ResultHttpFileCouldntReadFile) { return "HTTP_FILE_COULDNT_READ_FILE"; } else if (result == act::ResultHttpLdapCannotBind) { return "HTTP_LDAP_CANNOT_BIND"; } else if (result == act::ResultHttpLdapSearchFailed) { return "HTTP_LDAP_SEARCH_FAILED"; } else if (result == act::ResultHttpObsolete40) { return "HTTP_OBSOLETE40"; } else if (result == act::ResultHttpFunctionNotFound) { return "HTTP_FUNCTION_NOT_FOUND"; } else if (result == act::ResultHttpAbortedByCallback) { return "HTTP_ABORTED_BY_CALLBACK"; } else if (result == act::ResultHttpBadFunctionArgument) { return "HTTP_BAD_FUNCTION_ARGUMENT"; } else if (result == act::ResultHttpObsolete44) { return "HTTP_OBSOLETE44"; } else if (result == act::ResultHttpInterfaceFailed) { return "HTTP_INTERFACE_FAILED"; } else if (result == act::ResultHttpObsolete46) { return "HTTP_OBSOLETE46"; } else if (result == act::ResultHttpTooManyRedirects) { return "HTTP_TOO_MANY_REDIRECTS"; } else if (result == act::ResultHttpUnknownOption) { return "HTTP_UNKNOWN_OPTION"; } else if (result == act::ResultHttpTelnetOptionSyntax) { return "HTTP_TELNET_OPTION_SYNTAX"; } else if (result == act::ResultHttpObsolete50) { return "HTTP_OBSOLETE50"; } else if (result == act::ResultHttpPeerFailedVerification) { return "HTTP_PEER_FAILED_VERIFICATION"; } else if (result == act::ResultHttpGotNothing) { return "HTTP_GOT_NOTHING"; } else if (result == act::ResultHttpSslEngineNotfound) { return "HTTP_SSL_ENGINE_NOTFOUND"; } else if (result == act::ResultHttpSslEngineSetfailed) { return "HTTP_SSL_ENGINE_SETFAILED"; } else if (result == act::ResultHttpSendError) { return "HTTP_SEND_ERROR"; } else if (result == act::ResultHttpRecvError) { return "HTTP_RECV_ERROR"; } else if (result == act::ResultHttpObsolete57) { return "HTTP_OBSOLETE57"; } else if (result == act::ResultHttpSslCertproblem) { return "HTTP_SSL_CERTPROBLEM"; } else if (result == act::ResultHttpSslCipher) { return "HTTP_SSL_CIPHER"; } else if (result == act::ResultHttpSslCacert) { return "HTTP_SSL_CACERT"; } else if (result == act::ResultHttpBadContentEncoding) { return "HTTP_BAD_CONTENT_ENCODING"; } else if (result == act::ResultHttpLdapInvalidURL) { return "HTTP_LDAP_INVALID_URL"; } else if (result == act::ResultHttpFilesizeExceeded) { return "HTTP_FILESIZE_EXCEEDED"; } else if (result == act::ResultHttpUseSslFailed) { return "HTTP_USE_SSL_FAILED"; } else if (result == act::ResultHttpSendFailRewind) { return "HTTP_SEND_FAIL_REWIND"; } else if (result == act::ResultHttpSslEngineInitfailed) { return "HTTP_SSL_ENGINE_INITFAILED"; } else if (result == act::ResultHttpLoginDenied) { return "HTTP_LOGIN_DENIED"; } else if (result == act::ResultHttpTftpNotfound) { return "HTTP_TFTP_NOTFOUND"; } else if (result == act::ResultHttpTftpPerm) { return "HTTP_TFTP_PERM"; } else if (result == act::ResultHttpRemoteDiskFull) { return "HTTP_REMOTE_DISK_FULL"; } else if (result == act::ResultHttpTftpIllegal) { return "HTTP_TFTP_ILLEGAL"; } else if (result == act::ResultHttpTftpUnknownid) { return "HTTP_TFTP_UNKNOWNID"; } else if (result == act::ResultHttpRemoteFileExists) { return "HTTP_REMOTE_FILE_EXISTS"; } else if (result == act::ResultHttpTftpNosuchuser) { return "HTTP_TFTP_NOSUCHUSER"; } else if (result == act::ResultHttpConvFailed) { return "HTTP_CONV_FAILED"; } else if (result == act::ResultHttpConvReqd) { return "HTTP_CONV_REQD"; } else if (result == act::ResultHttpSslCacertBadfile) { return "HTTP_SSL_CACERT_BADFILE"; } else if (result == act::ResultHttpRemoteFileNotFound) { return "HTTP_REMOTE_FILE_NOT_FOUND"; } else if (result == act::ResultHttpSsh) { return "HTTP_SSH"; } else if (result == act::ResultHttpSslShutdownFailed) { return "HTTP_SSL_SHUTDOWN_FAILED"; } else if (result == act::ResultHttpAgain) { return "HTTP_AGAIN"; } else if (result == act::ResultHttpSslCrlBadfile) { return "HTTP_SSL_CRL_BADFILE"; } else if (result == act::ResultHttpSslIssuerError) { return "HTTP_SSL_ISSUER_ERROR"; } else if (result == act::ResultHttpFtpPretFailed) { return "HTTP_FTP_PRET_FAILED"; } else if (result == act::ResultHttpRtspCseqError) { return "HTTP_RTSP_CSEQ_ERROR"; } else if (result == act::ResultHttpRtspSessionError) { return "HTTP_RTSP_SESSION_ERROR"; } else if (result == act::ResultHttpFtpBadFileList) { return "HTTP_FTP_BAD_FILE_LIST"; } else if (result == act::ResultHttpChunkFailed) { return "HTTP_CHUNK_FAILED"; } else if (result == act::ResultHttpNsslNoCtx) { return "HTTP_NSSL_NO_CTX"; } if (result == act::ResultSoError) { return "SO_ERROR"; } else if (result == act::ResultSoSelectError) { return "SO_SELECT_ERROR"; } else if (result == act::ResultRequestError) { return "REQUEST_ERROR"; } else if (result == act::ResultBadFormatParameter) { return "BAD_FORMAT_PARAMETER"; } else if (result == act::ResultBadFormatRequest) { return "BAD_FORMAT_REQUEST"; } else if (result == act::ResultRequestParameterMissing) { return "REQUEST_PARAMETER_MISSING"; } else if (result == act::ResultWrongHttpMethod) { return "WRONG_HTTP_METHOD"; } else if (result == act::ResultResponseError) { return "RESPONSE_ERROR"; } else if (result == act::ResultBadFormatResponse) { return "BAD_FORMAT_RESPONSE"; } else if (result == act::ResultResponseItemMissing) { return "RESPONSE_ITEM_MISSING"; } else if (result == act::ResultResponseTooLarge) { return "RESPONSE_TOO_LARGE"; } else if (result == act::ResultNotModified) { return "NOT_MODIFIED"; } else if (result == act::ResultInvalidCommonParameter) { return "INVALID_COMMON_PARAMETER"; } else if (result == act::ResultInvalidPlatformID) { return "INVALID_PLATFORM_ID"; } else if (result == act::ResultUnauthorizedDevice) { return "UNAUTHORIZED_DEVICE"; } else if (result == act::ResultInvalidSerialID) { return "INVALID_SERIAL_ID"; } else if (result == act::ResultInvalidMacAddress) { return "INVALID_MAC_ADDRESS"; } else if (result == act::ResultInvalidRegion) { return "INVALID_REGION"; } else if (result == act::ResultInvalidCountry) { return "INVALID_COUNTRY"; } else if (result == act::ResultInvalidLanguage) { return "INVALID_LANGUAGE"; } else if (result == act::ResultUnauthorizedClient) { return "UNAUTHORIZED_CLIENT"; } else if (result == act::ResultDeviceIDEmpty) { return "DEVICE_ID_EMPTY"; } else if (result == act::ResultSerialIDEmpty) { return "SERIAL_ID_EMPTY"; } else if (result == act::ResultPlatformIDEmpty) { return "PLATFORM_ID_EMPTY"; } else if (result == act::ResultInvalidUniqueID) { return "INVALID_UNIQUE_ID"; } else if (result == act::ResultInvalidClientID) { return "INVALID_CLIENT_ID"; } else if (result == act::ResultInvalidClientKey) { return "INVALID_CLIENT_KEY"; } else if (result == act::ResultInvalidNexClientID) { return "INVALID_NEX_CLIENT_ID"; } else if (result == act::ResultInvalidGameServerID) { return "INVALID_GAME_SERVER_ID"; } else if (result == act::ResultGameServerIDEnvironmentNotFound) { return "GAME_SERVER_ID_ENVIRONMENT_NOT_FOUND"; } else if (result == act::ResultGameServerIDUniqueIDNotLinked) { return "GAME_SERVER_ID_UNIQUE_ID_NOT_LINKED"; } else if (result == act::ResultClientIDUniqueIDNotLinked) { return "CLIENT_ID_UNIQUE_ID_NOT_LINKED"; } else if (result == act::ResultDeviceMismatch) { return "DEVICE_MISMATCH"; } else if (result == act::ResultCountryMismatch) { return "COUNTRY_MISMATCH"; } else if (result == act::ResultEulaNotAccepted) { return "EULA_NOT_ACCEPTED"; } else if (result == act::ResultUpdateRequired) { return "UPDATE_REQUIRED"; } else if (result == act::ResultSystemUpdateRequired) { return "SYSTEM_UPDATE_REQUIRED"; } else if (result == act::ResultApplicationUpdateRequired) { return "APPLICATION_UPDATE_REQUIRED"; } else if (result == act::ResultUnauthorizedRequest) { return "UNAUTHORIZED_REQUEST"; } else if (result == act::ResultRequestForbidden) { return "REQUEST_FORBIDDEN"; } else if (result == act::ResultResourceNotFound) { return "RESOURCE_NOT_FOUND"; } else if (result == act::ResultPidNotFound) { return "PID_NOT_FOUND"; } else if (result == act::ResultNexAccountNotFound) { return "NEX_ACCOUNT_NOT_FOUND"; } else if (result == act::ResultGenerateTokenFailure) { return "GENERATE_TOKEN_FAILURE"; } else if (result == act::ResultRequestNotFound) { return "REQUEST_NOT_FOUND"; } else if (result == act::ResultMasterPinNotFound) { return "MASTER_PIN_NOT_FOUND"; } else if (result == act::ResultMailTextNotFound) { return "MAIL_TEXT_NOT_FOUND"; } else if (result == act::ResultSendMailFailure) { return "SEND_MAIL_FAILURE"; } else if (result == act::ResultApprovalIDNotFound) { return "APPROVAL_ID_NOT_FOUND"; } else if (result == act::ResultInvalidEulaParameter) { return "INVALID_EULA_PARAMETER"; } else if (result == act::ResultInvalidEulaCountry) { return "INVALID_EULA_COUNTRY"; } else if (result == act::ResultInvalidEulaCountryAndVersion) { return "INVALID_EULA_COUNTRY_AND_VERSION"; } else if (result == act::ResultEulaNotFound) { return "EULA_NOT_FOUND"; } else if (result == act::ResultPhraseNotAcceptable) { return "PHRASE_NOT_ACCEPTABLE"; } else if (result == act::ResultAccountIDAlreadyExists) { return "ACCOUNT_ID_ALREADY_EXISTS"; } else if (result == act::ResultAccountIDNotAcceptable) { return "ACCOUNT_ID_NOT_ACCEPTABLE"; } else if (result == act::ResultAccountPasswordNotAcceptable) { return "ACCOUNT_PASSWORD_NOT_ACCEPTABLE"; } else if (result == act::ResultMiiNameNotAcceptable) { return "MII_NAME_NOT_ACCEPTABLE"; } else if (result == act::ResultMailAddressNotAcceptable) { return "MAIL_ADDRESS_NOT_ACCEPTABLE"; } else if (result == act::ResultAccountIDFormatInvalid) { return "ACCOUNT_ID_FORMAT_INVALID"; } else if (result == act::ResultAccountIDPasswordSame) { return "ACCOUNT_ID_PASSWORD_SAME"; } else if (result == act::ResultAccountIDCharNotAcceptable) { return "ACCOUNT_ID_CHAR_NOT_ACCEPTABLE"; } else if (result == act::ResultAccountIDSuccessiveSymbol) { return "ACCOUNT_ID_SUCCESSIVE_SYMBOL"; } else if (result == act::ResultAccountIDSymbolPositionNotAcceptable) { return "ACCOUNT_ID_SYMBOL_POSITION_NOT_ACCEPTABLE"; } else if (result == act::ResultAccountIDTooManyDigit) { return "ACCOUNT_ID_TOO_MANY_DIGIT"; } else if (result == act::ResultAccountPasswordCharNotAcceptable) { return "ACCOUNT_PASSWORD_CHAR_NOT_ACCEPTABLE"; } else if (result == act::ResultAccountPasswordTooFewCharTypes) { return "ACCOUNT_PASSWORD_TOO_FEW_CHAR_TYPES"; } else if (result == act::ResultAccountPasswordSuccessiveSameChar) { return "ACCOUNT_PASSWORD_SUCCESSIVE_SAME_CHAR"; } else if (result == act::ResultMailAddressDomainNameNotAcceptable) { return "MAIL_ADDRESS_DOMAIN_NAME_NOT_ACCEPTABLE"; } else if (result == act::ResultMailAddressDomainNameNotResolved) { return "MAIL_ADDRESS_DOMAIN_NAME_NOT_RESOLVED"; } else if (result == act::ResultReachedAssociationLimit) { return "REACHED_ASSOCIATION_LIMIT"; } else if (result == act::ResultReachedRegistrationLimit) { return "REACHED_REGISTRATION_LIMIT"; } else if (result == act::ResultCoppaNotAccepted) { return "COPPA_NOT_ACCEPTED"; } else if (result == act::ResultParentalControlsRequired) { return "PARENTAL_CONTROLS_REQUIRED"; } else if (result == act::ResultMiiNotRegistered) { return "MII_NOT_REGISTERED"; } else if (result == act::ResultDeviceEulaCountryMismatch) { return "DEVICE_EULA_COUNTRY_MISMATCH"; } else if (result == act::ResultPendingMigration) { return "PENDING_MIGRATION"; } else if (result == act::ResultWrongUserInput) { return "WRONG_USER_INPUT"; } else if (result == act::ResultWrongAccountPassword) { return "WRONG_ACCOUNT_PASSWORD"; } else if (result == act::ResultWrongMailAddress) { return "WRONG_MAIL_ADDRESS"; } else if (result == act::ResultWrongAccountPasswordOrMailAddress) { return "WRONG_ACCOUNT_PASSWORD_OR_MAIL_ADDRESS"; } else if (result == act::ResultWrongConfirmationCode) { return "WRONG_CONFIRMATION_CODE"; } else if (result == act::ResultWrongBirthDateOrMailAddress) { return "WRONG_BIRTH_DATE_OR_MAIL_ADDRESS"; } else if (result == act::ResultWrongAccountMail) { return "WRONG_ACCOUNT_MAIL"; } else if (result == act::ResultAccountAlreadyDeleted) { return "ACCOUNT_ALREADY_DELETED"; } else if (result == act::ResultAccountIDChanged) { return "ACCOUNT_ID_CHANGED"; } else if (result == act::ResultAuthenticationLocked) { return "AUTHENTICATION_LOCKED"; } else if (result == act::ResultDeviceInactive) { return "DEVICE_INACTIVE"; } else if (result == act::ResultCoppaAgreementCanceled) { return "COPPA_AGREEMENT_CANCELED"; } else if (result == act::ResultDomainAccountAlreadyExists) { return "DOMAIN_ACCOUNT_ALREADY_EXISTS"; } else if (result == act::ResultAccountTokenExpired) { return "ACCOUNT_TOKEN_EXPIRED"; } else if (result == act::ResultInvalidAccountToken) { return "INVALID_ACCOUNT_TOKEN"; } else if (result == act::ResultAuthenticationRequired) { return "AUTHENTICATION_REQUIRED"; } else if (result == act::ResultConfirmationCodeExpired) { return "CONFIRMATION_CODE_EXPIRED"; } else if (result == act::ResultMailAddressNotValidated) { return "MAIL_ADDRESS_NOT_VALIDATED"; } else if (result == act::ResultExcessiveMailSendRequest) { return "EXCESSIVE_MAIL_SEND_REQUEST"; } else if (result == act::ResultCreditCardError) { return "CREDIT_CARD_ERROR"; } else if (result == act::ResultCreditCardGeneralFailure) { return "CREDIT_CARD_GENERAL_FAILURE"; } else if (result == act::ResultCreditCardDeclined) { return "CREDIT_CARD_DECLINED"; } else if (result == act::ResultCreditCardBlacklisted) { return "CREDIT_CARD_BLACKLISTED"; } else if (result == act::ResultInvalidCreditCardNumber) { return "INVALID_CREDIT_CARD_NUMBER"; } else if (result == act::ResultInvalidCreditCardDate) { return "INVALID_CREDIT_CARD_DATE"; } else if (result == act::ResultInvalidCreditCardPin) { return "INVALID_CREDIT_CARD_PIN"; } else if (result == act::ResultInvalidPostalCode) { return "INVALID_POSTAL_CODE"; } else if (result == act::ResultInvalidLocation) { return "INVALID_LOCATION"; } else if (result == act::ResultCreditCardDateExpired) { return "CREDIT_CARD_DATE_EXPIRED"; } else if (result == act::ResultCreditCardNumberWrong) { return "CREDIT_CARD_NUMBER_WRONG"; } else if (result == act::ResultCreditCardPinWrong) { return "CREDIT_CARD_PIN_WRONG"; } if (result == act::ResultBanned) { return "BANNED"; } else if (result == act::ResultBannedAccount) { return "BANNED_ACCOUNT"; } else if (result == act::ResultBannedAccountAll) { return "BANNED_ACCOUNT_ALL"; } else if (result == act::ResultBannedAccountInApplication) { return "BANNED_ACCOUNT_IN_APPLICATION"; } else if (result == act::ResultBannedAccountInNexService) { return "BANNED_ACCOUNT_IN_NEX_SERVICE"; } else if (result == act::ResultBannedAccountInIndependentService) { return "BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE"; } else if (result == act::ResultBannedDevice) { return "BANNED_DEVICE"; } else if (result == act::ResultBannedDeviceAll) { return "BANNED_DEVICE_ALL"; } else if (result == act::ResultBannedDeviceInApplication) { return "BANNED_DEVICE_IN_APPLICATION"; } else if (result == act::ResultBannedDeviceInNexService) { return "BANNED_DEVICE_IN_NEX_SERVICE"; } else if (result == act::ResultBannedDeviceInIndependentService) { return "BANNED_DEVICE_IN_INDEPENDENT_SERVICE"; } else if (result == act::ResultBannedAccountTemporarily) { return "BANNED_ACCOUNT_TEMPORARILY"; } else if (result == act::ResultBannedAccountAllTemporarily) { return "BANNED_ACCOUNT_ALL_TEMPORARILY"; } else if (result == act::ResultBannedAccountInApplicationTemporarily) { return "BANNED_ACCOUNT_IN_APPLICATION_TEMPORARILY"; } else if (result == act::ResultBannedAccountInNexServiceTemporarily) { return "BANNED_ACCOUNT_IN_NEX_SERVICE_TEMPORARILY"; } else if (result == act::ResultBannedAccountInIndependentServiceTemporarily) { return "BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE_TEMPORARILY"; } else if (result == act::ResultBannedDeviceTemporarily) { return "BANNED_DEVICE_TEMPORARILY"; } else if (result == act::ResultBannedDeviceAllTemporarily) { return "BANNED_DEVICE_ALL_TEMPORARILY"; } else if (result == act::ResultBannedDeviceInApplicationTemporarily) { return "BANNED_DEVICE_IN_APPLICATION_TEMPORARILY"; } else if (result == act::ResultBannedDeviceInNexServiceTemporarily) { return "BANNED_DEVICE_IN_NEX_SERVICE_TEMPORARILY"; } else if (result == act::ResultBannedDeviceInIndependentServiceTemporarily) { return "BANNED_DEVICE_IN_INDEPENDENT_SERVICE_TEMPORARILY"; } if (result == act::ResultServiceNotProvided) { return "SERVICE_NOT_PROVIDED"; } else if (result == act::ResultUnderMaintenance) { return "UNDER_MAINTENANCE"; } else if (result == act::ResultServiceClosed) { return "SERVICE_CLOSED"; } else if (result == act::ResultNintendoNetworkClosed) { return "NINTENDO_NETWORK_CLOSED"; } else if (result == act::ResultNotProvidedCountry) { return "NOT_PROVIDED_COUNTRY"; } else if (result == act::ResultRestrictionError) { return "RESTRICTION_ERROR"; } else if (result == act::ResultRestrictedByAge) { return "RESTRICTED_BY_AGE"; } else if (result == act::ResultRestrictedByParentalControls) { return "RESTRICTED_BY_PARENTAL_CONTROLS"; } else if (result == act::ResultOnGameInternetCommunicationRestricted) { return "ON_GAME_INTERNET_COMMUNICATION_RESTRICTED"; } else if (result == act::ResultInternalServerError) { return "INTERNAL_SERVER_ERROR"; } else if (result == act::ResultUnknownServerError) { return "UNKNOWN_SERVER_ERROR"; } else if (result == act::ResultUnauthenticatedAfterSalvage) { return "UNAUTHENTICATED_AFTER_SALVAGE"; } else if (result == act::ResultAuthenticationFailureUnknown) { return "AUTHENTICATION_FAILURE_UNKNOWN"; } return "<unknown>"; } const char * GetDescriptionString(nn::Result result) { if (result.isLegacyRepresentation()) { return GetLegacyDescriptionString(result); } switch (result.module()) { case nn::Result::MODULE_NN_ACP: return GetAcpDescriptionString(result); case nn::Result::MODULE_NN_ACT: return GetActDescriptionString(result); } return "<unknown>"; } } // namespace nn::dbg ================================================ FILE: src/libdecaf/src/nn/dbg/nn_dbg_result_string.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::dbg { const char * GetDescriptionString(nn::Result result); const char * GetLevelString(nn::Result result); const char * GetModuleString(nn::Result result); const char * GetSummaryString(nn::Result result); } // namespace nn::dbg ================================================ FILE: src/libdecaf/src/nn/ffl/nn_ffl_miidata.h ================================================ #pragma once #include <array> #include <cstdint> #include <libcpu/be2_struct.h> namespace nn::ffl { #pragma pack(push, 1) enum FFLCreateIDFlags : uint8_t { IsWiiUMii = 0x1 | 0x4, IsTemporaryMii = 0x2, IsNormalMii = 0x8, }; struct FFLCreateID { uint32_t flags : 4; //! Seconds since Jan 1st 2010 uint32_t timestamp : 28; uint8_t deviceHash[6]; }; CHECK_OFFSET(FFLCreateID, 4, deviceHash); CHECK_SIZE(FFLCreateID, 10); // This structure is intentionally little-endian as the data // is stored in a cross-platform manner for multiple devices. struct FFLiMiiDataCore { // 0x00 uint8_t birth_platform : 4; uint8_t unk_0x00_b4 : 4; // 0x01 uint8_t unk_0x01_b0 : 4; uint8_t unk_0x01_b4 : 4; // 0x02 uint8_t font_region : 4; uint8_t region_move : 2; uint8_t unk_0x02_b6 : 1; uint8_t copyable : 1; // 0x03 uint8_t mii_version; // 0x4 uint64_t author_id; // 0xC FFLCreateID mii_id; // 0x16 uint16_t unk_0x16; // 0x18 uint16_t unk_0x18_b0 : 1; uint16_t unk_0x18_b1 : 1; uint16_t color : 4; uint16_t birth_day : 5; uint16_t birth_month : 4; uint16_t gender : 1; // 0x1A char16_t mii_name[10]; // 0x2E uint8_t size; // 0x2F uint8_t fatness; // 0x30 uint8_t blush_type : 4; uint8_t face_style : 4; // 0x31 uint8_t face_color : 3; uint8_t face_type : 4; uint8_t local_only : 1; // 0x32 uint8_t hair_mirrored : 5; uint8_t hair_color : 3; // 0x33 uint8_t hair_type; // 0x34 uint32_t eye_thickness : 3; uint32_t eye_scale : 4; uint32_t eye_color : 3; uint32_t eye_type : 6; uint32_t eye_height : 7; uint32_t eye_distance : 4; uint32_t eye_rotation : 5; // 0x38 uint32_t eyebrow_thickness : 4; uint32_t eyebrow_scale : 4; uint32_t eyebrow_color : 3; uint32_t eyebrow_type : 5; uint32_t eyebrow_height : 7; uint32_t eyebrow_distance : 4; uint32_t eyebrow_rotation : 5; // 0x3c uint32_t nose_height : 7; uint32_t nose_scale : 4; uint32_t nose_type : 5; uint32_t mouth_thickness : 3; uint32_t mouth_scale : 4; uint32_t mouth_color : 3; uint32_t mouth_type : 6; // 0x40 uint32_t unk_0x40 : 8; uint32_t mustache_type : 3; uint32_t mouth_height : 5; uint32_t mustache_height : 6; uint32_t mustache_scale : 4; uint32_t beard_color : 3; uint32_t beard_type : 3; // 0x44 uint16_t glass_height : 5; uint16_t glass_scale : 4; uint16_t glass_color : 3; uint16_t glass_type : 4; // 0x46 uint16_t unk_0x46_b0 : 1; uint16_t mole_ypos : 5; uint16_t mole_xpos : 5; uint16_t mole_scale : 4; uint16_t mole_enabled : 1; }; CHECK_OFFSET(FFLiMiiDataCore, 0x03, mii_version); CHECK_OFFSET(FFLiMiiDataCore, 0x04, author_id); CHECK_OFFSET(FFLiMiiDataCore, 0x0C, mii_id); CHECK_OFFSET(FFLiMiiDataCore, 0x16, unk_0x16); CHECK_OFFSET(FFLiMiiDataCore, 0x1A, mii_name); CHECK_OFFSET(FFLiMiiDataCore, 0x2E, size); CHECK_OFFSET(FFLiMiiDataCore, 0x2F, fatness); CHECK_OFFSET(FFLiMiiDataCore, 0x33, hair_type); CHECK_SIZE(FFLiMiiDataCore, 0x48); struct FFLiMiiDataOfficial : FFLiMiiDataCore { char16_t creator_name[10]; }; CHECK_OFFSET(FFLiMiiDataOfficial, 0x48, creator_name); CHECK_SIZE(FFLiMiiDataOfficial, 0x5C); struct FFLStoreData : FFLiMiiDataOfficial { uint16_t unk_0x5C; be2_val<uint16_t> checksum; }; CHECK_OFFSET(FFLStoreData, 0x5C, unk_0x5C); CHECK_OFFSET(FFLStoreData, 0x5E, checksum); CHECK_SIZE(FFLStoreData, 0x60); #pragma pack(pop) } // namespace nn::ffl ================================================ FILE: src/libdecaf/src/nn/ios/nn_ios_error.cpp ================================================ #include "nn_ios_error.h" namespace nn::ios { nn::Result convertError(::ios::Error error) { switch (error) { case ::ios::Error::OK: return ResultOK; case ::ios::Error::Access: return ResultAccess; case ::ios::Error::Exists: return ResultExists; case ::ios::Error::Intr: return ResultIntr; case ::ios::Error::Invalid: return ResultInvalid; case ::ios::Error::Max: return ResultMax; case ::ios::Error::NoExists: return ResultNoExists; case ::ios::Error::QEmpty: return ResultQEmpty; case ::ios::Error::QFull: return ResultQFull; case ::ios::Error::Unknown: return ResultUnknown; case ::ios::Error::NotReady: return ResultNotReady; case ::ios::Error::InvalidObjType: return ResultInvalidObjType; case ::ios::Error::InvalidVersion: return ResultInvalidVersion; case ::ios::Error::InvalidSigner: return ResultInvalidSigner; case ::ios::Error::FailCheckValue: return ResultFailCheckValue; case ::ios::Error::FailInternal: return ResultFailInternal; case ::ios::Error::FailAlloc: return ResultFailAlloc; case ::ios::Error::InvalidSize: return ResultInvalidSize; case ::ios::Error::NoLink: return ResultNoLink; case ::ios::Error::ANFailed: return ResultANFailed; case ::ios::Error::MaxSemCount: return ResultMaxSemCount; case ::ios::Error::SemUnavailable: return ResultSemUnavailable; case ::ios::Error::InvalidHandle: return ResultInvalidHandle; case ::ios::Error::InvalidArg: return ResultInvalidArg; case ::ios::Error::NoResource: return ResultNoResource; case ::ios::Error::Busy: return ResultBusy; case ::ios::Error::Timeout: return ResultTimeout; case ::ios::Error::Alignment: return ResultAlignment; case ::ios::Error::BSP: return ResultBSP; case ::ios::Error::DataPending: return ResultDataPending; case ::ios::Error::Expired: return ResultExpired; case ::ios::Error::NoReadAccess: return ResultNoReadAccess; case ::ios::Error::NoWriteAccess: return ResultNoWriteAccess; case ::ios::Error::NoReadWriteAccess: return ResultNoReadWriteAccess; case ::ios::Error::ClientTxnLimit: return ResultClientTxnLimit; case ::ios::Error::StaleHandle: return ResultStaleHandle; default: return ResultUnknownValue; } } } // namespace nn::ios ================================================ FILE: src/libdecaf/src/nn/ios/nn_ios_error.h ================================================ #pragma once #include "nn/nn_result.h" #include "ios/ios_enum.h" namespace nn::ios { static constexpr Result ResultOK { Result::MODULE_NN_IOS, Result::LEVEL_SUCCESS, 0 }; static constexpr const auto ResultAccess = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x100, 0x180>(); static constexpr const auto ResultExists = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x180, 0x200>(); static constexpr const auto ResultIntr = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x200, 0x280>(); static constexpr const auto ResultInvalid = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x280, 0x300>(); static constexpr const auto ResultMax = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x300, 0x380>(); static constexpr const auto ResultNoExists = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x380, 0x400>(); static constexpr const auto ResultQEmpty = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x400, 0x480>(); static constexpr const auto ResultQFull = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x480, 0x500>(); static constexpr const auto ResultUnknown = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x500, 0x580>(); static constexpr const auto ResultNotReady = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x580, 0x600>(); static constexpr const auto ResultInvalidObjType = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x600, 0x680>(); static constexpr const auto ResultInvalidVersion = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x680, 0x700>(); static constexpr const auto ResultInvalidSigner = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x700, 0x780>(); static constexpr const auto ResultFailCheckValue = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x780, 0x800>(); static constexpr const auto ResultFailInternal = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x800, 0x880>(); static constexpr const auto ResultFailAlloc = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x880, 0x900>(); static constexpr const auto ResultInvalidSize = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x900, 0x980>(); static constexpr const auto ResultNoLink = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x980, 0xA00>(); static constexpr const auto ResultANFailed = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xA00, 0xA80>(); static constexpr const auto ResultMaxSemCount = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xA80, 0xB00>(); static constexpr const auto ResultSemUnavailable = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xB00, 0xB80>(); static constexpr const auto ResultInvalidHandle = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xB80, 0xC00>(); static constexpr const auto ResultInvalidArg = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xC00, 0xC80>(); static constexpr const auto ResultNoResource = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xC80, 0xD00>(); static constexpr const auto ResultBusy = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xD00, 0xD80>(); static constexpr const auto ResultTimeout = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xD80, 0xE00>(); static constexpr const auto ResultAlignment = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xE00, 0xE80>(); static constexpr const auto ResultBSP = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xE80, 0xF00>(); static constexpr const auto ResultDataPending = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xF00, 0xF80>(); static constexpr const auto ResultExpired = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xF80, 0x1000>(); static constexpr const auto ResultNoReadAccess = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1000, 0x1080>(); static constexpr const auto ResultNoWriteAccess = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1080, 0x1100>(); static constexpr const auto ResultNoReadWriteAccess = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1100, 0x1180>(); static constexpr const auto ResultClientTxnLimit = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1180, 0x1200>(); static constexpr const auto ResultStaleHandle = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1200, 0x1280>(); static constexpr const auto ResultUnknownValue = ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x3180, 0x3180>(); Result convertError(::ios::Error error); } // namespace nn::ios ================================================ FILE: src/libdecaf/src/nn/ipc/nn_ipc_command.h ================================================ #pragma once #include <cstdint> #include <utility> namespace nn::ipc { using CommandId = int32_t; using ServiceId = int32_t; template<typename Service, int Id> struct Command { template<typename... ParameterTypes> struct Parameters { static constexpr ServiceId service = Service::id; static constexpr CommandId command = Id; using parameters = std::tuple<ParameterTypes...>; using response = std::tuple<>; // ::Response is optional template<typename... ResponseTypes> struct Response { static constexpr ServiceId service = Service::id; static constexpr CommandId command = Id; using parameters = std::tuple<ParameterTypes...>; using response = std::tuple<ResponseTypes...>; }; }; }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/nn/ipc/nn_ipc_format.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace nn::ipc { /* * IOCTLV vec order: * Vectors which are both input and output are treated as output vectors. * * numVecIn = 1 + 2 * number of user output vectors * numVecOut = 1 + 2 * number of user input vectors * * vec[0] = response buffer * ... user output vecs ... * vec[numVecIn] = request buffer * ... user input vecs ... */ struct RequestHeader { be2_val<uint32_t> unk0x00; be2_val<uint32_t> service; be2_val<uint32_t> unk0x08; be2_val<uint32_t> command; }; struct ResponseHeader { be2_val<uint32_t> result; }; struct ManagedBufferParameter { be2_val<uint32_t> alignedBufferSize; be2_val<uint8_t> alignedBufferIndex; be2_val<uint8_t> unalignedBufferIndex; be2_val<uint8_t> unalignedBeforeBufferSize; be2_val<uint8_t> unalignedAfterBufferSize; }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/nn/ipc/nn_ipc_managedbuffer.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace nn::ipc { struct ManagedBuffer { ManagedBuffer(bool input, bool output) : input(input), output(output) { } ManagedBuffer(virt_ptr<void> ptr, uint32_t size, bool input, bool output) : ptr(ptr), size(size), input(input), output(output) { } uint32_t totalSize() { return unalignedBeforeBufferSize + alignedBufferSize + unalignedAfterBufferSize; } void writeOutput(phys_ptr<void> data, uint32_t writeSize) { writeOutput(data.get(), writeSize); } void writeOutput(const void *data, size_t writeSize) { auto writePtr = reinterpret_cast<const uint8_t *>(data); if (unalignedBeforeBufferSize && writeSize) { auto count = std::min<size_t>(unalignedBeforeBufferSize, writeSize); std::memcpy(unalignedBeforeBuffer.get(), writePtr, count); writePtr += count; writeSize -= count; } if (alignedBufferSize && writeSize) { auto count = std::min<size_t>(alignedBufferSize, writeSize); std::memcpy(alignedBuffer.get(), writePtr, count); writePtr += count; writeSize -= count; } if (unalignedAfterBufferSize && writeSize) { auto count = std::min<size_t>(unalignedAfterBufferSize, writeSize); std::memcpy(unalignedAfterBuffer.get(), writePtr, count); writePtr += count; writeSize -= count; } } // For serialisation: virt_ptr<void> ptr = nullptr; uint32_t size = 0u; // For deserialisation: phys_ptr<void> unalignedBeforeBuffer = nullptr; uint32_t unalignedBeforeBufferSize = 0u; phys_ptr<void> alignedBuffer = nullptr; uint32_t alignedBufferSize = 0u; phys_ptr<void> unalignedAfterBuffer = nullptr; uint32_t unalignedAfterBufferSize = 0u; // For both: bool input; bool output; }; template<typename Type> struct InBuffer : ManagedBuffer { InBuffer() : ManagedBuffer(true, false) { } InBuffer(virt_ptr<Type> ptr) : ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type)), true, false) { } InBuffer(virt_ptr<Type> ptr, uint32_t count) : ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type) * count), true, false) { } }; template<> struct InBuffer<void> : ManagedBuffer { InBuffer() : ManagedBuffer(true, false) { } InBuffer(virt_ptr<void> ptr, uint32_t count) : ManagedBuffer(ptr, count, true, false) { } }; template<typename Type> struct InOutBuffer : ManagedBuffer { InOutBuffer() : ManagedBuffer(true, true) { } InOutBuffer(virt_ptr<Type> ptr) : ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type)), true, true) { } InOutBuffer(virt_ptr<Type> ptr, uint32_t count) : ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type) * count), true, true) { } }; template<> struct InOutBuffer<void> : ManagedBuffer { InOutBuffer() : ManagedBuffer(true, true) { } InOutBuffer(virt_ptr<void> ptr, uint32_t count) : ManagedBuffer(ptr, count, true, true) { } }; template<typename Type> struct OutBuffer : ManagedBuffer { OutBuffer() : ManagedBuffer(false, true) { } OutBuffer(virt_ptr<Type> ptr) : ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type)), false, true) { } OutBuffer(virt_ptr<Type> ptr, uint32_t count) : ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type) * count), false, true) { } }; template<> struct OutBuffer<void> : ManagedBuffer { OutBuffer() : ManagedBuffer(false, true) { } OutBuffer(virt_ptr<void> ptr, uint32_t count) : ManagedBuffer(ptr, count, false, true) { } }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/nn/ipc/nn_ipc_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::ipc { static constexpr Result ResultNotImplemented { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x3200 }; static constexpr Result ResultInvalidIpcID { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xfa00 }; static constexpr Result ResultInvalidSessionID { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xfa80 }; static constexpr Result ResultInvalidIpcHeader { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x6400 }; static constexpr Result ResultInvalidIpcHeaderFormat { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x9600 }; static constexpr Result ResultInvalidIpcHeaderSize { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xc800 }; static constexpr Result ResultInvalidModuleID { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xff00 }; static constexpr Result ResultInvalidMethodTag { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x10400 }; static constexpr Result ResultInvalidIpcBuffer { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x12c00 }; static constexpr Result ResultInvalidIpcBufferLength { Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x12c80 }; static constexpr Result ResultAccessDenied { Result::MODULE_NN_IPC, Result::LEVEL_STATUS, 0x15e00 }; static constexpr const auto ResultCapabilityFailed = ResultRange<Result::MODULE_NN_IPC, Result::LEVEL_FATAL, 0x15e80, 0x16d00>(); static constexpr Result ResultDevelopmentOnly { Result::MODULE_NN_IPC, Result::LEVEL_STATUS, 0x16d00 }; static constexpr Result ResultUnusualControlFlow { Result::MODULE_NN_IPC, Result::LEVEL_STATUS, 0x1f400 }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/nn/ipc/nn_ipc_service.h ================================================ #pragma once #include "nn_ipc_command.h" #include <libcpu/be2_struct.h> namespace nn::ipc { using ServiceId = int; using ServiceCommandHandler = void(*)(CommandId id); template<int Id> struct Service { static constexpr ServiceId id = Id; }; } // namespace nn::ipc ================================================ FILE: src/libdecaf/src/nn/nfp/nn_nfp_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::nfp { static constexpr Result ResultSuccess { Result::MODULE_NN_NFP, Result::LEVEL_SUCCESS, 128 }; static constexpr Result ResultInvalidPointer { Result::MODULE_NN_NFP, Result::LEVEL_USAGE, 14208 }; static constexpr Result ResultIsBusy { Result::MODULE_NN_NFP, Result::LEVEL_STATUS, 38528 }; static constexpr Result ResultInvalidState { Result::MODULE_NN_NFP, Result::LEVEL_STATUS, 256000 }; } // namespace nn::nfp ================================================ FILE: src/libdecaf/src/nn/nn_result.h ================================================ #pragma once #include <cstdint> namespace nn { struct Result { enum Level : int32_t { LEVEL_SUCCESS = 0, LEVEL_FATAL = -1, LEVEL_USAGE = -2, LEVEL_STATUS = -3, LEVEL_END = -7, }; enum Module : int32_t { MODULE_COMMON = 0, MODULE_NN_IPC = 1, MODULE_NN_BOSS = 2, MODULE_NN_ACP = 3, MODULE_NN_IOS = 4, MODULE_NN_NIM = 5, MODULE_NN_PDM = 6, MODULE_NN_ACT = 7, MODULE_NN_NGC = 8, MODULE_NN_ECA = 9, MODULE_NN_NUP = 10, MODULE_NN_NDM = 11, MODULE_NN_FP = 12, MODULE_NN_AC = 13, MODULE_NN_CONNTEST = 14, MODULE_NN_DRMAPP = 15, MODULE_NN_TELNET = 16, MODULE_NN_OLV = 17, MODULE_NN_VCTL = 18, MODULE_NN_NEIA = 19, MODULE_NN_SPM = 20, MODULE_NN_EMD = 21, MODULE_NN_EC = 22, MODULE_NN_CIA = 23, MODULE_NN_SL = 24, MODULE_NN_ECO = 25, MODULE_NN_TRIAL = 26, MODULE_NN_NFP = 27, MODULE_NN_TEST = 125, }; enum LegacyLevel { LEGACY_LEVEL_INFO = 1, LEGACY_LEVEL_RESET = -4, LEGACY_LEVEL_REINIT = -5, LEGACY_LEVEL_PERMANENT = -6, LEGACY_LEVEL_TEMPORARY = -7, }; enum LegacyCommonDescription : int32_t { LEGACY_DESCRIPTION_SUCCESS = 0, LEGACY_DESCRIPTION_TIMEOUT = -2, LEGACY_DESCRIPTION_OUT_OF_RANGE = -3, LEGACY_DESCRIPTION_ALREADY_EXISTS = -4, LEGACY_DESCRIPTION_CANCEL_REQUESTED = -5, LEGACY_DESCRIPTION_NOT_FOUND = -6, LEGACY_DESCRIPTION_ALREADY_INITIALIZED = -7, LEGACY_DESCRIPTION_NOT_INITIALIZED = -8, LEGACY_DESCRIPTION_INVALID_HANDLE = -9, LEGACY_DESCRIPTION_INVALID_POINTER = -10, LEGACY_DESCRIPTION_INVALID_ADDRESS = -11, LEGACY_DESCRIPTION_NOT_IMPLEMENTED = -12, LEGACY_DESCRIPTION_OUT_OF_MEMORY = -13, LEGACY_DESCRIPTION_MISALIGNED_SIZE = -14, LEGACY_DESCRIPTION_MISALIGNED_ADDRESS = -15, LEGACY_DESCRIPTION_BUSY = -16, LEGACY_DESCRIPTION_NO_DATA = -17, LEGACY_DESCRIPTION_INVALID_COMBINATION = -18, LEGACY_DESCRIPTION_INVALID_ENUM_VALUE = -19, LEGACY_DESCRIPTION_INVALID_SIZE = -20, LEGACY_DESCRIPTION_ALREADY_DONE = -21, LEGACY_DESCRIPTION_NOT_AUTHORIZED = -22, LEGACY_DESCRIPTION_TOO_LARGE = -23, LEGACY_DESCRIPTION_INVALID_SELECTION = -24, }; enum LegacyModule { LEGACY_MODULE_COMMON = 0, LEGACY_MODULE_NN_KERNEL = 1, LEGACY_MODULE_NN_UTIL = 2, LEGACY_MODULE_NN_FILE_SERVER = 3, LEGACY_MODULE_NN_LOADER_SERVER = 4, LEGACY_MODULE_NN_TCB = 5, LEGACY_MODULE_NN_OS = 6, LEGACY_MODULE_NN_DBG = 7, LEGACY_MODULE_NN_DMNT = 8, LEGACY_MODULE_NN_PDN = 9, LEGACY_MODULE_NN_GX = 0xA, LEGACY_MODULE_NN_I2C = 0xB, LEGACY_MODULE_NN_GPIO = 0xC, LEGACY_MODULE_NN_DD = 0xD, LEGACY_MODULE_NN_CODEC = 0xE, LEGACY_MODULE_NN_SPI = 0xF, LEGACY_MODULE_NN_PXI = 0x10, LEGACY_MODULE_NN_FS = 0x11, LEGACY_MODULE_NN_DI = 0x12, LEGACY_MODULE_NN_HID = 0x13, LEGACY_MODULE_NN_CAMERA = 0x14, LEGACY_MODULE_NN_PI = 0x15, LEGACY_MODULE_NN_PM = 0x16, LEGACY_MODULE_NN_PMLOW = 0x17, LEGACY_MODULE_NN_FSI = 0x18, LEGACY_MODULE_NN_SRV = 0x19, LEGACY_MODULE_NN_NDM = 0x1A, LEGACY_MODULE_NN_NWM = 0x1B, LEGACY_MODULE_NN_SOCKET = 0x1C, LEGACY_MODULE_NN_LDR = 0x1D, LEGACY_MODULE_NN_ACC = 0x1E, LEGACY_MODULE_NN_ROMFS = 0x1F, LEGACY_MODULE_NN_AM = 0x20, LEGACY_MODULE_NN_HIO = 0x21, LEGACY_MODULE_NN_UPDATER = 0x22, LEGACY_MODULE_NN_MIC = 0x23, LEGACY_MODULE_NN_FND = 0x24, LEGACY_MODULE_NN_MP = 0x25, LEGACY_MODULE_NN_MPWL = 0x26, LEGACY_MODULE_NN_AC = 0x27, LEGACY_MODULE_NN_HTTP = 0x28, LEGACY_MODULE_NN_DSP = 0x29, LEGACY_MODULE_NN_SND = 0x2A, LEGACY_MODULE_NN_DLP = 0x2B, LEGACY_MODULE_NN_HIOLOW = 0x2C, LEGACY_MODULE_NN_CSND = 0x2D, LEGACY_MODULE_NN_SSL = 0x2E, LEGACY_MODULE_NN_AMLOW = 0x2F, LEGACY_MODULE_NN_NEX = 0x30, LEGACY_MODULE_NN_FRIENDS = 0x31, LEGACY_MODULE_NN_RDT = 0x32, LEGACY_MODULE_NN_APPLET = 0x33, LEGACY_MODULE_NN_NIM = 0x34, LEGACY_MODULE_NN_PTM = 0x35, LEGACY_MODULE_NN_MIDI = 0x36, LEGACY_MODULE_NN_MC = 0x37, LEGACY_MODULE_NN_SWC = 0x38, LEGACY_MODULE_NN_FATFS = 0x39, LEGACY_MODULE_NN_NGC = 0x3A, LEGACY_MODULE_NN_CARD = 0x3B, LEGACY_MODULE_NN_CARDNOR = 0x3C, LEGACY_MODULE_NN_SDMC = 0x3D, LEGACY_MODULE_NN_BOSS = 0x3E, LEGACY_MODULE_NN_DBM = 0x3F, LEGACY_MODULE_NN_CFG = 0x40, LEGACY_MODULE_NN_PS = 0x41, LEGACY_MODULE_NN_CEC = 0x42, LEGACY_MODULE_NN_IR = 0x43, LEGACY_MODULE_NN_UDS = 0x44, LEGACY_MODULE_NN_PL = 0x45, LEGACY_MODULE_NN_CUP = 0x46, LEGACY_MODULE_NN_GYROSCOPE = 0x47, LEGACY_MODULE_NN_MCU = 0x48, LEGACY_MODULE_NN_NS = 0x49, LEGACY_MODULE_NN_NEWS = 0x4A, LEGACY_MODULE_NN_RO = 0x4B, LEGACY_MODULE_NN_GD = 0x4C, LEGACY_MODULE_NN_CARDSPI = 0x4D, LEGACY_MODULE_NN_EC = 0x4E, LEGACY_MODULE_NN_WEBBRS = 0x4F, LEGACY_MODULE_NN_TEST = 0x50, LEGACY_MODULE_NN_ENC = 0x51, LEGACY_MODULE_NN_PIA = 0x52, LEGACY_MODULE_APPLICATION = 0x1FE, }; enum LegacySummary { LEGACY_SUMMARY_SUCCESS = 0, LEGACY_SUMMARY_NOTHING_HAPPENED = 1, LEGACY_SUMMARY_WOULD_BLOCK = 2, LEGACY_SUMMARY_OUT_OF_RESOURCE = 3, LEGACY_SUMMARY_NOT_FOUND = 4, LEGACY_SUMMARY_INVALID_STATE = 5, LEGACY_SUMMARY_NOT_SUPPORTED = 6, LEGACY_SUMMARY_INVALID_ARGUMENT = 7, LEGACY_SUMMARY_WRONG_ARGUMENT = 8, LEGACY_SUMMARY_CANCELLED = 9, LEGACY_SUMMARY_STATUS_CHANGED = 10, LEGACY_SUMMARY_INTERNAL = 11, }; enum LegacySignature { LEGACY_SIGNATURE = 3, }; constexpr Result() noexcept : mCode(0) { } constexpr Result(uint32_t code_) noexcept : mCode(code_) { } constexpr Result(Module module, Level level, int32_t description) noexcept : mDescription(description), mModule(module), mLevel(level) { } constexpr explicit operator uint32_t() const noexcept { return mCode; } constexpr explicit operator bool() const noexcept { return ok(); } constexpr bool operator==(const Result &other) const noexcept { return module() == other.module() && description() == other.description(); } constexpr bool ok() const noexcept { return (mCode & 0x80000000) == 0; } constexpr bool failed() const noexcept { return (mCode & 0x80000000) != 0; } constexpr bool isLegacyRepresentation() const noexcept { return mLegacy.signature == LEGACY_SIGNATURE; } constexpr uint32_t code() const noexcept { return mCode; } constexpr int description() const noexcept { if (isLegacyRepresentation()) { return mLegacy.description; } return mDescription; } constexpr int level() const noexcept { if (isLegacyRepresentation()) { return mLegacy.level; } return mLevel; } constexpr int module() const noexcept { if (isLegacyRepresentation()) { return mLegacy.module; } return mModule; } constexpr int legacySummary() const noexcept { return mLegacy.summary; } private: union { uint32_t mCode; struct { int32_t mDescription : 20; int32_t mModule : 9; int32_t mLevel : 3; }; struct { int32_t description : 10; int32_t summary : 4; int32_t level : 4; int32_t : 2; int32_t module : 7; int32_t signature : 2; int32_t : 3; } mLegacy; }; }; static constexpr Result ResultSuccess { 0 }; template<Result::Module Module_, Result::Level Level_, int DescStart, int DescEnd> struct ResultRange : Result { constexpr ResultRange() : Result(Module_, Level_, DescStart) { } constexpr bool operator==(const Result &other) noexcept { return other.module() == Module_ && other.description() >= DescStart && other.description() < DescEnd; } constexpr friend bool operator==(const Result &other, const ResultRange &) noexcept { return other.module() == Module_ && other.description() >= DescStart && other.description() < DescEnd; } }; } // namespace nn ================================================ FILE: src/libdecaf/src/nn/olv/nn_olv_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::olv { static constexpr Result ResultSuccess { nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_SUCCESS, 0x80 }; static constexpr Result ResultInvalidSize { nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6580 }; static constexpr Result ResultInvalidPointer { nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6600 }; static constexpr Result ResultNotOnline { nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6780 }; static constexpr Result ResultNoData { nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6800 }; } // namespace nn::olv ================================================ FILE: src/libdecaf/src/nn/pdm/nn_pdm_cosservice.h ================================================ #pragma once #include "nn/ipc/nn_ipc_service.h" #include <cstdint> namespace nn::pdm::services { struct CosService : ipc::Service<0> { using GetPlayDiaryMaxLength = ipc::Command<CosService, 0> ::Parameters<> ::Response<uint32_t>; using GetPlayStatsMaxLength = ipc::Command<CosService, 256> ::Parameters<> ::Response<uint32_t>; }; } // namespace nn::pdm::services ================================================ FILE: src/libdecaf/src/nn/pdm/nn_pdm_result.h ================================================ #pragma once #include "nn/nn_result.h" namespace nn::pdm { static constexpr Result ResultSuccess { nn::Result::MODULE_NN_PDM, nn::Result::LEVEL_SUCCESS, 128 }; } // namespace nn::pdm ================================================ FILE: src/libdecaf/src/nn/spm/nn_spm_extendedstorageservice.h ================================================ #pragma once #include "nn/ipc/nn_ipc_command.h" #include "nn/ipc/nn_ipc_service.h" #include <cstdint> namespace nn::spm::services { // Served from /dev/acp_main struct ExtendedStorageService : ipc::Service<303> { using SetAutoFatal = ipc::Command<ExtendedStorageService, 6> ::Parameters<uint8_t> ::Response<>; }; } // namespace nn::spm::services ================================================ FILE: src/libdecaf/src/traceiter.h ================================================ #pragma once #include "trace.h" class TraceIterator { public: TraceIterator(const Tracer* tracer) : mTracer(tracer), mCurTrace(nullptr), mPrevTrace(nullptr) { } const Trace * next() { } protected: const Tracer *mTracer; const Trace *mPrevTrace; const Trace *mCurTrace; }; ================================================ FILE: src/libdecaf/src/vfs/vfs_device.h ================================================ #pragma once #include "vfs_directoryiterator.h" #include "vfs_error.h" #include "vfs_filehandle.h" #include "vfs_result.h" #include "vfs_path.h" #include "vfs_permissions.h" #include "vfs_status.h" #include <memory> namespace vfs { class Device { public: enum Type { Host, Virtual, Overlay, Link, }; Device(Type type) : mType(type) { } virtual ~Device() = default; virtual Result<std::shared_ptr<Device>> getLinkDevice(const User &user, const Path &path) = 0; virtual Error makeFolder(const User &user, const Path &path) = 0; virtual Error makeFolders(const User &user, const Path &path) = 0; virtual Error mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) = 0; virtual Error mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) = 0; virtual Error unmountDevice(const User &user, const Path &path) = 0; virtual Error unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) = 0; virtual Result<DirectoryIterator> openDirectory(const User &user, const Path &path) = 0; virtual Result<std::unique_ptr<FileHandle>> openFile(const User &user, const Path &path, FileHandle::Mode mode) = 0; virtual Error remove(const User &user, const Path &path) = 0; virtual Error rename(const User &user, const Path &src, const Path &dst) = 0; virtual Error setGroup(const User &user, const Path &path, GroupId group) = 0; virtual Error setOwner(const User &user, const Path &path, OwnerId owner) = 0; virtual Error setPermissions(const User &user, const Path &path, Permissions mode) = 0; virtual Result<Status> status(const User &user, const Path &path) = 0; Type type() { return mType; } private: Type mType; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_directoryiterator.h ================================================ #pragma once #include "vfs_error.h" #include "vfs_result.h" #include "vfs_status.h" #include <memory> namespace vfs { struct DirectoryIteratorImpl { virtual ~DirectoryIteratorImpl() = default; virtual Result<Status> readEntry() = 0; virtual Error rewind() = 0; }; class DirectoryIterator { public: DirectoryIterator() = default; DirectoryIterator(std::shared_ptr<DirectoryIteratorImpl> impl) : mImpl(impl) { } Result<Status> readEntry() { return mImpl->readEntry(); } Error rewind() { return mImpl->rewind(); } private: std::shared_ptr<DirectoryIteratorImpl> mImpl; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_error.h ================================================ #pragma once #include <fmt/format.h> namespace vfs { enum class Error { Success = 0, AlreadyExists, EndOfDirectory, EndOfFile, ExecutePermission, GenericError, InvalidPath, InvalidSeekDirection, InvalidSeekPosition, InvalidTruncatePosition, NotDirectory, NotFile, NotFound, NotMountDevice, NotOpen, OperationNotSupported, ReadOnly, Permission, }; } // namespace vfs template <> struct fmt::formatter<vfs::Error> : fmt::formatter<std::string_view> { template <typename FormatContext> auto format(vfs::Error value, FormatContext& ctx) { std::string_view name = "unknown"; switch (value) { case vfs::Error::Success: name = "Success"; break; case vfs::Error::AlreadyExists: name = "AlreadyExists"; break; case vfs::Error::EndOfDirectory: name = "EndOfDirectory"; break; case vfs::Error::EndOfFile: name = "EndOfFile"; break; case vfs::Error::ExecutePermission: name = "ExecutePermission"; break; case vfs::Error::GenericError: name = "GenericError"; break; case vfs::Error::InvalidPath: name = "InvalidPath"; break; case vfs::Error::InvalidSeekDirection: name = "InvalidSeekDirection"; break; case vfs::Error::InvalidSeekPosition: name = "InvalidSeekPosition"; break; case vfs::Error::InvalidTruncatePosition: name = "InvalidTruncatePosition"; break; case vfs::Error::NotDirectory: name = "NotDirectory"; break; case vfs::Error::NotFile: name = "NotFile"; break; case vfs::Error::NotFound: name = "NotFound"; break; case vfs::Error::NotMountDevice: name = "NotMountDevice"; break; case vfs::Error::NotOpen: name = "NotOpen"; break; case vfs::Error::OperationNotSupported: name = "OperationNotSupported"; break; case vfs::Error::ReadOnly: name = "ReadOnly"; break; case vfs::Error::Permission: name = "Permission"; break; } return fmt::formatter<string_view>::format(name, ctx); } }; ================================================ FILE: src/libdecaf/src/vfs/vfs_filehandle.h ================================================ #pragma once #include "vfs_result.h" #include <cstdint> namespace vfs { class FileHandle { public: enum Mode { Read = 1 << 0, Write = 1 << 1, Append = 1 << 2, Update = 1 << 3, Execute = 1 << 4, }; enum SeekDirection { SeekStart, SeekCurrent, SeekEnd, }; virtual ~FileHandle() = default; virtual Error close() = 0; virtual Result<bool> eof() = 0; virtual Error flush() = 0; virtual Error seek(SeekDirection direction, int64_t position) = 0; virtual Result<int64_t> size() = 0; virtual Result<int64_t> tell() = 0; virtual Result<int64_t> truncate() = 0; virtual Result<int64_t> read(void *buffer, int64_t size, int64_t count) = 0; virtual Result<int64_t> write(const void *buffer, int64_t size, int64_t count) = 0; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_host_device.cpp ================================================ #include "vfs_host_device.h" #include "vfs_host_directoryiterator.h" #include "vfs_host_filehandle.h" #include "vfs_link_device.h" #include "vfs_virtual_device.h" #include <algorithm> #include <common/platform.h> #include <system_error> #include <vector> namespace vfs { HostDevice::HostDevice(std::filesystem::path path) : Device(Device::Host), mHostPath(std::move(path)), mVirtualDevice(std::make_shared<VirtualDevice>()) { } Result<std::shared_ptr<Device>> HostDevice::getLinkDevice(const User &user, const Path &path) { return { std::make_shared<LinkDevice>(shared_from_this(), path) }; } Error HostDevice::makeFolder(const User &user, const Path &path) { if (mVirtualDevice) { mVirtualDevice->makeFolder(user, path); } if (!checkWritePermission(user, path)) { return Error::Permission; } auto error = std::error_code { }; if (std::filesystem::create_directories(makeHostPath(path), error)) { return Error::Success; } return translateError(error); } Error HostDevice::makeFolders(const User &user, const Path &path) { if (mVirtualDevice) { mVirtualDevice->makeFolders(user, path); } if (!checkWritePermission(user, path)) { return Error::Permission; } auto error = std::error_code { }; if (std::filesystem::create_directories(makeHostPath(path), error)) { return Error::Success; } return translateError(error); } Error HostDevice::mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) { return mVirtualDevice->mountDevice(user, path, device); } Error HostDevice::mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) { return mVirtualDevice->mountOverlayDevice(user, priority, path, device); } Error HostDevice::unmountDevice(const User &user, const Path &path) { return mVirtualDevice->unmountDevice(user, path); } Error HostDevice::unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) { return mVirtualDevice->unmountOverlayDevice(user, priority, path); } Result<DirectoryIterator> HostDevice::openDirectory(const User &user, const Path &path) { if (!checkReadPermission(user, path)) { return { Error::Permission }; } auto ec = std::error_code { }; auto itr = std::filesystem::directory_iterator { makeHostPath(path), ec }; if (!ec) { // Iterate through the whole directory building up a list of entries // alphabetically sorted by name to ensure same behaviour across platforms auto listing = std::vector<Status> {}; for (auto &hostEntry : itr) { auto currentEntry = Status { }; currentEntry.name = hostEntry.path().filename().string(); if (auto size = hostEntry.file_size(ec); !ec) { currentEntry.size = size; currentEntry.flags = Status::HasSize; } if (hostEntry.is_directory()) { currentEntry.flags = Status::IsDirectory; } if (auto result = lookupPermissions(currentEntry.name); result) { currentEntry.group = result->group; currentEntry.owner = result->owner; currentEntry.permission = result->permission; currentEntry.flags = Status::HasPermissions; } listing.insert( std::upper_bound(listing.begin(), listing.end(), currentEntry, [](const Status &lhs, const Status &rhs) { return lhs.name < rhs.name; }), std::move(currentEntry)); } return { DirectoryIterator { std::make_shared<HostDirectoryIterator>(this, std::move(listing)) } }; } if (mVirtualDevice) { auto result = mVirtualDevice->openDirectory(user, path); if (result) { return result; } } return translateError(ec); } static std::string translateOpenMode(FileHandle::Mode mode) { auto result = std::string { }; if (mode & FileHandle::Read) { result += "r"; } if (mode & FileHandle::Write) { result += "w"; } if (mode & FileHandle::Append) { result += "a"; } result += "b"; if (mode & FileHandle::Update) { result += "+"; } return result; } Result<std::unique_ptr<FileHandle>> HostDevice::openFile(const User &user, const Path &path, FileHandle::Mode mode) { // Check file permissions if ((mode & FileHandle::Read) || (mode & FileHandle::Update)) { if (!checkReadPermission(user, path)) { return { Error::Permission }; } } if ((mode & FileHandle::Write) || (mode & FileHandle::Update)) { if (!checkWritePermission(user, path)) { return { Error::Permission }; } } if (mode & FileHandle::Execute) { if (!checkExecutePermission(user, path)) { return { Error::ExecutePermission }; } } auto hostMode = translateOpenMode(mode); #ifdef PLATFORM_WINDOWS auto handle = static_cast<FILE *>(nullptr); fopen_s(&handle, makeHostPath(path).string().c_str(), hostMode.c_str()); #else auto handle = fopen(makeHostPath(path).string().c_str(), hostMode.c_str()); #endif if (handle) { return { std::make_unique<HostFileHandle>(handle, mode) }; } if (mVirtualDevice) { auto result = mVirtualDevice->openFile(user, path, mode); if (result) { return result; } } // TODO: Translate fopen errno return { Error::NotFound }; } Error HostDevice::remove(const User &user, const Path &path) { if (!checkWritePermission(user, path)) { return Error::Permission; } auto ec = std::error_code { }; if (std::filesystem::remove(makeHostPath(path), ec)) { return Error::Success; } if (mVirtualDevice) { auto result = mVirtualDevice->remove(user, path); if (result == Error::Success) { return result; } } return translateError(ec); } Error HostDevice::rename(const User &user, const Path &src, const Path &dst) { if (!checkReadPermission(user, src)) { return Error::Permission; } if (!checkWritePermission(user, dst)) { return Error::Permission; } auto ec = std::error_code { }; std::filesystem::rename(makeHostPath(src), makeHostPath(dst), ec); if (!ec) { return Error::Success; } if (mVirtualDevice) { auto result = mVirtualDevice->rename(user, src, dst); if (result == Error::Success) { return result; } } return translateError(ec); } Error HostDevice::setGroup(const User &user, const Path &path, GroupId group) { auto ec = std::error_code { }; if (std::filesystem::exists(makeHostPath(path), ec)) { mPermissionsCache[path.path()].group = group; return Error::Success; } if (mVirtualDevice) { auto result = mVirtualDevice->setGroup(user, path, group); if (result == Error::Success) { return result; } } return translateError(ec); } Error HostDevice::setOwner(const User &user, const Path &path, OwnerId owner) { auto ec = std::error_code { }; if (std::filesystem::exists(makeHostPath(path), ec)) { mPermissionsCache[path.path()].owner = owner; return Error::Success; } if (mVirtualDevice) { auto result = mVirtualDevice->setOwner(user, path, owner); if (result == Error::Success) { return result; } } return translateError(ec); } Error HostDevice::setPermissions(const User &user, const Path &path, Permissions mode) { auto ec = std::error_code { }; if (std::filesystem::exists(makeHostPath(path), ec)) { mPermissionsCache[path.path()].permission = mode; return Error::Success; } if (mVirtualDevice) { auto result = mVirtualDevice->setPermissions(user, path, mode); if (result == Error::Success) { return result; } } return translateError(ec); } Result<Status> HostDevice::status(const User &user, const Path &path) { if (!checkReadPermission(user, path)) { return { Error::Permission }; } auto ec = std::error_code { }; auto hostPath = makeHostPath(path); auto hostStatus = std::filesystem::status(hostPath, ec); if (ec) { auto result = mVirtualDevice->status(user, path); if (result) { return result; } return { translateError(ec) }; } if (!std::filesystem::exists(hostStatus)) { return { Error::NotFound }; } auto status = Status { }; if (std::filesystem::is_directory(hostStatus)) { status.flags |= Status::IsDirectory; } if (auto size = std::filesystem::file_size(hostPath, ec); !ec) { status.size = size; status.flags |= Status::HasSize; } if (auto result = lookupPermissions(path); result) { auto &permissions = *result; status.group = permissions.group; status.owner = permissions.owner; status.permission = permissions.permission; status.flags |= Status::HasPermissions; } return { status }; } Result<HostNodePermission> HostDevice::lookupPermissions(const Path &path) const { auto itr = mPermissionsCache.find(path.path()); if (itr == mPermissionsCache.end()) { return { Error::NotFound }; } return { itr->second }; } bool HostDevice::checkReadPermission(const User &user, const Path &path) { auto itr = mPermissionsCache.find(path.path()); if (itr == mPermissionsCache.end()) { // No permissions mapped for this file, assume valid. return true; } auto &permissions = itr->second; if (user.id == permissions.owner) { if (permissions.permission & Permissions::OwnerRead) { return true; } } if (user.group == permissions.group) { if (permissions.permission & Permissions::GroupRead) { return true; } } if (permissions.permission & Permissions::OtherRead) { return true; } return false; } bool HostDevice::checkWritePermission(const User &user, const Path &path) { auto itr = mPermissionsCache.find(path.path()); if (itr == mPermissionsCache.end()) { // No permissions mapped for this file, assume valid. return true; } auto &permissions = itr->second; if (user.id == permissions.owner) { if (permissions.permission & Permissions::OwnerWrite) { return true; } } if (user.group == permissions.group) { if (permissions.permission & Permissions::GroupWrite) { return true; } } if (permissions.permission & Permissions::OtherWrite) { return true; } return false; } bool HostDevice::checkExecutePermission(const User &user, const Path &path) { auto itr = mPermissionsCache.find(path.path()); if (itr == mPermissionsCache.end()) { // No permissions mapped for this file, assume valid. return true; } auto &permissions = itr->second; if (user.id == permissions.owner) { if (permissions.permission & Permissions::OwnerExecute) { return true; } } if (user.group == permissions.group) { if (permissions.permission & Permissions::GroupExecute) { return true; } } if (permissions.permission & Permissions::OtherExecute) { return true; } return false; } std::filesystem::path HostDevice::makeHostPath(const Path &guestPath) const { return mHostPath / guestPath.path(); } Error HostDevice::translateError(const std::error_code &ec) const { if (!ec) { return Error::Success; } if (ec == std::errc::no_such_file_or_directory) { return Error::NotFound; } else if (ec == std::errc::file_exists) { return Error::AlreadyExists; } else if (ec == std::errc::is_a_directory) { return Error::NotFile; } else if (ec == std::errc::not_a_directory) { return Error::NotDirectory; } else if (ec == std::errc::operation_not_supported) { return Error::OperationNotSupported; } else if (ec == std::errc::permission_denied) { return Error::Permission; } else if (ec == std::errc::read_only_file_system) { return Error::ReadOnly; } return Error::GenericError; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_host_device.h ================================================ #pragma once #include "vfs_device.h" #include <filesystem> #include <map> #include <memory> #include <string> #include <system_error> namespace vfs { struct HostNodePermission { GroupId group; OwnerId owner; Permissions permission; }; class VirtualDevice; class HostDevice : public Device, public std::enable_shared_from_this<HostDevice> { public: HostDevice(std::filesystem::path path); ~HostDevice() override = default; Result<std::shared_ptr<Device>> getLinkDevice(const User &user, const Path &path) override; Error makeFolder(const User &user, const Path &path) override; Error makeFolders(const User &user, const Path &path) override; Error mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) override; Error mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) override; Error unmountDevice(const User &user, const Path &path) override; Error unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) override; Result<DirectoryIterator> openDirectory(const User &user, const Path &path) override; Result<std::unique_ptr<FileHandle>> openFile(const User &user, const Path &path, FileHandle::Mode mode) override; Error remove(const User &user, const Path &path) override; Error rename(const User &user, const Path &src, const Path &dst) override; Error setGroup(const User &user, const Path &path, GroupId group) override; Error setOwner(const User &user, const Path &path, OwnerId owner) override; Error setPermissions(const User &user, const Path &path, Permissions mode) override; Result<Status> status(const User &user, const Path &path) override; Result<HostNodePermission> lookupPermissions(const Path &path) const; private: bool checkReadPermission(const User &user, const Path &path); bool checkWritePermission(const User &user, const Path &path); bool checkExecutePermission(const User &user, const Path &path); std::filesystem::path makeHostPath(const Path &guestPath) const; Error translateError(const std::error_code &ec) const; Error translateError(const std::system_error &ec) const; private: std::filesystem::path mHostPath; std::map<std::string, HostNodePermission> mPermissionsCache; //! We want a virtual device backing this host device so we can do things //! like mount other devices within a host device. For example this could //! be useful if we have MLC / SLC on host device and we want to mount //! installed games / patches within there when the user stores their data //! external to these system paths. std::shared_ptr<VirtualDevice> mVirtualDevice; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_host_directoryiterator.cpp ================================================ #include "vfs_host_directoryiterator.h" #include "vfs_host_device.h" namespace vfs { HostDirectoryIterator::HostDirectoryIterator(HostDevice *hostDevice, std::vector<Status> listing) : mHostDevice(hostDevice), mListing(std::move(listing)), mIterator(mListing.begin()) { } Result<Status> HostDirectoryIterator::readEntry() { if (mIterator == mListing.end()) { return { Error::EndOfDirectory }; } auto currentEntry = *mIterator; ++mIterator; return { currentEntry }; } Error HostDirectoryIterator::rewind() { mIterator = mListing.begin(); return Error::Success; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_host_directoryiterator.h ================================================ #pragma once #include "vfs_directoryiterator.h" #include <filesystem> #include <vector> namespace vfs { class HostDevice; class HostDirectoryIterator : public DirectoryIteratorImpl { public: HostDirectoryIterator(HostDevice *hostDevice, std::vector<Status> listing); ~HostDirectoryIterator() override = default; Result<Status> readEntry() override; Error rewind() override; private: class HostDevice *mHostDevice; std::vector<Status> mListing; std::vector<Status>::iterator mIterator; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_host_filehandle.cpp ================================================ #include "vfs_host_filehandle.h" #include <common/platform.h> #ifdef PLATFORM_WINDOWS #include <io.h> #elif defined(PLATFORM_POSIX) #include <unistd.h> #include <sys/types.h> #endif namespace vfs { HostFileHandle::HostFileHandle(FILE *handle, Mode mode) : mHandle(handle), mMode(mode) { } HostFileHandle::~HostFileHandle() { close(); } Error HostFileHandle::close() { if (mHandle) { fclose(mHandle); mHandle = nullptr; } return Error::Success; } Result<bool> HostFileHandle::eof() { if (!mHandle) { return { Error::NotOpen }; } return { !!feof(mHandle) }; } Error HostFileHandle::flush() { if (!mHandle) { return Error::NotOpen; } fflush(mHandle); return Error::Success; } Error HostFileHandle::seek(SeekDirection direction, int64_t offset) { if (!mHandle) { return Error::NotOpen; } int seekDirection = SEEK_SET; switch (direction) { case SeekCurrent: seekDirection = SEEK_CUR; break; case SeekEnd: seekDirection = SEEK_END; break; case SeekStart: seekDirection = SEEK_SET; break; default: return Error::InvalidSeekDirection; } #ifdef PLATFORM_WINDOWS if (_fseeki64(mHandle, offset, seekDirection) != 0) { // TODO: Translate ERRNO ? return Error::GenericError; } #elif defined(PLATFORM_POSIX) if (fseeko(mHandle, offset, seekDirection) != 0) { // TODO: Translate ERRNO ? return Error::GenericError; } #else return Error::OperationNotSupported; #endif return Error::Success; } Result<int64_t> HostFileHandle::size() { if (!mHandle) { return { Error::NotOpen }; } auto position = tell(); if (!position) { return { position.error() }; } auto error = seek(SeekEnd, 0); if (error != Error::Success) { return { position.error() }; } auto size = tell(); if (!size) { return { size.error() }; } seek(SeekStart, *position); return size; } Result<int64_t> HostFileHandle::tell() { if (!mHandle) { return Error::NotOpen; } #ifdef PLATFORM_WINDOWS auto position = _ftelli64(mHandle); if (position < 0) { // TODO: Translate error? return { Error::GenericError }; } #elif defined(PLATFORM_POSIX) auto position = ftello(mHandle); if (position < 0) { // TODO: Translate error? return { Error::GenericError }; } #else return Error::OperationNotSupported; #endif return { static_cast<int64_t>(position) }; } Result<int64_t> HostFileHandle::truncate() { if (!mHandle) { return { Error::NotOpen }; } // TODO: Check open mode for read only auto length = tell(); if (!length) { return length.error(); } #ifdef PLATFORM_WINDOWS if (_chsize_s(_fileno(mHandle), static_cast<long long>(*length)) != 0) { // TODO: Translate error? return { Error::GenericError }; } #elif defined(PLATFORM_POSIX) fflush(mHandle); if (ftruncate(fileno(mHandle), static_cast<off_t>(*length)) != 0) { // TODO: Translate error? return { Error::GenericError }; } #else return Error::OperationNotSupported; #endif return length; } Result<int64_t> HostFileHandle::read(void *buffer, int64_t size, int64_t count) { return { static_cast<int64_t>(fread(buffer, size, count, mHandle)) }; } Result<int64_t> HostFileHandle::write(const void *buffer, int64_t size, int64_t count) { if (!mHandle) { return { Error::NotOpen }; } // TODO: Check open mode for read only return { static_cast<int64_t>(fwrite(buffer, size, count, mHandle)) }; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_host_filehandle.h ================================================ #pragma once #include "vfs_filehandle.h" #include <cstdio> namespace vfs { class HostFileHandle : public FileHandle { public: HostFileHandle(FILE *handle, Mode mode); ~HostFileHandle() override; Error close() override; Result<bool> eof() override; Error flush() override; Error seek(SeekDirection direction, int64_t offset) override; Result<int64_t> size() override; Result<int64_t> tell() override; Result<int64_t> truncate() override; Result<int64_t> read(void *buffer, int64_t size, int64_t count) override; Result<int64_t> write(const void *buffer, int64_t size, int64_t count) override; private: FILE *mHandle; Mode mMode; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_link_device.cpp ================================================ #include "vfs_link_device.h" namespace vfs { LinkDevice::LinkDevice(std::shared_ptr<Device> device, Path path) : Device(Device::Link), mDevice(device), mPath(path) { } Result<std::shared_ptr<Device>> LinkDevice::getLinkDevice(const User &user, const Path &path) { if (path.depth() == 0) { return { shared_from_this() }; } else { return mDevice->getLinkDevice(user, mPath / path); } } Error LinkDevice::makeFolder(const User &user, const Path &path) { return mDevice->makeFolder(user, mPath / path); } Error LinkDevice::makeFolders(const User &user, const Path &path) { return mDevice->makeFolders(user, mPath / path); } Error LinkDevice::mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) { return mDevice->mountDevice(user, mPath / path, device); } Error LinkDevice::mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) { return mDevice->mountOverlayDevice(user, priority, mPath / path, std::move(device)); } Error LinkDevice::unmountDevice(const User &user, const Path &path) { return mDevice->unmountDevice(user, mPath / path); } Error LinkDevice::unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) { return mDevice->unmountOverlayDevice(user, priority, mPath / path); } Result<DirectoryIterator> LinkDevice::openDirectory(const User &user, const Path &path) { return mDevice->openDirectory(user, mPath / path); } Result<std::unique_ptr<FileHandle>> LinkDevice::openFile(const User &user, const Path &path, FileHandle::Mode mode) { return mDevice->openFile(user, mPath / path, mode); } Error LinkDevice::remove(const User &user, const Path &path) { return mDevice->remove(user, mPath / path); } Error LinkDevice::rename(const User &user, const Path &src, const Path &dst) { return mDevice->rename(user, mPath / src, mPath / dst); } Error LinkDevice::setGroup(const User &user, const Path &path, GroupId group) { return mDevice->setGroup(user, mPath / path, group); } Error LinkDevice::setOwner(const User &user, const Path &path, OwnerId owner) { return mDevice->setOwner(user, mPath / path, owner); } Error LinkDevice::setPermissions(const User &user, const Path &path, Permissions mode) { return mDevice->setPermissions(user, mPath / path, mode); } Result<Status> LinkDevice::status(const User &user, const Path &path) { return mDevice->status(user, mPath / path); } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_link_device.h ================================================ #pragma once #include "vfs_device.h" namespace vfs { class LinkDevice : public Device, public std::enable_shared_from_this<LinkDevice> { public: LinkDevice(std::shared_ptr<Device> device, Path path); ~LinkDevice() override = default; Result<std::shared_ptr<Device>> getLinkDevice(const User &user, const Path &path) override; Error makeFolder(const User &user, const Path &path) override; Error makeFolders(const User &user, const Path &path) override; Error mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) override; Error mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) override; Error unmountDevice(const User &user, const Path &path) override; Error unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) override; Result<DirectoryIterator> openDirectory(const User &user, const Path &path) override; Result<std::unique_ptr<FileHandle>> openFile(const User &user, const Path &path, FileHandle::Mode mode) override; Error remove(const User &user, const Path &path) override; Error rename(const User &user, const Path &src, const Path &dst) override; Error setGroup(const User &user, const Path &path, GroupId group) override; Error setOwner(const User &user, const Path &path, OwnerId owner) override; Error setPermissions(const User &user, const Path &path, Permissions mode) override; Result<Status> status(const User &user, const Path &path) override; private: std::shared_ptr<Device> mDevice; Path mPath; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_overlay_device.cpp ================================================ #include "vfs_overlay_device.h" #include "vfs_overlay_directoryiterator.h" #include <algorithm> namespace vfs { OverlayDevice::OverlayDevice() : Device(Device::Overlay) { } Result<std::shared_ptr<Device>> OverlayDevice::getLinkDevice(const User &user, const Path &path) { for (auto &[priority, device] : mDevices) { auto result = device->getLinkDevice(user, path); if (result.error() != Error::NotFound) { return result; } } return { Error::NotFound }; } Error OverlayDevice::makeFolder(const User &user, const Path &path) { for (auto &[priority, device] : mDevices) { auto result = device->makeFolder(user, path); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::makeFolders(const User &user, const Path &path) { for (auto &[priority, device] : mDevices) { auto result = device->makeFolders(user, path); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) { if (path.depth() == 0) { // Should be using mountOverlayDevice, not mountDevice... return Error::OperationNotSupported; } for (auto &[basePriority, baseDevice] : mDevices) { auto result = baseDevice->mountDevice(user, path, device); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) { if (path.depth() == 0) { for (auto itr = mDevices.begin(); itr != mDevices.end(); ++itr) { if (itr->first == priority) { return Error::AlreadyExists; } } mDevices.emplace_back(priority, std::move(device)); std::sort(mDevices.begin(), mDevices.end(), [](const auto &lhs, const auto &rhs) { return lhs.first > rhs.first; }); return Error::Success; } for (auto &[basePriority, baseDevice] : mDevices) { auto result = baseDevice->mountOverlayDevice(user, priority, path, device); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::unmountDevice(const User &user, const Path &path) { if (mDevices.empty()) { return Error::NotFound; } if (path.depth() == 0) { // Should be using unmountOverlayDevice, not mountDevice... return Error::OperationNotSupported; } for (auto &[priority, device] : mDevices) { auto result = device->unmountDevice(user, path); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) { if (path.depth() == 0) { for (auto itr = mDevices.begin(); itr != mDevices.end(); ++itr) { if (itr->first == priority) { mDevices.erase(itr); return Error::Success; } } return Error::NotFound; } for (auto &[basePriority, baseDevice] : mDevices) { auto result = baseDevice->unmountOverlayDevice(user, priority, path); if (result == Error::Success) { return result; } } return Error::NotFound; } Result<DirectoryIterator> OverlayDevice::openDirectory(const User &user, const Path &path) { auto error = Error { }; auto iterator = std::make_shared<OverlayDirectoryIterator>(user, path, this, error); if (error != Error::Success) { return { error }; } return { DirectoryIterator { std::move(iterator) } }; } Result<std::unique_ptr<FileHandle>> OverlayDevice::openFile(const User &user, const Path &path, FileHandle::Mode mode) { for (auto &[priority, device] : mDevices) { auto result = device->openFile(user, path, mode); if (result.error() != Error::NotFound) { return result; } } return { Error::NotFound }; } Error OverlayDevice::remove(const User &user, const Path &path) { for (auto &[priority, device] : mDevices) { auto result = device->remove(user, path); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::rename(const User &user, const Path &src, const Path &dst) { for (auto &[priority, device] : mDevices) { auto result = device->rename(user, src, dst); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::setGroup(const User &user, const Path &path, GroupId group) { for (auto &[priority, device] : mDevices) { auto result = device->setGroup(user, path, group); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::setOwner(const User &user, const Path &path, OwnerId owner) { for (auto &[priority, device] : mDevices) { auto result = device->setOwner(user, path, owner); if (result == Error::Success) { return result; } } return Error::NotFound; } Error OverlayDevice::setPermissions(const User &user, const Path &path, Permissions mode) { for (auto &[priority, device] : mDevices) { auto result = device->setPermissions(user, path, mode); if (result == Error::Success) { return result; } } return Error::NotFound; } Result<Status> OverlayDevice::status(const User &user, const Path &path) { for (auto &[priority, device] : mDevices) { auto result = device->status(user, path); if (result.error() != Error::NotFound) { return result; } } return { Error::NotFound }; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_overlay_device.h ================================================ #pragma once #include "vfs_device.h" #include "vfs_permissions.h" #include "vfs_result.h" #include <memory> #include <vector> #include <utility> namespace vfs { class OverlayDevice : public Device { using device_list = std::vector<std::pair<OverlayPriority, std::shared_ptr<Device>>>; public: using iterator = device_list::iterator; using const_iterator = device_list::const_iterator; OverlayDevice(); ~OverlayDevice() override = default; Result<std::shared_ptr<Device>> getLinkDevice(const User &user, const Path &path) override; Error makeFolder(const User &user, const Path &path) override; Error makeFolders(const User &user, const Path &path) override; Error mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) override; Error mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) override; Error unmountDevice(const User &user, const Path &path) override; Error unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) override; Result<DirectoryIterator> openDirectory(const User &user, const Path &path) override; Result<std::unique_ptr<FileHandle>> openFile(const User &user, const Path &path, FileHandle::Mode mode) override; Error remove(const User &user, const Path &path) override; Error rename(const User &user, const Path &src, const Path &dst) override; Error setGroup(const User &user, const Path &path, GroupId group) override; Error setOwner(const User &user, const Path &path, OwnerId owner) override; Error setPermissions(const User &user, const Path &path, Permissions mode) override; Result<Status> status(const User &user, const Path &path) override; iterator begin() { return mDevices.begin(); } iterator end() { return mDevices.end(); } const_iterator begin() const { return mDevices.begin(); } const_iterator end() const { return mDevices.end(); } private: device_list mDevices; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_overlay_directoryiterator.cpp ================================================ #include "vfs_overlay_directoryiterator.h" namespace vfs { OverlayDirectoryIterator::OverlayDirectoryIterator(User user, Path path, OverlayDevice *overlayDevice, Error &error) : mUser(std::move(user)), mPath(std::move(path)), mDevice(overlayDevice) { error = rewind(); } Result<Status> OverlayDirectoryIterator::readEntry() { while (mDeviceIterator != mDevice->end()) { if (!mIterator.has_value()) { auto result = mDeviceIterator->second->openDirectory(mUser, mPath); if (!result) { mDeviceIterator++; continue; } mIterator = std::move(*result); } auto result = mIterator->readEntry(); if (result) { return result; } if (result.error() == Error::EndOfDirectory) { mIterator.reset(); mDeviceIterator++; continue; } } return Error::EndOfDirectory; } Error OverlayDirectoryIterator::rewind() { mDeviceIterator = mDevice->begin(); mIterator.reset(); // Find first device which can open directory while (mDeviceIterator != mDevice->end()) { auto result = mDeviceIterator->second->openDirectory(mUser, mPath); if (!result) { mDeviceIterator++; continue; } mIterator = std::move(*result); break; } return mIterator.has_value() ? Error::Success : Error::NotFound; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_overlay_directoryiterator.h ================================================ #pragma once #include "vfs_directoryiterator.h" #include "vfs_overlay_device.h" #include "vfs_path.h" #include <optional> namespace vfs { class OverlayDirectoryIterator : public DirectoryIteratorImpl { public: OverlayDirectoryIterator(User user, Path path, OverlayDevice *overlayDevice, Error &error); ~OverlayDirectoryIterator() override = default; Result<Status> readEntry() override; Error rewind() override; private: User mUser; Path mPath; OverlayDevice *mDevice; OverlayDevice::iterator mDeviceIterator; std::optional<DirectoryIterator> mIterator; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_path.cpp ================================================ #include "vfs_path.h" #include "vfs_pathiterator.h" #include <algorithm> #include <list> #include <string_view> namespace vfs { static std::string normalisePath(std::string_view path); Path::Path(const std::string &path) : mPath(normalisePath(std::string_view { path })) { } Path::Path(const char *path) : mPath(normalisePath(std::string_view { path })) { } Path::Path(std::string_view path) : mPath(normalisePath(path)) { } Path::Path(PathIterator first, PathIterator last) : mPath() { auto size = static_cast<size_t>(std::distance(first.mPosition, last.mPosition)); if (size) { mPath = normalisePath(std::string_view { &*first.mPosition, size }); } } void Path::clear() { mPath.clear(); } size_t Path::depth() const { if (mPath.empty()) { return 0; } return 1 + std::count(mPath.begin(), mPath.end(), '/'); } bool Path::empty() const { return mPath.empty(); } const std::string & Path::path() const { return mPath; } PathIterator Path::begin() const { if (!mPath.empty()) { auto firstBegin = mPath.begin(); auto firstEnd = std::find(mPath.begin(), mPath.end(), '/'); if (firstBegin == firstEnd) { return PathIterator { this, mPath.begin(), std::string_view { &*firstBegin, 1 } }; } else { auto size = static_cast<size_t>(firstEnd - firstBegin); return PathIterator { this, mPath.begin(), std::string_view { &*firstBegin, size } }; } } return PathIterator { this, mPath.begin() }; } PathIterator Path::end() const { return PathIterator { this, mPath.end() }; } Path Path::operator /(const Path &path) const { return Path(mPath + "/" + path.mPath); } std::string normalisePath(std::string_view path) { // 1) If the path is empty, stop if (path.empty()) { return {}; } // 2) Replace each directory-separator (which may consist of multiple // slashes) with a single path::preferred_separator. auto expanded = std::list<std::string_view> { }; for (auto itr = path.begin(); itr != path.end(); ) { if (*itr == '/') { if (expanded.empty() || !expanded.back().empty()) { expanded.emplace_back(); } ++itr; } else { auto end = std::find(itr + 1, path.end(), '/'); expanded.emplace_back(&*itr, static_cast<size_t>(end - itr)); itr = end; } } // 3) Replace each slash character in the root-name with // path::preferred_separator. // Nothing to do for us. // 4) Remove each dot and any immediately following directory-separator. for (auto itr = expanded.begin(); itr != expanded.end(); ) { if (*itr == ".") { // Erase dot filename. itr = expanded.erase(itr); if (itr != expanded.end()) { // Erase immediately following directory-separator. itr = expanded.erase(itr); } } else { ++itr; } } // 5) Remove each non-dot-dot filename immediately followed by a // directory-separator and a dot-dot, along with any immediately // following directory-separator. for (auto itr = expanded.begin(); itr != expanded.end(); ) { auto start = itr; ++itr; if (*start == ".." && start != expanded.begin() && --start != expanded.begin() && *--start != "..") { if (itr != expanded.end()) { // dot-dot filename has following directory-separator ++itr; } expanded.erase(start, itr); } } // 6) If there is root-directory, remove all dot-dots and any // directory-separators immediately following them. if (!expanded.empty() && expanded.front().empty()) { for (auto itr = expanded.begin(); itr != expanded.end(); ) { if (*itr == "..") { // Erase dot-dot filename. itr = expanded.erase(itr); if (itr != expanded.end()) { // Erase immediately following directory-separator. itr = expanded.erase(itr); } } else { break; } } } // 7) If the last filename is dot-dot, remove any trailing directory-separator. if (expanded.size() >= 2 && expanded.back().empty() && *prev(expanded.end(), 2) == "..") { expanded.pop_back(); } // Remove trailing separator while (expanded.size() > 1 && expanded.back().empty()) { expanded.pop_back(); } // Flatten into normalised path std::string normalised; for (auto itr = expanded.begin(); itr != expanded.end(); ++itr) { if (itr->empty()) { normalised.push_back('/'); } else { normalised.append(*itr); } } // 8) If the path is empty, add a dot (normal form of ./ is .) if (normalised.empty()) { normalised = "."; } return normalised; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_path.h ================================================ #pragma once #include <string> namespace vfs { class PathIterator; class Path { friend class PathIterator; public: Path() = default; Path(const std::string &path); Path(const char *path); Path(std::string_view path); Path(PathIterator first, PathIterator last); void clear(); size_t depth() const; bool empty() const; const std::string &path() const; PathIterator begin() const; PathIterator end() const; Path operator /(const Path &path) const; private: std::string mPath; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_pathiterator.cpp ================================================ #include "vfs_path.h" #include "vfs_pathiterator.h" #include <algorithm> namespace vfs { PathIterator::PathIterator(const Path *path, std::string::const_iterator position) : mPath(path), mPosition(position), mElement() { } PathIterator::PathIterator(const Path *path, std::string::const_iterator position, std::string_view element) : mPath(path), mPosition(position), mElement(element) { } PathIterator::reference PathIterator::operator *() const { return mElement; } const Path *PathIterator::operator ->() const { return &mElement; } PathIterator &PathIterator::operator++() { auto pathEnd = mPath->mPath.end(); mPosition += mElement.mPath.size(); if (mPosition == pathEnd) { mElement.clear(); return *this; } else { if (*mPosition == '/') { mPosition++; } auto elementStart = mPosition; auto elementEnd = std::find(mPosition, pathEnd, '/'); mElement.mPath.assign(elementStart, elementEnd); } return *this; } PathIterator PathIterator::operator++(int) { auto prev = *this; ++*this; return prev; } PathIterator & PathIterator::operator--() { auto pathBegin = mPath->mPath.begin(); auto pathEnd = mPath->mPath.end(); auto elementEnd = (mPosition == pathEnd) ? (pathEnd) : (mPosition - 1); mPosition = elementEnd - 1; while (mPosition > pathBegin) { if (*mPosition == '/') { ++mPosition; break; } --mPosition; } mElement.mPath.assign(mPosition, elementEnd); return *this; } PathIterator PathIterator::operator--(int) { auto prev = *this; --*this; return prev; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_pathiterator.h ================================================ #pragma once #include "vfs_path.h" #include <cstddef> #include <iterator> namespace vfs { class PathIterator { friend class Path; public: using iterator_category = std::bidirectional_iterator_tag; using value_type = Path; using difference_type = std::ptrdiff_t; using pointer = const Path *; using reference = const Path &; PathIterator() = default; PathIterator(const Path *path, std::string::const_iterator position); PathIterator(const Path *path, std::string::const_iterator position, std::string_view element); PathIterator(const PathIterator &) = default; PathIterator(PathIterator &&) = default; PathIterator &operator=(const PathIterator &) = default; PathIterator &operator=(PathIterator &&) = default; reference operator *() const; const Path *operator ->() const; PathIterator &operator++(); PathIterator operator++(int); PathIterator &operator--(); PathIterator operator--(int); friend bool operator ==(const PathIterator &lhs, const PathIterator &rhs) { return lhs.mPosition == rhs.mPosition; } friend bool operator !=(const PathIterator &lhs, const PathIterator &rhs) { return lhs.mPosition != rhs.mPosition; } private: const Path *mPath = nullptr; std::string::const_iterator mPosition; Path mElement; }; PathIterator begin(Path &path); PathIterator end(Path &path); } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_permissions.h ================================================ #pragma once namespace vfs { using GroupId = unsigned int; using OwnerId = unsigned int; using OverlayPriority = int; enum Permissions { NoPermissions = 0, OtherExecute = 0x001, OtherWrite = 0x002, OtherRead = 0x004, GroupExecute = 0x010, GroupWrite = 0x020, GroupRead = 0x040, OwnerExecute = 0x100, OwnerWrite = 0x200, OwnerRead = 0x400, }; inline Permissions operator |(Permissions lhs, Permissions rhs) { return static_cast<Permissions>(static_cast<int>(lhs) | static_cast<int>(rhs)); } inline Permissions operator &(Permissions lhs, Permissions rhs) { return static_cast<Permissions>(static_cast<int>(lhs) & static_cast<int>(rhs)); } struct User { OwnerId id = 0; GroupId group = 0; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_result.h ================================================ #pragma once #include "vfs_error.h" #include <utility> namespace vfs { template<typename ValueType> class Result { public: Result(ValueType value) : mError(Error::Success), mValue(std::move(value)) { } Result(Error error) : mError(error) { } Error error() const { return mError; } explicit operator bool() const { return mError == Error::Success; } ValueType &operator *() { return mValue; } const ValueType &operator *() const { return mValue; } ValueType *operator ->() { return &mValue; } const ValueType *operator ->() const { return &mValue; } private: Error mError; ValueType mValue; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_status.h ================================================ #pragma once #include "vfs_permissions.h" #include <cstdint> #include <string> namespace vfs { struct Status { enum Flags { None = 0, HasSize = 1 << 0, HasPermissions = 1 << 1, IsDirectory = 1 << 2, }; std::string name; GroupId group = 0; OwnerId owner = 0; Permissions permission = Permissions::NoPermissions; std::uintmax_t size = 0; int flags = Flags::None; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_device.cpp ================================================ #include "vfs_virtual_device.h" #include "vfs_virtual_directory.h" #include "vfs_virtual_directoryiterator.h" #include "vfs_virtual_file.h" #include "vfs_virtual_filehandle.h" #include "vfs_virtual_mounteddevice.h" #include "vfs_link_device.h" #include "vfs_overlay_device.h" #include "vfs_pathiterator.h" #include <cassert> namespace vfs { VirtualDevice::VirtualDevice(std::string rootDeviceName) : Device(Device::Virtual), mRootDeviceName(std::move(rootDeviceName)), mRoot(std::make_shared<VirtualDirectory>()) { } Result<std::shared_ptr<Device>> VirtualDevice::getLinkDevice(const User &user, const Path &path) { // TODO: This can be optimised to get a link device from the deepest path return { std::make_shared<LinkDevice>(shared_from_this(), path) }; } Error VirtualDevice::makeFolder(const User &user, const Path &path) { auto [node, relativePath] = findDeepest(user, path); if (relativePath.empty()) { return Error::AlreadyExists; } if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->makeFolder(user, relativePath); } if (node->type == VirtualNode::File) { return Error::NotDirectory; } if (relativePath.depth() > 1) { return Error::NotFound; } assert(node->type == VirtualNode::Directory); auto parentDirectory = static_cast<VirtualDirectory *>(node.get()); parentDirectory->children.emplace(relativePath.path(), std::make_shared<VirtualDirectory>()); return Error::Success; } Error VirtualDevice::makeFolders(const User &user, const Path &path) { auto [node, relativePath] = findDeepest(user, path); if (relativePath.empty()) { return Error::AlreadyExists; } if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->makeFolder(user, relativePath); } if (node->type == VirtualNode::File) { return Error::NotDirectory; } auto parentDirectory = static_cast<VirtualDirectory *>(node.get()); for (auto &childPath : relativePath) { auto childDirectory = new VirtualDirectory(); parentDirectory->children.emplace(childPath.path(), childDirectory); parentDirectory = childDirectory; } return Error::Success; } Error VirtualDevice::mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) { auto [node, relativePath] = findDeepest(user, path); if (relativePath.depth() == 0) { return Error::AlreadyExists; } if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->mountDevice(user, relativePath, device); } if (relativePath.depth() > 1) { return Error::NotFound; } if (node->type == VirtualNode::File) { return Error::NotDirectory; } assert(node->type == VirtualNode::Directory); auto parentDirectory = static_cast<VirtualDirectory *>(node.get()); parentDirectory->children.emplace( relativePath.path(), std::make_shared<VirtualMountedDevice>(std::move(device))); return Error::Success; } Error VirtualDevice::mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) { auto[node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); if (relativePath.depth() > 0) { return mountedDevice->device->mountOverlayDevice(user, priority, relativePath, device); } if (mountedDevice->device->type() != Device::Overlay) { // Remount the original device under an overlay device with priority 1 auto overlayDevice = std::make_shared<OverlayDevice>(); overlayDevice->mountOverlayDevice(user, 1, {}, std::move(mountedDevice->device)); mountedDevice->device = std::move(overlayDevice); } auto overlayDevice = static_cast<OverlayDevice *>(mountedDevice->device.get()); return overlayDevice->mountOverlayDevice(user, priority, {}, std::move(device)); } if (relativePath.depth() > 1) { return Error::NotFound; } if (node->type == VirtualNode::File) { return Error::NotDirectory; } assert(node->type == VirtualNode::Directory); auto parentDirectory = static_cast<VirtualDirectory *>(node.get()); auto overlayDevice = std::make_shared<OverlayDevice>(); overlayDevice->mountOverlayDevice(user, priority, {}, std::move(device)); parentDirectory->children.emplace(relativePath.path(), std::make_shared<VirtualMountedDevice>(std::move(overlayDevice))); return Error::Success; } Error VirtualDevice::unmountDevice(const User &user, const Path &path) { auto [parentNode, relativePath] = findDeepest(user, path, 1); if (parentNode->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(parentNode.get()); return mountedDevice->device->unmountDevice(user, relativePath); } if (relativePath.depth() > 1) { return Error::NotFound; } if (parentNode->type != VirtualNode::Directory) { return Error::NotDirectory; } // Remove device from parent auto parentDirectory = static_cast<VirtualDirectory *>(parentNode.get()); parentDirectory->children.erase(relativePath.path()); return Error::Success; } Error VirtualDevice::unmountOverlayDevice(const User &user, vfs::OverlayPriority priority, const Path &path) { auto [node, relativePath] = findDeepest(user, path); if (node->type != VirtualNode::MountedDevice) { return Error::NotMountDevice; } auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->unmountOverlayDevice(user, priority, relativePath); } Result<DirectoryIterator> VirtualDevice::openDirectory(const User &user, const Path &path) { auto [node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->openDirectory(user, relativePath); } if (relativePath.depth() > 0) { return Error::NotFound; } if (node->type != VirtualNode::Directory) { return Error::NotDirectory; } auto dir = std::static_pointer_cast<VirtualDirectory>(node); return { DirectoryIterator { std::make_shared<VirtualDirectoryIterator>(std::move(dir)) } }; } Result<std::unique_ptr<FileHandle>> VirtualDevice::openFile(const User &user, const Path &path, FileHandle::Mode mode) { auto [node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { if (relativePath.depth() == 0) { return Error::NotFile; } auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->openFile(user, relativePath, mode); } if (relativePath.depth() > 0) { return Error::NotFound; } if (node->type != VirtualNode::File) { return Error::NotFile; } auto file = std::static_pointer_cast<VirtualFile>(node); return { std::make_unique<VirtualFileHandle>(std::move(file), mode) }; } Error VirtualDevice::remove(const User &user, const Path &path) { auto [parentNode, relativePath] = findDeepest(user, path, 1); if (parentNode->type == VirtualNode::MountedDevice) { // Forward remove into mounted device auto mountedDevice = static_cast<VirtualMountedDevice *>(parentNode.get()); return mountedDevice->device->remove(user, relativePath); } if (relativePath.depth() > 1) { return Error::NotFound; } if (parentNode->type != VirtualNode::Directory) { // Can only erase from a directory return Error::NotDirectory; } // Remove device from parent auto parentDirectory = static_cast<VirtualDirectory *>(parentNode.get()); parentDirectory->children.erase(relativePath.path()); return Error::Success; } Error VirtualDevice::rename(const User &user, const Path &src, const Path &dst) { auto [srcParent, srcRelativePath] = findDeepest(user, src, 1); auto [dstParent, dstRelativePath] = findDeepest(user, dst, 1); if (srcParent->type == VirtualNode::MountedDevice || dstParent->type == VirtualNode::MountedDevice) { if (srcParent != dstParent) { // Cannot move across different mounted devices. return Error::OperationNotSupported; } auto mountedDevice = static_cast<VirtualMountedDevice *>(srcParent.get()); return mountedDevice->device->rename(user, srcRelativePath, dstRelativePath); } if (srcRelativePath.depth() > 1 || dstRelativePath.depth() > 1) { return Error::NotFound; } if (srcParent->type != VirtualNode::Directory || dstParent->type != VirtualNode::Directory) { // Can only move items between directories.. return Error::NotDirectory; } // Move node from src parent to dst parent auto srcParentDirectory = static_cast<VirtualDirectory *>(srcParent.get()); auto dstParentDirectory = static_cast<VirtualDirectory *>(dstParent.get()); dstParentDirectory->children[dstRelativePath.path()] = std::move(srcParentDirectory->children[srcRelativePath.path()]); srcParentDirectory->children.erase(srcRelativePath.path()); return Error::OperationNotSupported; } Error VirtualDevice::setGroup(const User &user, const Path &path, GroupId group) { auto [node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->setGroup(user, relativePath, group); } if (relativePath.depth() > 0) { return Error::NotFound; } node->group = group; return Error::Success; } Error VirtualDevice::setOwner(const User &user, const Path &path, OwnerId owner) { auto [node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->setOwner(user, relativePath, owner); } if (relativePath.depth() > 0) { return Error::NotFound; } node->owner = owner; return Error::Success; } Error VirtualDevice::setPermissions(const User &user, const Path &path, Permissions mode) { auto [node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->setPermissions(user, relativePath, mode); } if (relativePath.depth() > 0) { return Error::NotFound; } node->permission = mode; return Error::Success; } Result<Status> VirtualDevice::status(const User &user, const Path &path) { auto [node, relativePath] = findDeepest(user, path); if (node->type == VirtualNode::MountedDevice) { auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get()); return mountedDevice->device->status(user, relativePath); } if (relativePath.depth() > 0) { return Error::NotFound; } auto status = Status { }; status.name = std::prev(std::end(path))->path(); if (node->type == VirtualNode::Directory) { status.flags |= Status::IsDirectory; } if (node->type == VirtualNode::File) { auto file = static_cast<VirtualFile *>(node.get()); status.size = file->data.size(); } status.group = node->group; status.owner = node->owner; status.permission = node->permission; status.flags |= Status::HasPermissions; return status; } std::pair<std::shared_ptr<VirtualNode>, Path> VirtualDevice::findDeepest(const User &user, Path path, int parentLevel) { if (path.empty()) { return { mRoot, {} }; } if (!mRootDeviceName.empty()) { auto pathRoot = std::begin(path); if (pathRoot->path() != mRootDeviceName) { return { nullptr, {} }; } path = Path { std::next(pathRoot), std::end(path) }; if (path.empty()) { return { mRoot, {} }; } } auto dir = mRoot; auto itr = path.begin(); auto last = std::prev(path.end(), parentLevel); for (; itr != last; ++itr) { auto name = itr->path(); auto childItr = dir->children.find(itr->path()); if (childItr == dir->children.end()) { // We have reached as far as we could. break; } auto child = childItr->second; if (child->type == VirtualNode::Directory) { // Iterate through directory dir = std::static_pointer_cast<VirtualDirectory>(child); } else if (child->type == VirtualNode::MountedDevice || child->type == VirtualNode::File) { // Cannot iterate through device or a file return { child, Path { ++itr, path.end() } }; } } return { dir, Path { itr, path.end() } }; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_device.h ================================================ #pragma once #include "vfs_device.h" #include "vfs_virtual_directory.h" #include <memory> namespace vfs { class VirtualDevice : public Device, public std::enable_shared_from_this<VirtualDevice> { public: VirtualDevice(std::string rootDeviceName = {}); ~VirtualDevice() override = default; Result<std::shared_ptr<Device>> getLinkDevice(const User &user, const Path &path) override; Error makeFolder(const User &user, const Path &path) override; Error makeFolders(const User &user, const Path &path) override; Error mountDevice(const User &user, const Path &path, std::shared_ptr<Device> device) override; Error mountOverlayDevice(const User &user, OverlayPriority priority, const Path &path, std::shared_ptr<Device> device) override; Error unmountDevice(const User &user, const Path &path) override; Error unmountOverlayDevice(const User &user, OverlayPriority priority, const Path &path) override; Result<DirectoryIterator> openDirectory(const User &user, const Path &path) override; Result<std::unique_ptr<FileHandle>> openFile(const User &user, const Path &path, FileHandle::Mode mode) override; Error remove(const User &user, const Path &path) override; Error rename(const User &user, const Path &src, const Path &dst) override; Error setGroup(const User &user, const Path &path, GroupId group) override; Error setOwner(const User &user, const Path &path, OwnerId owner) override; Error setPermissions(const User &user, const Path &path, Permissions mode) override; Result<Status> status(const User &user, const Path &path) override; private: std::pair<std::shared_ptr<VirtualNode>, Path> findDeepest(const User &user, Path path, int parentLevel = 0); private: std::string mRootDeviceName; std::shared_ptr<VirtualDirectory> mRoot; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_directory.h ================================================ #pragma once #include "vfs_virtual_node.h" #include <map> #include <memory> #include <string> namespace vfs { struct VirtualDirectory : public VirtualNode { using child_map = std::map<std::string, std::shared_ptr<VirtualNode>>; using iterator = child_map::iterator; using const_iterator = child_map::const_iterator; VirtualDirectory() : VirtualNode(VirtualNode::Directory) { } iterator begin() { return children.begin(); } iterator end() { return children.end(); } child_map children; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_directoryiterator.cpp ================================================ #include "vfs_virtual_directory.h" #include "vfs_virtual_directoryiterator.h" #include "vfs_virtual_file.h" #include <system_error> namespace vfs { VirtualDirectoryIterator::VirtualDirectoryIterator(std::shared_ptr<VirtualDirectory> directory) : mDirectory(std::move(directory)), mIterator(mDirectory->begin()) { } Result<Status> VirtualDirectoryIterator::readEntry() { if (mIterator == mDirectory->end()) { return Error::EndOfDirectory; } auto ec = std::error_code { }; auto entry = Status{ }; entry.name = mIterator->first; auto node = mIterator->second.get(); if (node->type == VirtualNode::File) { auto file = static_cast<VirtualFile *>(node); entry.size = file->data.size(); entry.flags = Status::HasSize; } if (node->type == VirtualNode::Directory) { entry.flags = Status::IsDirectory; } entry.group = node->group; entry.owner = node->owner; entry.permission = node->permission; entry.flags = Status::HasPermissions; ++mIterator; return entry; } Error VirtualDirectoryIterator::rewind() { mIterator = mDirectory->begin(); return Error::Success; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_directoryiterator.h ================================================ #pragma once #include "vfs_directoryiterator.h" #include "vfs_virtual_directory.h" #include <memory> namespace vfs { class VirtualDirectoryIterator : public DirectoryIteratorImpl { public: VirtualDirectoryIterator(std::shared_ptr<VirtualDirectory> directory); ~VirtualDirectoryIterator() override = default; Result<Status> readEntry() override; Error rewind() override; private: std::shared_ptr<VirtualDirectory> mDirectory; VirtualDirectory::iterator mIterator; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_file.h ================================================ #pragma once #include "vfs_virtual_node.h" #include <cstdint> #include <vector> namespace vfs { struct VirtualFile : public VirtualNode { VirtualFile() : VirtualNode(VirtualNode::File) { } ~VirtualFile() override = default; std::vector<uint8_t> data; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_filehandle.cpp ================================================ #include "vfs_virtual_file.h" #include "vfs_virtual_filehandle.h" #include <algorithm> #include <cstring> namespace vfs { VirtualFileHandle::VirtualFileHandle(std::shared_ptr<VirtualFile> file, Mode mode) : mFile(std::move(file)), mPosition(0), mMode(mode) { } VirtualFileHandle::~VirtualFileHandle() { close(); } Error VirtualFileHandle::close() { mFile.reset(); return Error::Success; } Result<bool> VirtualFileHandle::eof() { if (!mFile) { return { Error::NotOpen }; } return mPosition >= static_cast<int64_t>(mFile->data.size()); } Error VirtualFileHandle::flush() { if (!mFile) { return Error::NotOpen; } return Error::Success; } Error VirtualFileHandle::seek(SeekDirection direction, int64_t offset) { if (!mFile) { return Error::NotOpen; } auto oldPosition = mPosition; switch (direction) { case SeekCurrent: mPosition += offset; break; case SeekEnd: mPosition = static_cast<int64_t>(mFile->data.size()) + offset; break; case SeekStart: mPosition = offset; break; default: return Error::InvalidSeekDirection; } if (mPosition < 0) { // Cannot seek before start of file! mPosition = oldPosition; return Error::InvalidSeekPosition; } return Error::Success; } Result<int64_t> VirtualFileHandle::size() { if (!mFile) { return { Error::NotOpen }; } return { static_cast<int64_t>(mFile->data.size()) }; } Result<int64_t> VirtualFileHandle::tell() { if (!mFile) { return { Error::NotOpen }; } return { mPosition }; } Result<int64_t> VirtualFileHandle::truncate() { if (!mFile) { return { Error::NotOpen }; } mFile->data.resize(static_cast<size_t>(mPosition)); return { mPosition }; } Result<int64_t> VirtualFileHandle::read(void *buffer, int64_t size, int64_t count) { if (!mFile) { return { Error::NotOpen }; } if (mPosition >= static_cast<int64_t>(mFile->data.size())) { return { Error::EndOfFile }; } auto groupsRemaining = (static_cast<int64_t>(mFile->data.size()) - mPosition) / size; count = std::min(count, groupsRemaining); if (count == 0) { return { 0 }; } std::memcpy(buffer, mFile->data.data() + mPosition, size * count); mPosition += size * count; return { count }; } Result<int64_t> VirtualFileHandle::write(const void *buffer, int64_t size, int64_t count) { if (!mFile) { return { Error::NotOpen }; } if (mMode & Append) { mPosition = static_cast<int64_t>(mFile->data.size()); } auto endPosition = mPosition + size * count; if (endPosition > static_cast<int64_t>(mFile->data.size())) { mFile->data.resize(static_cast<size_t>(endPosition)); } std::memcpy(mFile->data.data() + mPosition, buffer, size * count); mPosition += size * count; return { count }; } } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_filehandle.h ================================================ #pragma once #include "vfs_filehandle.h" #include <memory> namespace vfs { struct VirtualFile; class VirtualFileHandle : public FileHandle { public: VirtualFileHandle(std::shared_ptr<VirtualFile> file, Mode mode); ~VirtualFileHandle() override; Error close() override; Result<bool> eof() override; Error flush() override; Error seek(SeekDirection direction, int64_t offset) override; Result<int64_t> size() override; Result<int64_t> tell() override; Result<int64_t> truncate() override; Result<int64_t> read(void *buffer, int64_t size, int64_t count) override; Result<int64_t> write(const void *buffer, int64_t size, int64_t count) override; private: std::shared_ptr<VirtualFile> mFile; int64_t mPosition; Mode mMode; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_mounteddevice.h ================================================ #pragma once #include "vfs_virtual_node.h" #include <memory> namespace vfs { class Device; struct VirtualMountedDevice : public VirtualNode { VirtualMountedDevice(std::shared_ptr<Device> device) : VirtualNode(VirtualNode::MountedDevice), device(std::move(device)) { } ~VirtualMountedDevice() override = default; std::shared_ptr<Device> device; }; } // namespace vfs ================================================ FILE: src/libdecaf/src/vfs/vfs_virtual_node.h ================================================ #pragma once #include "vfs_permissions.h" namespace vfs { struct VirtualNode { enum Type { File, Directory, MountedDevice, }; VirtualNode(Type type) : type(type) { } virtual ~VirtualNode() = default; Type type; GroupId group = 0; OwnerId owner = 0; Permissions permission = Permissions::OtherWrite | Permissions::OtherRead; }; } // namespace vfs ================================================ FILE: src/libgfd/CMakeLists.txt ================================================ project(libgfd) include_directories(".") include_directories("src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_library(libgfd STATIC ${SOURCE_FILES} ${HEADER_FILES}) GroupSources("Source Files" src) target_link_libraries(libgfd common) ================================================ FILE: src/libgfd/gfd.h ================================================ #pragma once #include "gfd_enum.h" #include "gfd_gx2.h" #include <cstdint> #include <stdexcept> #include <vector> namespace gfd { struct GFDFileHeader { static constexpr uint32_t Magic = 0x47667832; static constexpr uint32_t HeaderSize = 8 * 4; uint32_t magic; uint32_t headerSize; uint32_t majorVersion; uint32_t minorVersion; uint32_t gpuVersion; uint32_t align; uint32_t unk1; uint32_t unk2; }; struct GFDBlockHeader { static constexpr uint32_t Magic = 0x424C4B7B; static constexpr uint32_t HeaderSize = 8 * 4; uint32_t magic; uint32_t headerSize; uint32_t majorVersion; uint32_t minorVersion; GFDBlockType type; uint32_t dataSize; uint32_t id; uint32_t index; }; struct GFDRelocationHeader { static constexpr uint32_t Magic = 0x7D424C4B; static constexpr uint32_t HeaderSize = 10 * 4; uint32_t magic; uint32_t headerSize; uint32_t unk1; uint32_t dataSize; uint32_t dataOffset; uint32_t textSize; uint32_t textOffset; uint32_t patchBase; uint32_t patchCount; uint32_t patchOffset; }; struct GFDBlockRelocations { GFDRelocationHeader header; std::vector<uint32_t> patches; }; struct GFDBlock { GFDBlockHeader header; std::vector<uint8_t> data; }; struct GFDFile { std::vector<GFDVertexShader> vertexShaders; std::vector<GFDPixelShader> pixelShaders; std::vector<GFDGeometryShader> geometryShaders; std::vector<GFDTexture> textures; }; static constexpr uint32_t GFDFileMajorVersion = 7u; static constexpr uint32_t GFDFileMinorVersion = 1u; static constexpr uint32_t GFDFileGpuVersion = 2u; static constexpr uint32_t GFDBlockMajorVersion = 1u; static constexpr uint32_t GFDPatchMask = 0xFFF00000u; static constexpr uint32_t GFDPatchData = 0xD0600000u; static constexpr uint32_t GFDPatchText = 0xCA700000u; struct GFDReadException : public std::runtime_error { public: GFDReadException(const std::string &msg) : std::runtime_error(msg) { } }; bool readFile(GFDFile &file, const std::string &path); bool writeFile(const GFDFile &file, const std::string &path, bool align = false); } // namespace gfd ================================================ FILE: src/libgfd/gfd_enum.h ================================================ #ifndef GFD_ENUM_H #define GFD_ENUM_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(gfd) ENUM_BEG(GFDBlockType, uint32_t) ENUM_VALUE(EndOfFile, 1) ENUM_VALUE(Padding, 2) ENUM_VALUE(VertexShaderHeader, 3) ENUM_VALUE(VertexShaderProgram, 5) ENUM_VALUE(PixelShaderHeader, 6) ENUM_VALUE(PixelShaderProgram, 7) ENUM_VALUE(GeometryShaderHeader, 8) ENUM_VALUE(GeometryShaderProgram, 9) ENUM_VALUE(GeometryShaderCopyProgram, 10) ENUM_VALUE(TextureHeader, 11) ENUM_VALUE(TextureImage, 12) ENUM_VALUE(TextureMipmap, 13) ENUM_VALUE(ComputeShaderHeader, 14) ENUM_VALUE(ComputeShaderProgram, 15) ENUM_END(GFDBlockType) ENUM_NAMESPACE_EXIT(gfd) #include <common/enum_end.inl> #endif // ifdef GFD_ENUM_H ================================================ FILE: src/libgfd/gfd_gx2.h ================================================ #pragma once #include <array> #include <cstdint> #include <libdecaf/src/cafe/libraries/gx2/gx2_enum.h> #include <libgpu/latte/latte_registers.h> #include <string> #include <vector> namespace gfd { using cafe::gx2::GX2AAMode; using cafe::gx2::GX2FetchShaderType; using cafe::gx2::GX2RResourceFlags; using cafe::gx2::GX2RResourceFlags; using cafe::gx2::GX2SamplerVarType; using cafe::gx2::GX2ShaderMode; using cafe::gx2::GX2ShaderVarType; using cafe::gx2::GX2SurfaceDim; using cafe::gx2::GX2SurfaceFormat; using cafe::gx2::GX2SurfaceUse; using cafe::gx2::GX2TileMode; struct GFDSurface { GX2SurfaceDim dim; uint32_t width; uint32_t height; uint32_t depth; uint32_t mipLevels; GX2SurfaceFormat format; GX2AAMode aa; union { GX2SurfaceUse use; GX2RResourceFlags resourceFlags; }; std::vector<uint8_t> image; std::vector<uint8_t> mipmap; GX2TileMode tileMode; uint32_t swizzle; uint32_t alignment; uint32_t pitch; std::array<uint32_t, 13> mipLevelOffset; }; struct GFDTexture { GFDSurface surface; uint32_t viewFirstMip; uint32_t viewNumMips; uint32_t viewFirstSlice; uint32_t viewNumSlices; uint32_t compMap; struct { latte::SQ_TEX_RESOURCE_WORD0_N word0; latte::SQ_TEX_RESOURCE_WORD1_N word1; latte::SQ_TEX_RESOURCE_WORD4_N word4; latte::SQ_TEX_RESOURCE_WORD5_N word5; latte::SQ_TEX_RESOURCE_WORD6_N word6; } regs; }; struct GFDFetchShader { GX2FetchShaderType type; struct { latte::SQ_PGM_RESOURCES_FS sq_pgm_resources_fs; } regs; uint32_t size; uint8_t *data; uint32_t attribCount; uint32_t numDivisors; uint32_t divisors[2]; }; struct GFDUniformVar { std::string name; GX2ShaderVarType type; uint32_t count; uint32_t offset; int32_t block; }; struct GFDUniformInitialValue { std::array<float, 4> value; uint32_t offset; }; struct GFDUniformBlock { std::string name; uint32_t offset; uint32_t size; }; struct GFDAttribVar { std::string name; GX2ShaderVarType type; uint32_t count; uint32_t location; }; struct GFDSamplerVar { std::string name; GX2SamplerVarType type; uint32_t location; }; struct GFDLoopVar { uint32_t offset; uint32_t value; }; struct GFDRBuffer { GX2RResourceFlags flags; uint32_t elemSize; uint32_t elemCount; std::vector<uint8_t> buffer; }; struct GFDVertexShader { struct { latte::SQ_PGM_RESOURCES_VS sq_pgm_resources_vs; latte::VGT_PRIMITIVEID_EN vgt_primitiveid_en; latte::SPI_VS_OUT_CONFIG spi_vs_out_config; uint32_t num_spi_vs_out_id; std::array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_id; latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl; latte::SQ_VTX_SEMANTIC_CLEAR sq_vtx_semantic_clear; uint32_t num_sq_vtx_semantic; std::array<latte::SQ_VTX_SEMANTIC_N, 32> sq_vtx_semantic; latte::VGT_STRMOUT_BUFFER_EN vgt_strmout_buffer_en; latte::VGT_VERTEX_REUSE_BLOCK_CNTL vgt_vertex_reuse_block_cntl; latte::VGT_HOS_REUSE_DEPTH vgt_hos_reuse_depth; } regs; std::vector<uint8_t> data; GX2ShaderMode mode; std::vector<GFDUniformBlock> uniformBlocks; std::vector<GFDUniformVar> uniformVars; std::vector<GFDUniformInitialValue> initialValues; std::vector<GFDLoopVar> loopVars; std::vector<GFDSamplerVar> samplerVars; std::vector<GFDAttribVar> attribVars; uint32_t ringItemSize; bool hasStreamOut; std::array<uint32_t, 4> streamOutStride; GFDRBuffer gx2rData; }; struct GFDPixelShader { struct { latte::SQ_PGM_RESOURCES_PS sq_pgm_resources_ps; latte::SQ_PGM_EXPORTS_PS sq_pgm_exports_ps; latte::SPI_PS_IN_CONTROL_0 spi_ps_in_control_0; latte::SPI_PS_IN_CONTROL_1 spi_ps_in_control_1; uint32_t num_spi_ps_input_cntl; std::array<latte::SPI_PS_INPUT_CNTL_N, 32> spi_ps_input_cntls; latte::CB_SHADER_MASK cb_shader_mask; latte::CB_SHADER_CONTROL cb_shader_control; latte::DB_SHADER_CONTROL db_shader_control; latte::SPI_INPUT_Z spi_input_z; } regs; std::vector<uint8_t> data; GX2ShaderMode mode; std::vector<GFDUniformBlock> uniformBlocks; std::vector<GFDUniformVar> uniformVars; std::vector<GFDUniformInitialValue> initialValues; std::vector<GFDLoopVar> loopVars; std::vector<GFDSamplerVar> samplerVars; GFDRBuffer gx2rData; }; struct GFDGeometryShader { struct { latte::SQ_PGM_RESOURCES_GS sq_pgm_resources_gs; latte::VGT_GS_OUT_PRIM_TYPE vgt_gs_out_prim_type; latte::VGT_GS_MODE vgt_gs_mode; latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl; latte::SQ_PGM_RESOURCES_VS sq_pgm_resources_vs; latte::SQ_GS_VERT_ITEMSIZE sq_gs_vert_itemsize; latte::SPI_VS_OUT_CONFIG spi_vs_out_config; uint32_t num_spi_vs_out_id; std::array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_id; latte::VGT_STRMOUT_BUFFER_EN vgt_strmout_buffer_en; } regs; std::vector<uint8_t> data; std::vector<uint8_t> vertexShaderData; GX2ShaderMode mode; std::vector<GFDUniformBlock> uniformBlocks; std::vector<GFDUniformVar> uniformVars; std::vector<GFDUniformInitialValue> initialValues; std::vector<GFDLoopVar> loopVars; std::vector<GFDSamplerVar> samplerVars; uint32_t ringItemSize; bool hasStreamOut; std::array<uint32_t, 4> streamOutStride; GFDRBuffer gx2rData; GFDRBuffer gx2rVertexShaderData; }; } // namespace gfd ================================================ FILE: src/libgfd/src/gfd_read.cpp ================================================ #include "gfd.h" #include <common/byte_swap.h> #include <fstream> #include <fmt/core.h> #include <string> namespace gfd { struct MemoryFile { bool eof() { return pos >= data.size(); } uint32_t pos = 0; std::vector<uint8_t> data; }; static bool openFile(const std::string &path, MemoryFile & file) { std::ifstream fh { path, std::ifstream::binary }; if (!fh.is_open()) { return false; } fh.seekg(0, std::istream::end); file.data.resize(fh.tellg()); fh.seekg(0); fh.read(reinterpret_cast<char *>(file.data.data()), file.data.size()); file.pos = 0; return true; } template<typename Type> inline Type read(MemoryFile &fh) { if (fh.pos + sizeof(Type) > fh.data.size()) { throw GFDReadException { "Tried to read past end of file" }; } auto value = byte_swap(*reinterpret_cast<Type *>(fh.data.data() + fh.pos)); fh.pos += sizeof(Type); return value; } inline void readBinary(MemoryFile &fh, std::vector<uint8_t> &value, uint32_t size) { if (fh.pos + size > fh.data.size()) { throw GFDReadException { "Tried to read past end of file" }; } value.resize(size); std::memcpy(value.data(), fh.data.data() + fh.pos, size); fh.pos += size; } static bool readFileHeader(MemoryFile &fh, GFDFileHeader &header) { header.magic = read<uint32_t>(fh); if (header.magic != GFDFileHeader::Magic) { throw GFDReadException { fmt::format("Unexpected file magic {:X}", header.magic) }; } header.headerSize = read<uint32_t>(fh); if (header.headerSize != GFDFileHeader::HeaderSize) { throw GFDReadException { fmt::format("Unexpected file header size {}", header.headerSize) }; } header.majorVersion = read<uint32_t>(fh); if (header.majorVersion != GFDFileMajorVersion) { throw GFDReadException { fmt::format("Unsupported file version {}.{}.{}", header.majorVersion, header.minorVersion, header.gpuVersion) }; } header.minorVersion = read<uint32_t>(fh); header.gpuVersion = read<uint32_t>(fh); header.align = read<uint32_t>(fh); header.unk1 = read<uint32_t>(fh); header.unk2 = read<uint32_t>(fh); return true; } static bool readBlockHeader(MemoryFile &fh, GFDBlockHeader &header) { if (fh.eof()) { return false; } header.magic = read<uint32_t>(fh); if (header.magic != GFDBlockHeader::Magic) { throw GFDReadException { fmt::format("Unexpected block magic {:X}", header.magic) }; } header.headerSize = read<uint32_t>(fh); if (header.headerSize != GFDBlockHeader::HeaderSize) { throw GFDReadException { fmt::format("Unexpected block header size {:X}", header.headerSize) }; } header.majorVersion = read<uint32_t>(fh); header.minorVersion = read<uint32_t>(fh); header.type = static_cast<GFDBlockType>(read<uint32_t>(fh)); header.dataSize = read<uint32_t>(fh); header.id = read<uint32_t>(fh); header.index = read<uint32_t>(fh); return true; } static bool readRelocationHeader(MemoryFile &fh, GFDRelocationHeader &header) { header.magic = read<uint32_t>(fh); if (header.magic != GFDRelocationHeader::Magic) { return false; } header.headerSize = read<uint32_t>(fh); if (header.headerSize != GFDRelocationHeader::HeaderSize) { throw GFDReadException { fmt::format("Unexpected relocation header size {:X}", header.headerSize) }; } header.unk1 = read<uint32_t>(fh); header.dataSize = read<uint32_t>(fh); header.dataOffset = read<uint32_t>(fh); header.textSize = read<uint32_t>(fh); header.textOffset = read<uint32_t>(fh); header.patchBase = read<uint32_t>(fh); header.patchCount = read<uint32_t>(fh); header.patchOffset = read<uint32_t>(fh); return true; } static bool readBlockRelocations(MemoryFile &fh, uint32_t blockBase, uint32_t blockSize, GFDBlockRelocations &block) { fh.pos = blockBase + blockSize - GFDRelocationHeader::HeaderSize; if (!readRelocationHeader(fh, block.header)) { return false; } fh.pos = blockBase + (block.header.patchOffset & ~GFDPatchMask); for (auto i = 0u; i < block.header.patchCount; ++i) { block.patches.push_back(read<uint32_t>(fh)); } return true; } static bool checkRelocation(const GFDBlockRelocations &relocations, uint32_t pos) { for (auto &patch : relocations.patches) { if (pos == (patch & ~GFDPatchMask)) { return true; } } return false; } static std::string readString(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations) { auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!checkRelocation(relocations, fh.pos - 4 - blockBase)) { return { }; } return reinterpret_cast<const char *>(fh.data.data() + blockBase + offset); } static bool readUniformBlocks(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations, std::vector<GFDUniformBlock> &uniformBlocks) { auto pos = fh.pos; auto count = read<uint32_t>(fh); auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!count) { return true; } decaf_check(offset); decaf_check(checkRelocation(relocations, pos + 4 - blockBase)); fh.pos = blockBase + offset; uniformBlocks.resize(count); for (auto &block : uniformBlocks) { block.name = readString(fh, blockBase, relocations); block.offset = read<uint32_t>(fh); block.size = read<uint32_t>(fh); } fh.pos = pos + 8; return true; } static bool readUniformVars(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations, std::vector<GFDUniformVar> &uniformVars) { auto pos = fh.pos; auto count = read<uint32_t>(fh); auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!count) { return true; } decaf_check(offset); decaf_check(checkRelocation(relocations, pos + 4 - blockBase)); fh.pos = blockBase + offset; uniformVars.resize(count); for (auto &var : uniformVars) { var.name = readString(fh, blockBase, relocations); var.type = static_cast<GX2ShaderVarType>(read<uint32_t>(fh)); var.count = read<uint32_t>(fh); var.offset = read<uint32_t>(fh); var.block = read<int32_t>(fh); } fh.pos = pos + 8; return true; } static bool readInitialValues(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations, std::vector<GFDUniformInitialValue> &initialValues) { auto pos = fh.pos; auto count = read<uint32_t>(fh); auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!count) { return true; } decaf_check(offset); decaf_check(checkRelocation(relocations, pos + 4 - blockBase)); fh.pos = blockBase + offset; initialValues.resize(count); for (auto &var : initialValues) { for (auto &value : var.value) { value = read<float>(fh); } var.offset = read<uint32_t>(fh); } fh.pos = pos + 8; return true; } static bool readLoopVars(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations, std::vector<GFDLoopVar> &loopVars) { auto pos = fh.pos; auto count = read<uint32_t>(fh); auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!count) { return true; } decaf_check(offset); decaf_check(checkRelocation(relocations, pos + 4 - blockBase)); fh.pos = blockBase + offset; loopVars.resize(count); for (auto &var : loopVars) { var.offset = read<uint32_t>(fh); var.value = read<uint32_t>(fh); } fh.pos = pos + 8; return true; } static bool readSamplerVars(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations, std::vector<GFDSamplerVar> &samplerVars) { auto pos = fh.pos; auto count = read<uint32_t>(fh); auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!count) { return true; } decaf_check(offset); decaf_check(checkRelocation(relocations, pos + 4 - blockBase)); fh.pos = blockBase + offset; samplerVars.resize(count); for (auto &var : samplerVars) { var.name = readString(fh, blockBase, relocations); var.type = static_cast<GX2SamplerVarType>(read<uint32_t>(fh)); var.location = read<uint32_t>(fh); } fh.pos = pos + 8; return true; } static bool readAttribVars(MemoryFile &fh, uint32_t blockBase, const GFDBlockRelocations &relocations, std::vector<GFDAttribVar> &attribVars) { auto pos = fh.pos; auto count = read<uint32_t>(fh); auto offset = read<uint32_t>(fh) & ~GFDPatchMask; if (!count) { return true; } decaf_check(offset); decaf_check(checkRelocation(relocations, pos + 4 - blockBase)); fh.pos = blockBase + offset; attribVars.resize(count); for (auto &var : attribVars) { var.name = readString(fh, blockBase, relocations); var.type = static_cast<GX2ShaderVarType>(read<uint32_t>(fh)); var.count = read<uint32_t>(fh); var.location = read<uint32_t>(fh); } fh.pos = pos + 8; return true; } static bool readGx2rBuffer(MemoryFile &fh, GFDRBuffer &gx2r) { gx2r.flags = static_cast<GX2RResourceFlags>(read<uint32_t>(fh)); gx2r.elemSize = read<uint32_t>(fh); gx2r.elemCount = read<uint32_t>(fh); decaf_check(read<uint32_t>(fh) == 0); return true; } static bool readVertexShaderHeader(MemoryFile &fh, GFDBlockHeader &block, GFDVertexShader &vsh) { GFDBlockRelocations relocations; auto blockBase = fh.pos; if (!readBlockRelocations(fh, blockBase, block.dataSize, relocations)) { return false; } fh.pos = blockBase; if (block.majorVersion == 1) { vsh.regs.sq_pgm_resources_vs = latte::SQ_PGM_RESOURCES_VS::get(read<uint32_t>(fh)); vsh.regs.vgt_primitiveid_en = latte::VGT_PRIMITIVEID_EN::get(read<uint32_t>(fh)); vsh.regs.spi_vs_out_config = latte::SPI_VS_OUT_CONFIG::get(read<uint32_t>(fh)); vsh.regs.num_spi_vs_out_id = read<uint32_t>(fh); for (auto i = 0u; i < vsh.regs.spi_vs_out_id.size(); ++i) { vsh.regs.spi_vs_out_id[i] = latte::SPI_VS_OUT_ID_N::get(read<uint32_t>(fh)); } vsh.regs.pa_cl_vs_out_cntl = latte::PA_CL_VS_OUT_CNTL::get(read<uint32_t>(fh)); vsh.regs.sq_vtx_semantic_clear = latte::SQ_VTX_SEMANTIC_CLEAR::get(read<uint32_t>(fh)); vsh.regs.num_sq_vtx_semantic = read<uint32_t>(fh); for (auto i = 0u; i < vsh.regs.sq_vtx_semantic.size(); ++i) { vsh.regs.sq_vtx_semantic[i] = latte::SQ_VTX_SEMANTIC_N::get(read<uint32_t>(fh)); } vsh.regs.vgt_strmout_buffer_en = latte::VGT_STRMOUT_BUFFER_EN::get(read<uint32_t>(fh)); vsh.regs.vgt_vertex_reuse_block_cntl = latte::VGT_VERTEX_REUSE_BLOCK_CNTL::get(read<uint32_t>(fh)); vsh.regs.vgt_hos_reuse_depth = latte::VGT_HOS_REUSE_DEPTH::get(read<uint32_t>(fh)); decaf_check(fh.pos - blockBase == 0xD0); } else { throw GFDReadException { fmt::format("Unsupported VertexShaderHeader version {}.{}", block.majorVersion, block.minorVersion) }; } vsh.data.reserve(read<uint32_t>(fh)); decaf_check(read<uint32_t>(fh) == 0); // vsh.data vsh.mode = static_cast<GX2ShaderMode>(read<uint32_t>(fh)); readUniformBlocks(fh, blockBase, relocations, vsh.uniformBlocks); readUniformVars(fh, blockBase, relocations, vsh.uniformVars); readInitialValues(fh, blockBase, relocations, vsh.initialValues); readLoopVars(fh, blockBase, relocations, vsh.loopVars); readSamplerVars(fh, blockBase, relocations, vsh.samplerVars); readAttribVars(fh, blockBase, relocations, vsh.attribVars); vsh.ringItemSize = read<uint32_t>(fh); vsh.hasStreamOut = read<uint32_t>(fh) ? true : false; for (auto &stride : vsh.streamOutStride) { stride = read<uint32_t>(fh); } readGx2rBuffer(fh, vsh.gx2rData); if (block.majorVersion == 1) { decaf_check(fh.pos - blockBase == 0x134); } fh.pos = blockBase + block.dataSize; return true; } static bool readVertexShaderProgram(MemoryFile &fh, GFDBlockHeader &block, GFDVertexShader &vsh) { if (block.dataSize != vsh.data.capacity()) { throw GFDReadException { fmt::format("VertexShaderProgram block.dataSize {} != vsh.size {}", block.dataSize, vsh.data.capacity()) }; } readBinary(fh, vsh.data, block.dataSize); return true; } static bool readPixelShaderHeader(MemoryFile &fh, GFDBlockHeader &block, GFDPixelShader &psh) { GFDBlockRelocations relocations; auto blockBase = fh.pos; if (!readBlockRelocations(fh, blockBase, block.dataSize, relocations)) { return false; } if (block.majorVersion != 0 && block.majorVersion != 1) { throw GFDReadException { fmt::format("Unsupported PixelShaderHeader version {}.{}", block.majorVersion, block.minorVersion) }; } fh.pos = blockBase; psh.regs.sq_pgm_resources_ps = latte::SQ_PGM_RESOURCES_PS::get(read<uint32_t>(fh)); psh.regs.sq_pgm_exports_ps = latte::SQ_PGM_EXPORTS_PS::get(read<uint32_t>(fh)); psh.regs.spi_ps_in_control_0 = latte::SPI_PS_IN_CONTROL_0::get(read<uint32_t>(fh)); psh.regs.spi_ps_in_control_1 = latte::SPI_PS_IN_CONTROL_1::get(read<uint32_t>(fh)); psh.regs.num_spi_ps_input_cntl = read<uint32_t>(fh); for (auto &spi_ps_input_cntl : psh.regs.spi_ps_input_cntls) { spi_ps_input_cntl = latte::SPI_PS_INPUT_CNTL_N::get(read<uint32_t>(fh)); } psh.regs.cb_shader_mask = latte::CB_SHADER_MASK::get(read<uint32_t>(fh)); psh.regs.cb_shader_control = latte::CB_SHADER_CONTROL::get(read<uint32_t>(fh)); psh.regs.db_shader_control = latte::DB_SHADER_CONTROL::get(read<uint32_t>(fh)); psh.regs.spi_input_z = latte::SPI_INPUT_Z::get(read<uint32_t>(fh)); psh.data.reserve(read<uint32_t>(fh)); decaf_check(read<uint32_t>(fh) == 0); // psh.data psh.mode = static_cast<GX2ShaderMode>(read<uint32_t>(fh)); readUniformBlocks(fh, blockBase, relocations, psh.uniformBlocks); readUniformVars(fh, blockBase, relocations, psh.uniformVars); readInitialValues(fh, blockBase, relocations, psh.initialValues); readLoopVars(fh, blockBase, relocations, psh.loopVars); readSamplerVars(fh, blockBase, relocations, psh.samplerVars); if (block.majorVersion == 0) { std::memset(&psh.gx2rData, 0, sizeof(GFDRBuffer)); decaf_check(fh.pos - blockBase == 0xD8); } else if (block.majorVersion == 1) { readGx2rBuffer(fh, psh.gx2rData); decaf_check(fh.pos - blockBase == 0xE8); } fh.pos = blockBase + block.dataSize; return true; } static bool readPixelShaderProgram(MemoryFile &fh, GFDBlockHeader &block, GFDPixelShader &psh) { if (block.dataSize != psh.data.capacity()) { throw GFDReadException { fmt::format("PixelShaderProgram block.dataSize {} != vsh.size {}", block.dataSize, psh.data.capacity()) }; } readBinary(fh, psh.data, block.dataSize); return true; } static bool readGeometryShaderHeader(MemoryFile &fh, GFDBlockHeader &block, GFDGeometryShader &gsh) { GFDBlockRelocations relocations; auto blockBase = fh.pos; if (!readBlockRelocations(fh, blockBase, block.dataSize, relocations)) { return false; } if (block.majorVersion != 1) { throw GFDReadException { fmt::format("Unsupported GeometryShaderHeader version {}.{}", block.majorVersion, block.minorVersion) }; } fh.pos = blockBase; gsh.regs.sq_pgm_resources_gs = latte::SQ_PGM_RESOURCES_GS::get(read<uint32_t>(fh)); gsh.regs.vgt_gs_out_prim_type = latte::VGT_GS_OUT_PRIM_TYPE::get(read<uint32_t>(fh)); gsh.regs.vgt_gs_mode = latte::VGT_GS_MODE::get(read<uint32_t>(fh)); gsh.regs.pa_cl_vs_out_cntl = latte::PA_CL_VS_OUT_CNTL::get(read<uint32_t>(fh)); gsh.regs.sq_pgm_resources_vs = latte::SQ_PGM_RESOURCES_VS::get(read<uint32_t>(fh)); gsh.regs.sq_gs_vert_itemsize = latte::SQ_GS_VERT_ITEMSIZE::get(read<uint32_t>(fh)); gsh.regs.spi_vs_out_config = latte::SPI_VS_OUT_CONFIG::get(read<uint32_t>(fh)); gsh.regs.num_spi_vs_out_id = read<uint32_t>(fh); for (auto &spi_vs_out_id : gsh.regs.spi_vs_out_id) { spi_vs_out_id = latte::SPI_VS_OUT_ID_N::get(read<uint32_t>(fh)); } gsh.regs.vgt_strmout_buffer_en = latte::VGT_STRMOUT_BUFFER_EN::get(read<uint32_t>(fh)); gsh.data.reserve(read<uint32_t>(fh)); decaf_check(read<uint32_t>(fh) == 0); // psh.data gsh.vertexShaderData.reserve(read<uint32_t>(fh)); decaf_check(read<uint32_t>(fh) == 0); // psh.vertexShaderData gsh.mode = static_cast<GX2ShaderMode>(read<uint32_t>(fh)); readUniformBlocks(fh, blockBase, relocations, gsh.uniformBlocks); readUniformVars(fh, blockBase, relocations, gsh.uniformVars); readInitialValues(fh, blockBase, relocations, gsh.initialValues); readLoopVars(fh, blockBase, relocations, gsh.loopVars); readSamplerVars(fh, blockBase, relocations, gsh.samplerVars); gsh.ringItemSize = read<uint32_t>(fh); gsh.hasStreamOut = read<uint32_t>(fh) ? true : false; for (auto &stride : gsh.streamOutStride) { stride = read<uint32_t>(fh); } readGx2rBuffer(fh, gsh.gx2rData); readGx2rBuffer(fh, gsh.gx2rVertexShaderData); if (block.majorVersion == 1) { decaf_check(fh.pos - blockBase == 0xC0); } fh.pos = blockBase + block.dataSize; return true; } static bool readGeometryShaderProgram(MemoryFile &fh, GFDBlockHeader &block, GFDGeometryShader &gsh) { if (block.dataSize != gsh.data.capacity()) { throw GFDReadException { fmt::format("GeometryShaderProgram block.dataSize {} != gsh.size {}", block.dataSize, gsh.data.capacity()) }; } readBinary(fh, gsh.data, block.dataSize); return true; } static bool readGeometryShaderCopyProgram(MemoryFile &fh, GFDBlockHeader &block, GFDGeometryShader &gsh) { if (block.dataSize != gsh.vertexShaderData.capacity()) { throw GFDReadException { fmt::format("GeometryShaderCopyProgram block.dataSize {} != gsh.vertexShaderSize {}", block.dataSize, gsh.vertexShaderData.capacity()) }; } readBinary(fh, gsh.vertexShaderData, block.dataSize); return true; } static bool readTextureHeader(MemoryFile &fh, GFDBlockHeader &block, GFDTexture &tex) { auto blockBase = fh.pos; if (block.majorVersion != 1) { throw GFDReadException { fmt::format("Unsupported TextureHeader version {}.{}", block.majorVersion, block.minorVersion) }; } tex.surface.dim = static_cast<GX2SurfaceDim>(read<uint32_t>(fh)); tex.surface.width = read<uint32_t>(fh); tex.surface.height = read<uint32_t>(fh); tex.surface.depth = read<uint32_t>(fh); tex.surface.mipLevels = read<uint32_t>(fh); tex.surface.format = static_cast<GX2SurfaceFormat>(read<uint32_t>(fh)); tex.surface.aa = static_cast<GX2AAMode>(read<uint32_t>(fh)); tex.surface.use = static_cast<GX2SurfaceUse>(read<uint32_t>(fh)); tex.surface.image.reserve(read<uint32_t>(fh)); decaf_check(read<uint32_t>(fh) == 0); // tex.surface.image tex.surface.mipmap.reserve(read<uint32_t>(fh)); decaf_check(read<uint32_t>(fh) == 0); // tex.surface.mipmap tex.surface.tileMode = static_cast<GX2TileMode>(read<uint32_t>(fh)); tex.surface.swizzle = read<uint32_t>(fh); tex.surface.alignment = read<uint32_t>(fh); tex.surface.pitch = read<uint32_t>(fh); for (auto &mipLevelOffset : tex.surface.mipLevelOffset) { mipLevelOffset = read<uint32_t>(fh); } tex.viewFirstMip = read<uint32_t>(fh); tex.viewNumMips = read<uint32_t>(fh); tex.viewFirstSlice = read<uint32_t>(fh); tex.viewNumSlices = read<uint32_t>(fh); tex.compMap = read<uint32_t>(fh); tex.regs.word0 = latte::SQ_TEX_RESOURCE_WORD0_N::get(read<uint32_t>(fh)); tex.regs.word1 = latte::SQ_TEX_RESOURCE_WORD1_N::get(read<uint32_t>(fh)); tex.regs.word4 = latte::SQ_TEX_RESOURCE_WORD4_N::get(read<uint32_t>(fh)); tex.regs.word5 = latte::SQ_TEX_RESOURCE_WORD5_N::get(read<uint32_t>(fh)); tex.regs.word6 = latte::SQ_TEX_RESOURCE_WORD6_N::get(read<uint32_t>(fh)); if (block.majorVersion == 1) { decaf_check(fh.pos - blockBase == 0x9c); } fh.pos = blockBase + block.dataSize; return true; } static bool readTextureImage(MemoryFile &fh, GFDBlockHeader &block, GFDTexture &tex) { if (block.dataSize != tex.surface.image.capacity()) { throw GFDReadException { fmt::format("TextureImage block.dataSize {} != tex.surface.imageSize {}", block.dataSize, tex.surface.image.capacity()) }; } readBinary(fh, tex.surface.image, block.dataSize); return true; } static bool readTextureMipmap(MemoryFile &fh, GFDBlockHeader &block, GFDTexture &tex) { if (block.dataSize != tex.surface.mipmap.capacity()) { throw GFDReadException { fmt::format("TextureMipmap block.dataSize {} != tex.surface.mipmapSize {}", block.dataSize, tex.surface.mipmap.capacity()) }; } readBinary(fh, tex.surface.mipmap, block.dataSize); return true; } bool readFile(GFDFile &file, const std::string &path) { MemoryFile fh; GFDBlock block; GFDFileHeader header; if (!openFile(path, fh)) { return false; } if (!readFileHeader(fh, header)) { return false; } while (readBlockHeader(fh, block.header)) { auto pos = fh.pos; if (block.header.type == GFDBlockType::EndOfFile) { break; } switch (block.header.type) { case GFDBlockType::VertexShaderHeader: { file.vertexShaders.emplace_back(); readVertexShaderHeader(fh, block.header, file.vertexShaders.back()); break; } case GFDBlockType::VertexShaderProgram: { decaf_check(file.vertexShaders.size()); readVertexShaderProgram(fh, block.header, file.vertexShaders.back()); break; } case GFDBlockType::PixelShaderHeader: { file.pixelShaders.emplace_back(); readPixelShaderHeader(fh, block.header, file.pixelShaders.back()); break; } case GFDBlockType::PixelShaderProgram: { decaf_check(file.pixelShaders.size()); readPixelShaderProgram(fh, block.header, file.pixelShaders.back()); break; } case GFDBlockType::GeometryShaderHeader: { file.geometryShaders.emplace_back(); readGeometryShaderHeader(fh, block.header, file.geometryShaders.back()); break; } case GFDBlockType::GeometryShaderProgram: { decaf_check(file.geometryShaders.size()); readGeometryShaderProgram(fh, block.header, file.geometryShaders.back()); break; } case GFDBlockType::GeometryShaderCopyProgram: { decaf_check(file.geometryShaders.size()); readGeometryShaderCopyProgram(fh, block.header, file.geometryShaders.back()); break; } case GFDBlockType::TextureHeader: { file.textures.emplace_back(); readTextureHeader(fh, block.header, file.textures.back()); break; } case GFDBlockType::TextureImage: { decaf_check(file.textures.size()); readTextureImage(fh, block.header, file.textures.back()); break; } case GFDBlockType::TextureMipmap: { decaf_check(file.textures.size()); readTextureMipmap(fh, block.header, file.textures.back()); break; } case GFDBlockType::ComputeShaderHeader: case GFDBlockType::ComputeShaderProgram: case GFDBlockType::Padding: default: fh.pos += block.header.dataSize; } decaf_check(fh.pos == pos + block.header.dataSize); } return true; } } // namespace gfd ================================================ FILE: src/libgfd/src/gfd_write.cpp ================================================ #include "gfd.h" #include <common/align.h> #include <fstream> namespace gfd { struct DataPatch { size_t offset; size_t target; }; struct TextPatch { size_t offset; size_t target; const char *text; }; using MemoryFile = std::vector<uint8_t>; template<typename Type> inline void writeAt(MemoryFile &fh, size_t pos, Type value) { *reinterpret_cast<Type *>(fh.data() + pos) = byte_swap(value); } template<typename Type> inline void write(MemoryFile &fh, Type value) { auto pos = fh.size(); fh.resize(pos + sizeof(Type)); *reinterpret_cast<Type *>(fh.data() + pos) = byte_swap(value); } inline void writeNullTerminatedString(MemoryFile &fh, const char *str) { auto pos = fh.size(); auto len = strlen(str) + 1; fh.resize(align_up(pos + len, 4)); std::memcpy(fh.data() + pos, str, len); } inline void writeBinary(MemoryFile &fh, const std::vector<uint8_t> &data) { auto pos = fh.size(); auto len = data.size(); fh.resize(pos + len); std::memcpy(fh.data() + pos, data.data(), len); } static bool writeGX2RBuffer(MemoryFile &fh, const GFDRBuffer &buffer) { write<uint32_t>(fh, static_cast<uint32_t>(buffer.flags)); write<uint32_t>(fh, buffer.elemSize); write<uint32_t>(fh, buffer.elemCount); write<uint32_t>(fh, 0); // buffer.buffer return true; } static bool writeUniformBlocksData(std::vector<uint8_t> &fh, DataPatch &patch, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches, const std::vector<GFDUniformBlock> &uniformBlocks) { if (uniformBlocks.size() > 0) { patch.target = fh.size(); dataPatches.push_back(patch); for (auto &block : uniformBlocks) { textPatches.push_back(TextPatch { fh.size(), 0, block.name.c_str() }); write<uint32_t>(fh, 0); // block.name write<uint32_t>(fh, block.offset); write<uint32_t>(fh, block.size); } } return true; } static bool writeUniformVarsData(std::vector<uint8_t> &fh, DataPatch &patch, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches, const std::vector<GFDUniformVar> &uniformVars) { if (uniformVars.size() > 0) { patch.target = fh.size(); dataPatches.push_back(patch); for (auto &var : uniformVars) { textPatches.push_back(TextPatch { fh.size(), 0, var.name.c_str() }); write<uint32_t>(fh, 0); // var.name write<uint32_t>(fh, static_cast<uint32_t>(var.type)); write<uint32_t>(fh, var.count); write<uint32_t>(fh, var.offset); write<int32_t>(fh, var.block); } } return true; } static bool writeInitialValuesData(std::vector<uint8_t> &fh, DataPatch &patch, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches, const std::vector<GFDUniformInitialValue> &initialValues) { if (initialValues.size() > 0) { patch.target = fh.size(); dataPatches.push_back(patch); for (auto &var : initialValues) { for (auto &value : var.value) { write<float>(fh, value); } write<uint32_t>(fh, var.offset); } } return true; } static bool writeLoopVarsData(std::vector<uint8_t> &fh, DataPatch &patch, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches, const std::vector<GFDLoopVar> &loopVars) { if (loopVars.size() > 0) { patch.target = fh.size(); dataPatches.push_back(patch); for (auto &var : loopVars) { write<uint32_t>(fh, var.offset); write<uint32_t>(fh, var.value); } } return true; } static bool writeSamplerVarsData(std::vector<uint8_t> &fh, DataPatch &patch, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches, const std::vector<GFDSamplerVar> &samplerVars) { if (samplerVars.size() > 0) { patch.target = fh.size(); dataPatches.push_back(patch); for (auto &var : samplerVars) { textPatches.push_back(TextPatch { fh.size(), 0, var.name.c_str() }); write<uint32_t>(fh, 0); // var.name write<uint32_t>(fh, static_cast<uint32_t>(var.type)); write<uint32_t>(fh, var.location); } } return true; } static bool writeAttribVarsData(std::vector<uint8_t> &fh, DataPatch &patch, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches, const std::vector<GFDAttribVar> &attribVars) { if (attribVars.size() > 0) { patch.target = fh.size(); dataPatches.push_back(patch); for (auto &var : attribVars) { textPatches.push_back(TextPatch { fh.size(), 0, var.name.c_str() }); write<uint32_t>(fh, 0); // var.name write<uint32_t>(fh, static_cast<uint32_t>(var.type)); write<uint32_t>(fh, var.count); write<uint32_t>(fh, var.location); } } return true; } static bool writeRelocationData(std::vector<uint8_t> &fh, std::vector<DataPatch> &dataPatches, std::vector<TextPatch> &textPatches) { // Now write text auto textOffset = fh.size(); for (auto &patch : textPatches) { patch.target = fh.size(); writeNullTerminatedString(fh, patch.text); } auto textSize = fh.size() - textOffset; auto patchOffset = fh.size(); auto dataSize = patchOffset; auto dataOffset = 0; // Now write patches for (auto &patch : dataPatches) { writeAt<uint32_t>(fh, patch.offset, static_cast<uint32_t>(patch.target) | GFDPatchData); write<uint32_t>(fh, static_cast<uint32_t>(patch.offset) | GFDPatchData); } for (auto &patch : textPatches) { writeAt<uint32_t>(fh, patch.offset, static_cast<uint32_t>(patch.target) | GFDPatchText); write<uint32_t>(fh, static_cast<uint32_t>(patch.offset) | GFDPatchText); } // Write relocation header write<uint32_t>(fh, GFDRelocationHeader::Magic); write<uint32_t>(fh, GFDRelocationHeader::HeaderSize); write<uint32_t>(fh, 0); // unk1 write<uint32_t>(fh, static_cast<uint32_t>(dataSize)); write<uint32_t>(fh, static_cast<uint32_t>(dataOffset) | GFDPatchData); write<uint32_t>(fh, static_cast<uint32_t>(textSize)); write<uint32_t>(fh, static_cast<uint32_t>(textOffset) | GFDPatchData); write<uint32_t>(fh, 0); // patchBase write<uint32_t>(fh, static_cast<uint32_t>(dataPatches.size() + textPatches.size())); write<uint32_t>(fh, static_cast<uint32_t>(patchOffset) | GFDPatchData); return true; } static bool writeVertexShader(std::vector<uint8_t> &fh, const GFDVertexShader &vsh) { std::vector<uint8_t> text; std::vector<DataPatch> dataPatches; std::vector<TextPatch> textPatches; decaf_check(fh.size() == 0); write<uint32_t>(fh, vsh.regs.sq_pgm_resources_vs.value); write<uint32_t>(fh, vsh.regs.vgt_primitiveid_en.value); write<uint32_t>(fh, vsh.regs.spi_vs_out_config.value); write<uint32_t>(fh, vsh.regs.num_spi_vs_out_id); for (auto &spi_vs_out_id : vsh.regs.spi_vs_out_id) { write<uint32_t>(fh, spi_vs_out_id.value); } write<uint32_t>(fh, vsh.regs.pa_cl_vs_out_cntl.value); write<uint32_t>(fh, vsh.regs.sq_vtx_semantic_clear.value); write<uint32_t>(fh, vsh.regs.num_sq_vtx_semantic); for (auto &sq_vtx_semantic : vsh.regs.sq_vtx_semantic) { write<uint32_t>(fh, sq_vtx_semantic.value); } write<uint32_t>(fh, vsh.regs.vgt_strmout_buffer_en.value); write<uint32_t>(fh, vsh.regs.vgt_vertex_reuse_block_cntl.value); write<uint32_t>(fh, vsh.regs.vgt_hos_reuse_depth.value); write<uint32_t>(fh, static_cast<uint32_t>(vsh.data.size())); write<uint32_t>(fh, 0); // vsh.data write<uint32_t>(fh, static_cast<uint32_t>(vsh.mode)); auto uniformBlocksPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(vsh.uniformBlocks.size())); write<uint32_t>(fh, 0); // vsh.uniformBlocks.data auto uniformVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(vsh.uniformVars.size())); write<uint32_t>(fh, 0); // vsh.uniformVars.data auto initialValuesPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(vsh.initialValues.size())); write<uint32_t>(fh, 0); // vsh.initialValues.data auto loopVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(vsh.loopVars.size())); write<uint32_t>(fh, 0); // vsh.loopVars.data auto samplerVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(vsh.samplerVars.size())); write<uint32_t>(fh, 0); // vsh.samplerVars.data auto attribVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(vsh.attribVars.size())); write<uint32_t>(fh, 0); // vsh.attribVars.data write<uint32_t>(fh, vsh.ringItemSize); write<uint32_t>(fh, vsh.hasStreamOut ? 1 : 0); for (auto &stride : vsh.streamOutStride) { write<uint32_t>(fh, stride); } writeGX2RBuffer(fh, vsh.gx2rData); decaf_check(fh.size() == 0x134); // Now write relocated data writeUniformBlocksData(fh, uniformBlocksPatch, dataPatches, textPatches, vsh.uniformBlocks); writeUniformVarsData(fh, uniformVarsPatch, dataPatches, textPatches, vsh.uniformVars); writeInitialValuesData(fh, initialValuesPatch, dataPatches, textPatches, vsh.initialValues); writeLoopVarsData(fh, loopVarsPatch, dataPatches, textPatches, vsh.loopVars); writeSamplerVarsData(fh, samplerVarsPatch, dataPatches, textPatches, vsh.samplerVars); writeAttribVarsData(fh, attribVarsPatch, dataPatches, textPatches, vsh.attribVars); writeRelocationData(fh, dataPatches, textPatches); return true; } static bool writePixelShader(std::vector<uint8_t> &fh, const GFDPixelShader &psh) { std::vector<uint8_t> text; std::vector<DataPatch> dataPatches; std::vector<TextPatch> textPatches; decaf_check(fh.size() == 0); write<uint32_t>(fh, psh.regs.sq_pgm_resources_ps.value); write<uint32_t>(fh, psh.regs.sq_pgm_exports_ps.value); write<uint32_t>(fh, psh.regs.spi_ps_in_control_0.value); write<uint32_t>(fh, psh.regs.spi_ps_in_control_1.value); write<uint32_t>(fh, psh.regs.num_spi_ps_input_cntl); for (auto &spi_ps_input_cntl : psh.regs.spi_ps_input_cntls) { write<uint32_t>(fh, spi_ps_input_cntl.value); } write<uint32_t>(fh, psh.regs.cb_shader_mask.value); write<uint32_t>(fh, psh.regs.cb_shader_control.value); write<uint32_t>(fh, psh.regs.db_shader_control.value); write<uint32_t>(fh, psh.regs.spi_input_z.value); write<uint32_t>(fh, static_cast<uint32_t>(psh.data.size())); write<uint32_t>(fh, 0); // psh.data write<uint32_t>(fh, static_cast<uint32_t>(psh.mode)); auto uniformBlocksPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(psh.uniformBlocks.size())); write<uint32_t>(fh, 0); // vsh.uniformBlocks.data auto uniformVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(psh.uniformVars.size())); write<uint32_t>(fh, 0); // vsh.uniformVars.data auto initialValuesPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(psh.initialValues.size())); write<uint32_t>(fh, 0); // vsh.initialValues.data auto loopVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(psh.loopVars.size())); write<uint32_t>(fh, 0); // vsh.loopVars.data auto samplerVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(psh.samplerVars.size())); write<uint32_t>(fh, 0); // vsh.samplerVars.data writeGX2RBuffer(fh, psh.gx2rData); decaf_check(fh.size() == 0xE8); // Now write relocated data writeUniformBlocksData(fh, uniformBlocksPatch, dataPatches, textPatches, psh.uniformBlocks); writeUniformVarsData(fh, uniformVarsPatch, dataPatches, textPatches, psh.uniformVars); writeInitialValuesData(fh, initialValuesPatch, dataPatches, textPatches, psh.initialValues); writeLoopVarsData(fh, loopVarsPatch, dataPatches, textPatches, psh.loopVars); writeSamplerVarsData(fh, samplerVarsPatch, dataPatches, textPatches, psh.samplerVars); writeRelocationData(fh, dataPatches, textPatches); return true; } static bool writeGeometryShader(std::vector<uint8_t> &fh, const GFDGeometryShader &gsh) { std::vector<uint8_t> text; std::vector<DataPatch> dataPatches; std::vector<TextPatch> textPatches; decaf_check(fh.size() == 0); write<uint32_t>(fh, gsh.regs.sq_pgm_resources_gs.value); write<uint32_t>(fh, gsh.regs.vgt_gs_out_prim_type.value); write<uint32_t>(fh, gsh.regs.vgt_gs_mode.value); write<uint32_t>(fh, gsh.regs.pa_cl_vs_out_cntl.value); write<uint32_t>(fh, gsh.regs.sq_pgm_resources_vs.value); write<uint32_t>(fh, gsh.regs.sq_gs_vert_itemsize.value); write<uint32_t>(fh, gsh.regs.spi_vs_out_config.value); write<uint32_t>(fh, gsh.regs.num_spi_vs_out_id); for (auto &spi_vs_out_id : gsh.regs.spi_vs_out_id) { write<uint32_t>(fh, spi_vs_out_id.value); } write<uint32_t>(fh, gsh.regs.vgt_strmout_buffer_en.value); write<uint32_t>(fh, static_cast<uint32_t>(gsh.data.size())); write<uint32_t>(fh, 0); // gsh.data write<uint32_t>(fh, static_cast<uint32_t>(gsh.vertexShaderData.size())); write<uint32_t>(fh, 0); // gsh.vertexShaderData write<uint32_t>(fh, static_cast<uint32_t>(gsh.mode)); auto uniformBlocksPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(gsh.uniformBlocks.size())); write<uint32_t>(fh, 0); // vsh.uniformBlocks.data auto uniformVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(gsh.uniformVars.size())); write<uint32_t>(fh, 0); // vsh.uniformVars.data auto initialValuesPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(gsh.initialValues.size())); write<uint32_t>(fh, 0); // vsh.initialValues.data auto loopVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(gsh.loopVars.size())); write<uint32_t>(fh, 0); // vsh.loopVars.data auto samplerVarsPatch = DataPatch { fh.size() + 4, 0 }; write<uint32_t>(fh, static_cast<uint32_t>(gsh.samplerVars.size())); write<uint32_t>(fh, 0); // vsh.samplerVars.data write<uint32_t>(fh, gsh.ringItemSize); write<uint32_t>(fh, gsh.hasStreamOut ? 1 : 0); for (auto &stride : gsh.streamOutStride) { write<uint32_t>(fh, stride); } writeGX2RBuffer(fh, gsh.gx2rData); writeGX2RBuffer(fh, gsh.gx2rVertexShaderData); decaf_check(fh.size() == 0xC0); // Now write relocated data writeUniformBlocksData(fh, uniformBlocksPatch, dataPatches, textPatches, gsh.uniformBlocks); writeUniformVarsData(fh, uniformVarsPatch, dataPatches, textPatches, gsh.uniformVars); writeInitialValuesData(fh, initialValuesPatch, dataPatches, textPatches, gsh.initialValues); writeLoopVarsData(fh, loopVarsPatch, dataPatches, textPatches, gsh.loopVars); writeSamplerVarsData(fh, samplerVarsPatch, dataPatches, textPatches, gsh.samplerVars); writeRelocationData(fh, dataPatches, textPatches); return true; } static bool writeTexture(MemoryFile &fh, const GFDTexture &texture) { write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.dim)); write<uint32_t>(fh, texture.surface.width); write<uint32_t>(fh, texture.surface.height); write<uint32_t>(fh, texture.surface.depth); write<uint32_t>(fh, texture.surface.mipLevels); write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.format)); write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.aa)); write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.use)); write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.image.size())); write<uint32_t>(fh, 0); // texture.surface.image write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.mipmap.size())); write<uint32_t>(fh, 0); // texture.surface.mipmap write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.tileMode)); write<uint32_t>(fh, texture.surface.swizzle); write<uint32_t>(fh, texture.surface.alignment); write<uint32_t>(fh, texture.surface.pitch); for (auto &mipLevelOffset : texture.surface.mipLevelOffset) { write<uint32_t>(fh, mipLevelOffset); } write<uint32_t>(fh, texture.viewFirstMip); write<uint32_t>(fh, texture.viewNumMips); write<uint32_t>(fh, texture.viewFirstSlice); write<uint32_t>(fh, texture.viewNumSlices); write<uint32_t>(fh, texture.compMap); write<uint32_t>(fh, texture.regs.word0.value); write<uint32_t>(fh, texture.regs.word1.value); write<uint32_t>(fh, texture.regs.word4.value); write<uint32_t>(fh, texture.regs.word5.value); write<uint32_t>(fh, texture.regs.word6.value); decaf_check(fh.size() == 0x9C); return true; } static bool writeBlock(MemoryFile &fh, const GFDBlockHeader &header, const std::vector<uint8_t> &data) { write<uint32_t>(fh, GFDBlockHeader::Magic); write<uint32_t>(fh, GFDBlockHeader::HeaderSize); write<uint32_t>(fh, header.majorVersion); write<uint32_t>(fh, header.minorVersion); write<uint32_t>(fh, static_cast<uint32_t>(header.type)); write<uint32_t>(fh, static_cast<uint32_t>(data.size())); write<uint32_t>(fh, header.id); write<uint32_t>(fh, header.index); writeBinary(fh, data); return true; } static bool alignNextBlock(MemoryFile &fh, uint32_t &blockID) { auto paddingSize = (0x200 - ((fh.size() + sizeof(GFDBlockHeader)) & 0x1FF)) & 0x1FF; if (paddingSize) { if (paddingSize < sizeof(GFDBlockHeader)) { paddingSize += 0x200; } paddingSize -= sizeof(GFDBlockHeader); GFDBlockHeader paddingHeader; paddingHeader.majorVersion = GFDBlockMajorVersion; paddingHeader.minorVersion = 0; paddingHeader.type = GFDBlockType::Padding; paddingHeader.id = blockID++; paddingHeader.index = 0; writeBlock(fh, paddingHeader, std::vector<uint8_t>(paddingSize, 0)); } return true; } bool writeFile(const GFDFile &file, const std::string &path, bool align) { MemoryFile fh; auto blockID = uint32_t { 0 }; // Write File Header write<uint32_t>(fh, GFDFileHeader::Magic); write<uint32_t>(fh, GFDFileHeader::HeaderSize); write<uint32_t>(fh, GFDFileMajorVersion); write<uint32_t>(fh, GFDFileMinorVersion); write<uint32_t>(fh, GFDFileGpuVersion); write<uint32_t>(fh, align ? 1 : 0); // align write<uint32_t>(fh, 0); // unk1 write<uint32_t>(fh, 0); // unk2 // Write vertex shaders for (auto i = 0u; i < file.vertexShaders.size(); ++i) { std::vector<uint8_t> vertexShaderHeader; writeVertexShader(vertexShaderHeader, file.vertexShaders[i]); GFDBlockHeader vshHeader; vshHeader.majorVersion = GFDBlockMajorVersion; vshHeader.minorVersion = 0; vshHeader.type = GFDBlockType::VertexShaderHeader; vshHeader.id = blockID++; vshHeader.index = i; writeBlock(fh, vshHeader, vertexShaderHeader); if (align) { alignNextBlock(fh, blockID); } GFDBlockHeader dataHeader; dataHeader.majorVersion = GFDBlockMajorVersion; dataHeader.minorVersion = 0; dataHeader.type = GFDBlockType::VertexShaderProgram; dataHeader.id = blockID++; dataHeader.index = i; writeBlock(fh, dataHeader, file.vertexShaders[i].data); } // Write pixel shaders for (auto i = 0u; i < file.pixelShaders.size(); ++i) { std::vector<uint8_t> pixelShaderHeader; writePixelShader(pixelShaderHeader, file.pixelShaders[i]); GFDBlockHeader pshHeader; pshHeader.majorVersion = GFDBlockMajorVersion; pshHeader.minorVersion = 0; pshHeader.type = GFDBlockType::PixelShaderHeader; pshHeader.id = blockID++; pshHeader.index = i; writeBlock(fh, pshHeader, pixelShaderHeader); if (align) { alignNextBlock(fh, blockID); } GFDBlockHeader dataHeader; dataHeader.majorVersion = GFDBlockMajorVersion; dataHeader.minorVersion = 0; dataHeader.type = GFDBlockType::PixelShaderProgram; dataHeader.id = blockID++; dataHeader.index = i; writeBlock(fh, dataHeader, file.pixelShaders[i].data); } // Write geometry shaders for (auto i = 0u; i < file.geometryShaders.size(); ++i) { std::vector<uint8_t> geometryShaderHeader; writeGeometryShader(geometryShaderHeader, file.geometryShaders[i]); GFDBlockHeader gshHeader; gshHeader.majorVersion = GFDBlockMajorVersion; gshHeader.minorVersion = 0; gshHeader.type = GFDBlockType::GeometryShaderHeader; gshHeader.id = blockID++; gshHeader.index = i; writeBlock(fh, gshHeader, geometryShaderHeader); if (align) { alignNextBlock(fh, blockID); } if (file.geometryShaders[i].data.size()) { GFDBlockHeader dataHeader; dataHeader.majorVersion = GFDBlockMajorVersion; dataHeader.minorVersion = 0; dataHeader.type = GFDBlockType::GeometryShaderProgram; dataHeader.id = blockID++; dataHeader.index = i; writeBlock(fh, dataHeader, file.geometryShaders[i].data); } if (align) { alignNextBlock(fh, blockID); } if (file.geometryShaders[i].vertexShaderData.size()) { GFDBlockHeader dataHeader; dataHeader.majorVersion = GFDBlockMajorVersion; dataHeader.minorVersion = 0; dataHeader.type = GFDBlockType::GeometryShaderCopyProgram; dataHeader.id = blockID++; dataHeader.index = i; writeBlock(fh, dataHeader, file.geometryShaders[i].vertexShaderData); } } // Write textures for (auto i = 0u; i < file.textures.size(); ++i) { std::vector<uint8_t> textureHeader; writeTexture(textureHeader, file.textures[i]); GFDBlockHeader texHeader; texHeader.majorVersion = GFDBlockMajorVersion; texHeader.minorVersion = 0; texHeader.type = GFDBlockType::TextureHeader; texHeader.id = blockID++; texHeader.index = i; writeBlock(fh, texHeader, textureHeader); if (align) { alignNextBlock(fh, blockID); } GFDBlockHeader imageHeader; imageHeader.majorVersion = GFDBlockMajorVersion; imageHeader.minorVersion = 0; imageHeader.type = GFDBlockType::TextureImage; imageHeader.id = blockID++; imageHeader.index = i; writeBlock(fh, imageHeader, file.textures[i].surface.image); if (align) { alignNextBlock(fh, blockID); } if (file.textures[i].surface.mipmap.size()) { GFDBlockHeader mipmapHeader; mipmapHeader.majorVersion = GFDBlockMajorVersion; mipmapHeader.minorVersion = 0; mipmapHeader.type = GFDBlockType::TextureImage; mipmapHeader.id = blockID++; mipmapHeader.index = i; writeBlock(fh, mipmapHeader, file.textures[i].surface.mipmap); } } // Write EOF GFDBlockHeader eofHeader; eofHeader.majorVersion = GFDBlockMajorVersion; eofHeader.minorVersion = 0; eofHeader.type = GFDBlockType::EndOfFile; eofHeader.id = blockID++; eofHeader.index = 0; writeBlock(fh, eofHeader, {}); std::ofstream out { path, std::ofstream::binary }; out.write(reinterpret_cast<const char *>(fh.data()), fh.size()); return true; } } // namespace gfd ================================================ FILE: src/libgpu/CMakeLists.txt ================================================ project(libgpu) include_directories(".") include_directories("src") include_directories("vulkan_shaders") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) file(GLOB_RECURSE INLINE_FILES *.inl) file(GLOB_RECURSE VULKANSHADER_FILES vulkan_shaders/*) if(DECAF_VULKAN) find_program(GLSLANG_VALIDATOR NAMES glslangValidator HINTS "${Vulkan_INCLUDE_DIR}/../Bin") find_program(SPIRV_OPT NAMES spirv-opt HINTS "${Vulkan_INCLUDE_DIR}/../Bin") set(GENERATED_BASEPATH "${PROJECT_BINARY_DIR}/generated") set(SHDRBIN_BASEPATH "${GENERATED_BASEPATH}/vulkan_shaders_bin") include_directories(${GENERATED_BASEPATH}) if(DECAF_PLATFORM_COCOA) set(SHADER_DEFINES "-DDECAF_MVK_COMPAT") endif() macro(compile_vulkan_shader OUTFILE INFILE) set(BIN2C "${CMAKE_SOURCE_DIR}/libraries/bin2c.cmake") set(INPATH "${PROJECT_SOURCE_DIR}/vulkan_shaders/${INFILE}") set(OUTPATH "${SHDRBIN_BASEPATH}/${OUTFILE}") add_custom_command( OUTPUT "${OUTPATH}.h" "${OUTPATH}.cpp" COMMAND ${CMAKE_COMMAND} -E make_directory "${SHDRBIN_BASEPATH}" COMMAND ${GLSLANG_VALIDATOR} -V ${SHADER_DEFINES} -o "${OUTPATH}" "${INPATH}" COMMAND ${CMAKE_COMMAND} ARGS "-DOUTPUT_C=${OUTPATH}.cpp" "-DOUTPUT_H=${OUTPATH}.h" "-DINPUT_FILES=${OUTPATH}" -P "${BIN2C}" DEPENDS "${INPATH}" "${BIN2C}" VERBATIM) list(APPEND VK_BIN_FILES "${OUTPATH}.h") list(APPEND VK_BIN_FILES "${OUTPATH}.cpp") set_source_files_properties("${OUTPATH}.cpp" PROPERTIES GENERATED TRUE) set_source_files_properties("${OUTPATH}.h" PROPERTIES GENERATED TRUE) endmacro() # compile the main retiling shader itself compile_vulkan_shader("gpu7_tiling.comp.spv" "gpu7_tiling.comp.glsl") add_custom_target(libgpu-shaders DEPENDS ${VK_BIN_FILES}) endif() add_library(libgpu STATIC ${SOURCE_FILES} ${HEADER_FILES} ${INLINE_FILES} ${VULKANSHADER_FILES} ${VK_BIN_FILES}) GroupSources("Header Files/latte" latte) GroupSources("Source Files" src) GroupSources("Vulkan Shader Files" vulkan_shaders) # We have to do this manually as the files don't exist until the first # build occurs, which is after the solution is generated. if(DECAF_VULKAN) if(MSVC) add_dependencies(libgpu libgpu-shaders) source_group("Generated Files" FILES ${VK_BIN_FILES}) endif() endif() target_link_libraries(libgpu common libcpu addrlib) if(DECAF_PLATFORM_XLIB) target_link_libraries(libgpu ${X11_LIBRARIES}) endif() if(DECAF_PLATFORM_XCB) target_link_libraries(libgpu ${XCB_LIBRARIES}) if(X11_Xau_FOUND) target_link_libraries(libgpu ${X11_Xau_LIB}) endif() if(X11_Xdmcp_FOUND) target_link_libraries(libgpu ${X11_Xdmcp_LIB}) endif() endif() if(DECAF_PLATFORM_WAYLAND) target_link_libraries(libgpu ${WAYLAND_CLIENT_LIBRARIES}) endif() if(DECAF_VULKAN) target_link_libraries(libgpu vulkan SPIRV) endif() if(DECAF_PCH) target_precompile_headers(libgpu PRIVATE <common/pch.h> "latte/latte_registers.h" "latte/latte_pm4.h" "latte/latte_pm4_commands.h" ) if(DECAF_VULKAN) target_precompile_headers(libgpu PRIVATE <common/vulkan_hpp.h> "src/vulkan/vk_mem_alloc_decaf.h" ) endif() AutoGroupPCHFiles() endif() ================================================ FILE: src/libgpu/gpu.h ================================================ #pragma once #include <cstdint> namespace gpu { using FlipCallbackFn = void(*)(); void setFlipCallback(FlipCallbackFn callback); } // namespace gpu ================================================ FILE: src/libgpu/gpu7_displaylayout.h ================================================ #pragma once #include <array> namespace gpu7 { struct DisplayLayout { struct Display { bool visible; float x; float y; float width; float height; }; Display tv; Display drc; std::array<float, 4> backgroundColour; }; struct DisplayTouchEvent { enum Screen { None, Tv, Drc1, Drc2, }; Screen screen = None; float x = 0; float y = 0; }; void updateDisplayLayout(DisplayLayout &layout, float windowWidth, float windowHeight); DisplayTouchEvent translateDisplayTouch(DisplayLayout &layout, float x, float y); static inline DisplayLayout getDisplayLayout(float width, float height) { DisplayLayout layout; updateDisplayLayout(layout, width, height); return layout; } } // namespace gpu7 ================================================ FILE: src/libgpu/gpu7_tiling.h ================================================ #pragma once #include <array> namespace gpu7::tiling { #include <common/enum_start.inl> ENUM_BEG(DataFormat, uint32_t) ENUM_VALUE(Invalid, 0) ENUM_VALUE(FMT_8, 1) ENUM_VALUE(FMT_4_4, 2) ENUM_VALUE(FMT_3_3_2, 3) ENUM_VALUE(FMT_16, 5) ENUM_VALUE(FMT_16_FLOAT, 6) ENUM_VALUE(FMT_8_8, 7) ENUM_VALUE(FMT_5_6_5, 8) ENUM_VALUE(FMT_6_5_5, 9) ENUM_VALUE(FMT_1_5_5_5, 10) ENUM_VALUE(FMT_4_4_4_4, 11) ENUM_VALUE(FMT_5_5_5_1, 12) ENUM_VALUE(FMT_32, 13) ENUM_VALUE(FMT_32_FLOAT, 14) ENUM_VALUE(FMT_16_16, 15) ENUM_VALUE(FMT_16_16_FLOAT, 16) ENUM_VALUE(FMT_8_24, 17) ENUM_VALUE(FMT_8_24_FLOAT, 18) ENUM_VALUE(FMT_24_8, 19) ENUM_VALUE(FMT_24_8_FLOAT, 20) ENUM_VALUE(FMT_10_11_11, 21) ENUM_VALUE(FMT_10_11_11_FLOAT, 22) ENUM_VALUE(FMT_11_11_10, 23) ENUM_VALUE(FMT_11_11_10_FLOAT, 24) ENUM_VALUE(FMT_2_10_10_10, 25) ENUM_VALUE(FMT_8_8_8_8, 26) ENUM_VALUE(FMT_10_10_10_2, 27) ENUM_VALUE(FMT_X24_8_32_FLOAT, 28) ENUM_VALUE(FMT_32_32, 29) ENUM_VALUE(FMT_32_32_FLOAT, 30) ENUM_VALUE(FMT_16_16_16_16, 31) ENUM_VALUE(FMT_16_16_16_16_FLOAT, 32) ENUM_VALUE(FMT_32_32_32_32, 34) ENUM_VALUE(FMT_32_32_32_32_FLOAT, 35) ENUM_VALUE(FMT_1, 37) ENUM_VALUE(FMT_GB_GR, 39) ENUM_VALUE(FMT_BG_RG, 40) ENUM_VALUE(FMT_32_AS_8, 41) ENUM_VALUE(FMT_32_AS_8_8, 42) ENUM_VALUE(FMT_5_9_9_9_SHAREDEXP, 43) ENUM_VALUE(FMT_8_8_8, 44) ENUM_VALUE(FMT_16_16_16, 45) ENUM_VALUE(FMT_16_16_16_FLOAT, 46) ENUM_VALUE(FMT_32_32_32, 47) ENUM_VALUE(FMT_32_32_32_FLOAT, 48) ENUM_VALUE(FMT_BC1, 49) ENUM_VALUE(FMT_BC2, 50) ENUM_VALUE(FMT_BC3, 51) ENUM_VALUE(FMT_BC4, 52) ENUM_VALUE(FMT_BC5, 53) ENUM_VALUE(FMT_APC0, 54) ENUM_VALUE(FMT_APC1, 55) ENUM_VALUE(FMT_APC2, 56) ENUM_VALUE(FMT_APC3, 57) ENUM_VALUE(FMT_APC4, 58) ENUM_VALUE(FMT_APC5, 59) ENUM_VALUE(FMT_APC6, 60) ENUM_VALUE(FMT_APC7, 61) ENUM_VALUE(FMT_CTX1, 62) ENUM_END(DataFormat) // TODO(brett19): Use this... ENUM_BEG(TileMode, uint32_t) ENUM_VALUE(LinearGeneral, 0x00) ENUM_VALUE(LinearAligned, 0x01) ENUM_VALUE(Micro1DTiledThin1, 0x02) ENUM_VALUE(Micro1DTiledThick, 0x03) ENUM_VALUE(Macro2DTiledThin1, 0x04) ENUM_VALUE(Macro2DTiledThin2, 0x05) ENUM_VALUE(Macro2DTiledThin4, 0x06) ENUM_VALUE(Macro2DTiledThick, 0x07) ENUM_VALUE(Macro2BTiledThin1, 0x08) ENUM_VALUE(Macro2BTiledThin2, 0x09) ENUM_VALUE(Macro2BTiledThin4, 0x0A) ENUM_VALUE(Macro2BTiledThick, 0x0B) ENUM_VALUE(Macro3DTiledThin1, 0x0C) ENUM_VALUE(Macro3DTiledThick, 0x0D) ENUM_VALUE(Macro3BTiledThin1, 0x0E) ENUM_VALUE(Macro3BTiledThick, 0x0F) ENUM_VALUE(LinearSpecial, 0x10) ENUM_END(TileMode) ENUM_BEG(SurfaceDim, uint32_t) ENUM_VALUE(Texture1D, 0) ENUM_VALUE(Texture2D, 1) ENUM_VALUE(Texture3D, 2) ENUM_VALUE(TextureCube, 3) ENUM_VALUE(Texture1DArray, 4) ENUM_VALUE(Texture2DArray, 5) ENUM_VALUE(Texture2DMSAA, 6) ENUM_VALUE(Texture2DMSAAArray, 7) ENUM_END(SurfaceDim) FLAGS_BEG(SurfaceUse, uint32_t) FLAGS_VALUE(None, 0) FLAGS_VALUE(Texture, 1 << 0) FLAGS_VALUE(ColorBuffer, 1 << 1) FLAGS_VALUE(DepthBuffer, 1 << 2) FLAGS_VALUE(ScanBuffer, 1 << 3) FLAGS_END(SurfaceUse) #include <common/enum_end.inl> static constexpr auto MicroTileWidth = 8; static constexpr auto MicroTileHeight = 8; static constexpr auto NumPipes = 2; static constexpr auto NumBanks = 4; static constexpr auto PipeInterleaveBytes = 256; static constexpr auto NumGroupBits = 8; static constexpr auto NumPipeBits = 1; static constexpr auto NumBankBits = 2; static constexpr auto GroupMask = (1 << NumGroupBits) - 1; static constexpr auto RowSize = 2048; static constexpr auto SwapSize = 256; static constexpr auto SampleSplitSize = 2048; static constexpr auto BankSwapBytes = 256; static constexpr int MacroTileWidth[] = { /* LinearGeneral = */ 1, /* LinearAligned = */ 1, /* Tiled1DThin1 = */ 1, /* Tiled1DThick = */ 1, /* Tiled2DThin1 = */ NumBanks, /* Tiled2DThin2 = */ NumBanks / 2, /* Tiled2DThin4 = */ NumBanks / 4, /* Tiled2DThick = */ NumBanks, /* Tiled2BThin1 = */ NumBanks, /* Tiled2BThin2 = */ NumBanks / 2, /* Tiled2BThin4 = */ NumBanks / 4, /* Tiled2BThick = */ NumBanks, /* Tiled3DThin1 = */ NumBanks, /* Tiled3DThick = */ NumBanks, /* Tiled3BThin1 = */ NumBanks, /* Tiled3BThick = */ NumBanks, }; static constexpr int MacroTileHeight[] = { /* LinearGeneral = */ 1, /* LinearAligned = */ 1, /* Tiled1DThin1 = */ 1, /* Tiled1DThick = */ 1, /* Tiled2DThin1 = */ NumPipes, /* Tiled2DThin2 = */ NumPipes * 2, /* Tiled2DThin4 = */ NumPipes * 4, /* Tiled2DThick = */ NumPipes, /* Tiled2BThin1 = */ NumPipes, /* Tiled2BThin2 = */ NumPipes * 2, /* Tiled2BThin4 = */ NumPipes * 4, /* Tiled2BThick = */ NumPipes, /* Tiled3DThin1 = */ NumPipes, /* Tiled3DThick = */ NumPipes, /* Tiled3BThin1 = */ NumPipes, /* Tiled3BThick = */ NumPipes, }; static constexpr int MicroTileThickness[] = { /* LinearGeneral = */ 1, /* LinearAligned = */ 1, /* Tiled1DThin1 = */ 1, /* Tiled1DThick = */ 4, /* Tiled2DThin1 = */ 1, /* Tiled2DThin2 = */ 1, /* Tiled2DThin4 = */ 1, /* Tiled2DThick = */ 4, /* Tiled2BThin1 = */ 1, /* Tiled2BThin2 = */ 1, /* Tiled2BThin4 = */ 1, /* Tiled2BThick = */ 4, /* Tiled3DThin1 = */ 1, /* Tiled3DThick = */ 4, /* Tiled3BThin1 = */ 1, /* Tiled3BThick = */ 4, }; static constexpr bool TileModeIsMacro[] = { /* LinearGeneral = */ false, /* LinearAligned = */ false, /* Tiled1DThin1 = */ false, /* Tiled1DThick = */ false, /* Tiled2DThin1 = */ true, /* Tiled2DThin2 = */ true, /* Tiled2DThin4 = */ true, /* Tiled2DThick = */ true, /* Tiled2BThin1 = */ true, /* Tiled2BThin2 = */ true, /* Tiled2BThin4 = */ true, /* Tiled2BThick = */ true, /* Tiled3DThin1 = */ true, /* Tiled3DThick = */ true, /* Tiled3BThin1 = */ true, /* Tiled3BThick = */ true, }; static constexpr bool TileModeIsMacro3X[] = { /* LinearGeneral = */ false, /* LinearAligned = */ false, /* Tiled1DThin1 = */ false, /* Tiled1DThick = */ false, /* Tiled2DThin1 = */ false, /* Tiled2DThin2 = */ false, /* Tiled2DThin4 = */ false, /* Tiled2DThick = */ false, /* Tiled2BThin1 = */ false, /* Tiled2BThin2 = */ false, /* Tiled2BThin4 = */ false, /* Tiled2BThick = */ false, /* Tiled3DThin1 = */ true, /* Tiled3DThick = */ true, /* Tiled3BThin1 = */ true, /* Tiled3BThick = */ true, }; static constexpr bool TileModeIsBankSwapped[] = { /* LinearGeneral = */ false, /* LinearAligned = */ false, /* Tiled1DThin1 = */ false, /* Tiled1DThick = */ false, /* Tiled2DThin1 = */ false, /* Tiled2DThin2 = */ false, /* Tiled2DThin4 = */ false, /* Tiled2DThick = */ false, /* Tiled2BThin1 = */ true, /* Tiled2BThin2 = */ true, /* Tiled2BThin4 = */ true, /* Tiled2BThick = */ true, /* Tiled3DThin1 = */ false, /* Tiled3DThick = */ false, /* Tiled3BThin1 = */ true, /* Tiled3BThick = */ true, }; static constexpr int getMacroTileWidth(TileMode tileMode) { return MacroTileWidth[static_cast<size_t>(tileMode)]; } static constexpr int getMacroTileHeight(TileMode tileMode) { return MacroTileHeight[static_cast<size_t>(tileMode)]; } static constexpr int getMicroTileThickness(TileMode tileMode) { return MicroTileThickness[static_cast<size_t>(tileMode)]; } static constexpr int getTileModeIsMacro(TileMode tileMode) { return TileModeIsMacro[static_cast<size_t>(tileMode)]; } static constexpr int getTileModeIs3X(TileMode tileMode) { return TileModeIsMacro3X[static_cast<size_t>(tileMode)]; } static constexpr int getTileModeIsBankSwapped(TileMode tileMode) { return TileModeIsBankSwapped[static_cast<size_t>(tileMode)]; } struct SurfaceDescription { TileMode tileMode = TileMode::LinearGeneral; DataFormat format = DataFormat::Invalid; uint32_t bpp = 0u; uint32_t numSamples = 0u; uint32_t width = 0u; uint32_t height = 0u; uint32_t numSlices = 0u; uint32_t numFrags = 0u; uint32_t numLevels = 0u; uint32_t bankSwizzle = 0u; uint32_t pipeSwizzle = 0u; SurfaceUse use = SurfaceUse::None; SurfaceDim dim = SurfaceDim::Texture1D; }; struct SurfaceInfo { TileMode tileMode; SurfaceUse use; uint32_t bpp; uint32_t pitch; uint32_t height; uint32_t depth; uint32_t surfSize; uint32_t sliceSize; uint32_t baseAlign; uint32_t pitchAlign; uint32_t heightAlign; uint32_t depthAlign; uint32_t bankSwizzle; uint32_t pipeSwizzle; }; struct RetileInfo { // Input values TileMode tileMode; uint32_t bitsPerElement; bool isDepth; // Some helpful stuff uint32_t thinSliceBytes; // Used for both micro and macro tiling bool isTiled; bool isMacroTiled; uint32_t macroTileWidth; uint32_t macroTileHeight; uint32_t microTileThickness; uint32_t thickMicroTileBytes; uint32_t numTilesPerRow; uint32_t numTilesPerSlice; // Used only for macro tiling uint32_t bankSwizzle; uint32_t pipeSwizzle; uint32_t bankSwapWidth; }; SurfaceInfo computeSurfaceInfo(const SurfaceDescription &surface, int mipLevel); size_t computeUnpitchedImageSize(const SurfaceDescription &desc); size_t computeUnpitchedMipMapSize(const SurfaceDescription &desc); void unpitchImage(const SurfaceDescription &desc, const void *pitched, void *unpitched); void unpitchMipMap(const SurfaceDescription &desc, const void *pitched, void *unpitched); RetileInfo computeRetileInfo(const SurfaceInfo &info); } // namespace gpu7::tiling ================================================ FILE: src/libgpu/gpu7_tiling_cpu.h ================================================ #pragma once #include "gpu7_tiling.h" namespace gpu7::tiling::cpu { void untile(const RetileInfo& desc, uint8_t* untiled, uint8_t* tiled, uint32_t firstSlice, uint32_t numSlices); void tile(const RetileInfo& desc, uint8_t* untiled, uint8_t* tiled, uint32_t firstSlice, uint32_t numSlices); } // namespace gpu7::tiling::cpu ================================================ FILE: src/libgpu/gpu7_tiling_vulkan.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "gpu7_tiling.h" #include <common/vulkan_hpp.h> #include <map> namespace gpu7::tiling::vulkan { typedef void * RetileHandle; class Retiler { public: Retiler() { } void initialise(vk::Device device); RetileHandle tile(const RetileInfo& retileInfo, vk::CommandBuffer &commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices) { return retile(false, retileInfo, commandBuffer, dstBuffer, dstOffset, srcBuffer, srcOffset, firstSlice, numSlices); } RetileHandle untile(const RetileInfo& retileInfo, vk::CommandBuffer &commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices) { return retile(true, retileInfo, commandBuffer, srcBuffer, srcOffset, dstBuffer, dstOffset, firstSlice, numSlices); } void releaseHandle(RetileHandle handle) { releaseHandle(reinterpret_cast<HandleImpl*>(handle)); } private: struct HandleImpl { vk::DescriptorPool descriptorPool; vk::DescriptorSet descriptorSet; }; HandleImpl * allocateHandle(); void releaseHandle(HandleImpl *handle); RetileHandle retile(bool wantsUntile, const RetileInfo& retileInfo, vk::CommandBuffer &commandBuffer, vk::Buffer tiledBuffer, uint32_t tiledOffset, vk::Buffer untiledBuffer, uint32_t untiledOffset, uint32_t firstSlice, uint32_t numSlices); void retile(bool wantsUntile, const RetileInfo& retileInfo, vk::DescriptorSet& descriptorSet, vk::CommandBuffer& commandBuffer, vk::Buffer tiledBuffer, uint32_t tiledOffset, vk::Buffer untiledBuffer, uint32_t untiledOffset, uint32_t firstSlice, uint32_t numSlices); protected: vk::Device mDevice; vk::PipelineLayout mPipelineLayout; vk::ShaderModule mShader; std::map<uint32_t, vk::Pipeline> mPipelines; vk::DescriptorSetLayout mDescriptorSetLayout; std::vector<HandleImpl *> mHandlesPool; }; } #endif // DECAF_VULKAN ================================================ FILE: src/libgpu/gpu_config.h ================================================ #pragma once #include <array> #include <cstdint> #include <memory> #include <vector> namespace gpu { struct DebugSettings { //! Enable debugging bool debug_enabled = false; //! Dump shaders bool dump_shaders = false; //! Only dump shader binaries bool dump_shader_binaries_only = false; }; struct DisplaySettings { enum Backend { Null, // Previously OpenGL = 1 Vulkan = 2, }; enum ScreenMode { Windowed, Fullscreen, }; enum ViewMode { Split, TV, Gamepad1, Gamepad2, }; Backend backend = Backend::Vulkan; std::array<int, 3> backgroundColour = { 153, 51, 51 }; bool maintainAspectRatio = true; ScreenMode screenMode = ScreenMode::Windowed; double splitSeperation = 5.0; ViewMode viewMode = ViewMode::Split; }; struct Settings { DebugSettings debug; DisplaySettings display; }; std::shared_ptr<const Settings> config(); void setConfig(const Settings &settings); } // namespace gpu ================================================ FILE: src/libgpu/gpu_graphicsdriver.h ================================================ #pragma once #include <fmt/format.h> #include <libcpu/be2_struct.h> #include <cstdint> #include <string> #include <string_view> namespace gpu { enum class GraphicsDriverType { Null, // Previously OpenGL = 1 Vulkan = 2, }; enum class WindowSystemType { Headless, Windows, Cocoa, X11, Xcb, Wayland, }; struct WindowSystemInfo { WindowSystemType type = WindowSystemType::Headless; void *displayConnection = nullptr; void *renderSurface = nullptr; double renderSurfaceScale = 1.0; }; struct GraphicsDriverDebugInfo { GraphicsDriverType type = GraphicsDriverType::Null; double averageFps = 0.0f; double averageFrameTimeMS = 0.0f; }; class GraphicsDriver { public: virtual ~GraphicsDriver() = default; virtual void setWindowSystemInfo(const WindowSystemInfo &wsi) = 0; virtual void windowHandleChanged(void *handle) = 0; virtual void windowSizeChanged(int width, int height) = 0; virtual void run() = 0; virtual void runUntilFlip() = 0; virtual void stop() = 0; virtual GraphicsDriverType type() = 0; virtual GraphicsDriverDebugInfo *getDebugInfo() = 0; // Called for stores to emulated physical RAM, such as via DCFlushRange(). // May be called from any CPU core! virtual void notifyCpuFlush(phys_addr address, uint32_t size) = 0; // Called when the emulated CPU is about to read from emulated physical RAM, // such as after DCInvalidateRange(). May be called from any CPU core! virtual void notifyGpuFlush(phys_addr address, uint32_t size) = 0; // Begin a frame capture, frames will be dumped to outPrefix0.tga -> outPrefixN.tga virtual bool startFrameCapture(const std::string &outPrefix, bool captureTV, bool captureDRC) { return false; } virtual size_t stopFrameCapture() { return 0; } }; GraphicsDriver * createGraphicsDriver(); GraphicsDriver * createGraphicsDriver(GraphicsDriverType type); } // namespace gpu template <> struct fmt::formatter<gpu::WindowSystemType> : fmt::formatter<std::string_view> { template <typename FormatContext> auto format(gpu::WindowSystemType value, FormatContext& ctx) { std::string_view name = "unknown"; switch (value) { case gpu::WindowSystemType::Headless: name = "Headless"; break; case gpu::WindowSystemType::Windows: name = "Windows"; break; case gpu::WindowSystemType::Cocoa: name = "Cocoa"; break; case gpu::WindowSystemType::X11: name = "X11"; break; case gpu::WindowSystemType::Xcb: name = "Xcb"; break; case gpu::WindowSystemType::Wayland: name = "Wayland"; break; } return fmt::formatter<string_view>::format(name, ctx); } }; ================================================ FILE: src/libgpu/gpu_ih.h ================================================ #pragma once #include "latte/latte_enum_cp.h" #include "latte/latte_registers_cp.h" #include <cstdint> #include <gsl/gsl-lite.hpp> namespace gpu::ih { struct Entry { uint32_t word0; uint32_t word1; uint32_t word2; uint32_t word3; }; using Entries = gsl::span<const Entry>; using InterruptCallbackFn = void(*) (); void write(const Entries &entries); inline void write(const Entry &entry) { write({ &entry, 1 }); } Entries read(); void setInterruptCallback(InterruptCallbackFn callback); void enable(latte::CP_INT_CNTL cntl); void disable(latte::CP_INT_CNTL cntl); } // namespace gpu::ih ================================================ FILE: src/libgpu/gpu_memory.h ================================================ #pragma once #include <libcpu/be2_struct.h> namespace gpu::internal { template<typename Type = void> Type * translateAddress(phys_addr address) { return cpu::internal::translate<Type>(address); } } // namespace gpu::internal ================================================ FILE: src/libgpu/gpu_ringbuffer.h ================================================ #pragma once #include <cstdint> #include <gsl/gsl-lite.hpp> namespace gpu::ringbuffer { using Buffer = gsl::span<uint32_t>; void write(const Buffer &buffer); Buffer read(); bool wait(); void wake(); } // namespace gpu::ringbuffer ================================================ FILE: src/libgpu/gpu_tiling.h ================================================ #pragma once #include "latte/latte_enum_sq.h" #include <addrlib/addrinterface.h> namespace gpu { ADDR_HANDLE getAddrLibHandle(); void alignTiling(latte::SQ_TILE_MODE& tileMode, latte::SQ_DATA_FORMAT& format, uint32_t& swizzle, uint32_t& pitch, uint32_t& width, uint32_t& height, uint32_t& depth, uint32_t& aa, bool& isDepth, uint32_t& bpp); bool copySurfacePixels(uint8_t *dstBasePtr, uint32_t dstWidth, uint32_t dstHeight, ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &dstAddrInput, uint8_t *srcBasePtr, uint32_t srcWidth, uint32_t srcHeight, ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &srcAddrInput); bool convertFromTiled(uint8_t *output, uint32_t outputPitch, uint8_t *input, latte::SQ_TILE_MODE tileMode, uint32_t swizzle, uint32_t pitch, uint32_t width, uint32_t height, uint32_t depth, uint32_t aa, bool isDepth, uint32_t bpp, uint32_t beginSlice = 0, uint32_t endSlice = 0); bool convertToTiled(uint8_t *output, uint8_t *input, uint32_t inputPitch, latte::SQ_TILE_MODE tileMode, uint32_t swizzle, uint32_t pitch, uint32_t width, uint32_t height, uint32_t depth, uint32_t aa, bool isDepth, uint32_t bpp, uint32_t beginSlice = 0, uint32_t endSlice = 0); } // namespace gpu ================================================ FILE: src/libgpu/gpu_vulkandriver.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "gpu_graphicsdriver.h" #include <common/vulkan_hpp.h> namespace gpu { struct VulkanDriverDebugInfo : GraphicsDriverDebugInfo { VulkanDriverDebugInfo() { type = GraphicsDriverType::Vulkan; } uint64_t numVertexShaders = 0; uint64_t numGeometryShaders = 0; uint64_t numPixelShaders = 0; uint64_t numRenderPasses = 0; uint64_t numPipelines = 0; uint64_t numSamplers = 0; uint64_t numSurfaces = 0; uint64_t numDataBuffers = 0; }; } // namespace gpu #endif // DECAF_VULKAN ================================================ FILE: src/libgpu/latte/latte_constants.h ================================================ #pragma once namespace latte { static const unsigned MaxAttribBuffers = 16u; static const unsigned MaxRenderTargets = 8u; static const unsigned MaxSamplers = 16u; static const unsigned MaxStreamOutBuffers = 4u; static const unsigned MaxTextures = 16u; static const unsigned MaxUniformRegisters = 256u; static const unsigned MaxUniformBlocks = 16u; static const unsigned MaxUniformBlockSize = 65536u; } // namespace latte ================================================ FILE: src/libgpu/latte/latte_contextstate.h ================================================ #pragma once #include "latte_registers.h" #include <common/bitfield.h> #include <libcpu/be2_struct.h> #include <cstdint> #pragma pack(push, 1) namespace latte { BITFIELD_BEG(CONTEXT_CONTROL_ENABLE, uint32_t) BITFIELD_ENTRY(0, 1, bool, ENABLE_CONFIG_REG); BITFIELD_ENTRY(1, 1, bool, ENABLE_CONTEXT_REG); BITFIELD_ENTRY(2, 1, bool, ENABLE_ALU_CONST); BITFIELD_ENTRY(3, 1, bool, ENABLE_BOOL_CONST); BITFIELD_ENTRY(4, 1, bool, ENABLE_LOOP_CONST); BITFIELD_ENTRY(5, 1, bool, ENABLE_RESOURCE); BITFIELD_ENTRY(6, 1, bool, ENABLE_SAMPLER); BITFIELD_ENTRY(7, 1, bool, ENABLE_CTL_CONST); BITFIELD_ENTRY(31, 1, bool, ENABLE_ORDINAL); BITFIELD_END struct ShadowState { CONTEXT_CONTROL_ENABLE LOAD_CONTROL = CONTEXT_CONTROL_ENABLE::get(0); CONTEXT_CONTROL_ENABLE SHADOW_ENABLE = CONTEXT_CONTROL_ENABLE::get(0); phys_ptr<uint32_t> CONFIG_REG_BASE = nullptr; phys_ptr<uint32_t> CONTEXT_REG_BASE = nullptr; phys_ptr<uint32_t> ALU_CONST_BASE = nullptr; phys_ptr<uint32_t> BOOL_CONST_BASE = nullptr; phys_ptr<uint32_t> LOOP_CONST_BASE = nullptr; phys_ptr<uint32_t> RESOURCE_CONST_BASE = nullptr; phys_ptr<uint32_t> SAMPLER_CONST_BASE = nullptr; phys_ptr<uint32_t> CTL_CONST_BASE = nullptr; }; } // namespace latte #pragma pack(pop) ================================================ FILE: src/libgpu/latte/latte_disassembler.h ================================================ #pragma once #include <gsl/gsl-lite.hpp> namespace latte { std::string disassemble(const gsl::span<const uint8_t> &binary, bool isSubroutine = false); } // namespace latte ================================================ FILE: src/libgpu/latte/latte_enum_as_string.cpp ================================================ #include "latte_enum_as_string.h" #undef LATTE_ENUM_CB_H #undef LATTE_ENUM_COMMON_H #undef LATTE_ENUM_DB_H #undef LATTE_ENUM_PA_H #undef LATTE_ENUM_PM4_H #undef LATTE_ENUM_SPI_H #undef LATTE_ENUM_SQ_H #undef LATTE_ENUM_VGT_H #include <common/enum_string_define.inl> #include "latte_enum_cb.h" #include <common/enum_string_define.inl> #include "latte_enum_common.h" #include <common/enum_string_define.inl> #include "latte_enum_db.h" #include <common/enum_string_define.inl> #include "latte_enum_pa.h" #include <common/enum_string_define.inl> #include "latte_enum_pm4.h" #include <common/enum_string_define.inl> #include "latte_enum_spi.h" // TODO: Fix collision in SQ_RES_OFFSET // #include <common/enum_string_define.inl> // #include "latte_enum_sq.h" #include <common/enum_string_define.inl> #include "latte_enum_vgt.h" ================================================ FILE: src/libgpu/latte/latte_enum_as_string.h ================================================ #pragma once #include <cstdint> #include <string> #include "latte_enum_cb.h" #include "latte_enum_common.h" #include "latte_enum_db.h" #include "latte_enum_pa.h" #include "latte_enum_pm4.h" #include "latte_enum_spi.h" #include "latte_enum_sq.h" #include "latte_enum_vgt.h" #undef LATTE_ENUM_CB_H #undef LATTE_ENUM_COMMON_H #undef LATTE_ENUM_DB_H #undef LATTE_ENUM_PA_H #undef LATTE_ENUM_PM4_H #undef LATTE_ENUM_SPI_H #undef LATTE_ENUM_SQ_H #undef LATTE_ENUM_VGT_H #include <common/enum_string_declare.inl> #include "latte_enum_cb.h" #include <common/enum_string_declare.inl> #include "latte_enum_common.h" #include <common/enum_string_declare.inl> #include "latte_enum_db.h" #include <common/enum_string_declare.inl> #include "latte_enum_pa.h" #include <common/enum_string_declare.inl> #include "latte_enum_pm4.h" #include <common/enum_string_declare.inl> #include "latte_enum_spi.h" // TODO: Fix collision in SQ_RES_OFFSET // #include <common/enum_string_declare.inl> // #include "latte_enum_sq.h" #include <common/enum_string_declare.inl> #include "latte_enum_vgt.h" ================================================ FILE: src/libgpu/latte/latte_enum_cb.h ================================================ #ifndef LATTE_ENUM_CB_H #define LATTE_ENUM_CB_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(CB_BLEND_FUNC, uint32_t) ENUM_VALUE(ZERO, 0) ENUM_VALUE(ONE, 1) ENUM_VALUE(SRC_COLOR, 2) ENUM_VALUE(ONE_MINUS_SRC_COLOR, 3) ENUM_VALUE(SRC_ALPHA, 4) ENUM_VALUE(ONE_MINUS_SRC_ALPHA, 5) ENUM_VALUE(DST_ALPHA, 6) ENUM_VALUE(ONE_MINUS_DST_ALPHA, 7) ENUM_VALUE(DST_COLOR, 8) ENUM_VALUE(ONE_MINUS_DST_COLOR, 9) ENUM_VALUE(SRC_ALPHA_SATURATE, 10) ENUM_VALUE(BOTH_SRC_ALPHA, 11) ENUM_VALUE(BOTH_INV_SRC_ALPHA, 12) ENUM_VALUE(CONSTANT_COLOR, 13) ENUM_VALUE(ONE_MINUS_CONSTANT_COLOR, 14) ENUM_VALUE(SRC1_COLOR, 15) ENUM_VALUE(ONE_MINUS_SRC1_COLOR, 16) ENUM_VALUE(SRC1_ALPHA, 17) ENUM_VALUE(ONE_MINUS_SRC1_ALPHA, 18) ENUM_VALUE(CONSTANT_ALPHA, 19) ENUM_VALUE(ONE_MINUS_CONSTANT_ALPHA, 20) ENUM_END(CB_BLEND_FUNC) ENUM_BEG(CB_CLRCMP_DRAW, uint32_t) ENUM_VALUE(ALWAYS, 0) ENUM_VALUE(NEVER, 1) ENUM_VALUE(ON_NEQ, 4) ENUM_VALUE(ON_EQ, 5) ENUM_END(CB_CLRCMP_DRAW) ENUM_BEG(CB_CLRCMP_SEL, uint32_t) ENUM_VALUE(DST, 0) ENUM_VALUE(SRC, 1) ENUM_VALUE(AND, 2) ENUM_END(CB_CLRCMP_SEL) ENUM_BEG(CB_COMB_FUNC, uint32_t) ENUM_VALUE(DST_PLUS_SRC, 0) ENUM_VALUE(SRC_MINUS_DST, 1) ENUM_VALUE(MIN_DST_SRC, 2) ENUM_VALUE(MAX_DST_SRC, 3) ENUM_VALUE(DST_MINUS_SRC, 4) ENUM_END(CB_COMB_FUNC) ENUM_BEG(CB_ENDIAN, uint32_t) ENUM_VALUE(NONE, 0) ENUM_VALUE(SWAP_8IN16, 1) ENUM_VALUE(SWAP_8IN32, 2) ENUM_VALUE(SWAP_8IN64, 3) ENUM_END(CB_ENDIAN) ENUM_BEG(CB_FORMAT, uint32_t) ENUM_VALUE(COLOR_INVALID, 0) ENUM_VALUE(COLOR_8, 1) ENUM_VALUE(COLOR_4_4, 2) ENUM_VALUE(COLOR_3_3_2, 3) ENUM_VALUE(COLOR_16, 5) ENUM_VALUE(COLOR_16_FLOAT, 6) ENUM_VALUE(COLOR_8_8, 7) ENUM_VALUE(COLOR_5_6_5, 8) ENUM_VALUE(COLOR_6_5_5, 9) ENUM_VALUE(COLOR_1_5_5_5, 10) ENUM_VALUE(COLOR_4_4_4_4, 11) ENUM_VALUE(COLOR_5_5_5_1, 12) ENUM_VALUE(COLOR_32, 13) ENUM_VALUE(COLOR_32_FLOAT, 14) ENUM_VALUE(COLOR_16_16, 15) ENUM_VALUE(COLOR_16_16_FLOAT, 16) ENUM_VALUE(COLOR_8_24, 17) ENUM_VALUE(COLOR_8_24_FLOAT, 18) ENUM_VALUE(COLOR_24_8, 19) ENUM_VALUE(COLOR_24_8_FLOAT, 20) ENUM_VALUE(COLOR_10_11_11, 21) ENUM_VALUE(COLOR_10_11_11_FLOAT, 22) ENUM_VALUE(COLOR_11_11_10, 23) ENUM_VALUE(COLOR_11_11_10_FLOAT, 24) ENUM_VALUE(COLOR_2_10_10_10, 25) ENUM_VALUE(COLOR_8_8_8_8, 26) ENUM_VALUE(COLOR_10_10_10_2, 27) ENUM_VALUE(COLOR_X24_8_32_FLOAT, 28) ENUM_VALUE(COLOR_32_32, 29) ENUM_VALUE(COLOR_32_32_FLOAT, 30) ENUM_VALUE(COLOR_16_16_16_16, 31) ENUM_VALUE(COLOR_16_16_16_16_FLOAT, 32) ENUM_VALUE(COLOR_32_32_32_32, 34) ENUM_VALUE(COLOR_32_32_32_32_FLOAT, 35) ENUM_END(CB_FORMAT) ENUM_BEG(CB_NUMBER_TYPE, uint32_t) ENUM_VALUE(UNORM, 0) ENUM_VALUE(SNORM, 1) ENUM_VALUE(USCALED, 2) ENUM_VALUE(SSCALED, 3) ENUM_VALUE(UINT, 4) ENUM_VALUE(SINT, 5) ENUM_VALUE(SRGB, 6) ENUM_VALUE(FLOAT, 7) ENUM_END(CB_NUMBER_TYPE) ENUM_BEG(CB_ROUND_MODE, uint32_t) ENUM_VALUE(BY_HALF, 0) ENUM_VALUE(TRUNCATE, 1) ENUM_END(CB_ROUND_MODE) ENUM_BEG(CB_SOURCE_FORMAT, uint32_t) ENUM_VALUE(EXPORT_FULL, 0) ENUM_VALUE(EXPORT_NORM, 1) ENUM_END(CB_SOURCE_FORMAT) ENUM_BEG(CB_SPECIAL_OP, uint32_t) ENUM_VALUE(NORMAL, 0) ENUM_VALUE(DISABLE, 1) ENUM_VALUE(FAST_CLEAR, 2) ENUM_VALUE(FORCE_CLEAR, 3) ENUM_VALUE(EXPAND_COLOR, 4) ENUM_VALUE(EXPAND_TEXTURE, 5) ENUM_VALUE(EXPAND_SAMPLES, 6) ENUM_VALUE(RESOLVE_BOX, 7) ENUM_END(CB_SPECIAL_OP) ENUM_BEG(CB_COMP_SWAP, uint32_t) ENUM_VALUE(STD, 0) ENUM_VALUE(ALT, 1) ENUM_VALUE(STD_REV, 2) ENUM_VALUE(ALT_REV, 3) ENUM_END(CB_COMP_SWAP) ENUM_BEG(CB_TILE_MODE, uint32_t) ENUM_VALUE(DISABLE, 0) ENUM_VALUE(CLEAR_ENABLE, 1) ENUM_VALUE(FRAG_ENABLE, 2) ENUM_END(CB_TILE_MODE) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_CB_H ================================================ FILE: src/libgpu/latte/latte_enum_common.h ================================================ #ifndef LATTE_ENUM_COMMON_H #define LATTE_ENUM_COMMON_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(BUFFER_ARRAY_MODE, uint32_t) ENUM_VALUE(LINEAR_GENERAL, 0) ENUM_VALUE(LINEAR_ALIGNED, 1) ENUM_VALUE(TILED_2D_THIN1, 4) ENUM_END(BUFFER_ARRAY_MODE) ENUM_BEG(BUFFER_READ_SIZE, uint32_t) ENUM_VALUE(READ_256_BITS, 0) ENUM_VALUE(READ_512_BITS, 1) ENUM_END(BUFFER_READ_SIZE) ENUM_BEG(REF_FUNC, uint32_t) ENUM_VALUE(NEVER, 0) ENUM_VALUE(LESS, 1) ENUM_VALUE(EQUAL, 2) ENUM_VALUE(LESS_EQUAL, 3) ENUM_VALUE(GREATER, 4) ENUM_VALUE(NOT_EQUAL, 5) ENUM_VALUE(GREATER_EQUAL, 6) ENUM_VALUE(ALWAYS, 7) ENUM_END(REF_FUNC) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_COMMON_H ================================================ FILE: src/libgpu/latte/latte_enum_cp.h ================================================ #ifndef LATTE_ENUM_CP_H #define LATTE_ENUM_CP_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) // Interrupt types taken from linux/drivers/gpu/drm/radeon and // avm.rpl dc.rpl tcl.rpl uvd.rpl ENUM_BEG(CP_INT_SRC_ID, uint32_t) ENUM_VALUE(D1_VSYNC, 1) ENUM_VALUE(D1_TRIGA, 2) ENUM_VALUE(D2_VSYNC, 5) ENUM_VALUE(D2_TRIGA, 6) ENUM_VALUE(D1_VUPD, 8) ENUM_VALUE(D1_PFLIP, 9) ENUM_VALUE(D2_VUPD, 10) ENUM_VALUE(D2_PFLIP, 11) ENUM_VALUE(HPD_DAC_HOTPLUG, 19) ENUM_VALUE(HDMI, 21) ENUM_VALUE(DVOCAP, 22) ENUM_VALUE(UVD, 124) ENUM_VALUE(CP_RB, 176) ENUM_VALUE(CP_IB1, 177) ENUM_VALUE(CP_IB2, 178) ENUM_VALUE(CP_RESERVED_BITS, 180) ENUM_VALUE(CP_EOP_EVENT, 181) ENUM_VALUE(SCRATCH, 182) ENUM_VALUE(CP_BAD_OPCODE, 183) ENUM_VALUE(CP_CTX_EMPTY, 187) ENUM_VALUE(CP_CTX_BUSY, 188) ENUM_VALUE(UNKNOWN_192, 192) ENUM_VALUE(DMA_TRAP_EVENT, 224) ENUM_VALUE(DMA_SEM_INCOMPLETE, 225) ENUM_VALUE(DMA_SEM_WAIT, 226) ENUM_VALUE(THERMAL_LOW_TO_HIGH, 230) ENUM_VALUE(THERMAL_HIGH_TO_LOW, 231) ENUM_VALUE(GUI_IDLE, 233) ENUM_VALUE(DMA_CTX_EMPTY, 243) ENUM_END(CP_INT_SRC_ID) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_CP_H ================================================ FILE: src/libgpu/latte/latte_enum_db.h ================================================ #ifndef LATTE_ENUM_DB_H #define LATTE_ENUM_DB_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(DB_FORMAT, uint32_t) ENUM_VALUE(DEPTH_INVALID, 0) ENUM_VALUE(DEPTH_16, 1) ENUM_VALUE(DEPTH_X8_24, 2) ENUM_VALUE(DEPTH_8_24, 3) ENUM_VALUE(DEPTH_X8_24_FLOAT, 4) ENUM_VALUE(DEPTH_8_24_FLOAT, 5) ENUM_VALUE(DEPTH_32_FLOAT, 6) ENUM_VALUE(DEPTH_X24_8_32_FLOAT, 7) ENUM_END(DB_FORMAT) ENUM_BEG(DB_FORCE, uint32_t) ENUM_VALUE(OFF, 0) ENUM_VALUE(ENABLE, 1) ENUM_VALUE(DISABLE, 2) ENUM_END(DB_FORCE) ENUM_BEG(DB_STENCIL_FUNC, uint32_t) ENUM_VALUE(KEEP, 0) ENUM_VALUE(ZERO, 1) ENUM_VALUE(REPLACE, 2) ENUM_VALUE(INCR_CLAMP, 3) ENUM_VALUE(DECR_CLAMP, 4) ENUM_VALUE(INVERT, 5) ENUM_VALUE(INCR_WRAP, 6) ENUM_VALUE(DECR_WRAP, 7) ENUM_END(DB_STENCIL_FUNC) ENUM_BEG(DB_Z_EXPORT, uint32_t) ENUM_VALUE(ANY_Z, 0) ENUM_VALUE(LESS_THAN_Z, 1) ENUM_VALUE(GREATER_THAN_Z, 2) ENUM_END(DB_Z_EXPORT) ENUM_BEG(DB_Z_ORDER, uint32_t) ENUM_VALUE(LATE_Z, 0) ENUM_VALUE(EARLY_Z_THEN_LATE_Z, 1) ENUM_VALUE(RE_Z, 2) ENUM_VALUE(EARLY_Z_THEN_RE_Z, 3) ENUM_END(DB_Z_ORDER) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_DB_H ================================================ FILE: src/libgpu/latte/latte_enum_pa.h ================================================ #ifndef LATTE_ENUM_PA_H #define LATTE_ENUM_PA_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(PA_FACE, uint32_t) ENUM_VALUE(CCW, 0) ENUM_VALUE(CW, 1) ENUM_END(PA_FACE) ENUM_BEG(PA_PTYPE, uint32_t) ENUM_VALUE(POINTS, 0) ENUM_VALUE(LINES, 1) ENUM_VALUE(TRIANGLES, 2) ENUM_END(PA_PTYPE) ENUM_BEG(PA_PS_UCP_MODE, uint32_t) ENUM_VALUE(CULL_DISTANCE, 0) ENUM_VALUE(CULL_RADIUS, 1) ENUM_VALUE(CULL_RADIUS_EXPAND, 2) ENUM_VALUE(CULL_EXPAND, 3) ENUM_END(PA_PS_UCP_MODE) ENUM_BEG(PA_SU_VTX_CNTL_PIX_CENTER, uint32_t) ENUM_VALUE(D3D, 0) ENUM_VALUE(OGL, 1) ENUM_END(PA_SU_VTX_CNTL_PIX_CENTER) ENUM_BEG(PA_SU_VTX_CNTL_ROUND_MODE, uint32_t) ENUM_VALUE(TRUNCATE, 0) ENUM_VALUE(NEAREST, 1) ENUM_VALUE(TO_EVEN, 2) ENUM_VALUE(TO_ODD, 3) ENUM_END(PA_SU_VTX_CNTL_ROUND_MODE) ENUM_BEG(PA_SU_VTX_CNTL_QUANT_MODE, uint32_t) ENUM_VALUE(QUANT_1_16TH, 0) ENUM_VALUE(QUANT_1_8TH, 1) ENUM_VALUE(QUANT_1_4TH, 2) ENUM_VALUE(QUANT_1_2ND, 3) ENUM_VALUE(QUANT_1, 4) ENUM_VALUE(QUANT_1_256TH, 5) ENUM_END(PA_SU_VTX_CNTL_QUANT_MODE) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_PA_H ================================================ FILE: src/libgpu/latte/latte_enum_pm4.h ================================================ #ifndef LATTE_ENUM_PM4_H #define LATTE_ENUM_PM4_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_NAMESPACE_ENTER(pm4) ENUM_BEG(PacketType, uint32_t) ENUM_VALUE(Type0, 0x00) ENUM_VALUE(Type1, 0x01) ENUM_VALUE(Type2, 0x02) ENUM_VALUE(Type3, 0x03) ENUM_END(PacketType) ENUM_BEG(IT_OPCODE, uint32_t) ENUM_VALUE(DECAF_COPY_COLOR_TO_SCAN, 0x01) ENUM_VALUE(DECAF_SWAP_BUFFERS, 0x02) ENUM_VALUE(DECAF_CLEAR_COLOR, 0x03) ENUM_VALUE(DECAF_CLEAR_DEPTH_STENCIL, 0x04) ENUM_VALUE(DECAF_CAP_SYNC_REGISTERS, 0x05) ENUM_VALUE(DECAF_SET_BUFFER, 0x06) ENUM_VALUE(DECAF_COPY_SURFACE, 0x07) ENUM_VALUE(DECAF_EXPAND_COLORBUFFER, 0x08) ENUM_VALUE(DECAF_OSSCREEN_FLIP, 0x09) ENUM_VALUE(NOP, 0x10) ENUM_VALUE(INDIRECT_BUFFER_END, 0x17) ENUM_VALUE(NEXTCHAR, 0x19) ENUM_VALUE(PLY_NEXTSCAN, 0x1D) ENUM_VALUE(SET_SCISSORS, 0x1E) ENUM_VALUE(OCCLUSION_QUERY, 0x1F) ENUM_VALUE(SET_PREDICATION, 0x20) ENUM_VALUE(REG_RMW, 0x21) ENUM_VALUE(COND_EXEC, 0x22) ENUM_VALUE(PRED_EXEC, 0x23) ENUM_VALUE(START_3D_CMDBUF, 0x24) ENUM_VALUE(START_2D_CMDBUF, 0x25) ENUM_VALUE(INDEX_BASE, 0x26) ENUM_VALUE(DRAW_INDEX_2, 0x27) ENUM_VALUE(CONTEXT_CTL, 0x28) ENUM_VALUE(DRAW_INDEX_OFFSET, 0x29) ENUM_VALUE(INDEX_TYPE, 0x2A) ENUM_VALUE(DRAW_INDEX, 0x2B) ENUM_VALUE(LOAD_PALETTE, 0x2C) ENUM_VALUE(DRAW_INDEX_AUTO, 0x2D) ENUM_VALUE(DRAW_INDEX_IMMD, 0x2E) ENUM_VALUE(NUM_INSTANCES, 0x2F) ENUM_VALUE(DRAW_INDEX_MULTI_AUTO, 0x30) ENUM_VALUE(INDIRECT_BUFFER_PRIV, 0x32) ENUM_VALUE(STRMOUT_BUFFER_UPDATE, 0x34) ENUM_VALUE(DRAW_INDEX_OFFSET_2, 0x35) ENUM_VALUE(DRAW_INDEX_MULTI_ELEMENT, 0x36) ENUM_VALUE(INDIRECT_BUFFER_MP, 0x38) ENUM_VALUE(MEM_SEMAPHORE, 0x39) ENUM_VALUE(MPEG_INDEX, 0x3A) ENUM_VALUE(COPY_DW, 0x3B) ENUM_VALUE(WAIT_REG_MEM, 0x3C) ENUM_VALUE(MEM_WRITE, 0x3D) ENUM_VALUE(PER_FRAME, 0x3E) ENUM_VALUE(INDIRECT_BUFFER, 0x3F) ENUM_VALUE(CP_DMA, 0x41) ENUM_VALUE(PFP_SYNC_ME, 0x42) ENUM_VALUE(SURFACE_SYNC, 0x43) ENUM_VALUE(ME_INITIALIZE, 0x44) ENUM_VALUE(COND_WRITE, 0x45) ENUM_VALUE(EVENT_WRITE, 0x46) ENUM_VALUE(EVENT_WRITE_EOP, 0x47) ENUM_VALUE(LOAD_SURFACE_PROBE, 0x48) ENUM_VALUE(SURFACE_PROBE, 0x49) ENUM_VALUE(PREAMBLE_CNTL, 0x4A) ENUM_VALUE(RB_OFFSET, 0x4B) ENUM_VALUE(GFX_CNTX_UPDATE, 0x52) ENUM_VALUE(BLK_CNTX_UPDATE, 0x53) ENUM_VALUE(IB_OFFSET, 0x54) ENUM_VALUE(INCR_UPDT_STATE, 0x55) ENUM_VALUE(INCR_UPDT_CONST, 0x56) ENUM_VALUE(ONE_REG_WRITE, 0x57) ENUM_VALUE(LOAD_CONFIG_REG, 0x60) ENUM_VALUE(LOAD_CONTEXT_REG, 0x61) ENUM_VALUE(LOAD_ALU_CONST, 0x62) ENUM_VALUE(LOAD_BOOL_CONST, 0x63) ENUM_VALUE(LOAD_LOOP_CONST, 0x64) ENUM_VALUE(LOAD_RESOURCE, 0x65) ENUM_VALUE(LOAD_SAMPLER, 0x66) ENUM_VALUE(LOAD_CTL_CONST, 0x67) ENUM_VALUE(SET_CONFIG_REG, 0x68) ENUM_VALUE(SET_CONTEXT_REG, 0x69) ENUM_VALUE(SET_ALU_CONST, 0x6A) ENUM_VALUE(SET_BOOL_CONST, 0x6B) ENUM_VALUE(SET_LOOP_CONST, 0x6C) ENUM_VALUE(SET_RESOURCE, 0x6D) ENUM_VALUE(SET_SAMPLER, 0x6E) ENUM_VALUE(SET_CTL_CONST, 0x6F) ENUM_VALUE(SET_RESOURCE_OFFSET, 0x70) ENUM_VALUE(STRMOUT_BASE_UPDATE, 0x72) ENUM_VALUE(SURFACE_BASE_UPDATE, 0x73) ENUM_VALUE(SET_ALL_CONTEXTS, 0x74) ENUM_VALUE(INDIRECT_BUFFER_BASE, 0x78) ENUM_VALUE(EXECUTE_IB2, 0x79) ENUM_VALUE(PFP_REG_WR, 0x7B) ENUM_VALUE(FORWARD_HEADER, 0x7C) ENUM_VALUE(PAINT, 0x91) ENUM_VALUE(BITBLT, 0x92) ENUM_VALUE(HOSTDATA_BLT, 0x94) ENUM_VALUE(POLYLINE, 0x95) ENUM_VALUE(POLYSCANLINES, 0x98) ENUM_VALUE(PAINT_MULTI, 0x9A) ENUM_VALUE(BITBLT_MULTI, 0x9B) ENUM_VALUE(TRANS_BITBLT, 0x9C) ENUM_VALUE(DRAW_2D_DIRTY_AREA, 0xFF) ENUM_END(IT_OPCODE) ENUM_NAMESPACE_EXIT(pm4) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_PM4_H ================================================ FILE: src/libgpu/latte/latte_enum_spi.h ================================================ #ifndef LATTE_ENUM_SPI_H #define LATTE_ENUM_SPI_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(SPI_BARYC_CNTL, uint32_t) ENUM_VALUE(CENTROIDS_ONLY, 0) ENUM_VALUE(CENTERS_ONLY, 1) ENUM_VALUE(CENTROIDS_AND_CENTERS, 2) ENUM_END(SPI_BARYC_CNTL) ENUM_BEG(SPI_FOG_FUNC, uint32_t) ENUM_VALUE(NONE, 0) ENUM_VALUE(EXP, 1) ENUM_VALUE(EXP2, 2) ENUM_VALUE(LINEAR, 3) ENUM_END(SPI_FOG_FUNC) ENUM_BEG(SPI_FOG_SRC_SEL, uint32_t) ENUM_VALUE(SEL_Z, 0) ENUM_VALUE(SEL_W, 1) ENUM_END(SPI_FOG_SRC_SEL) ENUM_BEG(SPI_PNT_SPRITE_SEL, uint32_t) ENUM_VALUE(SEL_0, 0) ENUM_VALUE(SEL_1, 1) ENUM_VALUE(SEL_S, 2) ENUM_VALUE(SEL_T, 3) ENUM_VALUE(SEL_NONE, 4) ENUM_END(SPI_PNT_SPRITE_SEL) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_SPI_H ================================================ FILE: src/libgpu/latte/latte_enum_sq.h ================================================ #ifndef LATTE_ENUM_SQ_H #define LATTE_ENUM_SQ_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(SQ_ALU_ENCODING, uint32_t) ENUM_VALUE(OP2, 0) // OP3 is not a specific value, only that it the encoding != OP2. This // is because ALU_ENCODING is actually multiple bits, where 0 means OP2. //ENUM_VALUE(OP3, NOT_ZERO) ENUM_END(SQ_ALU_ENCODING) ENUM_BEG(SQ_ALU_OMOD, uint32_t) ENUM_VALUE(OFF, 0) ENUM_VALUE(M2, 1) ENUM_VALUE(M4, 2) ENUM_VALUE(D2, 3) ENUM_END(SQ_ALU_OMOD) ENUM_BEG(SQ_ALU_SRC, uint32_t) ENUM_VALUE(REGISTER_FIRST, 0) ENUM_VALUE(REGISTER_TEMP_FIRST, 120) ENUM_VALUE(REGISTER_LAST, 127) ENUM_VALUE(KCACHE_BANK0_FIRST, 128) ENUM_VALUE(KCACHE_BANK0_LAST, 159) ENUM_VALUE(KCACHE_BANK1_FIRST, 160) ENUM_VALUE(KCACHE_BANK1_LAST, 191) ENUM_VALUE(LDS_OQ_A, 219) ENUM_VALUE(LDS_OQ_B, 220) ENUM_VALUE(LDS_OQ_A_POP, 221) ENUM_VALUE(LDS_OQ_B_POP, 222) ENUM_VALUE(LDS_DIRECT_A, 223) ENUM_VALUE(LDS_DIRECT_B, 224) ENUM_VALUE(TIME_HI, 227) ENUM_VALUE(TIME_LO, 228) ENUM_VALUE(MASK_HI, 229) ENUM_VALUE(MASK_LO, 230) ENUM_VALUE(HW_WAVE_ID, 231) ENUM_VALUE(SIMD_ID, 232) ENUM_VALUE(SE_ID, 233) ENUM_VALUE(HW_THREADGRP_ID, 234) ENUM_VALUE(WAVE_ID_IN_GRP, 235) ENUM_VALUE(NUM_THREADGRP_WAVES, 236) ENUM_VALUE(HW_ALU_ODD, 237) ENUM_VALUE(LOOP_IDX, 238) ENUM_VALUE(PARAM_BASE_ADDR, 240) ENUM_VALUE(NEW_PRIM_MASK, 241) ENUM_VALUE(PRIM_MASK_HI, 242) ENUM_VALUE(PRIM_MASK_LO, 243) ENUM_VALUE(IMM_1_DBL_L, 244) ENUM_VALUE(IMM_1_DBL_M, 245) ENUM_VALUE(IMM_0_5_DBL_L, 246) ENUM_VALUE(IMM_0_5_DBL_M, 247) ENUM_VALUE(IMM_0, 248) ENUM_VALUE(IMM_1, 249) ENUM_VALUE(IMM_1_INT, 250) ENUM_VALUE(IMM_M_1_INT, 251) ENUM_VALUE(IMM_0_5, 252) ENUM_VALUE(LITERAL, 253) ENUM_VALUE(PV, 254) ENUM_VALUE(PS, 255) ENUM_VALUE(CONST_FILE_FIRST, 256) ENUM_VALUE(CONST_FILE_LAST, 511) ENUM_END(SQ_ALU_SRC) ENUM_BEG(SQ_ALU_VEC_BANK_SWIZZLE, uint32_t) ENUM_VALUE(VEC_012, 0) ENUM_VALUE(VEC_021, 1) ENUM_VALUE(VEC_120, 2) ENUM_VALUE(VEC_102, 3) ENUM_VALUE(VEC_201, 4) ENUM_VALUE(VEC_210, 5) ENUM_END(SQ_ALU_VEC_BANK_SWIZZLE) ENUM_BEG(SQ_ALU_SCL_BANK_SWIZZLE, uint32_t) ENUM_VALUE(SCL_210, 0) ENUM_VALUE(SCL_122, 1) ENUM_VALUE(SCL_212, 2) ENUM_VALUE(SCL_221, 3) ENUM_END(SQ_ALU_SCL_BANK_SWIZZLE) ENUM_BEG(SQ_CF_COND, uint32_t) ENUM_VALUE(ACTIVE, 0) ENUM_VALUE(ALWAYS_FALSE, 1) ENUM_VALUE(CF_BOOL, 2) ENUM_VALUE(CF_NOT_BOOL, 3) ENUM_END(SQ_CF_COND) ENUM_BEG(SQ_DATA_FORMAT, uint32_t) ENUM_VALUE(FMT_INVALID, 0) ENUM_VALUE(FMT_8, 1) ENUM_VALUE(FMT_4_4, 2) ENUM_VALUE(FMT_3_3_2, 3) ENUM_VALUE(FMT_16, 5) ENUM_VALUE(FMT_16_FLOAT, 6) ENUM_VALUE(FMT_8_8, 7) ENUM_VALUE(FMT_5_6_5, 8) ENUM_VALUE(FMT_6_5_5, 9) ENUM_VALUE(FMT_1_5_5_5, 10) ENUM_VALUE(FMT_4_4_4_4, 11) ENUM_VALUE(FMT_5_5_5_1, 12) ENUM_VALUE(FMT_32, 13) ENUM_VALUE(FMT_32_FLOAT, 14) ENUM_VALUE(FMT_16_16, 15) ENUM_VALUE(FMT_16_16_FLOAT, 16) ENUM_VALUE(FMT_8_24, 17) ENUM_VALUE(FMT_8_24_FLOAT, 18) ENUM_VALUE(FMT_24_8, 19) ENUM_VALUE(FMT_24_8_FLOAT, 20) ENUM_VALUE(FMT_10_11_11, 21) ENUM_VALUE(FMT_10_11_11_FLOAT, 22) ENUM_VALUE(FMT_11_11_10, 23) ENUM_VALUE(FMT_11_11_10_FLOAT, 24) ENUM_VALUE(FMT_2_10_10_10, 25) ENUM_VALUE(FMT_8_8_8_8, 26) ENUM_VALUE(FMT_10_10_10_2, 27) ENUM_VALUE(FMT_X24_8_32_FLOAT, 28) ENUM_VALUE(FMT_32_32, 29) ENUM_VALUE(FMT_32_32_FLOAT, 30) ENUM_VALUE(FMT_16_16_16_16, 31) ENUM_VALUE(FMT_16_16_16_16_FLOAT, 32) ENUM_VALUE(FMT_32_32_32_32, 34) ENUM_VALUE(FMT_32_32_32_32_FLOAT, 35) ENUM_VALUE(FMT_1, 37) ENUM_VALUE(FMT_GB_GR, 39) ENUM_VALUE(FMT_BG_RG, 40) ENUM_VALUE(FMT_32_AS_8, 41) ENUM_VALUE(FMT_32_AS_8_8, 42) ENUM_VALUE(FMT_5_9_9_9_SHAREDEXP, 43) ENUM_VALUE(FMT_8_8_8, 44) ENUM_VALUE(FMT_16_16_16, 45) ENUM_VALUE(FMT_16_16_16_FLOAT, 46) ENUM_VALUE(FMT_32_32_32, 47) ENUM_VALUE(FMT_32_32_32_FLOAT, 48) ENUM_VALUE(FMT_BC1, 49) ENUM_VALUE(FMT_BC2, 50) ENUM_VALUE(FMT_BC3, 51) ENUM_VALUE(FMT_BC4, 52) ENUM_VALUE(FMT_BC5, 53) ENUM_VALUE(FMT_APC0, 54) ENUM_VALUE(FMT_APC1, 55) ENUM_VALUE(FMT_APC2, 56) ENUM_VALUE(FMT_APC3, 57) ENUM_VALUE(FMT_APC4, 58) ENUM_VALUE(FMT_APC5, 59) ENUM_VALUE(FMT_APC6, 60) ENUM_VALUE(FMT_APC7, 61) ENUM_VALUE(FMT_CTX1, 62) ENUM_VALUE(FMT_MASK, 0x3F) ENUM_END(SQ_DATA_FORMAT) ENUM_BEG(SQ_EXPORT_TYPE, uint32_t) ENUM_VALUE(PIXEL, 0) ENUM_VALUE(POS, 1) ENUM_VALUE(PARAM, 2) ENUM_END(SQ_EXPORT_TYPE) ENUM_BEG(SQ_MEM_EXPORT_TYPE, uint32_t) ENUM_VALUE(WRITE, 0) ENUM_VALUE(WRITE_IND, 1) ENUM_VALUE(READ, 2) ENUM_VALUE(READ_IND, 3) ENUM_END(SQ_MEM_EXPORT_TYPE) ENUM_BEG(SQ_CF_KCACHE_MODE, uint32_t) ENUM_VALUE(NOP, 0) ENUM_VALUE(LOCK_1, 1) ENUM_VALUE(LOCK_2, 2) ENUM_VALUE(LOCK_LOOP_INDEX, 3) ENUM_END(SQ_CF_KCACHE_MODE) ENUM_BEG(SQ_CHAN, uint32_t) ENUM_VALUE(X, 0) ENUM_VALUE(Y, 1) ENUM_VALUE(Z, 2) ENUM_VALUE(W, 3) ENUM_VALUE(T, 4) ENUM_END(SQ_CHAN) ENUM_BEG(SQ_ENDIAN, uint32_t) ENUM_VALUE(NONE, 0) ENUM_VALUE(SWAP_8IN16, 1) ENUM_VALUE(SWAP_8IN32, 2) ENUM_VALUE(AUTO, 3) ENUM_END(SQ_ENDIAN) ENUM_BEG(SQ_FORMAT_COMP, uint32_t) ENUM_VALUE(UNSIGNED, 0) ENUM_VALUE(SIGNED, 1) ENUM_END(SQ_FORMAT_COMP) ENUM_BEG(SQ_INDEX_MODE, uint32_t) ENUM_VALUE(AR_X, 0) ENUM_VALUE(AR_Y, 1) ENUM_VALUE(AR_Z, 2) ENUM_VALUE(AR_W, 3) ENUM_VALUE(LOOP, 4) ENUM_END(SQ_INDEX_MODE) ENUM_BEG(SQ_NUM_FORMAT, uint32_t) ENUM_VALUE(NORM, 0) ENUM_VALUE(INT, 1) ENUM_VALUE(SCALED, 2) ENUM_END(SQ_NUM_FORMAT) ENUM_BEG(SQ_PRED_SEL, uint32_t) ENUM_VALUE(OFF, 0) ENUM_VALUE(ZERO, 2) ENUM_VALUE(ONE, 3) ENUM_END(SQ_PRED_SEL) ENUM_BEG(SQ_SRF_MODE, uint32_t) ENUM_VALUE(ZERO_CLAMP_MINUS_ONE, 0) ENUM_VALUE(NO_ZERO, 1) ENUM_END(SQ_SRF_MODE) ENUM_BEG(SQ_REL, uint32_t) ENUM_VALUE(ABS, 0) ENUM_VALUE(REL, 1) ENUM_END(SQ_REL) ENUM_BEG(SQ_RES_OFFSET, uint32_t) ENUM_VALUE(PS_TEX_RESOURCE_0, 0x0) ENUM_VALUE(PS_BUF_RESOURCE_0, 0x80) ENUM_VALUE(VS_TEX_RESOURCE_0, 0xA0) ENUM_VALUE(VS_BUF_RESOURCE_0, 0x120) ENUM_VALUE(VS_GSOUT_RESOURCE, 0x13F) ENUM_VALUE(VS_ATTRIB_RESOURCE_0, 0x140) ENUM_VALUE(GS_TEX_RESOURCE_0, 0x150) ENUM_VALUE(GS_BUF_RESOURCE_0, 0x1D0) ENUM_VALUE(GS_GSIN_RESOURCE, 0x1EF) ENUM_END(SQ_RES_OFFSET) ENUM_BEG(SQ_SEL, uint32_t) ENUM_VALUE(SEL_X, 0) ENUM_VALUE(SEL_Y, 1) ENUM_VALUE(SEL_Z, 2) ENUM_VALUE(SEL_W, 3) ENUM_VALUE(SEL_0, 4) ENUM_VALUE(SEL_1, 5) ENUM_VALUE(SEL_MASK, 7) ENUM_END(SQ_SEL) ENUM_BEG(SQ_TEX_COORD_TYPE, uint32_t) ENUM_VALUE(UNNORMALIZED, 0) ENUM_VALUE(NORMALIZED, 1) ENUM_END(SQ_TEX_COORD_TYPE) ENUM_BEG(SQ_TEX_DIM, uint32_t) ENUM_VALUE(DIM_1D, 0) ENUM_VALUE(DIM_2D, 1) ENUM_VALUE(DIM_3D, 2) ENUM_VALUE(DIM_CUBEMAP, 3) ENUM_VALUE(DIM_1D_ARRAY, 4) ENUM_VALUE(DIM_2D_ARRAY, 5) ENUM_VALUE(DIM_2D_MSAA, 6) ENUM_VALUE(DIM_2D_ARRAY_MSAA, 7) ENUM_END(SQ_TEX_DIM) ENUM_BEG(SQ_TEX_VTX_TYPE, uint32_t) ENUM_VALUE(INVALID_TEXTURE, 0) ENUM_VALUE(INVALID_BUFFER, 1) ENUM_VALUE(VALID_TEXTURE, 2) ENUM_VALUE(VALID_BUFFER, 3) ENUM_END(SQ_TEX_VTX_TYPE) ENUM_BEG(SQ_TILE_TYPE, uint32_t) ENUM_VALUE(DEFAULT, 0) ENUM_VALUE(DEPTH, 1) ENUM_END(SQ_TILE_TYPE) ENUM_BEG(SQ_TILE_MODE, uint32_t) ENUM_VALUE(DEFAULT, 0) ENUM_VALUE(LINEAR_ALIGNED, 1) ENUM_VALUE(TILED_1D_THIN1, 2) ENUM_VALUE(TILED_1D_THICK, 3) ENUM_VALUE(TILED_2D_THIN1, 4) ENUM_VALUE(TILED_2D_THIN2, 5) ENUM_VALUE(TILED_2D_THIN4, 6) ENUM_VALUE(TILED_2D_THICK, 7) ENUM_VALUE(TILED_2B_THIN1, 8) ENUM_VALUE(TILED_2B_THIN2, 9) ENUM_VALUE(TILED_2B_THIN4, 10) ENUM_VALUE(TILED_2B_THICK, 11) ENUM_VALUE(TILED_3D_THIN1, 12) ENUM_VALUE(TILED_3D_THICK, 13) ENUM_VALUE(TILED_3B_THIN1, 14) ENUM_VALUE(TILED_3B_THICK, 15) ENUM_END(SQ_TILE_MODE) ENUM_BEG(SQ_VTX_CLAMP, uint32_t) ENUM_VALUE(TO_ZERO, 0) ENUM_VALUE(TO_NAN, 1) ENUM_END(SQ_VTX_CLAMP) ENUM_BEG(SQ_VTX_FETCH_TYPE, uint32_t) ENUM_VALUE(VERTEX_DATA, 0) ENUM_VALUE(INSTANCE_DATA, 1) ENUM_VALUE(NO_INDEX_OFFSET, 2) ENUM_END(SQ_VTX_FETCH_TYPE) ENUM_BEG(SQ_TEX_CLAMP, uint32_t) ENUM_VALUE(WRAP, 0) ENUM_VALUE(MIRROR, 1) ENUM_VALUE(CLAMP_LAST_TEXEL, 2) ENUM_VALUE(MIRROR_ONCE_LAST_TEXEL, 3) ENUM_VALUE(CLAMP_HALF_BORDER, 4) ENUM_VALUE(MIRROR_ONCE_HALF_BORDER, 5) ENUM_VALUE(CLAMP_BORDER, 6) ENUM_VALUE(MIRROR_ONCE_BORDER, 7) ENUM_END(SQ_TEX_CLAMP) ENUM_BEG(SQ_TEX_ANISO, uint32_t) ENUM_VALUE(ANISO_1_TO_1, 0) ENUM_VALUE(ANISO_2_TO_1, 1) ENUM_VALUE(ANISO_4_TO_1, 2) ENUM_VALUE(ANISO_8_TO_1, 3) ENUM_VALUE(ANISO_16_TO_1, 4) ENUM_END(SQ_TEX_ANISO) ENUM_BEG(SQ_TEX_BORDER_COLOR, uint32_t) ENUM_VALUE(TRANS_BLACK, 0) ENUM_VALUE(OPAQUE_BLACK, 1) ENUM_VALUE(OPAQUE_WHITE, 2) ENUM_VALUE(REGISTER, 3) ENUM_END(SQ_TEX_BORDER_COLOR) ENUM_BEG(SQ_TEX_CHROMA_KEY, uint32_t) ENUM_VALUE(DISABLED, 0) ENUM_VALUE(KILL, 1) ENUM_VALUE(BLEND, 2) ENUM_END(SQ_TEX_CHROMA_KEY) ENUM_BEG(SQ_TEX_MPEG_CLAMP, uint32_t) ENUM_VALUE(OFF, 0) ENUM_VALUE(CLAMP_9, 1) ENUM_VALUE(CLAMP_10, 2) ENUM_END(SQ_TEX_MPEG_CLAMP) ENUM_BEG(SQ_TEX_ROUNDING_MODE, uint32_t) ENUM_VALUE(TRUNCATE, 0) ENUM_VALUE(NEAREST_EVEN, 1) ENUM_END(SQ_TEX_ROUNDING_MODE) ENUM_BEG(SQ_TEX_XY_FILTER, uint32_t) ENUM_VALUE(POINT, 0) ENUM_VALUE(BILINEAR, 1) ENUM_VALUE(BICUBIC, 2) ENUM_END(SQ_TEX_XY_FILTER) ENUM_BEG(SQ_TEX_Z_FILTER, uint32_t) ENUM_VALUE(NONE, 0) ENUM_VALUE(POINT, 1) ENUM_VALUE(LINEAR, 2) ENUM_END(SQ_TEX_Z_FILTER) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_SQ_H ================================================ FILE: src/libgpu/latte/latte_enum_vgt.h ================================================ #ifndef LATTE_ENUM_VGT_H #define LATTE_ENUM_VGT_H #include <common/enum_start.inl> ENUM_NAMESPACE_ENTER(latte) ENUM_BEG(VGT_DI_MAJOR_MODE, uint32_t) ENUM_VALUE(MODE0, 0) ENUM_VALUE(MODE1, 1) ENUM_END(VGT_DI_MAJOR_MODE) ENUM_BEG(VGT_DI_PRIMITIVE_TYPE, uint32_t) ENUM_VALUE(NONE, 0) ENUM_VALUE(POINTLIST, 1) ENUM_VALUE(LINELIST, 2) ENUM_VALUE(LINESTRIP, 3) ENUM_VALUE(TRILIST, 4) ENUM_VALUE(TRIFAN, 5) ENUM_VALUE(TRISTRIP, 6) ENUM_VALUE(UNUSED_0, 7) ENUM_VALUE(UNUSED_1, 8) ENUM_VALUE(UNUSED_2, 9) ENUM_VALUE(LINELIST_ADJ, 10) ENUM_VALUE(LINESTRIP_ADJ, 11) ENUM_VALUE(TRILIST_ADJ, 12) ENUM_VALUE(TRISTRIP_ADJ, 13) ENUM_VALUE(UNUSED_3, 14) ENUM_VALUE(UNUSED_4, 15) ENUM_VALUE(TRI_WITH_WFLAGS, 16) ENUM_VALUE(RECTLIST, 17) ENUM_VALUE(LINELOOP, 18) ENUM_VALUE(QUADLIST, 19) ENUM_VALUE(QUADSTRIP, 20) ENUM_VALUE(POLYGON, 21) ENUM_VALUE(COPY_RECT_LIST_2D_V0, 22) ENUM_VALUE(COPY_RECT_LIST_2D_V1, 23) ENUM_VALUE(COPY_RECT_LIST_2D_V2, 24) ENUM_VALUE(COPY_RECT_LIST_2D_V3, 25) ENUM_VALUE(FILL_RECT_LIST_2D, 26) ENUM_VALUE(LINE_STRIP_2D, 27) ENUM_VALUE(TRI_STRIP_2D, 28) ENUM_END(VGT_DI_PRIMITIVE_TYPE) ENUM_BEG(VGT_DI_SRC_SEL, uint32_t) ENUM_VALUE(DMA, 0) ENUM_VALUE(IMMEDIATE, 1) ENUM_VALUE(AUTO_INDEX, 2) ENUM_VALUE(RESERVED, 3) ENUM_END(VGT_DI_SRC_SEL) ENUM_BEG(VGT_DMA_SWAP, uint32_t) ENUM_VALUE(NONE, 0) ENUM_VALUE(SWAP_16_BIT, 1) ENUM_VALUE(SWAP_32_BIT, 2) ENUM_VALUE(SWAP_WORD, 3) ENUM_END(VGT_DMA_SWAP) ENUM_BEG(VGT_GS_CUT_MODE, uint32_t) ENUM_VALUE(CUT_1024, 0) ENUM_VALUE(CUT_512, 1) ENUM_VALUE(CUT_256, 2) ENUM_VALUE(CUT_128, 3) ENUM_END(VGT_GS_CUT_MODE) ENUM_BEG(VGT_GS_ENABLE_MODE, uint32_t) ENUM_VALUE(OFF, 0) ENUM_VALUE(SCENARIO_A, 1) ENUM_VALUE(SCENARIO_B, 2) ENUM_VALUE(SCENARIO_G, 3) ENUM_END(VGT_GS_ENABLE_MODE) ENUM_BEG(VGT_EVENT_TYPE, uint32_t) ENUM_VALUE(CACHE_FLUSH_TS, 4) ENUM_VALUE(CONTEXT_DONE, 5) ENUM_VALUE(CACHE_FLUSH, 6) ENUM_VALUE(VIZQUERY_START, 7) ENUM_VALUE(VIZQUERY_END, 8) ENUM_VALUE(SC_WAIT_WC, 9) ENUM_VALUE(MPASS_PS_CP_REFETCH, 10) ENUM_VALUE(MPASS_PS_RST_START, 11) ENUM_VALUE(MPASS_PS_INCR_START, 12) ENUM_VALUE(RST_PIX_CNT, 13) ENUM_VALUE(RST_VTX_CNT, 14) ENUM_VALUE(VS_PARTIAL_FLUSH, 15) ENUM_VALUE(PS_PARTIAL_FLUSH, 16) ENUM_VALUE(CACHE_FLUSH_AND_INV_TS_EVENT, 20) ENUM_VALUE(ZPASS_DONE, 21) ENUM_VALUE(CACHE_FLUSH_AND_INV_EVENT, 22) ENUM_VALUE(PERFCOUNTER_START, 23) ENUM_VALUE(PERFCOUNTER_STOP, 24) ENUM_VALUE(PIPELINESTAT_START, 25) ENUM_VALUE(PIPELINESTAT_STOP, 26) ENUM_VALUE(PERFCOUNTER_SAMPLE, 27) ENUM_VALUE(FLUSH_ES_OUTPUT, 28) ENUM_VALUE(FLUSH_GS_OUTPUT, 29) ENUM_VALUE(SAMPLE_PIPELINESTAT, 30) ENUM_VALUE(SO_VGTSTREAMOUT_FLUSH, 31) ENUM_VALUE(SAMPLE_STREAMOUTSTATS, 32) ENUM_VALUE(RESET_VTX_CNT, 33) ENUM_VALUE(BLOCK_CONTEXT_DONE, 34) ENUM_VALUE(VGT_FLUSH, 36) ENUM_VALUE(SQ_NON_EVENT, 38) ENUM_VALUE(SC_SEND_DB_VPZ, 39) ENUM_VALUE(BOTTOM_OF_PIPE_TS, 40) ENUM_VALUE(FLUSH_SX_TS, 41) ENUM_VALUE(DB_CACHE_FLUSH_AND_INV, 42) ENUM_VALUE(FLUSH_AND_INV_DB_DATA_TS, 43) ENUM_VALUE(FLUSH_AND_INV_DB_META, 44) ENUM_VALUE(FLUSH_AND_INV_CB_DATA_TS, 45) ENUM_VALUE(FLUSH_AND_INV_CB_META, 46) ENUM_END(VGT_EVENT_TYPE) ENUM_BEG(VGT_EVENT_INDEX, uint32_t) ENUM_VALUE(GENERIC, 0) ENUM_VALUE(ZPASS_DONE, 1) ENUM_VALUE(SAMPLE_PIPELINESTAT, 2) ENUM_VALUE(SAMPLE_STREAMOUTSTAT, 3) ENUM_VALUE(PARTIAL_FLUSH, 4) ENUM_VALUE(TS, 5) ENUM_VALUE(CACHE_FLUSH, 7) ENUM_END(VGT_EVENT_INDEX) ENUM_BEG(VGT_GS_OUT_PRIMITIVE_TYPE, uint32_t) ENUM_VALUE(POINTLIST, 0) ENUM_VALUE(LINESTRIP, 1) ENUM_VALUE(TRISTRIP, 2) ENUM_END(VGT_GS_OUT_PRIMITIVE_TYPE) ENUM_BEG(VGT_INDEX_TYPE, uint32_t) ENUM_VALUE(INDEX_16, 0) ENUM_VALUE(INDEX_32, 1) ENUM_END(VGT_INDEX_TYPE) ENUM_BEG(VGT_OUTPUT_PATH_SELECT, uint32_t) ENUM_VALUE(VTX_REUSE, 0) ENUM_VALUE(TESS_EN, 1) ENUM_VALUE(PASSTHRU, 2) ENUM_VALUE(GS_BLOCK, 3) ENUM_END(VGT_OUTPUT_PATH_SELECT) ENUM_NAMESPACE_EXIT(latte) #include <common/enum_end.inl> #endif // ifdef LATTE_ENUM_VGT_H ================================================ FILE: src/libgpu/latte/latte_formats.h ================================================ #pragma once #include <array> #include <libgpu/latte/latte_enum_cb.h> #include <libgpu/latte/latte_enum_db.h> #include <libgpu/latte/latte_enum_sq.h> #include <libgpu/latte/latte_enum_common.h> #include <string> namespace latte { enum SurfaceFormat : uint32_t { Invalid = 0x0000, // FMT_INVALID R8Unorm = 0x0001, // FMT_8 R8Uint = 0x0101, // FMT_8 R8Snorm = 0x0201, // FMT_8 R8Sint = 0x0301, // FMT_8 R4G4Unorm = 0x0002, // FMT_4_4 R16Unorm = 0x0005, // FMT_16 R16Uint = 0x0105, // FMT_16 R16Snorm = 0x0205, // FMT_16 R16Sint = 0x0305, // FMT_16 R16Float = 0x0806, // FMT_16_FLOAT R8G8Unorm = 0x0007, // FMT_8_8 R8G8Uint = 0x0107, // FMT_8_8 R8G8Snorm = 0x0207, // FMT_8_8 R8G8Sint = 0x0307, // FMT_8_8 R5G6B5Unorm = 0x0008, // FMT_5_6_5 R5G5B5A1Unorm = 0x000a, // FMT_1_5_5_5 R4G4B4A4Unorm = 0x000b, // FMT_4_4_4_4 A1B5G5R5Unorm = 0x000c, // FMT_5_5_5_1 R32Uint = 0x010d, // FMT_32 R32Sint = 0x030d, // FMT_32 R32Float = 0x080e, // FMT_32_FLOAT R16G16Unorm = 0x000f, // FMT_16_16 R16G16Uint = 0x010f, // FMT_16_16 R16G16Snorm = 0x020f, // FMT_16_16 R16G16Sint = 0x030f, // FMT_16_16 R16G16Float = 0x0810, // FMT_16_16_FLOAT D24UnormS8Uint = 0x0011, // FMT_8_24 X24G8Uint = 0x0111, // FMT_8_24 R11G11B10Float = 0x0816, // FMT_10_11_11_FLOAT R10G10B10A2Unorm = 0x0019, // FMT_2_10_10_10 R10G10B10A2Uint = 0x0119, // FMT_2_10_10_10 R10G10B10A2Snorm = 0x0219, // FMT_2_10_10_10 R10G10B10A2Sint = 0x0319, // FMT_2_10_10_10 R8G8B8A8Unorm = 0x001a, // FMT_8_8_8_8 R8G8B8A8Uint = 0x011a, // FMT_8_8_8_8 R8G8B8A8Snorm = 0x021a, // FMT_8_8_8_8 R8G8B8A8Sint = 0x031a, // FMT_8_8_8_8 R8G8B8A8Srgb = 0x041a, // FMT_8_8_8_8 A2B10G10R10Unorm = 0x001b, // FMT_10_10_10_2 A2B10G10R10Uint = 0x011b, // FMT_10_10_10_2 D32FloatS8UintX24 = 0x081c, // FMT_X24_8_32_FLOAT D32G8UintX24 = 0x011c, // FMT_X24_8_32_FLOAT R32G32Uint = 0x011d, // FMT_32_32 R32G32Sint = 0x031d, // FMT_32_32 R32G32Float = 0x081e, // FMT_32_32_FLOAT R16G16B16A16Unorm = 0x001f, // FMT_16_16_16_16 R16G16B16A16Uint = 0x011f, // FMT_16_16_16_16 R16G16B16A16Snorm = 0x021f, // FMT_16_16_16_16 R16G16B16A16Sint = 0x031f, // FMT_16_16_16_16 R16G16B16A16Float = 0x0820, // FMT_16_16_16_16_FLOAT R32G32B32A32Uint = 0x0122, // FMT_32_32_32_32 R32G32B32A32Sint = 0x0322, // FMT_32_32_32_32 R32G32B32A32Float = 0x0823, // FMT_32_32_32_32_FLOAT BC1Unorm = 0x0031, // FMT_BC1 BC1Srgb = 0x0431, // FMT_BC1 BC2Unorm = 0x0032, // FMT_BC2 BC2Srgb = 0x0432, // FMT_BC2 BC3Unorm = 0x0033, // FMT_BC3 BC3Srgb = 0x0433, // FMT_BC3 BC4Unorm = 0x0034, // FMT_BC4 BC4Snorm = 0x0234, // FMT_BC4 BC5Unorm = 0x0035, // FMT_BC5 BC5Snorm = 0x0235, // FMT_BC5 NV12 = 0x0081, // FMT_NV12 }; enum class DataFormatMetaType : uint32_t { None, UINT, FLOAT }; struct DataFormatMetaElem { uint32_t index; uint32_t start; uint32_t length; }; struct DataFormatMeta { uint32_t inputWidth; uint32_t inputCount; DataFormatMetaType type; DataFormatMetaElem elems[4]; }; SurfaceFormat getSurfaceFormat(latte::SQ_DATA_FORMAT dataFormat, latte::SQ_NUM_FORMAT numFormat, latte::SQ_FORMAT_COMP formatComp, bool forceDegamma); latte::SQ_DATA_FORMAT getSurfaceFormatDataFormat(latte::SurfaceFormat format); SurfaceFormat getColorBufferSurfaceFormat(latte::CB_FORMAT format, latte::CB_NUMBER_TYPE numberType); SurfaceFormat getDepthBufferSurfaceFormat(latte::DB_FORMAT format); uint32_t getDataFormatBitsPerElement(latte::SQ_DATA_FORMAT format); bool getDataFormatIsCompressed(latte::SQ_DATA_FORMAT format); std::string getDataFormatName(latte::SQ_DATA_FORMAT format); DataFormatMeta getDataFormatMeta(latte::SQ_DATA_FORMAT format); uint32_t getDataFormatComponents(latte::SQ_DATA_FORMAT format); uint32_t getDataFormatComponentBits(latte::SQ_DATA_FORMAT format); bool getDataFormatIsFloat(latte::SQ_DATA_FORMAT format); uint32_t getTexDimDimensions(latte::SQ_TEX_DIM dim); latte::SQ_TILE_MODE getArrayModeTileMode(latte::BUFFER_ARRAY_MODE mode); } // namespace gpu ================================================ FILE: src/libgpu/latte/latte_instructions.h ================================================ #pragma once #include "latte_enum_sq.h" #include <cstdint> #include <common/bitfield.h> #include <common/fixed.h> #include <common/structsize.h> namespace latte { #pragma pack(push, 1) enum SQ_CF_INST : uint32_t { #define CF_INST(name, value) SQ_CF_INST_##name = value, #include "latte_instructions_def.inl" #undef CF_INST SQ_CF_INST_INVALID = 0xFFFFFFFF, }; enum SQ_CF_EXP_INST : uint32_t { #define EXP_INST(name, value) SQ_CF_INST_##name = value, #include "latte_instructions_def.inl" #undef EXP_INST SQ_CF_EXP_INST_INVALID = 0xFFFFFFFF, }; enum SQ_CF_ALU_INST : uint32_t { #define ALU_INST(name, value) SQ_CF_INST_##name = value, #include "latte_instructions_def.inl" #undef ALU_INST SQ_CF_ALU_INST_INVALID = 0xFFFFFFFF, }; enum SQ_OP2_INST : uint32_t { #define ALU_OP2(name, value, srcs, flags) SQ_OP2_INST_##name = value, #include "latte_instructions_def.inl" #undef ALU_OP2 SQ_OP2_INST_INVALID = 0xFFFFFFFF, }; enum SQ_OP3_INST : uint32_t { #define ALU_OP3(name, value, srcs, flags) SQ_OP3_INST_##name = value, #include "latte_instructions_def.inl" #undef ALU_OP3 SQ_OP3_INST_INVALID = 0xFFFFFFFF, }; enum SQ_TEX_INST : uint32_t { #define TEX_INST(name, value) SQ_TEX_INST_##name = value, #include "latte_instructions_def.inl" #undef TEX_INST SQ_TEX_INST_INVALID = 0xFFFFFFFF, }; enum SQ_VTX_INST : uint32_t { #define VTX_INST(name, value) SQ_VTX_INST_##name = value, #include "latte_instructions_def.inl" #undef VTX_INST SQ_VTX_INST_INVALID = 0xFFFFFFFF, }; enum SQ_CF_INST_TYPE : uint32_t { SQ_CF_INST_TYPE_NORMAL = 0, SQ_CF_INST_TYPE_EXPORT = 1, SQ_CF_INST_TYPE_ALU = 2, SQ_CF_INST_TYPE_ALU_EXTENDED = 3, }; enum SQ_ALU_FLAGS : uint32_t { SQ_ALU_FLAG_NONE = 0, SQ_ALU_FLAG_VECTOR = (1 << 0), SQ_ALU_FLAG_TRANSCENDENTAL = (1 << 1), SQ_ALU_FLAG_REDUCTION = (1 << 2), SQ_ALU_FLAG_PRED_SET = (1 << 3), SQ_ALU_FLAG_INT_IN = (1 << 4), SQ_ALU_FLAG_INT_OUT = (1 << 5), SQ_ALU_FLAG_UINT_IN = (1 << 6), SQ_ALU_FLAG_UINT_OUT = (1 << 7), }; // Control flow instruction word 0 BITFIELD_BEG(SQ_CF_WORD0, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, ADDR); BITFIELD_END // Control flow instruction word 1 BITFIELD_BEG(SQ_CF_WORD1, uint32_t) BITFIELD_ENTRY(0, 3, uint32_t, POP_COUNT); BITFIELD_ENTRY(3, 5, uint32_t, CF_CONST); BITFIELD_ENTRY(8, 2, SQ_CF_COND, COND); BITFIELD_ENTRY(10, 3, uint32_t, COUNT); BITFIELD_ENTRY(13, 6, uint32_t, CALL_COUNT); BITFIELD_ENTRY(19, 1, uint32_t, COUNT_3); BITFIELD_ENTRY(21, 1, bool, END_OF_PROGRAM); BITFIELD_ENTRY(22, 1, bool, VALID_PIXEL_MODE); BITFIELD_ENTRY(23, 7, SQ_CF_INST, CF_INST); BITFIELD_ENTRY(28, 2, SQ_CF_INST_TYPE, CF_INST_TYPE); BITFIELD_ENTRY(30, 1, bool, WHOLE_QUAD_MODE); BITFIELD_ENTRY(31, 1, bool, BARRIER); BITFIELD_END // Control flow ALU clause instruction word 0 BITFIELD_BEG(SQ_CF_ALU_WORD0, uint32_t) BITFIELD_ENTRY(0, 22, uint32_t, ADDR); BITFIELD_ENTRY(22, 4, uint32_t, KCACHE_BANK0); BITFIELD_ENTRY(26, 4, uint32_t, KCACHE_BANK1); BITFIELD_ENTRY(30, 2, SQ_CF_KCACHE_MODE, KCACHE_MODE0); BITFIELD_END // Control flow ALU clause instruction word 1 BITFIELD_BEG(SQ_CF_ALU_WORD1, uint32_t) BITFIELD_ENTRY(0, 2, SQ_CF_KCACHE_MODE, KCACHE_MODE1); BITFIELD_ENTRY(2, 8, uint32_t, KCACHE_ADDR0); BITFIELD_ENTRY(10, 8, uint32_t, KCACHE_ADDR1); BITFIELD_ENTRY(18, 7, uint32_t, COUNT); BITFIELD_ENTRY(25, 1, bool, ALT_CONST); BITFIELD_ENTRY(26, 4, SQ_CF_ALU_INST, CF_INST); BITFIELD_ENTRY(30, 1, bool, WHOLE_QUAD_MODE); BITFIELD_ENTRY(31, 1, bool, BARRIER); BITFIELD_END // ALU instruction word 0 BITFIELD_BEG(SQ_ALU_WORD0, uint32_t) BITFIELD_ENTRY(0, 9, SQ_ALU_SRC, SRC0_SEL); BITFIELD_ENTRY(9, 1, SQ_REL, SRC0_REL); BITFIELD_ENTRY(10, 2, SQ_CHAN, SRC0_CHAN); BITFIELD_ENTRY(12, 1, bool, SRC0_NEG); BITFIELD_ENTRY(13, 9, SQ_ALU_SRC, SRC1_SEL); BITFIELD_ENTRY(22, 1, SQ_REL, SRC1_REL); BITFIELD_ENTRY(23, 2, SQ_CHAN, SRC1_CHAN); BITFIELD_ENTRY(25, 1, bool, SRC1_NEG); BITFIELD_ENTRY(26, 3, SQ_INDEX_MODE, INDEX_MODE); BITFIELD_ENTRY(29, 2, SQ_PRED_SEL, PRED_SEL); BITFIELD_ENTRY(31, 1, bool, LAST); BITFIELD_END // ALU instruction word 1 BITFIELD_BEG(SQ_ALU_WORD1, uint32_t) BITFIELD_ENTRY(15, 3, SQ_ALU_ENCODING, ENCODING); BITFIELD_ENTRY(18, 3, SQ_ALU_VEC_BANK_SWIZZLE, BANK_SWIZZLE); BITFIELD_ENTRY(21, 7, uint32_t, DST_GPR); BITFIELD_ENTRY(28, 1, SQ_REL, DST_REL); BITFIELD_ENTRY(29, 2, SQ_CHAN, DST_CHAN); BITFIELD_ENTRY(31, 1, bool, CLAMP); BITFIELD_END // ALU instruction word 1. This subencoding is used for instructions taking 0-2 operands. BITFIELD_BEG(SQ_ALU_WORD1_OP2, uint32_t) BITFIELD_ENTRY(0, 1, bool, SRC0_ABS); BITFIELD_ENTRY(1, 1, bool, SRC1_ABS); BITFIELD_ENTRY(2, 1, bool, UPDATE_EXECUTE_MASK); BITFIELD_ENTRY(3, 1, bool, UPDATE_PRED); BITFIELD_ENTRY(4, 1, bool, WRITE_MASK); BITFIELD_ENTRY(5, 2, SQ_ALU_OMOD, OMOD); BITFIELD_ENTRY(7, 11, SQ_OP2_INST, ALU_INST); BITFIELD_END // ALU instruction word 1. This subencoding is used for instructions taking 3 operands. BITFIELD_BEG(SQ_ALU_WORD1_OP3, uint32_t) BITFIELD_ENTRY(0, 9, SQ_ALU_SRC, SRC2_SEL); BITFIELD_ENTRY(9, 1, SQ_REL, SRC2_REL); BITFIELD_ENTRY(10, 2, SQ_CHAN, SRC2_CHAN); BITFIELD_ENTRY(12, 1, bool, SRC2_NEG); BITFIELD_ENTRY(13, 5, SQ_OP3_INST, ALU_INST); BITFIELD_END // Word 0 of the control flow instruction for alloc/export. BITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD0, uint32_t) BITFIELD_ENTRY(0, 13, uint32_t, ARRAY_BASE); BITFIELD_ENTRY(13, 2, SQ_EXPORT_TYPE, TYPE); BITFIELD_ENTRY(15, 7, uint32_t, RW_GPR); BITFIELD_ENTRY(22, 1, SQ_REL, RW_REL); BITFIELD_ENTRY(23, 7, uint32_t, INDEX_GPR); BITFIELD_ENTRY(30, 2, uint32_t, ELEM_SIZE); BITFIELD_END // Word 1 of the control flow instruction BITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD1, uint32_t) BITFIELD_ENTRY(17, 4, uint32_t, BURST_COUNT); BITFIELD_ENTRY(21, 1, bool, END_OF_PROGRAM); BITFIELD_ENTRY(22, 1, bool, VALID_PIXEL_MODE); BITFIELD_ENTRY(23, 7, SQ_CF_EXP_INST, CF_INST); BITFIELD_ENTRY(30, 1, bool, WHOLE_QUAD_MODE); BITFIELD_ENTRY(31, 1, bool, BARRIER); BITFIELD_END // Word 1 of the control flow instruction. This subencoding is used by alloc/exports // for all input / outputs to scratch / ring / stream / reduction buffers. BITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD1_BUF, uint32_t) BITFIELD_ENTRY(0, 12, uint32_t, ARRAY_SIZE); BITFIELD_ENTRY(12, 4, uint32_t, COMP_MASK); BITFIELD_END // Word 1 of the control flow instruction. This subencoding is used by // alloc/exports for PIXEL, POS, and PARAM. BITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD1_SWIZ, uint32_t) BITFIELD_ENTRY(0, 3, SQ_SEL, SEL_X); BITFIELD_ENTRY(3, 3, SQ_SEL, SEL_Y); BITFIELD_ENTRY(6, 3, SQ_SEL, SEL_Z); BITFIELD_ENTRY(9, 3, SQ_SEL, SEL_W); BITFIELD_END // Texture fetch clause instruction word 0 BITFIELD_BEG(SQ_TEX_WORD0, uint32_t) BITFIELD_ENTRY(0, 5, SQ_TEX_INST, TEX_INST); BITFIELD_ENTRY(5, 1, bool, BC_FRAC_MODE); BITFIELD_ENTRY(7, 1, bool, FETCH_WHOLE_QUAD); BITFIELD_ENTRY(8, 8, uint32_t, RESOURCE_ID); BITFIELD_ENTRY(16, 7, uint32_t, SRC_GPR); BITFIELD_ENTRY(23, 1, SQ_REL, SRC_REL); BITFIELD_ENTRY(24, 1, bool, ALT_CONST); BITFIELD_END // Texture fetch clause instruction word 1 BITFIELD_BEG(SQ_TEX_WORD1, uint32_t) BITFIELD_ENTRY(0, 7, uint32_t, DST_GPR); BITFIELD_ENTRY(7, 1, SQ_REL, DST_REL); BITFIELD_ENTRY(9, 3, SQ_SEL, DST_SEL_X); BITFIELD_ENTRY(12, 3, SQ_SEL, DST_SEL_Y); BITFIELD_ENTRY(15, 3, SQ_SEL, DST_SEL_Z); BITFIELD_ENTRY(18, 3, SQ_SEL, DST_SEL_W); BITFIELD_ENTRY(21, 7, sfixed_1_3_3_t, LOD_BIAS); BITFIELD_ENTRY(28, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_X); BITFIELD_ENTRY(29, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_Y); BITFIELD_ENTRY(30, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_Z); BITFIELD_ENTRY(31, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_W); BITFIELD_END // Texture fetch clause instruction word 2 BITFIELD_BEG(SQ_TEX_WORD2, uint32_t) BITFIELD_ENTRY(0, 5, sfixed_1_3_1_t, OFFSET_X); BITFIELD_ENTRY(5, 5, sfixed_1_3_1_t, OFFSET_Y); BITFIELD_ENTRY(10, 5, sfixed_1_3_1_t, OFFSET_Z); BITFIELD_ENTRY(15, 5, uint32_t, SAMPLER_ID); BITFIELD_ENTRY(20, 3, SQ_SEL, SRC_SEL_X); BITFIELD_ENTRY(23, 3, SQ_SEL, SRC_SEL_Y); BITFIELD_ENTRY(26, 3, SQ_SEL, SRC_SEL_Z); BITFIELD_ENTRY(29, 3, SQ_SEL, SRC_SEL_W); BITFIELD_END // Vertex fetch clause instruction word 0. BITFIELD_BEG(SQ_VTX_WORD0, uint32_t) BITFIELD_ENTRY(0, 5, SQ_VTX_INST, VTX_INST); BITFIELD_ENTRY(5, 2, SQ_VTX_FETCH_TYPE, FETCH_TYPE); BITFIELD_ENTRY(7, 1, uint32_t, FETCH_WHOLE_QUAD); BITFIELD_ENTRY(8, 8, uint32_t, BUFFER_ID); BITFIELD_ENTRY(16, 7, uint32_t, SRC_GPR); BITFIELD_ENTRY(23, 1, SQ_REL, SRC_REL); BITFIELD_ENTRY(24, 2, SQ_SEL, SRC_SEL_X); BITFIELD_ENTRY(26, 6, uint32_t, MEGA_FETCH_COUNT); BITFIELD_END // Vertex fetch clause instruction word 1 BITFIELD_BEG(SQ_VTX_WORD1_SEM, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, SEMANTIC_ID); BITFIELD_END BITFIELD_BEG(SQ_VTX_WORD1_GPR, uint32_t) BITFIELD_ENTRY(0, 7, uint32_t, DST_GPR); BITFIELD_ENTRY(7, 1, SQ_REL, DST_REL); BITFIELD_END BITFIELD_BEG(SQ_VTX_WORD1, uint32_t) BITFIELD_ENTRY(9, 3, SQ_SEL, DST_SEL_X); BITFIELD_ENTRY(12, 3, SQ_SEL, DST_SEL_Y); BITFIELD_ENTRY(15, 3, SQ_SEL, DST_SEL_Z); BITFIELD_ENTRY(18, 3, SQ_SEL, DST_SEL_W); BITFIELD_ENTRY(21, 1, bool, USE_CONST_FIELDS); BITFIELD_ENTRY(22, 6, SQ_DATA_FORMAT, DATA_FORMAT); BITFIELD_ENTRY(28, 2, SQ_NUM_FORMAT, NUM_FORMAT_ALL); BITFIELD_ENTRY(30, 1, SQ_FORMAT_COMP, FORMAT_COMP_ALL); BITFIELD_ENTRY(31, 1, SQ_SRF_MODE, SRF_MODE_ALL); BITFIELD_END // Vertex fetch clause instruction word 2 BITFIELD_BEG(SQ_VTX_WORD2, uint32_t) BITFIELD_ENTRY(0, 16, uint32_t, OFFSET); BITFIELD_ENTRY(16, 2, SQ_ENDIAN, ENDIAN_SWAP); BITFIELD_ENTRY(18, 1, bool, CONST_BUF_NO_STRIDE); BITFIELD_ENTRY(19, 1, bool, MEGA_FETCH); BITFIELD_ENTRY(20, 1, bool, ALT_CONST); BITFIELD_END struct ControlFlowInst { union { struct { SQ_CF_WORD0 word0; SQ_CF_WORD1 word1; }; struct { SQ_CF_ALU_WORD0 word0; SQ_CF_ALU_WORD1 word1; } alu; struct { SQ_CF_ALLOC_EXPORT_WORD0 word0; union { SQ_CF_ALLOC_EXPORT_WORD1 word1; SQ_CF_ALLOC_EXPORT_WORD1_BUF buf; SQ_CF_ALLOC_EXPORT_WORD1_SWIZ swiz; }; } exp; }; }; CHECK_SIZE(ControlFlowInst, 8); struct AluInst { SQ_ALU_WORD0 word0; union { SQ_ALU_WORD1 word1; SQ_ALU_WORD1_OP2 op2; SQ_ALU_WORD1_OP3 op3; }; }; CHECK_SIZE(AluInst, 8); struct VertexFetchInst { SQ_VTX_WORD0 word0; union { SQ_VTX_WORD1 word1; SQ_VTX_WORD1_GPR gpr; SQ_VTX_WORD1_SEM sem; }; SQ_VTX_WORD2 word2; uint32_t padding; }; CHECK_SIZE(VertexFetchInst, 16); struct TextureFetchInst { SQ_TEX_WORD0 word0; SQ_TEX_WORD1 word1; SQ_TEX_WORD2 word2; uint32_t padding; }; CHECK_SIZE(TextureFetchInst, 16); #pragma pack(pop) const char *getInstructionName(SQ_CF_INST id); const char *getInstructionName(SQ_CF_EXP_INST id); const char *getInstructionName(SQ_CF_ALU_INST id); const char *getInstructionName(SQ_OP2_INST id); const char *getInstructionName(SQ_OP3_INST id); const char *getInstructionName(SQ_TEX_INST id); const char *getInstructionName(SQ_VTX_INST id); uint32_t getInstructionNumSrcs(SQ_OP2_INST id); uint32_t getInstructionNumSrcs(SQ_OP3_INST id); SQ_ALU_FLAGS getInstructionFlags(SQ_OP2_INST id); SQ_ALU_FLAGS getInstructionFlags(SQ_OP3_INST id); } // namespace latte ================================================ FILE: src/libgpu/latte/latte_instructions_def.inl ================================================ /** * Reference documentation: * R600/R700/Evergreen Assembly Language Format * http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/R600-R700-Evergreen_Assembly_Language_Format.pdf * * ATI R700-Family ISA * http://developer.amd.com/wordpress/media/2012/10/R700-Family_Instruction_Set_Architecture.pdf * * ATI Evergreen-Family ISA * http://developer.amd.com/wordpress/media/2012/10/AMD_Evergreen-Family_Instruction_Set_Architecture.pdf * * AMD HD 6900 ISA * http://developer.amd.com/wordpress/media/2012/10/AMD_HD_6900_Series_Instruction_Set_Architecture.pdf */ #ifndef CF_INST #define CF_INST(name, value) #endif #ifndef EXP_INST #define EXP_INST(name, value) #endif #ifndef ALU_INST #define ALU_INST(name, value) #endif #ifndef ALU_OP2 #define ALU_OP2(name, value, srcs, flags) #endif #ifndef ALU_OP3 #define ALU_OP3(name, value, srcs, flags) #endif #ifndef TEX_INST #define TEX_INST(name, value) #endif #ifndef VTX_INST #define VTX_INST(name, value) #endif #ifndef MEM_INST #define MEM_INST(name, value) #endif #ifndef ALU_REDUC #define ALU_REDUC Opcode::Reduction #endif #ifndef ALU_VEC #define ALU_VEC Opcode::Vector #endif #ifndef ALU_TRANS #define ALU_TRANS Opcode::Transcendental #endif #ifndef ALU_PRED_SET #define ALU_PRED_SET Opcode::PredSet #endif #ifndef ALU_INT #define ALU_INT Opcode::IntIn | Opcode::IntOut #endif #ifndef ALU_UINT #define ALU_UINT Opcode::UintIn | Opcode::UintOut #endif #ifndef ALU_INT_IN #define ALU_INT_IN Opcode::IntIn #endif #ifndef ALU_UINT_IN #define ALU_UINT_IN Opcode::UintIn #endif #ifndef ALU_INT_OUT #define ALU_INT_OUT Opcode::IntOut #endif #ifndef ALU_UINT_OUT #define ALU_UINT_OUT Opcode::UintOut #endif // CF CF_INST(NOP, 0x00000000) CF_INST(TEX, 0x00000001) CF_INST(VTX, 0x00000002) CF_INST(VTX_TC, 0x00000003) CF_INST(LOOP_START, 0x00000004) CF_INST(LOOP_END, 0x00000005) CF_INST(LOOP_START_DX10, 0x00000006) CF_INST(LOOP_START_NO_AL, 0x00000007) CF_INST(LOOP_CONTINUE, 0x00000008) CF_INST(LOOP_BREAK, 0x00000009) CF_INST(JUMP, 0x0000000A) CF_INST(PUSH, 0x0000000B) CF_INST(PUSH_ELSE, 0x0000000C) CF_INST(ELSE, 0x0000000D) CF_INST(POP, 0x0000000E) CF_INST(POP_JUMP, 0x0000000F) CF_INST(POP_PUSH, 0x00000010) CF_INST(POP_PUSH_ELSE, 0x00000011) CF_INST(CALL, 0x00000012) CF_INST(CALL_FS, 0x00000013) CF_INST(RETURN, 0x00000014) CF_INST(EMIT_VERTEX, 0x00000015) CF_INST(EMIT_CUT_VERTEX, 0x00000016) CF_INST(CUT_VERTEX, 0x00000017) CF_INST(KILL, 0x00000018) CF_INST(END_PROGRAM, 0x00000019) CF_INST(WAIT_ACK, 0x0000001A) CF_INST(TEX_ACK, 0x0000001B) CF_INST(VTX_ACK, 0x0000001C) CF_INST(VTX_TC_ACK, 0x0000001D) /* I don't think Wii U has these. CF_INST(TC, 0x0000001E) CF_INST(VC, 0x0000001F) CF_INST(GDS, 0x00000020) CF_INST(TC_ACK, 0x00000021) CF_INST(VC_ACK, 0x00000022) CF_INST(JUMPTABLE, 0x00000023) CF_INST(GLOBAL_WAVE_SYNC, 0x00000024) CF_INST(HALT, 0x00000025) CF_INST(END, 0x00000026) CF_INST(LDS_DEALLOC, 0x00000027) CF_INST(PUSH_WQM, 0x00000028) CF_INST(POP_WQM, 0x00000029) CF_INST(ELSE_WQM, 0x0000002A) CF_INST(JUMP_ANY, 0x0000002B) CF_INST(REACTIVATE, 0x0000002C) CF_INST(REACTIVATE_WQM, 0x0000002D) CF_INST(INTERRUPT, 0x0000002E) CF_INST(INTERRUPT_AND_SLEEP, 0x0000002F) CF_INST(SET_PRIORITY, 0x00000030) */ // EXP EXP_INST(MEM_STREAM0, 0x00000020) EXP_INST(MEM_STREAM1, 0x00000021) EXP_INST(MEM_STREAM2, 0x00000022) EXP_INST(MEM_STREAM3, 0x00000023) EXP_INST(MEM_SCRATCH, 0x00000024) EXP_INST(MEM_REDUCTION, 0x00000025) EXP_INST(MEM_RING, 0x00000026) EXP_INST(EXP, 0x00000027) EXP_INST(EXP_DONE, 0x00000028) EXP_INST(MEM_EXPORT, 0x0000003A) /* I don't think Wii U has these EXP_INST(MEM_STREAM0_BUF0, 0x00000040) EXP_INST(MEM_STREAM0_BUF1, 0x00000041) EXP_INST(MEM_STREAM0_BUF2, 0x00000042) EXP_INST(MEM_STREAM0_BUF3, 0x00000043) EXP_INST(MEM_STREAM1_BUF0, 0x00000044) EXP_INST(MEM_STREAM1_BUF1, 0x00000045) EXP_INST(MEM_STREAM1_BUF2, 0x00000046) EXP_INST(MEM_STREAM1_BUF3, 0x00000047) EXP_INST(MEM_STREAM2_BUF0, 0x00000048) EXP_INST(MEM_STREAM2_BUF1, 0x00000049) EXP_INST(MEM_STREAM2_BUF2, 0x0000004A) EXP_INST(MEM_STREAM2_BUF3, 0x0000004B) EXP_INST(MEM_STREAM3_BUF0, 0x0000004C) EXP_INST(MEM_STREAM3_BUF1, 0x0000004D) EXP_INST(MEM_STREAM3_BUF2, 0x0000004E) EXP_INST(MEM_STREAM3_BUF3, 0x0000004F) EXP_INST(MEM_RAT, 0x00000056) EXP_INST(MEM_RAT_CACHELESS, 0x00000057) EXP_INST(MEM_RING1, 0x00000058) EXP_INST(MEM_RING2, 0x00000059) EXP_INST(MEM_RING3, 0x0000005A) EXP_INST(MEM_EXPORT_COMBINED, 0x0000005B) EXP_INST(MEM_RAT_COMBINED_CACHELESS, 0x0000005C) EXP_INST(MEM_RAT_COMBINED, 0x0000005D) EXP_INST(EXP_DONE_END_IS_NEXT, 0x0000005E) */ // ALU ALU_INST(ALU, 0x00000008) ALU_INST(ALU_PUSH_BEFORE, 0x00000009) ALU_INST(ALU_POP_AFTER, 0x0000000A) ALU_INST(ALU_POP2_AFTER, 0x0000000B) ALU_INST(ALU_EXT, 0x0000000C) ALU_INST(ALU_CONTINUE, 0x0000000D) ALU_INST(ALU_BREAK, 0x0000000E) ALU_INST(ALU_ELSE_AFTER, 0x0000000F) // ALU OP2 ALU_OP2(ADD, 0x00000000, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MUL, 0x00000001, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MUL_IEEE, 0x00000002, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MAX, 0x00000003, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MIN, 0x00000004, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MAX_DX10, 0x00000005, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MIN_DX10, 0x00000006, 2, ALU_VEC | ALU_TRANS) ALU_OP2(FREXP_64, 0x00000007, 1, ALU_VEC | ALU_REDUC) ALU_OP2(SETE, 0x00000008, 2, ALU_VEC | ALU_TRANS) ALU_OP2(SETGT, 0x00000009, 2, ALU_VEC | ALU_TRANS) ALU_OP2(SETGE, 0x0000000A, 2, ALU_VEC | ALU_TRANS) ALU_OP2(SETNE, 0x0000000B, 2, ALU_VEC | ALU_TRANS) ALU_OP2(SETE_DX10, 0x0000000C, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT) ALU_OP2(SETGT_DX10, 0x0000000D, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT) ALU_OP2(SETGE_DX10, 0x0000000E, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT) ALU_OP2(SETNE_DX10, 0x0000000F, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT) ALU_OP2(FRACT, 0x00000010, 1, ALU_VEC | ALU_TRANS) ALU_OP2(TRUNC, 0x00000011, 1, ALU_VEC | ALU_TRANS) ALU_OP2(CEIL, 0x00000012, 1, ALU_VEC | ALU_TRANS) ALU_OP2(RNDNE, 0x00000013, 1, ALU_VEC | ALU_TRANS) ALU_OP2(FLOOR, 0x00000014, 1, ALU_VEC | ALU_TRANS) ALU_OP2(MOVA, 0x00000015, 1, ALU_VEC | ALU_INT_OUT) ALU_OP2(MOVA_FLOOR, 0x00000016, 1, ALU_VEC | ALU_INT_OUT) ALU_OP2(ADD_64, 0x00000017, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MOVA_INT, 0x00000018, 1, ALU_VEC | ALU_INT) ALU_OP2(MOV, 0x00000019, 1, ALU_VEC | ALU_TRANS) ALU_OP2(NOP, 0x0000001A, 0, ALU_VEC | ALU_TRANS) ALU_OP2(MUL_64, 0x0000001B, 2, ALU_VEC | ALU_REDUC) ALU_OP2(FLT64_TO_FLT32, 0x0000001C, 1, ALU_VEC | ALU_REDUC) ALU_OP2(FLT32_TO_FLT64, 0x0000001D, 1, ALU_VEC | ALU_REDUC) ALU_OP2(PRED_SETGT_UINT, 0x0000001E, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_UINT) ALU_OP2(PRED_SETGE_UINT, 0x0000001F, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_UINT) ALU_OP2(PRED_SETE, 0x00000020, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETGT, 0x00000021, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETGE, 0x00000022, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETNE, 0x00000023, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SET_INV, 0x00000024, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SET_POP, 0x00000025, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SET_CLR, 0x00000026, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SET_RESTORE, 0x00000027, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETE_PUSH, 0x00000028, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETGT_PUSH, 0x00000029, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETGE_PUSH, 0x0000002A, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(PRED_SETNE_PUSH, 0x0000002B, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET) ALU_OP2(KILLE, 0x0000002C, 2, ALU_VEC | ALU_TRANS) ALU_OP2(KILLGT, 0x0000002D, 2, ALU_VEC | ALU_TRANS) ALU_OP2(KILLGE, 0x0000002E, 2, ALU_VEC | ALU_TRANS) ALU_OP2(KILLNE, 0x0000002F, 2, ALU_VEC | ALU_TRANS) ALU_OP2(AND_INT, 0x00000030, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(OR_INT, 0x00000031, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(XOR_INT, 0x00000032, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(NOT_INT, 0x00000033, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(ADD_INT, 0x00000034, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(SUB_INT, 0x00000035, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(MAX_INT, 0x00000036, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(MIN_INT, 0x00000037, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(MAX_UINT, 0x00000038, 2, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP2(MIN_UINT, 0x00000039, 2, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP2(SETE_INT, 0x0000003A, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(SETGT_INT, 0x0000003B, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(SETGE_INT, 0x0000003C, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(SETNE_INT, 0x0000003D, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(SETGT_UINT, 0x0000003E, 2, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP2(SETGE_UINT, 0x0000003F, 2, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP2(KILLGT_UINT, 0x00000040, 2, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP2(KILLGE_UINT, 0x00000041, 2, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP2(PRED_SETE_INT, 0x00000042, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETGT_INT, 0x00000043, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETGE_INT, 0x00000044, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETNE_INT, 0x00000045, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(KILLE_INT, 0x00000046, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(KILLGT_INT, 0x00000047, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(KILLGE_INT, 0x00000048, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(KILLNE_INT, 0x00000049, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(PRED_SETE_PUSH_INT, 0x0000004A, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETGT_PUSH_INT, 0x0000004B, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETGE_PUSH_INT, 0x0000004C, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETNE_PUSH_INT, 0x0000004D, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETLT_PUSH_INT, 0x0000004E, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(PRED_SETLE_PUSH_INT, 0x0000004F, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT) ALU_OP2(DOT4, 0x00000050, 2, ALU_VEC | ALU_REDUC) ALU_OP2(DOT4_IEEE, 0x00000051, 2, ALU_VEC | ALU_REDUC) ALU_OP2(CUBE, 0x00000052, 2, ALU_VEC | ALU_REDUC) ALU_OP2(MAX4, 0x00000053, 2, ALU_VEC | ALU_REDUC) ALU_OP2(GROUP_BARRIER, 0x00000054, 2, ALU_VEC | ALU_TRANS) ALU_OP2(GROUP_SEQ_BEGIN, 0x00000055, 2, ALU_VEC | ALU_TRANS) ALU_OP2(GROUP_SEQ_END, 0x00000056, 2, ALU_VEC | ALU_TRANS) ALU_OP2(SET_MODE, 0x00000057, 0, ALU_VEC | ALU_TRANS) ALU_OP2(SET_CF_IDX0, 0x00000058, 0, ALU_VEC | ALU_TRANS) ALU_OP2(SET_CF_IDX1, 0x00000059, 0, ALU_VEC | ALU_TRANS) ALU_OP2(SET_LDS_SIZE, 0x0000005A, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MUL_INT24, 0x0000005B, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MULHI_INT24, 0x0000005C, 2, ALU_VEC | ALU_TRANS) ALU_OP2(MOVA_GPR_INT, 0x00000060, 2, ALU_VEC | ALU_INT) ALU_OP2(EXP_IEEE, 0x00000061, 1, ALU_TRANS) ALU_OP2(LOG_CLAMPED, 0x00000062, 1, ALU_TRANS) ALU_OP2(LOG_IEEE, 0x00000063, 1, ALU_TRANS) ALU_OP2(RECIP_CLAMPED, 0x00000064, 1, ALU_TRANS) ALU_OP2(RECIP_FF, 0x00000065, 1, ALU_TRANS) ALU_OP2(RECIP_IEEE, 0x00000066, 1, ALU_TRANS) ALU_OP2(RECIPSQRT_CLAMPED, 0x00000067, 1, ALU_TRANS) ALU_OP2(RECIPSQRT_FF, 0x00000068, 1, ALU_TRANS) ALU_OP2(RECIPSQRT_IEEE, 0x00000069, 1, ALU_TRANS) ALU_OP2(SQRT_IEEE, 0x0000006A, 1, ALU_TRANS) ALU_OP2(FLT_TO_INT, 0x0000006B, 1, ALU_TRANS | ALU_INT_OUT) ALU_OP2(INT_TO_FLT, 0x0000006C, 1, ALU_TRANS | ALU_INT_IN) ALU_OP2(UINT_TO_FLT, 0x0000006D, 1, ALU_TRANS | ALU_UINT_IN) ALU_OP2(SIN, 0x0000006E, 1, ALU_TRANS) ALU_OP2(COS, 0x0000006F, 1, ALU_TRANS) ALU_OP2(ASHR_INT, 0x00000070, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(LSHR_INT, 0x00000071, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(LSHL_INT, 0x00000072, 2, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP2(MULLO_INT, 0x00000073, 2, ALU_TRANS | ALU_INT) ALU_OP2(MULHI_INT, 0x00000074, 2, ALU_TRANS | ALU_INT) ALU_OP2(MULLO_UINT, 0x00000075, 2, ALU_TRANS | ALU_UINT) ALU_OP2(MULHI_UINT, 0x00000076, 2, ALU_TRANS | ALU_UINT) ALU_OP2(RECIP_INT, 0x00000077, 1, ALU_TRANS | ALU_INT) ALU_OP2(RECIP_UINT, 0x00000078, 1, ALU_TRANS | ALU_UINT) ALU_OP2(FLT_TO_UINT, 0x00000079, 1, ALU_TRANS | ALU_UINT_OUT) ALU_OP2(LDEXP_64, 0x0000007A, 2, ALU_VEC | ALU_REDUC) ALU_OP2(FRACT_64, 0x0000007B, 1, ALU_VEC | ALU_REDUC) ALU_OP2(PRED_SETGT_64, 0x0000007C, 2, ALU_VEC | ALU_REDUC | ALU_PRED_SET) ALU_OP2(PRED_SETE_64, 0x0000007D, 2, ALU_VEC | ALU_REDUC | ALU_PRED_SET) ALU_OP2(PRED_SETGE_64, 0x0000007E, 2, ALU_VEC | ALU_REDUC | ALU_PRED_SET) // ALU OP3 ALU_OP3(BFE_UINT, 0x00000004, 3, ALU_VEC | ALU_TRANS | ALU_UINT) ALU_OP3(BFE_INT, 0x00000005, 3, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP3(BFI_INT, 0x00000006, 3, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP3(FMA, 0x00000007, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_64, 0x00000008, 3, ALU_VEC | ALU_REDUC) ALU_OP3(MULADD_64_M2, 0x00000009, 3, ALU_VEC | ALU_REDUC) ALU_OP3(MULADD_64_M4, 0x0000000A, 3, ALU_VEC | ALU_REDUC) ALU_OP3(MULADD_64_D2, 0x0000000B, 3, ALU_VEC | ALU_REDUC) ALU_OP3(MUL_LIT, 0x0000000C, 3, ALU_TRANS) ALU_OP3(MUL_LIT_M2, 0x0000000D, 3, ALU_TRANS) ALU_OP3(MUL_LIT_M4, 0x0000000E, 3, ALU_TRANS) ALU_OP3(MUL_LIT_D2, 0x0000000F, 3, ALU_TRANS) ALU_OP3(MULADD, 0x00000010, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_M2, 0x00000011, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_M4, 0x00000012, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_D2, 0x00000013, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_IEEE, 0x00000014, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_IEEE_M2, 0x00000015, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_IEEE_M4, 0x00000016, 3, ALU_VEC | ALU_TRANS) ALU_OP3(MULADD_IEEE_D2, 0x00000017, 3, ALU_VEC | ALU_TRANS) ALU_OP3(CNDE, 0x00000018, 3, ALU_VEC | ALU_TRANS) ALU_OP3(CNDGT, 0x00000019, 3, ALU_VEC | ALU_TRANS) ALU_OP3(CNDGE, 0x0000001A, 3, ALU_VEC | ALU_TRANS) ALU_OP3(CNDE_INT, 0x0000001C, 3, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP3(CNDGT_INT, 0x0000001D, 3, ALU_VEC | ALU_TRANS | ALU_INT) ALU_OP3(CNDGE_INT, 0x0000001E, 3, ALU_VEC | ALU_TRANS | ALU_INT) // TEX TEX_INST(VTX_FETCH, 0x00000000) TEX_INST(VTX_SEMANTIC, 0x00000001) TEX_INST(MEM, 0x00000002) TEX_INST(LD, 0x00000003) TEX_INST(GET_TEXTURE_INFO, 0x00000004) TEX_INST(GET_SAMPLE_INFO, 0x00000005) TEX_INST(GET_COMP_TEX_LOD, 0x00000006) TEX_INST(GET_GRADIENTS_H, 0x00000007) TEX_INST(GET_GRADIENTS_V, 0x00000008) TEX_INST(GET_LERP, 0x00000009) TEX_INST(KEEP_GRADIENTS, 0x0000000A) TEX_INST(SET_GRADIENTS_H, 0x0000000B) TEX_INST(SET_GRADIENTS_V, 0x0000000C) TEX_INST(PASS, 0x0000000D) TEX_INST(SET_CUBEMAP_INDEX, 0x0000000E) TEX_INST(FETCH4, 0x0000000F) TEX_INST(SAMPLE, 0x00000010) TEX_INST(SAMPLE_L, 0x00000011) TEX_INST(SAMPLE_LB, 0x00000012) TEX_INST(SAMPLE_LZ, 0x00000013) TEX_INST(SAMPLE_G, 0x00000014) TEX_INST(SAMPLE_G_L, 0x00000015) TEX_INST(SAMPLE_G_LB, 0x00000016) TEX_INST(SAMPLE_G_LZ, 0x00000017) TEX_INST(SAMPLE_C, 0x00000018) TEX_INST(SAMPLE_C_L, 0x00000019) TEX_INST(SAMPLE_C_LB, 0x0000001A) TEX_INST(SAMPLE_C_LZ, 0x0000001B) TEX_INST(SAMPLE_C_G, 0x0000001C) TEX_INST(SAMPLE_C_G_L, 0x0000001D) TEX_INST(SAMPLE_C_G_LB, 0x0000001E) TEX_INST(SAMPLE_C_G_LZ, 0x0000001F) TEX_INST(SET_TEXTURE_OFFSETS, 0x00000020) TEX_INST(GATHER4, 0x00000021) TEX_INST(GATHER4_O, 0x00000022) TEX_INST(GATHER4_C, 0x00000023) TEX_INST(GATHER4_C_O, 0x00000024) TEX_INST(GET_BUFFER_RESINFO, 0x00000025) // VTX VTX_INST(FETCH, 0x00000000) VTX_INST(SEMANTIC, 0x00000001) VTX_INST(BUFINFO, 0x0000000E) // MEM MEM_INST(RD_SCRATCH, 0x00000000) MEM_INST(RD_REDUC, 0x00000001) MEM_INST(RD_SCATTER, 0x00000002) MEM_INST(LOCAL_DS_WRITE, 0x00000004) MEM_INST(LOCAL_DS_READ, 0x00000005) MEM_INST(DS_GLOBAL_WRITE, 0x00000006) MEM_INST(DS_GLOBAL_READ, 0x00000007) MEM_INST(MEM_GDS, 0x00000008) MEM_INST(TF_WRITE, 0x00000009) #undef CF_INST #undef EXP_INST #undef ALU_INST #undef ALU_OP2 #undef ALU_OP3 #undef TEX_INST #undef VTX_INST #undef MEM_INST /* #undef ALU_REDUC #undef ALU_VEC #undef ALU_TRANS #undef ALU_PRED_SET #undef ALU_INT #undef ALU_UINT #undef ALU_INT_IN #undef ALU_UINT_IN #undef ALU_INT_OUT #undef ALU_UINT_OUT */ ================================================ FILE: src/libgpu/latte/latte_pm4.h ================================================ #pragma once #include "latte_enum_pm4.h" #include <common/bitfield.h> #include <cstdint> namespace latte { namespace pm4 { #pragma pack(push, 1) BITFIELD_BEG(Header, uint32_t) BITFIELD_ENTRY(30, 2, PacketType, type); BITFIELD_END BITFIELD_BEG(HeaderType0, uint32_t) BITFIELD_ENTRY(0, 16, uint32_t, baseIndex); BITFIELD_ENTRY(16, 14, uint32_t, count); BITFIELD_ENTRY(30, 2, PacketType, type); BITFIELD_END BITFIELD_BEG(HeaderType2, uint32_t) BITFIELD_ENTRY(30, 2, PacketType, type); BITFIELD_END BITFIELD_BEG(HeaderType3, uint32_t) BITFIELD_ENTRY(0, 1, bool, predicate); BITFIELD_ENTRY(8, 8, IT_OPCODE, opcode); BITFIELD_ENTRY(16, 14, uint32_t, size); BITFIELD_ENTRY(30, 2, PacketType, type); BITFIELD_END #pragma pack(pop) } // namespace pm4 } // namespace latte ================================================ FILE: src/libgpu/latte/latte_pm4_commands.h ================================================ #pragma once #include "latte_enum_pm4.h" #include "latte_pm4.h" #include "latte_registers.h" #include <common/bitfield.h> #include <libcpu/be2_struct.h> #include <cstdint> #include <gsl/gsl-lite.hpp> #pragma pack(push, 1) namespace latte { namespace pm4 { enum ScanTarget : uint32_t { TV = 1, DRC = 4 }; struct DecafSwapBuffers { static const auto Opcode = IT_OPCODE::DECAF_SWAP_BUFFERS; uint32_t dummy; template<typename Serialiser> void serialise(Serialiser &se) { se(dummy); } }; struct DecafCopyColorToScan { static const auto Opcode = IT_OPCODE::DECAF_COPY_COLOR_TO_SCAN; ScanTarget scanTarget; latte::CB_COLORN_BASE cb_color_base; latte::CB_COLORN_FRAG cb_color_frag; uint32_t width; uint32_t height; latte::CB_COLORN_SIZE cb_color_size; latte::CB_COLORN_INFO cb_color_info; latte::CB_COLORN_VIEW cb_color_view; latte::CB_COLORN_MASK cb_color_mask; template<typename Serialiser> void serialise(Serialiser &se) { se(scanTarget); se(cb_color_base.value); se(cb_color_frag.value); se(width); se(height); se(cb_color_size.value); se(cb_color_info.value); se(cb_color_view.value); se(cb_color_mask.value); } }; struct DecafClearColor { static const auto Opcode = IT_OPCODE::DECAF_CLEAR_COLOR; float red; float green; float blue; float alpha; latte::CB_COLORN_BASE cb_color_base; latte::CB_COLORN_FRAG cb_color_frag; latte::CB_COLORN_SIZE cb_color_size; latte::CB_COLORN_INFO cb_color_info; latte::CB_COLORN_VIEW cb_color_view; latte::CB_COLORN_MASK cb_color_mask; template<typename Serialiser> void serialise(Serialiser &se) { se(red); se(green); se(blue); se(alpha); se(cb_color_base.value); se(cb_color_frag.value); se(cb_color_size.value); se(cb_color_info.value); se(cb_color_view.value); se(cb_color_mask.value); } }; struct DecafClearDepthStencil { static const auto Opcode = IT_OPCODE::DECAF_CLEAR_DEPTH_STENCIL; uint32_t flags; latte::DB_DEPTH_BASE db_depth_base; latte::DB_DEPTH_HTILE_DATA_BASE db_depth_htile_data_base; latte::DB_DEPTH_INFO db_depth_info; latte::DB_DEPTH_SIZE db_depth_size; latte::DB_DEPTH_VIEW db_depth_view; template<typename Serialiser> void serialise(Serialiser &se) { se(flags); se(db_depth_base.value); se(db_depth_htile_data_base.value); se(db_depth_info.value); se(db_depth_size.value); se(db_depth_view.value); } }; struct DecafSetBuffer { static const auto Opcode = IT_OPCODE::DECAF_SET_BUFFER; ScanTarget scanTarget; phys_addr buffer; uint32_t numBuffers; uint32_t width; uint32_t height; template<typename Serialiser> void serialise(Serialiser &se) { se(scanTarget); se(buffer); se(numBuffers); se(width); se(height); } }; struct DecafOSScreenFlip { static const auto Opcode = IT_OPCODE::DECAF_OSSCREEN_FLIP; uint32_t screen; phys_addr buffer; template<typename Serialiser> void serialise(Serialiser &se) { se(screen); se(buffer); } }; struct DecafCopySurface { static const auto Opcode = IT_OPCODE::DECAF_COPY_SURFACE; phys_addr dstImage; phys_addr dstMipmaps; uint32_t dstLevel; uint32_t dstSlice; uint32_t dstPitch; uint32_t dstWidth; uint32_t dstHeight; uint32_t dstDepth; uint32_t dstSamples; latte::SQ_TEX_DIM dstDim; latte::SQ_DATA_FORMAT dstFormat; latte::SQ_NUM_FORMAT dstNumFormat; latte::SQ_FORMAT_COMP dstFormatComp; uint32_t dstForceDegamma; latte::SQ_TILE_TYPE dstTileType; latte::SQ_TILE_MODE dstTileMode; phys_addr srcImage; phys_addr srcMipmaps; uint32_t srcLevel; uint32_t srcSlice; uint32_t srcPitch; uint32_t srcWidth; uint32_t srcHeight; uint32_t srcDepth; uint32_t srcSamples; latte::SQ_TEX_DIM srcDim; latte::SQ_DATA_FORMAT srcFormat; latte::SQ_NUM_FORMAT srcNumFormat; latte::SQ_FORMAT_COMP srcFormatComp; uint32_t srcForceDegamma; latte::SQ_TILE_TYPE srcTileType; latte::SQ_TILE_MODE srcTileMode; template<typename Serialiser> void serialise(Serialiser &se) { se(dstImage); se(dstMipmaps); se(dstLevel); se(dstSlice); se(dstPitch); se(dstWidth); se(dstHeight); se(dstDepth); se(dstSamples); se(dstDim); se(dstFormat); se(dstNumFormat); se(dstFormatComp); se(dstForceDegamma); se(dstTileType); se(dstTileMode); se(srcImage); se(srcMipmaps); se(srcLevel); se(srcSlice); se(srcPitch); se(srcWidth); se(srcHeight); se(srcDepth); se(srcSamples); se(srcDim); se(srcFormat); se(srcNumFormat); se(srcFormatComp); se(srcForceDegamma); se(srcTileType); se(srcTileMode); } }; struct DecafExpandColorBuffer { static const auto Opcode = IT_OPCODE::DECAF_EXPAND_COLORBUFFER; phys_addr dstImage; phys_addr dstMipmaps; uint32_t dstLevel; uint32_t dstSlice; uint32_t dstPitch; uint32_t dstWidth; uint32_t dstHeight; uint32_t dstDepth; uint32_t dstSamples; latte::SQ_TEX_DIM dstDim; latte::SQ_DATA_FORMAT dstFormat; latte::SQ_NUM_FORMAT dstNumFormat; latte::SQ_FORMAT_COMP dstFormatComp; uint32_t dstForceDegamma; latte::SQ_TILE_TYPE dstTileType; latte::SQ_TILE_MODE dstTileMode; phys_addr srcImage; phys_addr srcFmask; phys_addr srcMipmaps; uint32_t srcLevel; uint32_t srcSlice; uint32_t srcPitch; uint32_t srcWidth; uint32_t srcHeight; uint32_t srcDepth; uint32_t srcSamples; latte::SQ_TEX_DIM srcDim; latte::SQ_DATA_FORMAT srcFormat; latte::SQ_NUM_FORMAT srcNumFormat; latte::SQ_FORMAT_COMP srcFormatComp; uint32_t srcForceDegamma; latte::SQ_TILE_TYPE srcTileType; latte::SQ_TILE_MODE srcTileMode; uint32_t numSlices; template<typename Serialiser> void serialise(Serialiser &se) { se(dstImage); se(dstMipmaps); se(dstLevel); se(dstSlice); se(dstPitch); se(dstWidth); se(dstHeight); se(dstDepth); se(dstSamples); se(dstDim); se(dstFormat); se(dstNumFormat); se(dstFormatComp); se(dstForceDegamma); se(dstTileType); se(dstTileMode); se(srcImage); se(srcFmask); se(srcMipmaps); se(srcLevel); se(srcSlice); se(srcPitch); se(srcWidth); se(srcHeight); se(srcDepth); se(srcSamples); se(srcDim); se(srcFormat); se(srcNumFormat); se(srcFormatComp); se(srcForceDegamma); se(srcTileType); se(srcTileMode); se(numSlices); } }; enum COPY_DW_SEL : uint32_t { COPY_DW_SEL_REGISTER = 0, COPY_DW_SEL_MEMORY = 1, }; BITFIELD_BEG(COPY_DW_SELECT, uint32_t) BITFIELD_ENTRY(0, 1, COPY_DW_SEL, SRC); BITFIELD_ENTRY(1, 1, COPY_DW_SEL, DST); BITFIELD_END struct CopyDw { static const auto Opcode = IT_OPCODE::COPY_DW; COPY_DW_SELECT select; // Memory address or RegisterIndex/4 based on select.src phys_addr srcLo; uint32_t srcHi; // Memory address or RegisterIndex/4 based on select.dst latte::Register dstLo; uint32_t dstHi; template<typename Serialiser> void serialise(Serialiser &se) { se(select.value); se(srcLo); se(srcHi); se.REG_OFFSET(dstLo, static_cast<latte::Register>(0)); se(dstHi); } }; struct DrawIndexAuto { static const auto Opcode = IT_OPCODE::DRAW_INDEX_AUTO; uint32_t count; latte::VGT_DRAW_INITIATOR drawInitiator; template<typename Serialiser> void serialise(Serialiser &se) { se(count); se(drawInitiator.value); } }; struct DrawIndex2 { static const auto Opcode = IT_OPCODE::DRAW_INDEX_2; uint32_t maxIndices; // VGT_DMA_MAX_SIZE phys_addr addr; // VGT_DMA_BASE uint32_t count; // VGT_DMA_SIZE latte::VGT_DRAW_INITIATOR drawInitiator; // VGT_DRAW_INITIATOR template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(maxIndices); se(addr); se(unusedAddrHi); se(count); se(drawInitiator.value); } }; struct DrawIndexImmd { static const auto Opcode = IT_OPCODE::DRAW_INDEX_IMMD; uint32_t count; // VGT_DMA_SIZE latte::VGT_DRAW_INITIATOR drawInitiator; // VGT_DRAW_INITIATOR gsl::span<uint32_t> indices; template<typename Serialiser> void serialise(Serialiser &se) { se(count); se(drawInitiator); se(indices); } }; // This structure should only be used to WRITE struct DrawIndexImmdBE { static const auto Opcode = IT_OPCODE::DRAW_INDEX_IMMD; uint32_t count; // VGT_DMA_SIZE latte::VGT_DRAW_INITIATOR drawInitiator; // VGT_DRAW_INITIATOR gsl::span<be2_val<uint32_t>> indices; template<typename Serialiser> void serialise(Serialiser &se) { se(count); se(drawInitiator); se(indices); } }; // This structure should only be used to WRITE struct DrawIndexImmdBE16 { static const auto Opcode = IT_OPCODE::DRAW_INDEX_IMMD; uint32_t count; // VGT_DMA_SIZE latte::VGT_DRAW_INITIATOR drawInitiator; // VGT_DRAW_INITIATOR gsl::span<be2_val<uint32_t>> indices; template<typename Serialiser> void serialise(Serialiser &se) { se(count); se(drawInitiator); // Swap around the two 16 bit indices for (auto i = 0u; i < indices.size(); ++i) { auto index = static_cast<uint32_t>(indices[i]); index = static_cast<uint32_t>((index >> 16) | (index << 16)); se(index); } } }; struct IndexType { static const auto Opcode = IT_OPCODE::INDEX_TYPE; latte::VGT_DMA_INDEX_TYPE type; // VGT_DMA_INDEX_TYPE template<typename Serialiser> void serialise(Serialiser &se) { se(type.value); } }; struct NumInstances { static const auto Opcode = IT_OPCODE::NUM_INSTANCES; uint32_t count; // VGT_DMA_NUM_INSTANCES template<typename Serialiser> void serialise(Serialiser &se) { se(count); } }; struct SetAluConsts { static const auto Opcode = IT_OPCODE::SET_ALU_CONST; latte::Register id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::AluConstRegisterBase); se(values); } }; struct SetAluConstsBE { static const auto Opcode = IT_OPCODE::SET_ALU_CONST; latte::Register id; gsl::span<be2_val<uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::AluConstRegisterBase); se(values); } }; struct SetConfigReg { static const auto Opcode = IT_OPCODE::SET_CONFIG_REG; latte::Register id; uint32_t value; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ConfigRegisterBase); se(value); } }; struct SetConfigRegs { static const auto Opcode = IT_OPCODE::SET_CONFIG_REG; latte::Register id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ConfigRegisterBase); se(values); } }; struct SetContextReg { static const auto Opcode = IT_OPCODE::SET_CONTEXT_REG; latte::Register id; uint32_t value; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ContextRegisterBase); se(value); } }; struct SetContextRegs { static const auto Opcode = IT_OPCODE::SET_CONTEXT_REG; latte::Register id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ContextRegisterBase); se(values); } }; struct SetAllContextsReg { static const auto Opcode = IT_OPCODE::SET_ALL_CONTEXTS; latte::Register id; uint32_t value; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ContextRegisterBase); se(value); } }; struct SetControlConstant { static const auto Opcode = IT_OPCODE::SET_CTL_CONST; latte::Register id; uint32_t value; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ControlRegisterBase); se(value); } }; struct SetControlConstants { static const auto Opcode = IT_OPCODE::SET_CTL_CONST; latte::Register id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::ControlRegisterBase); se(values); } }; struct SetLoopConst { static const auto Opcode = IT_OPCODE::SET_LOOP_CONST; latte::Register id; uint32_t value; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::LoopConstRegisterBase); se(value); } }; struct SetLoopConsts { static const auto Opcode = IT_OPCODE::SET_LOOP_CONST; latte::Register id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::LoopConstRegisterBase); se(values); } }; struct SetSamplerAttrib { static const auto Opcode = IT_OPCODE::SET_SAMPLER; uint32_t id; latte::SQ_TEX_SAMPLER_WORD0_N word0; latte::SQ_TEX_SAMPLER_WORD1_N word1; latte::SQ_TEX_SAMPLER_WORD2_N word2; template<typename Serialiser> void serialise(Serialiser &se) { se.CONST_OFFSET(id); se(word0.value); se(word1.value); se(word2.value); } }; struct SetSamplers { static const auto Opcode = IT_OPCODE::SET_SAMPLER; latte::Register id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.REG_OFFSET(id, latte::Register::SamplerRegisterBase); se(values); } }; struct SetVtxResource { static const auto Opcode = IT_OPCODE::SET_RESOURCE; uint32_t id; phys_addr baseAddress; latte::SQ_VTX_CONSTANT_WORD1_N word1; latte::SQ_VTX_CONSTANT_WORD2_N word2; latte::SQ_VTX_CONSTANT_WORD3_N word3; latte::SQ_VTX_CONSTANT_WORD6_N word6; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedWord4 = 0xABCD1234; uint32_t unusedWord5 = 0xABCD1234; se.CONST_OFFSET(id); se(baseAddress); se(word1.value); se(word2.value); se(word3.value); se(unusedWord4); se(unusedWord5); se(word6.value); } }; struct SetTexResource { static const auto Opcode = IT_OPCODE::SET_RESOURCE; uint32_t id; latte::SQ_TEX_RESOURCE_WORD0_N word0; latte::SQ_TEX_RESOURCE_WORD1_N word1; latte::SQ_TEX_RESOURCE_WORD2_N word2; latte::SQ_TEX_RESOURCE_WORD3_N word3; latte::SQ_TEX_RESOURCE_WORD4_N word4; latte::SQ_TEX_RESOURCE_WORD5_N word5; latte::SQ_TEX_RESOURCE_WORD6_N word6; template<typename Serialiser> void serialise(Serialiser &se) { se.CONST_OFFSET(id); se(word0.value); se(word1.value); se(word2.value); se(word3.value); se(word4.value); se(word5.value); se(word6.value); } }; struct SetResources { static const auto Opcode = IT_OPCODE::SET_RESOURCE; uint32_t id; gsl::span<uint32_t> values; template<typename Serialiser> void serialise(Serialiser &se) { se.CONST_OFFSET(id); se(values); } }; struct IndirectBufferCall { static const auto Opcode = IT_OPCODE::INDIRECT_BUFFER; phys_addr addr; uint32_t size; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(size); } }; struct IndirectBufferCallPriv { static const auto Opcode = IT_OPCODE::INDIRECT_BUFFER_PRIV; phys_addr addr; uint32_t size; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(size); } }; struct LoadConfigReg { static const auto Opcode = IT_OPCODE::LOAD_CONFIG_REG; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadContextReg { static const auto Opcode = IT_OPCODE::LOAD_CONTEXT_REG; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadAluConst { static const auto Opcode = IT_OPCODE::LOAD_ALU_CONST; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadBoolConst { static const auto Opcode = IT_OPCODE::LOAD_BOOL_CONST; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadLoopConst { static const auto Opcode = IT_OPCODE::LOAD_LOOP_CONST; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadResource { static const auto Opcode = IT_OPCODE::LOAD_RESOURCE; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadSampler { static const auto Opcode = IT_OPCODE::LOAD_SAMPLER; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; struct LoadControlConst { static const auto Opcode = IT_OPCODE::LOAD_CTL_CONST; phys_addr addr; gsl::span<std::pair<uint32_t, uint32_t>> values; template<typename Serialiser> void serialise(Serialiser &se) { uint32_t unusedAddrHi = 0; se(addr); se(unusedAddrHi); se(values); } }; BITFIELD_BEG(MW_ADDR_LO, uint32_t) BITFIELD_ENTRY(0, 2, latte::CB_ENDIAN, ENDIAN_SWAP); BITFIELD_ENTRY(2, 30, uint32_t, ADDR_LO); BITFIELD_END enum MW_CNTR_SEL : uint32_t { MW_WRITE_DATA = 0, MW_WRITE_CLOCK = 1, }; BITFIELD_BEG(MW_ADDR_HI, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, ADDR_HI); BITFIELD_ENTRY(16, 1, MW_CNTR_SEL, CNTR_SEL); BITFIELD_ENTRY(17, 1, bool, WR_CONFIRM); BITFIELD_ENTRY(18, 1, bool, DATA32); BITFIELD_END struct MemWrite { static const auto Opcode = IT_OPCODE::MEM_WRITE; MW_ADDR_LO addrLo; MW_ADDR_HI addrHi; uint32_t dataLo; uint32_t dataHi; template<typename Serialiser> void serialise(Serialiser &se) { se(addrLo.value); se(addrHi.value); se(dataLo); se(dataHi); } }; BITFIELD_BEG(EW_ADDR_LO, uint32_t) BITFIELD_ENTRY(0, 2, latte::CB_ENDIAN, ENDIAN_SWAP); BITFIELD_ENTRY(2, 30, uint32_t, ADDR_LO); BITFIELD_END BITFIELD_BEG(EW_ADDR_HI, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, ADDR_HI); BITFIELD_END struct EventWrite { static const auto Opcode = IT_OPCODE::EVENT_WRITE; latte::VGT_EVENT_INITIATOR eventInitiator; EW_ADDR_LO addrLo; EW_ADDR_HI addrHi; template<typename Serialiser> void serialise(Serialiser &se) { se(eventInitiator.value); if (eventInitiator.EVENT_INDEX() == latte::VGT_EVENT_INDEX::ZPASS_DONE || eventInitiator.EVENT_INDEX() == latte::VGT_EVENT_INDEX::SAMPLE_PIPELINESTAT || eventInitiator.EVENT_INDEX() == latte::VGT_EVENT_INDEX::SAMPLE_STREAMOUTSTAT) { se(addrLo.value); se(addrHi.value); } } }; enum EWP_DATA_SEL : uint32_t { EWP_DATA_DISCARD = 0, EWP_DATA_32 = 1, EWP_DATA_64 = 2, EWP_DATA_CLOCK = 3, }; enum EWP_INT_SEL : uint32_t { EWP_INT_NONE = 0, EWP_INT_ONLY = 1, EWP_INT_WRITE_CONFIRM = 2, }; BITFIELD_BEG(EWP_ADDR_HI, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, ADDR_HI); BITFIELD_ENTRY(24, 2, EWP_INT_SEL, INT_SEL); BITFIELD_ENTRY(29, 3, EWP_DATA_SEL, DATA_SEL); BITFIELD_END struct EventWriteEOP { static const auto Opcode = IT_OPCODE::EVENT_WRITE_EOP; latte::VGT_EVENT_INITIATOR eventInitiator; EW_ADDR_LO addrLo; EWP_ADDR_HI addrHi; uint32_t dataLo; uint32_t dataHi; template<typename Serialiser> void serialise(Serialiser &se) { se(eventInitiator.value); se(addrLo.value); se(addrHi.value); se(dataLo); se(dataHi); } }; struct PfpSyncMe { static const auto Opcode = IT_OPCODE::PFP_SYNC_ME; uint32_t dummy; template<typename Serialiser> void serialise(Serialiser &se) { se(dummy); } }; struct StreamOutBaseUpdate { static const auto Opcode = IT_OPCODE::STRMOUT_BASE_UPDATE; uint32_t index; uint32_t addr; template<typename Serialiser> void serialise(Serialiser &se) { se(index); se(addr); } }; enum STRMOUT_OFFSET_SOURCE : uint32_t { STRMOUT_OFFSET_FROM_PACKET = 0, STRMOUT_OFFSET_FROM_VGT_FILLED_SIZE = 1, STRMOUT_OFFSET_FROM_MEM = 2, STRMOUT_OFFSET_NONE = 3, }; BITFIELD_BEG(SBU_CONTROL, uint32_t) BITFIELD_ENTRY(0, 1, bool, STORE_BUFFER_FILLED_SIZE); BITFIELD_ENTRY(1, 2, STRMOUT_OFFSET_SOURCE, OFFSET_SOURCE); BITFIELD_ENTRY(8, 2, uint8_t, SELECT_BUFFER); BITFIELD_END struct StreamOutBufferUpdate { static const auto Opcode = IT_OPCODE::STRMOUT_BUFFER_UPDATE; SBU_CONTROL control; phys_addr dstLo; // Store target for STORE_BUFFER_FILLED_SIZE uint32_t dstHi; phys_addr srcLo; // Offset for STRMOUT_OFFSET_FROM_PACKET; uint32_t srcHi; // address of offset for STRMOUT_OFFSET_FROM_MEM template<typename Serialiser> void serialise(Serialiser &se) { se(control.value); se(dstLo); se(dstHi); se(srcLo); se(srcHi); } }; struct Nop { static const auto Opcode = IT_OPCODE::NOP; uint32_t unk; gsl::span<uint32_t> strWords; template<typename Serialiser> void serialise(Serialiser &se) { se(unk); se(strWords); } }; struct NopBE { static const auto Opcode = IT_OPCODE::NOP; uint32_t unk; gsl::span<be2_val<uint32_t>> strWords; template<typename Serialiser> void serialise(Serialiser &se) { se(unk); se(strWords); } }; struct SurfaceSync { static const auto Opcode = IT_OPCODE::SURFACE_SYNC; latte::CP_COHER_CNTL cp_coher_cntl; uint32_t size; uint32_t addr; uint32_t pollInterval; template<typename Serialiser> void serialise(Serialiser &se) { se(cp_coher_cntl.value); se(size); se(addr); se(pollInterval); } }; enum SP_PRED_OP { SP_PRED_OP_CLEAR = 0, SP_PRED_OP_ZPASS = 1, SP_PRED_OP_PRIMCOUNT = 2, }; BITFIELD_BEG(SET_PRED, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, ADDR_HI); BITFIELD_ENTRY(8, 1, bool, PREDICATE); BITFIELD_ENTRY(12, 1, bool, HINT); BITFIELD_ENTRY(16, 3, SP_PRED_OP, PRED_OP); BITFIELD_ENTRY(31, 1, bool, CONTINUE); BITFIELD_END struct SetPredication { static const auto Opcode = IT_OPCODE::SET_PREDICATION; uint32_t addrLo; SET_PRED set_pred; template<typename Serialiser> void serialise(Serialiser &se) { se(addrLo); se(set_pred.value); } }; struct ContextControl { static const auto Opcode = IT_OPCODE::CONTEXT_CTL; latte::CONTEXT_CONTROL_ENABLE LOAD_CONTROL; latte::CONTEXT_CONTROL_ENABLE SHADOW_ENABLE; template<typename Serialiser> void serialise(Serialiser &se) { se(LOAD_CONTROL.value); se(SHADOW_ENABLE.value); } }; enum WRM_ENGINE : uint32_t { ENGINE_ME = 0, ENGINE_PFP = 1, }; enum WRM_FUNCTION : uint32_t { FUNCTION_ALWAYS = 0, FUNCTION_LESS_THAN = 1, FUNCTION_LESS_THAN_EQUAL = 2, FUNCTION_EQUAL = 3, FUNCTION_NOT_EQUAL = 4, FUNCTION_GREATER_THAN_EQUAL = 5, FUNCTION_GREATER_THAN = 6, }; enum WRM_MEM_SPACE : uint32_t { MEM_SPACE_REGISTER = 0, MEM_SPACE_MEMORY = 1, }; BITFIELD_BEG(MEM_SPACE_FUNCTION, uint32_t) BITFIELD_ENTRY(0, 3, WRM_FUNCTION, FUNCTION); BITFIELD_ENTRY(4, 1, WRM_MEM_SPACE, MEM_SPACE); BITFIELD_ENTRY(8, 1, WRM_ENGINE, ENGINE); BITFIELD_END BITFIELD_BEG(WRM_ADDR_LO, uint32_t) BITFIELD_ENTRY(0, 2, latte::CB_ENDIAN, ENDIAN_SWAP); BITFIELD_ENTRY(2, 30, uint32_t, ADDR_LO); BITFIELD_END BITFIELD_BEG(WRM_ADDR_HI, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, ADDR_HI); BITFIELD_END struct WaitReg { static constexpr auto Opcode = IT_OPCODE::WAIT_REG_MEM; MEM_SPACE_FUNCTION memSpaceFunction; latte::Register addrLo; WRM_ADDR_HI addrHi; uint32_t reference; uint32_t mask; uint32_t pollInterval; template<typename Serialiser> void serialise(Serialiser &se) { se(memSpaceFunction.value); se.REG_OFFSET(addrLo, static_cast<latte::Register>(0)); se(addrHi.value); se(reference); se(mask); se(pollInterval); } }; struct WaitMem { static constexpr auto Opcode = IT_OPCODE::WAIT_REG_MEM; MEM_SPACE_FUNCTION memSpaceFunction; WRM_ADDR_LO addrLo; WRM_ADDR_HI addrHi; uint32_t reference; uint32_t mask; uint32_t pollInterval; template<typename Serialiser> void serialise(Serialiser &se) { se(memSpaceFunction.value); se(addrLo); se(addrHi.value); se(reference); se(mask); se(pollInterval); } }; } // namespace pm4 } // namespace latte #pragma pack(pop) ================================================ FILE: src/libgpu/latte/latte_pm4_reader.h ================================================ #pragma once #include "latte_pm4.h" #include "latte_registers.h" #include <common/decaf_assert.h> #include <libcpu/mem.h> #include <gsl/gsl-lite.hpp> namespace latte { namespace pm4 { /** * Note: packet reader assumes the buffer has already been byte swapped to * little endian before reading. This is because it returns pointers to the * buffer, and we have to be able to return swapped memory. We cannot do an * in place swap because that could modify the game's memory. */ class PacketReader { public: PacketReader(gsl::span<uint32_t> data) : mBuffer(data) { } // Read one word PacketReader &operator()(uint32_t &value) { checkSize(1); value = mBuffer[mPosition++]; return *this; } // Read one float PacketReader &operator()(float &value) { checkSize(1); value = bit_cast<float>(mBuffer[mPosition++]); return *this; } // Read one uint32_t sized datatype template <typename Type> PacketReader &operator()(Type &value) { static_assert(sizeof(Type) == sizeof(uint32_t), "Invalid type size"); checkSize(1); value = bit_cast<Type>(mBuffer[mPosition++]); return *this; } // Read the rest of the entire packet template<typename Type> PacketReader &operator()(gsl::span<Type> &values) { if (mBuffer.size() - mPosition == 0) { values = {}; } else { values = gsl::make_span(reinterpret_cast<Type*>(&mBuffer[mPosition]), ((mBuffer.size() - mPosition) * sizeof(uint32_t)) / sizeof(Type)); } mPosition = mBuffer.size(); return *this; } // Read one word as a REG_OFFSET PacketReader ®_OFFSET(latte::Register &value, latte::Register base) { checkSize(1); value = static_cast<latte::Register>(((mBuffer[mPosition++] & 0xFFFF) * 4) + (uint32_t)base); return *this; } // Read one word as a CONST_OFFSET PacketReader &CONST_OFFSET(uint32_t &value) { checkSize(1); value = mBuffer[mPosition++] & 0xFFFF; return *this; } // Read one word as a size (N - 1) template<typename Type> PacketReader &size(Type &value) { checkSize(1); value = static_cast<Type>(mBuffer[mPosition++] + 1); return *this; } private: void checkSize(size_t sizeToRead) { if (mPosition + sizeToRead > mBuffer.size()) { decaf_abort("Read past end of packet"); } } private: size_t mPosition = 0; gsl::span<uint32_t> mBuffer; }; template<typename Type> Type read(PacketReader &reader) { Type result; result.serialise(reader); return result; } } // namespace pm4 } // namespace latte ================================================ FILE: src/libgpu/latte/latte_pm4_sizer.h ================================================ #pragma once #include "latte_registers.h" #include <gsl/gsl-lite.hpp> namespace latte { namespace pm4 { class PacketSizer { public: PacketSizer() : mPayloadSize(0) { } // Read one uint32_t sized datatype template <typename Type> PacketSizer &operator()(Type value) { static_assert(sizeof(Type) == sizeof(uint32_t), "Invalid type size"); mPayloadSize++; return *this; } // Write a pointer as one word template<typename Type> PacketSizer &operator()(Type *value) { mPayloadSize++; return *this; } // Write a list of words template<typename Type> PacketSizer &operator()(const gsl::span<Type> &values) { auto dataSize = gsl::narrow_cast<uint32_t>(((values.size() * sizeof(Type)) + 3) / 4); mPayloadSize += dataSize; return *this; } // Write one word as a REG_OFFSET PacketSizer ®_OFFSET(latte::Register value, latte::Register base) { mPayloadSize++; return *this; } // Write one word as a CONST_OFFSET PacketSizer &CONST_OFFSET(uint32_t value) { mPayloadSize++; return *this; } // Write one word as a size (N - 1) template<typename Type> PacketSizer &size(Type value) { mPayloadSize++; return *this; } uint32_t getSize() const { return mPayloadSize; } protected: uint32_t mPayloadSize; }; } // namespace pm4 } // namespace latte ================================================ FILE: src/libgpu/latte/latte_pm4_writer.h ================================================ #pragma once #include "latte_registers.h" #include "latte_pm4_commands.h" #include <libcpu/mmu.h> #include <gsl/gsl-lite.hpp> namespace latte::pm4 { class PacketWriter { public: PacketWriter(uint32_t *buffer, uint32_t &outSize, IT_OPCODE op, uint32_t totalSize) : mBuffer(buffer), mCurSize(outSize) { mTotalSize = totalSize; mSaveSize = mCurSize; auto header = latte::pm4::HeaderType3::get(0) .type(latte::pm4::PacketType::Type3) .opcode(op) .size(mTotalSize - 2); mBuffer[mCurSize++] = byte_swap(header.value); } ~PacketWriter() { decaf_check(mCurSize - mSaveSize == mTotalSize); } // Write one word PacketWriter &operator()(uint32_t value) { mBuffer[mCurSize++] = byte_swap(value); return *this; } // Write one float PacketWriter &operator()(float value) { mBuffer[mCurSize++] = byte_swap(bit_cast<uint32_t>(value)); return *this; } // Read one uint32_t sized datatype template <typename Type> PacketWriter &operator()(Type value) { static_assert(sizeof(Type) == sizeof(uint32_t), "Invalid type size"); mBuffer[mCurSize++] = byte_swap(bit_cast<uint32_t>(value)); return *this; } // Write a list of words template<typename Type> PacketWriter &operator()(const gsl::span<Type> &values) { auto dataSize = gsl::narrow_cast<uint32_t>(((values.size() * sizeof(Type)) + 3) / 4); std::memcpy(mBuffer + mCurSize, values.data(), dataSize * sizeof(uint32_t)); // We do the byte_swap here separately as Type may not be uint32_t sized for (auto i = 0u; i < dataSize; ++i) { mBuffer[mCurSize + i] = byte_swap(mBuffer[mCurSize + i]); } mCurSize += dataSize; return *this; } // Write a list of already swapped words template<typename Type> PacketWriter &operator()(const gsl::span<be2_val<Type>> &values) { auto dataSize = gsl::narrow_cast<uint32_t>(((values.size() * sizeof(Type)) + 3) / 4); std::memcpy(mBuffer + mCurSize, values.data(), dataSize * sizeof(uint32_t)); mCurSize += dataSize; return *this; } // Write one word as a REG_OFFSET PacketWriter ®_OFFSET(latte::Register value, latte::Register base) { auto offset = static_cast<uint32_t>(value) - static_cast<uint32_t>(base); mBuffer[mCurSize++] = byte_swap(offset / 4); return *this; } // Write one word as a CONST_OFFSET PacketWriter &CONST_OFFSET(uint32_t value) { mBuffer[mCurSize++] = byte_swap(value); return *this; } // Write one word as a size (N - 1) template<typename Type> PacketWriter &size(Type value) { mBuffer[mCurSize++] = byte_swap(static_cast<uint32_t>(value) - 1); return *this; } private: uint32_t *mBuffer; uint32_t &mCurSize; uint32_t mTotalSize; uint32_t mSaveSize; }; } // namespace namespace latte::pm4 ================================================ FILE: src/libgpu/latte/latte_registers.h ================================================ #pragma once #include "latte_contextstate.h" #include "latte_registers_cb.h" #include "latte_registers_cp.h" #include "latte_registers_db.h" #include "latte_registers_pa.h" #include "latte_registers_spi.h" #include "latte_registers_sq.h" #include "latte_registers_sx.h" #include "latte_registers_ta.h" #include "latte_registers_td.h" #include "latte_registers_vgt.h" #include <cstdint> namespace latte { static const uint32_t MicroTileWidth = 8; static const uint32_t MicroTileHeight = 8; namespace Register_ { enum Value : uint32_t { IH_RB_BASE = 0x03E04, IH_RB_RPTR = 0x03E08, IH_RB_WPTR = 0x03E0C, IH_STATUS = 0x03E20, // Config Registers ConfigRegisterBase = 0x08000, CP_COHER_STATUS = 0x085FC, CP_BUSY_STAT = 0x0867C, CP_STAT = 0x08680, CP_RB_RPTR = 0x08700, CP_IB1_BASE_LO = 0x08730, CP_IB1_BASE_HI = 0x08734, CP_IB1_BASE_SIZE = 0x08738, CP_IB2_BASE_LO = 0x0873C, CP_IB2_BASE_HI = 0x08740, CP_IB2_BASE_SIZE = 0x08744, CP_PERFMON_CNTL = 0x087FC, VGT_GS_PER_ES = 0x088C8, VGT_ES_PER_GS = 0x088CC, VGT_GS_VERTEX_REUSE = 0x088D4, VGT_GS_PER_VS = 0x088E8, VGT_PRIMITIVE_TYPE = 0x08958, VGT_INDEX_TYPE = 0x0895C, VGT_NUM_INDICES = 0x08970, SQ_CONFIG = 0x08C00, SQ_GPR_RESOURCE_MGMT_1 = 0x08C04, SQ_GPR_RESOURCE_MGMT_2 = 0x08C08, SQ_THREAD_RESOURCE_MGMT = 0x08C0C, SQ_STACK_RESOURCE_MGMT_1 = 0x08C10, SQ_STACK_RESOURCE_MGMT_2 = 0x08C14, SQ_ESGS_RING_BASE = 0x08C40, SQ_ESGS_RING_SIZE = 0x08C44, SQ_GSVS_RING_BASE = 0x08C48, SQ_GSVS_RING_SIZE = 0x08C4C, SQ_ESTMP_RING_BASE = 0x08C50, SQ_ESTMP_RING_SIZE = 0x08C54, SQ_GSTMP_RING_BASE = 0x08C58, SQ_GSTMP_RING_SIZE = 0x08C5C, SQ_VSTMP_RING_BASE = 0x08C60, SQ_VSTMP_RING_SIZE = 0x08C64, SQ_PSTMP_RING_BASE = 0x08C68, SQ_PSTMP_RING_SIZE = 0x08C6C, SQ_FBUF_RING_BASE = 0x08C70, SQ_FBUF_RING_SIZE = 0x08C74, SQ_REDUC_RING_BASE = 0x08C78, SQ_REDUC_RING_SIZE = 0x08C7C, SPI_CONFIG_CNTL_1 = 0x0913C, TA_CNTL_AUX = 0x09508, TD_PS_SAMPLER_BORDER0_RED = 0x0A400, TD_PS_SAMPLER_BORDER0_GREEN = 0x0A404, TD_PS_SAMPLER_BORDER0_BLUE = 0x0A408, TD_PS_SAMPLER_BORDER0_ALPHA = 0x0A40C, // ... TD_PS_SAMPLER_BORDER17_RED = 0x0A510, TD_PS_SAMPLER_BORDER17_GREEN = 0x0A514, TD_PS_SAMPLER_BORDER17_BLUE = 0x0A518, TD_PS_SAMPLER_BORDER17_ALPHA = 0x0A51C, TD_VS_SAMPLER_BORDER0_RED = 0x0A600, TD_VS_SAMPLER_BORDER0_GREEN = 0x0A604, TD_VS_SAMPLER_BORDER0_BLUE = 0x0A608, TD_VS_SAMPLER_BORDER0_ALPHA = 0x0A60C, // ... TD_VS_SAMPLER_BORDER17_RED = 0x0A710, TD_VS_SAMPLER_BORDER17_GREEN = 0x0A714, TD_VS_SAMPLER_BORDER17_BLUE = 0x0A718, TD_VS_SAMPLER_BORDER17_ALPHA = 0x0A71C, TD_GS_SAMPLER_BORDER0_RED = 0x0A900, TD_GS_SAMPLER_BORDER0_GREEN = 0x0A904, TD_GS_SAMPLER_BORDER0_BLUE = 0x0A908, TD_GS_SAMPLER_BORDER0_ALPHA = 0x0A90C, // ... TD_GS_SAMPLER_BORDER17_RED = 0x0AA10, TD_GS_SAMPLER_BORDER17_GREEN = 0x0AA14, TD_GS_SAMPLER_BORDER17_BLUE = 0x0AA18, TD_GS_SAMPLER_BORDER17_ALPHA = 0x0AA1C, ConfigRegisterEnd = 0x0AC00, CP_RB_BASE = 0x0C100, CP_RB_CNTL = 0x0C104, CP_INT_CNTL = 0x0C124, CP_INT_STATUS = 0x0C128, // Context Registers ContextRegisterBase = 0x28000, DB_DEPTH_SIZE = 0x28000, DB_DEPTH_VIEW = 0x28004, DB_DEPTH_BASE = 0x2800C, DB_DEPTH_INFO = 0x28010, DB_DEPTH_HTILE_DATA_BASE = 0x28014, DB_STENCIL_CLEAR = 0x28028, DB_DEPTH_CLEAR = 0x2802C, PA_SC_SCREEN_SCISSOR_TL = 0x28030, PA_SC_SCREEN_SCISSOR_BR = 0x28034, CB_COLOR0_BASE = 0x28040, CB_COLOR1_BASE = 0x28044, CB_COLOR2_BASE = 0x28048, CB_COLOR3_BASE = 0x2804C, CB_COLOR4_BASE = 0x28050, CB_COLOR5_BASE = 0x28054, CB_COLOR6_BASE = 0x28058, CB_COLOR7_BASE = 0x2805C, CB_COLOR0_SIZE = 0x28060, CB_COLOR1_SIZE = 0x28064, CB_COLOR2_SIZE = 0x28068, CB_COLOR3_SIZE = 0x2806C, CB_COLOR4_SIZE = 0x28070, CB_COLOR5_SIZE = 0x28074, CB_COLOR6_SIZE = 0x28078, CB_COLOR7_SIZE = 0x2807C, CB_COLOR0_VIEW = 0x28080, CB_COLOR1_VIEW = 0x28084, CB_COLOR2_VIEW = 0x28088, CB_COLOR3_VIEW = 0x2808C, CB_COLOR4_VIEW = 0x28090, CB_COLOR5_VIEW = 0x28094, CB_COLOR6_VIEW = 0x28098, CB_COLOR7_VIEW = 0x2809C, CB_COLOR0_INFO = 0x280A0, CB_COLOR1_INFO = 0x280A4, CB_COLOR2_INFO = 0x280A8, CB_COLOR3_INFO = 0x280AC, CB_COLOR4_INFO = 0x280B0, CB_COLOR5_INFO = 0x280B4, CB_COLOR6_INFO = 0x280B8, CB_COLOR7_INFO = 0x280BC, CB_COLOR0_TILE = 0x280C0, CB_COLOR1_TILE = 0x280C4, CB_COLOR2_TILE = 0x280C8, CB_COLOR3_TILE = 0x280CC, CB_COLOR4_TILE = 0x280D0, CB_COLOR5_TILE = 0x280D4, CB_COLOR6_TILE = 0x280D8, CB_COLOR7_TILE = 0x280DC, CB_COLOR0_FRAG = 0x280E0, CB_COLOR1_FRAG = 0x280E4, CB_COLOR2_FRAG = 0x280E8, CB_COLOR3_FRAG = 0x280EC, CB_COLOR4_FRAG = 0x280F0, CB_COLOR5_FRAG = 0x280F4, CB_COLOR6_FRAG = 0x280F8, CB_COLOR7_FRAG = 0x280FC, CB_COLOR0_MASK = 0x28100, CB_COLOR1_MASK = 0x28104, CB_COLOR2_MASK = 0x28108, CB_COLOR3_MASK = 0x2810C, CB_COLOR4_MASK = 0x28110, CB_COLOR5_MASK = 0x28114, CB_COLOR6_MASK = 0x28118, CB_COLOR7_MASK = 0x2811C, SQ_ALU_CONST_BUFFER_SIZE_PS_0 = 0x28140, SQ_ALU_CONST_BUFFER_SIZE_PS_1 = 0x28144, SQ_ALU_CONST_BUFFER_SIZE_PS_2 = 0x28148, SQ_ALU_CONST_BUFFER_SIZE_PS_3 = 0x2814C, SQ_ALU_CONST_BUFFER_SIZE_PS_4 = 0x28150, SQ_ALU_CONST_BUFFER_SIZE_PS_5 = 0x28154, SQ_ALU_CONST_BUFFER_SIZE_PS_6 = 0x28158, SQ_ALU_CONST_BUFFER_SIZE_PS_7 = 0x2815C, SQ_ALU_CONST_BUFFER_SIZE_PS_8 = 0x28160, SQ_ALU_CONST_BUFFER_SIZE_PS_9 = 0x28164, SQ_ALU_CONST_BUFFER_SIZE_PS_10 = 0x28168, SQ_ALU_CONST_BUFFER_SIZE_PS_11 = 0x2816C, SQ_ALU_CONST_BUFFER_SIZE_PS_12 = 0x28170, SQ_ALU_CONST_BUFFER_SIZE_PS_13 = 0x28174, SQ_ALU_CONST_BUFFER_SIZE_PS_14 = 0x28178, SQ_ALU_CONST_BUFFER_SIZE_PS_15 = 0x2817C, SQ_ALU_CONST_BUFFER_SIZE_VS_0 = 0x28180, SQ_ALU_CONST_BUFFER_SIZE_VS_1 = 0x28184, SQ_ALU_CONST_BUFFER_SIZE_VS_2 = 0x28188, SQ_ALU_CONST_BUFFER_SIZE_VS_3 = 0x2818C, SQ_ALU_CONST_BUFFER_SIZE_VS_4 = 0x28190, SQ_ALU_CONST_BUFFER_SIZE_VS_5 = 0x28194, SQ_ALU_CONST_BUFFER_SIZE_VS_6 = 0x28198, SQ_ALU_CONST_BUFFER_SIZE_VS_7 = 0x2819C, SQ_ALU_CONST_BUFFER_SIZE_VS_8 = 0x281A0, SQ_ALU_CONST_BUFFER_SIZE_VS_9 = 0x281A4, SQ_ALU_CONST_BUFFER_SIZE_VS_10 = 0x281A8, SQ_ALU_CONST_BUFFER_SIZE_VS_11 = 0x281AC, SQ_ALU_CONST_BUFFER_SIZE_VS_12 = 0x281B0, SQ_ALU_CONST_BUFFER_SIZE_VS_13 = 0x281B4, SQ_ALU_CONST_BUFFER_SIZE_VS_14 = 0x281B8, SQ_ALU_CONST_BUFFER_SIZE_VS_15 = 0x281BC, SQ_ALU_CONST_BUFFER_SIZE_GS_0 = 0x281C0, SQ_ALU_CONST_BUFFER_SIZE_GS_1 = 0x281C4, SQ_ALU_CONST_BUFFER_SIZE_GS_2 = 0x281C8, SQ_ALU_CONST_BUFFER_SIZE_GS_3 = 0x281CC, SQ_ALU_CONST_BUFFER_SIZE_GS_4 = 0x281D0, SQ_ALU_CONST_BUFFER_SIZE_GS_5 = 0x281D4, SQ_ALU_CONST_BUFFER_SIZE_GS_6 = 0x281D8, SQ_ALU_CONST_BUFFER_SIZE_GS_7 = 0x281DC, SQ_ALU_CONST_BUFFER_SIZE_GS_8 = 0x281E0, SQ_ALU_CONST_BUFFER_SIZE_GS_9 = 0x281E4, SQ_ALU_CONST_BUFFER_SIZE_GS_10 = 0x281E8, SQ_ALU_CONST_BUFFER_SIZE_GS_11 = 0x281EC, SQ_ALU_CONST_BUFFER_SIZE_GS_12 = 0x281F0, SQ_ALU_CONST_BUFFER_SIZE_GS_13 = 0x281F4, SQ_ALU_CONST_BUFFER_SIZE_GS_14 = 0x281F8, SQ_ALU_CONST_BUFFER_SIZE_GS_15 = 0x281FC, PA_SC_WINDOW_OFFSET = 0x28200, PA_SC_WINDOW_SCISSOR_TL = 0x28204, PA_SC_WINDOW_SCISSOR_BR = 0x28208, PA_SC_CLIPRECT_RULE = 0x2820C, CB_TARGET_MASK = 0x28238, CB_SHADER_MASK = 0x2823C, PA_SC_GENERIC_SCISSOR_TL = 0x28240, PA_SC_GENERIC_SCISSOR_BR = 0x28244, PA_SC_VPORT_SCISSOR_0_TL = 0x28250, PA_SC_VPORT_SCISSOR_0_BR = 0x28254, PA_SC_VPORT_ZMIN_0 = 0x282D0, PA_SC_VPORT_ZMAX_0 = 0x282D4, PA_SC_VPORT_ZMIN_1 = 0x282D8, PA_SC_VPORT_ZMAX_1 = 0x282DC, PA_SC_VPORT_ZMIN_2 = 0x282E0, PA_SC_VPORT_ZMAX_2 = 0x282E4, PA_SC_VPORT_ZMIN_3 = 0x282E8, PA_SC_VPORT_ZMAX_3 = 0x282EC, PA_SC_VPORT_ZMIN_4 = 0x282F0, PA_SC_VPORT_ZMAX_4 = 0x282F4, PA_SC_VPORT_ZMIN_5 = 0x282F8, PA_SC_VPORT_ZMAX_5 = 0x282FC, PA_SC_VPORT_ZMIN_6 = 0x28300, PA_SC_VPORT_ZMAX_6 = 0x28304, PA_SC_VPORT_ZMIN_7 = 0x28308, PA_SC_VPORT_ZMAX_7 = 0x2830C, PA_SC_VPORT_ZMIN_8 = 0x28310, PA_SC_VPORT_ZMAX_8 = 0x28314, PA_SC_VPORT_ZMIN_9 = 0x28318, PA_SC_VPORT_ZMAX_9 = 0x2831C, PA_SC_VPORT_ZMIN_10 = 0x28320, PA_SC_VPORT_ZMAX_10 = 0x28324, PA_SC_VPORT_ZMIN_11 = 0x28328, PA_SC_VPORT_ZMAX_11 = 0x2832C, PA_SC_VPORT_ZMIN_12 = 0x28330, PA_SC_VPORT_ZMAX_12 = 0x28334, PA_SC_VPORT_ZMIN_13 = 0x28338, PA_SC_VPORT_ZMAX_13 = 0x2833C, PA_SC_VPORT_ZMIN_14 = 0x28340, PA_SC_VPORT_ZMAX_14 = 0x28344, PA_SC_VPORT_ZMIN_15 = 0x28348, PA_SC_VPORT_ZMAX_15 = 0x2834C, SQ_VTX_SEMANTIC_0 = 0x28380, SQ_VTX_SEMANTIC_1 = 0x28384, SQ_VTX_SEMANTIC_2 = 0x28388, SQ_VTX_SEMANTIC_3 = 0x2838C, SQ_VTX_SEMANTIC_4 = 0x28390, SQ_VTX_SEMANTIC_5 = 0x28394, SQ_VTX_SEMANTIC_6 = 0x28398, SQ_VTX_SEMANTIC_7 = 0x2839C, SQ_VTX_SEMANTIC_8 = 0x283A0, SQ_VTX_SEMANTIC_9 = 0x283A4, SQ_VTX_SEMANTIC_10 = 0x283A8, SQ_VTX_SEMANTIC_11 = 0x283AC, SQ_VTX_SEMANTIC_12 = 0x283B0, SQ_VTX_SEMANTIC_13 = 0x283B4, SQ_VTX_SEMANTIC_14 = 0x283B8, SQ_VTX_SEMANTIC_15 = 0x283BC, SQ_VTX_SEMANTIC_16 = 0x283C0, SQ_VTX_SEMANTIC_17 = 0x283C4, SQ_VTX_SEMANTIC_18 = 0x283C8, SQ_VTX_SEMANTIC_19 = 0x283CC, SQ_VTX_SEMANTIC_20 = 0x283D0, SQ_VTX_SEMANTIC_21 = 0x283D4, SQ_VTX_SEMANTIC_22 = 0x283D8, SQ_VTX_SEMANTIC_23 = 0x283DC, SQ_VTX_SEMANTIC_24 = 0x283E0, SQ_VTX_SEMANTIC_25 = 0x283E4, SQ_VTX_SEMANTIC_26 = 0x283E8, SQ_VTX_SEMANTIC_27 = 0x283EC, SQ_VTX_SEMANTIC_28 = 0x283F0, SQ_VTX_SEMANTIC_29 = 0x283F4, SQ_VTX_SEMANTIC_30 = 0x283F8, SQ_VTX_SEMANTIC_31 = 0x283FC, VGT_MAX_VTX_INDX = 0x28400, VGT_MIN_VTX_INDX = 0x28404, VGT_INDX_OFFSET = 0x28408, VGT_MULTI_PRIM_IB_RESET_INDX = 0x2840C, SX_ALPHA_TEST_CONTROL = 0x28410, CB_BLEND_RED = 0x28414, CB_BLEND_GREEN = 0x28418, CB_BLEND_BLUE = 0x2841C, CB_BLEND_ALPHA = 0x28420, DB_STENCILREFMASK = 0x28430, DB_STENCILREFMASK_BF = 0x28434, SX_ALPHA_REF = 0x28438, PA_CL_VPORT_XSCALE_0 = 0x2843C, PA_CL_VPORT_XOFFSET_0 = 0x28440, PA_CL_VPORT_YSCALE_0 = 0x28444, PA_CL_VPORT_YOFFSET_0 = 0x28448, PA_CL_VPORT_ZSCALE_0 = 0x2844C, PA_CL_VPORT_ZOFFSET_0 = 0x28450, // ... PA_CL_VPORT_XSCALE_15 = 0x285A4, PA_CL_VPORT_XOFFSET_15 = 0x285A8, PA_CL_VPORT_YSCALE_15 = 0x285AC, PA_CL_VPORT_YOFFSET_15 = 0x285B0, PA_CL_VPORT_ZSCALE_15 = 0x285B4, PA_CL_VPORT_ZOFFSET_15 = 0x285B8, SPI_VS_OUT_ID_0 = 0x28614, SPI_VS_OUT_ID_1 = 0x28618, SPI_VS_OUT_ID_2 = 0x2861C, SPI_VS_OUT_ID_3 = 0x28620, SPI_VS_OUT_ID_4 = 0x28624, SPI_VS_OUT_ID_5 = 0x28628, SPI_VS_OUT_ID_6 = 0x2862C, SPI_VS_OUT_ID_7 = 0x28630, SPI_VS_OUT_ID_8 = 0x28634, SPI_VS_OUT_ID_9 = 0x28638, SPI_PS_INPUT_CNTL_0 = 0x28644, SPI_PS_INPUT_CNTL_1 = 0x28648, SPI_PS_INPUT_CNTL_2 = 0x2864C, SPI_PS_INPUT_CNTL_3 = 0x28650, SPI_PS_INPUT_CNTL_4 = 0x28654, SPI_PS_INPUT_CNTL_5 = 0x28658, SPI_PS_INPUT_CNTL_6 = 0x2865C, SPI_PS_INPUT_CNTL_7 = 0x28660, SPI_PS_INPUT_CNTL_8 = 0x28664, SPI_PS_INPUT_CNTL_9 = 0x28668, SPI_PS_INPUT_CNTL_10 = 0x2866C, SPI_PS_INPUT_CNTL_11 = 0x28670, SPI_PS_INPUT_CNTL_12 = 0x28674, SPI_PS_INPUT_CNTL_13 = 0x28678, SPI_PS_INPUT_CNTL_14 = 0x2867C, SPI_PS_INPUT_CNTL_15 = 0x28680, SPI_PS_INPUT_CNTL_16 = 0x28684, SPI_PS_INPUT_CNTL_17 = 0x28688, SPI_PS_INPUT_CNTL_18 = 0x2868C, SPI_PS_INPUT_CNTL_19 = 0x28690, SPI_PS_INPUT_CNTL_20 = 0x28694, SPI_PS_INPUT_CNTL_21 = 0x28698, SPI_PS_INPUT_CNTL_22 = 0x2869C, SPI_PS_INPUT_CNTL_23 = 0x286A0, SPI_PS_INPUT_CNTL_24 = 0x286A4, SPI_PS_INPUT_CNTL_25 = 0x286A8, SPI_PS_INPUT_CNTL_26 = 0x286AC, SPI_PS_INPUT_CNTL_27 = 0x286B0, SPI_PS_INPUT_CNTL_28 = 0x286B4, SPI_PS_INPUT_CNTL_29 = 0x286B8, SPI_PS_INPUT_CNTL_30 = 0x286BC, SPI_PS_INPUT_CNTL_31 = 0x286C0, SPI_VS_OUT_CONFIG = 0x286C4, SPI_PS_IN_CONTROL_0 = 0x286CC, SPI_PS_IN_CONTROL_1 = 0x286D0, SPI_INTERP_CONTROL_0 = 0x286D4, SPI_INPUT_Z = 0x286D8, SPI_FOG_CNTL = 0x286DC, SPI_FOG_FUNC_SCALE = 0x286E0, SPI_FOG_FUNC_BIAS = 0x286E4, CB_BLEND0_CONTROL = 0x28780, CB_BLEND1_CONTROL = 0x28784, CB_BLEND2_CONTROL = 0x28788, CB_BLEND3_CONTROL = 0x2878C, CB_BLEND4_CONTROL = 0x28790, CB_BLEND5_CONTROL = 0x28794, CB_BLEND6_CONTROL = 0x28798, CB_BLEND7_CONTROL = 0x2879C, CB_SHADER_CONTROL = 0x287A0, VGT_DRAW_INITIATOR = 0x287F0, VGT_DMA_BASE_HI = 0x287E4, VGT_DMA_BASE = 0x287E8, DB_DEPTH_CONTROL = 0x28800, CB_BLEND_CONTROL = 0x28804, CB_COLOR_CONTROL = 0x28808, DB_SHADER_CONTROL = 0x2880C, PA_CL_CLIP_CNTL = 0x28810, PA_SU_SC_MODE_CNTL = 0x28814, PA_CL_VTE_CNTL = 0x28818, PA_CL_VS_OUT_CNTL = 0x2881C, PA_CL_NANINF_CNTL = 0x28820, SQ_PGM_START_PS = 0x28840, SQ_PGM_SIZE_PS = 0x28844, SQ_PGM_RESOURCES_PS = 0x28850, SQ_PGM_EXPORTS_PS = 0x28854, SQ_PGM_START_VS = 0x28858, SQ_PGM_SIZE_VS = 0x2885C, SQ_PGM_RESOURCES_VS = 0x28868, SQ_PGM_START_GS = 0x2886C, SQ_PGM_SIZE_GS = 0x28870, SQ_PGM_RESOURCES_GS = 0x2887C, SQ_PGM_START_ES = 0x28880, SQ_PGM_SIZE_ES = 0x28884, SQ_PGM_RESOURCES_ES = 0x28890, SQ_PGM_START_FS = 0x28894, SQ_PGM_SIZE_FS = 0x28898, SQ_PGM_RESOURCES_FS = 0x288A4, SQ_ESGS_RING_ITEMSIZE = 0x288A8, SQ_GSVS_RING_ITEMSIZE = 0x288AC, SQ_ESTMP_RING_ITEMSIZE = 0x288B0, SQ_GSTMP_RING_ITEMSIZE = 0x288B4, SQ_VSTMP_RING_ITEMSIZE = 0x288B8, SQ_PSTMP_RING_ITEMSIZE = 0x288BC, SQ_FBUF_RING_ITEMSIZE = 0x288C0, SQ_REDUC_RING_ITEMSIZE = 0x288C4, SQ_GS_VERT_ITEMSIZE = 0x288C8, SQ_PGM_CF_OFFSET_PS = 0x288CC, SQ_PGM_CF_OFFSET_VS = 0x288D0, SQ_PGM_CF_OFFSET_GS = 0x288D4, SQ_PGM_CF_OFFSET_ES = 0x288D8, SQ_PGM_CF_OFFSET_FS = 0x288DC, SQ_VTX_SEMANTIC_CLEAR = 0x288E0, SQ_ALU_CONST_CACHE_PS_0 = 0x28940, SQ_ALU_CONST_CACHE_PS_1 = 0x28944, SQ_ALU_CONST_CACHE_PS_2 = 0x28948, SQ_ALU_CONST_CACHE_PS_3 = 0x2894C, SQ_ALU_CONST_CACHE_PS_4 = 0x28950, SQ_ALU_CONST_CACHE_PS_5 = 0x28954, SQ_ALU_CONST_CACHE_PS_6 = 0x28958, SQ_ALU_CONST_CACHE_PS_7 = 0x2895C, SQ_ALU_CONST_CACHE_PS_8 = 0x28960, SQ_ALU_CONST_CACHE_PS_9 = 0x28964, SQ_ALU_CONST_CACHE_PS_10 = 0x28968, SQ_ALU_CONST_CACHE_PS_11 = 0x2896C, SQ_ALU_CONST_CACHE_PS_12 = 0x28970, SQ_ALU_CONST_CACHE_PS_13 = 0x28974, SQ_ALU_CONST_CACHE_PS_14 = 0x28978, SQ_ALU_CONST_CACHE_PS_15 = 0x2897C, SQ_ALU_CONST_CACHE_VS_0 = 0x28980, SQ_ALU_CONST_CACHE_VS_1 = 0x28984, SQ_ALU_CONST_CACHE_VS_2 = 0x28988, SQ_ALU_CONST_CACHE_VS_3 = 0x2898C, SQ_ALU_CONST_CACHE_VS_4 = 0x28990, SQ_ALU_CONST_CACHE_VS_5 = 0x28994, SQ_ALU_CONST_CACHE_VS_6 = 0x28998, SQ_ALU_CONST_CACHE_VS_7 = 0x2899C, SQ_ALU_CONST_CACHE_VS_8 = 0x289A0, SQ_ALU_CONST_CACHE_VS_9 = 0x289A4, SQ_ALU_CONST_CACHE_VS_10 = 0x289A8, SQ_ALU_CONST_CACHE_VS_11 = 0x289AC, SQ_ALU_CONST_CACHE_VS_12 = 0x289B0, SQ_ALU_CONST_CACHE_VS_13 = 0x289B4, SQ_ALU_CONST_CACHE_VS_14 = 0x289B8, SQ_ALU_CONST_CACHE_VS_15 = 0x289BC, SQ_ALU_CONST_CACHE_GS_0 = 0x289C0, SQ_ALU_CONST_CACHE_GS_1 = 0x289C4, SQ_ALU_CONST_CACHE_GS_2 = 0x289C8, SQ_ALU_CONST_CACHE_GS_3 = 0x289CC, SQ_ALU_CONST_CACHE_GS_4 = 0x289D0, SQ_ALU_CONST_CACHE_GS_5 = 0x289D4, SQ_ALU_CONST_CACHE_GS_6 = 0x289D8, SQ_ALU_CONST_CACHE_GS_7 = 0x289DC, SQ_ALU_CONST_CACHE_GS_8 = 0x289E0, SQ_ALU_CONST_CACHE_GS_9 = 0x289E4, SQ_ALU_CONST_CACHE_GS_10 = 0x289E8, SQ_ALU_CONST_CACHE_GS_11 = 0x289EC, SQ_ALU_CONST_CACHE_GS_12 = 0x289F0, SQ_ALU_CONST_CACHE_GS_13 = 0x289F4, SQ_ALU_CONST_CACHE_GS_14 = 0x289F8, SQ_ALU_CONST_CACHE_GS_15 = 0x289FC, PA_SU_POINT_SIZE = 0x28A00, PA_SU_POINT_MINMAX = 0x28A04, PA_SU_LINE_CNTL = 0x28A08, PA_SC_LINE_STIPPLE = 0x28A0C, VGT_OUTPUT_PATH_CNTL = 0x28A10, VGT_HOS_MAX_TESS_LEVEL = 0x28A18, VGT_HOS_MIN_TESS_LEVEL = 0x28A1C, VGT_HOS_REUSE_DEPTH = 0x28A20, VGT_GS_MODE = 0x28A40, PA_SC_MPASS_PS_CNTL = 0x28A48, PA_SC_MODE_CNTL = 0x28A4C, VGT_GS_OUT_PRIM_TYPE = 0x28A6C, VGT_DMA_SIZE = 0x28A74, VGT_DMA_MAX_SIZE = 0x28A78, VGT_DMA_INDEX_TYPE = 0x28A7C, VGT_PRIMITIVEID_EN = 0x28A84, VGT_DMA_NUM_INSTANCES = 0x28A88, VGT_MULTI_PRIM_IB_RESET_EN = 0x28A94, VGT_INSTANCE_STEP_RATE_0 = 0x28AA0, VGT_INSTANCE_STEP_RATE_1 = 0x28AA4, VGT_STRMOUT_EN = 0x28AB0, VGT_REUSE_OFF = 0x28AB4, VGT_VTX_CNT_EN = 0x28AB8, VGT_STRMOUT_BUFFER_SIZE_0 = 0x28AD0, VGT_STRMOUT_VTX_STRIDE_0 = 0x28AD4, VGT_STRMOUT_BUFFER_BASE_0 = 0x28AD8, VGT_STRMOUT_BUFFER_OFFSET_0 = 0x28ADC, VGT_STRMOUT_BUFFER_SIZE_1 = 0x28AE0, VGT_STRMOUT_VTX_STRIDE_1 = 0x28AE4, VGT_STRMOUT_BUFFER_BASE_1 = 0x28AE8, VGT_STRMOUT_BUFFER_OFFSET_1 = 0x28AEC, VGT_STRMOUT_BUFFER_SIZE_2 = 0x28AF0, VGT_STRMOUT_VTX_STRIDE_2 = 0x28AF4, VGT_STRMOUT_BUFFER_BASE_2 = 0x28AF8, VGT_STRMOUT_BUFFER_OFFSET_2 = 0x28AFC, VGT_STRMOUT_BUFFER_SIZE_3 = 0x28B00, VGT_STRMOUT_VTX_STRIDE_3 = 0x28B04, VGT_STRMOUT_BUFFER_BASE_3 = 0x28B08, VGT_STRMOUT_BUFFER_OFFSET_3 = 0x28B0C, VGT_STRMOUT_BASE_OFFSET_0 = 0x28B10, VGT_STRMOUT_BASE_OFFSET_1 = 0x28B14, VGT_STRMOUT_BASE_OFFSET_2 = 0x28B18, VGT_STRMOUT_BASE_OFFSET_3 = 0x28B1C, VGT_STRMOUT_BUFFER_EN = 0x28B20, VGT_STRMOUT_DRAW_OPAQUE_OFFSET = 0x28B28, VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE = 0x28B2C, VGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE = 0x28B30, PA_SC_LINE_CNTL = 0x28C00, PA_SU_VTX_CNTL = 0x28C08, PA_CL_GB_VERT_CLIP_ADJ = 0x28C0C, PA_CL_GB_VERT_DISC_ADJ = 0x28C10, PA_CL_GB_HORZ_CLIP_ADJ = 0x28C14, PA_CL_GB_HORZ_DISC_ADJ = 0x28C18, CB_CLRCMP_CONTROL = 0x28C30, CB_CLRCMP_SRC = 0x28C34, CB_CLRCMP_DST = 0x28C38, CB_CLRCMP_MSK = 0x28C3C, PA_SC_AA_MASK = 0x28C48, VGT_VERTEX_REUSE_BLOCK_CNTL = 0x28C58, VGT_OUT_DEALLOC_CNTL = 0x28C5C, DB_RENDER_CONTROL = 0x28D0C, DB_RENDER_OVERRIDE = 0x28D10, DB_HTILE_SURFACE = 0x28D24, DB_SRESULTS_COMPARE_STATE0 = 0x28D28, DB_SRESULTS_COMPARE_STATE1 = 0x28D2C, DB_PRELOAD_CONTROL = 0x28D30, DB_PREFETCH_LIMIT = 0x28D34, DB_ALPHA_TO_MASK = 0x28D44, PA_SU_POLY_OFFSET_DB_FMT_CNTL = 0x28DF8, PA_SU_POLY_OFFSET_CLAMP = 0x28DFC, PA_SU_POLY_OFFSET_FRONT_SCALE = 0x28E00, PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x28E04, PA_SU_POLY_OFFSET_BACK_SCALE = 0x28E08, PA_SU_POLY_OFFSET_BACK_OFFSET = 0x28E0C, PA_CL_POINT_X_RAD = 0x28E10, PA_CL_POINT_Y_RAD = 0x28E14, PA_CL_POINT_POINT_SIZE = 0x28E18, PA_CL_POINT_POINT_CULL_RAD = 0x28E1C, PA_CL_UCP_0_X = 0x28E20, PA_CL_UCP_0_Y = 0x28E24, PA_CL_UCP_0_Z = 0x28E28, PA_CL_UCP_0_W = 0x28E2C, // ... PA_CL_UCP_5_X = 0x28E70, PA_CL_UCP_5_Y = 0x28E74, PA_CL_UCP_5_Z = 0x28E78, PA_CL_UCP_5_W = 0x28E7C, ContextRegisterEnd = 0x29000, // Alu Const Registers AluConstRegisterBase = 0x30000, SQ_ALU_CONSTANT0_0 = 0x30000, SQ_ALU_CONSTANT1_0 = 0x30004, SQ_ALU_CONSTANT2_0 = 0x30008, SQ_ALU_CONSTANT3_0 = 0x3000C, // ... SQ_ALU_CONSTANT0_256 = 0x31000, SQ_ALU_CONSTANT1_256 = 0x31004, SQ_ALU_CONSTANT2_256 = 0x31008, SQ_ALU_CONSTANT3_256 = 0x3100C, AluConstRegisterEnd = 0x32000, // Resource Registers ResourceRegisterBase = 0x38000, SQ_RESOURCE_WORD0_0 = 0x38000, SQ_RESOURCE_WORD1_0 = 0x38004, SQ_RESOURCE_WORD2_0 = 0x38008, SQ_RESOURCE_WORD3_0 = 0x3800C, SQ_RESOURCE_WORD4_0 = 0x38010, SQ_RESOURCE_WORD5_0 = 0x38014, SQ_RESOURCE_WORD6_0 = 0x38018, ResourceRegisterEnd = 0x3B678, // Sampler Registers SamplerRegisterBase = 0x3C000, SQ_TEX_SAMPLER_WORD0_0 = 0x3C000, SQ_TEX_SAMPLER_WORD1_0 = 0x3C004, SQ_TEX_SAMPLER_WORD2_0 = 0x3C008, SamplerRegisterEnd = 0x3C288, // Control Registers ControlRegisterBase = 0x3CFF0, SQ_VTX_BASE_VTX_LOC = 0x3CFF0, SQ_VTX_START_INST_LOC = 0x3CFF4, ControlRegisterEnd = 0x3CFF8, // TODO: Find real ControlRegisterEnd // Loop Const Registers LoopConstRegisterBase = 0x3E200, SQ_LOOP_CONST_PS_0 = 0x3E200, // ... SQ_LOOP_CONST_PS_31 = 0x3E27C, SQ_LOOP_CONST_VS_0 = 0x3E280, // ... SQ_LOOP_CONST_VS_31 = 0x3E2FC, SQ_LOOP_CONST_GS_0 = 0x3E300, // ... SQ_LOOP_CONST_GS_31 = 0x3E37C, LoopConstRegisterEnd = 0x3E380, // Bool Const Registers BoolConstRegisterBase = 0x3E380, BoolConstRegisterEnd = 0x3E384, // TODO: Find real BoolConstRegisterEnd }; } using Register = Register_::Value; } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_cb.h ================================================ #pragma once #include "latte_enum_cb.h" #include "latte_enum_common.h" #include <cstdint> #include <common/bitfield.h> namespace latte { // Blend function used for all render targets BITFIELD_BEG(CB_BLENDN_CONTROL, uint32_t) BITFIELD_ENTRY(0, 5, CB_BLEND_FUNC, COLOR_SRCBLEND) BITFIELD_ENTRY(5, 3, CB_COMB_FUNC, COLOR_COMB_FCN) BITFIELD_ENTRY(8, 5, CB_BLEND_FUNC, COLOR_DESTBLEND) BITFIELD_ENTRY(13, 1, bool, OPACITY_WEIGHT) BITFIELD_ENTRY(16, 5, CB_BLEND_FUNC, ALPHA_SRCBLEND) BITFIELD_ENTRY(21, 3, CB_COMB_FUNC, ALPHA_COMB_FCN) BITFIELD_ENTRY(24, 5, CB_BLEND_FUNC, ALPHA_DESTBLEND) BITFIELD_ENTRY(29, 1, bool, SEPARATE_ALPHA_BLEND) BITFIELD_END BITFIELD_BEG(CB_BLEND_RED, uint32_t) BITFIELD_ENTRY(0, 32, float, BLEND_RED) BITFIELD_END BITFIELD_BEG(CB_BLEND_GREEN, uint32_t) BITFIELD_ENTRY(0, 32, float, BLEND_GREEN) BITFIELD_END BITFIELD_BEG(CB_BLEND_BLUE, uint32_t) BITFIELD_ENTRY(0, 32, float, BLEND_BLUE) BITFIELD_END BITFIELD_BEG(CB_BLEND_ALPHA, uint32_t) BITFIELD_ENTRY(0, 32, float, BLEND_ALPHA) BITFIELD_END // This register controls color keying, which masks individual pixel writes based on comparing the // source(pre - ROP) color and / or the dest(frame buffer) color to comparison values, after masking both by CLRCMP_MSK BITFIELD_BEG(CB_CLRCMP_CONTROL, uint32_t) BITFIELD_ENTRY(0, 3, CB_CLRCMP_DRAW, CLRCMP_FCN_SRC) BITFIELD_ENTRY(8, 3, CB_CLRCMP_DRAW, CLRCMP_FCN_DST) BITFIELD_ENTRY(24, 2, CB_CLRCMP_SEL, CLRCMP_FCN_SEL) BITFIELD_END BITFIELD_BEG(CB_CLRCMP_SRC, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, CLRCMP_SRC) BITFIELD_END BITFIELD_BEG(CB_CLRCMP_DST, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, CLRCMP_DST) BITFIELD_END BITFIELD_BEG(CB_CLRCMP_MSK, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, CLRCMP_MSK) BITFIELD_END BITFIELD_BEG(CB_COLORN_BASE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B) BITFIELD_END BITFIELD_BEG(CB_COLORN_TILE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B) BITFIELD_END BITFIELD_BEG(CB_COLORN_FRAG, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B) BITFIELD_END BITFIELD_BEG(CB_COLORN_SIZE, uint32_t) BITFIELD_ENTRY(0, 10, uint32_t, PITCH_TILE_MAX) BITFIELD_ENTRY(10, 20, uint32_t, SLICE_TILE_MAX) BITFIELD_END BITFIELD_BEG(CB_COLORN_INFO, uint32_t) BITFIELD_ENTRY(0, 2, CB_ENDIAN, ENDIAN) BITFIELD_ENTRY(2, 6, CB_FORMAT, FORMAT) BITFIELD_ENTRY(8, 4, BUFFER_ARRAY_MODE, ARRAY_MODE) BITFIELD_ENTRY(12, 3, CB_NUMBER_TYPE, NUMBER_TYPE) BITFIELD_ENTRY(15, 1, BUFFER_READ_SIZE, READ_SIZE) BITFIELD_ENTRY(16, 2, CB_COMP_SWAP, COMP_SWAP) BITFIELD_ENTRY(18, 2, CB_TILE_MODE, TILE_MODE) BITFIELD_ENTRY(20, 1, bool, BLEND_CLAMP) BITFIELD_ENTRY(21, 1, bool, CLEAR_COLOR) BITFIELD_ENTRY(22, 1, bool, BLEND_BYPASS) BITFIELD_ENTRY(23, 1, bool, BLEND_FLOAT32) BITFIELD_ENTRY(24, 1, bool, SIMPLE_FLOAT) BITFIELD_ENTRY(25, 1, CB_ROUND_MODE, ROUND_MODE) BITFIELD_ENTRY(26, 1, bool, TILE_COMPACT) BITFIELD_ENTRY(27, 1, CB_SOURCE_FORMAT, SOURCE_FORMAT) BITFIELD_END // Selects slice index range for render target BITFIELD_BEG(CB_COLORN_VIEW, uint32_t) BITFIELD_ENTRY(0, 11, uint32_t, SLICE_START) BITFIELD_ENTRY(13, 11, uint32_t, SLICE_MAX) BITFIELD_END BITFIELD_BEG(CB_COLORN_MASK, uint32_t) BITFIELD_ENTRY(0, 12, uint32_t, CMASK_BLOCK_MAX) BITFIELD_ENTRY(12, 20, uint32_t, FMASK_TILE_MAX) BITFIELD_END BITFIELD_BEG(CB_COLOR_CONTROL, uint32_t) BITFIELD_ENTRY(0, 1, bool, FOG_ENABLE) BITFIELD_ENTRY(1, 1, bool, MULTIWRITE_ENABLE) BITFIELD_ENTRY(2, 1, bool, DITHER_ENABLE) BITFIELD_ENTRY(3, 1, bool, DEGAMMA_ENABLE) BITFIELD_ENTRY(4, 3, CB_SPECIAL_OP, SPECIAL_OP) BITFIELD_ENTRY(7, 1, bool, PER_MRT_BLEND) BITFIELD_ENTRY(8, 8, uint8_t, TARGET_BLEND_ENABLE) BITFIELD_ENTRY(16, 8, uint8_t, ROP3) BITFIELD_END BITFIELD_BEG(CB_SHADER_CONTROL, uint32_t) BITFIELD_ENTRY(0, 1, bool, RT0_ENABLE) BITFIELD_ENTRY(1, 1, bool, RT1_ENABLE) BITFIELD_ENTRY(2, 1, bool, RT2_ENABLE) BITFIELD_ENTRY(3, 1, bool, RT3_ENABLE) BITFIELD_ENTRY(4, 1, bool, RT4_ENABLE) BITFIELD_ENTRY(5, 1, bool, RT5_ENABLE) BITFIELD_ENTRY(6, 1, bool, RT6_ENABLE) BITFIELD_ENTRY(7, 1, bool, RT7_ENABLE) BITFIELD_END // Contains color component mask fields for the colors output by the shader BITFIELD_BEG(CB_SHADER_MASK, uint32_t) BITFIELD_ENTRY(0, 4, uint32_t, OUTPUT0_ENABLE) BITFIELD_ENTRY(4, 4, uint32_t, OUTPUT1_ENABLE) BITFIELD_ENTRY(8, 4, uint32_t, OUTPUT2_ENABLE) BITFIELD_ENTRY(12, 4, uint32_t, OUTPUT3_ENABLE) BITFIELD_ENTRY(16, 4, uint32_t, OUTPUT4_ENABLE) BITFIELD_ENTRY(20, 4, uint32_t, OUTPUT5_ENABLE) BITFIELD_ENTRY(24, 4, uint32_t, OUTPUT6_ENABLE) BITFIELD_ENTRY(28, 4, uint32_t, OUTPUT7_ENABLE) BITFIELD_END // Contains color component mask fields for writing the render targets. Red, green, blue, and alpha // are components 0, 1, 2, and 3 in the pixel shader and are enabled by bits 0, 1, 2, and 3 in each field BITFIELD_BEG(CB_TARGET_MASK, uint32_t) BITFIELD_ENTRY(0, 4, uint8_t, TARGET0_ENABLE) BITFIELD_ENTRY(4, 4, uint8_t, TARGET1_ENABLE) BITFIELD_ENTRY(8, 4, uint8_t, TARGET2_ENABLE) BITFIELD_ENTRY(12, 4, uint8_t, TARGET3_ENABLE) BITFIELD_ENTRY(16, 4, uint8_t, TARGET4_ENABLE) BITFIELD_ENTRY(20, 4, uint8_t, TARGET5_ENABLE) BITFIELD_ENTRY(24, 4, uint8_t, TARGET6_ENABLE) BITFIELD_ENTRY(28, 4, uint8_t, TARGET7_ENABLE) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_cp.h ================================================ #pragma once #include "latte_enum_cb.h" #include <common/bitfield.h> #include <cstdint> namespace latte { // Interrupt Control BITFIELD_BEG(CP_INT_CNTL, uint32_t) BITFIELD_ENTRY(17, 1, bool, UNK17_INT_ENABLE) BITFIELD_ENTRY(19, 1, bool, CNTX_BUSY_INT_ENABLE) BITFIELD_ENTRY(20, 1, bool, CNTX_EMPTY_INT_ENABLE) BITFIELD_ENTRY(24, 1, bool, BAD_OPCODE_EXCEPTION) BITFIELD_ENTRY(25, 1, bool, SCRATCH_INT_ENABLE) BITFIELD_ENTRY(26, 1, bool, TIME_STAMP_INT_ENABLE) BITFIELD_ENTRY(27, 1, bool, RESERVED_BITS_EXCEPTION) BITFIELD_ENTRY(29, 1, bool, IB2_INT_ENABLE) BITFIELD_ENTRY(30, 1, bool, IB1_INT_ENABLE) BITFIELD_ENTRY(31, 1, bool, RB_INT_ENABLE) BITFIELD_END // Interrupt Status BITFIELD_BEG(CP_INT_STATUS, uint32_t) BITFIELD_ENTRY(25, 1, bool, SCRATCH_INT_STAT) BITFIELD_ENTRY(26, 1, bool, TIME_STAMP_INT_STAT) BITFIELD_ENTRY(29, 1, bool, IB2_INT_STAT) BITFIELD_ENTRY(30, 1, bool, IB1_INT_STAT) BITFIELD_ENTRY(31, 1, bool, RB_INT_STAT) BITFIELD_END // Ring Buffer Control BITFIELD_BEG(CP_RB_CNTL, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, RB_BUFSZ) BITFIELD_ENTRY(8, 8, uint8_t, RB_BLKSZ) BITFIELD_ENTRY(27, 1, bool, RB_NO_UPDATE) BITFIELD_ENTRY(16, 2, CB_ENDIAN, BUF_SWAP) BITFIELD_ENTRY(31, 1, bool, RB_RPTR_WR_ENA) BITFIELD_END // Coherence Control BITFIELD_BEG(CP_COHER_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, DEST_BASE_0_ENA) BITFIELD_ENTRY(1, 1, bool, DEST_BASE_1_ENA) BITFIELD_ENTRY(2, 1, bool, SO0_DEST_BASE_ENA) BITFIELD_ENTRY(3, 1, bool, SO1_DEST_BASE_ENA) BITFIELD_ENTRY(4, 1, bool, SO2_DEST_BASE_ENA) BITFIELD_ENTRY(5, 1, bool, SO3_DEST_BASE_ENA) BITFIELD_ENTRY(6, 1, bool, CB0_DEST_BASE_ENA) BITFIELD_ENTRY(7, 1, bool, CB1_DEST_BASE_ENA) BITFIELD_ENTRY(8, 1, bool, CB2_DEST_BASE_ENA) BITFIELD_ENTRY(9, 1, bool, CB3_DEST_BASE_ENA) BITFIELD_ENTRY(10, 1, bool, CB4_DEST_BASE_ENA) BITFIELD_ENTRY(11, 1, bool, CB5_DEST_BASE_ENA) BITFIELD_ENTRY(12, 1, bool, CB6_DEST_BASE_ENA) BITFIELD_ENTRY(13, 1, bool, CB7_DEST_BASE_ENA) BITFIELD_ENTRY(14, 1, bool, DB_DEST_BASE_ENA) BITFIELD_ENTRY(20, 1, bool, FULL_CACHE_ENA) BITFIELD_ENTRY(23, 1, bool, TC_ACTION_ENA) BITFIELD_ENTRY(24, 1, bool, VC_ACTION_ENA) BITFIELD_ENTRY(25, 1, bool, CB_ACTION_ENA) BITFIELD_ENTRY(26, 1, bool, DB_ACTION_ENA) BITFIELD_ENTRY(27, 1, bool, SH_ACTION_ENA) BITFIELD_ENTRY(28, 1, bool, SX_ACTION_ENA) BITFIELD_ENTRY(31, 1, bool, ENGINE_ME) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_db.h ================================================ #pragma once #include "latte_enum_common.h" #include "latte_enum_db.h" #include <common/bitfield.h> #include <cstdint> namespace latte { BITFIELD_BEG(DB_ALPHA_TO_MASK, uint32_t) BITFIELD_ENTRY(0, 1, bool, ALPHA_TO_MASK_ENABLE) BITFIELD_ENTRY(8, 2, uint32_t, ALPHA_TO_MASK_OFFSET0) BITFIELD_ENTRY(10, 2, uint32_t, ALPHA_TO_MASK_OFFSET1) BITFIELD_ENTRY(12, 2, uint32_t, ALPHA_TO_MASK_OFFSET2) BITFIELD_ENTRY(14, 2, uint32_t, ALPHA_TO_MASK_OFFSET3) BITFIELD_ENTRY(16, 1, bool, OFFSET_ROUND) BITFIELD_END BITFIELD_BEG(DB_DEPTH_BASE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B) BITFIELD_END BITFIELD_BEG(DB_DEPTH_HTILE_DATA_BASE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B) BITFIELD_END BITFIELD_BEG(DB_DEPTH_INFO, uint32_t) BITFIELD_ENTRY(0, 3, DB_FORMAT, FORMAT) BITFIELD_ENTRY(3, 1, BUFFER_READ_SIZE, READ_SIZE) BITFIELD_ENTRY(15, 4, BUFFER_ARRAY_MODE, ARRAY_MODE) BITFIELD_ENTRY(25, 1, bool, TILE_SURFACE_ENABLE) BITFIELD_ENTRY(26, 1, bool, TILE_COMPACT) BITFIELD_ENTRY(31, 1, bool, ZRANGE_PRECISION) BITFIELD_END BITFIELD_BEG(DB_DEPTH_SIZE, uint32_t) BITFIELD_ENTRY(0, 10, uint32_t, PITCH_TILE_MAX) BITFIELD_ENTRY(10, 20, uint32_t, SLICE_TILE_MAX) BITFIELD_END BITFIELD_BEG(DB_DEPTH_VIEW, uint32_t) BITFIELD_ENTRY(0, 11, uint32_t, SLICE_START) BITFIELD_ENTRY(13, 11, uint32_t, SLICE_MAX) BITFIELD_END BITFIELD_BEG(DB_HTILE_SURFACE, uint32_t) BITFIELD_ENTRY(0, 1, bool, HTILE_WIDTH) BITFIELD_ENTRY(1, 1, bool, HTILE_HEIGHT) BITFIELD_ENTRY(2, 1, bool, LINEAR) BITFIELD_ENTRY(3, 1, bool, FULL_CACHE) BITFIELD_ENTRY(4, 1, bool, HTILE_USES_PRELOAD_WIN) BITFIELD_ENTRY(5, 1, bool, PRELOAD) BITFIELD_ENTRY(6, 6, uint32_t, PREFETCH_WIDTH) BITFIELD_ENTRY(12, 6, uint32_t, PREFETCH_HEIGHT) BITFIELD_END BITFIELD_BEG(DB_DEPTH_CLEAR, uint32_t) BITFIELD_ENTRY(0, 32, float, DEPTH_CLEAR) BITFIELD_END // This register controls depth and stencil tests. BITFIELD_BEG(DB_DEPTH_CONTROL, uint32_t) BITFIELD_ENTRY(0, 1, bool, STENCIL_ENABLE) BITFIELD_ENTRY(1, 1, bool, Z_ENABLE) BITFIELD_ENTRY(2, 1, bool, Z_WRITE_ENABLE) BITFIELD_ENTRY(4, 3, REF_FUNC, ZFUNC) BITFIELD_ENTRY(7, 1, bool, BACKFACE_ENABLE) BITFIELD_ENTRY(8, 3, REF_FUNC, STENCILFUNC) BITFIELD_ENTRY(11, 3, DB_STENCIL_FUNC, STENCILFAIL) BITFIELD_ENTRY(14, 3, DB_STENCIL_FUNC, STENCILZPASS) BITFIELD_ENTRY(17, 3, DB_STENCIL_FUNC, STENCILZFAIL) BITFIELD_ENTRY(20, 3, REF_FUNC, STENCILFUNC_BF) BITFIELD_ENTRY(23, 3, DB_STENCIL_FUNC, STENCILFAIL_BF) BITFIELD_ENTRY(26, 3, DB_STENCIL_FUNC, STENCILZPASS_BF) BITFIELD_ENTRY(29, 3, DB_STENCIL_FUNC, STENCILZFAIL_BF) BITFIELD_END BITFIELD_BEG(DB_RENDER_CONTROL, uint32_t) BITFIELD_ENTRY(0, 1, bool, DEPTH_CLEAR_ENABLE) BITFIELD_ENTRY(1, 1, bool, STENCIL_CLEAR_ENABLE) BITFIELD_ENTRY(2, 1, bool, DEPTH_COPY) BITFIELD_ENTRY(3, 1, bool, STENCIL_COPY) BITFIELD_ENTRY(4, 1, bool, RESUMMARIZE_ENABLE) BITFIELD_ENTRY(5, 1, bool, STENCIL_COMPRESS_DISABLE) BITFIELD_ENTRY(6, 1, bool, DEPTH_COMPRESS_DISABLE) BITFIELD_ENTRY(7, 1, bool, COPY_CENTROID) BITFIELD_ENTRY(8, 3, uint8_t, COPY_SAMPLE) BITFIELD_ENTRY(11, 1, bool, ZPASS_INCREMENT_DISABLE) BITFIELD_ENTRY(12, 1, bool, COLOR_DISABLE) BITFIELD_ENTRY(13, 2, DB_Z_EXPORT, CONSERVATIVE_Z_EXPORT) BITFIELD_ENTRY(15, 1, bool, PERFECT_ZPASS_COUNTS) BITFIELD_END BITFIELD_BEG(DB_RENDER_OVERRIDE, uint32_t) BITFIELD_ENTRY(0, 2, DB_FORCE, FORCE_HIZ_ENABLE) BITFIELD_ENTRY(2, 2, DB_FORCE, FORCE_HIS_ENABLE0) BITFIELD_ENTRY(4, 2, DB_FORCE, FORCE_HIS_ENABLE1) BITFIELD_ENTRY(6, 1, bool, FORCE_SHADER_Z_ORDER) BITFIELD_ENTRY(7, 1, bool, FAST_Z_DISABLE) BITFIELD_ENTRY(8, 1, bool, FAST_STENCIL_DISABLE) BITFIELD_ENTRY(9, 1, bool, NOOP_CULL_DISABLE) BITFIELD_ENTRY(10, 1, bool, FORCE_COLOR_KILL) BITFIELD_ENTRY(11, 1, bool, FORCE_Z_READ) BITFIELD_ENTRY(12, 1, bool, FORCE_STENCIL_READ) BITFIELD_ENTRY(13, 1, DB_FORCE, FORCE_FULL_Z_RANGE) BITFIELD_ENTRY(15, 1, bool, FORCE_QC_SMASK_CONFLICT) BITFIELD_ENTRY(16, 1, bool, DISABLE_VIEWPORT_CLAMP) BITFIELD_ENTRY(17, 1, bool, IGNORE_SC_ZRANGE) BITFIELD_END BITFIELD_BEG(DB_STENCILREFMASK, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, STENCILREF) BITFIELD_ENTRY(8, 8, uint8_t, STENCILMASK) BITFIELD_ENTRY(16, 8, uint8_t, STENCILWRITEMASK) BITFIELD_END BITFIELD_BEG(DB_STENCILREFMASK_BF, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, STENCILREF_BF) BITFIELD_ENTRY(8, 8, uint8_t, STENCILMASK_BF) BITFIELD_ENTRY(16, 8, uint8_t, STENCILWRITEMASK_BF) BITFIELD_END BITFIELD_BEG(DB_PREFETCH_LIMIT, uint32_t) BITFIELD_ENTRY(0, 10, uint32_t, DEPTH_HEIGHT_TILE_MAX) BITFIELD_END BITFIELD_BEG(DB_PRELOAD_CONTROL, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, START_X) BITFIELD_ENTRY(8, 8, uint8_t, START_Y) BITFIELD_ENTRY(16, 8, uint8_t, MAX_X) BITFIELD_ENTRY(24, 8, uint8_t, MAX_Y) BITFIELD_END BITFIELD_BEG(DB_SHADER_CONTROL, uint32_t) BITFIELD_ENTRY(0, 1, bool, Z_EXPORT_ENABLE) BITFIELD_ENTRY(1, 1, bool, STENCIL_REF_EXPORT_ENABLE) BITFIELD_ENTRY(4, 2, DB_Z_ORDER, Z_ORDER) BITFIELD_ENTRY(6, 1, bool, KILL_ENABLE) BITFIELD_ENTRY(7, 1, bool, COVERAGE_TO_MASK_ENABLE) BITFIELD_ENTRY(8, 1, bool, MASK_EXPORT_ENABLE) BITFIELD_ENTRY(9, 1, bool, DUAL_EXPORT_ENABLE) BITFIELD_ENTRY(10, 1, bool, EXEC_ON_HIER_FAIL) BITFIELD_ENTRY(11, 1, bool, EXEC_ON_NOOP) BITFIELD_ENTRY(12, 1, bool, ALPHA_TO_MASK_DISABLE) BITFIELD_END BITFIELD_BEG(DB_SRESULTS_COMPARE_STATE0, uint32_t) BITFIELD_ENTRY(0, 3, REF_FUNC, COMPAREFUNC0) BITFIELD_ENTRY(4, 8, uint8_t, COMPAREVALUE0) BITFIELD_ENTRY(12, 8, uint8_t, COMPAREMASK0) BITFIELD_ENTRY(24, 1, bool, ENABLE0) BITFIELD_END BITFIELD_BEG(DB_SRESULTS_COMPARE_STATE1, uint32_t) BITFIELD_ENTRY(0, 3, REF_FUNC, COMPAREFUNC1) BITFIELD_ENTRY(4, 8, uint8_t, COMPAREVALUE1) BITFIELD_ENTRY(12, 8, uint8_t, COMPAREMASK1) BITFIELD_ENTRY(24, 1, bool, ENABLE1) BITFIELD_END BITFIELD_BEG(DB_STENCIL_CLEAR, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, CLEAR) BITFIELD_ENTRY(16, 8, uint32_t, MIN) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_pa.h ================================================ #pragma once #include "latte_enum_spi.h" #include "latte_enum_pa.h" #include <common/bitfield.h> #include <cstdint> namespace latte { // Clipper Control Bits BITFIELD_BEG(PA_CL_CLIP_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, UCP_ENA_0) BITFIELD_ENTRY(1, 1, bool, UCP_ENA_1) BITFIELD_ENTRY(2, 1, bool, UCP_ENA_2) BITFIELD_ENTRY(3, 1, bool, UCP_ENA_3) BITFIELD_ENTRY(4, 1, bool, UCP_ENA_4) BITFIELD_ENTRY(5, 1, bool, UCP_ENA_5) BITFIELD_ENTRY(13, 1, bool, PS_UCP_Y_SCALE_NEG) BITFIELD_ENTRY(14, 2, PA_PS_UCP_MODE, PS_UCP_MODE) BITFIELD_ENTRY(16, 1, bool, CLIP_DISABLE) BITFIELD_ENTRY(17, 1, bool, UCP_CULL_ONLY_ENA) BITFIELD_ENTRY(18, 1, bool, BOUNDARY_EDGE_FLAG_ENA) BITFIELD_ENTRY(19, 1, bool, DX_CLIP_SPACE_DEF) BITFIELD_ENTRY(20, 1, bool, DIS_CLIP_ERR_DETECT) BITFIELD_ENTRY(21, 1, bool, VTX_KILL_OR) BITFIELD_ENTRY(22, 1, bool, RASTERISER_DISABLE) BITFIELD_ENTRY(24, 1, bool, DX_LINEAR_ATTR_CLIP_ENA) BITFIELD_ENTRY(25, 1, bool, VTE_VPORT_PROVOKE_DISABLE) BITFIELD_ENTRY(26, 1, bool, ZCLIP_NEAR_DISABLE) BITFIELD_ENTRY(27, 1, bool, ZCLIP_FAR_DISABLE) BITFIELD_END // Horizontal Guard Band Clip Adjust Register BITFIELD_BEG(PA_CL_GB_HORZ_CLIP_ADJ, uint32_t) BITFIELD_ENTRY(0, 32, float, DATA_REGISTER) BITFIELD_END // Horizontal Guard Band Discard Adjust Register BITFIELD_BEG(PA_CL_GB_HORZ_DISC_ADJ, uint32_t) BITFIELD_ENTRY(0, 32, float, DATA_REGISTER) BITFIELD_END // Vertical Guard Band Clip Adjust Register BITFIELD_BEG(PA_CL_GB_VERT_CLIP_ADJ, uint32_t) BITFIELD_ENTRY(0, 32, float, DATA_REGISTER) BITFIELD_END // Vertical Guard Band Discard Adjust Register BITFIELD_BEG(PA_CL_GB_VERT_DISC_ADJ, uint32_t) BITFIELD_ENTRY(0, 32, float, DATA_REGISTER) BITFIELD_END BITFIELD_BEG(PA_CL_NANINF_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, VTE_XY_INF_DISCARD) BITFIELD_ENTRY(1, 1, bool, VTE_Z_INF_DISCARD) BITFIELD_ENTRY(2, 1, bool, VTE_W_INF_DISCARD) BITFIELD_ENTRY(3, 1, bool, VTE_0XNANINF_IS_0) BITFIELD_ENTRY(4, 1, bool, VTE_XY_NAN_RETAIN) BITFIELD_ENTRY(5, 1, bool, VTE_Z_NAN_RETAIN) BITFIELD_ENTRY(6, 1, bool, VTE_W_NAN_RETAIN) BITFIELD_ENTRY(7, 1, bool, VTE_W_RECIP_NAN_IS_0) BITFIELD_ENTRY(8, 1, bool, VS_XY_NAN_TO_INF) BITFIELD_ENTRY(9, 1, bool, VS_XY_INF_RETAIN) BITFIELD_ENTRY(10, 1, bool, VS_Z_NAN_TO_INF) BITFIELD_ENTRY(11, 1, bool, VS_Z_INF_RETAIN) BITFIELD_ENTRY(12, 1, bool, VS_W_NAN_TO_INF) BITFIELD_ENTRY(13, 1, bool, VS_W_INF_RETAIN) BITFIELD_ENTRY(14, 1, bool, VS_CLIP_DIST_INF_DISCARD) BITFIELD_ENTRY(20, 1, bool, VTE_NO_OUTPUT_NEG_0) BITFIELD_END // Point Sprite X Radius Expansion BITFIELD_BEG(PA_CL_POINT_X_RAD, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER) BITFIELD_END // Point Sprite Y Radius Expansion BITFIELD_BEG(PA_CL_POINT_Y_RAD, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER) BITFIELD_END // Point Sprite Constant Size BITFIELD_BEG(PA_CL_POINT_SIZE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER) BITFIELD_END // Point Sprite Culling Radius Expansion BITFIELD_BEG(PA_CL_POINT_CULL_RAD, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER) BITFIELD_END // Viewport Transform X Scale Factor BITFIELD_BEG(PA_CL_VPORT_XSCALE_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_XSCALE) BITFIELD_END // Viewport Transform X Offset BITFIELD_BEG(PA_CL_VPORT_XOFFSET_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_XOFFSET) BITFIELD_END // Viewport Transform Y Scale Factor BITFIELD_BEG(PA_CL_VPORT_YSCALE_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_YSCALE) BITFIELD_END // Viewport Transform Y Offset BITFIELD_BEG(PA_CL_VPORT_YOFFSET_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_YOFFSET) BITFIELD_END // Viewport Transform Z Scale Factor BITFIELD_BEG(PA_CL_VPORT_ZSCALE_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_ZSCALE) BITFIELD_END // Viewport Transform Z Offset BITFIELD_BEG(PA_CL_VPORT_ZOFFSET_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_ZOFFSET) BITFIELD_END // Vertex Shader Output Control BITFIELD_BEG(PA_CL_VS_OUT_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, CLIP_DIST_ENA_0) BITFIELD_ENTRY(1, 1, bool, CLIP_DIST_ENA_1) BITFIELD_ENTRY(2, 1, bool, CLIP_DIST_ENA_2) BITFIELD_ENTRY(3, 1, bool, CLIP_DIST_ENA_3) BITFIELD_ENTRY(4, 1, bool, CLIP_DIST_ENA_4) BITFIELD_ENTRY(5, 1, bool, CLIP_DIST_ENA_5) BITFIELD_ENTRY(6, 1, bool, CLIP_DIST_ENA_6) BITFIELD_ENTRY(7, 1, bool, CLIP_DIST_ENA_7) BITFIELD_ENTRY(8, 1, bool, CULL_DIST_ENA_0) BITFIELD_ENTRY(9, 1, bool, CULL_DIST_ENA_1) BITFIELD_ENTRY(10, 1, bool, CULL_DIST_ENA_2) BITFIELD_ENTRY(11, 1, bool, CULL_DIST_ENA_3) BITFIELD_ENTRY(12, 1, bool, CULL_DIST_ENA_4) BITFIELD_ENTRY(13, 1, bool, CULL_DIST_ENA_5) BITFIELD_ENTRY(14, 1, bool, CULL_DIST_ENA_6) BITFIELD_ENTRY(15, 1, bool, CULL_DIST_ENA_7) BITFIELD_ENTRY(16, 1, bool, USE_VTX_POINT_SIZE) BITFIELD_ENTRY(17, 1, bool, USE_VTX_EDGE_FLAG) BITFIELD_ENTRY(18, 1, bool, USE_VTX_RENDER_TARGET_INDX) BITFIELD_ENTRY(19, 1, bool, USE_VTX_VIEWPORT_INDX) BITFIELD_ENTRY(20, 1, bool, USE_VTX_KILL_FLAG) BITFIELD_ENTRY(21, 1, bool, VS_OUT_MISC_VEC_ENA) BITFIELD_ENTRY(22, 1, bool, VS_OUT_CCDIST0_VEC_ENA) BITFIELD_ENTRY(23, 1, bool, VS_OUT_CCDIST1_VEC_ENA) BITFIELD_ENTRY(24, 1, bool, VS_OUT_MISC_SIDE_BUS_ENA) BITFIELD_ENTRY(25, 1, bool, USE_VTX_GS_CUT_FLAG) BITFIELD_END // Viewport Transform Engine Control BITFIELD_BEG(PA_CL_VTE_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, VPORT_X_SCALE_ENA) BITFIELD_ENTRY(1, 1, bool, VPORT_X_OFFSET_ENA) BITFIELD_ENTRY(2, 1, bool, VPORT_Y_SCALE_ENA) BITFIELD_ENTRY(3, 1, bool, VPORT_Y_OFFSET_ENA) BITFIELD_ENTRY(4, 1, bool, VPORT_Z_SCALE_ENA) BITFIELD_ENTRY(5, 1, bool, VPORT_Z_OFFSET_ENA) BITFIELD_ENTRY(8, 1, bool, VTX_XY_FMT) BITFIELD_ENTRY(9, 1, bool, VTX_Z_FMT) BITFIELD_ENTRY(10, 1, bool, VTX_W0_FMT) BITFIELD_ENTRY(11, 1, bool, PERFCOUNTER_REF) BITFIELD_END // Multisample AA Mask BITFIELD_BEG(PA_SC_AA_MASK, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, AA_MASK_ULC) BITFIELD_ENTRY(8, 8, uint8_t, AA_MASK_URC) BITFIELD_ENTRY(16, 8, uint8_t, AA_MASK_LLC) BITFIELD_ENTRY(24, 8, uint8_t, AA_MASK_LRC) BITFIELD_END // OpenGL Clip boolean function BITFIELD_BEG(PA_SC_CLIPRECT_RULE, uint32_t) BITFIELD_ENTRY(0, 16, uint16_t, CLIP_RULE) BITFIELD_END // Line Drawing Control BITFIELD_BEG(PA_SC_LINE_CNTL, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, BRES_CNTL) BITFIELD_ENTRY(8, 1, bool, USE_BRES_CNTL) BITFIELD_ENTRY(9, 1, bool, EXPAND_LINE_WIDTH) BITFIELD_ENTRY(10, 1, bool, LAST_PIXEL) BITFIELD_END // Line Stipple Control BITFIELD_BEG(PA_SC_LINE_STIPPLE, uint32_t) BITFIELD_ENTRY(0, 16, uint16_t, LINE_PATTERN) BITFIELD_ENTRY(16, 8, uint8_t, REPEAT_COUNT) BITFIELD_ENTRY(28, 1, bool, PATTERN_BIT_ORDER) BITFIELD_ENTRY(29, 2, uint8_t, AUTO_RESET_CNTL) BITFIELD_END // SC Mode Control Register for Various Enables BITFIELD_BEG(PA_SC_MODE_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, MSAA_ENABLE) BITFIELD_ENTRY(1, 1, bool, CLIPRECT_ENABLE) BITFIELD_ENTRY(2, 1, bool, LINE_STIPPLE_ENABLE) BITFIELD_ENTRY(3, 1, bool, MULTI_CHIP_PRIM_DISCARD_ENABLE) BITFIELD_ENTRY(4, 1, bool, WALK_ORDER_ENABLE) BITFIELD_ENTRY(5, 1, bool, HALVE_DETAIL_SAMPLE_PERF) BITFIELD_ENTRY(6, 1, bool, WALK_SIZE) BITFIELD_ENTRY(7, 1, bool, WALK_ALIGNMENT) BITFIELD_ENTRY(8, 1, bool, WALK_ALIGN8_PRIM_FITS_ST) BITFIELD_ENTRY(9, 1, bool, TILE_COVER_NO_SCISSOR) BITFIELD_ENTRY(10, 1, bool, KILL_PIX_POST_HI_Z) BITFIELD_ENTRY(11, 1, bool, KILL_PIX_POST_DETAIL_MASK) BITFIELD_ENTRY(12, 1, bool, MULTI_CHIP_SUPERTILE_ENABLE) BITFIELD_ENTRY(13, 1, bool, TILE_COVER_DISABLE) BITFIELD_ENTRY(14, 1, bool, FORCE_EOV_CNTDWN_ENABLE) BITFIELD_ENTRY(15, 1, bool, FORCE_EOV_TILE_ENABLE) BITFIELD_ENTRY(16, 1, bool, FORCE_EOV_REZ_ENABLE) BITFIELD_ENTRY(17, 1, bool, PS_ITER_SAMPLE) BITFIELD_END // Multi-Pass Pixel Shader Control Register BITFIELD_BEG(PA_SC_MPASS_PS_CNTL, uint32_t) BITFIELD_ENTRY(0, 20, uint32_t, MPASS_PIX_VEC_PER_PASS) BITFIELD_ENTRY(31, 1, bool, MPASS_PS_ENA) BITFIELD_END // Screen Scissor rectangle specification BITFIELD_BEG(PA_SC_SCREEN_SCISSOR_BR, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, BR_X) BITFIELD_ENTRY(16, 14, uint32_t, BR_Y) BITFIELD_END // Screen Scissor rectangle specification BITFIELD_BEG(PA_SC_SCREEN_SCISSOR_TL, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, TL_X) BITFIELD_ENTRY(16, 14, uint32_t, TL_Y) BITFIELD_END // Generic Scissor rectangle specification BITFIELD_BEG(PA_SC_GENERIC_SCISSOR_BR, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, BR_X) BITFIELD_ENTRY(16, 14, uint32_t, BR_Y) BITFIELD_END // Generic Scissor rectangle specification BITFIELD_BEG(PA_SC_GENERIC_SCISSOR_TL, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, TL_X) BITFIELD_ENTRY(16, 14, uint32_t, TL_Y) BITFIELD_ENTRY(31, 1, bool, WINDOW_OFFSET_DISABLE) BITFIELD_END // WGF ViewportId Scissor rectangle specification (0-15). BITFIELD_BEG(PA_SC_VPORT_SCISSOR_0_BR, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, BR_X) BITFIELD_ENTRY(16, 14, uint32_t, BR_Y) BITFIELD_END // WGF ViewportId Scissor rectangle specification (0-15). BITFIELD_BEG(PA_SC_VPORT_SCISSOR_0_TL, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, TL_X) BITFIELD_ENTRY(16, 14, uint32_t, TL_Y) BITFIELD_ENTRY(31, 1, bool, WINDOW_OFFSET_DISABLE) BITFIELD_END // Viewport Transform Z Min Clamp BITFIELD_BEG(PA_SC_VPORT_ZMIN_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_ZMIN) BITFIELD_END // Viewport Transform Z Max Clamp BITFIELD_BEG(PA_SC_VPORT_ZMAX_N, uint32_t) BITFIELD_ENTRY(0, 32, float, VPORT_ZMAX) BITFIELD_END // Offset from screen coords to window coords. BITFIELD_BEG(PA_SC_WINDOW_OFFSET, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, WINDOW_X_OFFSET) BITFIELD_ENTRY(16, 14, uint32_t, WINDOW_Y_OFFSET) BITFIELD_END // Window Scissor rectangle specification. BITFIELD_BEG(PA_SC_WINDOW_SCISSOR_TL, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, TL_X) BITFIELD_ENTRY(16, 14, uint32_t, TL_Y) BITFIELD_ENTRY(31, 1, bool, WINDOW_OFFSET_DISABLE) BITFIELD_END // Window Scissor rectangle specification. BITFIELD_BEG(PA_SC_WINDOW_SCISSOR_BR, uint32_t) BITFIELD_ENTRY(0, 14, uint32_t, BR_X) BITFIELD_ENTRY(16, 14, uint32_t, BR_Y) BITFIELD_END // Line control BITFIELD_BEG(PA_SU_LINE_CNTL, uint32_t) BITFIELD_ENTRY(0, 16, uint32_t, WIDTH) // 16.0 fixed BITFIELD_END // Specifies maximum and minimum point & sprite sizes for per vertex size specification BITFIELD_BEG(PA_SU_POINT_MINMAX, uint32_t) BITFIELD_ENTRY(0, 16, uint32_t, MIN_SIZE) // 12.4 fixed BITFIELD_ENTRY(16, 16, uint32_t, MAX_SIZE) // 12.4 fixed BITFIELD_END // Dimensions for Points BITFIELD_BEG(PA_SU_POINT_SIZE, uint32_t) BITFIELD_ENTRY(0, 16, uint32_t, HEIGHT) // 12.4 fixed BITFIELD_ENTRY(16, 16, uint32_t, WIDTH) // 12.4 fixed BITFIELD_END // Clamp Value for Polygon Offset BITFIELD_BEG(PA_SU_POLY_OFFSET_CLAMP, uint32_t) BITFIELD_ENTRY(0, 32, float, CLAMP) BITFIELD_END // Back-Facing Polygon Offset Scale BITFIELD_BEG(PA_SU_POLY_OFFSET_BACK_SCALE, uint32_t) BITFIELD_ENTRY(0, 32, float, SCALE) BITFIELD_END // Back-Facing Polygon Offset Scale BITFIELD_BEG(PA_SU_POLY_OFFSET_BACK_OFFSET, uint32_t) BITFIELD_ENTRY(0, 32, float, OFFSET) BITFIELD_END // Front-Facing Polygon Offset Scale BITFIELD_BEG(PA_SU_POLY_OFFSET_FRONT_SCALE, uint32_t) BITFIELD_ENTRY(0, 32, float, SCALE) BITFIELD_END // Front-Facing Polygon Offset Scale BITFIELD_BEG(PA_SU_POLY_OFFSET_FRONT_OFFSET, uint32_t) BITFIELD_ENTRY(0, 32, float, OFFSET) BITFIELD_END // SU/SC Controls for Facedness Culling, Polymode, Polygon Offset, and various Enables BITFIELD_BEG(PA_SU_SC_MODE_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, CULL_FRONT) BITFIELD_ENTRY(1, 1, bool, CULL_BACK) BITFIELD_ENTRY(2, 1, PA_FACE, FACE) BITFIELD_ENTRY(3, 2, uint32_t, POLY_MODE) BITFIELD_ENTRY(5, 3, PA_PTYPE, POLYMODE_FRONT_PTYPE) BITFIELD_ENTRY(8, 3, PA_PTYPE, POLYMODE_BACK_PTYPE) BITFIELD_ENTRY(11, 1, bool, POLY_OFFSET_FRONT_ENABLE) BITFIELD_ENTRY(12, 1, bool, POLY_OFFSET_BACK_ENABLE) BITFIELD_ENTRY(13, 1, bool, POLY_OFFSET_PARA_ENABLE) BITFIELD_ENTRY(16, 1, bool, VTX_WINDOW_OFFSET_ENABLE) BITFIELD_ENTRY(19, 1, bool, PROVOKING_VTX_LAST) BITFIELD_ENTRY(20, 1, bool, PERSP_CORR_DIS) BITFIELD_ENTRY(21, 1, bool, MULTI_PRIM_IB_ENA) BITFIELD_END // Polygon Offset Depth Buffer Format Control BITFIELD_BEG(PA_SU_POLY_OFFSET_DB_FMT_CNTL, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, POLY_OFFSET_NEG_NUM_DB_BITS) BITFIELD_ENTRY(8, 1, bool, POLY_OFFSET_DB_IS_FLOAT_FMT) BITFIELD_END // Miscellaneous SU Control BITFIELD_BEG(PA_SU_VTX_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, PA_SU_VTX_CNTL_PIX_CENTER, PIX_CENTER) BITFIELD_ENTRY(1, 2, PA_SU_VTX_CNTL_ROUND_MODE, ROUND_MODE) BITFIELD_ENTRY(3, 3, PA_SU_VTX_CNTL_QUANT_MODE, QUANT_MODE) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_spi.h ================================================ #pragma once #include "latte_enum_spi.h" #include <common/bitfield.h> #include <cstdint> namespace latte { BITFIELD_BEG(SPI_CONFIG_CNTL_1, uint32_t) BITFIELD_ENTRY(0, 4, uint8_t, VTX_DONE_DELAY) BITFIELD_ENTRY(4, 1, bool, INTERP_ONE_PRIM_PER_ROW) BITFIELD_END BITFIELD_BEG(SPI_FOG_CNTL, uint32_t) BITFIELD_ENTRY(0, 1, bool, PASS_FOG_THROUGH_PS) BITFIELD_ENTRY(1, 2, SPI_FOG_FUNC, PIXEL_FOG_FUNC) BITFIELD_ENTRY(3, 1, SPI_FOG_SRC_SEL, PIXEL_FOG_SRC_SEL) BITFIELD_ENTRY(4, 1, bool, VS_FOG_CLAMP_DISABLE) BITFIELD_END BITFIELD_BEG(SPI_INTERP_CONTROL_0, uint32_t) BITFIELD_ENTRY(0, 1, bool, FLAT_SHADE_ENA) BITFIELD_ENTRY(1, 1, bool, PNT_SPRITE_ENA) BITFIELD_ENTRY(2, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_X) BITFIELD_ENTRY(5, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_Y) BITFIELD_ENTRY(8, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_Z) BITFIELD_ENTRY(11, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_W) BITFIELD_ENTRY(14, 1, bool, PNT_SPRITE_TOP_1) BITFIELD_END BITFIELD_BEG(SPI_INPUT_Z, uint32_t) BITFIELD_ENTRY(0, 1, bool, PROVIDE_Z_TO_SPI) BITFIELD_END // Interpolator control settings BITFIELD_BEG(SPI_PS_IN_CONTROL_0, uint32_t) BITFIELD_ENTRY(0, 6, uint32_t, NUM_INTERP) BITFIELD_ENTRY(8, 1, bool, POSITION_ENA) BITFIELD_ENTRY(9, 1, bool, POSITION_CENTROID) BITFIELD_ENTRY(10, 5, uint32_t, POSITION_ADDR) BITFIELD_ENTRY(15, 4, uint32_t, PARAM_GEN) BITFIELD_ENTRY(19, 7, uint32_t, PARAM_GEN_ADDR) BITFIELD_ENTRY(26, 2, SPI_BARYC_CNTL, BARYC_SAMPLE_CNTL) BITFIELD_ENTRY(28, 1, bool, PERSP_GRADIENT_ENA) BITFIELD_ENTRY(29, 1, bool, LINEAR_GRADIENT_ENA) BITFIELD_ENTRY(30, 1, bool, POSITION_SAMPLE) BITFIELD_ENTRY(31, 1, bool, BARYC_AT_SAMPLE_ENA) BITFIELD_END // Interpolator control settings BITFIELD_BEG(SPI_PS_IN_CONTROL_1, uint32_t) BITFIELD_ENTRY(0, 1, bool, GEN_INDEX_PIX) BITFIELD_ENTRY(1, 7, uint32_t, GEN_INDEX_PIX_ADDR) BITFIELD_ENTRY(8, 1, bool, FRONT_FACE_ENA) BITFIELD_ENTRY(9, 2, uint32_t, FRONT_FACE_CHAN) BITFIELD_ENTRY(11, 1, bool, FRONT_FACE_ALL_BITS) BITFIELD_ENTRY(12, 5, uint32_t, FRONT_FACE_ADDR) BITFIELD_ENTRY(17, 7, uint32_t, FOG_ADDR) BITFIELD_ENTRY(24, 1, bool, FIXED_PT_POSITION_ENA) BITFIELD_ENTRY(25, 5, uint32_t, FIXED_PT_POSITION_ADDR) BITFIELD_ENTRY(30, 1, bool, POSITION_ULC) BITFIELD_END // PS interpolator setttings for parameter N BITFIELD_BEG(SPI_PS_INPUT_CNTL_N, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, SEMANTIC) BITFIELD_ENTRY(8, 2, uint32_t, DEFAULT_VAL) BITFIELD_ENTRY(10, 1, bool, FLAT_SHADE) BITFIELD_ENTRY(11, 1, bool, SEL_CENTROID) BITFIELD_ENTRY(12, 1, bool, SEL_LINEAR) BITFIELD_ENTRY(13, 4, uint32_t, CYL_WRAP) BITFIELD_ENTRY(17, 1, bool, PT_SPRITE_TEX) BITFIELD_ENTRY(18, 1, bool, SEL_SAMPLE) BITFIELD_END // Vertex Shader output configuration BITFIELD_BEG(SPI_VS_OUT_CONFIG, uint32_t) BITFIELD_ENTRY(0, 1, bool, VS_PER_COMPONENT) BITFIELD_ENTRY(1, 5, uint32_t, VS_EXPORT_COUNT) BITFIELD_ENTRY(8, 1, bool, VS_EXPORTS_FOG) BITFIELD_ENTRY(9, 5, uint32_t, VS_OUT_FOG_VEC_ADDR) BITFIELD_END // Vertex Shader output semantic mapping BITFIELD_BEG(SPI_VS_OUT_ID_N, uint32_t) BITFIELD_ENTRY(0, 8, uint8_t, SEMANTIC_0) BITFIELD_ENTRY(8, 8, uint8_t, SEMANTIC_1) BITFIELD_ENTRY(16, 8, uint8_t, SEMANTIC_2) BITFIELD_ENTRY(24, 8, uint8_t, SEMANTIC_3) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_sq.h ================================================ #pragma once #include "latte_enum_common.h" #include "latte_enum_sq.h" #include <common/bitfield.h> #include <common/fixed.h> #include <cstdint> namespace latte { // ALU Constant store data for use in DX9 mode (DX10 mode uses the constant-cache // instead and this constant-file is not available). // Constants 0-225 are reserved for pixel shader // Constants 256-511 are reserved for vertex shader BITFIELD_BEG(SQ_ALU_CONSTANT0_0, uint32_t) BITFIELD_ENTRY(0, 32, float, X) BITFIELD_END BITFIELD_BEG(SQ_ALU_CONSTANT1_0, uint32_t) BITFIELD_ENTRY(0, 32, float, Y) BITFIELD_END BITFIELD_BEG(SQ_ALU_CONSTANT2_0, uint32_t) BITFIELD_ENTRY(0, 32, float, Z) BITFIELD_END BITFIELD_BEG(SQ_ALU_CONSTANT3_0, uint32_t) BITFIELD_ENTRY(0, 32, float, W) BITFIELD_END BITFIELD_BEG(SQ_CONFIG, uint32_t) BITFIELD_ENTRY(0, 1, bool, VC_ENABLE) BITFIELD_ENTRY(1, 1, bool, EXPORT_SRC_C) BITFIELD_ENTRY(2, 1, bool, DX9_CONSTS) BITFIELD_ENTRY(3, 1, bool, ALU_INST_PREFER_VECTOR) BITFIELD_ENTRY(4, 1, bool, DX10_CLAMP) BITFIELD_ENTRY(5, 1, bool, ALU_PREFER_ONE_WATERFALL) BITFIELD_ENTRY(6, 1, bool, ALU_MAX_ONE_WATERFALL) BITFIELD_ENTRY(8, 2, uint32_t, CLAUSE_SEQ_PRIO) BITFIELD_ENTRY(10, 1, bool, NO_GPR_CLAMP) BITFIELD_ENTRY(11, 1, bool, EN_TEX_SKEW) BITFIELD_ENTRY(24, 2, uint32_t, PS_PRIO) BITFIELD_ENTRY(26, 2, uint32_t, VS_PRIO) BITFIELD_ENTRY(28, 2, uint32_t, GS_PRIO) BITFIELD_ENTRY(30, 2, uint32_t, ES_PRIO) BITFIELD_END // Space allocated to a single GS output vertex in GS Temp Buffer. This defines the // size of a single vertex output by the GS.Multiple vertices can be output so long // as the total output size does not exceed SQ_GSVS_RING_ITEMSIZE. BITFIELD_BEG(SQ_GS_VERT_ITEMSIZE, uint32_t) BITFIELD_ENTRY(0, 15, uint32_t, ITEMSIZE) BITFIELD_END // Defines how GPR space is divided among the 4 thread types. BITFIELD_BEG(SQ_GPR_RESOURCE_MGMT_1, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_PS_GPRS) BITFIELD_ENTRY(16, 8, uint32_t, NUM_VS_GPRS) BITFIELD_ENTRY(27, 1, bool, DYN_GPR_ENABLE) BITFIELD_ENTRY(28, 4, uint32_t, NUM_CLAUSE_TEMP_GPRS) BITFIELD_END // Defines how GPR space is divided among the 4 thread types. BITFIELD_BEG(SQ_GPR_RESOURCE_MGMT_2, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_GS_GPRS) BITFIELD_ENTRY(16, 8, uint32_t, NUM_ES_GPRS) BITFIELD_END // Used for SQ_CF_INST_LOOP and SQ_CF_INST_LOOP_NO_AL BITFIELD_BEG(SQ_LOOP_CONST_DX9_0, uint32_t) BITFIELD_ENTRY(0, 12, uint32_t, COUNT) BITFIELD_ENTRY(12, 12, uint32_t, INIT) BITFIELD_ENTRY(24, 8, uint32_t, INC) BITFIELD_END // Used for SQ_CF_INST_LOOP_DX10 BITFIELD_BEG(SQ_LOOP_CONST_DX10_0, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, COUNT) BITFIELD_END // Defines how thread stack space is divided among the thread types BITFIELD_BEG(SQ_STACK_RESOURCE_MGMT_1, uint32_t) BITFIELD_ENTRY(0, 12, uint32_t, NUM_PS_STACK_ENTRIES) BITFIELD_ENTRY(16, 12, uint32_t, NUM_VS_STACK_ENTRIES) BITFIELD_END // Defines how thread stack space is divided among the thread types BITFIELD_BEG(SQ_STACK_RESOURCE_MGMT_2, uint32_t) BITFIELD_ENTRY(0, 12, uint32_t, NUM_GS_STACK_ENTRIES) BITFIELD_ENTRY(16, 12, uint32_t, NUM_ES_STACK_ENTRIES) BITFIELD_END // Defines how thread space is divided among the thread types BITFIELD_BEG(SQ_THREAD_RESOURCE_MGMT, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_PS_THREADS) BITFIELD_ENTRY(8, 8, uint32_t, NUM_VS_THREADS) BITFIELD_ENTRY(16, 8, uint32_t, NUM_GS_THREADS) BITFIELD_ENTRY(24, 8, uint32_t, NUM_ES_THREADS) BITFIELD_END BITFIELD_BEG(SQ_VTX_CONSTANT_WORD0_N, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_ADDRESS) BITFIELD_END BITFIELD_BEG(SQ_VTX_CONSTANT_WORD1_N, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, SIZE) BITFIELD_END BITFIELD_BEG(SQ_VTX_CONSTANT_WORD2_N, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, BASE_ADDRESS_HI) BITFIELD_ENTRY(8, 11, uint32_t, STRIDE) BITFIELD_ENTRY(19, 1, SQ_VTX_CLAMP, CLAMP_X) BITFIELD_ENTRY(20, 6, SQ_DATA_FORMAT, DATA_FORMAT) BITFIELD_ENTRY(26, 2, SQ_NUM_FORMAT, NUM_FORMAT_ALL) BITFIELD_ENTRY(28, 1, SQ_FORMAT_COMP, FORMAT_COMP_ALL) BITFIELD_ENTRY(29, 1, SQ_SRF_MODE, SRF_MODE_ALL) BITFIELD_ENTRY(30, 2, SQ_ENDIAN, ENDIAN_SWAP) BITFIELD_END BITFIELD_BEG(SQ_VTX_CONSTANT_WORD3_N, uint32_t) BITFIELD_ENTRY(0, 2, uint32_t, MEM_REQUEST_SIZE) BITFIELD_ENTRY(2, 1, bool, UNCACHED) BITFIELD_END BITFIELD_BEG(SQ_VTX_CONSTANT_WORD6_N, uint32_t) BITFIELD_ENTRY(30, 2, SQ_TEX_VTX_TYPE, TYPE) BITFIELD_END // Vertex fetch base location BITFIELD_BEG(SQ_VTX_BASE_VTX_LOC, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, OFFSET) BITFIELD_END // Vertex fetch instance offset BITFIELD_BEG(SQ_VTX_START_INST_LOC, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, OFFSET) BITFIELD_END // Resource requirements to run the GS program BITFIELD_BEG(SQ_PGM_RESOURCES_GS, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS) BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE) BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP) BITFIELD_ENTRY(22, 1, bool, PRIME_CACHE_PGM_EN) BITFIELD_ENTRY(23, 1, bool, PRIME_CACHE_ON_DRAW) BITFIELD_ENTRY(24, 3, uint32_t, FETCH_CACHE_LINES) BITFIELD_ENTRY(28, 1, bool, UNCACHED_FIRST_INST) BITFIELD_ENTRY(29, 1, bool, PRIME_CACHE_ENABLE) BITFIELD_ENTRY(30, 1, bool, PRIME_CACHE_ON_CONST) BITFIELD_END // Resource requirements to run the Vertex Shader program BITFIELD_BEG(SQ_PGM_RESOURCES_VS, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS) BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE) BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP) BITFIELD_ENTRY(22, 1, bool, PRIME_CACHE_PGM_EN) BITFIELD_ENTRY(23, 1, bool, PRIME_CACHE_ON_DRAW) BITFIELD_ENTRY(24, 3, uint32_t, FETCH_CACHE_LINES) BITFIELD_ENTRY(28, 1, bool, UNCACHED_FIRST_INST) BITFIELD_ENTRY(29, 1, bool, PRIME_CACHE_ENABLE) BITFIELD_ENTRY(30, 1, bool, PRIME_CACHE_ON_CONST) BITFIELD_END // Resource requirements to run the Pixel Shader program BITFIELD_BEG(SQ_PGM_RESOURCES_PS, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS) BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE) BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP) BITFIELD_ENTRY(22, 1, bool, PRIME_CACHE_PGM_EN) BITFIELD_ENTRY(23, 1, bool, PRIME_CACHE_ON_DRAW) BITFIELD_ENTRY(24, 3, uint32_t, FETCH_CACHE_LINES) BITFIELD_ENTRY(28, 1, bool, UNCACHED_FIRST_INST) BITFIELD_ENTRY(29, 1, bool, PRIME_CACHE_ENABLE) BITFIELD_ENTRY(30, 1, bool, PRIME_CACHE_ON_CONST) BITFIELD_ENTRY(31, 1, bool, CLAMP_CONSTS) BITFIELD_END // Resource requirements to run the Fetch Shader program BITFIELD_BEG(SQ_PGM_RESOURCES_FS, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS) BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE) BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP) BITFIELD_END // Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(FS) BITFIELD_BEG(SQ_PGM_START_FS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_START) BITFIELD_END // Size >> 3 BITFIELD_BEG(SQ_PGM_SIZE_FS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE) BITFIELD_END // Offset >> 3 BITFIELD_BEG(SQ_PGM_CF_OFFSET_FS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET) BITFIELD_END // Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(FS) BITFIELD_BEG(SQ_PGM_START_ES, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_START) BITFIELD_END // Size >> 3 BITFIELD_BEG(SQ_PGM_SIZE_ES, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE) BITFIELD_END // Offset >> 3 BITFIELD_BEG(SQ_PGM_CF_OFFSET_ES, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET) BITFIELD_END // Memory address of the (256-byte aligned) first CF instruction of the shader code for the geometry shader(GS) BITFIELD_BEG(SQ_PGM_START_GS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_START) BITFIELD_END // Size >> 3 BITFIELD_BEG(SQ_PGM_SIZE_GS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE) BITFIELD_END // Offset >> 3 BITFIELD_BEG(SQ_PGM_CF_OFFSET_GS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET) BITFIELD_END // Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(VS) BITFIELD_BEG(SQ_PGM_START_VS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_START) BITFIELD_END // Size >> 3 BITFIELD_BEG(SQ_PGM_SIZE_VS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE) BITFIELD_END // Offset >> 3 BITFIELD_BEG(SQ_PGM_CF_OFFSET_VS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET) BITFIELD_END // Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(PS) BITFIELD_BEG(SQ_PGM_START_PS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_START) BITFIELD_END // Size >> 3 BITFIELD_BEG(SQ_PGM_SIZE_PS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE) BITFIELD_END // Offset >> 3 BITFIELD_BEG(SQ_PGM_CF_OFFSET_PS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET) BITFIELD_END // Defines the exports from the Pixel Shader Program. BITFIELD_BEG(SQ_PGM_EXPORTS_PS, uint32_t) BITFIELD_ENTRY(0, 5, uint32_t, EXPORT_MODE) BITFIELD_END // This register is used to clear the contents of the vertex semantic table. // Entries can be cleared independently -- each has one bit in this register to clear or leave alone. BITFIELD_BEG(SQ_VTX_SEMANTIC_CLEAR, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, CLEAR) BITFIELD_END BITFIELD_BEG(SQ_VTX_SEMANTIC_N, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, SEMANTIC_ID) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD0_N, uint32_t) BITFIELD_ENTRY(0, 3, SQ_TEX_DIM, DIM) BITFIELD_ENTRY(3, 4, SQ_TILE_MODE, TILE_MODE) BITFIELD_ENTRY(7, 1, SQ_TILE_TYPE, TILE_TYPE) BITFIELD_ENTRY(8, 11, uint32_t, PITCH) BITFIELD_ENTRY(19, 13, uint32_t, TEX_WIDTH) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD1_N, uint32_t) BITFIELD_ENTRY(0, 13, uint32_t, TEX_HEIGHT) BITFIELD_ENTRY(13, 13, uint32_t, TEX_DEPTH) BITFIELD_ENTRY(26, 6, SQ_DATA_FORMAT, DATA_FORMAT) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD2_N, uint32_t) BITFIELD_ENTRY(0, 3, uint32_t, SWIZZLE) BITFIELD_ENTRY(0, 32, uint32_t, BASE_ADDRESS) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD3_N, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, MIP_ADDRESS) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD4_N, uint32_t) BITFIELD_ENTRY(0, 2, SQ_FORMAT_COMP, FORMAT_COMP_X) BITFIELD_ENTRY(2, 2, SQ_FORMAT_COMP, FORMAT_COMP_Y) BITFIELD_ENTRY(4, 2, SQ_FORMAT_COMP, FORMAT_COMP_Z) BITFIELD_ENTRY(6, 2, SQ_FORMAT_COMP, FORMAT_COMP_W) BITFIELD_ENTRY(8, 2, SQ_NUM_FORMAT, NUM_FORMAT_ALL) BITFIELD_ENTRY(10, 1, SQ_SRF_MODE, SRF_MODE_ALL) BITFIELD_ENTRY(11, 1, bool, FORCE_DEGAMMA) BITFIELD_ENTRY(12, 2, SQ_ENDIAN, ENDIAN_SWAP) BITFIELD_ENTRY(14, 2, uint32_t, REQUEST_SIZE) BITFIELD_ENTRY(16, 3, SQ_SEL, DST_SEL_X) BITFIELD_ENTRY(19, 3, SQ_SEL, DST_SEL_Y) BITFIELD_ENTRY(22, 3, SQ_SEL, DST_SEL_Z) BITFIELD_ENTRY(25, 3, SQ_SEL, DST_SEL_W) BITFIELD_ENTRY(28, 4, uint32_t, BASE_LEVEL) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD5_N, uint32_t) BITFIELD_ENTRY(0, 4, uint32_t, LAST_LEVEL) BITFIELD_ENTRY(4, 13, uint32_t, BASE_ARRAY) BITFIELD_ENTRY(17, 13, uint32_t, LAST_ARRAY) BITFIELD_ENTRY(30, 2, uint32_t, YUV_CONV) BITFIELD_END BITFIELD_BEG(SQ_TEX_RESOURCE_WORD6_N, uint32_t) BITFIELD_ENTRY(0, 2, SQ_TEX_MPEG_CLAMP, MPEG_CLAMP) BITFIELD_ENTRY(2, 3, uint32_t, MAX_ANISO_RATIO) BITFIELD_ENTRY(5, 3, uint32_t, PERF_MODULATION) BITFIELD_ENTRY(8, 1, bool, INTERLACED) BITFIELD_ENTRY(9, 4, uint32_t, ADVIS_FAULT_LOD) BITFIELD_ENTRY(13, 6, uint32_t, ADVIS_CLAMP_LOD) BITFIELD_ENTRY(30, 2, SQ_TEX_VTX_TYPE, TYPE) BITFIELD_END BITFIELD_BEG(SQ_TEX_SAMPLER_WORD0_N, uint32_t) BITFIELD_ENTRY(0, 3, SQ_TEX_CLAMP, CLAMP_X) BITFIELD_ENTRY(3, 3, SQ_TEX_CLAMP, CLAMP_Y) BITFIELD_ENTRY(6, 3, SQ_TEX_CLAMP, CLAMP_Z) BITFIELD_ENTRY(9, 3, SQ_TEX_XY_FILTER, XY_MAG_FILTER) BITFIELD_ENTRY(12, 3, SQ_TEX_XY_FILTER, XY_MIN_FILTER) BITFIELD_ENTRY(15, 2, SQ_TEX_Z_FILTER, Z_FILTER) BITFIELD_ENTRY(17, 2, SQ_TEX_Z_FILTER, MIP_FILTER) BITFIELD_ENTRY(19, 3, SQ_TEX_ANISO, MAX_ANISO_RATIO) BITFIELD_ENTRY(22, 2, SQ_TEX_BORDER_COLOR, BORDER_COLOR_TYPE) BITFIELD_ENTRY(24, 1, bool, POINT_SAMPLING_CLAMP) BITFIELD_ENTRY(25, 1, bool, TEX_ARRAY_OVERRIDE) BITFIELD_ENTRY(26, 3, REF_FUNC, DEPTH_COMPARE_FUNCTION) BITFIELD_ENTRY(29, 2, SQ_TEX_CHROMA_KEY, CHROMA_KEY) BITFIELD_ENTRY(31, 1, bool, LOD_USES_MINOR_AXIS) BITFIELD_END BITFIELD_BEG(SQ_TEX_SAMPLER_WORD1_N, uint32_t) BITFIELD_ENTRY(0, 10, ufixed_4_6_t, MIN_LOD) BITFIELD_ENTRY(10, 10, ufixed_4_6_t, MAX_LOD) BITFIELD_ENTRY(20, 12, sfixed_1_5_6_t, LOD_BIAS) BITFIELD_END BITFIELD_BEG(SQ_TEX_SAMPLER_WORD2_N, uint32_t) BITFIELD_ENTRY(0, 12, uint32_t, LOD_BIAS_SEC) BITFIELD_ENTRY(12, 1, bool, MC_COORD_TRUNCATE) BITFIELD_ENTRY(13, 1, bool, FORCE_DEGAMMA) BITFIELD_ENTRY(14, 1, bool, HIGH_PRECISION_FILTER) BITFIELD_ENTRY(15, 3, uint32_t, PERF_MIP) BITFIELD_ENTRY(18, 2, uint32_t, PERF_Z) BITFIELD_ENTRY(20, 6, ufixed_1_5_t, ANISO_BIAS) BITFIELD_ENTRY(26, 1, bool, FETCH_4) BITFIELD_ENTRY(27, 1, bool, SAMPLE_IS_PCF) BITFIELD_ENTRY(28, 1, SQ_TEX_ROUNDING_MODE, TRUNCATE_COORD) BITFIELD_ENTRY(29, 1, bool, DISABLE_CUBE_WRAP) BITFIELD_ENTRY(31, 1, bool, TYPE) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_sx.h ================================================ #pragma once #include "latte_enum_common.h" #include <common/bitfield.h> #include <cstdint> namespace latte { BITFIELD_BEG(SX_ALPHA_TEST_CONTROL, uint32_t) BITFIELD_ENTRY(0, 3, REF_FUNC, ALPHA_FUNC) BITFIELD_ENTRY(3, 1, bool, ALPHA_TEST_ENABLE) BITFIELD_ENTRY(8, 1, bool, ALPHA_TEST_BYPASS) BITFIELD_END BITFIELD_BEG(SX_ALPHA_REF, uint32_t) BITFIELD_ENTRY(0, 32, float, ALPHA_REF) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_ta.h ================================================ #pragma once #include <common/bitfield.h> #include <cstdint> namespace latte { // Texture Addresser Common Control BITFIELD_BEG(TA_CNTL_AUX, uint32_t) BITFIELD_ENTRY(0, 1, bool, DISABLE_CUBE_WRAP) BITFIELD_ENTRY(1, 1, bool, UNK0) BITFIELD_ENTRY(24, 1, bool, SYNC_GRADIENT) BITFIELD_ENTRY(25, 1, bool, SYNC_WALKER) BITFIELD_ENTRY(26, 1, bool, SYNC_ALIGNER) BITFIELD_ENTRY(31, 1, bool, BILINEAR_PRECISION) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_td.h ================================================ #pragma once #include <common/bitfield.h> #include <cstdint> namespace latte { BITFIELD_BEG(TD_PS_SAMPLER_BORDERN_RED, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_RED) BITFIELD_END BITFIELD_BEG(TD_PS_SAMPLER_BORDERN_GREEN, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_GREEN) BITFIELD_END BITFIELD_BEG(TD_PS_SAMPLER_BORDERN_BLUE, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_BLUE) BITFIELD_END BITFIELD_BEG(TD_PS_SAMPLER_BORDERN_ALPHA, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_ALPHA) BITFIELD_END BITFIELD_BEG(TD_VS_SAMPLER_BORDERN_RED, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_RED) BITFIELD_END BITFIELD_BEG(TD_VS_SAMPLER_BORDERN_GREEN, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_GREEN) BITFIELD_END BITFIELD_BEG(TD_VS_SAMPLER_BORDERN_BLUE, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_BLUE) BITFIELD_END BITFIELD_BEG(TD_VS_SAMPLER_BORDERN_ALPHA, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_ALPHA) BITFIELD_END BITFIELD_BEG(TD_GS_SAMPLER_BORDERN_RED, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_RED) BITFIELD_END BITFIELD_BEG(TD_GS_SAMPLER_BORDERN_GREEN, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_GREEN) BITFIELD_END BITFIELD_BEG(TD_GS_SAMPLER_BORDERN_BLUE, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_BLUE) BITFIELD_END BITFIELD_BEG(TD_GS_SAMPLER_BORDERN_ALPHA, uint32_t) BITFIELD_ENTRY(0, 32, float, BORDER_ALPHA) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/latte/latte_registers_vgt.h ================================================ #pragma once #include "latte_enum_vgt.h" #include <common/bitfield.h> #include <cstdint> namespace latte { // Draw Inititiator BITFIELD_BEG(VGT_DRAW_INITIATOR, uint32_t) BITFIELD_ENTRY(0, 2, VGT_DI_SRC_SEL, SOURCE_SELECT) BITFIELD_ENTRY(2, 2, VGT_DI_MAJOR_MODE, MAJOR_MODE) BITFIELD_ENTRY(4, 1, bool, SPRITE_EN_R6XX) BITFIELD_ENTRY(5, 1, bool, NOT_EOP) BITFIELD_ENTRY(6, 1, bool, USE_OPAQUE) BITFIELD_END // VGT Non-DMA Index Type BITFIELD_BEG(VGT_NODMA_INDEX_TYPE, uint32_t) BITFIELD_ENTRY(0, 2, VGT_INDEX_TYPE, INDEX_TYPE) BITFIELD_END // VGT DMA Base Address BITFIELD_BEG(VGT_DMA_BASE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, BASE_ADDR) BITFIELD_END // VGT DMA Base Address : upper 8-bits of 40 bit address BITFIELD_BEG(VGT_DMA_BASE_HI, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, BASE_ADDR) BITFIELD_END // VGT DMA Index Type and Mode BITFIELD_BEG(VGT_DMA_INDEX_TYPE, uint32_t) BITFIELD_ENTRY(0, 2, VGT_INDEX_TYPE, INDEX_TYPE) BITFIELD_ENTRY(2, 2, VGT_DMA_SWAP, SWAP_MODE) BITFIELD_END // VGT DMA Maximum Size BITFIELD_BEG(VGT_DMA_MAX_SIZE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, MAX_SIZE) BITFIELD_END // VGT DMA Number of Instances BITFIELD_BEG(VGT_DMA_NUM_INSTANCES, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, NUM_INSTANCES) BITFIELD_END // VGT DMA Size BITFIELD_BEG(VGT_DMA_SIZE, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, NUM_INDICES) BITFIELD_END // Maximum ES vertices per GS thread BITFIELD_BEG(VGT_ES_PER_GS, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, ES_PER_GS) BITFIELD_END // Event Initiator BITFIELD_BEG(VGT_EVENT_INITIATOR, uint32_t) BITFIELD_ENTRY(0, 6, VGT_EVENT_TYPE, EVENT_TYPE) BITFIELD_ENTRY(8, 4, VGT_EVENT_INDEX, EVENT_INDEX) BITFIELD_ENTRY(19, 8, uint32_t, ADDRESS_HI) BITFIELD_ENTRY(27, 1, uint32_t, EXTENDED_EVENT) BITFIELD_END // VGT GS Enable Mode BITFIELD_BEG(VGT_GS_MODE, uint32_t) BITFIELD_ENTRY(0, 2, VGT_GS_ENABLE_MODE, MODE) BITFIELD_ENTRY(2, 1, bool, ES_PASSTHRU) BITFIELD_ENTRY(3, 2, VGT_GS_CUT_MODE, CUT_MODE) BITFIELD_ENTRY(8, 1, bool, MODE_HI) BITFIELD_ENTRY(11, 1, bool, GS_C_PACK_EN) BITFIELD_ENTRY(14, 1, bool, COMPUTE_MODE) BITFIELD_ENTRY(15, 1, bool, FAST_COMPUTE_MODE) BITFIELD_ENTRY(16, 1, bool, ELEMENT_INFO_EN) BITFIELD_ENTRY(17, 1, bool, PARTIAL_THD_AT_EOI) BITFIELD_END // VGT GS output primitive type BITFIELD_BEG(VGT_GS_OUT_PRIM_TYPE, uint32_t) BITFIELD_ENTRY(0, 6, VGT_GS_OUT_PRIMITIVE_TYPE, PRIM_TYPE) BITFIELD_END // Maximum GS prims per ES thread BITFIELD_BEG(VGT_GS_PER_ES, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, GS_PER_ES) BITFIELD_END // Maximum GS prims per VS thread BITFIELD_BEG(VGT_GS_PER_VS, uint32_t) BITFIELD_ENTRY(0, 4, uint8_t, GS_PER_VS) BITFIELD_END // Reuseability for GS path, it is nothing to do with number of good simd BITFIELD_BEG(VGT_GS_VERTEX_REUSE, uint32_t) BITFIELD_ENTRY(0, 5, uint8_t, VERT_REUSE) BITFIELD_END BITFIELD_BEG(VGT_HOS_REUSE_DEPTH, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, REUSE_DEPTH) BITFIELD_END // For continuous and discrete tessellation modes, this register contains the tessellation level. // For adaptive tessellation, this register contains the maximum tessellation level. BITFIELD_BEG(VGT_HOS_MAX_TESS_LEVEL, uint32_t) BITFIELD_ENTRY(0, 32, float, MAX_TESS) BITFIELD_END // For continuous and discrete tessellation modes, this register is not applicable. // For adaptive tessellation, this register contains the minimum tessellation level. BITFIELD_BEG(VGT_HOS_MIN_TESS_LEVEL, uint32_t) BITFIELD_ENTRY(0, 32, float, MIN_TESS) BITFIELD_END // For components that are that are specified to be indices (see the VGT_GROUP_VECT_0_FMT_CNTL register), this register is the offset value. BITFIELD_BEG(VGT_INDX_OFFSET, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, INDX_OFFSET) BITFIELD_END // For components that are that are specified to be indices (see the VGT_GROUP_VECT_0_FMT_CNTL register), this register is the maximum clamp value. BITFIELD_BEG(VGT_MAX_VTX_INDX, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, MAX_INDX) BITFIELD_END // For components that are that are specified to be indices (see the VGT_GROUP_VECT_0_FMT_CNTL register), this register is the minimum clamp value. BITFIELD_BEG(VGT_MIN_VTX_INDX, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, MIN_INDX) BITFIELD_END // This register enabling reseting of prim based on reset index BITFIELD_BEG(VGT_MULTI_PRIM_IB_RESET_EN, uint32_t) BITFIELD_ENTRY(0, 1, bool, RESET_EN) BITFIELD_END // This register defines the index which resets primitive sets when MULTI_PRIM_IB is enabled. BITFIELD_BEG(VGT_MULTI_PRIM_IB_RESET_INDX, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, RESET_INDX) BITFIELD_END // VGT Number of Indices BITFIELD_BEG(VGT_NUM_INDICES, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, NUM_INDICES) BITFIELD_END // This register controls, within a process vector, when the previous process vector is de-allocated. BITFIELD_BEG(VGT_OUT_DEALLOC_CNTL, uint32_t) BITFIELD_ENTRY(0, 7, uint32_t, DEALLOC_DIST) BITFIELD_END // This register selects which backend path will be used by the VGT block. BITFIELD_BEG(VGT_OUTPUT_PATH_CNTL, uint32_t) BITFIELD_ENTRY(0, 2, VGT_OUTPUT_PATH_SELECT, PATH_SELECT) BITFIELD_END // Primitive ID generation is enabled BITFIELD_BEG(VGT_PRIMITIVEID_EN, uint32_t) BITFIELD_ENTRY(0, 1, bool, PRIMITIVEID_EN) BITFIELD_END // VGT Primitive Type BITFIELD_BEG(VGT_PRIMITIVE_TYPE, uint32_t) BITFIELD_ENTRY(0, 6, VGT_DI_PRIMITIVE_TYPE, PRIM_TYPE) BITFIELD_END // VGT reuse is off. This will expand strip primitives to list primitives BITFIELD_BEG(VGT_REUSE_OFF, uint32_t) BITFIELD_ENTRY(0, 1, bool, REUSE_OFF) BITFIELD_END // This register enables streaming out BITFIELD_BEG(VGT_STRMOUT_EN, uint32_t) BITFIELD_ENTRY(0, 1, bool, STREAMOUT) BITFIELD_END // Stream out enable bits. BITFIELD_BEG(VGT_STRMOUT_BUFFER_EN, uint32_t) BITFIELD_ENTRY(0, 1, bool, BUFFER_0_EN) BITFIELD_ENTRY(1, 1, bool, BUFFER_1_EN) BITFIELD_ENTRY(2, 1, bool, BUFFER_2_EN) BITFIELD_ENTRY(3, 1, bool, BUFFER_3_EN) BITFIELD_END // Draw opaque offset. BITFIELD_BEG(VGT_STRMOUT_DRAW_OPAQUE_OFFSET, uint32_t) BITFIELD_ENTRY(0, 32, uint32_t, OFFSET) BITFIELD_END // This register controls the behavior of the Vertex Reuse block at the backend of the VGT. BITFIELD_BEG(VGT_VERTEX_REUSE_BLOCK_CNTL, uint32_t) BITFIELD_ENTRY(0, 8, uint32_t, VTX_REUSE_DEPTH) BITFIELD_END // Auto-index generation is on. BITFIELD_BEG(VGT_VTX_CNT_EN, uint32_t) BITFIELD_ENTRY(0, 1, bool, VTX_CNT_EN) BITFIELD_END } // namespace latte ================================================ FILE: src/libgpu/src/gpu7_displaylayout.cpp ================================================ #include "gpu7_displaylayout.h" #include "gpu_config.h" #include <algorithm> #include <cmath> namespace gpu7 { void updateDisplayLayout(DisplayLayout &layout, float windowWidth, float windowHeight) { // Sizes for calculating aspect ratio constexpr auto TvWidth = 1280.0f; constexpr auto TvHeight = 720.0f; constexpr auto DrcWidth = 854.0f; constexpr auto DrcHeight = 480.0f; constexpr auto SplitCombinedWidth = std::max(TvWidth, DrcWidth); constexpr auto SplitCombinedHeight = TvHeight + DrcHeight; const auto &displaySettings = gpu::config()->display; const auto splitScreenSeparation = static_cast<float>(displaySettings.splitSeperation); auto maintainAspectRatio = [&](DisplayLayout::Display &display, float width, float height, float referenceWidth, float referenceHeight) { const auto widthRatio = static_cast<float>(width) / referenceWidth; const auto heightRatio = static_cast<float>(height) / referenceHeight; const auto aspectRatio = std::min(widthRatio, heightRatio); display.width = aspectRatio * referenceWidth; display.height = aspectRatio * referenceHeight; }; if (displaySettings.viewMode == gpu::DisplaySettings::TV) { layout.tv.visible = true; layout.drc.visible = false; if (displaySettings.maintainAspectRatio) { maintainAspectRatio(layout.tv, windowWidth, windowHeight, TvWidth, TvHeight); layout.tv.x = (windowWidth - layout.tv.width) / 2.0f; layout.tv.y = (windowHeight - layout.tv.height) / 2.0f; } else { layout.tv.x = 0.0f; layout.tv.y = 0.0f; layout.tv.width = windowWidth; layout.tv.height = windowHeight; } } else if (displaySettings.viewMode == gpu::DisplaySettings::Gamepad1) { layout.tv.visible = false; layout.drc.visible = true; if (displaySettings.maintainAspectRatio) { maintainAspectRatio(layout.drc, windowWidth, windowHeight, DrcWidth, DrcHeight); layout.drc.x = (windowWidth - layout.drc.width) / 2.0f; layout.drc.y = (windowHeight - layout.drc.height) / 2.0f; } else { layout.drc.x = 0.0f; layout.drc.y = 0.0f; layout.drc.width = windowWidth; layout.drc.height = windowHeight; } } else if (displaySettings.viewMode == gpu::DisplaySettings::Split) { layout.tv.visible = true; layout.drc.visible = true; if (displaySettings.maintainAspectRatio) { auto combined = DisplayLayout::Display { }; maintainAspectRatio(combined, windowWidth, windowHeight, SplitCombinedWidth, SplitCombinedHeight + splitScreenSeparation); layout.tv.width = combined.width * (TvWidth / SplitCombinedWidth); layout.tv.height = combined.height * (TvHeight / SplitCombinedHeight) - (splitScreenSeparation / 2.0f); layout.tv.x = (windowWidth - layout.tv.width) / 2.0f; layout.tv.y = (windowHeight - combined.height) / 2.0f; layout.drc.width = combined.width * (DrcWidth / SplitCombinedWidth); layout.drc.height = combined.height * (DrcHeight / SplitCombinedHeight) - (splitScreenSeparation / 2.0f); layout.drc.x = (windowWidth - layout.drc.width) / 2.0f; layout.drc.y = layout.tv.y + layout.tv.height + splitScreenSeparation; } else { layout.tv.x = 0.0f; layout.tv.y = 0.0f; layout.tv.width = windowWidth; layout.tv.height = windowHeight * (TvHeight / SplitCombinedHeight); layout.drc.x = 0.0f; layout.drc.y = layout.tv.height; layout.drc.width = windowWidth; layout.drc.height = windowHeight * (DrcHeight / SplitCombinedHeight); } } layout.backgroundColour[0] = displaySettings.backgroundColour[0] / 255.0f; layout.backgroundColour[1] = displaySettings.backgroundColour[1] / 255.0f; layout.backgroundColour[2] = displaySettings.backgroundColour[2] / 255.0f; layout.backgroundColour[3] = 1.0f; // TODO: Only do if SRGB layout.backgroundColour[0] = std::pow(layout.backgroundColour[0], 2.2f); layout.backgroundColour[1] = std::pow(layout.backgroundColour[1], 2.2f); layout.backgroundColour[2] = std::pow(layout.backgroundColour[2], 2.2f); } DisplayTouchEvent translateDisplayTouch(DisplayLayout &layout, float x, float y) { if (layout.tv.visible && x >= layout.tv.x && x < layout.tv.x + layout.tv.width && y >= layout.tv.y && y < layout.tv.y + layout.tv.height) { return { DisplayTouchEvent::Tv, (x - layout.tv.x) / layout.tv.width, (y - layout.tv.y) / layout.tv.height, }; } if (layout.drc.visible && x >= layout.drc.x && x < layout.drc.x + layout.drc.width && y >= layout.drc.y && y < layout.drc.y + layout.drc.height) { return { DisplayTouchEvent::Drc1, (x - layout.drc.x) / layout.drc.width, (y - layout.drc.y) / layout.drc.height, }; } return { DisplayTouchEvent::None, 0.0f, 0.0f }; } } // namespace gpu7 ================================================ FILE: src/libgpu/src/gpu7_tiling.cpp ================================================ #include "gpu7_tiling.h" #include <algorithm> #include <cstdint> #include <cstring> #include <common/align.h> #include <common/decaf_assert.h> #include <addrlib/addrinterface.h> namespace gpu7::tiling { int computeSurfaceBankSwappedWidth(TileMode tileMode, uint32_t bpp, uint32_t numSamples, uint32_t pitch) { const auto bytesPerSample = 8 * bpp; const auto samplesPerTile = SampleSplitSize / bytesPerSample; auto bankSwapWidth = uint32_t { 0 }; auto slicesPerTile = uint32_t { 1 }; if (samplesPerTile) { slicesPerTile = std::max<uint32_t>(1u, numSamples / samplesPerTile); } if (getMicroTileThickness(tileMode) > 1) { numSamples = 4; } if (tileMode == TileMode::Macro2BTiledThin1 || tileMode == TileMode::Macro2BTiledThin2 || tileMode == TileMode::Macro2BTiledThin4 || tileMode == TileMode::Macro2BTiledThick || tileMode == TileMode::Macro3BTiledThin1 || tileMode == TileMode::Macro3BTiledThick) { const auto swapTiles = std::max<uint32_t>(1u, (SwapSize >> 1) / bpp); const auto swapWidth = swapTiles * 8 * NumBanks; const auto macroTileHeight = getMacroTileHeight(tileMode); const auto heightBytes = numSamples * macroTileHeight * bpp / slicesPerTile; const auto swapMax = NumPipes * NumBanks * RowSize / heightBytes; const auto bytesPerTileSlice = numSamples * bytesPerSample / slicesPerTile; const auto swapMin = PipeInterleaveBytes * 8 * NumBanks / bytesPerTileSlice; bankSwapWidth = std::min(swapMax, std::max(swapMin, swapWidth)); while (bankSwapWidth >= 2 * pitch) { bankSwapWidth >>= 1; } } return bankSwapWidth; } static void * addrLibAlloc(const ADDR_ALLOCSYSMEM_INPUT *pInput) { return std::malloc(pInput->sizeInBytes); } static ADDR_E_RETURNCODE addrLibFree(const ADDR_FREESYSMEM_INPUT *pInput) { std::free(pInput->pVirtAddr); return ADDR_OK; } static ADDR_HANDLE getAddrLibHandle() { static ADDR_HANDLE handle = nullptr; if (!handle) { auto input = ADDR_CREATE_INPUT { }; input.size = sizeof(ADDR_CREATE_INPUT); input.chipEngine = CIASICIDGFXENGINE_R600; input.chipFamily = 0x51; input.chipRevision = 71; input.createFlags.fillSizeFields = 1; input.regValue.gbAddrConfig = 0x44902; input.callbacks.allocSysMem = &addrLibAlloc; input.callbacks.freeSysMem = &addrLibFree; auto output = ADDR_CREATE_OUTPUT { }; output.size = sizeof(ADDR_CREATE_OUTPUT); if (AddrCreate(&input, &output) == ADDR_OK) { handle = output.hLib; } } return handle; } SurfaceInfo computeSurfaceInfo(const SurfaceDescription &surface, int mipLevel) { auto output = ADDR_COMPUTE_SURFACE_INFO_OUTPUT { }; output.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT); auto input = ADDR_COMPUTE_SURFACE_INFO_INPUT { }; input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT); input.tileMode = static_cast<AddrTileMode>(surface.tileMode); input.format = static_cast<AddrFormat>(surface.format); input.bpp = surface.bpp; input.numSamples = surface.numSamples; input.numFrags = surface.numFrags; input.mipLevel = mipLevel; input.slice = 0; input.numSlices = surface.numSlices; input.width = std::max(surface.width >> mipLevel, 1u); input.height = std::max(surface.height >> mipLevel, 1u); input.flags.inputBaseMap = mipLevel == 0 ? 1 : 0; if (surface.use & SurfaceUse::ScanBuffer) { input.flags.display = 1; } if (surface.use & SurfaceUse::DepthBuffer) { input.flags.depth = 1; } if (surface.dim == SurfaceDim::Texture3D) { input.flags.volume = 1; input.numSlices = std::max(surface.numSlices >> mipLevel, 1u); } if (surface.dim == SurfaceDim::TextureCube) { input.flags.cube = 1; } auto handle = getAddrLibHandle(); decaf_check(handle); decaf_check(AddrComputeSurfaceInfo(handle, &input, &output) == ADDR_OK); if (surface.dim == SurfaceDim::Texture3D) { output.sliceSize /= output.depth; } auto result = SurfaceInfo { }; result.tileMode = static_cast<TileMode>(output.tileMode); result.use = surface.use; result.bpp = output.bpp; result.pitch = output.pitch; result.height = output.height; result.depth = output.depth; result.surfSize = static_cast<uint32_t>(output.surfSize); result.sliceSize = output.sliceSize; result.baseAlign = output.baseAlign; result.pitchAlign = output.pitchAlign; result.heightAlign = output.heightAlign; result.depthAlign = output.depthAlign; result.bankSwizzle = surface.bankSwizzle; result.pipeSwizzle = surface.pipeSwizzle; return result; } void unpitchImage(const SurfaceDescription &desc, const void *pitched, void *unpitched) { const auto info = computeSurfaceInfo(desc, 0); const auto bytesPerElem = info.bpp / 8; const auto unpitchedSliceSize = desc.width * desc.height * bytesPerElem; for (auto slice = 0u; slice < desc.numSlices; ++slice) { auto src = reinterpret_cast<const uint8_t*>(pitched) + info.sliceSize * slice; auto dst = reinterpret_cast<uint8_t*>(unpitched) + unpitchedSliceSize * slice; for (auto y = 0u; y < desc.height; ++y) { std::memcpy(dst, src, desc.width * bytesPerElem); src += info.pitch * bytesPerElem; dst += desc.width * bytesPerElem; } } } size_t computeUnpitchedImageSize(const SurfaceDescription &desc) { return desc.width * desc.height * (desc.bpp / 8) * desc.numSlices; } size_t computeUnpitchedMipMapSize(const SurfaceDescription &desc) { auto size = size_t { 0 }; for (auto level = 1u; level < desc.numLevels; ++level) { const auto width = desc.width >> level; const auto height = desc.height >> level; auto numSlices = desc.numSlices; if (desc.dim == SurfaceDim::Texture3D) { numSlices >>= level; } size += width * height * (desc.bpp / 8) * numSlices; } return size; } void unpitchMipMap(const SurfaceDescription &desc, const void *pitched, void *unpitched) { auto srcMipOffset = size_t { 0 }; auto dstMipOffset = size_t { 0 }; for (auto level = 1u; level < desc.numLevels; ++level) { const auto info = computeSurfaceInfo(desc, level); const auto bytesPerElem = info.bpp / 8; const auto width = desc.width >> level; const auto height = desc.height >> level; const auto unpitchedSliceSize = width * height * bytesPerElem; auto numSlices = desc.numSlices; if (desc.dim == SurfaceDim::Texture3D) { numSlices >>= level; } srcMipOffset = align_up(srcMipOffset, info.baseAlign); for (auto slice = 0u; slice < numSlices; ++slice) { auto src = reinterpret_cast<const uint8_t*>(pitched) + srcMipOffset + info.sliceSize * slice; auto dst = reinterpret_cast<uint8_t*>(unpitched) + dstMipOffset + unpitchedSliceSize * slice; for (auto y = 0u; y < height; ++y) { std::memcpy(dst, src, width * bytesPerElem); src += info.pitch * bytesPerElem; dst += width * bytesPerElem; } } srcMipOffset += info.surfSize; dstMipOffset += unpitchedSliceSize * numSlices; } } RetileInfo computeLinearRetileInfo(const SurfaceInfo &info) { const auto bytesPerElement = info.bpp / 8; const auto pitch = info.pitch; const auto height = info.height; const auto thinSliceBytes = pitch * height * bytesPerElement; RetileInfo out; out.tileMode = static_cast<TileMode>(info.tileMode); out.bitsPerElement = info.bpp; out.isDepth = !!(info.use & gpu7::tiling::SurfaceUse::DepthBuffer); out.thinSliceBytes = thinSliceBytes; out.isTiled = false; out.isMacroTiled = false; out.macroTileWidth = 0; out.macroTileHeight = 0; out.microTileThickness = 0; out.thickMicroTileBytes = 0; out.numTilesPerRow = 0; out.numTilesPerSlice = 0; out.bankSwizzle = 0; out.pipeSwizzle = 0; out.bankSwapWidth = 0; return out; } RetileInfo computeMicroRetileInfo(const SurfaceInfo &info) { const auto bytesPerElement = info.bpp / 8; const auto pitch = info.pitch; const auto height = info.height; const auto microTileThickness = getMicroTileThickness(info.tileMode); const auto microTileBytes = MicroTileWidth * MicroTileHeight * microTileThickness * bytesPerElement; const auto microTilesPerRow = pitch / MicroTileWidth; const auto microTilesNumRows = height / MicroTileHeight; const auto microTilesPerSlice = microTilesPerRow * microTilesNumRows; const auto thinSliceBytes = pitch * height * bytesPerElement; RetileInfo out; out.tileMode = static_cast<TileMode>(info.tileMode); out.bitsPerElement = info.bpp; out.isDepth = !!(info.use & gpu7::tiling::SurfaceUse::DepthBuffer); out.thinSliceBytes = thinSliceBytes; out.isTiled = true; out.isMacroTiled = false; out.macroTileWidth = 1; out.macroTileHeight = 1; out.microTileThickness = microTileThickness; out.thickMicroTileBytes = microTileBytes; out.numTilesPerRow = microTilesPerRow; out.numTilesPerSlice = microTilesPerSlice; out.bankSwizzle = 0; out.pipeSwizzle = 0; out.bankSwapWidth = 0; return out; } RetileInfo computeMacroRetileInfo(const SurfaceInfo &info) { const auto bytesPerElement = info.bpp / 8; const auto pitch = info.pitch; const auto height = info.height; const auto macroTileWidth = getMacroTileWidth(info.tileMode); const auto macroTileHeight = getMacroTileHeight(info.tileMode); const auto microTileThickness = getMicroTileThickness(info.tileMode); const auto microTileBytes = MicroTileWidth * MicroTileHeight * microTileThickness * bytesPerElement; const auto microTilesPerRow = pitch / MicroTileWidth; const auto microTilesNumRows = height / MicroTileHeight; const auto microTilesPerSlice = microTilesPerRow * microTilesNumRows; auto bankSwapWidth = computeSurfaceBankSwappedWidth( info.tileMode, info.bpp, 1, info.pitch); const auto thinSliceBytes = pitch * height * bytesPerElement; RetileInfo out; out.tileMode = static_cast<TileMode>(info.tileMode); out.bitsPerElement = info.bpp; out.isDepth = !!(info.use & gpu7::tiling::SurfaceUse::DepthBuffer); out.thinSliceBytes = thinSliceBytes; out.isTiled = true; out.isMacroTiled = true; out.macroTileWidth = macroTileWidth; out.macroTileHeight = macroTileHeight; out.microTileThickness = microTileThickness; out.thickMicroTileBytes = microTileBytes; out.numTilesPerRow = microTilesPerRow; out.numTilesPerSlice = microTilesPerSlice; out.bankSwizzle = info.bankSwizzle; out.pipeSwizzle = info.pipeSwizzle; out.bankSwapWidth = bankSwapWidth; return out; } RetileInfo computeRetileInfo(const SurfaceInfo &info) { switch (info.tileMode) { case TileMode::LinearGeneral: case TileMode::LinearAligned: return computeLinearRetileInfo(info); case TileMode::Micro1DTiledThin1: case TileMode::Micro1DTiledThick: return computeMicroRetileInfo(info); case TileMode::Macro2DTiledThin1: case TileMode::Macro2DTiledThin2: case TileMode::Macro2DTiledThin4: case TileMode::Macro2DTiledThick: case TileMode::Macro2BTiledThin1: case TileMode::Macro2BTiledThin2: case TileMode::Macro2BTiledThin4: case TileMode::Macro2BTiledThick: case TileMode::Macro3DTiledThin1: case TileMode::Macro3DTiledThick: case TileMode::Macro3BTiledThin1: case TileMode::Macro3BTiledThick: return computeMacroRetileInfo(info); default: decaf_abort("Invalid tile mode"); } } } // namespace gpu7::tiling ================================================ FILE: src/libgpu/src/gpu7_tiling_cpu.cpp ================================================ #include "gpu7_tiling_cpu.h" #include <common/align.h> #include <common/decaf_assert.h> #include <cstring> namespace gpu7::tiling::cpu { template< bool IsUntiling, uint32_t MicroTileThickness, uint32_t MacroTileWidth, uint32_t MacroTileHeight, bool IsMacro3X, bool IsBankSwapped, uint32_t BitsPerElement, bool IsDepth > struct RetileCore { static constexpr bool IsMacroTiling = (MacroTileWidth > 1 || MacroTileHeight > 1); static constexpr uint32_t BytesPerElement = BitsPerElement / 8; static constexpr uint32_t MicroTileBytes = MicroTileWidth * MicroTileHeight * MicroTileThickness * BytesPerElement; static constexpr uint32_t MacroTileBytes = MacroTileWidth * MacroTileHeight * MicroTileBytes; template<uint32_t bytesPerElem> static inline void copyElems(uint8_t *untiled, uint8_t *tiled, size_t numElems) { if (IsUntiling) { std::memcpy(untiled, tiled, bytesPerElem * numElems); } else { std::memcpy(tiled, untiled, bytesPerElem * numElems); } } static inline void retileMicro8(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride) { static constexpr auto tiledStride = MicroTileWidth; static constexpr auto rowElems = MicroTileWidth / 8; for (int y = 0; y < MicroTileHeight; y += 4) { auto untiledRow0 = untiled + 0 * untiledStride; auto untiledRow1 = untiled + 1 * untiledStride; auto untiledRow2 = untiled + 2 * untiledStride; auto untiledRow3 = untiled + 3 * untiledStride; auto tiledRow0 = tiled + 0 * tiledStride; auto tiledRow1 = tiled + 1 * tiledStride; auto tiledRow2 = tiled + 2 * tiledStride; auto tiledRow3 = tiled + 3 * tiledStride; copyElems<8>(untiledRow0, tiledRow0, rowElems); copyElems<8>(untiledRow1, tiledRow2, rowElems); copyElems<8>(untiledRow2, tiledRow1, rowElems); copyElems<8>(untiledRow3, tiledRow3, rowElems); untiled += 4 * untiledStride; tiled += 4 * tiledStride; } } static inline void retileMicro16(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride) { static constexpr auto tiledStride = MicroTileWidth * 2; static constexpr auto rowElems = MicroTileWidth * 2 / 16; for (int y = 0; y < MicroTileHeight; ++y) { copyElems<16>(untiled, tiled, rowElems); untiled += untiledStride; tiled += tiledStride; } } static inline void retileMicro32(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride) { static constexpr auto tiledStride = MicroTileWidth * 4; static constexpr auto groupElems = 4 * 4 / 16; for (int y = 0; y < MicroTileHeight; y += 2) { auto untiledRow1 = untiled + 0 * untiledStride; auto untiledRow2 = untiled + 1 * untiledStride; auto tiledRow1 = tiled + 0 * tiledStride; auto tiledRow2 = tiled + 1 * tiledStride; copyElems<16>(untiledRow1 + 0, tiledRow1 + 0, groupElems); copyElems<16>(untiledRow1 + 16, tiledRow2 + 0, groupElems); copyElems<16>(untiledRow2 + 0, tiledRow1 + 16, groupElems); copyElems<16>(untiledRow2 + 16, tiledRow2 + 16, groupElems); tiled += tiledStride * 2; untiled += untiledStride * 2; } } static inline void retileMicro64(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride) { static constexpr auto tiledStride = MicroTileWidth * 8; static constexpr auto groupElems = 2 * (64 / 8) / 16; for (int y = 0; y < MicroTileHeight; y += 2) { if constexpr (IsMacroTiling) { if (y == 4) { // At y == 4 we hit the next group (at element offset 256) tiled -= tiledStride * y; tiled += 0x100 << (NumBankBits + NumPipeBits); } } auto untiledRow1 = untiled + 0 * untiledStride; auto untiledRow2 = untiled + 1 * untiledStride; auto tiledRow1 = tiled + 0 * tiledStride; auto tiledRow2 = tiled + 1 * tiledStride; copyElems<16>(untiledRow1 + 0, tiledRow1 + 0, groupElems); copyElems<16>(untiledRow2 + 0, tiledRow1 + 16, groupElems); copyElems<16>(untiledRow1 + 16, tiledRow1 + 32, groupElems); copyElems<16>(untiledRow2 + 16, tiledRow1 + 48, groupElems); copyElems<16>(untiledRow1 + 32, tiledRow2 + 0, groupElems); copyElems<16>(untiledRow2 + 32, tiledRow2 + 16, groupElems); copyElems<16>(untiledRow1 + 48, tiledRow2 + 32, groupElems); copyElems<16>(untiledRow2 + 48, tiledRow2 + 48, groupElems); tiled += tiledStride * 2; untiled += untiledStride * 2; } } static inline void retileMicro128(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride) { static constexpr auto tiledStride = MicroTileWidth * 16; static constexpr auto groupBytes = 16; static constexpr auto groupElems = 16 / 16; for (int y = 0; y < MicroTileHeight; y += 2) { auto untiledRow1 = untiled + 0 * untiledStride; auto untiledRow2 = untiled + 1 * untiledStride; auto tiledRow1 = tiled + 0 * tiledStride; auto tiledRow2 = tiled + 1 * tiledStride; copyElems<16>(untiledRow1 + 0 * groupBytes, tiledRow1 + 0 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 1 * groupBytes, tiledRow1 + 2 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 0 * groupBytes, tiledRow1 + 1 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 1 * groupBytes, tiledRow1 + 3 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 2 * groupBytes, tiledRow1 + 4 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 3 * groupBytes, tiledRow1 + 6 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 2 * groupBytes, tiledRow1 + 5 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 3 * groupBytes, tiledRow1 + 7 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 4 * groupBytes, tiledRow2 + 0 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 5 * groupBytes, tiledRow2 + 2 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 4 * groupBytes, tiledRow2 + 1 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 5 * groupBytes, tiledRow2 + 3 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 6 * groupBytes, tiledRow2 + 4 * groupBytes, groupElems); copyElems<16>(untiledRow1 + 7 * groupBytes, tiledRow2 + 6 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 6 * groupBytes, tiledRow2 + 5 * groupBytes, groupElems); copyElems<16>(untiledRow2 + 7 * groupBytes, tiledRow2 + 7 * groupBytes, groupElems); if (IsMacroTiling) { tiled += 0x100 << (NumBankBits + NumPipeBits); } else { tiled += tiledStride * 2; } untiled += untiledStride * 2; } } static inline void copyDepthXYGroup(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride, uint32_t tX, uint32_t tY, uint32_t uX, uint32_t uY) { static constexpr auto groupBytes = 2 * BytesPerElement; static constexpr auto groupElems = 2 * BytesPerElement / 4; static constexpr auto tiledStride = MicroTileWidth * BytesPerElement; copyElems<4>(untiled + uY * untiledStride + uX * groupBytes, tiled + tY * tiledStride + tX * groupBytes, groupElems); } static inline void retileMicroDepth(uint8_t *tiled, uint8_t *untiled, uint32_t untiledStride) { for (int y = 0; y < MicroTileHeight; y += 4) { copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 0, 0, y + 0); copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 0, 0, y + 1); copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 0, 1, y + 0); copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 0, 1, y + 1); copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 1, 0, y + 2); copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 1, 0, y + 3); copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 1, 1, y + 2); copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 1, 1, y + 3); copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 2, 2, y + 0); copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 2, 2, y + 1); copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 2, 3, y + 0); copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 2, 3, y + 1); copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 3, 2, y + 2); copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 3, 2, y + 3); copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 3, 3, y + 2); copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 3, 3, y + 3); } } struct Params { uint32_t firstSliceIndex; // Micro tiling parameters uint32_t numTilesPerRow; uint32_t numTilesPerSlice; uint32_t thinMicroTileBytes; uint32_t thickSliceBytes; // Macro tiling parameters uint32_t bankSwizzle; uint32_t pipeSwizzle; uint32_t bankSwapWidth; }; /* We always execute in a problem-space which starts at a thick-slice boundary. The tiled pointer will point to the start of that boundary and the untiled pointer will point to the start of a specific slice within it (if untiling on a not-thick-slice-aligned boundary). */ static inline void retileMicro(const Params& params, uint32_t tileIndex, uint8_t *untiled, uint8_t *tiled) { const uint32_t thinSliceBytes = params.thickSliceBytes / MicroTileThickness; const uint32_t untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement; const uint32_t thickMicroTileBytes = params.thinMicroTileBytes * MicroTileThickness; const uint32_t dispatchSliceIndex = tileIndex / params.numTilesPerSlice; const uint32_t sliceTileIndex = tileIndex % params.numTilesPerSlice; // Find the global slice index we are currently at. const uint32_t srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex; // We need to identify where inside the current thick slice we are. const uint32_t localSliceIndex = srcSliceIndex % MicroTileThickness; // Calculate the offset to our untiled data starting from the thick slice const uint32_t srcTileY = sliceTileIndex / params.numTilesPerRow; const uint32_t srcTileX = sliceTileIndex % params.numTilesPerRow; uint32_t untiledOffset = (localSliceIndex * thinSliceBytes) + (srcTileX * MicroTileWidth * BytesPerElement) + (srcTileY * params.numTilesPerRow * params.thinMicroTileBytes); // Calculate the offset to our tiled data starting from the thick slice uint32_t tiledOffset = (localSliceIndex * params.thinMicroTileBytes) + sliceTileIndex * thickMicroTileBytes; // In the case that we are using thick micro tiles, we need to advance our // offsets to the current thick slice boundary that we are at. const uint32_t firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness; const uint32_t thickSliceIndex = srcSliceIndex / MicroTileThickness; const uint32_t thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes; tiledOffset += thickSliceOffset; untiledOffset += thickSliceOffset; // The untiled pointers are offset forward by the local slice index already, // we need to back it up since our calculations above consider it. const uint32_t firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness; untiledOffset -= firstThinSliceIndex * thinSliceBytes; // Update our pointers based on the calculated offset. tiled = tiled + tiledOffset; untiled = untiled + untiledOffset; if constexpr (IsDepth) { retileMicroDepth(tiled, untiled, untiledStride); } else { if constexpr (BitsPerElement == 8) { retileMicro8(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 16) { retileMicro16(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 32) { retileMicro32(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 64) { retileMicro64(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 128) { retileMicro128(tiled, untiled, untiledStride); } } } static inline void retileMacro(const Params& params, uint32_t tileIndex, uint8_t *untiled, uint8_t *tiled) { const uint32_t thinSliceBytes = params.thickSliceBytes / MicroTileThickness; const uint32_t untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement; const uint32_t dispatchSliceIndex = tileIndex / params.numTilesPerSlice; const uint32_t sliceTileIndex = tileIndex % params.numTilesPerSlice; // Find the global slice index we are currently at. const uint32_t srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex; // We need to identify where inside the current thick slice we are. const uint32_t localSliceIndex = srcSliceIndex % MicroTileThickness; // Calculate the thickSliceIndex const uint32_t thickSliceIndex = srcSliceIndex / MicroTileThickness; // Calculate our tile positions const uint32_t microTilesPerMacro = MacroTileWidth * MacroTileHeight; const uint32_t macroTilesPerRow = params.numTilesPerRow / MacroTileWidth; const uint32_t microTilesPerMacroRow = microTilesPerMacro * macroTilesPerRow; const uint32_t srcMacroTileY = sliceTileIndex / microTilesPerMacroRow; const uint32_t macroRowTileIndex = sliceTileIndex % microTilesPerMacroRow; const uint32_t srcMacroTileX = macroRowTileIndex / microTilesPerMacro; const uint32_t microTileIndex = macroRowTileIndex % microTilesPerMacro; const uint32_t srcMicroTileY = microTileIndex / MacroTileWidth; const uint32_t srcMicroTileX = microTileIndex % MacroTileWidth; const uint32_t srcTileX = srcMacroTileX * MacroTileWidth + srcMicroTileX; const uint32_t srcTileY = srcMacroTileY * MacroTileHeight + srcMicroTileY; // Figure out what our untiled offset shall be uint32_t untiledOffset = (localSliceIndex * thinSliceBytes) + (srcTileX * MicroTileWidth * BytesPerElement) + (srcTileY * MicroTileHeight * untiledStride); // Calculate the offset to our untiled data starting from the thick slice const uint32_t macroTileIndex = (srcMacroTileY * macroTilesPerRow) + srcMacroTileX; const uint32_t macroTileOffset = macroTileIndex * MacroTileBytes; const uint32_t tiledBaseOffset = (macroTileOffset >> (NumBankBits + NumPipeBits)) + (localSliceIndex * params.thinMicroTileBytes); const uint32_t offsetHigh = (tiledBaseOffset & ~GroupMask) << (NumBankBits + NumPipeBits); const uint32_t offsetLow = tiledBaseOffset & GroupMask; // Calculate our bank/pipe/sample rotations and swaps uint32_t bankSliceRotation = 0; uint32_t pipeSliceRotation = 0; if (!IsMacro3X) { // 2_ format bankSliceRotation = ((NumBanks >> 1) - 1)* thickSliceIndex; } else { // 3_ format bankSliceRotation = thickSliceIndex / NumPipes; pipeSliceRotation = thickSliceIndex; } uint32_t bankSwapRotation = 0; if (IsBankSwapped) { const uint32_t bankSwapOrder[] = { 0, 1, 3, 2 }; const uint32_t swapIndex = ((srcMacroTileX * MicroTileWidth * MacroTileWidth) / params.bankSwapWidth); bankSwapRotation = bankSwapOrder[swapIndex % NumBanks]; } uint32_t bank = 0; bank |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 2) & 1); bank |= (((srcTileX >> 1) & 1) ^ ((srcTileY >> 1) & 1)) << 1; bank ^= (params.bankSwizzle + bankSliceRotation) & (NumBanks - 1); bank ^= bankSwapRotation; uint32_t pipe = 0; pipe |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 0) & 1); pipe ^= (params.pipeSwizzle + pipeSliceRotation) & (NumPipes - 1); uint32_t tiledOffset = (bank << (NumGroupBits + NumPipeBits)) | (pipe << NumGroupBits) | offsetLow | offsetHigh; // In the case that we are using thick micro tiles, we need to advance our // offsets to the current thick slice boundary that we are at. const uint32_t firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness; const uint32_t thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes; tiledOffset += thickSliceOffset; untiledOffset += thickSliceOffset; // The untiled pointers are offset forward by the local slice index already, // we need to back it up since our calculations above consider it. const uint32_t firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness; untiledOffset -= firstThinSliceIndex * thinSliceBytes; // Update our pointers based on the calculated offset. tiled = tiled + tiledOffset; untiled = untiled + untiledOffset; if constexpr (IsDepth) { retileMicroDepth(tiled, untiled, untiledStride); } else { if constexpr (BitsPerElement == 8) { retileMicro8(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 16) { retileMicro16(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 32) { retileMicro32(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 64) { retileMicro64(tiled, untiled, untiledStride); } else if constexpr (BitsPerElement == 128) { retileMicro128(tiled, untiled, untiledStride); } } } static inline void retile(const Params& params, uint32_t tileIndex, uint8_t *untiled, uint8_t *tiled) { if constexpr (IsMacroTiling) { retileMacro(params, tileIndex, untiled, tiled); } else { retileMicro(params, tileIndex, untiled, tiled); } } }; template<bool IsUntiling, TileMode RetileMode, uint32_t BitsPerElement, bool IsDepth> static inline void retileTiledSurface3(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { using Retiler = RetileCore< IsUntiling, getMicroTileThickness(RetileMode), getMacroTileWidth(RetileMode), getMacroTileHeight(RetileMode), getTileModeIs3X(RetileMode), getTileModeIsBankSwapped(RetileMode), BitsPerElement, IsDepth>; typename Retiler::Params params; params.firstSliceIndex = firstSlice; params.numTilesPerRow = info.numTilesPerRow; params.numTilesPerSlice = info.numTilesPerSlice; params.thinMicroTileBytes = info.thickMicroTileBytes / info.microTileThickness; params.thickSliceBytes = info.thinSliceBytes * info.microTileThickness; params.bankSwizzle = info.bankSwizzle; params.pipeSwizzle = info.pipeSwizzle; params.bankSwapWidth = info.bankSwapWidth; uint32_t numTiles = numSlices * info.numTilesPerSlice; for (auto tileIndex = 0u; tileIndex < numTiles; ++tileIndex) { Retiler::retile(params, tileIndex, untiled, tiled); } } template<bool IsUntiling, TileMode RetileMode> static inline void retileTiledSurface2(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { if (!info.isDepth) { if (info.bitsPerElement == 8) { retileTiledSurface3<IsUntiling, RetileMode, 8, false>(info, untiled, tiled, firstSlice, numSlices); } else if (info.bitsPerElement == 16) { retileTiledSurface3<IsUntiling, RetileMode, 16, false>(info, untiled, tiled, firstSlice, numSlices); } else if (info.bitsPerElement == 32) { retileTiledSurface3<IsUntiling, RetileMode, 32, false>(info, untiled, tiled, firstSlice, numSlices); } else if (info.bitsPerElement == 64) { retileTiledSurface3<IsUntiling, RetileMode, 64, false>(info, untiled, tiled, firstSlice, numSlices); } else if (info.bitsPerElement == 128) { retileTiledSurface3<IsUntiling, RetileMode, 128, false>(info, untiled, tiled, firstSlice, numSlices); } else { decaf_abort("Invalid color surface bpp"); } } else { if (info.bitsPerElement == 16) { retileTiledSurface3<IsUntiling, RetileMode, 16, true>(info, untiled, tiled, firstSlice, numSlices); } else if (info.bitsPerElement == 32) { retileTiledSurface3<IsUntiling, RetileMode, 32, true>(info, untiled, tiled, firstSlice, numSlices); } else if (info.bitsPerElement == 64) { retileTiledSurface3<IsUntiling, RetileMode, 64, true>(info, untiled, tiled, firstSlice, numSlices); } else { decaf_abort("Invalid depth surface bpp"); } } } template<bool IsUntiling> static inline void retileTiledSurface(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { switch (info.tileMode) { case TileMode::Micro1DTiledThin1: retileTiledSurface2<IsUntiling, TileMode::Micro1DTiledThin1>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Micro1DTiledThick: retileTiledSurface2<IsUntiling, TileMode::Micro1DTiledThick>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2DTiledThin1: retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThin1>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2DTiledThin2: retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThin2>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2DTiledThin4: retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThin4>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2DTiledThick: retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThick>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2BTiledThin1: retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThin1>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2BTiledThin2: retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThin2>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2BTiledThin4: retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThin4>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro2BTiledThick: retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThick>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro3DTiledThin1: retileTiledSurface2<IsUntiling, TileMode::Macro3DTiledThin1>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro3DTiledThick: retileTiledSurface2<IsUntiling, TileMode::Macro3DTiledThick>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro3BTiledThin1: retileTiledSurface2<IsUntiling, TileMode::Macro3BTiledThin1>(info, untiled, tiled, firstSlice, numSlices); break; case TileMode::Macro3BTiledThick: retileTiledSurface2<IsUntiling, TileMode::Macro3BTiledThick>(info, untiled, tiled, firstSlice, numSlices); break; default: decaf_abort("Unexpected tiled tile mode"); } } template<bool IsUntiling> static void retileLinearSurface(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { // These functions assume that the tiled side is aligned to a thick slice // boundary, since this is linear, we just pull that pointer forward. uint32_t thinSliceAdjust = firstSlice % info.microTileThickness; uint32_t thinMicroTileBytes = info.thickMicroTileBytes / info.microTileThickness; tiled += thinSliceAdjust * info.numTilesPerSlice * thinMicroTileBytes; // Calculate the total number of tiles to copy. uint32_t totalTiles = numSlices * info.numTilesPerSlice; // Copy all the bytes from one place to the other. uint32_t totalBytes = totalTiles * thinMicroTileBytes; if (IsUntiling) { std::memcpy(untiled, tiled, totalBytes); } else { std::memcpy(tiled, untiled, totalBytes); } } template<bool IsUntiling> static inline void retile(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { if (info.tileMode == TileMode::LinearGeneral || info.tileMode == TileMode::LinearAligned) { return retileLinearSurface<IsUntiling>(info, untiled, tiled, firstSlice, numSlices); } retileTiledSurface<IsUntiling>(info, untiled, tiled, firstSlice, numSlices); } void untile(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { retile<true>(info, untiled, tiled, firstSlice, numSlices); } void tile(const RetileInfo& info, uint8_t *untiled, uint8_t *tiled, uint32_t firstSlice, uint32_t numSlices) { retile<false>(info, untiled, tiled, firstSlice, numSlices); } } // namespace gpu::tiling::cpu ================================================ FILE: src/libgpu/src/gpu7_tiling_vulkan.cpp ================================================ #ifdef DECAF_VULKAN #include "gpu7_tiling_vulkan.h" #include <common/align.h> #include <common/decaf_assert.h> #include <vulkan_shaders_bin/gpu7_tiling.comp.spv.h> namespace gpu7::tiling::vulkan { struct GeneralPushConstants { uint32_t firstSliceIndex; uint32_t maxTiles; }; struct MicroTilePushConstants : GeneralPushConstants { uint32_t numTilesPerRow; uint32_t numTilesPerSlice; uint32_t thinMicroTileBytes; uint32_t thickSliceBytes; }; struct MacroTilePushConstants : MicroTilePushConstants { uint32_t bankSwizzle; uint32_t pipeSwizzle; uint32_t bankSwapWidth; }; struct TileShaderSpecialisation { uint32_t isUntiling; uint32_t microTileThickness; uint32_t macroTileWidth; uint32_t macroTileHeight; uint32_t isMacro3X; uint32_t isBankSwapped; uint32_t bpp; uint32_t isDepth; uint32_t subGroupSize; }; struct BppDepthInfo { uint32_t bpp; bool isDepth; }; std::array<BppDepthInfo, 8> ValidBppDepths = { { { 8, false }, { 16, false }, { 32, false }, { 64, false }, { 128, false }, { 16, true }, { 32, true }, { 64, true } } }; struct TileModeInfo { TileMode tileMode; uint32_t microTileThickness; uint32_t macroTileWidth; uint32_t macroTileHeight; bool isMacro3X; bool isBankSwapped; }; std::array<TileModeInfo, 14> ValidTileConfigs = { { { TileMode::Micro1DTiledThin1, 1, 1, 1, false, false }, { TileMode::Micro1DTiledThick, 4, 1, 1, false, false }, { TileMode::Macro2DTiledThin1, 1, 4, 2, false, false }, { TileMode::Macro2DTiledThin2, 1, 2, 4, false, false }, { TileMode::Macro2DTiledThin4, 1, 1, 8, false, false }, { TileMode::Macro2DTiledThick, 4, 4, 2, false, false }, { TileMode::Macro2BTiledThin1, 1, 4, 2, false, true }, { TileMode::Macro2BTiledThin2, 1, 2, 4, false, true }, { TileMode::Macro2BTiledThin4, 1, 1, 8, false, true }, { TileMode::Macro2BTiledThick, 4, 4, 2, false, true }, { TileMode::Macro3DTiledThin1, 1, 4, 2, true, false }, { TileMode::Macro3DTiledThick, 4, 4, 2, true, false }, { TileMode::Macro3BTiledThin1, 1, 4, 2, true, true }, { TileMode::Macro3BTiledThick, 4, 4, 2, true, true }, } }; // TODO: This should be based on the GPU in use static const uint32_t GpuSubGroupSize = 32; static inline uint32_t getRetileSpecKey(uint32_t bpp, bool isDepth, TileMode tileMode, bool isUntiling) { uint32_t fmtKey = bpp + (isDepth ? 100 : 0); uint32_t tileModeKey = static_cast<uint32_t>(tileMode); uint32_t isUntilingKey = isUntiling ? 1 : 0; uint32_t key = 0; key |= (fmtKey << 0) & 0x0000FFFF; key |= (tileModeKey << 16) & 0x0FFF0000; key |= (isUntilingKey << 28) & 0xF0000000; return key; } void Retiler::initialise(vk::Device device) { mDevice = device; std::array<vk::DescriptorSetLayoutBinding, 2> descriptorSetLayoutBinding = {}; descriptorSetLayoutBinding[0].binding = 0; descriptorSetLayoutBinding[0].descriptorType = vk::DescriptorType::eStorageBuffer; descriptorSetLayoutBinding[0].descriptorCount = 1; descriptorSetLayoutBinding[0].stageFlags = vk::ShaderStageFlagBits::eCompute; descriptorSetLayoutBinding[1].binding = 1; descriptorSetLayoutBinding[1].descriptorType = vk::DescriptorType::eStorageBuffer; descriptorSetLayoutBinding[1].descriptorCount = 1; descriptorSetLayoutBinding[1].stageFlags = vk::ShaderStageFlagBits::eCompute; vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {}; descriptorSetLayoutCreateInfo.bindingCount = static_cast<uint32_t>(descriptorSetLayoutBinding.size()); descriptorSetLayoutCreateInfo.pBindings = descriptorSetLayoutBinding.data(); mDescriptorSetLayout = mDevice.createDescriptorSetLayout(descriptorSetLayoutCreateInfo); // We create a push constant block that is the size of the largest push // constants block we could use, saves us from having multiple layouts. vk::PushConstantRange pushConstant = {}; pushConstant.stageFlags = vk::ShaderStageFlagBits::eCompute; pushConstant.offset = 0; pushConstant.size = sizeof(MacroTilePushConstants); vk::PipelineLayoutCreateInfo pipelineLayoutDesc; pipelineLayoutDesc.setLayoutCount = 1; pipelineLayoutDesc.pSetLayouts = &mDescriptorSetLayout; pipelineLayoutDesc.pushConstantRangeCount = 1; pipelineLayoutDesc.pPushConstantRanges = &pushConstant; mPipelineLayout = mDevice.createPipelineLayout(pipelineLayoutDesc); vk::ShaderModuleCreateInfo shaderDesc; shaderDesc.pCode = reinterpret_cast<const uint32_t*>(gpu7_tiling_comp_spv); shaderDesc.codeSize = gpu7_tiling_comp_spv_size; mShader = mDevice.createShaderModule(shaderDesc); static const std::array<vk::SpecializationMapEntry, 9> specEntries = { { { 0, offsetof(TileShaderSpecialisation, isUntiling), sizeof(uint32_t) }, { 1, offsetof(TileShaderSpecialisation, microTileThickness), sizeof(uint32_t) }, { 2, offsetof(TileShaderSpecialisation, macroTileWidth), sizeof(uint32_t) }, { 3, offsetof(TileShaderSpecialisation, macroTileHeight), sizeof(uint32_t) }, { 4, offsetof(TileShaderSpecialisation, isMacro3X), sizeof(uint32_t) }, { 5, offsetof(TileShaderSpecialisation, isBankSwapped), sizeof(uint32_t) }, { 6, offsetof(TileShaderSpecialisation, bpp), sizeof(uint32_t) }, { 7, offsetof(TileShaderSpecialisation, isDepth), sizeof(uint32_t) }, { 8, offsetof(TileShaderSpecialisation, subGroupSize), sizeof(uint32_t) } } }; for (auto tileConfig : ValidTileConfigs) { for (auto bppDepth : ValidBppDepths) { for (auto tileOrUntile : { true, false }) { uint32_t specKey = getRetileSpecKey(bppDepth.bpp, bppDepth.isDepth, tileConfig.tileMode, tileOrUntile); TileShaderSpecialisation specValues; specValues.isUntiling = tileOrUntile ? 1 : 0; specValues.microTileThickness = tileConfig.microTileThickness; specValues.macroTileWidth = tileConfig.macroTileWidth; specValues.macroTileHeight = tileConfig.macroTileHeight; specValues.isMacro3X = tileConfig.isMacro3X ? 1 : 0; specValues.isBankSwapped = tileConfig.isBankSwapped ? 1 : 0; specValues.bpp = bppDepth.bpp; specValues.isDepth = bppDepth.isDepth ? 1 : 0; specValues.subGroupSize = GpuSubGroupSize; vk::SpecializationInfo specInfo; specInfo.mapEntryCount = static_cast<uint32_t>(specEntries.size()); specInfo.pMapEntries = specEntries.data(); specInfo.dataSize = sizeof(TileShaderSpecialisation); specInfo.pData = &specValues; vk::PipelineShaderStageCreateInfo shaderStageDesc = {}; shaderStageDesc.stage = vk::ShaderStageFlagBits::eCompute; shaderStageDesc.module = mShader; shaderStageDesc.pName = "main"; shaderStageDesc.pSpecializationInfo = &specInfo; vk::ComputePipelineCreateInfo pipelineDesc = {}; pipelineDesc.stage = shaderStageDesc; pipelineDesc.layout = mPipelineLayout; auto pipeline = mDevice.createComputePipeline(vk::PipelineCache(), pipelineDesc); mPipelines.insert({ specKey, pipeline.value }); } } } } /* When retiling THICK tile modes, this algorithm assumes that you've aligned the tiled buffer to the edge of a group of 4 slices and the untiled buffer directly to the slice. */ void Retiler::retile(bool wantsUntile, const RetileInfo& retileInfo, vk::DescriptorSet& descriptorSet, vk::CommandBuffer& commandBuffer, vk::Buffer tiledBuffer, uint32_t tiledOffset, vk::Buffer untiledBuffer, uint32_t untiledOffset, uint32_t firstSlice, uint32_t numSlices) { // Would be odd to dispatch a retile when we are not tiled... decaf_check(retileInfo.isTiled); // Calcualate the spec key for this retiler configuration // Due to know known issues with depth, we instead retile it normally. auto specKey = getRetileSpecKey(retileInfo.bitsPerElement, retileInfo.isDepth, retileInfo.tileMode, wantsUntile); // Find the specific pipeline for this configuration auto pipelineIter = mPipelines.find(specKey); if (pipelineIter == mPipelines.end()) { decaf_abort("Attempted to retile an unsupported surface configuration"); } // Bind the pipeline for execution commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipelineIter->second); // Calculate the sizing for our retiling space. Note that we have to make sure // we are correctly aligning the location when the user is doing partial groups // of slices on thickness>1 auto alignedFirstSlice = align_down(firstSlice, retileInfo.microTileThickness); auto alignedLastSlice = align_up(firstSlice + numSlices, retileInfo.microTileThickness); auto alignedNumSlices = alignedLastSlice - alignedFirstSlice; uint32_t tiledSize = alignedNumSlices * retileInfo.thinSliceBytes; uint32_t untiledSize = numSlices * retileInfo.thinSliceBytes; std::array<vk::DescriptorBufferInfo, 2> descriptorBufferDescs; descriptorBufferDescs[0].buffer = tiledBuffer; descriptorBufferDescs[0].offset = tiledOffset; descriptorBufferDescs[0].range = tiledSize; descriptorBufferDescs[1].buffer = untiledBuffer; descriptorBufferDescs[1].offset = untiledOffset; descriptorBufferDescs[1].range = untiledSize; vk::WriteDescriptorSet setWriteDesc; setWriteDesc.dstSet = descriptorSet; setWriteDesc.dstBinding = 0; setWriteDesc.descriptorCount = static_cast<uint32_t>(descriptorBufferDescs.size()); setWriteDesc.descriptorType = vk::DescriptorType::eStorageBuffer; setWriteDesc.pBufferInfo = descriptorBufferDescs.data(); mDevice.updateDescriptorSets({ setWriteDesc }, {}); // Bind our new descriptor set commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, mPipelineLayout, 0, { descriptorSet }, {}); // Calculate the number of tiles we need to process! uint32_t numDispatchTiles = numSlices * retileInfo.numTilesPerSlice; // Setup our arguments if (!retileInfo.isMacroTiled) { MicroTilePushConstants pushConstants; pushConstants.firstSliceIndex = firstSlice; pushConstants.maxTiles = numDispatchTiles; pushConstants.numTilesPerRow = retileInfo.numTilesPerRow; pushConstants.numTilesPerSlice = retileInfo.numTilesPerSlice; pushConstants.thinMicroTileBytes = retileInfo.thickMicroTileBytes / retileInfo.microTileThickness; pushConstants.thickSliceBytes = retileInfo.thinSliceBytes * retileInfo.microTileThickness; commandBuffer.pushConstants<MicroTilePushConstants>( mPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, { pushConstants }); } else { MacroTilePushConstants pushConstants; pushConstants.firstSliceIndex = firstSlice; pushConstants.maxTiles = numDispatchTiles; pushConstants.numTilesPerRow = retileInfo.numTilesPerRow; pushConstants.numTilesPerSlice = retileInfo.numTilesPerSlice; pushConstants.thinMicroTileBytes = retileInfo.thickMicroTileBytes / retileInfo.microTileThickness; pushConstants.thickSliceBytes = retileInfo.thinSliceBytes * retileInfo.microTileThickness; pushConstants.bankSwizzle = retileInfo.bankSwizzle; pushConstants.pipeSwizzle = retileInfo.pipeSwizzle; pushConstants.bankSwapWidth = retileInfo.bankSwapWidth; commandBuffer.pushConstants<MacroTilePushConstants>( mPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, { pushConstants }); } // Calculate the number of groups we need to dispatch! uint32_t numDispatchGroups = align_up(numDispatchTiles, GpuSubGroupSize) / GpuSubGroupSize; // Actually dispatch the work to the GPU commandBuffer.dispatch(numDispatchGroups, 1, 1); } RetileHandle Retiler::retile(bool wantsUntile, const RetileInfo& retileInfo, vk::CommandBuffer& commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices) { auto handle = allocateHandle(); vk::DescriptorSetAllocateInfo allocInfo; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &mDescriptorSetLayout; allocInfo.descriptorPool = handle->descriptorPool; auto descriptorSets = mDevice.allocateDescriptorSets(allocInfo); retile(wantsUntile, retileInfo, descriptorSets[0], commandBuffer, dstBuffer, dstOffset, srcBuffer, srcOffset, firstSlice, numSlices); return handle; } Retiler::HandleImpl* Retiler::allocateHandle() { Retiler::HandleImpl* handle = nullptr; if (!mHandlesPool.empty()) { handle = mHandlesPool.back(); mHandlesPool.pop_back(); } if (!handle) { std::vector<vk::DescriptorPoolSize> descriptorPoolSizes = { vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 2 * 13), }; vk::DescriptorPoolCreateInfo descriptorPoolInfo; descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size()); descriptorPoolInfo.pPoolSizes = descriptorPoolSizes.data(); descriptorPoolInfo.maxSets = static_cast<uint32_t>(13); auto descriptorPool = mDevice.createDescriptorPool(descriptorPoolInfo); handle = new Retiler::HandleImpl(); handle->descriptorPool = descriptorPool; } return handle; } void Retiler::releaseHandle(Retiler::HandleImpl* handle) { mDevice.resetDescriptorPool(handle->descriptorPool); mHandlesPool.push_back(handle); } } #endif // DECAF_VULKAN ================================================ FILE: src/libgpu/src/gpu_clock.h ================================================ #pragma once #include <chrono> namespace gpu::clock { using Time = int64_t; inline Time now() { return std::chrono::steady_clock::now().time_since_epoch().count(); } } // namespace gpu::clock ================================================ FILE: src/libgpu/src/gpu_configstorage.cpp ================================================ #include "gpu_config.h" #include "gpu_configstorage.h" #include <common/configstorage.h> namespace gpu { static ConfigStorage<Settings> sSettings; std::shared_ptr<const Settings> config() { return sSettings.get(); } void setConfig(const Settings &settings) { sSettings.set(std::make_shared<Settings>(settings)); } void registerConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener) { sSettings.addListener(listener); } } // namespace gpu ================================================ FILE: src/libgpu/src/gpu_configstorage.h ================================================ #pragma once #include "gpu_config.h" #include <common/configstorage.h> namespace gpu { void registerConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener); } // namespace gpu ================================================ FILE: src/libgpu/src/gpu_event.cpp ================================================ #include "gpu.h" #include "gpu_event.h" namespace gpu { static FlipCallbackFn sFlipCallbackFn = nullptr; void setFlipCallback(FlipCallbackFn callback) { sFlipCallbackFn = callback; } void onFlip() { if (sFlipCallbackFn) { sFlipCallbackFn(); } } } // namespace gpu ================================================ FILE: src/libgpu/src/gpu_event.h ================================================ #pragma once #include <cstdint> namespace gpu { void onFlip(); } // namespace gpu ================================================ FILE: src/libgpu/src/gpu_graphicsdriver.cpp ================================================ #include "gpu_graphicsdriver.h" #include "gpu_config.h" #include "null/null_driver.h" #include "vulkan/vulkan_driver.h" namespace gpu { GraphicsDriver * createGraphicsDriver() { switch (config()->display.backend) { case DisplaySettings::Null: return createGraphicsDriver(GraphicsDriverType::Null); case DisplaySettings::Vulkan: return createGraphicsDriver(GraphicsDriverType::Vulkan); default: return nullptr; } } GraphicsDriver * createGraphicsDriver(GraphicsDriverType type) { switch (type) { case GraphicsDriverType::Null: return new null::Driver{}; #ifdef DECAF_VULKAN case GraphicsDriverType::Vulkan: return new vulkan::Driver{}; #endif default: return nullptr; } } } // namespace gpu ================================================ FILE: src/libgpu/src/gpu_ih.cpp ================================================ #include "gpu_ih.h" #include <atomic> #include <condition_variable> #include <mutex> #include <vector> namespace gpu::ih { static std::vector<Entry> mWriteVector; static std::vector<Entry> mReadVector; static std::mutex sMutex; static InterruptCallbackFn sInterruptCallback = nullptr; static std::atomic<uint32_t> sInterruptControl { 0u }; void write(const Entries &entries) { auto generateInterrupt = false; { std::unique_lock<std::mutex> lock { sMutex }; generateInterrupt = mWriteVector.empty(); mWriteVector.insert(mWriteVector.end(), entries.begin(), entries.end()); } if (generateInterrupt && sInterruptCallback) { sInterruptCallback(); } } Entries read() { std::unique_lock<std::mutex> lock { sMutex }; mReadVector.clear(); mReadVector.swap(mWriteVector); return mReadVector; } /** * Set callback to be called when a GPU interrupt is triggered. */ void setInterruptCallback(InterruptCallbackFn callback) { sInterruptCallback = callback; } void enable(latte::CP_INT_CNTL cntl) { sInterruptControl |= cntl.value; } void disable(latte::CP_INT_CNTL cntl) { sInterruptControl &= ~cntl.value; } } // namespace gpu::ih ================================================ FILE: src/libgpu/src/gpu_ringbuffer.cpp ================================================ #include "gpu_ringbuffer.h" #include <condition_variable> #include <mutex> #include <vector> namespace gpu::ringbuffer { static std::vector<uint32_t> mWriteVector; static std::vector<uint32_t> mReadVector; static std::mutex sMutex; static std::condition_variable sConditionVariable; static bool sPendingWake = false; void write(const Buffer &items) { std::unique_lock<std::mutex> lock { sMutex }; mWriteVector.insert(mWriteVector.end(), items.begin(), items.end()); sConditionVariable.notify_all(); } Buffer read() { std::unique_lock<std::mutex> lock { sMutex }; mReadVector.clear(); mReadVector.swap(mWriteVector); return mReadVector; } bool wait() { std::unique_lock<std::mutex> lock { sMutex }; if (mWriteVector.empty() && !sPendingWake) { sConditionVariable.wait(lock); } sPendingWake = false; return !mWriteVector.empty(); } void wake() { std::unique_lock<std::mutex> lock { sMutex }; sPendingWake = true; sConditionVariable.notify_all(); } } // namespace gpu::ringbuffer ================================================ FILE: src/libgpu/src/gpu_tiling.cpp ================================================ #include "gpu_tiling.h" #include <common/decaf_assert.h> #include <cstdlib> #include <cstring> namespace gpu { static ADDR_HANDLE gAddrLibHandle = nullptr; static void * allocSysMem(const ADDR_ALLOCSYSMEM_INPUT *pInput) { return std::malloc(pInput->sizeInBytes); } static ADDR_E_RETURNCODE freeSysMem(const ADDR_FREESYSMEM_INPUT *pInput) { std::free(pInput->pVirtAddr); return ADDR_OK; } bool initAddrLib() { ADDR_CREATE_INPUT input; ADDR_CREATE_OUTPUT output; std::memset(&input, 0, sizeof(input)); std::memset(&output, 0, sizeof(output)); input.size = sizeof(ADDR_CREATE_INPUT); output.size = sizeof(ADDR_CREATE_OUTPUT); input.chipEngine = CIASICIDGFXENGINE_R600; input.chipFamily = 0x51; input.chipRevision = 71; input.createFlags.fillSizeFields = 1; input.regValue.gbAddrConfig = 0x44902; input.callbacks.allocSysMem = &allocSysMem; input.callbacks.freeSysMem = &freeSysMem; auto result = AddrCreate(&input, &output); if (result != ADDR_OK) { return false; } gAddrLibHandle = output.hLib; return true; } ADDR_HANDLE getAddrLibHandle() { if (!gAddrLibHandle) { initAddrLib(); } return gAddrLibHandle; } static inline void calcSurfaceBankPipeSwizzle(uint32_t swizzle, uint32_t *bankSwizzle, uint32_t *pipeSwizzle) { auto handle = getAddrLibHandle(); ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT input; ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT output; std::memset(&input, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT)); std::memset(&output, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT)); input.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT); output.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT); input.base256b = (swizzle >> 8) & 0xFF; AddrExtractBankPipeSwizzle(handle, &input, &output); *bankSwizzle = output.bankSwizzle; *pipeSwizzle = output.pipeSwizzle; } void alignTiling(latte::SQ_TILE_MODE& tileMode, latte::SQ_DATA_FORMAT& format, uint32_t& swizzle, uint32_t& pitch, uint32_t& width, uint32_t& height, uint32_t& depth, uint32_t& aa, bool& isDepth, uint32_t& bpp) { auto handle = getAddrLibHandle(); // We only partially complete this, knowing that we only need // some information which does not depend on it... ADDR_COMPUTE_SURFACE_INFO_INPUT input; memset(&input, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT)); input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT); input.tileMode = static_cast<AddrTileMode>(tileMode); input.format = static_cast<AddrFormat>(format); input.bpp = bpp; input.width = pitch; input.height = height; input.numSlices = depth; input.numSamples = 1; input.numFrags = 0; input.slice = 0; input.mipLevel = 0; ADDR_COMPUTE_SURFACE_INFO_OUTPUT output; std::memset(&output, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT)); output.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT); auto result = AddrComputeSurfaceInfo(handle, &input, &output); decaf_check(result == ADDR_OK); pitch = output.pitch; height = output.height; } bool copySurfacePixels(uint8_t *dstBasePtr, uint32_t dstWidth, uint32_t dstHeight, ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &dstAddrInput, uint8_t *srcBasePtr, uint32_t srcWidth, uint32_t srcHeight, ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &srcAddrInput) { auto handle = getAddrLibHandle(); decaf_check(srcAddrInput.bpp == dstAddrInput.bpp); auto bpp = dstAddrInput.bpp; auto srcAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { 0 }; srcAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT); auto dstAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { 0 }; dstAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT); for (auto y = 0u; y < dstHeight; ++y) { for (auto x = 0u; x < dstWidth; ++x) { srcAddrInput.x = srcWidth * x / dstWidth; srcAddrInput.y = srcHeight * y / dstHeight; AddrComputeSurfaceAddrFromCoord(handle, &srcAddrInput, &srcAddrOutput); dstAddrInput.x = x; dstAddrInput.y = y; AddrComputeSurfaceAddrFromCoord(handle, &dstAddrInput, &dstAddrOutput); auto src = &srcBasePtr[srcAddrOutput.addr]; auto dst = &dstBasePtr[dstAddrOutput.addr]; std::memcpy(dst, src, bpp / 8); } } return true; } bool convertFromTiled( uint8_t *output, uint32_t outputPitch, uint8_t *input, latte::SQ_TILE_MODE tileMode, uint32_t swizzle, uint32_t pitch, uint32_t width, uint32_t height, uint32_t depth, uint32_t aa, bool isDepth, uint32_t bpp, uint32_t beginSlice, uint32_t endSlice) { if (endSlice == 0) { endSlice = depth; } decaf_check(beginSlice >= 0); decaf_check(endSlice > 0); decaf_check(endSlice <= depth); ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT srcAddrInput; std::memset(&srcAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT)); srcAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); srcAddrInput.bpp = bpp; srcAddrInput.pitch = pitch; srcAddrInput.height = height; srcAddrInput.numSlices = depth; srcAddrInput.numSamples = 1 << aa; srcAddrInput.tileMode = static_cast<AddrTileMode>(tileMode); srcAddrInput.isDepth = isDepth; srcAddrInput.tileBase = 0; srcAddrInput.compBits = 0; srcAddrInput.numFrags = 0; calcSurfaceBankPipeSwizzle(swizzle, &srcAddrInput.bankSwizzle, &srcAddrInput.pipeSwizzle); // Setup dst ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT dstAddrInput; std::memset(&dstAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT)); dstAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); dstAddrInput.bpp = bpp; dstAddrInput.pitch = outputPitch; dstAddrInput.height = height; dstAddrInput.numSlices = depth; dstAddrInput.numSamples = 1; dstAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL; dstAddrInput.isDepth = isDepth; dstAddrInput.tileBase = 0; dstAddrInput.compBits = 0; dstAddrInput.numFrags = 0; dstAddrInput.bankSwizzle = 0; dstAddrInput.pipeSwizzle = 0; // Untiling always takes sample 0 srcAddrInput.sample = 0; dstAddrInput.sample = 0; // Untile all of the slices of this surface for (uint32_t slice = beginSlice; slice < endSlice; ++slice) { srcAddrInput.slice = slice; dstAddrInput.slice = slice; copySurfacePixels( output, width, height, dstAddrInput, input, width, height, srcAddrInput); } return true; } bool convertToTiled( uint8_t *output, uint8_t *input, uint32_t inputPitch, latte::SQ_TILE_MODE tileMode, uint32_t swizzle, uint32_t pitch, uint32_t width, uint32_t height, uint32_t depth, uint32_t aa, bool isDepth, uint32_t bpp, uint32_t beginSlice, uint32_t endSlice) { if (endSlice == 0) { endSlice = depth; } decaf_check(beginSlice >= 0); decaf_check(endSlice > 0); decaf_check(endSlice <= depth); ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT srcAddrInput; std::memset(&srcAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT)); srcAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); srcAddrInput.bpp = bpp; srcAddrInput.pitch = inputPitch; srcAddrInput.height = height; srcAddrInput.numSlices = depth; srcAddrInput.numSamples = 1; srcAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL; srcAddrInput.isDepth = isDepth; srcAddrInput.tileBase = 0; srcAddrInput.compBits = 0; srcAddrInput.numFrags = 0; srcAddrInput.bankSwizzle = 0; srcAddrInput.pipeSwizzle = 0; // Setup dst ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT dstAddrInput; std::memset(&dstAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT)); dstAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); dstAddrInput.bpp = bpp; dstAddrInput.pitch = pitch; dstAddrInput.height = height; dstAddrInput.numSlices = depth; dstAddrInput.numSamples = 1 << aa; dstAddrInput.tileMode = static_cast<AddrTileMode>(tileMode); dstAddrInput.isDepth = isDepth; dstAddrInput.tileBase = 0; dstAddrInput.compBits = 0; dstAddrInput.numFrags = 0; calcSurfaceBankPipeSwizzle(swizzle, &srcAddrInput.bankSwizzle, &srcAddrInput.pipeSwizzle); // Untiling always takes sample 0 srcAddrInput.sample = 0; dstAddrInput.sample = 0; // Untile all of the slices of this surface for (uint32_t slice = beginSlice; slice < endSlice; ++slice) { srcAddrInput.slice = slice; dstAddrInput.slice = slice; copySurfacePixels( output, width, height, dstAddrInput, input, width, height, srcAddrInput); } return true; } } // namespace gpu ================================================ FILE: src/libgpu/src/latte/latte_decoders.h ================================================ #pragma once #include "latte/latte_instructions.h" #include <common/align.h> #include <common/decaf_assert.h> #include <algorithm> #include <fmt/core.h> #include <gsl/gsl-lite.hpp> #include <stdexcept> namespace latte { static constexpr auto InvalidGprIdx = static_cast<uint32_t>(-1); inline bool isTranscendentalOnly(SQ_ALU_FLAGS flags) { if (flags & SQ_ALU_FLAG_VECTOR) { return false; } if (flags & SQ_ALU_FLAG_TRANSCENDENTAL) { return true; } return false; } inline bool isVectorOnly(SQ_ALU_FLAGS flags) { if (flags & SQ_ALU_FLAG_TRANSCENDENTAL) { return false; } if (flags & SQ_ALU_FLAG_VECTOR) { return true; } return false; } inline SQ_ALU_FLAGS getInstructionFlags(const AluInst &inst) { if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { return getInstructionFlags(inst.op2.ALU_INST()); } else { return getInstructionFlags(inst.op3.ALU_INST()); } } inline uint32_t getInstructionNumSrcs(const AluInst &inst) { if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { return getInstructionNumSrcs(inst.op2.ALU_INST()); } else { return getInstructionNumSrcs(inst.op3.ALU_INST()); } } inline bool isTranscendentalOnlyInst(const AluInst &inst) { auto flags = getInstructionFlags(inst); return isTranscendentalOnly(flags); } inline bool isVectorOnlyInst(const AluInst &inst) { auto flags = getInstructionFlags(inst); return isVectorOnly(flags); } inline bool isReductionInst(const AluInst &inst) { auto flags = getInstructionFlags(inst); return !!(flags & SQ_ALU_FLAG_REDUCTION); } struct AluGroup { AluGroup(const latte::AluInst *group) { auto instructionCount = 0u; auto literalCount = 0u; for (instructionCount = 1u; instructionCount <= 5u; ++instructionCount) { auto &inst = group[instructionCount - 1]; auto srcCount = 0u; if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { srcCount = getInstructionNumSrcs(inst.op2.ALU_INST()); } else { srcCount = getInstructionNumSrcs(inst.op3.ALU_INST()); } if (srcCount > 0 && inst.word0.SRC0_SEL() == SQ_ALU_SRC::LITERAL) { literalCount = std::max<unsigned>(literalCount, 1u + inst.word0.SRC0_CHAN()); } if (srcCount > 1 && inst.word0.SRC1_SEL() == SQ_ALU_SRC::LITERAL) { literalCount = std::max<unsigned>(literalCount, 1u + inst.word0.SRC1_CHAN()); } if (srcCount > 2 && inst.op3.SRC2_SEL() == SQ_ALU_SRC::LITERAL) { literalCount = std::max<unsigned>(literalCount, 1u + inst.op3.SRC2_CHAN()); } if (inst.word0.LAST()) { break; } } instructions = gsl::make_span(group, instructionCount); literals = gsl::make_span(reinterpret_cast<const uint32_t *>(group + instructionCount), literalCount); } size_t getNextSlot(size_t slot) { slot += instructions.size(); slot += (literals.size() + 1) / 2; return slot; } gsl::span<const latte::AluInst> instructions; gsl::span<const uint32_t> literals; }; struct AluGroupUnits { SQ_CHAN addInstructionUnit(const latte::AluInst &inst) { SQ_ALU_FLAGS flags; SQ_CHAN unit = inst.word1.DST_CHAN(); if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { flags = getInstructionFlags(inst.op2.ALU_INST()); } else { flags = getInstructionFlags(inst.op3.ALU_INST()); } if (isTranscendentalOnly(flags)) { unit = SQ_CHAN::T; } else if (isVectorOnly(flags)) { unit = unit; } else if (units[unit]) { unit = SQ_CHAN::T; } decaf_assert(!units[unit], fmt::format("Clause instruction unit collision for unit {}", unit)); units[unit] = true; return unit; } bool units[5] = { false, false, false, false, false }; }; struct AluInstructionGroup { std::array<const AluInst*, 5> units; gsl::span<const uint32_t> literals; }; class AluClauseParser { public: AluClauseParser(gsl::span<const AluInst> slots, bool aluInstPreferVector) : mSlots(slots), mAluInstPreferVector(aluInstPreferVector), mIndex(0) { } AluInstructionGroup readOneGroup() { AluInstructionGroup group = { 0 }; auto literalCount = 0; while (true) { auto &inst = mSlots[mIndex++]; // Read some internal data about the instruction uint32_t srcCount = getInstructionNumSrcs(inst); // Calculate the number of literals used by this instruction if (srcCount > 0 && inst.word0.SRC0_SEL() == SQ_ALU_SRC::LITERAL) { literalCount = std::max<uint32_t>(literalCount, 1u + inst.word0.SRC0_CHAN()); } if (srcCount > 1 && inst.word0.SRC1_SEL() == SQ_ALU_SRC::LITERAL) { literalCount = std::max<uint32_t>(literalCount, 1u + inst.word0.SRC1_CHAN()); } if (srcCount > 2 && inst.op3.SRC2_SEL() == SQ_ALU_SRC::LITERAL) { literalCount = std::max<uint32_t>(literalCount, 1u + inst.op3.SRC2_CHAN()); } // This logic comes straight from the reference pages. auto elem = inst.word1.DST_CHAN(); bool isLast = inst.word0.LAST(); bool isTrans; if (isTranscendentalOnlyInst(inst)) { isTrans = true; } else if (isVectorOnlyInst(inst)) { isTrans = false; } else if (group.units[elem] || (!mAluInstPreferVector && isLast)) { isTrans = true; } else { isTrans = false; } if (isTrans) { decaf_check(!group.units[SQ_CHAN::T]); group.units[SQ_CHAN::T] = &inst; } else { decaf_check(!group.units[elem]); group.units[elem] = &inst; } // If this is marked as the last instruction, we are done if (isLast) { break; } } // We decode the literals using subspan to allow GSL to perform proper // span bounds checking for us! if (literalCount > 0) { auto literalSlotCount = align_up(literalCount, 2) / 2; auto literalSpan = mSlots.subspan(mIndex, literalSlotCount); group.literals = gsl::make_span(reinterpret_cast<const uint32_t*>(&literalSpan[0]), literalCount); mIndex += literalSlotCount; } // We place NOP data into all the ALU units that are not used. This // permits us to simplify the logical handling to avoid NULL checks. static const uint32_t NopInstData[] = { 0x00000000, 0x00000d00 }; static const auto NopInstPtr = reinterpret_cast<const AluInst *>(NopInstData); for (auto i = 0; i < 5; ++i) { if (!group.units[i]) { group.units[i] = NopInstPtr; } } return group; } bool isEndOfClause() { decaf_check(mIndex <= mSlots.size()); return mIndex >= mSlots.size(); } private: bool mAluInstPreferVector; size_t mIndex; gsl::span<const AluInst> mSlots; }; enum class GprIndexMode : uint32_t { None, AR_X, AL }; enum class CfileIndexMode : uint32_t { None, AR_X, AR_Y, AR_Z, AR_W, AL }; enum class CbufferIndexMode : uint32_t { None, AL }; enum class VarRefType : uint32_t { UNKNOWN, FLOAT, INT, UINT }; struct GprRef { uint32_t number; GprIndexMode indexMode; inline void next() { number++; } }; struct CfileRef { uint32_t index; CfileIndexMode indexMode; }; struct CbufferRef { uint32_t bufferId; uint32_t index; CbufferIndexMode indexMode; }; struct GprMaskRef { GprRef gpr; std::array<SQ_SEL, 4> mask; }; struct GprSelRef { GprRef gpr; SQ_SEL sel; }; struct GprChanRef { GprRef gpr; SQ_CHAN chan; }; struct CfileChanRef { CfileRef cfile; SQ_CHAN chan; }; struct CbufferChanRef { CbufferRef cbuffer; SQ_CHAN chan; }; struct PrevValRef { SQ_CHAN unit; }; struct ValueRef { union { int32_t intValue; uint32_t uintValue; float floatValue; }; }; struct ExportRef { enum class Type : uint32_t { Position, Param, Pixel, PixelWithFog, ComputedZ, Stream0Write, Stream1Write, Stream2Write, Stream3Write, VsGsRingWrite, GsDcRingWrite }; Type type; uint32_t dataStride; uint32_t elemCount; uint32_t arrayBase; uint32_t arraySize; uint32_t indexGpr; inline void next() { // Normal exports have a specific behaviour that they increment // by one and have a elemCount of 1, even though they write vec4's if (type < Type::Stream0Write) { decaf_check(elemCount == 1); } arrayBase += elemCount; } }; struct ExportMaskRef { ExportRef output; std::array<SQ_SEL, 4> mask; }; struct SrcVarRef { enum class Type : uint32_t { GPR, CBUFFER, CFILE, PREVRES, VALUE }; Type type; union { GprChanRef gprChan; CbufferChanRef cbufferChan; CfileChanRef cfileChan; PrevValRef prevres; ValueRef value; }; VarRefType valueType; bool isAbsolute; bool isNegated; }; inline GprRef makeGprRef(uint32_t gprNum, SQ_REL rel = SQ_REL::ABS, SQ_INDEX_MODE indexMode = SQ_INDEX_MODE::AR_X) { GprRef gpr; gpr.number = gprNum; if (rel == SQ_REL::REL) { switch (indexMode) { case SQ_INDEX_MODE::AR_X: case SQ_INDEX_MODE::AR_Y: case SQ_INDEX_MODE::AR_Z: case SQ_INDEX_MODE::AR_W: gpr.indexMode = GprIndexMode::AR_X; break; case SQ_INDEX_MODE::LOOP: gpr.indexMode = GprIndexMode::AL; break; default: decaf_abort("Unexpected GPR index mode"); } } else { gpr.indexMode = GprIndexMode::None; } return gpr; } inline CfileRef makeCfileRef(uint32_t offset, SQ_REL rel, SQ_INDEX_MODE indexMode) { CfileRef cfile; cfile.index = offset; if (rel == SQ_REL::REL) { switch (indexMode) { case SQ_INDEX_MODE::AR_X: cfile.indexMode = CfileIndexMode::AR_X; break; case SQ_INDEX_MODE::AR_Y: cfile.indexMode = CfileIndexMode::AR_Y; break; case SQ_INDEX_MODE::AR_Z: cfile.indexMode = CfileIndexMode::AR_Z; break; case SQ_INDEX_MODE::AR_W: cfile.indexMode = CfileIndexMode::AR_W; break; case SQ_INDEX_MODE::LOOP: cfile.indexMode = CfileIndexMode::AL; break; default: decaf_abort("Unexpected GPR index mode"); } } else { cfile.indexMode = CfileIndexMode::None; } return cfile; } inline CbufferRef makeCbufferRef(uint32_t offset, SQ_CF_KCACHE_MODE mode, uint32_t bank, uint32_t addr) { CbufferRef cbuffer; uint32_t lockedCount; CbufferIndexMode cbufferIndexMode; switch (mode) { case SQ_CF_KCACHE_MODE::NOP: lockedCount = 0; cbufferIndexMode = CbufferIndexMode::None; break; case SQ_CF_KCACHE_MODE::LOCK_1: lockedCount = 16; cbufferIndexMode = CbufferIndexMode::None; break; case SQ_CF_KCACHE_MODE::LOCK_2: lockedCount = 32; cbufferIndexMode = CbufferIndexMode::None; break; case SQ_CF_KCACHE_MODE::LOCK_LOOP_INDEX: lockedCount = 32; cbufferIndexMode = CbufferIndexMode::AL; break; default: decaf_abort("Unexpected KCACHE_MODE"); } decaf_check(offset < lockedCount); cbuffer.bufferId = bank; cbuffer.index = addr * 16 + offset; cbuffer.indexMode = cbufferIndexMode; return cbuffer; } inline SrcVarRef makeSrcVar(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_ALU_SRC selId, SQ_CHAN chan, SQ_REL rel, bool abs, bool neg, SQ_INDEX_MODE indexMode, VarRefType type) { SrcVarRef out; out.isAbsolute = abs; out.isNegated = neg; out.valueType = type; if (selId >= 0 && selId < 128) { out.type = SrcVarRef::Type::GPR; out.gprChan.gpr = makeGprRef(selId, rel, indexMode); out.gprChan.chan = chan; } else if (selId >= 128 && selId < 160) { // Cannot use relative indexing to a Cbuffer, instead you have to do it // at the lock level, as below. decaf_check(rel == SQ_REL::ABS); out.type = SrcVarRef::Type::CBUFFER; out.cbufferChan.cbuffer = makeCbufferRef(selId - 128, cf.alu.word0.KCACHE_MODE0(), cf.alu.word0.KCACHE_BANK0(), cf.alu.word1.KCACHE_ADDR0()); out.cbufferChan.chan = chan; } else if (selId >= 160 && selId < 192) { // Cannot use relative indexing to a Cbuffer, instead you have to do it // at the lock level, as below. decaf_check(rel == SQ_REL::ABS); out.type = SrcVarRef::Type::CBUFFER; out.cbufferChan.cbuffer = makeCbufferRef(selId - 160, cf.alu.word1.KCACHE_MODE1(), cf.alu.word0.KCACHE_BANK1(), cf.alu.word1.KCACHE_ADDR1()); out.cbufferChan.chan = chan; } else if (selId >= 256 && selId < 512) { out.type = SrcVarRef::Type::CFILE; out.cfileChan.cfile = makeCfileRef(selId - 256, rel, indexMode); out.cfileChan.chan = chan; } else { switch (selId) { case latte::SQ_ALU_SRC::LDS_OQ_A: decaf_abort("Unsupported ALU SRC: LDS_OQ_A"); case latte::SQ_ALU_SRC::LDS_OQ_B: decaf_abort("Unsupported ALU SRC: LDS_OQ_B"); case latte::SQ_ALU_SRC::LDS_OQ_A_POP: decaf_abort("Unsupported ALU SRC: LDS_OQ_A_POP"); case latte::SQ_ALU_SRC::LDS_OQ_B_POP: decaf_abort("Unsupported ALU SRC: LDS_OQ_B_POP"); case latte::SQ_ALU_SRC::LDS_DIRECT_A: decaf_abort("Unsupported ALU SRC: LDS_DIRECT_A"); case latte::SQ_ALU_SRC::LDS_DIRECT_B: decaf_abort("Unsupported ALU SRC: LDS_DIRECT_B"); case latte::SQ_ALU_SRC::TIME_HI: decaf_abort("Unsupported ALU SRC: TIME_HI"); case latte::SQ_ALU_SRC::TIME_LO: decaf_abort("Unsupported ALU SRC: TIME_LO"); case latte::SQ_ALU_SRC::MASK_HI: decaf_abort("Unsupported ALU SRC: MASK_HI"); case latte::SQ_ALU_SRC::MASK_LO: decaf_abort("Unsupported ALU SRC: MASK_LO"); case latte::SQ_ALU_SRC::HW_WAVE_ID: decaf_abort("Unsupported ALU SRC: HW_WAVE_ID"); case latte::SQ_ALU_SRC::SIMD_ID: decaf_abort("Unsupported ALU SRC: SIMD_ID"); case latte::SQ_ALU_SRC::SE_ID: decaf_abort("Unsupported ALU SRC: SE_ID"); case latte::SQ_ALU_SRC::HW_THREADGRP_ID: decaf_abort("Unsupported ALU SRC: HW_THREADGRP_ID"); case latte::SQ_ALU_SRC::WAVE_ID_IN_GRP: decaf_abort("Unsupported ALU SRC: WAVE_ID_IN_GRP"); case latte::SQ_ALU_SRC::NUM_THREADGRP_WAVES: decaf_abort("Unsupported ALU SRC: NUM_THREADGRP_WAVES"); case latte::SQ_ALU_SRC::HW_ALU_ODD: decaf_abort("Unsupported ALU SRC: HW_ALU_ODD"); case latte::SQ_ALU_SRC::LOOP_IDX: decaf_abort("Unsupported ALU SRC: LOOP_IDX"); case latte::SQ_ALU_SRC::PARAM_BASE_ADDR: decaf_abort("Unsupported ALU SRC: PARAM_BASE_ADDR"); case latte::SQ_ALU_SRC::NEW_PRIM_MASK: decaf_abort("Unsupported ALU SRC: NEW_PRIM_MASK"); case latte::SQ_ALU_SRC::PRIM_MASK_HI: decaf_abort("Unsupported ALU SRC: PRIM_MASK_HI"); case latte::SQ_ALU_SRC::PRIM_MASK_LO: decaf_abort("Unsupported ALU SRC: PRIM_MASK_LO"); case latte::SQ_ALU_SRC::IMM_1_DBL_L: decaf_abort("Unsupported ALU SRC: IMM_1_DBL_L"); case latte::SQ_ALU_SRC::IMM_1_DBL_M: decaf_abort("Unsupported ALU SRC: IMM_1_DBL_M"); case latte::SQ_ALU_SRC::IMM_0_5_DBL_L: decaf_abort("Unsupported ALU SRC: IMM_0_5_DBL_L"); case latte::SQ_ALU_SRC::IMM_0_5_DBL_M: decaf_abort("Unsupported ALU SRC: IMM_0_5_DBL_M"); case latte::SQ_ALU_SRC::IMM_0: out.type = SrcVarRef::Type::VALUE; out.value.floatValue = 0.0f; break; case latte::SQ_ALU_SRC::IMM_1: out.type = SrcVarRef::Type::VALUE; out.value.floatValue = 1.0f; break; case latte::SQ_ALU_SRC::IMM_1_INT: out.type = SrcVarRef::Type::VALUE; out.value.uintValue = 1; break; case latte::SQ_ALU_SRC::IMM_M_1_INT: out.type = SrcVarRef::Type::VALUE; out.value.intValue = -1; break; case latte::SQ_ALU_SRC::IMM_0_5: out.type = SrcVarRef::Type::VALUE; out.value.floatValue = 0.5f; break; case latte::SQ_ALU_SRC::LITERAL: out.type = SrcVarRef::Type::VALUE; out.value.uintValue = group.literals[chan]; break; case latte::SQ_ALU_SRC::PV: out.type = SrcVarRef::Type::PREVRES; out.prevres.unit = chan; break; case latte::SQ_ALU_SRC::PS: out.type = SrcVarRef::Type::PREVRES; out.prevres.unit = SQ_CHAN::T; break; default: decaf_abort(fmt::format("Unexpected ALU SRC encountered: {}", selId)); } } return out; } inline SrcVarRef makeSrcVar(const ControlFlowInst &cf, const AluInstructionGroup &group, const AluInst &inst, uint32_t srcIndex, VarRefType valueType = VarRefType::UNKNOWN) { auto instFlags = getInstructionFlags(inst); if (valueType == VarRefType::UNKNOWN) { // TODO: Rewrite GLSL generator to input the expected type information. // This would allow us to remove the UNKNOWN type and just use that inputs. valueType = VarRefType::FLOAT; if (instFlags & SQ_ALU_FLAG_INT_IN) { valueType = VarRefType::INT; } else if (instFlags & SQ_ALU_FLAG_UINT_IN) { valueType = VarRefType::UINT; } } else { if (instFlags & (SQ_ALU_FLAG_INT_IN | SQ_ALU_FLAG_UINT_IN)) { decaf_check(valueType == VarRefType::INT || valueType == VarRefType::UINT); } else { decaf_check(valueType == VarRefType::FLOAT); } } if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { switch (srcIndex) { case 0: return makeSrcVar(cf, group, inst.word0.SRC0_SEL(), inst.word0.SRC0_CHAN(), inst.word0.SRC0_REL(), inst.op2.SRC0_ABS(), inst.word0.SRC0_NEG(), inst.word0.INDEX_MODE(), valueType); case 1: return makeSrcVar(cf, group, inst.word0.SRC1_SEL(), inst.word0.SRC1_CHAN(), inst.word0.SRC1_REL(), inst.op2.SRC1_ABS(), inst.word0.SRC1_NEG(), inst.word0.INDEX_MODE(), valueType); default: decaf_abort("Invalid source var index"); } } else { switch (srcIndex) { case 0: return makeSrcVar(cf, group, inst.word0.SRC0_SEL(), inst.word0.SRC0_CHAN(), inst.word0.SRC0_REL(), false, inst.word0.SRC0_NEG(), inst.word0.INDEX_MODE(), valueType); case 1: return makeSrcVar(cf, group, inst.word0.SRC1_SEL(), inst.word0.SRC1_CHAN(), inst.word0.SRC1_REL(), false, inst.word0.SRC1_NEG(), inst.word0.INDEX_MODE(), valueType); case 2: return makeSrcVar(cf, group, inst.op3.SRC2_SEL(), inst.op3.SRC2_CHAN(), inst.op3.SRC2_REL(), false, inst.op3.SRC2_NEG(), inst.word0.INDEX_MODE(), valueType); default: decaf_abort("Invalid source var index"); } } } inline uint32_t condenseSwizzleMask(std::array<SQ_SEL, 4> &source, std::array<SQ_CHAN, 4> &dest, uint32_t numSwizzle) { uint32_t numSwizzleOut = 0; for (auto i = 0u; i < numSwizzle; ++i) { if (source[i] != SQ_SEL::SEL_MASK) { source[numSwizzleOut] = source[i]; dest[numSwizzleOut] = static_cast<SQ_CHAN>(i); numSwizzleOut++; } } return numSwizzleOut; } inline bool simplifySwizzle(std::array<SQ_CHAN, 4> &out, const std::array<SQ_SEL, 4> &source, uint32_t numSwizzle) { for (auto i = 0u; i < numSwizzle; ++i) { switch (source[i]) { case SQ_SEL::SEL_X: out[i] = SQ_CHAN::X; break; case SQ_SEL::SEL_Y: out[i] = SQ_CHAN::Y; break; case SQ_SEL::SEL_Z: out[i] = SQ_CHAN::Z; break; case SQ_SEL::SEL_W: out[i] = SQ_CHAN::W; break; default: return false; } } return true; } inline bool isSwizzleFullyMasked(const std::array<SQ_SEL, 4> &dest) { for (auto i = 0u; i < 4; ++i) { if (dest[i] != SQ_SEL::SEL_MASK) { return false; } } return true; } inline bool isSwizzleFullyUnmasked(const std::array<SQ_SEL, 4> &dest) { for (auto i = 0u; i < 4; ++i) { if (dest[i] == SQ_SEL::SEL_MASK) { return false; } } return true; } inline ExportRef makeExportRef(SQ_EXPORT_TYPE type, uint32_t arrayBase) { ExportRef out; out.dataStride = 0; out.arraySize = 1; out.elemCount = 1; out.indexGpr = InvalidGprIdx; if (type == SQ_EXPORT_TYPE::POS) { if (60 <= arrayBase && arrayBase <= 63) { out.type = ExportRef::Type::Position; out.arrayBase = arrayBase - 60; } else { decaf_abort("Unexpected POS EXPORT index"); } } else if (type == SQ_EXPORT_TYPE::PARAM) { if (0 <= arrayBase && arrayBase <= 31) { out.type = ExportRef::Type::Param; out.arrayBase = arrayBase; } else { decaf_abort("Unexpected PARAM EXPORT index"); } } else if (type == SQ_EXPORT_TYPE::PIXEL) { if (0 <= arrayBase && arrayBase <= 7) { out.type = ExportRef::Type::Pixel; out.arrayBase = arrayBase; } else if (16 <= arrayBase && arrayBase <= 23) { out.type = ExportRef::Type::PixelWithFog; out.arrayBase = arrayBase - 16; } else if (arrayBase == 61) { out.type = ExportRef::Type::ComputedZ; out.arrayBase = 0; } else { decaf_abort("Unexpected PIXEL EXPORT index"); } } else { decaf_abort("Unexpected shader EXPORT type"); } return out; } inline ExportRef _makeGenericMemExportRef(ExportRef::Type refType, SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t dataStride, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { ExportRef out; out.type = refType; out.dataStride = dataStride; out.elemCount = elemCount; out.arrayBase = arrayBase; out.arraySize = arraySize; out.indexGpr = InvalidGprIdx; switch(static_cast<SQ_MEM_EXPORT_TYPE>(type)) { case SQ_MEM_EXPORT_TYPE::WRITE: // This is handled implicitly break; case SQ_MEM_EXPORT_TYPE::WRITE_IND: out.indexGpr = indexGpr; break; default: decaf_abort("Unexpected shader MEM EXPORT type"); } return out; } inline ExportRef makeStreamExportRef(SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t streamIdx, uint32_t streamStride, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { ExportRef::Type writeType; if (streamIdx == 0) { writeType = ExportRef::Type::Stream0Write; } else if (streamIdx == 1) { writeType = ExportRef::Type::Stream1Write; } else if (streamIdx == 2) { writeType = ExportRef::Type::Stream2Write; } else if (streamIdx == 3) { writeType = ExportRef::Type::Stream3Write; } else { decaf_abort("Unexpected stream index for stream memory export"); } return _makeGenericMemExportRef(writeType, type, indexGpr, streamStride, arrayBase, arraySize, elemCount); } inline ExportRef makeVsGsRingExportRef(SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { return _makeGenericMemExportRef(ExportRef::Type::VsGsRingWrite, type, indexGpr, 0, arrayBase, arraySize, elemCount); } inline ExportRef makeGsDcRingExportRef(SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { return _makeGenericMemExportRef(ExportRef::Type::GsDcRingWrite, type, indexGpr, 0, arrayBase, arraySize, elemCount); } } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_disassembler.cpp ================================================ #include "latte/latte_disassembler.h" #include "latte/latte_disassembler_state.h" #include "latte/latte_instructions.h" #include <common/decaf_assert.h> #include <fmt/core.h> #include <gsl/gsl-lite.hpp> #include <iterator> namespace latte { namespace disassembler { static const std::string SingleIndent = " "; void increaseIndent(State &state) { state.indent += SingleIndent; } void decreaseIndent(State &state) { if (state.indent.size() >= SingleIndent.size()) { state.indent.resize(state.indent.size() - SingleIndent.size()); } else { decaf_abort("Invalid decrease indent"); } } void disassembleCondition(fmt::memory_buffer &out, const ControlFlowInst &inst) { if (inst.word1.COND()) { fmt::format_to(std::back_inserter(out), " CND("); switch (inst.word1.COND()) { case SQ_CF_COND::ALWAYS_FALSE: fmt::format_to(std::back_inserter(out), "FALSE"); break; case SQ_CF_COND::CF_BOOL: fmt::format_to(std::back_inserter(out), "BOOL"); break; case SQ_CF_COND::CF_NOT_BOOL: fmt::format_to(std::back_inserter(out), "NOT_BOOL"); break; } fmt::format_to(std::back_inserter(out), ") CF_CONST({})", inst.word1.CF_CONST()); } } static void disassembleLoop(fmt::memory_buffer &out, const ControlFlowInst &inst) { disassembleCondition(out, inst); switch (inst.word1.CF_INST()) { case SQ_CF_INST_LOOP_START: case SQ_CF_INST_LOOP_END: if (!inst.word1.COND()) { // If .COND is set - disassembleCondition will print CF_CONST fmt::format_to(std::back_inserter(out), " CF_CONST({})", inst.word1.CF_CONST()); } } switch (inst.word1.CF_INST()) { case SQ_CF_INST_LOOP_START: case SQ_CF_INST_LOOP_START_DX10: case SQ_CF_INST_LOOP_START_NO_AL: fmt::format_to(std::back_inserter(out), " FAIL_JUMP_ADDR({})", inst.word0.ADDR()); break; case SQ_CF_INST_LOOP_CONTINUE: case SQ_CF_INST_LOOP_BREAK: fmt::format_to(std::back_inserter(out), " ADDR({})", inst.word0.ADDR()); break; case SQ_CF_INST_LOOP_END: fmt::format_to(std::back_inserter(out), " PASS_JUMP_ADDR({})", inst.word0.ADDR()); break; default: fmt::format_to(std::back_inserter(out), " UNKNOWN_LOOP_CF_INST"); } if (inst.word1.POP_COUNT()) { fmt::format_to(std::back_inserter(out), " POP_CNT({})", inst.word1.POP_COUNT()); } if (inst.word1.VALID_PIXEL_MODE()) { fmt::format_to(std::back_inserter(out), " VALID_PIX"); } if (!inst.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } } static void disassembleJump(fmt::memory_buffer &out, const ControlFlowInst &inst) { auto id = inst.word1.CF_INST(); if (id == SQ_CF_INST_CALL && inst.word1.CALL_COUNT()) { fmt::format_to(std::back_inserter(out), " CALL_COUNT({})", inst.word1.CALL_COUNT()); } disassembleCondition(out, inst); if (inst.word1.POP_COUNT()) { fmt::format_to(std::back_inserter(out), " POP_CNT({})", inst.word1.POP_COUNT()); } if (id == SQ_CF_INST_CALL || id == SQ_CF_INST_ELSE || id == SQ_CF_INST_JUMP) { fmt::format_to(std::back_inserter(out), " ADDR({})", inst.word0.ADDR()); } if (inst.word1.VALID_PIXEL_MODE()) { fmt::format_to(std::back_inserter(out), " VALID_PIX"); } if (!inst.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } } void disassembleCF(fmt::memory_buffer &out, const ControlFlowInst &inst) { auto id = inst.word1.CF_INST(); auto name = getInstructionName(id); fmt::format_to(std::back_inserter(out), "{}", name); switch (id) { case SQ_CF_INST_TEX: disassembleCfTEX(out, inst); break; case SQ_CF_INST_VTX: case SQ_CF_INST_VTX_TC: disassembleCfVTX(out, inst); break; case SQ_CF_INST_LOOP_START: case SQ_CF_INST_LOOP_START_DX10: case SQ_CF_INST_LOOP_START_NO_AL: case SQ_CF_INST_LOOP_END: case SQ_CF_INST_LOOP_CONTINUE: case SQ_CF_INST_LOOP_BREAK: disassembleLoop(out, inst); break; case SQ_CF_INST_JUMP: case SQ_CF_INST_ELSE: case SQ_CF_INST_CALL: case SQ_CF_INST_CALL_FS: case SQ_CF_INST_RETURN: case SQ_CF_INST_POP_JUMP: disassembleJump(out, inst); break; case SQ_CF_INST_EMIT_VERTEX: case SQ_CF_INST_EMIT_CUT_VERTEX: case SQ_CF_INST_CUT_VERTEX: if (!inst.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } break; case SQ_CF_INST_PUSH: case SQ_CF_INST_PUSH_ELSE: case SQ_CF_INST_KILL: disassembleCondition(out, inst); // switch case pass through case SQ_CF_INST_POP: case SQ_CF_INST_POP_PUSH: case SQ_CF_INST_POP_PUSH_ELSE: if (inst.word1.POP_COUNT()) { fmt::format_to(std::back_inserter(out), " POP_CNT({})", inst.word1.POP_COUNT()); } if (inst.word1.VALID_PIXEL_MODE()) { fmt::format_to(std::back_inserter(out), " VALID_PIX"); } break; case SQ_CF_INST_END_PROGRAM: case SQ_CF_INST_NOP: break; case SQ_CF_INST_WAIT_ACK: case SQ_CF_INST_TEX_ACK: case SQ_CF_INST_VTX_ACK: case SQ_CF_INST_VTX_TC_ACK: default: fmt::format_to(std::back_inserter(out), " UNK_FORMAT"); break; } } static void disassembleNormal(State &state, const ControlFlowInst &inst) { auto id = inst.word1.CF_INST(); auto name = getInstructionName(id); switch (id) { case SQ_CF_INST_WAIT_ACK: case SQ_CF_INST_TEX_ACK: case SQ_CF_INST_VTX_ACK: case SQ_CF_INST_VTX_TC_ACK: decaf_abort(fmt::format("Unable to decode instruction {} {}", id, name)); } // Decode instruction clause fmt::format_to(state.out, "{}{:02} ", state.indent, state.cfPC); disassembleCF(state.out, inst); fmt::format_to(state.out, "\n"); switch (id) { case SQ_CF_INST_LOOP_START: case SQ_CF_INST_LOOP_START_DX10: case SQ_CF_INST_LOOP_START_NO_AL: increaseIndent(state); break; case SQ_CF_INST_LOOP_END: decreaseIndent(state); break; case SQ_CF_INST_TEX: disassembleTEXClause(state, inst); break; case SQ_CF_INST_VTX: case SQ_CF_INST_VTX_TC: disassembleVtxClause(state, inst); break; } } } // namespace disassembler std::string disassemble(const gsl::span<const uint8_t> &binary, bool isSubroutine) { disassembler::State state; state.binary = binary; state.cfPC = 0; state.groupPC = 0; for (auto i = 0; i < binary.size(); i += sizeof(ControlFlowInst)) { auto cf = *reinterpret_cast<const ControlFlowInst *>(binary.data() + i); // cf.word1.CF_INST(); auto type = cf.word1.CF_INST_TYPE(); switch (type) { case SQ_CF_INST_TYPE_NORMAL: disassembler::disassembleNormal(state, cf); break; case SQ_CF_INST_TYPE_EXPORT: disassembler::disassembleExport(state, cf); break; case SQ_CF_INST_TYPE_ALU: case SQ_CF_INST_TYPE_ALU_EXTENDED: disassembler::disassembleControlFlowALU(state, cf); break; default: decaf_abort(fmt::format("Invalid top level instruction type {}", type)); } if (cf.word1.CF_INST() == SQ_CF_INST_RETURN && isSubroutine) { break; } if (cf.word1.CF_INST_TYPE() == SQ_CF_INST_TYPE_NORMAL || cf.word1.CF_INST_TYPE() == SQ_CF_INST_TYPE_EXPORT) { if (cf.word1.END_OF_PROGRAM()) { fmt::format_to(state.out, "\nEND_OF_PROGRAM\n"); break; } } state.cfPC++; fmt::format_to(state.out, "\n"); } return to_string(state.out); } } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_disassembler_alu.cpp ================================================ #include "latte/latte_disassembler_state.h" #include "latte_decoders.h" #include <common/bit_cast.h> #include <common/decaf_assert.h> #include <common/log.h> #include <fmt/core.h> #include <iterator> namespace latte { namespace disassembler { static void disassembleKcache(fmt::memory_buffer &out, uint32_t id, SQ_CF_KCACHE_MODE mode, uint32_t bank, uint32_t addr) { switch (mode) { case SQ_CF_KCACHE_MODE::NOP: break; case SQ_CF_KCACHE_MODE::LOCK_1: fmt::format_to(std::back_inserter(out), " KCACHE{}(CB{}:{}-{})", id, bank, 16 * addr, 16 * addr + 15); break; case SQ_CF_KCACHE_MODE::LOCK_2: fmt::format_to(std::back_inserter(out), " KCACHE{}(CB{}:{}-{})", id, bank, 16 * addr, 16 * addr + 31); break; case SQ_CF_KCACHE_MODE::LOCK_LOOP_INDEX: fmt::format_to(std::back_inserter(out), " KCACHE{}(CB{}:AL+{}-AL+{})", id, bank, 16 * addr, 16 * addr + 31); break; } } static void disassembleAluSource(fmt::memory_buffer &out, const latte::ControlFlowInst &parent, size_t groupPC, SQ_INDEX_MODE indexMode, uint32_t sel, SQ_REL rel, SQ_CHAN chan, uint32_t literalValue, bool negate, bool absolute) { bool useChannel = true; if (negate) { fmt::format_to(std::back_inserter(out), "-"); } if (absolute) { fmt::format_to(std::back_inserter(out), "|"); } if (sel >= SQ_ALU_SRC::KCACHE_BANK0_FIRST && sel <= SQ_ALU_SRC::KCACHE_BANK0_LAST) { auto id = sel - SQ_ALU_SRC::KCACHE_BANK0_FIRST; fmt::format_to(std::back_inserter(out), "KC0[{}]", id); } else if (sel >= SQ_ALU_SRC::KCACHE_BANK1_FIRST && sel <= SQ_ALU_SRC::KCACHE_BANK1_LAST) { auto id = sel - SQ_ALU_SRC::KCACHE_BANK1_FIRST; fmt::format_to(std::back_inserter(out), "KC1[{}]", id); } else if (sel >= SQ_ALU_SRC::REGISTER_FIRST && sel <= SQ_ALU_SRC::REGISTER_LAST) { fmt::format_to(std::back_inserter(out), "R{}", sel - SQ_ALU_SRC::REGISTER_FIRST); } else if (sel >= SQ_ALU_SRC::CONST_FILE_FIRST && sel <= SQ_ALU_SRC::CONST_FILE_LAST) { fmt::format_to(std::back_inserter(out), "C{}", sel - SQ_ALU_SRC::CONST_FILE_FIRST); } else { useChannel = false; switch (sel) { case SQ_ALU_SRC::LDS_OQ_A: fmt::format_to(std::back_inserter(out), "LDS_OQ_A"); break; case SQ_ALU_SRC::LDS_OQ_B: fmt::format_to(std::back_inserter(out), "LDS_OQ_B"); break; case SQ_ALU_SRC::LDS_OQ_A_POP: fmt::format_to(std::back_inserter(out), "LDS_OQ_A_POP"); break; case SQ_ALU_SRC::LDS_OQ_B_POP: fmt::format_to(std::back_inserter(out), "LDS_OQ_B_POP"); break; case SQ_ALU_SRC::LDS_DIRECT_A: fmt::format_to(std::back_inserter(out), "LDS_DIRECT_A"); break; case SQ_ALU_SRC::LDS_DIRECT_B: fmt::format_to(std::back_inserter(out), "LDS_DIRECT_B"); break; case SQ_ALU_SRC::TIME_HI: fmt::format_to(std::back_inserter(out), "TIME_HI"); break; case SQ_ALU_SRC::TIME_LO: fmt::format_to(std::back_inserter(out), "TIME_LO"); break; case SQ_ALU_SRC::MASK_HI: fmt::format_to(std::back_inserter(out), "MASK_HI"); break; case SQ_ALU_SRC::MASK_LO: fmt::format_to(std::back_inserter(out), "MASK_LO"); break; case SQ_ALU_SRC::HW_WAVE_ID: fmt::format_to(std::back_inserter(out), "HW_WAVE_ID"); break; case SQ_ALU_SRC::SIMD_ID: fmt::format_to(std::back_inserter(out), "SIMD_ID"); break; case SQ_ALU_SRC::SE_ID: fmt::format_to(std::back_inserter(out), "SE_ID"); break; case SQ_ALU_SRC::HW_THREADGRP_ID: fmt::format_to(std::back_inserter(out), "HW_THREADGRP_ID"); break; case SQ_ALU_SRC::WAVE_ID_IN_GRP: fmt::format_to(std::back_inserter(out), "WAVE_ID_IN_GRP"); break; case SQ_ALU_SRC::NUM_THREADGRP_WAVES: fmt::format_to(std::back_inserter(out), "NUM_THREADGRP_WAVES"); break; case SQ_ALU_SRC::HW_ALU_ODD: fmt::format_to(std::back_inserter(out), "HW_ALU_ODD"); break; case SQ_ALU_SRC::LOOP_IDX: fmt::format_to(std::back_inserter(out), "AL"); break; case SQ_ALU_SRC::PARAM_BASE_ADDR: fmt::format_to(std::back_inserter(out), "PARAM_BASE_ADDR"); break; case SQ_ALU_SRC::NEW_PRIM_MASK: fmt::format_to(std::back_inserter(out), "NEW_PRIM_MASK"); break; case SQ_ALU_SRC::PRIM_MASK_HI: fmt::format_to(std::back_inserter(out), "PRIM_MASK_HI"); break; case SQ_ALU_SRC::PRIM_MASK_LO: fmt::format_to(std::back_inserter(out), "PRIM_MASK_LO"); break; case SQ_ALU_SRC::IMM_1_DBL_L: fmt::format_to(std::back_inserter(out), "1.0_L"); break; case SQ_ALU_SRC::IMM_1_DBL_M: fmt::format_to(std::back_inserter(out), "1.0_M"); break; case SQ_ALU_SRC::IMM_0_5_DBL_L: fmt::format_to(std::back_inserter(out), "0.5_L"); break; case SQ_ALU_SRC::IMM_0_5_DBL_M: fmt::format_to(std::back_inserter(out), "0.5_M"); break; case SQ_ALU_SRC::IMM_0: fmt::format_to(std::back_inserter(out), "0.0f"); break; case SQ_ALU_SRC::IMM_1: fmt::format_to(std::back_inserter(out), "1.0f"); break; case SQ_ALU_SRC::IMM_1_INT: fmt::format_to(std::back_inserter(out), "1"); break; case SQ_ALU_SRC::IMM_M_1_INT: fmt::format_to(std::back_inserter(out), "-1"); break; case SQ_ALU_SRC::IMM_0_5: fmt::format_to(std::back_inserter(out), "0.5f"); break; case SQ_ALU_SRC::LITERAL: fmt::format_to(std::back_inserter(out), "(0x{:08X}, {})", literalValue, bit_cast<float>(literalValue)); break; case SQ_ALU_SRC::PV: fmt::format_to(std::back_inserter(out), "PV{}", groupPC - 1); useChannel = true; break; case SQ_ALU_SRC::PS: fmt::format_to(std::back_inserter(out), "PS{}", groupPC - 1); break; default: fmt::format_to(std::back_inserter(out), "UNKNOWN"); } } if (rel) { switch (indexMode) { case SQ_INDEX_MODE::AR_X: fmt::format_to(std::back_inserter(out), "[AR.x]"); break; case SQ_INDEX_MODE::AR_Y: fmt::format_to(std::back_inserter(out), "[AR.y]"); break; case SQ_INDEX_MODE::AR_Z: fmt::format_to(std::back_inserter(out), "[AR.z]"); break; case SQ_INDEX_MODE::AR_W: fmt::format_to(std::back_inserter(out), "[AR.w]"); break; case SQ_INDEX_MODE::LOOP: fmt::format_to(std::back_inserter(out), "[AL]"); break; default: fmt::format_to(std::back_inserter(out), "[UNKNOWN]"); } } if (useChannel) { switch (chan) { case SQ_CHAN::X: fmt::format_to(std::back_inserter(out), ".x"); break; case SQ_CHAN::Y: fmt::format_to(std::back_inserter(out), ".y"); break; case SQ_CHAN::Z: fmt::format_to(std::back_inserter(out), ".z"); break; case SQ_CHAN::W: fmt::format_to(std::back_inserter(out), ".w"); break; default: fmt::format_to(std::back_inserter(out), ".UNKNOWN"); break; } } if (absolute) { fmt::format_to(std::back_inserter(out), "|"); } } void disassembleAluInstruction(fmt::memory_buffer &out, const ControlFlowInst &parent, const AluInst &inst, size_t groupPC, SQ_CHAN unit, const gsl::span<const uint32_t> &literals, int namePad) { std::string name; SQ_ALU_FLAGS flags; auto srcCount = 0u; if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { name = getInstructionName(inst.op2.ALU_INST()); flags = getInstructionFlags(inst.op2.ALU_INST()); srcCount = getInstructionNumSrcs(inst.op2.ALU_INST()); } else { name = getInstructionName(inst.op3.ALU_INST()); flags = getInstructionFlags(inst.op3.ALU_INST()); srcCount = getInstructionNumSrcs(inst.op3.ALU_INST()); } if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2 && !inst.op2.UPDATE_EXECUTE_MASK()) { switch (inst.op2.OMOD()) { case SQ_ALU_OMOD::OFF: break; case SQ_ALU_OMOD::D2: name += "/2"; break; case SQ_ALU_OMOD::M2: name += "*2"; break; case SQ_ALU_OMOD::M4: name += "*4"; break; default: decaf_abort(fmt::format("Unexpected OMOD {}", inst.op2.OMOD())); } } fmt::format_to(std::back_inserter(out), "{: <{}} ", name, namePad); auto writeMask = true; if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { writeMask = inst.op2.WRITE_MASK(); } if (!writeMask) { fmt::format_to(std::back_inserter(out), "____"); } else { disassembleAluSource(out, parent, groupPC, inst.word0.INDEX_MODE(), inst.word1.DST_GPR(), inst.word1.DST_REL(), inst.word1.DST_CHAN(), 0, false, false); } if (srcCount > 0) { auto literal = 0u; auto abs = false; if (inst.word0.SRC0_SEL() == SQ_ALU_SRC::LITERAL) { literal = literals[inst.word0.SRC0_CHAN()]; } if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { abs = !!inst.op2.SRC0_ABS(); } fmt::format_to(std::back_inserter(out), ", "); disassembleAluSource(out, parent, groupPC, inst.word0.INDEX_MODE(), inst.word0.SRC0_SEL(), inst.word0.SRC0_REL(), inst.word0.SRC0_CHAN(), literal, inst.word0.SRC0_NEG(), abs); } if (srcCount > 1) { auto literal = 0u; auto abs = false; if (inst.word0.SRC1_SEL() == SQ_ALU_SRC::LITERAL) { literal = literals[inst.word0.SRC1_CHAN()]; } if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { abs = !!inst.op2.SRC1_ABS(); } fmt::format_to(std::back_inserter(out), ", "); disassembleAluSource(out, parent, groupPC, inst.word0.INDEX_MODE(), inst.word0.SRC1_SEL(), inst.word0.SRC1_REL(), inst.word0.SRC1_CHAN(), literal, inst.word0.SRC1_NEG(), abs); } if (srcCount > 2) { auto literal = 0u; if (inst.op3.SRC2_SEL() == SQ_ALU_SRC::LITERAL) { literal = literals[inst.op3.SRC2_CHAN()]; } fmt::format_to(std::back_inserter(out), ", "); disassembleAluSource(out, parent, groupPC, inst.word0.INDEX_MODE(), inst.op3.SRC2_SEL(), inst.op3.SRC2_REL(), inst.op3.SRC2_CHAN(), literal, inst.op3.SRC2_NEG(), false); } if (inst.word1.CLAMP()) { fmt::format_to(std::back_inserter(out), " CLAMP"); } if (isTranscendentalOnly(flags)) { switch (static_cast<SQ_ALU_SCL_BANK_SWIZZLE>(inst.word1.BANK_SWIZZLE())) { case SQ_ALU_SCL_BANK_SWIZZLE::SCL_210: fmt::format_to(std::back_inserter(out), " SCL_210"); break; case SQ_ALU_SCL_BANK_SWIZZLE::SCL_122: fmt::format_to(std::back_inserter(out), " SCL_122"); break; case SQ_ALU_SCL_BANK_SWIZZLE::SCL_212: fmt::format_to(std::back_inserter(out), " SCL_212"); break; case SQ_ALU_SCL_BANK_SWIZZLE::SCL_221: fmt::format_to(std::back_inserter(out), " SCL_221"); break; default: decaf_abort(fmt::format("Unexpected BANK_SWIZZLE {}", inst.word1.BANK_SWIZZLE())); } } else { switch (inst.word1.BANK_SWIZZLE()) { case SQ_ALU_VEC_BANK_SWIZZLE::VEC_012: // This is default, no need to print break; case SQ_ALU_VEC_BANK_SWIZZLE::VEC_021: fmt::format_to(std::back_inserter(out), " VEC_021"); break; case SQ_ALU_VEC_BANK_SWIZZLE::VEC_120: fmt::format_to(std::back_inserter(out), " VEC_120"); break; case SQ_ALU_VEC_BANK_SWIZZLE::VEC_102: fmt::format_to(std::back_inserter(out), " VEC_102"); break; case SQ_ALU_VEC_BANK_SWIZZLE::VEC_201: fmt::format_to(std::back_inserter(out), " VEC_201"); break; case SQ_ALU_VEC_BANK_SWIZZLE::VEC_210: fmt::format_to(std::back_inserter(out), " VEC_210"); break; default: decaf_abort(fmt::format("Unexpected BANK_SWIZZLE {}", inst.word1.BANK_SWIZZLE())); } } switch (inst.word0.PRED_SEL()) { case SQ_PRED_SEL::OFF: break; case SQ_PRED_SEL::ZERO: fmt::format_to(std::back_inserter(out), " PRED_SEL_ZERO"); break; case SQ_PRED_SEL::ONE: fmt::format_to(std::back_inserter(out), " PRED_SEL_ONE"); break; default: decaf_abort(fmt::format("Unexpected PRED_SEL {}", inst.word0.PRED_SEL())); } if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { if (inst.op2.UPDATE_EXECUTE_MASK()) { fmt::format_to(std::back_inserter(out), " UPDATE_EXEC_MASK"); } if (inst.op2.UPDATE_PRED()) { fmt::format_to(std::back_inserter(out), " UPDATE_PRED"); } } } static void disassembleAluClause(State &state, const latte::ControlFlowInst &parent, uint32_t addr, uint32_t slots) { static char unitName[5] = { 'x', 'y', 'z', 'w', 't' }; auto clause = reinterpret_cast<const AluInst *>(state.binary.data() + 8 * addr); for (size_t slot = 0u; slot < slots; ) { auto units = AluGroupUnits { }; auto group = AluGroup { clause + slot }; fmt::format_to(std::back_inserter(state.out), "\n{}{: <3}", state.indent, state.groupPC); for (auto j = 0u; j < group.instructions.size(); ++j) { auto &inst = group.instructions[j]; auto unit = units.addInstructionUnit(inst); if (j > 0) { fmt::format_to(std::back_inserter(state.out), "{} ", state.indent); } fmt::format_to(std::back_inserter(state.out), " {}: ", unitName[unit]); disassembleAluInstruction(state.out, parent, inst, state.groupPC, unit, group.literals, 15); fmt::format_to(std::back_inserter(state.out), "\n"); } slot = group.getNextSlot(slot); state.groupPC++; } } void disassembleCfALUInstruction(fmt::memory_buffer &out, const ControlFlowInst &inst) { auto name = getInstructionName(inst.alu.word1.CF_INST()); auto addr = inst.alu.word0.ADDR(); auto count = inst.alu.word1.COUNT() + 1; fmt::format_to(std::back_inserter(out), "{}: ADDR({}) CNT({})", name, addr, count); if (!inst.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } if (inst.word1.WHOLE_QUAD_MODE()) { fmt::format_to(std::back_inserter(out), " WHOLE_QUAD"); } disassembleKcache(out, 0, inst.alu.word0.KCACHE_MODE0(), inst.alu.word0.KCACHE_BANK0(), inst.alu.word1.KCACHE_ADDR0()); disassembleKcache(out, 1, inst.alu.word1.KCACHE_MODE1(), inst.alu.word0.KCACHE_BANK1(), inst.alu.word1.KCACHE_ADDR1()); } void disassembleControlFlowALU(State &state, const ControlFlowInst &inst) { auto addr = inst.alu.word0.ADDR(); auto count = inst.alu.word1.COUNT() + 1; fmt::format_to(std::back_inserter(state.out), "{}{:02} ", state.indent, state.cfPC); disassembleCfALUInstruction(state.out, inst); increaseIndent(state); disassembleAluClause(state, inst, addr, count); decreaseIndent(state); } } // namespace disassembler } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_disassembler_export.cpp ================================================ #include "latte/latte_disassembler_state.h" #include <iterator> namespace latte { namespace disassembler { char disassembleDestMask(SQ_SEL sel) { switch (sel) { case SQ_SEL::SEL_X: return 'x'; case SQ_SEL::SEL_Y: return 'y'; case SQ_SEL::SEL_Z: return 'z'; case SQ_SEL::SEL_W: return 'w'; case SQ_SEL::SEL_0: return '0'; case SQ_SEL::SEL_1: return '1'; case SQ_SEL::SEL_MASK: return '_'; default: return '?'; } } void disassembleExpInstruction(fmt::memory_buffer &out, const ControlFlowInst &inst) { auto id = inst.exp.word1.CF_INST(); auto name = getInstructionName(id); fmt::format_to(std::back_inserter(out), "{}:", name); auto type = inst.exp.word0.TYPE(); auto memExpType = static_cast<SQ_MEM_EXPORT_TYPE>(inst.exp.word0.TYPE()); auto arrayBase = inst.exp.word0.ARRAY_BASE(); if (id == SQ_CF_INST_EXP || id == SQ_CF_INST_EXP_DONE) { switch (type) { case SQ_EXPORT_TYPE::PIXEL: fmt::format_to(std::back_inserter(out), " PIX{}", arrayBase); break; case SQ_EXPORT_TYPE::POS: fmt::format_to(std::back_inserter(out), " POS{}", arrayBase - 60); break; case SQ_EXPORT_TYPE::PARAM: fmt::format_to(std::back_inserter(out), " PARAM{}", arrayBase); break; default: fmt::format_to(std::back_inserter(out), " INVALID{}", arrayBase); break; } } else if (id >= SQ_CF_INST_MEM_STREAM0 && id <= SQ_CF_INST_MEM_STREAM3 && (memExpType == SQ_MEM_EXPORT_TYPE::READ || memExpType == SQ_MEM_EXPORT_TYPE::READ_IND)) { fmt::format_to(std::back_inserter(out), " INVALID_READ"); } else { if (memExpType == SQ_MEM_EXPORT_TYPE::WRITE || memExpType == SQ_MEM_EXPORT_TYPE::WRITE_IND) { fmt::format_to(std::back_inserter(out), " WRITE("); } else { fmt::format_to(std::back_inserter(out), " READ("); } if (id == SQ_CF_INST_MEM_SCRATCH || id == SQ_CF_INST_MEM_REDUCTION) { fmt::format_to(std::back_inserter(out), "{}", arrayBase * 16); } else { fmt::format_to(std::back_inserter(out), "{}", arrayBase * 4); } if (memExpType == SQ_MEM_EXPORT_TYPE::WRITE_IND || memExpType == SQ_MEM_EXPORT_TYPE::READ_IND) { fmt::format_to(std::back_inserter(out), " + R{}", inst.exp.word0.INDEX_GPR()); } fmt::format_to(std::back_inserter(out), ")"); } fmt::format_to(std::back_inserter(out), ", "); if (inst.exp.word0.RW_REL() == SQ_REL::REL) { fmt::format_to(std::back_inserter(out), "R[AL + {}]", inst.exp.word0.RW_GPR()); } else { fmt::format_to(std::back_inserter(out), "R{}", inst.exp.word0.RW_GPR()); } if (id == SQ_CF_INST_EXP || id == SQ_CF_INST_EXP_DONE) { fmt::format_to(std::back_inserter(out), ".{}{}{}{}", disassembleDestMask(inst.exp.swiz.SEL_X()), disassembleDestMask(inst.exp.swiz.SEL_Y()), disassembleDestMask(inst.exp.swiz.SEL_Z()), disassembleDestMask(inst.exp.swiz.SEL_W())); } else { fmt::format_to(std::back_inserter(out), ".{}{}{}{}", (inst.exp.buf.COMP_MASK() & (1 << 0) ? 'x' : '_'), (inst.exp.buf.COMP_MASK() & (1 << 1) ? 'y' : '_'), (inst.exp.buf.COMP_MASK() & (1 << 2) ? 'z' : '_'), (inst.exp.buf.COMP_MASK() & (1 << 3) ? 'w' : '_')); fmt::format_to(std::back_inserter(out), " ARRAY_SIZE({})", inst.exp.buf.ARRAY_SIZE()); } if (inst.exp.word0.ELEM_SIZE()) { fmt::format_to(std::back_inserter(out), " ELEM_SIZE({})", inst.exp.word0.ELEM_SIZE()); } if (inst.exp.word1.BURST_COUNT()) { fmt::format_to(std::back_inserter(out), " BURSTCNT({})", inst.exp.word1.BURST_COUNT()); } if (!inst.exp.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } if (inst.exp.word1.WHOLE_QUAD_MODE()) { fmt::format_to(std::back_inserter(out), " WHOLE_QUAD"); } if (inst.word1.VALID_PIXEL_MODE()) { fmt::format_to(std::back_inserter(out), " VALID_PIX"); } } void disassembleExport(State &state, const ControlFlowInst &inst) { fmt::format_to(std::back_inserter(state.out), "{}{:02} ", state.indent, state.cfPC); disassembleExpInstruction(state.out, inst); fmt::format_to(std::back_inserter(state.out), "\n"); } } // namespace disassembler } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_disassembler_state.h ================================================ #pragma once #include "latte/latte_instructions.h" #include <fmt/format.h> #include <gsl/gsl-lite.hpp> namespace latte { namespace disassembler { struct State { gsl::span<const uint8_t> binary; fmt::memory_buffer out; std::string indent; size_t cfPC; size_t groupPC; }; void increaseIndent(State& state); void decreaseIndent(State& state); void disassembleControlFlowALU(State& state, const ControlFlowInst& inst); void disassembleVtxClause(State& state, const latte::ControlFlowInst& parent); void disassembleTEXClause(State& state, const ControlFlowInst& inst); void disassembleExport(State& state, const ControlFlowInst& inst); char disassembleDestMask(SQ_SEL sel); void disassembleCondition(fmt::memory_buffer& out, const ControlFlowInst& inst); void disassembleCfTEX(fmt::memory_buffer& out, const ControlFlowInst& inst); void disassembleCfVTX(fmt::memory_buffer& out, const ControlFlowInst& inst); void disassembleCF(fmt::memory_buffer& out, const ControlFlowInst& inst); void disassembleAluInstruction(fmt::memory_buffer& out, const ControlFlowInst& parent, const AluInst& inst, size_t groupPC, SQ_CHAN unit, const gsl::span<const uint32_t>& literals, int namePad = 0); void disassembleCfALUInstruction(fmt::memory_buffer& out, const ControlFlowInst& inst); void disassembleExpInstruction(fmt::memory_buffer& out, const ControlFlowInst& inst); void disassembleVtxInstruction(fmt::memory_buffer& out, const latte::ControlFlowInst& parent, const VertexFetchInst& tex, int namePad = 0); void disassembleTexInstruction(fmt::memory_buffer& out, const latte::ControlFlowInst& parent, const TextureFetchInst& tex, int namePad = 0); } // namespace disassembler } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_disassembler_tex.cpp ================================================ #include "latte/latte_disassembler_state.h" #include <common/bitutils.h> #include <common/decaf_assert.h> #include <common/fixed.h> #include <fmt/core.h> #include <iterator> namespace latte { namespace disassembler { void disassembleTexInstruction(fmt::memory_buffer &out, const latte::ControlFlowInst &parent, const TextureFetchInst &tex, int namePad) { auto id = tex.word0.TEX_INST(); auto name = getInstructionName(id); if (id == SQ_TEX_INST_VTX_FETCH || id == SQ_TEX_INST_VTX_SEMANTIC) { // This only works because the TEX instruction IDs precisely match the // VTX instruction IDs. Ensure that is the case for anything else // that is added to this redirection code. auto vtx = *reinterpret_cast<const VertexFetchInst*>(&tex); disassembleVtxInstruction(out, parent, vtx, namePad); return; } fmt::format_to(std::back_inserter(out), "{: <{}} ", name, namePad); // dst auto dstSelX = tex.word1.DST_SEL_X(); auto dstSelY = tex.word1.DST_SEL_Y(); auto dstSelZ = tex.word1.DST_SEL_Z(); auto dstSelW = tex.word1.DST_SEL_W(); if (dstSelX != latte::SQ_SEL::SEL_MASK || dstSelY != latte::SQ_SEL::SEL_MASK || dstSelZ != latte::SQ_SEL::SEL_MASK || dstSelW != latte::SQ_SEL::SEL_MASK) { fmt::format_to(std::back_inserter(out), "R{}", tex.word1.DST_GPR()); if (tex.word1.DST_REL() == SQ_REL::REL) { fmt::format_to(std::back_inserter(out), "[AL]"); } fmt::format_to(std::back_inserter(out), ".{}{}{}{}", disassembleDestMask(dstSelX), disassembleDestMask(dstSelY), disassembleDestMask(dstSelZ), disassembleDestMask(dstSelW)); } else { fmt::format_to(std::back_inserter(out), "____"); } // src fmt::format_to(std::back_inserter(out), ", R{}", tex.word0.SRC_GPR()); if (tex.word0.SRC_REL() == SQ_REL::REL) { fmt::format_to(std::back_inserter(out), "[AL]"); } fmt::format_to(std::back_inserter(out), ".{}{}{}{}", disassembleDestMask(tex.word2.SRC_SEL_X()), disassembleDestMask(tex.word2.SRC_SEL_Y()), disassembleDestMask(tex.word2.SRC_SEL_Z()), disassembleDestMask(tex.word2.SRC_SEL_W())); fmt::format_to(std::back_inserter(out), ", t{}, s{}", tex.word0.RESOURCE_ID(), tex.word2.SAMPLER_ID()); if (tex.word1.LOD_BIAS()) { fmt::format_to(std::back_inserter(out), " LOD({})", static_cast<float>(tex.word1.LOD_BIAS())); } if (tex.word0.FETCH_WHOLE_QUAD()) { fmt::format_to(std::back_inserter(out), " WHOLE_QUAD"); } if (tex.word0.BC_FRAC_MODE()) { fmt::format_to(std::back_inserter(out), " BC_FRAC_MODE"); } if (tex.word0.ALT_CONST()) { fmt::format_to(std::back_inserter(out), " ALT_CONST"); } auto normX = tex.word1.COORD_TYPE_X(); auto normY = tex.word1.COORD_TYPE_Y(); auto normZ = tex.word1.COORD_TYPE_Z(); auto normW = tex.word1.COORD_TYPE_W(); if (!normX || !normY || !normZ || !normW) { fmt::format_to(std::back_inserter(out), " DENORM("); if (!normX) { fmt::format_to(std::back_inserter(out), "X"); } if (!normY) { fmt::format_to(std::back_inserter(out), "Y"); } if (!normZ) { fmt::format_to(std::back_inserter(out), "Z"); } if (!normW) { fmt::format_to(std::back_inserter(out), "W"); } fmt::format_to(std::back_inserter(out), ")"); } if (tex.word2.OFFSET_X()) { fmt::format_to(std::back_inserter(out), " XOFFSET({})", static_cast<float>(tex.word2.OFFSET_X())); } if (tex.word2.OFFSET_Y()) { fmt::format_to(std::back_inserter(out), " YOFFSET({})", static_cast<float>(tex.word2.OFFSET_Y())); } if (tex.word2.OFFSET_Z()) { fmt::format_to(std::back_inserter(out), " ZOFFSET({})", static_cast<float>(tex.word2.OFFSET_Z())); } } void disassembleTEXClause(State &state, const ControlFlowInst &inst) { auto addr = inst.word0.ADDR(); auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1; auto clause = reinterpret_cast<const TextureFetchInst *>(state.binary.data() + 8 * addr); increaseIndent(state); for (auto i = 0u; i < count; ++i) { const auto &tex = clause[i]; auto id = tex.word0.TEX_INST(); fmt::format_to(std::back_inserter(state.out), "\n{}{: <3} ", state.indent, state.groupPC); if (id == SQ_TEX_INST_VTX_FETCH || id == SQ_TEX_INST_VTX_SEMANTIC) { // Someone at AMD must have been having a laugh when they designed this... auto vtx = *reinterpret_cast<const VertexFetchInst *>(&tex); disassembleVtxInstruction(state.out, inst, vtx, 15); } else { disassembleTexInstruction(state.out, inst, tex, 15); } fmt::format_to(std::back_inserter(state.out), "\n"); state.groupPC++; } decreaseIndent(state); } void disassembleCfTEX(fmt::memory_buffer &out, const ControlFlowInst &inst) { auto addr = inst.word0.ADDR(); auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1; fmt::format_to(std::back_inserter(out), ": ADDR({}) CNT({})", addr, count); if (!inst.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } disassembleCondition(out, inst); if (inst.word1.WHOLE_QUAD_MODE()) { fmt::format_to(std::back_inserter(out), " WHOLE_QUAD"); } if (inst.word1.VALID_PIXEL_MODE()) { fmt::format_to(std::back_inserter(out), " VALID_PIX"); } } } // namespace disassembler } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_disassembler_vtx.cpp ================================================ #include "latte/latte_disassembler_state.h" #include <common/decaf_assert.h> #include <fmt/core.h> #include <iterator> namespace latte { namespace disassembler { void disassembleVtxInstruction(fmt::memory_buffer &out, const latte::ControlFlowInst &parent, const VertexFetchInst &vtx, int namePad) { auto id = vtx.word0.VTX_INST(); auto name = getInstructionName(id); fmt::format_to(std::back_inserter(out), "{: <{}} ", name, namePad); // dst auto dstSelX = vtx.word1.DST_SEL_X(); auto dstSelY = vtx.word1.DST_SEL_Y(); auto dstSelZ = vtx.word1.DST_SEL_Z(); auto dstSelW = vtx.word1.DST_SEL_W(); if (dstSelX != latte::SQ_SEL::SEL_MASK || dstSelY != latte::SQ_SEL::SEL_MASK || dstSelZ != latte::SQ_SEL::SEL_MASK || dstSelW != latte::SQ_SEL::SEL_MASK) { if (id == SQ_VTX_INST_SEMANTIC) { fmt::format_to(std::back_inserter(out), "SEM[{}]", vtx.sem.SEMANTIC_ID()); } else { fmt::format_to(std::back_inserter(out), "R{}", vtx.gpr.DST_GPR()); if (vtx.gpr.DST_REL() == SQ_REL::REL) { fmt::format_to(std::back_inserter(out), "[AL]"); } } fmt::format_to(std::back_inserter(out), ".{}{}{}{}", disassembleDestMask(dstSelX), disassembleDestMask(dstSelY), disassembleDestMask(dstSelZ), disassembleDestMask(dstSelW)); } else { fmt::format_to(std::back_inserter(out), "____"); } // src fmt::format_to(std::back_inserter(out), ", R{}", vtx.word0.SRC_GPR()); if (vtx.word0.SRC_REL() == SQ_REL::REL) { fmt::format_to(std::back_inserter(out), "[AL]"); } fmt::format_to(std::back_inserter(out), ".{}, b{}", disassembleDestMask(vtx.word0.SRC_SEL_X()), vtx.word0.BUFFER_ID()); // fetch_type fmt::format_to(std::back_inserter(out), " FETCH_TYPE("); if (vtx.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::VERTEX_DATA) { fmt::format_to(std::back_inserter(out), "VERTEX_DATA"); } else if (vtx.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::INSTANCE_DATA) { fmt::format_to(std::back_inserter(out), "INSTANCE_DATA"); } else if(vtx.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::NO_INDEX_OFFSET) { fmt::format_to(std::back_inserter(out), "NO_INDEX_OFFSET"); } else { fmt::format_to(std::back_inserter(out), "{}", vtx.word0.FETCH_TYPE()); } fmt::format_to(std::back_inserter(out), ")"); // format if (!vtx.word1.USE_CONST_FIELDS()) { fmt::format_to(std::back_inserter(out), " FORMAT("); fmt::format_to(std::back_inserter(out), " {}", vtx.word1.DATA_FORMAT()); if (vtx.word1.NUM_FORMAT_ALL() == 0) { fmt::format_to(std::back_inserter(out), " NORM"); } else if (vtx.word1.NUM_FORMAT_ALL() == 1) { fmt::format_to(std::back_inserter(out), " INT"); } else if (vtx.word1.NUM_FORMAT_ALL() == 2) { fmt::format_to(std::back_inserter(out), " SCALED"); } else { fmt::format_to(std::back_inserter(out), "{}", vtx.word1.NUM_FORMAT_ALL()); } if (vtx.word1.FORMAT_COMP_ALL()) { fmt::format_to(std::back_inserter(out), " SIGNED"); } else { fmt::format_to(std::back_inserter(out), " UNSIGNED"); } fmt::format_to(std::back_inserter(out), " {}", vtx.word1.SRF_MODE_ALL()); fmt::format_to(std::back_inserter(out), ")"); } if (vtx.word2.MEGA_FETCH()) { fmt::format_to(std::back_inserter(out), " MEGA({})", vtx.word0.MEGA_FETCH_COUNT() + 1); } else { fmt::format_to(std::back_inserter(out), " MINI({})", vtx.word0.MEGA_FETCH_COUNT() + 1); } fmt::format_to(std::back_inserter(out), " OFFSET({})", vtx.word2.OFFSET()); if (vtx.word0.FETCH_WHOLE_QUAD()) { fmt::format_to(std::back_inserter(out), " WHOLE_QUAD"); } if (vtx.word2.ENDIAN_SWAP() == SQ_ENDIAN::SWAP_8IN16) { fmt::format_to(std::back_inserter(out), " ENDIAN_SWAP(8IN16)"); } else if (vtx.word2.ENDIAN_SWAP() == SQ_ENDIAN::SWAP_8IN32) { fmt::format_to(std::back_inserter(out), " ENDIAN_SWAP(8IN32)"); } else if (vtx.word2.ENDIAN_SWAP() != SQ_ENDIAN::NONE) { fmt::format_to(std::back_inserter(out), " ENDIAN_SWAP({})", vtx.word2.ENDIAN_SWAP()); } if (vtx.word2.CONST_BUF_NO_STRIDE()) { fmt::format_to(std::back_inserter(out), " CONST_BUF_NO_STRIDE"); } if (vtx.word2.ALT_CONST()) { fmt::format_to(std::back_inserter(out), " ALT_CONST"); } } void disassembleVtxClause(State &state, const latte::ControlFlowInst &inst) { auto addr = inst.word0.ADDR(); auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1; auto clause = reinterpret_cast<const VertexFetchInst *>(state.binary.data() + 8 * addr); increaseIndent(state); for (auto i = 0u; i < count; ++i) { const auto &vtx = clause[i]; fmt::format_to(std::back_inserter(state.out), "\n{}{: <3} ", state.indent, state.groupPC); disassembleVtxInstruction(state.out, inst, vtx, 15); fmt::format_to(std::back_inserter(state.out), "\n"); state.groupPC++; } decreaseIndent(state); } void disassembleCfVTX(fmt::memory_buffer &out, const ControlFlowInst &inst) { auto addr = inst.word0.ADDR(); auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1; fmt::format_to(std::back_inserter(out), ": ADDR({}) CNT({})", addr, count); if (!inst.word1.BARRIER()) { fmt::format_to(std::back_inserter(out), " NO_BARRIER"); } disassembleCondition(out, inst); } } // namespace disassembler } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_endian.h ================================================ #pragma once #include "latte/latte_enum_cb.h" #include <common/byte_swap.h> namespace latte { static inline uint64_t applyEndianSwap(uint64_t value, latte::CB_ENDIAN swap) { static const auto swap16 = [](uint64_t value, int pos) { auto word = static_cast<uint16_t>((value >> pos) & 0xFFFFu); word = byte_swap(word); return static_cast<uint64_t>(word) << pos; }; static const auto swap32 = [](uint64_t value, int pos) { auto word = static_cast<uint32_t>((value >> pos) & 0xFFFFFFFFu); word = byte_swap(word); return static_cast<uint64_t>(word) << pos; }; switch (swap) { case latte::CB_ENDIAN::NONE: break; case latte::CB_ENDIAN::SWAP_8IN16: value = swap16(value, 0) | swap16(value, 16) | swap16(value, 32) | swap16(value, 48); break; case latte::CB_ENDIAN::SWAP_8IN32: value = swap32(value, 0) | swap32(value, 32); break; case latte::CB_ENDIAN::SWAP_8IN64: value = byte_swap(value); break; } return value; } } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_formats.cpp ================================================ #include "latte/latte_formats.h" #include <common/decaf_assert.h> #include <common/log.h> #include <fmt/core.h> namespace latte { enum SurfaceFormatType : uint32_t { Unorm = 0x0, Uint = 0x1, Snorm = 0x2, Sint = 0x3, Srgb = 0x4, Float = 0x8, }; static void validateSurfaceFormat(SurfaceFormat format) { switch (format) { case SurfaceFormat::R8Unorm: case SurfaceFormat::R8Uint: case SurfaceFormat::R8Snorm: case SurfaceFormat::R8Sint: case SurfaceFormat::R4G4Unorm: case SurfaceFormat::R16Unorm: case SurfaceFormat::R16Uint: case SurfaceFormat::R16Snorm: case SurfaceFormat::R16Sint: case SurfaceFormat::R16Float: case SurfaceFormat::R8G8Unorm: case SurfaceFormat::R8G8Uint: case SurfaceFormat::R8G8Snorm: case SurfaceFormat::R8G8Sint: case SurfaceFormat::R5G6B5Unorm: case SurfaceFormat::R5G5B5A1Unorm: case SurfaceFormat::R4G4B4A4Unorm: case SurfaceFormat::A1B5G5R5Unorm: case SurfaceFormat::R32Uint: case SurfaceFormat::R32Sint: case SurfaceFormat::R32Float: case SurfaceFormat::R16G16Unorm: case SurfaceFormat::R16G16Uint: case SurfaceFormat::R16G16Snorm: case SurfaceFormat::R16G16Sint: case SurfaceFormat::R16G16Float: case SurfaceFormat::D24UnormS8Uint: case SurfaceFormat::X24G8Uint: case SurfaceFormat::R11G11B10Float: case SurfaceFormat::R10G10B10A2Unorm: case SurfaceFormat::R10G10B10A2Uint: case SurfaceFormat::R10G10B10A2Snorm: case SurfaceFormat::R10G10B10A2Sint: case SurfaceFormat::R8G8B8A8Unorm: case SurfaceFormat::R8G8B8A8Uint: case SurfaceFormat::R8G8B8A8Snorm: case SurfaceFormat::R8G8B8A8Sint: case SurfaceFormat::R8G8B8A8Srgb: case SurfaceFormat::A2B10G10R10Unorm: case SurfaceFormat::A2B10G10R10Uint: case SurfaceFormat::D32FloatS8UintX24: case SurfaceFormat::D32G8UintX24: case SurfaceFormat::R32G32Uint: case SurfaceFormat::R32G32Sint: case SurfaceFormat::R32G32Float: case SurfaceFormat::R16G16B16A16Unorm: case SurfaceFormat::R16G16B16A16Uint: case SurfaceFormat::R16G16B16A16Snorm: case SurfaceFormat::R16G16B16A16Sint: case SurfaceFormat::R16G16B16A16Float: case SurfaceFormat::R32G32B32A32Uint: case SurfaceFormat::R32G32B32A32Sint: case SurfaceFormat::R32G32B32A32Float: case SurfaceFormat::BC1Unorm: case SurfaceFormat::BC1Srgb: case SurfaceFormat::BC2Unorm: case SurfaceFormat::BC2Srgb: case SurfaceFormat::BC3Unorm: case SurfaceFormat::BC3Srgb: case SurfaceFormat::BC4Unorm: case SurfaceFormat::BC4Snorm: case SurfaceFormat::BC5Unorm: case SurfaceFormat::BC5Snorm: case SurfaceFormat::NV12: return; default: decaf_abort(fmt::format("Unexpected generated surface format {}", format)); } } static SurfaceFormatType getSurfaceFormatType(latte::SQ_NUM_FORMAT numFormat, latte::SQ_FORMAT_COMP formatComp, bool forceDegamma) { if (forceDegamma) { decaf_check(numFormat == latte::SQ_NUM_FORMAT::NORM); decaf_check(formatComp == latte::SQ_FORMAT_COMP::UNSIGNED); return SurfaceFormatType::Srgb; } else { if (numFormat == latte::SQ_NUM_FORMAT::NORM) { if (formatComp == latte::SQ_FORMAT_COMP::UNSIGNED) { return SurfaceFormatType::Unorm; } else if (formatComp == latte::SQ_FORMAT_COMP::SIGNED) { return SurfaceFormatType::Snorm; } else { decaf_abort(fmt::format("Unexpected surface format comp {}", formatComp)); } } else if (numFormat == latte::SQ_NUM_FORMAT::INT) { if (formatComp == latte::SQ_FORMAT_COMP::UNSIGNED) { return SurfaceFormatType::Uint; } else if (formatComp == latte::SQ_FORMAT_COMP::SIGNED) { return SurfaceFormatType::Sint; } else { decaf_abort(fmt::format("Unexpected surface format comp {}", formatComp)); } } else if (numFormat == latte::SQ_NUM_FORMAT::SCALED) { decaf_check(formatComp == latte::SQ_FORMAT_COMP::UNSIGNED); return SurfaceFormatType::Float; } else { decaf_abort(fmt::format("Unexpected surface number format {}", numFormat)); } } } SurfaceFormat getSurfaceFormat(latte::SQ_DATA_FORMAT dataFormat, latte::SQ_NUM_FORMAT numFormat, latte::SQ_FORMAT_COMP formatComp, bool forceDegamma) { if (dataFormat == latte::SQ_DATA_FORMAT::FMT_INVALID) { return latte::SurfaceFormat::Invalid; } auto formatType = getSurfaceFormatType(numFormat, formatComp, forceDegamma); auto formatTypeBits = static_cast<uint32_t>(formatType) << 8; auto dataFormatBits = static_cast<uint32_t>(dataFormat); auto surfaceFormat = static_cast<SurfaceFormat>(formatTypeBits | dataFormatBits); validateSurfaceFormat(surfaceFormat); return surfaceFormat; } latte::SQ_DATA_FORMAT getSurfaceFormatDataFormat(latte::SurfaceFormat format) { return static_cast<latte::SQ_DATA_FORMAT>(format & 0x00FF); } static SurfaceFormatType getColorBufferSurfaceFormatType(latte::CB_NUMBER_TYPE numberType) { switch (numberType) { case latte::CB_NUMBER_TYPE::UNORM: return SurfaceFormatType::Unorm; case latte::CB_NUMBER_TYPE::SNORM: return SurfaceFormatType::Snorm; case latte::CB_NUMBER_TYPE::UINT: return SurfaceFormatType::Uint; case latte::CB_NUMBER_TYPE::SINT: return SurfaceFormatType::Sint; case latte::CB_NUMBER_TYPE::FLOAT: return SurfaceFormatType::Float; case latte::CB_NUMBER_TYPE::SRGB: return SurfaceFormatType::Srgb; default: decaf_abort(fmt::format("Unexpected color buffer number type {}", numberType)); } } SurfaceFormat getColorBufferSurfaceFormat(latte::CB_FORMAT format, latte::CB_NUMBER_TYPE numberType) { // Pick the correct surface format type for this number type auto formatType = getColorBufferSurfaceFormatType(numberType); // The format types belong in the upper portion of the bits auto formatTypeBits = static_cast<uint32_t>(formatType) << 8; // This is safe only becase CB_FORMAT perfectly overlays SQ_DATA_FORMAT auto dataFormatBits = static_cast<uint32_t>(format); // Generate our surface format auto surfaceFormat = static_cast<SurfaceFormat>(formatTypeBits | dataFormatBits); validateSurfaceFormat(surfaceFormat); return surfaceFormat; } SurfaceFormat getDepthBufferSurfaceFormat(latte::DB_FORMAT format) { switch (format) { case latte::DB_FORMAT::DEPTH_16: return SurfaceFormat::R16Unorm; case latte::DB_FORMAT::DEPTH_8_24: return SurfaceFormat::D24UnormS8Uint; //case latte::DB_FORMAT::DEPTH_8_24_FLOAT: // I don't believe this format is supported by the WiiU case latte::DB_FORMAT::DEPTH_32_FLOAT: return SurfaceFormat::R32Float; case latte::DB_FORMAT::DEPTH_X24_8_32_FLOAT: return SurfaceFormat::D32G8UintX24; } decaf_abort(fmt::format("Depth buffer with unsupported format {}", format)); } uint32_t getDataFormatBitsPerElement(latte::SQ_DATA_FORMAT format) { switch (format) { case latte::SQ_DATA_FORMAT::FMT_8: case latte::SQ_DATA_FORMAT::FMT_3_3_2: return 8; case latte::SQ_DATA_FORMAT::FMT_8_8: case latte::SQ_DATA_FORMAT::FMT_16: case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_5_6_5: case latte::SQ_DATA_FORMAT::FMT_5_5_5_1: case latte::SQ_DATA_FORMAT::FMT_1_5_5_5: case latte::SQ_DATA_FORMAT::FMT_4_4_4_4: return 16; case latte::SQ_DATA_FORMAT::FMT_8_8_8: return 24; case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32: case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_10_10_10_2: case latte::SQ_DATA_FORMAT::FMT_2_10_10_10: case latte::SQ_DATA_FORMAT::FMT_10_11_11: case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT: case latte::SQ_DATA_FORMAT::FMT_11_11_10: case latte::SQ_DATA_FORMAT::FMT_11_11_10_FLOAT: case latte::SQ_DATA_FORMAT::FMT_8_24: case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT: return 32; case latte::SQ_DATA_FORMAT::FMT_16_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT: return 48; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_BC1: case latte::SQ_DATA_FORMAT::FMT_BC4: return 64; case latte::SQ_DATA_FORMAT::FMT_32_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT: return 96; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_BC2: case latte::SQ_DATA_FORMAT::FMT_BC3: case latte::SQ_DATA_FORMAT::FMT_BC5: return 128; default: decaf_abort(fmt::format("Unimplemented data format {}", format)); } } bool getDataFormatIsCompressed(latte::SQ_DATA_FORMAT format) { switch (format) { case latte::SQ_DATA_FORMAT::FMT_BC1: case latte::SQ_DATA_FORMAT::FMT_BC2: case latte::SQ_DATA_FORMAT::FMT_BC3: case latte::SQ_DATA_FORMAT::FMT_BC4: case latte::SQ_DATA_FORMAT::FMT_BC5: return true; default: return false; } } std::string getDataFormatName(latte::SQ_DATA_FORMAT format) { switch (format) { case latte::SQ_DATA_FORMAT::FMT_8: return "FMT_8"; case latte::SQ_DATA_FORMAT::FMT_16: return "FMT_16"; case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: return "FMT_16_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_32: return "FMT_32"; case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: return "FMT_32_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_8_8: return "FMT_8_8"; case latte::SQ_DATA_FORMAT::FMT_16_16: return "FMT_16_16"; case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: return "FMT_16_16_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_32_32: return "FMT_32_32"; case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: return "FMT_32_32_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_8_8_8: return "FMT_8_8_8"; case latte::SQ_DATA_FORMAT::FMT_16_16_16: return "FMT_16_16_16"; case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT: return "FMT_16_16_16_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_32_32_32: return "FMT_32_32_32"; case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT: return "FMT_32_32_32_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: return "FMT_8_8_8_8"; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: return "FMT_16_16_16_16"; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: return "FMT_16_16_16_16_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: return "FMT_32_32_32_32"; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: return "FMT_32_32_32_32_FLOAT"; case latte::SQ_DATA_FORMAT::FMT_2_10_10_10: return "FMT_2_10_10_10"; case latte::SQ_DATA_FORMAT::FMT_10_10_10_2: return "FMT_10_10_10_2"; default: decaf_abort(fmt::format("Unimplemented attribute format: {}", format)); } } DataFormatMeta getDataFormatMeta(latte::SQ_DATA_FORMAT format) { static const auto DFT_UINT = DataFormatMetaType::UINT; static const auto DFT_FLOAT = DataFormatMetaType::FLOAT; static const auto BADELEM = DataFormatMetaElem { 0, 0, 0 }; // Note: In order to reduce the likelyhood of encountering an unsupported // 8-bit type in the SPIRV, we intentionally collapse some types... switch (format) { case latte::SQ_DATA_FORMAT::FMT_8: return{ 8, 1, DFT_UINT, {{ 0, 0, 8 }, BADELEM, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_4_4: return{ 8, 1, DFT_UINT, {{0, 0, 4}, {0, 4, 4}, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_3_3_2: return{ 8, 1, DFT_UINT, {{0, 0, 3}, {0, 3, 3}, {0, 6, 2}, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16: return{ 16, 1,DFT_UINT, {{ 0, 0, 16 }, BADELEM, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: return{ 16, 1,DFT_FLOAT, {{ 0, 0, 16 }, BADELEM, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_8_8: return{ 16, 1, DFT_UINT, {{ 0, 0, 8 },{ 0, 8, 8 }, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_5_6_5: return{ 16, 1, DFT_UINT, {{0, 0, 5}, {0, 5, 6}, {0, 11, 5}, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_6_5_5: return{ 16, 1, DFT_UINT, {{ 0, 0, 6 },{ 0, 6, 5 },{ 0, 11, 5 }, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_1_5_5_5: return{ 16, 1, DFT_UINT, {{0, 11, 5}, {0, 6, 5}, {0, 1, 5}, {0, 0, 1} } }; case latte::SQ_DATA_FORMAT::FMT_4_4_4_4: return{ 16, 1, DFT_UINT, {{0, 0, 4}, {0, 4, 4}, {0, 8, 4}, {0, 12, 4} } }; case latte::SQ_DATA_FORMAT::FMT_5_5_5_1: return{ 16, 1, DFT_UINT, {{0, 0, 5}, {0, 5, 5}, {0, 10, 5}, {0, 15, 1} } }; case latte::SQ_DATA_FORMAT::FMT_32: return{ 32, 1,DFT_UINT, {{ 0, 0, 32 }, BADELEM, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: return{ 32, 1,DFT_FLOAT, {{ 0, 0, 32 }, BADELEM, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16_16: return{ 16, 2,DFT_UINT, {{ 0, 0, 16 },{ 1, 0, 16 }, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: return{ 16, 2,DFT_FLOAT, {{ 0, 0, 16 },{ 1, 0, 16 }, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_8_24: return{ 32, 1, DFT_UINT, {{0, 0, 8}, {0, 8, 24}, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT: return{ 32, 1, DFT_FLOAT, {{ 0, 0, 8 }, { 0, 8, 24 }, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_24_8: return{ 32, 1, DFT_UINT, {{0, 0, 24}, {0, 24, 8}, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_24_8_FLOAT: return{ 32, 1, DFT_FLOAT, {{0, 0, 24}, {0, 24, 8}, BADELEM, BADELEM } }; /*case latte::SQ_DATA_FORMAT::FMT_10_11_11: return{ 32, 1, DFT_UINT, {{0, 0, 11}, {0, 11, 11}, {0, 22, 10}, BADELEM } };*/ case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT: return{ 32, 1, DFT_FLOAT,{{0, 0, 11}, {0, 11, 11}, {0, 22, 10}, BADELEM } }; // This attribute format appears to oddly have the same layout as FMT_10_10_10_2? case latte::SQ_DATA_FORMAT::FMT_2_10_10_10: return{ 32, 1, DFT_UINT, {{0, 0, 10}, {0, 10, 10}, { 0, 20, 10 }, {0, 30, 2} } }; case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: return{ 32, 1, DFT_UINT,{{ 0, 0, 8 },{ 0, 8, 8 },{ 0, 16, 8 },{0, 24, 8 } } }; case latte::SQ_DATA_FORMAT::FMT_10_10_10_2: return{ 32, 1, DFT_UINT, {{0, 0, 10}, {0, 10, 10}, {0, 20, 10}, {0, 30, 2} } }; case latte::SQ_DATA_FORMAT::FMT_X24_8_32_FLOAT: return{ 32, 2, DFT_FLOAT, {{0, 24, 8}, {1, 0, 32}, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_32_32: return{ 32, 2,DFT_UINT,{{ 0, 0, 32 },{ 1, 0, 32 }, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: return{ 32, 2,DFT_FLOAT,{{ 0, 0, 32 },{ 1, 0, 32 }, BADELEM, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: return{ 16, 4,DFT_UINT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 },{ 3, 0, 16 } } }; case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: return{ 16, 4,DFT_FLOAT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 },{ 3, 0, 16 } } }; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: return{ 32, 4,DFT_UINT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 },{ 3, 0, 32 } } }; case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: return{ 32, 4,DFT_FLOAT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 },{ 3, 0, 32 } } }; //case latte::SQ_DATA_FORMAT::FMT_1: //case latte::SQ_DATA_FORMAT::FMT_GB_GR: //case latte::SQ_DATA_FORMAT::FMT_BG_RG: //case latte::SQ_DATA_FORMAT::FMT_32_AS_8: //case latte::SQ_DATA_FORMAT::FMT_32_AS_8_8: //case latte::SQ_DATA_FORMAT::FMT_5_9_9_9_SHAREDEXP: case latte::SQ_DATA_FORMAT::FMT_8_8_8: return{ 8, 3,DFT_UINT,{{ 0, 0, 8 },{ 1, 0, 8 },{ 2, 0, 8 }, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16_16_16: return{ 16, 3,DFT_UINT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 }, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT: return{ 16, 3,DFT_FLOAT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 }, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_32_32_32: return{ 32, 3,DFT_UINT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 }, BADELEM } }; case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT: return{ 32, 3,DFT_FLOAT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 }, BADELEM } }; //case latte::SQ_DATA_FORMAT::FMT_BC1: //case latte::SQ_DATA_FORMAT::FMT_BC2: //case latte::SQ_DATA_FORMAT::FMT_BC3: //case latte::SQ_DATA_FORMAT::FMT_BC4: //case latte::SQ_DATA_FORMAT::FMT_BC5: //case latte::SQ_DATA_FORMAT::FMT_APC0: //case latte::SQ_DATA_FORMAT::FMT_APC1: //case latte::SQ_DATA_FORMAT::FMT_APC2: //case latte::SQ_DATA_FORMAT::FMT_APC3: //case latte::SQ_DATA_FORMAT::FMT_APC4: //case latte::SQ_DATA_FORMAT::FMT_APC5: //case latte::SQ_DATA_FORMAT::FMT_APC6: //case latte::SQ_DATA_FORMAT::FMT_APC7: //case latte::SQ_DATA_FORMAT::FMT_CTX1: default: decaf_abort(fmt::format("Unimplemented attribute format: {}", format)); } } uint32_t getDataFormatComponents(latte::SQ_DATA_FORMAT format) { switch (format) { case latte::SQ_DATA_FORMAT::FMT_8: case latte::SQ_DATA_FORMAT::FMT_16: case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32: case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: return 1; case latte::SQ_DATA_FORMAT::FMT_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: return 2; case latte::SQ_DATA_FORMAT::FMT_8_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT: return 3; case latte::SQ_DATA_FORMAT::FMT_2_10_10_10: case latte::SQ_DATA_FORMAT::FMT_10_10_10_2: case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: return 4; default: decaf_abort(fmt::format("Unimplemented attribute format: {}", format)); } } uint32_t getDataFormatComponentBits(latte::SQ_DATA_FORMAT format) { switch (format) { case latte::SQ_DATA_FORMAT::FMT_8: case latte::SQ_DATA_FORMAT::FMT_8_8: case latte::SQ_DATA_FORMAT::FMT_8_8_8: case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: return 8; case latte::SQ_DATA_FORMAT::FMT_16: case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_16_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: return 16; case latte::SQ_DATA_FORMAT::FMT_32: case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: return 32; default: decaf_abort(fmt::format("Unimplemented attribute format: {}", format)); } } bool getDataFormatIsFloat(latte::SQ_DATA_FORMAT format) { switch (format) { case latte::SQ_DATA_FORMAT::FMT_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT: return true; case latte::SQ_DATA_FORMAT::FMT_8: case latte::SQ_DATA_FORMAT::FMT_16: case latte::SQ_DATA_FORMAT::FMT_32: case latte::SQ_DATA_FORMAT::FMT_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16: case latte::SQ_DATA_FORMAT::FMT_32_32: case latte::SQ_DATA_FORMAT::FMT_8_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16_16: case latte::SQ_DATA_FORMAT::FMT_32_32_32: case latte::SQ_DATA_FORMAT::FMT_2_10_10_10: case latte::SQ_DATA_FORMAT::FMT_10_10_10_2: case latte::SQ_DATA_FORMAT::FMT_8_8_8_8: case latte::SQ_DATA_FORMAT::FMT_16_16_16_16: case latte::SQ_DATA_FORMAT::FMT_32_32_32_32: return false; default: decaf_abort(fmt::format("Unimplemented attribute format: {}", format)); } } uint32_t getTexDimDimensions(latte::SQ_TEX_DIM dim) { switch (dim) { case latte::SQ_TEX_DIM::DIM_1D: return 1; case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: case latte::SQ_TEX_DIM::DIM_1D_ARRAY: return 2; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_CUBEMAP: case latte::SQ_TEX_DIM::DIM_3D: return 3; break; default: decaf_abort(fmt::format("Unsupported texture dim: {}", dim)); } } latte::SQ_TILE_MODE getArrayModeTileMode(latte::BUFFER_ARRAY_MODE mode) { // The buffer array modes match up with our SQ_TILE_MODE's perfectly. return static_cast<latte::SQ_TILE_MODE>(mode); } } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_instructions.cpp ================================================ #include "latte/latte_instructions.h" namespace latte { #undef ALU_REDUC #undef ALU_VEC #undef ALU_TRANS #undef ALU_PRED_SET #undef ALU_INT #undef ALU_UINT #undef ALU_INT_IN #undef ALU_UINT_IN #undef ALU_INT_OUT #undef ALU_UINT_OUT #define ALU_REDUC SQ_ALU_FLAG_REDUCTION #define ALU_VEC SQ_ALU_FLAG_VECTOR #define ALU_TRANS SQ_ALU_FLAG_TRANSCENDENTAL #define ALU_PRED_SET SQ_ALU_FLAG_PRED_SET #define ALU_INT_IN SQ_ALU_FLAG_INT_IN #define ALU_INT_OUT SQ_ALU_FLAG_INT_OUT #define ALU_UINT_IN SQ_ALU_FLAG_UINT_IN #define ALU_UINT_OUT SQ_ALU_FLAG_UINT_OUT #define ALU_INT SQ_ALU_FLAG_INT_IN | SQ_ALU_FLAG_INT_OUT #define ALU_UINT SQ_ALU_FLAG_UINT_IN | SQ_ALU_FLAG_UINT_OUT const char *getInstructionName(SQ_CF_INST id) { switch (id) { #define CF_INST(name, value) case SQ_CF_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef CF_INST default: return "UNKNOWN"; } } const char *getInstructionName(SQ_CF_EXP_INST id) { switch (id) { #define EXP_INST(name, value) case SQ_CF_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef EXP_INST default: return "UNKNOWN"; } } const char *getInstructionName(SQ_CF_ALU_INST id) { switch (id) { #define ALU_INST(name, value) case SQ_CF_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef ALU_INST default: return "UNKNOWN"; } } const char *getInstructionName(SQ_OP2_INST id) { switch (id) { #define ALU_OP2(name, value, srcs, flags) case SQ_OP2_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef ALU_OP2 default: return "UNKNOWN"; } } const char *getInstructionName(SQ_OP3_INST id) { switch (id) { #define ALU_OP3(name, value, srcs, flags) case SQ_OP3_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef ALU_OP3 default: return "UNKNOWN"; } } const char *getInstructionName(SQ_TEX_INST id) { switch (id) { #define TEX_INST(name, value) case SQ_TEX_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef TEX_INST default: return "UNKNOWN"; } } const char *getInstructionName(SQ_VTX_INST id) { switch (id) { #define VTX_INST(name, value) case SQ_VTX_INST_##name: return #name; #include "latte/latte_instructions_def.inl" #undef VTX_INST default: return "UNKNOWN"; } } uint32_t getInstructionNumSrcs(SQ_OP2_INST id) { switch (id) { #define ALU_OP2(name, value, srcs, flags) case SQ_OP2_INST_##name: return srcs; #include "latte/latte_instructions_def.inl" #undef ALU_OP2 default: return 0; } } uint32_t getInstructionNumSrcs(SQ_OP3_INST id) { switch (id) { #define ALU_OP3(name, value, srcs, flags) case SQ_OP3_INST_##name: return srcs; #include "latte/latte_instructions_def.inl" #undef ALU_OP3 default: return 0; } } SQ_ALU_FLAGS getInstructionFlags(SQ_OP2_INST id) { switch (id) { #define ALU_OP2(name, value, srcs, flags) case SQ_OP2_INST_##name: return static_cast<SQ_ALU_FLAGS>(flags); #include "latte/latte_instructions_def.inl" #undef ALU_OP2 default: return static_cast<SQ_ALU_FLAGS>(0); } } SQ_ALU_FLAGS getInstructionFlags(SQ_OP3_INST id) { switch (id) { #define ALU_OP3(name, value, srcs, flags) case SQ_OP3_INST_##name: return static_cast<SQ_ALU_FLAGS>(flags); #include "latte/latte_instructions_def.inl" #undef ALU_OP3 default: return static_cast<SQ_ALU_FLAGS>(0); } } } // namespace latte ================================================ FILE: src/libgpu/src/latte/latte_shaderparser.h ================================================ #pragma once #include "latte/latte_constants.h" #include "latte/latte_instructions.h" #include "latte/latte_registers_spi.h" #include "latte/latte_registers_sq.h" #include "latte_decoders.h" #include <common/decaf_assert.h> #include <cstdint> #include <gsl/gsl-lite.hpp> namespace latte { class ShaderParser { public: enum class Type : uint32_t { Unknown, Fetch, Vertex, Geometry, DataCache, Pixel }; #define TEX_INST(x, ...) \ virtual void translateTex_##x(const ControlFlowInst &cf, const TextureFetchInst &inst) { \ decaf_abort("Unimplemented TEX instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef TEX_INST #define VTX_INST(x, ...) \ virtual void translateVtx_##x(const ControlFlowInst &cf, const VertexFetchInst &ist) { \ decaf_abort("Unimplemented VTX instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef VTX_INST #define ALU_OP2(x, ...) \ virtual void translateAluOp2_##x(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { \ decaf_abort("Unimplemented ALU OP2 instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef ALU_OP2 #define ALU_OP3(x, ...) \ virtual void translateAluOp3_##x(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { \ decaf_abort("Unimplemented ALU OP3 instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef ALU_OP3 #define CF_INST(x, ...) \ virtual void translateCf_##x(const ControlFlowInst &cf) { \ decaf_abort("Unimplemented CF NORMAL instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef CF_INST #define EXP_INST(x, ...) \ virtual void translateCf_##x(const ControlFlowInst &cf) { \ decaf_abort("Unimplemented CF EXPORT instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef EXP_INST #define ALU_INST(x, ...) \ virtual void translateCf_##x(const ControlFlowInst &cf) { \ decaf_abort("Unimplemented CF ALU instruction "#x); \ } #include "latte/latte_instructions_def.inl" #undef ALU_INST virtual void translateTexInst(const ControlFlowInst &cf, const TextureFetchInst &inst) { switch (inst.word0.TEX_INST()) { #define TEX_INST(x, ...) \ case latte::SQ_TEX_INST_##x: \ translateTex_##x(cf, inst); \ break; #include "latte/latte_instructions_def.inl" #undef TEX_INST default: decaf_abort("Unexpected TEX instruction"); } } virtual void translateVtxInst(const ControlFlowInst &cf, const VertexFetchInst &inst) { switch (inst.word0.VTX_INST()) { #define VTX_INST(x, ...) \ case latte::SQ_VTX_INST_##x: \ translateVtx_##x(cf, inst); \ break; #include "latte/latte_instructions_def.inl" #undef VTX_INST default: decaf_abort("Unexpected VTX instruction"); } } virtual void translateAluInst(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { const AluInst *instX = &inst; bool isReducInst = (unit == 0xffffffff); if (isReducInst) { instX = group.units[SQ_CHAN::X]; } if (instX->word1.ENCODING() == SQ_ALU_ENCODING::OP2) { switch (instX->op2.ALU_INST()) { #define ALU_OP2(x, ...) \ case latte::SQ_OP2_INST_##x: \ translateAluOp2_##x(cf, group, unit, inst); \ break; #include "latte/latte_instructions_def.inl" #undef ALU_OP2 default: decaf_abort("Unexpected ALU OP2 instruction"); } } else { switch (instX->op3.ALU_INST()) { #define ALU_OP3(x, ...) \ case latte::SQ_OP3_INST_##x: \ translateAluOp3_##x(cf, group, unit, inst); \ break; #include "latte/latte_instructions_def.inl" #undef ALU_OP3 default: decaf_abort("Unexpected ALU OP3 instruction"); } } } virtual void translateTexClause(const ControlFlowInst &cf) { auto addr = cf.word0.ADDR(); auto count = (cf.word1.COUNT() | (cf.word1.COUNT_3() << 3)) + 1; auto texInstData = reinterpret_cast<const TextureFetchInst *>(mBinary.data() + 8 * addr); auto texInsts = gsl::make_span(texInstData, count); for (auto i = 0; i < texInsts.size(); ++i) { translateTexInst(cf, texInsts[i]); mTexVtxPC++; } } virtual void translateVtxClause(const ControlFlowInst &cf) { auto addr = cf.word0.ADDR(); auto count = (cf.word1.COUNT() | (cf.word1.COUNT_3() << 3)) + 1; auto vtxInstData = reinterpret_cast<const VertexFetchInst *>(mBinary.data() + 8 * addr); auto vtxInsts = gsl::make_span(vtxInstData, count); for (auto i = 0; i < vtxInsts.size(); ++i) { translateVtxInst(cf, vtxInsts[i]); mTexVtxPC++; } } virtual void translateAluGroup(const ControlFlowInst &cf, const AluInstructionGroup &group) { auto aluUnitIdx = 0; if (isReductionInst(*group.units[0])) { // For sanity, let's ensure every instruction in this reduction group // has the same instruction id and the same CLAMP + OMOD values for (auto i = 1u; i < 4; ++i) { if (group.units[0]->word1.ENCODING() == SQ_ALU_ENCODING::OP2) { if (group.units[i]->op2.ALU_INST() != group.units[0]->op2.ALU_INST()) { decaf_abort("Expected every instruction in reduction group to be the same."); } if (group.units[i]->op2.OMOD() != group.units[0]->op2.OMOD()) { decaf_abort("Expected every instruction in reduction group to have the same output modifier."); } } else { if (group.units[i]->op3.ALU_INST() != group.units[0]->op3.ALU_INST()) { decaf_abort("Expected every instruction in reduction group to be the same."); } } if (group.units[i]->word1.CLAMP() != group.units[0]->word1.CLAMP()) { decaf_abort("Expected every instruction in reduction group to have the same clamp value."); } } // Translate the reduction by doing executing the ALU instruction only // once for the whole reduction. We pass an invalid instruction to try // and avoid having people accidentally use it... static const uint32_t InvalidInstData[] = { 0xffffffff, 0xffffffff }; static const auto InvalidInstPtr = reinterpret_cast<const AluInst *>(InvalidInstData); translateAluInst(cf, group, static_cast<SQ_CHAN>(0xffffffff), *InvalidInstPtr); // Skip the 4 units we just processed as a reduction instruction aluUnitIdx += 4; } for (; aluUnitIdx < 5; ++aluUnitIdx) { auto &inst = *group.units[aluUnitIdx]; // Should never encounter reduction instructions during normal ALU // instruction processing after the above logic. decaf_check(!isReductionInst(inst)); // Translate the ALU instruction translateAluInst(cf, group, static_cast<SQ_CHAN>(aluUnitIdx), inst); if (inst.word0.LAST()) { break; } } } virtual void translateAluClause(const ControlFlowInst &cf) { auto addr = cf.alu.word0.ADDR(); auto count = cf.alu.word1.COUNT() + 1; auto aluInstData = reinterpret_cast<const AluInst *>(mBinary.data() + 8 * addr); auto aluInsts = gsl::make_span(aluInstData, count); AluClauseParser clauseParser(aluInsts, mAluInstPreferVector); while (!clauseParser.isEndOfClause()) { auto group = clauseParser.readOneGroup(); translateAluGroup(cf, group); mGroupPC++; } } virtual void translateCfNormalInst(const ControlFlowInst &cf) { switch (cf.word1.CF_INST()) { #define CF_INST(x, ...) \ case latte::SQ_CF_INST_##x: \ translateCf_##x(cf); \ break; #include "latte/latte_instructions_def.inl" #undef CF_INST default: decaf_abort("Unexpected CF NORMAL instruction id"); } // We need special handling for a couple of the instruction types // as they affect the control flow of the parser. switch (cf.word1.CF_INST()) { case SQ_CF_INST_RETURN: // RETURN inside a non-function block doesn't make any sense decaf_check(mIsFunction); // Mark EOP as having been reached mReachedEop = true; } // Handle potential END_OF_PROGRAM bit being set if (cf.word1.END_OF_PROGRAM()) { mReachedEop = true; } } virtual void translateCfExportInst(const ControlFlowInst& cf) { switch (cf.exp.word1.CF_INST()) { #define EXP_INST(x, ...) \ case latte::SQ_CF_INST_##x: \ translateCf_##x(cf); \ break; #include "latte/latte_instructions_def.inl" #undef EXP_INST default: decaf_abort("Unexpected CF EXPORT instruction id"); } // Handle potential END_OF_PROGRAM bit being set if (cf.word1.END_OF_PROGRAM()) { mReachedEop = true; } } virtual void translateCfAluInst(const ControlFlowInst &cf) { switch (cf.alu.word1.CF_INST()) { #define ALU_INST(x, ...) \ case latte::SQ_CF_INST_##x: \ translateCf_##x(cf); \ break; #include "latte/latte_instructions_def.inl" #undef ALU_INST default: decaf_abort("Unexpected CF ALU instruction id"); } } virtual void translateCfInst(const ControlFlowInst& cf) { switch (cf.word1.CF_INST_TYPE()) { case SQ_CF_INST_TYPE_NORMAL: translateCfNormalInst(cf); break; case SQ_CF_INST_TYPE_EXPORT: translateCfExportInst(cf); break; case SQ_CF_INST_TYPE_ALU: case SQ_CF_INST_TYPE_ALU_EXTENDED: translateCfAluInst(cf); break; default: decaf_abort("Unexpected CF instruction type"); } } virtual void translate() { decaf_check(mType != Type::Unknown); decaf_check(!mBinary.empty()); if (mType == Type::Fetch) { mIsFunction = true; } for (auto i = 0; i < mBinary.size() && !mReachedEop; i += sizeof(ControlFlowInst)) { auto cf = *reinterpret_cast<const ControlFlowInst *>(mBinary.data() + i); translateCfInst(cf); mCfPC++; } } virtual void reset() { mIsFunction = false; mReachedEop = false; mCfPC = 0; mGroupPC = 0; mTexVtxPC = 0; } protected: // Input Type mType = Type::Unknown; gsl::span<const uint8_t> mBinary; bool mAluInstPreferVector; // Temporaries bool mIsFunction = false; bool mReachedEop = false; uint32_t mCfPC = 0; uint32_t mGroupPC = 0; uint32_t mTexVtxPC = 0; }; } // namespace latte ================================================ FILE: src/libgpu/src/null/null_driver.cpp ================================================ #include "null_driver.h" #include "gpu_event.h" #include "gpu_ringbuffer.h" #include <common/decaf_assert.h> namespace null { void Driver::setWindowSystemInfo(const gpu::WindowSystemInfo &wsi) { } void Driver::windowHandleChanged(void *handle) { } void Driver::windowSizeChanged(int width, int height) { } void Driver::run() { mRunning = true; while (mRunning) { if (gpu::ringbuffer::wait()) { auto items = gpu::ringbuffer::read(); // TODO: We need to actually process pm4 to do EVENT_WRITE_EOP for retired timestamps } } } void Driver::runUntilFlip() { decaf_abort("NullDriver::runUntilFlip unimplemented"); } void Driver::stop() { mRunning = false; gpu::ringbuffer::wake(); } gpu::GraphicsDriverType Driver::type() { return gpu::GraphicsDriverType::Null; } gpu::GraphicsDriverDebugInfo * Driver::getDebugInfo() { return nullptr; } void Driver::notifyCpuFlush(phys_addr address, uint32_t size) { } void Driver::notifyGpuFlush(phys_addr address, uint32_t size) { } } // namespace null ================================================ FILE: src/libgpu/src/null/null_driver.h ================================================ #pragma once #include "gpu_graphicsdriver.h" namespace null { class Driver : public gpu::GraphicsDriver { public: virtual ~Driver() = default; virtual void setWindowSystemInfo(const gpu::WindowSystemInfo &wsi) override; virtual void windowHandleChanged(void *handle) override; virtual void windowSizeChanged(int width, int height) override; virtual void run() override; virtual void runUntilFlip() override; virtual void stop() override; virtual gpu::GraphicsDriverType type() override; virtual gpu::GraphicsDriverDebugInfo *getDebugInfo() override; virtual void notifyCpuFlush(phys_addr address, uint32_t size) override; virtual void notifyGpuFlush(phys_addr address, uint32_t size) override; private: bool mRunning = false; }; } // namespace null ================================================ FILE: src/libgpu/src/pm4_processor.cpp ================================================ #include "latte/latte_pm4_reader.h" #include "gpu_memory.h" #include "pm4_processor.h" #include <common/byte_swap_array.h> #include <common/log.h> #include <libcpu/mmu.h> void Pm4Processor::indirectBufferCall(const IndirectBufferCall &data) { auto buffer = gpu::internal::translateAddress<uint32_t>(data.addr); runCommandBuffer({ buffer, data.size }); } void Pm4Processor::indirectBufferCallPriv(const IndirectBufferCallPriv &data) { auto buffer = gpu::internal::translateAddress<uint32_t>(data.addr); runCommandBuffer({ buffer, data.size }); } void Pm4Processor::runCommandBuffer(const gpu::ringbuffer::Buffer &buffer) { decaf_check(mSwapScratchDepth + 1 < MaxPm4IndirectDepth); auto& scratchBuffer = mSwapScratch[mSwapScratchDepth++]; auto numDwords = buffer.size(); auto swappedBytes = byte_swap_to_scratch<uint32_t>( buffer.data(), static_cast<uint32_t>(buffer.size_bytes()), scratchBuffer); auto swapped = reinterpret_cast<uint32_t*>(swappedBytes); for (auto pos = 0u; pos < numDwords; ) { auto header = *reinterpret_cast<Header *>(&swapped[pos]); auto size = 0u; if (swapped[pos] == 0) { break; } switch (header.type()) { case PacketType::Type3: { auto header3 = HeaderType3::get(header.value); size = header3.size() + 1; decaf_check(pos + size <= numDwords); handlePacketType3(header3, gsl::make_span(&swapped[pos + 1], size)); break; } case PacketType::Type0: { auto header0 = HeaderType0::get(header.value); size = header0.count() + 1; decaf_check(pos + size <= numDwords); handlePacketType0(header0, gsl::make_span(&swapped[pos + 1], size)); break; } case PacketType::Type2: { // Filler packet, ignore break; } case PacketType::Type1: default: gLog->error("Invalid packet header type {}, header = 0x{:08X}", header.type(), header.value); pos = static_cast<uint32_t>(numDwords); break; } pos += size + 1; } mSwapScratchDepth--; } void Pm4Processor::handlePacketType0(HeaderType0 header, const gsl::span<uint32_t> &data) { auto base = static_cast<latte::Register>(header.baseIndex() * 4); setRegisters(base, data); } void Pm4Processor::handlePacketType3(HeaderType3 header, const gsl::span<uint32_t> &data) { PacketReader reader{ data }; switch (header.opcode()) { case IT_OPCODE::DECAF_COPY_COLOR_TO_SCAN: decafCopyColorToScan(read<DecafCopyColorToScan>(reader)); break; case IT_OPCODE::DECAF_SWAP_BUFFERS: decafSwapBuffers(read<DecafSwapBuffers>(reader)); break; case IT_OPCODE::DECAF_CLEAR_COLOR: decafClearColor(read<DecafClearColor>(reader)); break; case IT_OPCODE::DECAF_CLEAR_DEPTH_STENCIL: decafClearDepthStencil(read<DecafClearDepthStencil>(reader)); break; case IT_OPCODE::DECAF_SET_BUFFER: decafSetBuffer(read<DecafSetBuffer>(reader)); break; case IT_OPCODE::DECAF_OSSCREEN_FLIP: decafOSScreenFlip(read<DecafOSScreenFlip>(reader)); break; case IT_OPCODE::DECAF_COPY_SURFACE: decafCopySurface(read<DecafCopySurface>(reader)); break; case IT_OPCODE::DECAF_EXPAND_COLORBUFFER: decafExpandColorBuffer(read<DecafExpandColorBuffer>(reader)); break; case IT_OPCODE::DRAW_INDEX_AUTO: drawIndexAuto(read<DrawIndexAuto>(reader)); break; case IT_OPCODE::DRAW_INDEX_2: drawIndex2(read<DrawIndex2>(reader)); break; case IT_OPCODE::DRAW_INDEX_IMMD: drawIndexImmd(read<DrawIndexImmd>(reader)); break; case IT_OPCODE::INDEX_TYPE: indexType(read<IndexType>(reader)); break; case IT_OPCODE::NUM_INSTANCES: numInstances(read<NumInstances>(reader)); break; case IT_OPCODE::SET_ALU_CONST: setAluConsts(read<SetAluConsts>(reader)); break; case IT_OPCODE::SET_CONFIG_REG: setConfigRegs(read<SetConfigRegs>(reader)); break; case IT_OPCODE::SET_CONTEXT_REG: setContextRegs(read<SetContextRegs>(reader)); break; case IT_OPCODE::SET_CTL_CONST: setControlConstants(read<SetControlConstants>(reader)); break; case IT_OPCODE::SET_LOOP_CONST: setLoopConsts(read<SetLoopConsts>(reader)); break; case IT_OPCODE::SET_SAMPLER: setSamplers(read<SetSamplers>(reader)); break; case IT_OPCODE::SET_RESOURCE: setResources(read<SetResources>(reader)); break; case IT_OPCODE::LOAD_CONFIG_REG: loadConfigRegs(read<LoadConfigReg>(reader)); break; case IT_OPCODE::LOAD_CONTEXT_REG: loadContextRegs(read<LoadContextReg>(reader)); break; case IT_OPCODE::LOAD_ALU_CONST: loadAluConsts(read<LoadAluConst>(reader)); break; case IT_OPCODE::LOAD_BOOL_CONST: loadBoolConsts(read<LoadBoolConst>(reader)); break; case IT_OPCODE::LOAD_LOOP_CONST: loadLoopConsts(read<LoadLoopConst>(reader)); break; case IT_OPCODE::LOAD_RESOURCE: loadResources(read<latte::pm4::LoadResource>(reader)); break; case IT_OPCODE::LOAD_SAMPLER: loadSamplers(read<LoadSampler>(reader)); break; case IT_OPCODE::LOAD_CTL_CONST: loadControlConstants(read<LoadControlConst>(reader)); break; case IT_OPCODE::INDIRECT_BUFFER: indirectBufferCall(read<IndirectBufferCall>(reader)); break; case IT_OPCODE::INDIRECT_BUFFER_PRIV: indirectBufferCallPriv(read<IndirectBufferCallPriv>(reader)); break; case IT_OPCODE::WAIT_REG_MEM: waitMem(read<WaitMem>(reader)); break; case IT_OPCODE::MEM_WRITE: memWrite(read<MemWrite>(reader)); break; case IT_OPCODE::EVENT_WRITE: eventWrite(read<EventWrite>(reader)); break; case IT_OPCODE::EVENT_WRITE_EOP: eventWriteEOP(read<EventWriteEOP>(reader)); break; case IT_OPCODE::PFP_SYNC_ME: pfpSyncMe(read<PfpSyncMe>(reader)); break; case IT_OPCODE::SET_PREDICATION: setPredication(read<SetPredication>(reader)); break; case IT_OPCODE::STRMOUT_BASE_UPDATE: streamOutBaseUpdate(read<StreamOutBaseUpdate>(reader)); break; case IT_OPCODE::STRMOUT_BUFFER_UPDATE: streamOutBufferUpdate(read<StreamOutBufferUpdate>(reader)); break; case IT_OPCODE::NOP: nopPacket(read<Nop>(reader)); break; case IT_OPCODE::SURFACE_SYNC: surfaceSync(read<SurfaceSync>(reader)); break; case IT_OPCODE::CONTEXT_CTL: contextControl(read<ContextControl>(reader)); break; case IT_OPCODE::COPY_DW: copyDw(read<CopyDw>(reader)); break; default: gLog->debug("Unhandled pm4 packet type 3 opcode {}", header.opcode()); } } void Pm4Processor::nopPacket(const Nop &data) { auto str = std::string{}; if (data.strWords.size()) { for (auto i = 0u; i < data.strWords.size(); ++i) { auto word = data.strWords[i]; for (auto c = 0u; c < 4; ++c) { auto chr = static_cast<char>((word >> (c * 8)) & 0xFF); if (!chr) { break; } str.push_back(chr); } } } if (false) { gLog->debug("NOP unk: {} str: {}", data.unk, str); } } void Pm4Processor::indexType(const IndexType &data) { mRegisters[latte::Register::VGT_INDEX_TYPE / 4] = data.type.value; mRegisters[latte::Register::VGT_DMA_INDEX_TYPE / 4] = data.type.value; } void Pm4Processor::numInstances(const NumInstances &data) { mRegisters[latte::Register::VGT_DMA_NUM_INSTANCES / 4] = data.count; } void Pm4Processor::contextControl(const ContextControl &data) { mShadowState.LOAD_CONTROL = data.LOAD_CONTROL; mShadowState.SHADOW_ENABLE = data.SHADOW_ENABLE; } void Pm4Processor::copyDw(const CopyDw &data) { decaf_check(data.select.SRC() == latte::pm4::COPY_DW_SEL_MEMORY); decaf_check(data.select.DST() == latte::pm4::COPY_DW_SEL_REGISTER); decaf_check(data.dstLo == latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE); mRegAddr_VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE = data.srcLo; } void Pm4Processor::shadowWrite(phys_ptr<uint32_t> memory, const gsl::span<uint32_t> ®isters) { // Shadow memory is not byte-swapped memcpy(memory.getRawPointer(), registers.data(), registers.size_bytes()); } void Pm4Processor::setRegisters(latte::Register base, const gsl::span<uint32_t> &values) { if (latte::Register::SQ_VTX_SEMANTIC_CLEAR >= base && latte::Register::SQ_VTX_SEMANTIC_CLEAR < base + values.size_bytes()) { auto clearRegBase = latte::Register::SQ_VTX_SEMANTIC_0 / 4; auto valueIdx = (latte::Register::SQ_VTX_SEMANTIC_CLEAR - base) / 4; auto clearFlags = values[valueIdx]; for (auto i = 0u; i < 32; ++i) { if (clearFlags & (1 << i)) { mRegisters[clearRegBase + i] = 0xffffffff; } } } memcpy(&mRegisters[base / 4], values.data(), values.size_bytes()); } void Pm4Processor::setAluConsts(const SetAluConsts &data) { decaf_check(data.id >= latte::Register::AluConstRegisterBase); decaf_check(data.id < latte::Register::AluConstRegisterEnd); if (mShadowState.SHADOW_ENABLE.ENABLE_ALU_CONST() && mShadowState.ALU_CONST_BASE) { auto offset = (data.id - latte::Register::AluConstRegisterBase) / 4; shadowWrite(mShadowState.ALU_CONST_BASE + offset, data.values); } setRegisters(data.id, data.values); } void Pm4Processor::setConfigRegs(const SetConfigRegs &data) { decaf_check(data.id >= latte::Register::ConfigRegisterBase); decaf_check(data.id < latte::Register::ConfigRegisterEnd); if (mShadowState.SHADOW_ENABLE.ENABLE_CONFIG_REG() && mShadowState.CONFIG_REG_BASE) { auto offset = (data.id - latte::Register::ConfigRegisterBase) / 4; shadowWrite(mShadowState.CONFIG_REG_BASE + offset, data.values); } setRegisters(data.id, data.values); } void Pm4Processor::setContextRegs(const SetContextRegs &data) { decaf_check(data.id >= latte::Register::ContextRegisterBase); decaf_check(data.id < latte::Register::ContextRegisterEnd); if (mShadowState.SHADOW_ENABLE.ENABLE_CONTEXT_REG() && mShadowState.CONTEXT_REG_BASE) { auto offset = (data.id - latte::Register::ContextRegisterBase) / 4; shadowWrite(mShadowState.CONTEXT_REG_BASE + offset, data.values); } setRegisters(data.id, data.values); } void Pm4Processor::setControlConstants(const SetControlConstants &data) { decaf_check(data.id >= latte::Register::ControlRegisterBase); decaf_check(data.id < latte::Register::ControlRegisterEnd); if (mShadowState.SHADOW_ENABLE.ENABLE_CTL_CONST() && mShadowState.CTL_CONST_BASE) { auto offset = (data.id - latte::Register::ControlRegisterBase) / 4; shadowWrite(mShadowState.CTL_CONST_BASE + offset, data.values); } setRegisters(data.id, data.values); } void Pm4Processor::setLoopConsts(const SetLoopConsts &data) { decaf_check(data.id >= latte::Register::LoopConstRegisterBase); decaf_check(data.id < latte::Register::LoopConstRegisterEnd); if (mShadowState.SHADOW_ENABLE.ENABLE_LOOP_CONST() && mShadowState.LOOP_CONST_BASE) { auto offset = (data.id - latte::Register::LoopConstRegisterBase) / 4; shadowWrite(mShadowState.LOOP_CONST_BASE + offset, data.values); } setRegisters(data.id, data.values); } void Pm4Processor::setSamplers(const SetSamplers &data) { decaf_check(data.id >= latte::Register::SamplerRegisterBase); decaf_check(data.id < latte::Register::SamplerRegisterEnd); if (mShadowState.SHADOW_ENABLE.ENABLE_SAMPLER() && mShadowState.SAMPLER_CONST_BASE) { auto offset = (data.id - latte::Register::SamplerRegisterBase) / 4; shadowWrite(mShadowState.SAMPLER_CONST_BASE + offset, data.values); } setRegisters(data.id, data.values); } void Pm4Processor::setResources(const SetResources &data) { if (mShadowState.SHADOW_ENABLE.ENABLE_RESOURCE() && mShadowState.RESOURCE_CONST_BASE) { shadowWrite(mShadowState.RESOURCE_CONST_BASE + data.id, data.values); } auto id = static_cast<latte::Register>(latte::Register::ResourceRegisterBase + (4 * data.id)); setRegisters(id, data.values); } void Pm4Processor::loadRegisters(latte::Register base, phys_addr address, const gsl::span<std::pair<uint32_t, uint32_t>> ®isters) { auto src = phys_cast<uint32_t *>(address); for (auto &range : registers) { auto start = range.first; auto count = range.second; // We can directly call setRegisters since there is no byte-swapping for shadow. setRegisters(static_cast<latte::Register>(base + start * 4), gsl::make_span(src.getRawPointer() + start, count)); } } void Pm4Processor::loadAluConsts(const LoadAluConst &data) { if (mShadowState.LOAD_CONTROL.ENABLE_ALU_CONST()) { mShadowState.ALU_CONST_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::AluConstRegisterBase, data.addr, data.values); } } void Pm4Processor::loadBoolConsts(const LoadBoolConst &data) { if (mShadowState.LOAD_CONTROL.ENABLE_BOOL_CONST()) { mShadowState.BOOL_CONST_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::BoolConstRegisterBase, data.addr, data.values); } } void Pm4Processor::loadConfigRegs(const LoadConfigReg &data) { if (mShadowState.LOAD_CONTROL.ENABLE_CONFIG_REG()) { mShadowState.CONFIG_REG_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::ConfigRegisterBase, data.addr, data.values); } } void Pm4Processor::loadContextRegs(const LoadContextReg &data) { if (mShadowState.LOAD_CONTROL.ENABLE_CONTEXT_REG()) { mShadowState.CONTEXT_REG_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::ContextRegisterBase, data.addr, data.values); } } void Pm4Processor::loadControlConstants(const LoadControlConst &data) { if (mShadowState.LOAD_CONTROL.ENABLE_CTL_CONST()) { mShadowState.CTL_CONST_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::ControlRegisterBase, data.addr, data.values); } } void Pm4Processor::loadLoopConsts(const LoadLoopConst &data) { if (mShadowState.LOAD_CONTROL.ENABLE_LOOP_CONST()) { mShadowState.LOOP_CONST_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::LoopConstRegisterBase, data.addr, data.values); } } void Pm4Processor::loadSamplers(const LoadSampler &data) { if (mShadowState.LOAD_CONTROL.ENABLE_SAMPLER()) { mShadowState.SAMPLER_CONST_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::SamplerRegisterBase, data.addr, data.values); } } void Pm4Processor::loadResources(const latte::pm4::LoadResource &data) { if (mShadowState.LOAD_CONTROL.ENABLE_RESOURCE()) { mShadowState.RESOURCE_CONST_BASE = phys_cast<uint32_t *>(data.addr); loadRegisters(latte::Register::ResourceRegisterBase, data.addr, data.values); } } ================================================ FILE: src/libgpu/src/pm4_processor.h ================================================ #pragma once #include "latte/latte_pm4_commands.h" #include "gpu_ringbuffer.h" #include <array> #include <common/byte_swap_array.h> #include <libcpu/pointer.h> #include <vector> using namespace latte::pm4; constexpr int MaxPm4IndirectDepth = 6; class Pm4Processor { protected: Pm4Processor() { } virtual void decafSetBuffer(const DecafSetBuffer &data) = 0; virtual void decafCopyColorToScan(const DecafCopyColorToScan &data) = 0; virtual void decafSwapBuffers(const DecafSwapBuffers &data) = 0; virtual void decafClearColor(const DecafClearColor &data) = 0; virtual void decafClearDepthStencil(const DecafClearDepthStencil &data) = 0; virtual void decafOSScreenFlip(const DecafOSScreenFlip &data) = 0; virtual void decafCopySurface(const DecafCopySurface &data) = 0; virtual void decafExpandColorBuffer(const DecafExpandColorBuffer &data) = 0; virtual void drawIndexAuto(const DrawIndexAuto &data) = 0; virtual void drawIndex2(const DrawIndex2 &data) = 0; virtual void drawIndexImmd(const DrawIndexImmd &data) = 0; virtual void waitMem(const WaitMem &data) = 0; virtual void memWrite(const MemWrite &data) = 0; virtual void eventWrite(const EventWrite &data) = 0; virtual void eventWriteEOP(const EventWriteEOP &data) = 0; virtual void pfpSyncMe(const PfpSyncMe &data) = 0; virtual void setPredication(const SetPredication &data) = 0; virtual void streamOutBaseUpdate(const StreamOutBaseUpdate &data) = 0; virtual void streamOutBufferUpdate(const StreamOutBufferUpdate &data) = 0; virtual void surfaceSync(const SurfaceSync &data) = 0; void handlePacketType0(HeaderType0 header, const gsl::span<uint32_t> &data); void handlePacketType3(HeaderType3 header, const gsl::span<uint32_t> &data); void nopPacket(const Nop &data); void indirectBufferCall(const IndirectBufferCall &data); void indirectBufferCallPriv(const IndirectBufferCallPriv &data); void indexType(const IndexType &data); void numInstances(const NumInstances &data); void contextControl(const ContextControl &data); void copyDw(const CopyDw &data); void setAluConsts(const SetAluConsts &data); void setConfigRegs(const SetConfigRegs &data); void setContextRegs(const SetContextRegs &data); void setControlConstants(const SetControlConstants &data); void setLoopConsts(const SetLoopConsts &data); void setSamplers(const SetSamplers &data); void setResources(const SetResources &data); void shadowWrite(phys_ptr<uint32_t> address, const gsl::span<uint32_t> ®isters); void setRegisters(latte::Register base, const gsl::span<uint32_t> &values); void loadAluConsts(const LoadAluConst &data); void loadBoolConsts(const LoadBoolConst &data); void loadConfigRegs(const LoadConfigReg &data); void loadContextRegs(const LoadContextReg &data); void loadControlConstants(const LoadControlConst &data); void loadLoopConsts(const LoadLoopConst &data); void loadSamplers(const LoadSampler &data); void loadResources(const latte::pm4::LoadResource &data); // Thanks Windows! void loadRegisters(latte::Register base, phys_addr address, const gsl::span<std::pair<uint32_t, uint32_t>> ®isters); void runCommandBuffer(const gpu::ringbuffer::Buffer &buffer); uint32_t *byteSwapRegValues(uint32_t *values, size_t numValues) { return reinterpret_cast<uint32_t*>(byte_swap_to_scratch<uint32_t>( values, static_cast<uint32_t>(numValues) * sizeof(uint32_t), mRegisterScratch)); } template<typename Type> Type getRegister(uint32_t id) { decaf_check(id != latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE); static_assert(sizeof(Type) == 4, "Register storage must be a uint32_t"); return *reinterpret_cast<Type *>(&mRegisters[id / 4]); } phys_addr getRegisterAddr(uint32_t id) { if (id == latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE) { return mRegAddr_VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE; } decaf_abort("Unsupported register address fetch"); } latte::ShadowState mShadowState; std::array<uint32_t, 0x10000> mRegisters = { 0 }; phys_addr mRegAddr_VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE = phys_addr { 0 }; std::vector<uint8_t> mRegisterScratch; std::array<std::vector<uint8_t>, MaxPm4IndirectDepth> mSwapScratch; uint32_t mSwapScratchDepth = 0; }; ================================================ FILE: src/libgpu/src/spirv/spirv_alu_op2.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { using namespace latte; /* Notes: 1. KILL instructions only are used to discard a pixel in the pixel shader according to the ISA. However, based on empirical evidence in some Geometry shaders, its also used to cancel the shader when the ringbuffers overflow. */ void Transpiler::translateAluOp2_ADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = mSpv->createBinOp(spv::OpFAdd, mSpv->floatType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_ADD_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT); auto output = mSpv->createBinOp(spv::OpIAdd, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_AND_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT); auto output = mSpv->createBinOp(spv::OpBitwiseAnd, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_ASHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT); auto output = mSpv->createBinOp(spv::OpShiftRightArithmetic, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_COS(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); // dst = cos(src0 / 0.1591549367) auto halfPiFConst = mSpv->makeFloatConstant(0.1591549367f); auto srcDived = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), src0, halfPiFConst); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Cos, { srcDived }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_EXP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Exp, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Floor, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_FLT_TO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT); auto output = mSpv->createUnaryOp(spv::OpConvertFToS, mSpv->intType(), src0); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_FLT_TO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT); auto output = mSpv->createUnaryOp(spv::OpConvertFToU, mSpv->uintType(), src0); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_FRACT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Fract, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_INT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto output = mSpv->createUnaryOp(spv::OpConvertSToF, mSpv->floatType(), src0); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_KILLE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto pred = mSpv->createBinOp(spv::Op::OpFOrdEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto pred = mSpv->createBinOp(spv::Op::OpIEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto pred = mSpv->createBinOp(spv::Op::OpFOrdNotEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto pred = mSpv->createBinOp(spv::Op::OpINotEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto pred = mSpv->createBinOp(spv::Op::OpFOrdGreaterThan, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto pred = mSpv->createBinOp(spv::Op::OpSGreaterThan, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto pred = mSpv->createBinOp(spv::Op::OpUGreaterThan, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto pred = mSpv->createBinOp(spv::Op::OpFOrdGreaterThanEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto pred = mSpv->createBinOp(spv::Op::OpSGreaterThanEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_KILLGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto pred = mSpv->createBinOp(spv::Op::OpUGreaterThanEqual, mSpv->boolType(), src0, src1); auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv }; if (mType == ShaderParser::Type::Pixel) { mSpv->makeDiscard(); } else { mSpv->makeReturn(false); } cond.makeEndIf(); } void Transpiler::translateAluOp2_LOG_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { // TODO: Implement log clamp-to-maxval translateAluOp2_LOG_IEEE(cf, group, unit, inst); } void Transpiler::translateAluOp2_LOG_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Log, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_LSHL_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT); auto output = mSpv->createBinOp(spv::OpShiftLeftLogical, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_LSHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT); auto output = mSpv->createBinOp(spv::OpShiftRightLogical, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MAX(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMax, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MAX_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { // TODO: MAX_DX10 has different behaviour to GLSL MAX // I believe for dx10 max returns non-NaN value: max(n, NaN) = n max(NaN, n) = n // Whereas glsl returns second parameter: max(n, NaN) = NaN, max(NaN, n) = n auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMax, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MAX_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = mSpv->createBuiltinCall(mSpv->intType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450SMax, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MAX_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto output = mSpv->createBuiltinCall(mSpv->uintType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450UMax, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMin, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MIN_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { // TODO: MIN_DX10 has different behaviour to GLSL MIN // I believe for dx10 min returns non-NaN value: min(n, NaN) = n min(NaN, n) = n // Whereas glsl returns second parameter: min(n, NaN) = NaN, min(NaN, n) = n auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMin, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MIN_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = mSpv->createBuiltinCall(mSpv->intType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450SMin, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MIN_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto output = mSpv->createBuiltinCall(mSpv->uintType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450UMin, { src0, src1 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MOV(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); mSpv->writeAluOpDest(cf, group, unit, inst, src0); } void Transpiler::translateAluOp2_MOVA_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto minClampConst = mSpv->makeIntConstant(-256); auto maxClampConst = mSpv->makeIntConstant(255); auto intVal = mSpv->createUnaryOp(spv::OpConvertFToS, mSpv->intType(), src0); auto output = mSpv->createBuiltinCall(mSpv->intType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450SClamp, { intVal, minClampConst, maxClampConst }); mSpv->writeAluOpDest(cf, group, unit, inst, output, true); } void Transpiler::translateAluOp2_MOVA_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); mSpv->writeAluOpDest(cf, group, unit, inst, src0, true); } void Transpiler::translateAluOp2_MUL(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { // Everyone gets to enjoy the IEEE standards since Vulkan is IEEE // TODO: Check if there are any side-effects of making MUL be IEEE compliant. translateAluOp2_MUL_IEEE(cf, group, unit, inst); } void Transpiler::translateAluOp2_MUL_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MULLO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT); auto output = mSpv->createBinOp(spv::Op::OpIMul, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_MULLO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT); auto output = mSpv->createBinOp(spv::Op::OpIMul, mSpv->uintType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_NOP(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { // No need to perform any form of translation here... } void Transpiler::translateAluOp2_NOT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto output = mSpv->createUnaryOp(spv::OpNot, mSpv->intType(), src0); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_OR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT); auto output = mSpv->createBinOp(spv::OpBitwiseOr, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdEqual, mSpv->floatType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpIEqual, mSpv->intType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::FLOAT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::FLOAT); auto output = genPredSetOp(inst, spv::OpFOrdGreaterThanEqual, mSpv->floatType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpSGreaterThanEqual, mSpv->intType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto output = genPredSetOp(inst, spv::OpUGreaterThanEqual, mSpv->uintType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::FLOAT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::FLOAT); auto output = genPredSetOp(inst, spv::OpFOrdGreaterThan, mSpv->floatType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpSGreaterThan, mSpv->intType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto output = genPredSetOp(inst, spv::OpUGreaterThan, mSpv->uintType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdNotEqual, mSpv->floatType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_PRED_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpINotEqual, mSpv->intType(), src0, src1, true); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2RecipCommon(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst, bool isRecipSqrt, bool isFF) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto zeroPositive = mSpv->makeFloatConstant(0.0f); auto zeroNegative = mSpv->makeFloatConstant(-0.0f); auto valueZero = spv::Id {}; auto valueRecip = spv::Id {}; auto blockZero = static_cast<spv::Block *>(nullptr); auto blockRecip = static_cast<spv::Block *>(nullptr); auto predIsZero = mSpv->createBinOp(spv::Op::OpFOrdEqual, mSpv->boolType(), src0, zeroPositive); auto condIsZero = spv::Builder::If { predIsZero, spv::SelectionControlMaskNone, *mSpv }; { auto src0Sign = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { src0 }); auto isNegative = mSpv->createBinOp(spv::Op::OpFOrdLessThan, mSpv->boolType(), src0Sign, zeroPositive); if (isFF) { valueZero = mSpv->createTriOp(spv::Op::OpSelect, mSpv->floatType(), isNegative, zeroNegative, zeroPositive); blockZero = mSpv->getBuildPoint(); } else { auto maxFloatNegative = mSpv->makeFloatConstant(std::numeric_limits<float>::lowest()); auto maxFloatPositive = mSpv->makeFloatConstant(std::numeric_limits<float>::max()); valueZero = mSpv->createTriOp(spv::Op::OpSelect, mSpv->floatType(), isNegative, maxFloatNegative, maxFloatPositive); blockZero = mSpv->getBuildPoint(); } } condIsZero.makeBeginElse(); { if (isRecipSqrt) { valueRecip = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450InverseSqrt, { src0 }); blockRecip = mSpv->getBuildPoint(); } else { auto one = mSpv->makeFloatConstant(1.0f); valueRecip = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), one, src0); blockRecip = mSpv->getBuildPoint(); } } condIsZero.makeEndIf(); auto output = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { valueZero, blockZero->getId(), valueRecip, blockRecip->getId(), }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_RECIP_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { translateAluOp2RecipCommon(cf, group, unit, inst, false, false); } void Transpiler::translateAluOp2_RECIP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto oneFConst = mSpv->makeFloatConstant(1.0f); auto output = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), oneFConst, src0); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_RECIP_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { translateAluOp2RecipCommon(cf, group, unit, inst, false, true); } void Transpiler::translateAluOp2_RECIP_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { latte::ShaderParser::translateAluOp2_RECIP_INT(cf, group, unit, inst); } void Transpiler::translateAluOp2_RECIP_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { latte::ShaderParser::translateAluOp2_RECIP_UINT(cf, group, unit, inst); } void Transpiler::translateAluOp2_RECIPSQRT_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { translateAluOp2RecipCommon(cf, group, unit, inst, true, false); } void Transpiler::translateAluOp2_RECIPSQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450InverseSqrt, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_RECIPSQRT_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { translateAluOp2RecipCommon(cf, group, unit, inst, true, true); } void Transpiler::translateAluOp2_RNDNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450RoundEven, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdEqual, mSpv->floatType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdEqual, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpIEqual, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdGreaterThanEqual, mSpv->floatType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdGreaterThanEqual, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpSGreaterThanEqual, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto output = genPredSetOp(inst, spv::OpUGreaterThanEqual, mSpv->uintType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdGreaterThan, mSpv->floatType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGT_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdGreaterThan, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpSGreaterThan, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT); auto output = genPredSetOp(inst, spv::OpUGreaterThan, mSpv->uintType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdNotEqual, mSpv->floatType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETNE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto output = genPredSetOp(inst, spv::OpFOrdNotEqual, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto output = genPredSetOp(inst, spv::OpINotEqual, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); // dst = sin(src0 / 0.1591549367) auto halfPiFConst = mSpv->makeFloatConstant(0.1591549367f); auto srcDived = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), src0, halfPiFConst); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Sin, { srcDived }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Sqrt, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_SUB_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT); auto output = mSpv->createBinOp(spv::OpISub, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_TRUNC(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Trunc, { src0 }); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_UINT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::UINT); auto output = mSpv->createUnaryOp(spv::OpConvertUToF, mSpv->floatType(), src0); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp2_XOR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT); auto output = mSpv->createBinOp(spv::OpBitwiseXor, mSpv->intType(), src0, src1); mSpv->writeAluOpDest(cf, group, unit, inst, output); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_alu_op3.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { using namespace latte; void Transpiler::translateAluOp3_CNDE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto output = genAluCondOp(spv::Op::OpFOrdEqual, src0, src1, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_CNDGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto output = genAluCondOp(spv::Op::OpFOrdGreaterThan, src0, src1, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_CNDGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto output = genAluCondOp(spv::Op::OpFOrdGreaterThanEqual, src0, src1, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_CNDE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2, VarRefType::INT); auto output = genAluCondOp(spv::Op::OpIEqual, src0, src1, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_CNDGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2, VarRefType::INT); auto output = genAluCondOp(spv::Op::OpSGreaterThan, src0, src1, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_CNDGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2, VarRefType::INT); auto output = genAluCondOp(spv::Op::OpSGreaterThanEqual, src0, src1, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_MULADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { translateAluOp3_MULADD_IEEE(cf, group, unit, inst); } void Transpiler::translateAluOp3_MULADD_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto muled = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1); auto output = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), muled, src2); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_MULADD_M2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto mul = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1); auto muladd = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), mul, src2); auto output = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), muladd, mSpv->makeFloatConstant(2.0f)); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_MULADD_M4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto mul = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1); auto muladd = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), mul, src2); auto output = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), muladd, mSpv->makeFloatConstant(4.0f)); mSpv->writeAluOpDest(cf, group, unit, inst, output); } void Transpiler::translateAluOp3_MULADD_D2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0); auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1); auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2); auto mul = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1); auto muladd = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), mul, src2); auto output = mSpv->createBinOp(spv::Op::OpFDiv, mSpv->floatType(), muladd, mSpv->makeFloatConstant(2.0f)); mSpv->writeAluOpDest(cf, group, unit, inst, output); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_alu_reduc.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { using namespace latte; void Transpiler::translateAluOp2_CUBE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { // TODO: This instructure guarentees a specific ordering among its // two source operands accross the entire reduction. We aren't going // to verify that guarentee here, but assume it holds. auto srcX = mSpv->readAluInstSrc(cf, group, *group.units[SQ_CHAN::Z], 0); auto srcY = mSpv->readAluInstSrc(cf, group, *group.units[SQ_CHAN::W], 0); auto srcZ = mSpv->readAluInstSrc(cf, group, *group.units[SQ_CHAN::X], 0); /* Concise pseudocode (v3): if (|z| >= |x| && |z| >= |y|) t = -y s = sign(z) * x ma = 2z f = (z < 0) ? 5 : 4 else if (|y| >= |x|) t = sign(y) * z s = x ma = 2y f = (y < 0) ? 3 : 2 else t = -y s = sign(x) * z ma = 2x f = (x < 0) ? 1 : 0 Note that CUBE reverses the order of the texture coordinates in the output: out.yx = face.st */ auto zeroFConst = mSpv->makeFloatConstant(0.0f); auto oneFConst = mSpv->makeFloatConstant(1.0f); auto twoFConst = mSpv->makeFloatConstant(2.0f); auto threeFConst = mSpv->makeFloatConstant(3.0f); auto fourFConst = mSpv->makeFloatConstant(4.0f); auto fiveFConst = mSpv->makeFloatConstant(5.0f); auto absX = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcX }); auto absY = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcY }); auto absZ = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcZ }); // if (|z| >= |x| && |z| >= |y|) { auto pred01_ZX = mSpv->createBinOp(spv::OpFOrdGreaterThanEqual, mSpv->boolType(), absZ, absX); auto pred01_ZY = mSpv->createBinOp(spv::OpFOrdGreaterThanEqual, mSpv->boolType(), absZ, absY); auto pred01 = mSpv->createBinOp(spv::OpLogicalAnd, mSpv->boolType(), pred01_ZX, pred01_ZY); auto pred01Block = spv::Builder::If { pred01, spv::SelectionControlMaskNone, *mSpv }; spv::Id dstX01, dstY01, dstZ01, dstW01; { auto signZ = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { srcZ }); auto negY = mSpv->createUnaryOp(spv::OpFNegate, mSpv->floatType(), srcY); dstX01 = negY; dstY01 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), signZ, srcX); dstZ01 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), srcZ, twoFConst); auto predZls0 = mSpv->createBinOp(spv::OpFOrdLessThan, mSpv->boolType(), srcZ, zeroFConst); dstW01 = mSpv->createOp(spv::OpSelect, mSpv->floatType(), { predZls0, fiveFConst, fourFConst }); } auto dst01Block = mSpv->getBuildPoint(); pred01Block.makeBeginElse(); // } else if (|y| >= |x|) { auto pred23 = mSpv->createBinOp(spv::OpFOrdGreaterThanEqual, mSpv->boolType(), absY, absX); auto pred23Block = spv::Builder::If { pred23, spv::SelectionControlMaskNone, *mSpv }; spv::Id dstX23, dstY23, dstZ23, dstW23; { auto signY = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { srcY }); dstX23 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), signY, srcZ); dstY23 = srcX; dstZ23 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), srcY, twoFConst); auto predYls0 = mSpv->createBinOp(spv::OpFOrdLessThan, mSpv->boolType(), srcY, zeroFConst); dstW23 = mSpv->createOp(spv::OpSelect, mSpv->floatType(), { predYls0, threeFConst, twoFConst }); } auto dst23Block = mSpv->getBuildPoint(); pred23Block.makeBeginElse(); // } else { spv::Id dstX45, dstY45, dstZ45, dstW45; { auto signX = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { srcX }); auto negY = mSpv->createUnaryOp(spv::OpFNegate, mSpv->floatType(), srcY); dstX45 = negY; dstY45 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), signX, srcZ); dstZ45 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), srcX, twoFConst); auto predXls0 = mSpv->createBinOp(spv::OpFOrdLessThan, mSpv->boolType(), srcX, zeroFConst); dstW45 = mSpv->createOp(spv::OpSelect, mSpv->floatType(), { predXls0, oneFConst, zeroFConst }); } auto dst45Block = mSpv->getBuildPoint(); pred23Block.makeEndIf(); auto dstX2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstX23, dst23Block->getId(), dstX45, dst45Block->getId() }); auto dstY2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstY23, dst23Block->getId(), dstY45, dst45Block->getId() }); auto dstZ2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstZ23, dst23Block->getId(), dstZ45, dst45Block->getId() }); auto dstW2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstW23, dst23Block->getId(), dstW45, dst45Block->getId() }); auto dst2345Block = mSpv->getBuildPoint(); pred01Block.makeEndIf(); auto dstX = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstX01, dst01Block->getId(), dstX2345, dst2345Block->getId() }); auto dstY = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstY01, dst01Block->getId(), dstY2345, dst2345Block->getId() }); auto dstZ = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstZ01, dst01Block->getId(), dstZ2345, dst2345Block->getId() }); auto dstW = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstW01, dst01Block->getId(), dstW2345, dst2345Block->getId() }); // Okay, we now have a sorta bloody clue why we need to do this. The CUBE instruction // intentionally performs a some math to the values such that the values shift out of // the 0.0-1.0 range and into the 1.0-2.0 range which has the benefit of a constant // mantissa component to the floating point number. Supposedly this improves the // performance of the lookup for AMD hardware. We would normally duplicate this // behaviour, but need to undo it because the samplers are set to BORDER, which // causes invalid values to be sampled. We're not sure how its meant to work with a // non-wrapping sampler, but thats a problem for another day. { auto dstAbsZ = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { dstZ }); dstX = mSpv->createBinOp(spv::OpFSub, mSpv->floatType(), dstX, dstAbsZ); dstY = mSpv->createBinOp(spv::OpFSub, mSpv->floatType(), dstY, dstAbsZ); } mSpv->writeAluOpDest(cf, group, SQ_CHAN::X, *group.units[SQ_CHAN::X], dstX); mSpv->writeAluOpDest(cf, group, SQ_CHAN::Y, *group.units[SQ_CHAN::Y], dstY); mSpv->writeAluOpDest(cf, group, SQ_CHAN::Z, *group.units[SQ_CHAN::Z], dstZ); mSpv->writeAluOpDest(cf, group, SQ_CHAN::W, *group.units[SQ_CHAN::W], dstW); } void Transpiler::translateAluOp2_DOT4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { translateAluOp2_DOT4_IEEE(cf, group, unit, inst); } void Transpiler::translateAluOp2_DOT4_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { auto src0 = mSpv->readAluReducSrc(cf, group, 0); auto src1 = mSpv->readAluReducSrc(cf, group, 1); auto output = mSpv->createBinOp(spv::Op::OpDot, mSpv->floatType(), src0, src1); mSpv->writeAluReducDest(cf, group, output); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_cf.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { using namespace latte; void Transpiler::translateCf_ALU(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(); translateAluClause(cf); mSpv->endCfCondBlock(afterBlock); } void Transpiler::translateCf_ALU_PUSH_BEFORE(const ControlFlowInst &cf) { mSpv->pushStack(); translateCf_ALU(cf); } void Transpiler::translateCf_ALU_POP_AFTER(const ControlFlowInst &cf) { translateCf_ALU(cf); mSpv->popStack(1); } void Transpiler::translateCf_ALU_POP2_AFTER(const ControlFlowInst &cf) { translateCf_ALU(cf); mSpv->popStack(2); } void Transpiler::translateCf_ALU_EXT(const ControlFlowInst &cf) { translateCf_ALU(cf); } void Transpiler::translateCf_ALU_CONTINUE(const ControlFlowInst &cf) { translateCf_ALU(cf); // If state is set to Inactive, set it to InactiveContinue auto isInactive = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(), mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision), mSpv->stateInactive()); auto ifBuilder = spv::Builder::If { isInactive, spv::SelectionControlMaskNone, *mSpv }; mSpv->createStore(mSpv->stateInactiveContinue(), mSpv->stateVar()); ifBuilder.makeEndIf(); } void Transpiler::translateCf_ALU_BREAK(const ControlFlowInst &cf) { translateCf_ALU(cf); // If state is set to Inactive, set it to InactiveBreak auto isInactive = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(), mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision), mSpv->stateInactive()); auto ifBuilder = spv::Builder::If { isInactive, spv::SelectionControlMaskNone, *mSpv }; mSpv->createStore(mSpv->stateInactiveBreak(), mSpv->stateVar()); ifBuilder.makeEndIf(); } void Transpiler::translateCf_ALU_ELSE_AFTER(const ControlFlowInst &cf) { translateCf_ALU(cf); translateCf_ELSE(cf); } void Transpiler::translateCf_CALL_FS(const ControlFlowInst &cf) { mSpv->createFunctionCall(mSpv->getFunction("fs_main"), {}); } void Transpiler::translateCf_ELSE(const ControlFlowInst &cf) { mSpv->elseStack(); } void Transpiler::translateCf_JUMP(const ControlFlowInst &cf) { // This is actually only an optimization so we can ignore it. } void Transpiler::translateCf_KILL(const ControlFlowInst &cf) { latte::ShaderParser::translateCf_KILL(cf); } void Transpiler::translateCf_NOP(const ControlFlowInst &cf) { // Who knows why they waste space with explicitly encoded NOP's... } void Transpiler::translateCf_PUSH(const ControlFlowInst &cf) { mSpv->pushStack(); } void Transpiler::translateCf_POP(const ControlFlowInst &cf) { mSpv->popStack(cf.word1.POP_COUNT()); } void Transpiler::translateCf_RETURN(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); mSpv->makeReturn(true); mSpv->endCfCondBlock(afterBlock, true); } void Transpiler::translateCf_TEX(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); translateTexClause(cf); mSpv->endCfCondBlock(afterBlock); } void Transpiler::translateCf_VTX(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); translateVtxClause(cf); mSpv->endCfCondBlock(afterBlock); } void Transpiler::translateCf_VTX_TC(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); translateVtxClause(cf); mSpv->endCfCondBlock(afterBlock); } void Transpiler::translateCf_LOOP_START(const ControlFlowInst &cf) { latte::ShaderParser::translateCf_LOOP_START(cf); } void Transpiler::translateCf_LOOP_START_DX10(const ControlFlowInst &cf) { decaf_check(cf.word1.COND() == latte::SQ_CF_COND::ACTIVE); mSpv->pushStack(); auto loop = LoopState { }; loop.startPC = mCfPC; loop.endPC = cf.word0.ADDR() - 1; loop.head = &mSpv->makeNewBlock(); loop.body = &mSpv->makeNewBlock(); loop.merge = &mSpv->makeNewBlock(); loop.continue_target = &mSpv->makeNewBlock(); mLoopStack.emplace_back(loop); mSpv->createBranch(loop.head); mSpv->setBuildPoint(loop.head); mSpv->createLoopMerge(loop.merge, loop.continue_target, spv::LoopControlMaskNone, {}); mSpv->createBranch(loop.body); mSpv->setBuildPoint(loop.body); auto stateVal = mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision); // If state is set to InactiveContinue, set it to Active { auto isInactiveContinue = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(), stateVal, mSpv->stateInactiveContinue()); auto ifBuilder = spv::Builder::If { isInactiveContinue, spv::SelectionControlMaskNone, *mSpv }; mSpv->createStore(mSpv->stateActive(), mSpv->stateVar()); ifBuilder.makeEndIf(); } // If state is set to Inactive, set it to InactiveBreak { auto isInactive = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(), stateVal, mSpv->stateInactive()); auto ifBuilder = spv::Builder::If { isInactive, spv::SelectionControlMaskNone, *mSpv }; mSpv->createStore(mSpv->stateInactiveBreak(), mSpv->stateVar()); ifBuilder.makeEndIf(); } } void Transpiler::translateCf_LOOP_START_NO_AL(const ControlFlowInst &cf) { latte::ShaderParser::translateCf_LOOP_START_NO_AL(cf); } void Transpiler::translateCf_LOOP_END(const ControlFlowInst &cf) { auto loop = mLoopStack.back(); // Sanity check to ensure we are at the correct cfPC decaf_check(mCfPC == loop.endPC); decaf_check((cf.word0.ADDR() - 1) == loop.startPC); mSpv->createBranch(loop.continue_target); mSpv->setBuildPoint(loop.continue_target); // Continue while state != InactiveBreak auto predContinue = mSpv->createBinOp(spv::OpINotEqual, mSpv->boolType(), mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision), mSpv->stateInactiveBreak()); mSpv->createConditionalBranch(predContinue, loop.head, loop.merge); mSpv->setBuildPoint(loop.merge); mLoopStack.pop_back(); // LOOP_END ignores POP_COUNT as per R600 ISA Documentation mSpv->popStack(1); } void Transpiler::translateCf_LOOP_CONTINUE(const ControlFlowInst &cf) { latte::ShaderParser::translateCf_LOOP_CONTINUE(cf); } void Transpiler::translateCf_LOOP_BREAK(const ControlFlowInst &cf) { latte::ShaderParser::translateCf_LOOP_BREAK(cf); } void Transpiler::translateCf_EMIT_VERTEX(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); mSpv->createFunctionCall(mSpv->getFunction("dc_main"), {}); mSpv->createNoResultOp(spv::OpEmitVertex); mSpv->endCfCondBlock(afterBlock); } void Transpiler::translateCf_EMIT_CUT_VERTEX(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); mSpv->createFunctionCall(mSpv->getFunction("dc_main"), {}); mSpv->createNoResultOp(spv::OpEmitVertex); mSpv->createNoResultOp(spv::OpEndPrimitive); mSpv->endCfCondBlock(afterBlock); } void Transpiler::translateCf_CUT_VERTEX(const ControlFlowInst &cf) { auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST()); mSpv->createNoResultOp(spv::OpEndPrimitive); mSpv->endCfCondBlock(afterBlock); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_export.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { using namespace latte; static inline void calcSpecialVecPositions(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl, uint32_t *miscVec, uint32_t *ccdist0Vec, uint32_t *ccdist1Vec) { uint32_t currentPos = 1; // TODO: When we encounter these, check the expected ordering. decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()); decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()); if (pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA()) { if (miscVec) { *miscVec = currentPos; } currentPos++; } if (pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()) { if (ccdist0Vec) { *ccdist0Vec = currentPos; } currentPos++; } if (pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()) { if (ccdist1Vec) { *ccdist1Vec = currentPos; } currentPos++; } } static inline uint32_t calcMiscVecPos(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl) { uint32_t output; calcSpecialVecPositions(pa_cl_vs_out_cntl, &output, nullptr, nullptr); return output; } static inline uint32_t calcCdist0VecPos(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl) { uint32_t output; calcSpecialVecPositions(pa_cl_vs_out_cntl, nullptr, &output, nullptr); return output; } static inline uint32_t calcCdist1VecPos(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl) { uint32_t output; calcSpecialVecPositions(pa_cl_vs_out_cntl, nullptr, nullptr, &output); return output; } void Transpiler::translateGenericExport(const ControlFlowInst &cf) { // cf.exp.word0.ARRAY_SIZE() is ignored for exports // cf.exp.word0.ELEM_SIZE() is ignored for exports GprRef srcGpr; srcGpr = makeGprRef(cf.exp.word0.RW_GPR(), cf.exp.word0.RW_REL(), SQ_INDEX_MODE::LOOP); ExportMaskRef exportRef; exportRef.output = makeExportRef(cf.exp.word0.TYPE(), cf.exp.word0.ARRAY_BASE()); exportRef.mask[SQ_CHAN::X] = cf.exp.swiz.SEL_X(); exportRef.mask[SQ_CHAN::Y] = cf.exp.swiz.SEL_Y(); exportRef.mask[SQ_CHAN::Z] = cf.exp.swiz.SEL_Z(); exportRef.mask[SQ_CHAN::W] = cf.exp.swiz.SEL_W(); if (isSwizzleFullyMasked(exportRef.mask)) { // We should just skip fully masked swizzles. return; } auto exportCount = cf.exp.word1.BURST_COUNT() + 1; for (auto i = 0u; i < exportCount; ++i) { // Read the source GPR auto sourcePtr = mSpv->getGprRef(srcGpr); auto sourceVal = mSpv->createLoad(sourcePtr, spv::NoPrecision); bool skipWrite = false; if (mType == ShaderParser::Type::Pixel) { // Update the export value type based on the color output format if (exportRef.output.type == ExportRef::Type::Pixel || exportRef.output.type == ExportRef::Type::PixelWithFog) { auto pixelFormat = mPixelOutType[exportRef.output.arrayBase]; if (pixelFormat == PixelOutputType::FLOAT) { // We are already in the right format, nothing to do here... } else if (pixelFormat == PixelOutputType::SINT) { sourceVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->int4Type(), sourceVal); } else if (pixelFormat == PixelOutputType::UINT) { sourceVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uint4Type(), sourceVal); } } // Apply the appropriate masking. if (exportRef.output.type == ExportRef::Type::Pixel || exportRef.output.type == ExportRef::Type::PixelWithFog) { // Calculate that the number of exports we expect matchs our number // of enabled rendertargets, or the search below will fail. auto numExports = mSqPgmExportsPs.EXPORT_MODE() >> 1; auto rtExports = 0u; rtExports += mCbShaderControl.RT0_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT1_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT2_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT3_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT4_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT5_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT6_ENABLE() ? 1u : 0u; rtExports += mCbShaderControl.RT7_ENABLE() ? 1u : 0u; decaf_check(rtExports == numExports); // Skip over render targets which are not being written. do { auto rtEnabled = !!((mCbShaderControl.value >> exportRef.output.arrayBase) & 0x1); if (rtEnabled) { break; } exportRef.output.arrayBase++; } while (exportRef.output.arrayBase < 8); decaf_check(exportRef.output.arrayBase < 8); auto compMask = (mCbShaderMask.value >> (exportRef.output.arrayBase * 4)) & 0xf; spv::Id maskXVal, maskYVal, maskZVal, maskWVal; auto sourceValType = mSpv->getTypeId(sourceVal); if (sourceValType == mSpv->float4Type()) { maskXVal = mSpv->makeFloatConstant(0.0f); maskYVal = mSpv->makeFloatConstant(0.0f); maskZVal = mSpv->makeFloatConstant(0.0f); maskWVal = mSpv->makeFloatConstant(1.0f); } else if (sourceValType == mSpv->int4Type()) { maskXVal = mSpv->makeIntConstant(0); maskYVal = mSpv->makeIntConstant(0); maskZVal = mSpv->makeIntConstant(0); maskWVal = mSpv->makeIntConstant(1); } else if (sourceValType == mSpv->uint4Type()) { maskXVal = mSpv->makeUintConstant(0); maskYVal = mSpv->makeUintConstant(0); maskZVal = mSpv->makeUintConstant(0); maskWVal = mSpv->makeUintConstant(1); } else { decaf_abort("Unexpected texture output format in component masking."); } if (!(compMask & 1)) { sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskXVal, sourceVal, 0 }); } if (!(compMask & 2)) { sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskYVal, sourceVal, 1 }); } if (!(compMask & 4)) { sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskZVal, sourceVal, 2 }); } if (!(compMask & 8)) { sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskWVal, sourceVal, 3 }); } } if (exportRef.output.type == ExportRef::Type::ComputedZ) { if (!mDbShaderControl.Z_EXPORT_ENABLE()) { // The shader exported a Z, but its not enabled. Lets skip it. skipWrite = true; } } } if (mType == ShaderParser::Type::Vertex || mType == ShaderParser::Type::DataCache) { // Check if this is a position output special case. if (exportRef.output.type == ExportRef::Type::Position) { if (exportRef.output.arrayBase == 0) { // POS_0 is just a standard position output. } else { auto pa_cl_vs_out_cntl = mPaClVsOutCntl; // Lets check for things we do not support decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_0()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_1()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_2()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_3()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_4()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_5()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_6()); decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_7()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_0()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_1()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_2()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_3()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_4()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_5()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_6()); decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_7()); decaf_check(!pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG()); decaf_check(!pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX()); decaf_check(!pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG()); decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()); decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()); decaf_check(!pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG()); // The MISC side-bus seems related to the VS_OUT_MISC_VEC_ENA // pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA() // We have to manually swizzle the value here for use below sourceVal = mSpv->applySelMask(spv::NoResult, sourceVal, exportRef.mask); if (exportRef.output.arrayBase == calcMiscVecPos(pa_cl_vs_out_cntl)) { if (pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX()) { auto sourceZ = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { sourceVal, 2 }); auto sourceZInt = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), sourceZ); mSpv->createStore(sourceZInt, mSpv->layerIdVar()); } } else if (exportRef.output.arrayBase == calcCdist0VecPos(pa_cl_vs_out_cntl)) { decaf_abort("Unsupported CCDIST0 usage"); } else if (exportRef.output.arrayBase == calcCdist1VecPos(pa_cl_vs_out_cntl)) { decaf_abort("Unsupported CCDIST1 usage"); } else { decaf_abort("Unexpected position export index"); } // We have to skip the write, since its already done. skipWrite = true; } } } // Write the exported data if (!skipWrite) { mSpv->writeExportRef(exportRef, sourceVal); } // Increase the indexing for each export srcGpr.next(); exportRef.output.next(); } } void Transpiler::translateCf_EXP(const ControlFlowInst &cf) { translateGenericExport(cf); } void Transpiler::translateCf_EXP_DONE(const ControlFlowInst &cf) { translateGenericExport(cf); } void Transpiler::translateGenericStream(const ControlFlowInst &cf, int streamIdx) { decaf_check(streamIdx < 4); // Find the right stride for this particular streamout. auto streamOutStride = mStreamOutStride[streamIdx]; GprRef srcGpr; srcGpr = makeGprRef(cf.exp.word0.RW_GPR(), cf.exp.word0.RW_REL(), SQ_INDEX_MODE::LOOP); ExportMaskRef exportRef; exportRef.output = makeStreamExportRef(cf.exp.word0.TYPE(), cf.exp.word0.INDEX_GPR(), streamIdx, streamOutStride, cf.exp.word0.ARRAY_BASE(), cf.exp.buf.ARRAY_SIZE() + 1, cf.exp.word0.ELEM_SIZE() + 1); exportRef.mask[SQ_CHAN::X] = (cf.exp.buf.COMP_MASK() & (1 << 0)) ? latte::SQ_SEL::SEL_X : latte::SQ_SEL::SEL_MASK; exportRef.mask[SQ_CHAN::Y] = (cf.exp.buf.COMP_MASK() & (1 << 1)) ? latte::SQ_SEL::SEL_Y : latte::SQ_SEL::SEL_MASK; exportRef.mask[SQ_CHAN::Z] = (cf.exp.buf.COMP_MASK() & (1 << 2)) ? latte::SQ_SEL::SEL_Z : latte::SQ_SEL::SEL_MASK; exportRef.mask[SQ_CHAN::W] = (cf.exp.buf.COMP_MASK() & (1 << 3)) ? latte::SQ_SEL::SEL_W : latte::SQ_SEL::SEL_MASK; if (isSwizzleFullyMasked(exportRef.mask)) { // We should just skip fully masked swizzles. return; } auto exportCount = cf.exp.word1.BURST_COUNT() + 1; for (auto i = 0u; i < exportCount; ++i) { // Read the source GPR auto sourcePtr = mSpv->getGprRef(srcGpr); auto sourceVal = mSpv->createLoad(sourcePtr, spv::NoPrecision); // Write the exported data mSpv->writeExportRef(exportRef, sourceVal); // Increase the indexing for each export srcGpr.next(); exportRef.output.next(); } } void Transpiler::translateCf_MEM_STREAM0(const ControlFlowInst &cf) { translateGenericStream(cf, 0); } void Transpiler::translateCf_MEM_STREAM1(const ControlFlowInst &cf) { translateGenericStream(cf, 1); } void Transpiler::translateCf_MEM_STREAM2(const ControlFlowInst &cf) { translateGenericStream(cf, 2); } void Transpiler::translateCf_MEM_STREAM3(const ControlFlowInst &cf) { translateGenericStream(cf, 3); } void Transpiler::translateCf_MEM_RING(const ControlFlowInst &cf) { GprRef srcGpr; srcGpr = makeGprRef(cf.exp.word0.RW_GPR(), cf.exp.word0.RW_REL(), SQ_INDEX_MODE::LOOP); ExportMaskRef exportRef; if (mType == ShaderParser::Type::Vertex) { exportRef.output = makeVsGsRingExportRef(cf.exp.word0.TYPE(), cf.exp.word0.INDEX_GPR(), cf.exp.word0.ARRAY_BASE(), cf.exp.buf.ARRAY_SIZE() + 1, cf.exp.word0.ELEM_SIZE() + 1); } else if (mType == ShaderParser::Type::Geometry) { exportRef.output = makeGsDcRingExportRef(cf.exp.word0.TYPE(), cf.exp.word0.INDEX_GPR(), cf.exp.word0.ARRAY_BASE(), cf.exp.buf.ARRAY_SIZE() + 1, cf.exp.word0.ELEM_SIZE() + 1); } else { decaf_abort("Unexpected shader type for MEM_RING") } exportRef.mask[SQ_CHAN::X] = (cf.exp.buf.COMP_MASK() & (1 << 0)) ? latte::SQ_SEL::SEL_X : latte::SQ_SEL::SEL_MASK; exportRef.mask[SQ_CHAN::Y] = (cf.exp.buf.COMP_MASK() & (1 << 1)) ? latte::SQ_SEL::SEL_Y : latte::SQ_SEL::SEL_MASK; exportRef.mask[SQ_CHAN::Z] = (cf.exp.buf.COMP_MASK() & (1 << 2)) ? latte::SQ_SEL::SEL_Z : latte::SQ_SEL::SEL_MASK; exportRef.mask[SQ_CHAN::W] = (cf.exp.buf.COMP_MASK() & (1 << 3)) ? latte::SQ_SEL::SEL_W : latte::SQ_SEL::SEL_MASK; if (isSwizzleFullyMasked(exportRef.mask)) { // We should just skip fully masked swizzles. return; } auto exportCount = cf.exp.word1.BURST_COUNT() + 1; for (auto i = 0u; i < exportCount; ++i) { // Read the source GPR auto sourcePtr = mSpv->getGprRef(srcGpr); auto sourceVal = mSpv->createLoad(sourcePtr, spv::NoPrecision); // Write the exported data mSpv->writeExportRef(exportRef, sourceVal); // Increase the indexing for each export srcGpr.next(); exportRef.output.next(); } } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_helpers.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { using namespace latte; // TODO: We really need to make up our minds on whether these kinds of things exist // as part of the Transpiler or the ShaderSpvBuilder... I think that if the item // requires access to any of the latte:: structures, it should probably exist here // and only generic things belong in ShaderSpvBuilder. spv::Id Transpiler::genAluCondOp(spv::Op predOp, spv::Id lhsVal, spv::Id trueVal, spv::Id falseVal) { auto trueType = mSpv->getTypeId(trueVal); auto falseType = mSpv->getTypeId(trueVal); decaf_check(trueType == falseType); spv::Id rhsVal; auto lhsValType = mSpv->getTypeId(lhsVal); if (mSpv->isFloatType(lhsValType)) { rhsVal = mSpv->makeFloatConstant(0.0f); } else if (mSpv->isUintType(lhsValType)) { rhsVal = mSpv->makeUintConstant(0); } else if (mSpv->isIntType(lhsValType)) { rhsVal = mSpv->makeUintConstant(0); } else { decaf_abort("Unexpected type passed for ALU condition"); } auto pred = mSpv->createBinOp(predOp, mSpv->boolType(), lhsVal, rhsVal); return mSpv->createTriOp(spv::Op::OpSelect, trueType, pred, trueVal, falseVal); } spv::Id Transpiler::genPredSetOp(const AluInst &inst, spv::Op predOp, spv::Id typeId, spv::Id lhsVal, spv::Id rhsVal, bool updatesPredicate) { // Ensure this is an OP2, which is what we expect decaf_check(inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2); auto pred = mSpv->createBinOp(predOp, mSpv->boolType(), lhsVal, rhsVal); if (updatesPredicate) { if (inst.op2.UPDATE_PRED()) { mSpv->createStore(pred, mSpv->predicateVar()); } if (inst.op2.UPDATE_EXECUTE_MASK()) { auto newStateVal = mSpv->createTriOp(spv::Op::OpSelect, mSpv->intType(), pred, mSpv->stateActive(), mSpv->stateInactive()); mSpv->createStore(newStateVal, mSpv->stateVar()); } } spv::Id trueVal, falseVal; if (mSpv->isFloatType(typeId)) { trueVal = mSpv->makeFloatConstant(1.0f); falseVal = mSpv->makeFloatConstant(0.0f); } else if (mSpv->isUintType(typeId)) { trueVal = mSpv->makeUintConstant(0xffffffff); falseVal = mSpv->makeUintConstant(0); } else if (mSpv->isIntType(typeId)) { trueVal = mSpv->makeIntConstant(-1); falseVal = mSpv->makeIntConstant(0); } else { decaf_abort("Unexpected return type for predicate op"); } return mSpv->createTriOp(spv::Op::OpSelect, typeId, pred, trueVal, falseVal); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_pushconstants.h ================================================ #pragma once #ifdef DECAF_VULKAN #include <cstdint> #include <common/align.h> namespace spirv { struct Vec4 { float x; float y; float z; float w; }; struct alignas(16) VertexPushConstants { Vec4 posMulAdd; Vec4 zSpaceMul; float pointSize; }; static constexpr int VertexPushConstantsSize = sizeof(VertexPushConstants); static constexpr int VertexPushConstantsOffset = 0; struct alignas(16) FragmentPushConstants { uint32_t alphaFunc; float alphaRef; uint32_t needsPremultiply; }; static constexpr int FragmentPushConstantsSize = sizeof(FragmentPushConstants); static constexpr int FragmentPushConstantsOffset = VertexPushConstantsSize; // Vulkan only requires 4 byte alignment but it seems MoltenVK wants us to // align to 16 bytes. static_assert((VertexPushConstantsSize % 16) == 0); static_assert((VertexPushConstantsOffset % 16) == 0); static_assert((FragmentPushConstantsSize % 16) == 0); static_assert((FragmentPushConstantsOffset % 16) == 0); } // namespace spirv #endif ================================================ FILE: src/libgpu/src/spirv/spirv_shaderspvbuilder.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "latte/latte_shaderparser.h" #include "spirv_spvbuilder.h" #include "spirv_pushconstants.h" #include "gpu_config.h" namespace spirv { using namespace latte; enum class ConstantsMode : uint32_t { Unknown, Registers, Buffers }; class ShaderSpvBuilder : public SpvBuilder { public: ShaderSpvBuilder(spv::ExecutionModel execModel) { setEmitOpLines(); setSource(spv::SourceLanguage::SourceLanguageUnknown, 0); setMemoryModel(spv::AddressingModel::AddressingModelLogical, spv::MemoryModel::MemoryModelGLSL450); addCapability(spv::Capability::CapabilityShader); auto mainFn = makeEntryPoint("main"); mFunctions["main"] = mainFn; auto entry = addEntryPoint(execModel, mainFn, "main"); mEntryPoint = entry; } void setBindingBase(int bindingBase) { mBindingBase = bindingBase; } void elseStack() { // stackIndexVal = *stackIndexVar auto stackIdxVal = createLoad(stackIndexVar(), spv::NoPrecision); addName(stackIdxVal, "stackIdx"); // prevStackIdxVal = stackIndexVal - 1 auto oneConst = makeIntConstant(1); auto prevStackIdxVal = createBinOp(spv::OpISub, intType(), stackIdxVal, oneConst); // prevStateVal = stack[stackIndexVal] auto prevStackPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, stackVar(), { prevStackIdxVal }); addName(prevStackPtr, "prevStackPtr"); auto prevStateVal = createLoad(prevStackPtr, spv::NoPrecision); addName(prevStateVal, "prevStackVal"); // if (prevStackVal == Active) { auto isActive = createBinOp(spv::OpIEqual, boolType(), prevStateVal, stateActive()); auto ifBuilder = spv::Builder::If { isActive, spv::SelectionControlMaskNone, *this }; // state = *stateVar auto stateVal = createLoad(stateVar(), spv::NoPrecision); addName(stateVal, "state"); // newState = (state == Active) ? InactiveBreak : Active auto pred = createBinOp(spv::OpIEqual, boolType(), stateVal, stateActive()); auto newState = createTriOp(spv::OpSelect, intType(), pred, stateInactive(), stateActive()); // *stateVar = newState createStore(newState, stateVar()); // } ifBuilder.makeEndIf(); } void pushStack() { // stackIndexVal = *stackIndexVar auto stackIdxVal = createLoad(stackIndexVar(), spv::NoPrecision); addName(stackIdxVal, "stackIdx"); // state = *stateVar auto stateVal = createLoad(stateVar(), spv::NoPrecision); addName(stateVal, "state"); // stack[stackIndexVal] = stateVal auto stackPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, stackVar(), { stackIdxVal }); addName(stackPtr, "stackPtr"); createStore(stateVal, stackPtr); // stackIndexVal += 1 auto constPushCount = makeIntConstant(1); auto newStackIdxVal = createBinOp(spv::Op::OpIAdd, intType(), stackIdxVal, constPushCount); this->addName(newStackIdxVal, "newStackIdx"); // *stackIndexVar = stackIndexVal createStore(newStackIdxVal, stackIndexVar()); } void popStack(int popCount) { // stateIdxVal = *stackIndexVar auto stackIdxVal = createLoad(stackIndexVar(), spv::NoPrecision); addName(stackIdxVal, "stackIdx"); if (popCount > 0) { // stateIdxVal -= {popCount} auto constPopCount = makeIntConstant(popCount); auto newStackIdxVal = createBinOp(spv::Op::OpISub, intType(), stackIdxVal, constPopCount); addName(newStackIdxVal, "newStackIdx"); // *stackIndexVar = stateIdxVal createStore(newStackIdxVal, stackIndexVar()); stackIdxVal = newStackIdxVal; } // newStateVal = stack[stackIndexVal] auto stackPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, stackVar(), { stackIdxVal }); addName(stackPtr, "stackPtr"); auto newStateVal = createLoad(stackPtr, spv::NoPrecision); addName(newStateVal, "newState"); // *stateVar = newStateVal createStore(newStateVal, stateVar()); } spv::Block *startCfCondBlock(latte::SQ_CF_COND cond = latte::SQ_CF_COND::ACTIVE, uint32_t condConst = 0) { // TODO: Support other cond types // Requires implementing the CF_CONST registers decaf_check(cond == latte::SQ_CF_COND::ACTIVE); auto insideBlock = &makeNewBlock(); auto afterBlock = &makeNewBlock(); auto state = createLoad(stateVar(), spv::NoPrecision); auto pred = createBinOp(spv::OpIEqual, boolType(), state, stateActive()); createSelectionMerge(afterBlock, spv::SelectionControlMaskNone); createConditionalBranch(pred, insideBlock, afterBlock); setBuildPoint(insideBlock); return afterBlock; } void endCfCondBlock(spv::Block *afterBlock, bool blockReturned = false) { if (!blockReturned) { createBranch(afterBlock); } setBuildPoint(afterBlock); } spv::Id readChanSel(spv::Id input, latte::SQ_CHAN chan) { decaf_check(chan != latte::SQ_CHAN::T); return createCompositeExtract(input, floatType(), { static_cast<unsigned int>(chan) }); } spv::Id getGprRefGprIndex(const latte::GprRef &gpr) { spv::Id regIdx; // regIdx = {gprNumber} auto constGprNum = makeUintConstant(gpr.number); regIdx = constGprNum; switch (gpr.indexMode) { case latte::GprIndexMode::None: { // for R[{gprNumber}] break; } case latte::GprIndexMode::AR_X: { // for R[{gprNumber} + AR.x] // ARx = $AR.x auto ARxVal = getArId(SQ_CHAN::X); // regIdx = regIdx + ARx auto gprNumARx = createBinOp(spv::Op::OpIAdd, uintType(), regIdx, ARxVal); addName(gprNumARx, "gprNum"); regIdx = gprNumARx; break; } case latte::GprIndexMode::AL: { // for R[{gprNumber} + AL] // AL = *ALVar auto ALVal = createLoad(ALVar(), spv::NoPrecision); addName(ALVal, "AL"); // regIdx = regIdx + AL regIdx = createBinOp(spv::Op::OpIAdd, uintType(), regIdx, ALVal); break; } default: decaf_abort("Unexpected GPR index mode"); } return regIdx; } spv::Id getGprRef(const latte::GprRef &gpr) { auto regIdx = getGprRefGprIndex(gpr); // $ = &R[regIdx] return createAccessChain(spv::StorageClass::StorageClassPrivate, gprVar(), { regIdx }); } spv::Id readGprChanRef(const latte::GprChanRef &ref) { auto regIdxVal = getGprRefGprIndex(ref.gpr); decaf_check(ref.chan != latte::SQ_CHAN::T); auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan)); // $ = R[regIdx].{refChan} auto gprChanPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, gprVar(), { regIdxVal, chanConst }); return createLoad(gprChanPtr, spv::NoPrecision); } spv::Id readGprSelRef(const latte::GprSelRef &ref) { switch (ref.sel) { case latte::SQ_SEL::SEL_X: return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::X }); case latte::SQ_SEL::SEL_Y: return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::Y }); case latte::SQ_SEL::SEL_Z: return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::Z }); case latte::SQ_SEL::SEL_W: return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::W }); case latte::SQ_SEL::SEL_0: return makeFloatConstant(0.0f); case latte::SQ_SEL::SEL_1: return makeFloatConstant(1.0f); default: decaf_abort("Unexpected SQ_SEL in gpr sel ref"); } } spv::Id readGprMaskRef(const latte::GprMaskRef &ref) { // Read the source GPR auto srcGprPtr = getGprRef(ref.gpr); auto srcGprVal = createLoad(srcGprPtr, spv::NoPrecision); // Apply the source GPR swizzling decaf_check(isSwizzleFullyUnmasked(ref.mask)); return applySelMask(spv::NoResult, srcGprVal, ref.mask); } spv::Id getCbufferRefIndex(const latte::CbufferRef &ref) { auto indexVal = makeIntConstant(ref.index); switch (ref.indexMode) { case latte::CbufferIndexMode::None: // This is the default mode... break; case latte::CbufferIndexMode::AL: indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, createLoad(ALVar(), spv::NoPrecision)); break; default: decaf_abort("Unexpected cfile index mode"); } return indexVal; } spv::Id getCbufferRef(const latte::CbufferRef &ref) { auto thisCbufVar = cbufferVar(ref.bufferId); auto cbufIdxVal = getCbufferRefIndex(ref); // $ = &CBUFFER{bufferindex}[index] auto zeroConst = makeUintConstant(0); return createAccessChain(spv::StorageClass::StorageClassUniform, thisCbufVar, { zeroConst, cbufIdxVal }); } spv::Id readCbufferChanRef(const latte::CbufferChanRef &ref) { auto thisCbufVar = cbufferVar(ref.cbuffer.bufferId); auto cbufIdxVal = getCbufferRefIndex(ref.cbuffer); decaf_check(ref.chan != latte::SQ_CHAN::T); auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan)); // $ = CBUFFER{bufferindex}[index].{refChan} auto zeroConst = makeUintConstant(0); auto cfileChanPtr = createAccessChain(spv::StorageClass::StorageClassUniform, thisCbufVar, { zeroConst, cbufIdxVal, chanConst }); return createLoad(cfileChanPtr, spv::NoPrecision); } spv::Id getCfileRefIndex(const latte::CfileRef &ref) { auto indexVal = makeIntConstant(ref.index); switch (ref.indexMode) { case latte::CfileIndexMode::None: // This is the default mode... break; case latte::CfileIndexMode::AR_X: indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::X)); break; case latte::CfileIndexMode::AR_Y: indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::Y)); break; case latte::CfileIndexMode::AR_Z: indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::Z)); break; case latte::CfileIndexMode::AR_W: indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::W)); break; case latte::CfileIndexMode::AL: indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, createLoad(ALVar(), spv::NoPrecision)); break; default: decaf_abort("Unexpected cfile index mode"); } return indexVal; } spv::Id getCfileRef(const latte::CfileRef &ref) { auto cfileIdxVal = getCfileRefIndex(ref); // $ = &CFILE[cfileIdx] auto zeroConst = makeUintConstant(0); return createAccessChain(spv::StorageClass::StorageClassUniform, cfileVar(), { zeroConst, cfileIdxVal }); } spv::Id readCfileChanRef(const latte::CfileChanRef &ref) { auto cfileIdxVal = getCfileRefIndex(ref.cfile); decaf_check(ref.chan != latte::SQ_CHAN::T); auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan)); // $ = CFILE[index].{refChan} auto zeroConst = makeUintConstant(0); auto cfileChanPtr = createAccessChain(spv::StorageClass::StorageClassUniform, cfileVar(), { zeroConst, cfileIdxVal, chanConst }); return createLoad(cfileChanPtr, spv::NoPrecision); } spv::Id readSrcVarRef(const SrcVarRef& srcRef) { spv::Id srcId; if (srcRef.type == latte::SrcVarRef::Type::GPR) { srcId = readGprChanRef(srcRef.gprChan); } else if (srcRef.type == latte::SrcVarRef::Type::CBUFFER) { srcId = readCbufferChanRef(srcRef.cbufferChan); } else if (srcRef.type == latte::SrcVarRef::Type::CFILE) { srcId = readCfileChanRef(srcRef.cfileChan); } else if (srcRef.type == latte::SrcVarRef::Type::PREVRES) { decaf_check(srcRef.prevres.unit >= latte::SQ_CHAN::X); decaf_check(srcRef.prevres.unit <= latte::SQ_CHAN::T); srcId = getPvId(srcRef.prevres.unit); } else if (srcRef.type == latte::SrcVarRef::Type::VALUE) { if (srcRef.valueType == latte::VarRefType::FLOAT) { // We write floats as UINT's which are bitcast to preserve precision // in the case that the value will be used as a uint. auto srcFloatData = makeUintConstant(srcRef.value.uintValue); srcId = createUnaryOp(spv::OpBitcast, floatType(), srcFloatData); } else if (srcRef.valueType == latte::VarRefType::INT) { srcId = makeIntConstant(srcRef.value.intValue); } else if (srcRef.valueType == latte::VarRefType::UINT) { srcId = makeUintConstant(srcRef.value.uintValue); } else { decaf_abort("Unexpected source value type"); } } else { decaf_abort("Unexpected source var type"); } if (srcRef.valueType == latte::VarRefType::FLOAT) { // We are naturally a float for SPIRV conversion } else if (srcRef.valueType == latte::VarRefType::INT) { srcId = createUnaryOp(spv::Op::OpBitcast, intType(), srcId); } else if (srcRef.valueType == latte::VarRefType::UINT) { srcId = createUnaryOp(spv::Op::OpBitcast, uintType(), srcId); } else { decaf_abort("Unexpected source value type"); } if (srcRef.isAbsolute) { if (getTypeId(srcId) == intType()) { srcId = createBuiltinCall(intType(), glslStd450(), GLSLstd450::GLSLstd450SAbs, { srcId }); } else if (getTypeId(srcId) == floatType()) { srcId = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcId }); } else { decaf_abort("unsupported source type for absolution"); } } if (srcRef.isNegated) { if (getTypeId(srcId) == intType()) { srcId = createUnaryOp(spv::Op::OpSNegate, intType(), srcId); } else if (getTypeId(srcId) == floatType()) { srcId = createUnaryOp(spv::Op::OpFNegate, floatType(), srcId); } else { decaf_abort("unsupported source type for negation"); } } return srcId; } spv::Id readAluInstSrc(const latte::ControlFlowInst &cf, const latte::AluInstructionGroup &group, const latte::AluInst &inst, uint32_t srcIndex, latte::VarRefType valueType = latte::VarRefType::FLOAT) { auto srcRef = makeSrcVar(cf, group, inst, srcIndex, valueType); return readSrcVarRef(srcRef); } spv::Id readAluReducSrc(const latte::ControlFlowInst &cf, const latte::AluInstructionGroup &group, uint32_t srcIndex, latte::VarRefType valueType = latte::VarRefType::FLOAT) { spv::Id resultType; if (valueType == VarRefType::FLOAT) { resultType = float4Type(); } else if (valueType == VarRefType::UINT) { resultType = uint4Type(); } else if (valueType == VarRefType::INT) { resultType = int4Type(); } else { decaf_abort("Unexpected value type"); } auto srcX = makeSrcVar(cf, group, *group.units[SQ_CHAN::X], srcIndex, valueType); auto srcY = makeSrcVar(cf, group, *group.units[SQ_CHAN::Y], srcIndex, valueType); auto srcZ = makeSrcVar(cf, group, *group.units[SQ_CHAN::Z], srcIndex, valueType); auto srcW = makeSrcVar(cf, group, *group.units[SQ_CHAN::W], srcIndex, valueType); // In order to improve our shader debugging experience, if we are simply // fetching the entirety of a register, lets fetch it all at once, instead // of doing it component by component... There is actually an opportunity // to allow swizzling here too. Thats probably unneccessary though. if (srcX.type == SrcVarRef::Type::GPR && srcY.type == SrcVarRef::Type::GPR && srcZ.type == SrcVarRef::Type::GPR && srcW.type == SrcVarRef::Type::GPR && srcX.gprChan.chan == latte::SQ_CHAN::X && srcY.gprChan.chan == latte::SQ_CHAN::Y && srcZ.gprChan.chan == latte::SQ_CHAN::Z && srcW.gprChan.chan == latte::SQ_CHAN::W && (srcX.gprChan.gpr.number == srcY.gprChan.gpr.number) && (srcY.gprChan.gpr.number == srcZ.gprChan.gpr.number) && (srcZ.gprChan.gpr.number == srcW.gprChan.gpr.number) && (srcX.gprChan.gpr.indexMode == srcY.gprChan.gpr.indexMode) && (srcY.gprChan.gpr.indexMode == srcZ.gprChan.gpr.indexMode) && (srcZ.gprChan.gpr.indexMode == srcW.gprChan.gpr.indexMode)) { auto srcRegPtr = getGprRef(srcX.gprChan.gpr); return createLoad(srcRegPtr, spv::NoPrecision); } if (srcX.type == SrcVarRef::Type::CFILE && srcY.type == SrcVarRef::Type::CFILE && srcZ.type == SrcVarRef::Type::CFILE && srcW.type == SrcVarRef::Type::CFILE && srcX.cfileChan.chan == latte::SQ_CHAN::X && srcY.cfileChan.chan == latte::SQ_CHAN::Y && srcZ.cfileChan.chan == latte::SQ_CHAN::Z && srcW.cfileChan.chan == latte::SQ_CHAN::W && srcX.cfileChan.cfile.index == srcY.cfileChan.cfile.index && srcY.cfileChan.cfile.index == srcZ.cfileChan.cfile.index && srcZ.cfileChan.cfile.index == srcW.cfileChan.cfile.index && srcX.cfileChan.cfile.indexMode == srcY.cfileChan.cfile.indexMode && srcY.cfileChan.cfile.indexMode == srcZ.cfileChan.cfile.indexMode && srcZ.cfileChan.cfile.indexMode == srcW.cfileChan.cfile.indexMode) { auto srcCfilePtr = getCfileRef(srcX.cfileChan.cfile); return createLoad(srcCfilePtr, spv::NoPrecision); } if (srcX.type == SrcVarRef::Type::CBUFFER && srcY.type == SrcVarRef::Type::CBUFFER && srcZ.type == SrcVarRef::Type::CBUFFER && srcW.type == SrcVarRef::Type::CBUFFER && srcX.cbufferChan.chan == latte::SQ_CHAN::X && srcY.cbufferChan.chan == latte::SQ_CHAN::Y && srcZ.cbufferChan.chan == latte::SQ_CHAN::Z && srcW.cbufferChan.chan == latte::SQ_CHAN::W && srcX.cbufferChan.cbuffer.bufferId == srcY.cbufferChan.cbuffer.bufferId && srcY.cbufferChan.cbuffer.bufferId == srcZ.cbufferChan.cbuffer.bufferId && srcZ.cbufferChan.cbuffer.bufferId == srcW.cbufferChan.cbuffer.bufferId && srcX.cbufferChan.cbuffer.index == srcY.cbufferChan.cbuffer.index && srcY.cbufferChan.cbuffer.index == srcZ.cbufferChan.cbuffer.index && srcZ.cbufferChan.cbuffer.index == srcW.cbufferChan.cbuffer.index && srcX.cbufferChan.cbuffer.indexMode == srcY.cbufferChan.cbuffer.indexMode && srcY.cbufferChan.cbuffer.indexMode == srcZ.cbufferChan.cbuffer.indexMode && srcZ.cbufferChan.cbuffer.indexMode == srcW.cbufferChan.cbuffer.indexMode) { auto srcCbufferPtr = getCbufferRef(srcX.cbufferChan.cbuffer); return createLoad(srcCbufferPtr, spv::NoPrecision); } return createCompositeConstruct(resultType, { readSrcVarRef(srcX), readSrcVarRef(srcY), readSrcVarRef(srcZ), readSrcVarRef(srcW) }); } void writeGprChanRef(const GprChanRef& ref, spv::Id srcId) { auto regIdx = getGprRefGprIndex(ref.gpr); decaf_check(ref.chan != latte::SQ_CHAN::T); auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan)); // $ = R[regIdx].{refChan} auto gprChanPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, gprVar(), { regIdx, chanConst }); createStore(srcId, gprChanPtr); } void writeGprMaskRef(const latte::GprMaskRef &ref, spv::Id srcId) { // Perform any neccessary type conversions auto srcTypeId = getTypeId(srcId); if (srcTypeId == float4Type()) { // Nothing to do, we are already a float! } else if (srcTypeId == int4Type()) { srcId = createUnaryOp(spv::OpBitcast, float4Type(), srcId); } else if (srcTypeId == uint4Type()) { srcId = createUnaryOp(spv::OpBitcast, float4Type(), srcId); } else { decaf_abort("Unexpected type at gpr masked instruction write"); } // Grab a reference to our GPR auto gprRef = getGprRef(ref.gpr); // We must put the srcId here, since some swizzles will just be rearranging auto writeVal = srcId; // If the swizzle masks anything, we need to load the original value // of the export so that we can preserve that data not being written. if (!isSwizzleFullyUnmasked(ref.mask)) { writeVal = createLoad(gprRef, spv::NoPrecision); } writeVal = applySelMask(writeVal, srcId, ref.mask); createStore(writeVal, gprRef); } spv::Id shrinkVector(spv::Id value, uint32_t maxComponents) { // We only support 4-component vector types here... decaf_check(getNumComponents(value) == 4); // Figure out what type we need to return auto valueType = getTypeId(value); auto valueBaseType = this->getContainedTypeId(valueType); if (maxComponents == 1) { return createOp(spv::OpCompositeExtract, valueBaseType, { value, 0 }); } else if (maxComponents == 2) { return createOp(spv::OpVectorShuffle, vecType(valueBaseType, 2), { value, value, 0, 1 }); } else if (maxComponents == 3) { return createOp(spv::OpVectorShuffle, vecType(valueBaseType, 3), { value, value, 0, 1, 2 }); } else if (maxComponents == 4) { return value; } else { decaf_abort("Unexpected component count during vector shrink"); } } spv::Id applySelMask(spv::Id dest, spv::Id src, std::array<SQ_SEL, 4> mask, uint32_t maxComponents = 4) { // We only support doing masking against 4-component vectors. decaf_check(getNumComponents(src) == 4); auto sourceType = getTypeId(src); auto sourceBaseType = this->getContainedTypeId(sourceType); // For simplicity in the checking below, we set the unused mask values // to the defaults that we might expect otherwise. for (auto i = maxComponents; i < 4; ++i) { mask[i] = static_cast<latte::SQ_SEL>(latte::SQ_SEL::SEL_X + i); } // If the swizzle is just XYZW, we don't actually need to do anything... bool isNoop = true; isNoop &= (mask[0] == latte::SQ_SEL::SEL_X); isNoop &= (mask[1] == latte::SQ_SEL::SEL_Y); isNoop &= (mask[2] == latte::SQ_SEL::SEL_Z); isNoop &= (mask[3] == latte::SQ_SEL::SEL_W); if (isNoop) { return shrinkVector(src, maxComponents); } // If the swizzle is ____, we should just skip processing entirely bool isMasked = true; isMasked &= (mask[0] == latte::SQ_SEL::SEL_MASK); isMasked &= (mask[1] == latte::SQ_SEL::SEL_MASK); isMasked &= (mask[2] == latte::SQ_SEL::SEL_MASK); isMasked &= (mask[3] == latte::SQ_SEL::SEL_MASK); if (isMasked) { decaf_check(dest != spv::NoResult); return shrinkVector(dest, maxComponents); } // Lets see if this swizzle is using constants at all, since we can optimize // away the need for an intermediary vector bool usesConstants = false; for (auto selIdx = 0; selIdx < 4; ++selIdx) { switch (mask[selIdx]) { case latte::SQ_SEL::SEL_X: break; case latte::SQ_SEL::SEL_Y: break; case latte::SQ_SEL::SEL_Z: break; case latte::SQ_SEL::SEL_W: break; case latte::SQ_SEL::SEL_0: usesConstants = true; break; case latte::SQ_SEL::SEL_1: usesConstants = true; break; case latte::SQ_SEL::SEL_MASK: break; default: decaf_abort("Unexpected selector during masking operation."); } } if (!usesConstants) { // If the swizzle isn't using constants, we can just directly shuffle // between the two input vectors in a single go. std::array<unsigned int, 4> shuffleIdx = { 0, 1, 2, 3 }; for (auto selIdx = 0u; selIdx < maxComponents; ++selIdx) { switch (mask[selIdx]) { case latte::SQ_SEL::SEL_X: shuffleIdx[selIdx] = 4; break; case latte::SQ_SEL::SEL_Y: shuffleIdx[selIdx] = 5; break; case latte::SQ_SEL::SEL_Z: shuffleIdx[selIdx] = 6; break; case latte::SQ_SEL::SEL_W: shuffleIdx[selIdx] = 7; break; case latte::SQ_SEL::SEL_MASK: break; default: decaf_abort("Unexpected selector during masking operation."); } } if (dest == spv::NoResult) { decaf_check(isSwizzleFullyUnmasked(mask)); dest = src; } if (maxComponents == 1) { return createOp(spv::OpCompositeExtract, sourceBaseType, { src, shuffleIdx[0] }); } else if (maxComponents == 2) { return createOp(spv::Op::OpVectorShuffle, vecType(sourceBaseType, 2), { dest, src, shuffleIdx[0], shuffleIdx[1] }); } else if (maxComponents == 2) { return createOp(spv::Op::OpVectorShuffle, vecType(sourceBaseType, 3), { dest, src, shuffleIdx[0], shuffleIdx[1], shuffleIdx[2] }); } else if (maxComponents == 4) { return createOp(spv::Op::OpVectorShuffle, vecType(sourceBaseType, 4), { dest, src, shuffleIdx[0], shuffleIdx[1], shuffleIdx[2], shuffleIdx[3] }); } else { decaf_abort("Unexpected component count during swizzle"); } } // If the swizzle is using constants, we need to pull out the individual pieces // and then build a new vector with all the values std::array<spv::Id, 4> resultElems = { spv::NoResult }; // Because its possible to swizzle the same source channel into multiple places // of the destination, we keep a cache of the ones we've already extracted. std::array<spv::Id, 4> sourceElems = { spv::NoResult }; auto fetchSrcElem = [&](unsigned int elemIdx) { auto& elem = sourceElems[elemIdx]; if (!elem) { elem = createOp(spv::OpCompositeExtract, floatType(), { src, elemIdx }); } return elem; }; for (auto selIdx = 0u; selIdx < maxComponents; ++selIdx) { switch (mask[selIdx]) { case latte::SQ_SEL::SEL_X: resultElems[selIdx] = fetchSrcElem(0); break; case latte::SQ_SEL::SEL_Y: resultElems[selIdx] = fetchSrcElem(1); break; case latte::SQ_SEL::SEL_Z: resultElems[selIdx] = fetchSrcElem(2); break; case latte::SQ_SEL::SEL_W: resultElems[selIdx] = fetchSrcElem(3); break; case latte::SQ_SEL::SEL_0: resultElems[selIdx] = makeFloatConstant(0.0f); break; case latte::SQ_SEL::SEL_1: resultElems[selIdx] = makeFloatConstant(1.0f); break; case latte::SQ_SEL::SEL_MASK: resultElems[selIdx] = createOp(spv::OpCompositeExtract, floatType(), { dest, selIdx }); break; default: decaf_abort("Unexpected selector during masking operation."); } } if (maxComponents == 1) { return resultElems[0]; } else if (maxComponents == 2) { return createOp(spv::OpCompositeConstruct, vecType(sourceBaseType, 2), { resultElems[0], resultElems[1] }); } else if (maxComponents == 2) { return createOp(spv::OpCompositeConstruct, vecType(sourceBaseType, 3), { resultElems[0], resultElems[1], resultElems[2] }); } else if (maxComponents == 4) { return createOp(spv::OpCompositeConstruct, vecType(sourceBaseType, 4), { resultElems[0], resultElems[1], resultElems[2], resultElems[3] }); } else { decaf_abort("Unexpected component count during swizzle result construct"); } } void writeAluOpDest(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst, spv::Id srcId, bool forAr = false) { if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) { switch (inst.op2.OMOD()) { case SQ_ALU_OMOD::D2: decaf_check(getTypeId(srcId) == floatType()); srcId = createBinOp(spv::Op::OpFMul, floatType(), srcId, makeFloatConstant(0.5f)); break; case SQ_ALU_OMOD::M2: decaf_check(getTypeId(srcId) == floatType()); srcId = createBinOp(spv::Op::OpFMul, floatType(), srcId, makeFloatConstant(2)); break; case SQ_ALU_OMOD::M4: decaf_check(getTypeId(srcId) == floatType()); srcId = createBinOp(spv::Op::OpFMul, floatType(), srcId, makeFloatConstant(4)); break; case SQ_ALU_OMOD::OFF: // Nothing to do break; default: decaf_abort("Unexpected dest var OMOD"); } } if (inst.word1.CLAMP()) { decaf_check(getTypeId(srcId) == floatType()); srcId = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450FClamp, { srcId, makeFloatConstant(0), makeFloatConstant(1) }); } if (forAr) { // Instruction is responsible for dispatching the result as a // UINT automatically, so we can safely directly store it // as AR is typed as UINT in the shader. } else { // Instruction returns the value in whatever type is intended // by the instruction. We use meta-data to translate that // type to the float type used for storage in shader. auto flags = getInstructionFlags(inst); if (flags & SQ_ALU_FLAG_INT_OUT) { decaf_check(getTypeId(srcId) == intType()); } else if (flags & SQ_ALU_FLAG_UINT_OUT) { decaf_check(getTypeId(srcId) == uintType()); } auto srcTypeId = getTypeId(srcId); if (srcTypeId == floatType()) { // Nothing to do, we are already a float! } else if (srcTypeId == intType()) { srcId = createUnaryOp(spv::OpBitcast, floatType(), srcId); } else if (srcTypeId == uintType()) { srcId = createUnaryOp(spv::OpBitcast, floatType(), srcId); } else { decaf_abort("Unexpected type at ALU instruction write"); } } // According to the documentation, AR/PV writes are only valid for their // respective instructions, and other accesses are illegal... if (forAr) { decaf_check(unit != latte::SQ_CHAN::T); mARId[unit] = srcId; } else { mNextPrevResId[unit] = srcId; } if (inst.word1.ENCODING() != SQ_ALU_ENCODING::OP2 || inst.op2.WRITE_MASK()) { // According to the docs, GPR writes are undefined for AR instructions // though its worth noting that it does not actually say its illegal. decaf_check(!forAr); GprChanRef destGpr; destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), inst.word0.INDEX_MODE()); destGpr.chan = inst.word1.DST_CHAN(); mAluGroupWrites.push_back({ destGpr, srcId }); } } void flushAluGroupWrites() { for (auto& write : mAluGroupWrites) { writeGprChanRef(write.first, write.second); } mAluGroupWrites.clear(); } void writeAluReducDest(const ControlFlowInst &cf, const AluInstructionGroup &group, spv::Id srcId, bool forAr = false) { // Reduction instructions occupy XYZW, but T is free for other operations. // Note that we intentionally select a default output unit of X here, this // ensures that if there is no specifically chosen unit, that this will mean // that X implicitly had its write-mask disabled, and the writeAluOpDest // function will elide the register write. Note that no matter which unit // is writing, the result always goes to PV.x auto outputUnit = SQ_CHAN::X; for (auto i = 0u; i < 4u; ++i) { if (group.units[i]->op2.WRITE_MASK()) { outputUnit = static_cast<SQ_CHAN>(i); break; } } writeAluOpDest(cf, group, SQ_CHAN::X, *group.units[outputUnit], srcId, forAr); } void updatePredicateAndExecuteMask(const ControlFlowInst &cf, const AluInst &inst, spv::Id pred) { if (inst.op2.UPDATE_PRED()) { createStore(pred, predicateVar()); } if (inst.op2.UPDATE_EXECUTE_MASK()) { createStore(createTriOp(spv::Op::OpSelect, intType(), pred, stateActive(), stateInactive()), stateVar()); } } spv::Id getExportRefVar(const ExportRef& ref, spv::Id dataType) { if (ref.type == ExportRef::Type::Position) { decaf_check(dataType == floatType()); decaf_check(ref.dataStride == 0); decaf_check(ref.arraySize == 1); decaf_check(ref.elemCount == 1); decaf_check(ref.indexGpr == -1); return posExportVar(ref.arrayBase); } else if (ref.type == ExportRef::Type::Param) { decaf_check(dataType == floatType()); decaf_check(ref.dataStride == 0); decaf_check(ref.arraySize == 1); decaf_check(ref.elemCount == 1); decaf_check(ref.indexGpr == -1); return paramExportVar(ref.arrayBase); } else if (ref.type == ExportRef::Type::Pixel) { decaf_check(ref.dataStride == 0); decaf_check(ref.arraySize == 1); decaf_check(ref.elemCount == 1); decaf_check(ref.indexGpr == -1); return pixelExportVar(ref.arrayBase, dataType); } else if (ref.type == ExportRef::Type::ComputedZ) { decaf_check(dataType == floatType()); decaf_check(ref.dataStride == 0); decaf_check(ref.arrayBase == 0); decaf_check(ref.arraySize == 1); decaf_check(ref.elemCount == 1); decaf_check(ref.indexGpr == -1); return zExportVar(); } else if (ref.type >= ExportRef::Type::Stream0Write && ref.type <= ExportRef::Type::Stream3Write) { decaf_check(dataType == floatType()); auto streamIdx = static_cast<uint32_t>(ref.type) - static_cast<uint32_t>(ExportRef::Type::Stream0Write); return memExportWriteVar(streamIdx, ref.dataStride, ref.indexGpr, ref.arrayBase, ref.arraySize, ref.elemCount); } else if (ref.type == ExportRef::Type::VsGsRingWrite) { decaf_check(dataType == floatType()); decaf_check(ref.dataStride == 0); return vsGsRingExportWriteVar(ref.indexGpr, ref.arrayBase, ref.arraySize, ref.elemCount); } else if (ref.type == ExportRef::Type::GsDcRingWrite) { decaf_check(dataType == floatType()); decaf_check(ref.dataStride == 0); return gsDcRingExportWriteVar(ref.indexGpr, ref.arrayBase, ref.arraySize, ref.elemCount); } else { decaf_abort("Encountered unexpected export type"); } } // Takes a value as input and expands it out to being a 4-component // vector of the same underlying type. spv::Id expandVector(spv::Id value) { auto numComps = getNumComponents(value); auto baseType = getTypeId(value); if (numComps > 1) { baseType = getContainedTypeId(baseType); } if (baseType == floatType()) { auto zeroFConst = makeFloatConstant(0.0f); if (numComps == 1) { value = createOp(spv::OpCompositeConstruct, float4Type(), { value, zeroFConst, zeroFConst, zeroFConst }); } else if (numComps == 2) { value = createOp(spv::OpCompositeConstruct, float4Type(), { value, zeroFConst, zeroFConst }); } else if (numComps == 3) { value = createOp(spv::OpCompositeConstruct, float4Type(), { value, zeroFConst }); } else if (numComps == 4) { // Already the right size } else { decaf_abort("Unexpected number of export components."); } } else if (baseType == intType()) { auto zeroConst = makeIntConstant(0); if (numComps == 1) { value = createOp(spv::OpCompositeConstruct, int4Type(), { value, zeroConst, zeroConst, zeroConst }); } else if (numComps == 2) { value = createOp(spv::OpCompositeConstruct, int4Type(), { value, zeroConst, zeroConst }); } else if (numComps == 3) { value = createOp(spv::OpCompositeConstruct, int4Type(), { value, zeroConst }); } else if (numComps == 4) { // Already the right size } else { decaf_abort("Unexpected number of export components."); } } else if (baseType == uintType()) { auto zeroUConst = makeUintConstant(0); if (numComps == 1) { value = createOp(spv::OpCompositeConstruct, uint4Type(), { value, zeroUConst, zeroUConst, zeroUConst }); } else if (numComps == 2) { value = createOp(spv::OpCompositeConstruct, uint4Type(), { value, zeroUConst, zeroUConst }); } else if (numComps == 3) { value = createOp(spv::OpCompositeConstruct, uint4Type(), { value, zeroUConst }); } else if (numComps == 4) { // Already the right size } else { decaf_abort("Unexpected number of export components."); } } else { decaf_abort("Unexpected export source data type"); } return value; } void writeExportRef(const ExportMaskRef& ref, spv::Id srcId) { // Fetch the underlying type of the export. Note that we require that // this method be invoked with 4-component vectors only! auto srcBaseType = getContainedTypeId(getTypeId(srcId)); // Find the export reference for this, we pass in the source type to hint // at the export creation what the output type should be. This function // must return something that is of the same type! auto exportPtr = getExportRefVar(ref.output, srcBaseType); // Lets figure out the correct size of the data so that we can // shrink the source down to the right size at the same time. auto exportType = getDerefTypeId(exportPtr); auto numExportComps = getNumTypeComponents(exportType); // Apply any export masking operation thats needed, this will also // shrink the object down to the expected size at the same time. // Note: We shouldn't be reading from outputs, but sometimes they // mask data into a write, which makes very little sense... auto origData = createLoad(exportPtr, spv::NoPrecision); auto exportVal = applySelMask(origData, srcId, ref.mask, numExportComps); auto sourceValType = getTypeId(exportVal); // Lets perform any specialized behaviour depending on the export type if (ref.output.type == ExportRef::Type::Position) { decaf_check(sourceValType == float4Type()); // Need to reposition the depth values from (-1.0 to 1.0) to (0.0 to 1.0) auto zeroConst = makeUintConstant(0); auto oneConst = makeUintConstant(1); auto zeroFConst = makeFloatConstant(0.0f); auto oneFConst = makeFloatConstant(1.0f); auto posMulAddPtr = createAccessChain(spv::StorageClass::StorageClassPushConstant, vsPushConstVar(), { zeroConst }); auto posMulAddVal = createLoad(posMulAddPtr, spv::NoPrecision); auto zSpaceMulPtr = createAccessChain(spv::StorageClass::StorageClassPushConstant, vsPushConstVar(), { oneConst }); auto zSpaceMulVal = createLoad(zSpaceMulPtr, spv::NoPrecision); // pos.xy = (pos.xy * posMulAdd.xy) + posMulAdd.zw; auto posMulPart = createOp(spv::OpVectorShuffle, float2Type(), { posMulAddVal, posMulAddVal, 0, 1 }); auto posMulVal = createOp(spv::OpCompositeConstruct, float4Type(), { posMulPart, oneFConst, oneFConst }); auto posAddPart = createOp(spv::OpVectorShuffle, float2Type(), { posMulAddVal, posMulAddVal, 2, 3 }); auto posAddVal = createOp(spv::OpCompositeConstruct, float4Type(), { posAddPart, zeroFConst, zeroFConst }); exportVal = createBinOp(spv::OpFMul, float4Type(), exportVal, posMulVal); exportVal = createBinOp(spv::OpFAdd, float4Type(), exportVal, posAddVal); // pos.y = -pos.y auto exportY = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 1 }); exportY = createUnaryOp(spv::OpFNegate, floatType(), exportY); exportVal = createOp(spv::OpCompositeInsert, float4Type(), { exportY, exportVal, 1 }); // pos.z = (pos.z + (pos.w * zSpaceMul.x)) * zSpaceMul.y; auto zsYWMul = createOp(spv::OpCompositeExtract, floatType(), { zSpaceMulVal, 0 }); auto zsYYMul = createOp(spv::OpCompositeExtract, floatType(), { zSpaceMulVal, 1 }); auto exportZ = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 2 }); auto exportW = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 3 }); auto yWAdd = createBinOp(spv::OpFMul, floatType(), exportW, zsYWMul); auto zAdj = createBinOp(spv::OpFAdd, floatType(), exportZ, yWAdd); auto zFinal = createBinOp(spv::OpFMul, floatType(), zAdj, zsYYMul); exportVal = createOp(spv::OpCompositeInsert, float4Type(), { zFinal, exportVal, 2 }); } if (ref.output.type == ExportRef::Type::Pixel) { decaf_check(sourceValType == float4Type() || sourceValType == int4Type() || sourceValType == uint4Type()); auto zeroConst = makeUintConstant(0); auto oneConst = makeUintConstant(1); // We use the first exported pixel to perform alpha reference testing. This // may not actually be the correct behaviour. // TODO: Check which exported pixel does alpha testing. if (ref.output.arrayBase == 0 && sourceValType == float4Type()) { auto exportAlpha = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 3 }); auto alphaFuncPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { zeroConst }); auto alphaDataVal = createLoad(alphaFuncPtr, spv::NoPrecision); auto alphaFuncVal = createBinOp(spv::OpBitwiseAnd, uintType(), alphaDataVal, makeUintConstant(0xFF)); auto alphaRefPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { oneConst }); auto alphaRefVal = createLoad(alphaRefPtr, spv::NoPrecision); auto makeCompareBlock = [&](spv::Op op) { auto pred = createBinOp(op, boolType(), exportAlpha, alphaRefVal); auto notPred = createUnaryOp(spv::Op::OpLogicalNot, boolType(), pred); auto block = spv::Builder::If { notPred, spv::SelectionControlMaskNone, *this }; makeDiscard(); block.makeEndIf(); }; auto makeEqCompareBlock = [&](bool wantEquality) { auto epsilonConst = makeFloatConstant(0.0001f); auto alphaDiff = createBinOp(spv::OpFSub, floatType(), exportAlpha, alphaRefVal); auto alphaDiffAbs = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450FAbs, { alphaDiff }); spv::Id pred; if (wantEquality) { pred = createBinOp(spv::OpFOrdGreaterThan, boolType(), alphaDiffAbs, epsilonConst); } else { pred = createBinOp(spv::OpFOrdLessThanEqual, boolType(), alphaDiffAbs, epsilonConst); } auto block = spv::Builder::If { pred, spv::SelectionControlMaskNone, *this }; makeDiscard(); block.makeEndIf(); }; auto switchSegments = std::vector<spv::Block *> { }; makeSwitch(alphaFuncVal, spv::SelectionControlMaskNone, 7, { latte::REF_FUNC::NEVER, latte::REF_FUNC::LESS, latte::REF_FUNC::EQUAL, latte::REF_FUNC::LESS_EQUAL, latte::REF_FUNC::GREATER, latte::REF_FUNC::NOT_EQUAL, latte::REF_FUNC::GREATER_EQUAL, }, { 0, 1, 2, 3, 4, 5, 6 }, -1, switchSegments); nextSwitchSegment(switchSegments, latte::REF_FUNC::NEVER); makeDiscard(); addSwitchBreak(); nextSwitchSegment(switchSegments, latte::REF_FUNC::LESS); makeCompareBlock(spv::OpFOrdLessThan); addSwitchBreak(); nextSwitchSegment(switchSegments, latte::REF_FUNC::EQUAL); makeEqCompareBlock(true); addSwitchBreak(); nextSwitchSegment(switchSegments, latte::REF_FUNC::LESS_EQUAL); makeCompareBlock(spv::OpFOrdLessThanEqual); addSwitchBreak(); nextSwitchSegment(switchSegments, latte::REF_FUNC::GREATER); makeCompareBlock(spv::OpFOrdGreaterThan); addSwitchBreak(); nextSwitchSegment(switchSegments, latte::REF_FUNC::NOT_EQUAL); makeEqCompareBlock(false); addSwitchBreak(); nextSwitchSegment(switchSegments, latte::REF_FUNC::GREATER_EQUAL); makeCompareBlock(spv::OpFOrdGreaterThanEqual); addSwitchBreak(); endSwitch(switchSegments); } auto pixelTmpVar = createVariable(spv::NoPrecision, spv::StorageClassPrivate, sourceValType, "_pixelTmp"); createStore(exportVal, pixelTmpVar); auto alphaDataPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { zeroConst }); auto alphaDataVal = createLoad(alphaDataPtr, spv::NoPrecision); auto logicOpVal = createBinOp(spv::OpShiftRightLogical, uintType(), alphaDataVal, makeUintConstant(8)); auto lopSet = createBinOp(spv::Op::OpIEqual, boolType(), logicOpVal, makeUintConstant(1)); auto eq1block = spv::Builder::If { lopSet, spv::SelectionControlMaskNone, *this }; { if (sourceValType == float4Type()) { auto newValElem = makeFloatConstant(1.0f); auto newVal = makeCompositeConstant(float4Type(), { newValElem, newValElem, newValElem, newValElem }); createStore(newVal, pixelTmpVar); } else if (sourceValType == int4Type()) { auto newValElem = makeIntConstant(0xFFFFFFFF); auto newVal = makeCompositeConstant(int4Type(), { newValElem, newValElem, newValElem, newValElem }); createStore(newVal, pixelTmpVar); } else if (sourceValType == uint4Type()) { auto newValElem = makeUintConstant(0xFFFFFFFF); auto newVal = makeCompositeConstant(uint4Type(), { newValElem, newValElem, newValElem, newValElem }); createStore(newVal, pixelTmpVar); } else { decaf_abort("Unexpected source pixel variable type"); } } eq1block.makeBeginElse(); { auto lopClear = createBinOp(spv::Op::OpIEqual, boolType(), logicOpVal, makeUintConstant(2)); auto eq2block = spv::Builder::If { lopClear, spv::SelectionControlMaskNone, *this }; { if (sourceValType == float4Type()) { auto newValElem = makeFloatConstant(0.0f); auto newVal = makeCompositeConstant(float4Type(), { newValElem, newValElem, newValElem, newValElem }); createStore(newVal, pixelTmpVar); } else if (sourceValType == int4Type()) { auto newValElem = makeIntConstant(0); auto newVal = makeCompositeConstant(int4Type(), { newValElem, newValElem, newValElem, newValElem }); createStore(newVal, pixelTmpVar); } else if (sourceValType == uint4Type()) { auto newValElem = makeUintConstant(0); auto newVal = makeCompositeConstant(uint4Type(), { newValElem, newValElem, newValElem, newValElem }); createStore(newVal, pixelTmpVar); } else { decaf_abort("Unexpected source pixel variable type"); } } eq2block.makeEndIf(); } eq1block.makeEndIf(); // We need to premultiply the alpha in cases where premultiplied alpha is enabled // globally but this specific target is not performing the premultiplication. if (sourceValType == float4Type()) { auto twoConst = makeUintConstant(2); auto oneFConst = makeFloatConstant(1.0f); auto needsPremulPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { twoConst }); auto needsPremulVal = createLoad(needsPremulPtr, spv::NoPrecision); auto targetBitConst = makeUintConstant(1 << ref.output.arrayBase); auto targetBitVal = createBinOp(spv::OpBitwiseAnd, uintType(), needsPremulVal, targetBitConst); auto pred = createBinOp(spv::OpINotEqual, boolType(), targetBitVal, zeroConst); auto eq0block = spv::Builder::If { pred, spv::SelectionControlMaskNone, *this }; { exportVal = createLoad(pixelTmpVar, spv::NoPrecision); auto exportAlpha = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 3 }); auto premulMul = createOp(spv::OpCompositeConstruct, float4Type(), { exportAlpha, exportAlpha, exportAlpha, oneFConst }); auto premulExportVal = createBinOp(spv::OpFMul, float4Type(), exportVal, premulMul); createStore(premulExportVal, pixelTmpVar); } eq0block.makeEndIf(); } exportVal = createLoad(pixelTmpVar, spv::NoPrecision); } createStore(exportVal, exportPtr); } spv::Id vertexIdVar() { if (!mVertexId) { mVertexId = createVariable(spv::NoPrecision, spv::StorageClassInput, intType(), "VertexID"); addDecoration(mVertexId, spv::DecorationBuiltIn, spv::BuiltInVertexIndex); mEntryPoint->addIdOperand(mVertexId); } return mVertexId; } spv::Id instanceIdVar() { if (!mInstanceId) { mInstanceId = createVariable(spv::NoPrecision, spv::StorageClassInput, intType(), "InstanceID"); addDecoration(mInstanceId, spv::DecorationBuiltIn, spv::BuiltInInstanceIndex); mEntryPoint->addIdOperand(mInstanceId); } return mInstanceId; } spv::Id fragCoordVar() { if (!mFragCoord) { mFragCoord = createVariable(spv::NoPrecision, spv::StorageClassInput, float4Type(), "FragCoord"); addDecoration(mFragCoord, spv::DecorationBuiltIn, spv::BuiltInFragCoord); mEntryPoint->addIdOperand(mFragCoord); } return mFragCoord; } spv::Id frontFacingVar() { if (!mFrontFacing) { mFrontFacing = createVariable(spv::NoPrecision, spv::StorageClassInput, boolType(), "FrontFacing"); addDecoration(mFrontFacing, spv::DecorationBuiltIn, spv::BuiltInFrontFacing); mEntryPoint->addIdOperand(mFrontFacing); } return mFrontFacing; } spv::Id layerIdVar() { if (!mLayerId) { mLayerId = createVariable(spv::NoPrecision, spv::StorageClassOutput, intType(), "LayerID"); addDecoration(mLayerId, spv::DecorationBuiltIn, spv::BuiltInLayer); mEntryPoint->addIdOperand(mLayerId); } return mLayerId; } spv::Id pointSizeVar() { if (!mPointSize) { mPointSize = createVariable(spv::NoPrecision, spv::StorageClassOutput, floatType(), "PointSize"); addDecoration(mPointSize, spv::DecorationBuiltIn, spv::BuiltInPointSize); mEntryPoint->addIdOperand(mPointSize); } return mPointSize; } spv::Id inputAttribVar(int semLocation, spv::Id attribType) { auto attribIdx = mAttribInputs.size(); auto attribVar = createVariable(spv::NoPrecision, spv::StorageClassInput, attribType, fmt::format("INPUT_{}", attribIdx).c_str()); addDecoration(attribVar, spv::DecorationLocation, static_cast<int>(semLocation)); mEntryPoint->addIdOperand(attribVar); mAttribInputs.push_back(attribVar); return attribVar; } spv::Id inputParamVar(int semLocation) { auto paramIdx = mParamInputs.size(); auto inputVar = createVariable(spv::NoPrecision, spv::StorageClassInput, float4Type(), fmt::format("PARAM_{}", paramIdx).c_str()); addDecoration(inputVar, spv::DecorationLocation, static_cast<int>(semLocation)); mEntryPoint->addIdOperand(inputVar); mParamInputs.push_back(inputVar); return inputVar; } spv::Id inputRingVar(int index) { while (mRingInputs.size() <= index) { mRingInputs.push_back(spv::NoResult); } auto inputVar = mRingInputs[index]; if (!inputVar) { inputVar = createVariable(spv::NoPrecision, spv::StorageClassInput, arrayType(float4Type(), 16, 3), fmt::format("RINGIN_{}", index).c_str()); addDecoration(inputVar, spv::DecorationLocation, static_cast<int>(index)); mEntryPoint->addIdOperand(inputVar); mRingInputs[index] = inputVar; } return inputVar; } spv::Id vsPushConstVar() { if (!mVsPushConsts) { auto vsPushStruct = makeStructType({ float4Type(), float4Type(), floatType() }, "VS_PUSH_CONSTANTS"); addMemberDecoration(vsPushStruct, 0, spv::DecorationOffset, spirv::VertexPushConstantsOffset + static_cast<int>(offsetof(spirv::VertexPushConstants, posMulAdd))); addMemberName(vsPushStruct, 0, "posMulAdd"); addMemberDecoration(vsPushStruct, 1, spv::DecorationOffset, spirv::VertexPushConstantsOffset + static_cast<int>(offsetof(spirv::VertexPushConstants, zSpaceMul))); addMemberName(vsPushStruct, 1, "zSpaceMul"); addMemberDecoration(vsPushStruct, 2, spv::DecorationOffset, spirv::VertexPushConstantsOffset + static_cast<int>(offsetof(spirv::VertexPushConstants, pointSize))); addMemberName(vsPushStruct, 2, "pointSize"); addDecoration(vsPushStruct, spv::DecorationBlock); mVsPushConsts = createVariable(spv::NoPrecision, spv::StorageClassPushConstant, vsPushStruct, "VS_PUSH"); } return mVsPushConsts; } spv::Id gsPushConstVar() { decaf_abort("There are not geometry shader push constants"); } spv::Id psPushConstVar() { if (!mPsPushConsts) { auto psPushStruct = makeStructType({ uintType(), floatType(), uintType() }, "PS_PUSH_DATA"); addMemberDecoration(psPushStruct, 0, spv::DecorationOffset, spirv::FragmentPushConstantsOffset + static_cast<int>(offsetof(spirv::FragmentPushConstants, alphaFunc))); addMemberName(psPushStruct, 0, "alphaFunc"); addMemberDecoration(psPushStruct, 1, spv::DecorationOffset, spirv::FragmentPushConstantsOffset + static_cast<int>(offsetof(spirv::FragmentPushConstants, alphaRef))); addMemberName(psPushStruct, 1, "alphaRef"); addMemberDecoration(psPushStruct, 2, spv::DecorationOffset, spirv::FragmentPushConstantsOffset + static_cast<int>(offsetof(spirv::FragmentPushConstants, needsPremultiply))); addMemberName(psPushStruct, 2, "needsPremultiply"); addDecoration(psPushStruct, spv::DecorationBlock); mPsPushConsts = createVariable(spv::NoPrecision, spv::StorageClassPushConstant, psPushStruct, "PS_PUSH"); } return mPsPushConsts; } spv::Id cfileVar() { if (!mRegistersBuffer) { auto regsType = arrayType(float4Type(), 16, 256); auto structType = this->makeStructType({ regsType }, "CFILE_DATA"); addMemberDecoration(structType, 0, spv::DecorationOffset, 0); addDecoration(structType, spv::DecorationBufferBlock); addMemberName(structType, 0, "values"); auto bindingIdx = mBindingBase + latte::MaxTextures; mRegistersBuffer = createVariable(spv::NoPrecision, spv::StorageClassUniform, structType, "CFILE"); addDecoration(mRegistersBuffer, spv::DecorationDescriptorSet, 0); addDecoration(mRegistersBuffer, spv::DecorationBinding, bindingIdx); addDecoration(mRegistersBuffer, spv::DecorationNonWritable); } return mRegistersBuffer; } spv::Id cbufferVar(uint32_t cbufferIdx) { decaf_check(cbufferIdx < latte::MaxUniformBlocks); auto cbuffer = mUniformBuffers[cbufferIdx]; if (!cbuffer) { auto valuesType = arrayType(float4Type(), 16, 0); auto structType = this->makeStructType({ valuesType }, fmt::format("CBUFFER_DATA_{}", cbufferIdx).c_str()); addMemberDecoration(structType, 0, spv::DecorationOffset, 0); addDecoration(structType, spv::DecorationBufferBlock); addMemberName(structType, 0, "values"); auto bindingIdx = mBindingBase + latte::MaxTextures + cbufferIdx; cbuffer = createVariable(spv::NoPrecision, spv::StorageClassUniform, structType, fmt::format("CBUFFER_{}", cbufferIdx).c_str()); addDecoration(cbuffer, spv::DecorationDescriptorSet, 0); addDecoration(cbuffer, spv::DecorationBinding, bindingIdx); addDecoration(cbuffer, spv::DecorationNonWritable); mUniformBuffers[cbufferIdx] = cbuffer; } return cbuffer; } spv::Id samplerVar(uint32_t samplerIdx) { decaf_check(samplerIdx < latte::MaxSamplers); auto samplerId = mSamplers[samplerIdx]; if (!samplerId) { samplerId = createVariable(spv::NoPrecision, spv::StorageClassUniformConstant, samplerType()); addName(samplerId, fmt::format("SAMPLER_{}", samplerIdx).c_str()); auto bindingIdx = mBindingBase + samplerIdx; addDecoration(samplerId, spv::DecorationDescriptorSet, 0); addDecoration(samplerId, spv::DecorationBinding, bindingIdx); mSamplers[samplerIdx] = samplerId; } return samplerId; } spv::Id textureVarType(uint32_t textureIdx, latte::SQ_TEX_DIM texDim, TextureInputType texFormat) { decaf_check(textureIdx < latte::MaxTextures); spv::Id resultType; if (texFormat == TextureInputType::FLOAT) { resultType = floatType(); } else if (texFormat == TextureInputType::INT) { resultType = uintType(); } else { decaf_abort("Unexpected texture input format"); } auto textureType = mTextureTypes[textureIdx]; if (!textureType) { // TODO: This shouldn't exist here... switch (texDim) { case latte::SQ_TEX_DIM::DIM_1D: textureType = makeImageType(resultType, spv::Dim1D, false, false, false, 1, spv::ImageFormatUnknown); break; case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: textureType = makeImageType(resultType, spv::Dim2D, false, false, false, 1, spv::ImageFormatUnknown); break; case latte::SQ_TEX_DIM::DIM_3D: textureType = makeImageType(resultType, spv::Dim3D, false, false, false, 1, spv::ImageFormatUnknown); break; case latte::SQ_TEX_DIM::DIM_CUBEMAP: textureType = makeImageType(resultType, spv::Dim2D, false, true, false, 1, spv::ImageFormatUnknown); break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: textureType = makeImageType(resultType, spv::Dim1D, false, true, false, 1, spv::ImageFormatUnknown); break; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: textureType = makeImageType(resultType, spv::Dim2D, false, true, false, 1, spv::ImageFormatUnknown); break; default: decaf_abort("Unexpected texture dim type"); } mTextureTypes[textureIdx] = textureType; } return textureType; } spv::Id textureVar(uint32_t textureIdx, latte::SQ_TEX_DIM texDim, TextureInputType texFormat) { decaf_check(textureIdx < latte::MaxTextures); auto textureId = mTextures[textureIdx]; if (!textureId) { textureId = createVariable(spv::NoPrecision, spv::StorageClassUniformConstant, textureVarType(textureIdx, texDim, texFormat)); addName(textureId, fmt::format("TEXTURE_{}", textureIdx).c_str()); auto bindingIdx = mBindingBase + textureIdx; addDecoration(textureId, spv::DecorationDescriptorSet, 0); addDecoration(textureId, spv::DecorationBinding, bindingIdx); mTextures[textureIdx] = textureId; } return textureId; } spv::Id pixelExportVar(uint32_t pixelIdx, spv::Id outputType) { decaf_check(pixelIdx < latte::MaxRenderTargets); auto exportType = vecType(outputType, 4); auto& exportId = mPixelExports[pixelIdx]; if (!exportId) { exportId = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, exportType, fmt::format("PIXEL_{}", pixelIdx).c_str()); addDecoration(exportId, spv::Decoration::DecorationLocation, pixelIdx); mPixelExports[pixelIdx] = exportId; mEntryPoint->addIdOperand(exportId); } // Lets confirm that the type is correct! If we fetched this from the // map, its possible that the type has changed, which we do not handle. decaf_check(getDerefTypeId(exportId) == exportType); return exportId; } spv::Id posExportVar(uint32_t posIdx) { decaf_check(posIdx < 4); auto& exportId = mPosExports[posIdx]; if (!exportId) { exportId = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, float4Type(), fmt::format("POS_{}", posIdx).c_str()); if (posIdx == 0) { // Mark this as being the position output addDecoration(exportId, spv::DecorationBuiltIn, spv::BuiltInPosition); } else { // Mark this to a location, not sure if this is really needed addDecoration(exportId, spv::Decoration::DecorationLocation, 60 + posIdx); } mEntryPoint->addIdOperand(exportId); } return exportId; } spv::Id paramExportVar(uint32_t paramIdx) { decaf_check(paramIdx < 32); auto& exportId = mParamExports[paramIdx]; if (!exportId) { exportId = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, float4Type(), fmt::format("PARAM_{}", paramIdx).c_str()); addDecoration(exportId, spv::Decoration::DecorationLocation, paramIdx); mEntryPoint->addIdOperand(exportId); } return exportId; } spv::Id zExportVar() { if (!mZExport) { addExecutionMode(mFunctions["main"], spv::ExecutionModeDepthReplacing); mZExport = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, floatType(), "Z_EXPORT"); addDecoration(mZExport, spv::DecorationBuiltIn, spv::BuiltInFragDepth); mEntryPoint->addIdOperand(mZExport); } return mZExport; } spv::Id memExportWriteVar(uint32_t streamIdx, uint32_t dataStride, uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { decaf_check(streamIdx < 4); decaf_check(dataStride > 0); // We do not currently support dynamic indexing into streamout decaf_check(indexGpr == -1); // TODO: Support write prevention based on the array size. // We have to add the transform feedback capability to use Xfb addCapability(spv::CapabilityTransformFeedback); addExecutionMode(mFunctions["main"], spv::ExecutionModeXfb); // Calculate the offset of vec4's since that is all we currently support auto& streamExports = mMemWriteExports[streamIdx]; // We only support writing 1-4 dwords per memory write. This might break // down when someone does a burst write :S decaf_check(elemCount == 1); decaf_check(arraySize <= 4); auto writeElemCount = arraySize * elemCount; // We only support writing in vec4 increments auto bufferOffset = arrayBase * 4; // Determine the type of this stream out object spv::Id resultType = spv::NoResult; if (writeElemCount == 1) { resultType = floatType(); } else if (writeElemCount == 2) { resultType = float2Type(); } else if (writeElemCount == 3) { resultType = float3Type(); } else if (writeElemCount == 4) { resultType = float4Type(); } else { decaf_abort("Unexpected stream out item size"); } auto& exportId = streamExports[bufferOffset]; if (!exportId) { // We currently place the stream-out data at the end of the params // assuming that nobody will ever use all stream-out with parameter // output as well. It may break in wierd cases. auto streamOutIndex = mNumStreamOut++; exportId = createVariable(spv::NoPrecision, spv::StorageClassOutput, resultType, fmt::format("STREAMOUT_{}_{}", streamIdx, bufferOffset).c_str()); addDecoration(exportId, spv::DecorationLocation, 31 - streamOutIndex); addDecoration(exportId, spv::DecorationXfbBuffer, streamIdx); addDecoration(exportId, spv::DecorationXfbStride, dataStride); addDecoration(exportId, spv::DecorationOffset, bufferOffset); mEntryPoint->addIdOperand(exportId); } decaf_check(getDerefTypeId(exportId) == resultType); return exportId; } spv::Id vsGsRingExportWriteVar(uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { // TODO: Support write prevention based on the array size. // We do not currently support dynamic indexing into streamout decaf_check(indexGpr == -1); auto bufferOffset = arrayBase * elemCount; decaf_check(bufferOffset % 16 == 0); auto bufferVec4Offset = bufferOffset / 16; // Determine the size for this ringbuffer write spv::Id resultType = spv::NoResult; if (elemCount == 1) { resultType = floatType(); } else if (elemCount == 2) { resultType = float2Type(); } else if (elemCount == 3) { resultType = float3Type(); } else if (elemCount == 4) { resultType = float4Type(); } else { decaf_abort("Unexpected element count for ringbuffer write"); } auto& exportId = mRingWriteExports[bufferVec4Offset]; if (!exportId) { exportId = createVariable(spv::NoPrecision, spv::StorageClassOutput, resultType, fmt::format("VSRING_{}", bufferVec4Offset).c_str()); addDecoration(exportId, spv::DecorationLocation, bufferVec4Offset); mEntryPoint->addIdOperand(exportId); } decaf_check(getDerefTypeId(exportId) == resultType); return exportId; } spv::Id gsDcRingExportWriteVar(uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount) { // TODO: Support write prevention based on the array size. // Calculate the overall offset into the buffer auto bufferOffset = arrayBase * elemCount; // Grab the vec4 offset, writes must only occur on the boundaries // of a single vec4 write... decaf_check(bufferOffset % 16 == 0); auto ringVec4Offset = bufferOffset / 16; // Create a pointer to our memory object auto ringOffset = makeIntConstant(ringVec4Offset); if (indexGpr != -1) { // Fetch the index GPR value GprSelRef gprRef; gprRef.gpr.number = indexGpr; gprRef.gpr.indexMode = GprIndexMode::None; gprRef.sel = latte::SQ_SEL::SEL_X; auto indexGprVal = readGprSelRef(gprRef); // Bitcast it to an integer indexGprVal = createUnaryOp(spv::OpBitcast, intType(), indexGprVal); // Add the indexed value to the ring offset ringOffset = createBinOp(spv::OpIAdd, intType(), indexGprVal, ringOffset); } return createAccessChain(spv::StorageClassPrivate, ringVar(), { ringOffset }); } spv::Id stateVar() { if (!mState) { mState = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, intType(), "stateVar"); } return mState; } spv::Id stateActive() { return makeIntConstant(0); } spv::Id stateInactive() { return makeIntConstant(1); } spv::Id stateInactiveBreak() { return makeIntConstant(2); } spv::Id stateInactiveContinue() { return makeIntConstant(3); } spv::Id predicateVar() { if (!mPredicate) { mPredicate = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, boolType(), "predVar"); } return mPredicate; } spv::Id stackVar() { if (!mStack) { auto stackType = arrayType(intType(), 4, 16); mStack = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, stackType, "stackVar"); } return mStack; } spv::Id stackIndexVar() { if (!mStackIndex) { mStackIndex = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, intType(), "stackIdxVar"); } return mStackIndex; } spv::Id gprVar() { if (!mGpr) { auto gprType = arrayType(float4Type(), 16, 128); mGpr = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, gprType, "RVar"); } return mGpr; } spv::Id ringVar() { if (!mRing) { auto ringType = arrayType(float4Type(), 16, 128); mRing = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, ringType, "LocalRing"); } return mRing; } spv::Id ringOffsetVar() { if (!mRingOffset) { mRingOffset = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, uintType(), "RingIndex"); } return mRingOffset; } spv::Id ALVar() { if (!mAL) { mAL = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, intType(), "ALVar"); } return mAL; } void resetAr() { // It is not legal to modify the AR register in the same instruction group // that reads it, so we don't need to worry about swapping things around // like we do for PV/PS. for (auto i = 0; i < 4; ++i) { mARId[i] = spv::NoResult; } } void swapPrevRes() { // Make the next prevRes active mPrevResId = mNextPrevResId; // Clear the next prevRes's for (auto i = 0; i < 5; ++i) { mNextPrevResId[i] = spv::NoResult; } } spv::Id getPvId(latte::SQ_CHAN unit) { auto pvVal = mPrevResId[unit]; decaf_check(getTypeId(pvVal) == floatType()); return pvVal; } spv::Id getArId(latte::SQ_CHAN unit) { decaf_check(unit != latte::SQ_CHAN::T); auto arVal = mARId[unit]; decaf_check(getTypeId(arVal) == intType()); return arVal; } bool hasFunction(const std::string& name) const { return mFunctions.find(name) != mFunctions.end(); } spv::Function *getFunction(const std::string& name) { auto &func = mFunctions[name]; if (!func) { auto savePos = getBuildPoint(); auto entryBlock = static_cast<spv::Block *>(nullptr); func = makeFunctionEntry(spv::NoPrecision, makeVoidType(), name.c_str(), {}, {}, &entryBlock); setBuildPoint(savePos); } return func; } bool isSamplerUsed(uint32_t samplerIdx) const { decaf_check(samplerIdx <= latte::MaxSamplers); return mSamplers[samplerIdx] != spv::NoResult; } bool isTextureUsed(uint32_t textureIdx) const { decaf_check(textureIdx <= latte::MaxTextures); return mTextures[textureIdx] != spv::NoResult; } bool isConstantFileUsed() const { return mRegistersBuffer != spv::NoResult; } bool isUniformBufferUsed(uint32_t bufferIdx) const { decaf_check(bufferIdx <= latte::MaxUniformBlocks); return mUniformBuffers[bufferIdx] != spv::NoResult; } bool isStreamOutUsed(uint32_t bufferIdx) const { decaf_check(bufferIdx <= latte::MaxStreamOutBuffers); return !mMemWriteExports[bufferIdx].empty(); } bool isPixelOutUsed(uint32_t exportIdx) const { return mPixelExports.find(exportIdx) != mPixelExports.end(); } int getNumPosExports() { return (int)mPosExports.size(); } int getNumParamExports() { return (int)mParamExports.size(); } int getNumPixelExports() { return (int)mPixelExports.size(); } void makeDiscard() { makeStatementTerminator(spv::OpKill, "post-discard"); } protected: std::vector<std::pair<GprChanRef, spv::Id>> mAluGroupWrites; uint32_t mBindingBase; spv::Instruction *mEntryPoint = nullptr; std::unordered_map<std::string, spv::Function*> mFunctions; spv::Id mVertexId = spv::NoResult; spv::Id mInstanceId = spv::NoResult; spv::Id mFragCoord = spv::NoResult; spv::Id mFrontFacing = spv::NoResult; spv::Id mLayerId = spv::NoResult; spv::Id mPointSize = spv::NoResult; spv::Id mRegistersBuffer = spv::NoResult; std::array<spv::Id, latte::MaxUniformBlocks> mUniformBuffers = { spv::NoResult }; spv::Id mVsPushConsts = spv::NoResult; spv::Id mGsPushConsts = spv::NoResult; spv::Id mPsPushConsts = spv::NoResult; std::vector<spv::Id> mAttribInputs; std::vector<spv::Id> mParamInputs; std::vector<spv::Id> mRingInputs; std::array<spv::Id, latte::MaxSamplers> mSamplers = { spv::NoResult }; std::array<spv::Id, latte::MaxTextures> mTextureTypes = { spv::NoResult }; std::array<spv::Id, latte::MaxTextures> mTextures = { spv::NoResult }; std::map<uint32_t, spv::Id> mPixelExports; std::map<uint32_t, spv::Id> mPosExports; std::map<uint32_t, spv::Id> mParamExports; spv::Id mZExport = spv::NoResult; std::array<std::map<uint32_t, spv::Id>, latte::MaxStreamOutBuffers> mMemWriteExports; uint32_t mNumStreamOut = 0; std::map<uint32_t, spv::Id> mRingWriteExports; spv::Id mState = spv::NoResult; spv::Id mPredicate = spv::NoResult; spv::Id mStackIndex = spv::NoResult; spv::Id mStack = spv::NoResult; spv::Id mGpr = spv::NoResult; spv::Id mAL = spv::NoResult; spv::Id mRing = spv::NoResult; spv::Id mRingOffset = spv::NoResult; std::array<spv::Id, 4> mARId = { spv::NoResult }; std::array<spv::Id, 5> mPrevResId = { spv::NoResult }; std::array<spv::Id, 5> mNextPrevResId = { spv::NoResult }; }; } //namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_spvbuilder.h ================================================ #pragma once #ifdef DECAF_VULKAN #include <common/bit_cast.h> #include <common/strutils.h> #include <SPIRV/SpvBuilder.h> #include <SPIRV/GLSL.std.450.h> namespace spirv { class SpvBuilder : public spv::Builder { public: SpvBuilder() : spv::Builder(0x10000, 0xFFFFFFFF, nullptr) { } spv::Id createAccessChain(spv::StorageClass storageClass, spv::Id base, const std::vector<spv::Id> &offsets) { SpvBuilder::accessChain.base = base; SpvBuilder::accessChain.indexChain = offsets; return spv::Builder::createAccessChain(storageClass, base, offsets); } // ------------------------------------------------------------ // Constants // ------------------------------------------------------------ spv::Id makeFloatConstant(float f, bool specConstant = false) { if (specConstant) { return spv::Builder::makeFloatConstant(f, specConstant); } auto bitsValue = bit_cast<uint32_t>(f); auto constVal = mFloatConstants[bitsValue]; if (constVal) { return constVal; } constVal = spv::Builder::makeFloatConstant(f, specConstant); auto name = fmt::format("CONST_{}f", f); replace_all(name, '-', 'n'); replace_all(name, '.', 'p'); addName(constVal, name.c_str()); mFloatConstants[bitsValue] = constVal; return constVal; } spv::Id makeUintConstant(unsigned int u, bool specConstant = false) { if (specConstant) { return spv::Builder::makeUintConstant(u, specConstant); } auto constVal = mUintConstants[u]; if (constVal) { return constVal; } constVal = spv::Builder::makeUintConstant(u, specConstant); switch (u) { case 0: addName(constVal, "CONST_0_or_X"); break; case 1: addName(constVal, "CONST_1_or_Y"); break; case 2: addName(constVal, "CONST_2_or_Z"); break; case 3: addName(constVal, "CONST_3_or_W"); break; case 4: addName(constVal, "CONST_4_or_T"); break; default: if (u < 100) { addName(constVal, fmt::format("CONST_{}", u).c_str()); } else { addName(constVal, fmt::format("CONST_0x{:x}", u).c_str()); } break; } mUintConstants[u] = constVal; return constVal; } spv::Id vectorizeConstant(spv::Id srcId, int elemCount) { std::vector<spv::Id> sources(elemCount); for (auto i = 0u; i < sources.size(); ++i) { sources[i] = srcId; } return makeCompositeConstant(makeVectorType(getTypeId(srcId), elemCount), sources); } // ------------------------------------------------------------ // Types // ------------------------------------------------------------ spv::Id makeIntType(int width) { if (width == 8) { addCapability(spv::Capability::CapabilityInt8); } else if (width == 16) { addCapability(spv::Capability::CapabilityInt16); } return spv::Builder::makeIntType(width); } spv::Id makeUintType(int width) { if (width == 8) { addCapability(spv::Capability::CapabilityInt8); } else if (width == 16) { addCapability(spv::Capability::CapabilityInt16); } return spv::Builder::makeUintType(width); } spv::Id samplerType() { if (!mSamplerType) { mSamplerType = makeSamplerType(); addName(mSamplerType, "sampler"); } return mSamplerType; } spv::Id boolType() { if (!mBoolType) { mBoolType = makeBoolType(); addName(mBoolType, "bool"); } return mBoolType; } spv::Id floatType() { if (!mFloatType) { mFloatType = makeFloatType(32); addName(mFloatType, "float"); } return mFloatType; } spv::Id float2Type() { return vecType(floatType(), 2); } spv::Id float3Type() { return vecType(floatType(), 3); } spv::Id float4Type() { return vecType(floatType(), 4); } spv::Id ubyteType() { if (!mUbyteType) { mUbyteType = makeUintType(8); addName(mUbyteType, "ubyte"); } return mUbyteType; } spv::Id ubyte2Type() { return vecType(ubyteType(), 2); } spv::Id ubyte3Type() { return vecType(ubyteType(), 3); } spv::Id ubyte4Type() { return vecType(ubyteType(), 4); } spv::Id ushortType() { if (!mUshortType) { mUshortType = makeUintType(16); addName(mUshortType, "ushort"); } return mUshortType; } spv::Id ushort2Type() { return vecType(ushortType(), 2); } spv::Id ushort3Type() { return vecType(ushortType(), 3); } spv::Id ushort4Type() { return vecType(ushortType(), 4); } spv::Id intType() { if (!mIntType) { mIntType = makeIntType(32); addName(mIntType, "int"); } return mIntType; } spv::Id int2Type() { return vecType(intType(), 2); } spv::Id int3Type() { return vecType(intType(), 3); } spv::Id int4Type() { return vecType(intType(), 4); } spv::Id uintType() { if (!mUintType) { mUintType = makeUintType(32); addName(mUintType, "uint"); } return mUintType; } spv::Id uint2Type() { return vecType(uintType(), 2); } spv::Id uint3Type() { return vecType(uintType(), 3); } spv::Id uint4Type() { return vecType(uintType(), 4); } spv::Id vecType(spv::Id elemType, int elemCount) { if (elemCount == 1) { return elemType; } auto vecPair = std::make_pair(elemType, elemCount); auto vecType = mVecType[vecPair]; if (!vecType) { vecType = makeVectorType(elemType, elemCount); auto baseTypeName = getTypeName(elemType); if (baseTypeName.empty()) { decaf_abort("Unexpected element type for vector type"); } addName(vecType, fmt::format("{}{}", baseTypeName, elemCount).c_str()); mVecType[vecPair] = vecType; } return vecType; } spv::Id arrayType(spv::Id elemType, int stride, int elemCount = 0) { auto arrPair = std::make_pair(elemType, elemCount); auto arrType = mArrType[arrPair]; if (!arrType) { if (elemCount == 0) { arrType = makeRuntimeArray(elemType); } else { auto sizeId = makeUintConstant(elemCount); arrType = makeArrayType(elemType, sizeId, stride); } addDecoration(arrType, spv::Decoration::DecorationArrayStride, stride); auto baseTypeName = getTypeName(elemType); if (!baseTypeName.size()) { decaf_abort("Unexpected element type for vector type"); } if (elemCount == 0) { addName(arrType, fmt::format("{}[]", baseTypeName).c_str()); } else { addName(arrType, fmt::format("{}[{}]", baseTypeName, elemCount).c_str()); } mArrType[arrPair] = arrType; } return arrType; } // ------------------------------------------------------------ // Extension Library Access // ------------------------------------------------------------ spv::Id glslStd450() { if (!mGlslStd450) { mGlslStd450 = import("GLSL.std.450"); addName(mGlslStd450, "glslStd450"); } return mGlslStd450; } // ------------------------------------------------------------ // Type Names // ------------------------------------------------------------ std::string getTypeName(spv::Id typeId) { for (auto &nameInst : names) { if (nameInst->getIdOperand(0) == typeId) { auto numCharOps = nameInst->getNumOperands() - 1; char *readName = new char[numCharOps * 4]; for (auto i = 0; i < numCharOps; ++i) { auto pieceValue = nameInst->getImmediateOperand(1 + i); readName[i * 4 + 0] = (pieceValue >> 0) & 0xFF; readName[i * 4 + 1] = (pieceValue >> 8) & 0xFF; readName[i * 4 + 2] = (pieceValue >> 16) & 0xFF; readName[i * 4 + 3] = (pieceValue >> 24) & 0xFF; } std::string readNameStr(readName); delete[] readName; return readNameStr; } } return std::string(); } // ------------------------------------------------------------ // Byte Swapping // ------------------------------------------------------------ spv::Id bswap8in16(spv::Id srcId) { auto sourceTypeId = getTypeId(srcId); auto inputTypeId = sourceTypeId; auto numComps = getNumTypeComponents(inputTypeId); if (isVectorType(inputTypeId)) { inputTypeId = getContainedTypeId(inputTypeId); } if (inputTypeId == uintType()) { auto xoxoConst = makeUintConstant(0xFF00FF00); auto oxoxConst = makeUintConstant(0x00FF00FF); auto shiftConst = makeUintConstant(8); if (numComps > 1) { xoxoConst = vectorizeConstant(xoxoConst, numComps); oxoxConst = vectorizeConstant(oxoxConst, numComps); shiftConst = vectorizeConstant(shiftConst, numComps); } auto xoxoBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, xoxoConst); auto oxoxBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oxoxConst); auto oxoxBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, xoxoBits, shiftConst); auto xoxoBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, oxoxBits, shiftConst); return createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, oxoxBitsMoved, xoxoBitsMoved); } else if (inputTypeId == ushortType()) { auto xoConst = makeUint16Constant(0xFF00); auto oxConst = makeUint16Constant(0x00FF); auto shiftConst = makeUintConstant(8); if (numComps > 1) { xoConst = vectorizeConstant(xoConst, numComps); oxConst = vectorizeConstant(oxConst, numComps); shiftConst = vectorizeConstant(shiftConst, numComps); } auto xoBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, xoConst); auto oxBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oxConst); auto oxBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, xoBits, shiftConst); auto xoBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, oxBits, shiftConst); return createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, oxBitsMoved, xoBitsMoved); } else { decaf_abort("Attempted to do 8in16 byte swap on invalid type"); } } spv::Id bswap8in32(spv::Id srcId) { auto sourceTypeId = getTypeId(srcId); auto xoooConst = makeUintConstant(0xFF000000); auto oxooConst = makeUintConstant(0x00FF0000); auto ooxoConst = makeUintConstant(0x0000FF00); auto oooxConst = makeUintConstant(0x000000FF); auto littleShiftConst = makeUintConstant(8); auto bigShiftConst = makeUintConstant(24); auto inputTypeId = sourceTypeId; if (isVectorType(inputTypeId)) { auto numComps = getNumTypeComponents(inputTypeId); inputTypeId = getContainedTypeId(inputTypeId); xoooConst = vectorizeConstant(xoooConst, numComps); oxooConst = vectorizeConstant(oxooConst, numComps); ooxoConst = vectorizeConstant(ooxoConst, numComps); oooxConst = vectorizeConstant(oooxConst, numComps); littleShiftConst = vectorizeConstant(littleShiftConst, numComps); bigShiftConst = vectorizeConstant(bigShiftConst, numComps); } decaf_check(inputTypeId == uintType()); auto xoooBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, xoooConst); auto oxooBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oxooConst); auto ooxoBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, ooxoConst); auto oooxBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oooxConst); auto xoooBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, oooxBits, bigShiftConst); auto oxooBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, ooxoBits, littleShiftConst); auto ooxoBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, oxooBits, littleShiftConst); auto oooxBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, xoooBits, bigShiftConst); auto xxooBitsMerged = createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, xoooBitsMoved, oxooBitsMoved); auto xxxoBitsMerged = createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, xxooBitsMerged, ooxoBitsMoved); return createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, xxxoBitsMerged, oooxBitsMoved); } /* // From OpenGL ES 3.0 spec 2.1.3 function from11uf(v) { const e = v >> 6; const m = v & 0x3F; if (e === 0) { if (m === 0) { return 0; } else { return Math.pow(2, -14) * (m / 64); } } else { if (e < 31) { return Math.pow(2, e - 15) * (1 + m / 64); } else { if (m === 0) { return 0; // Inf } else { return 0; // Nan } } } } */ spv::Id unpackFloat11(spv::Id srcId) { decaf_check(getTypeId(srcId) == uintType()); auto resPtr = createVariable(spv::NoPrecision, spv::StorageClassPrivate, floatType(), "unpackF11Res"); auto eVal = createBinOp(spv::OpShiftRightLogical, uintType(), srcId, makeIntConstant(6)); auto mVal = createBinOp(spv::OpBitwiseAnd, uintType(), srcId, makeUintConstant(0x3F)); auto eIsZero = createBinOp(spv::Op::OpIEqual, boolType(), eVal, makeUintConstant(0)); auto eIsZeroBlock = spv::Builder::If { eIsZero, spv::SelectionControlMaskNone, *this }; { auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0)); auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this }; { createStore(makeFloatConstant(0.0f), resPtr); } mIsZeroBlk.makeBeginElse(); { auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal); auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(64.0f)); auto resVal = createBinOp(spv::OpFMul, floatType(), mDiv64, makeFloatConstant(powf(2.0f, -14))); createStore(resVal, resPtr); } mIsZeroBlk.makeEndIf(); } eIsZeroBlock.makeBeginElse(); { auto eLessThan31 = createBinOp(spv::OpULessThan, boolType(), eVal, makeUintConstant(31)); auto eLessThan31Blk = spv::Builder::If { eLessThan31, spv::SelectionControlMaskNone, *this }; { auto eMinus15 = createBinOp(spv::OpISub, uintType(), eVal, makeUintConstant(15)); auto eMinus15Flt = createUnaryOp(spv::OpConvertUToF, floatType(), eMinus15); auto ePow = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450Pow, { makeFloatConstant(2.0f), eMinus15Flt }); auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal); auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(64.0f)); auto mDiv64Plus1 = createBinOp(spv::OpFAdd, floatType(), mDiv64, makeFloatConstant(1.0f)); auto resVal = createBinOp(spv::OpFMul, floatType(), ePow, mDiv64Plus1); createStore(resVal, resPtr); } eLessThan31Blk.makeBeginElse(); { auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0)); auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this }; { createStore(makeFloatConstant(0.0f), resPtr); // INF } mIsZeroBlk.makeBeginElse(); { createStore(makeFloatConstant(0.0f), resPtr); // NAN } mIsZeroBlk.makeEndIf(); } eLessThan31Blk.makeEndIf(); } eIsZeroBlock.makeEndIf(); return createLoad(resPtr, spv::NoPrecision); } /* // From OpenGL ES 3.0 spec 2.1.4 function from10uf(v) { const e = v >> 5; const m = v & 0x1F; if (e === 0) { if (m === 0) { return 0; } else { return Math.pow(2, -14) * (m / 32); } } else { if (e < 31) { return Math.pow(2, e - 15) * (1 + m / 32); } else { if (m === 0) { return 0; // Inf } else { return 0; // Nan } } } } */ spv::Id unpackFloat10(spv::Id srcId) { decaf_check(getTypeId(srcId) == uintType()); auto resPtr = createVariable(spv::NoPrecision, spv::StorageClassPrivate, floatType(), "unpackF11Res"); auto eVal = createBinOp(spv::OpShiftRightLogical, uintType(), srcId, makeIntConstant(5)); auto mVal = createBinOp(spv::OpBitwiseAnd, uintType(), srcId, makeUintConstant(0x1F)); auto eIsZero = createBinOp(spv::Op::OpIEqual, boolType(), eVal, makeUintConstant(0)); auto eIsZeroBlock = spv::Builder::If { eIsZero, spv::SelectionControlMaskNone, *this }; { auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0)); auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this }; { createStore(makeFloatConstant(0.0f), resPtr); } mIsZeroBlk.makeBeginElse(); { auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal); auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(32.0f)); auto resVal = createBinOp(spv::OpFMul, floatType(), mDiv64, makeFloatConstant(pow(2.0f, -14))); createStore(resVal, resPtr); } mIsZeroBlk.makeEndIf(); } eIsZeroBlock.makeBeginElse(); { auto eLessThan31 = createBinOp(spv::OpULessThan, boolType(), eVal, makeUintConstant(31)); auto eLessThan31Blk = spv::Builder::If { eLessThan31, spv::SelectionControlMaskNone, *this }; { auto eMinus15 = createBinOp(spv::OpISub, uintType(), eVal, makeUintConstant(15)); auto eMinus15Flt = createUnaryOp(spv::OpConvertUToF, floatType(), eMinus15); auto ePow = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450Pow, { makeFloatConstant(2.0f), eMinus15Flt }); auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal); auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(32.0f)); auto mDiv64Plus1 = createBinOp(spv::OpFAdd, floatType(), mDiv64, makeFloatConstant(1.0f)); auto resVal = createBinOp(spv::OpFMul, floatType(), ePow, mDiv64Plus1); createStore(resVal, resPtr); } eLessThan31Blk.makeBeginElse(); { auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0)); auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this }; { createStore(makeFloatConstant(0.0f), resPtr); // INF } mIsZeroBlk.makeBeginElse(); { createStore(makeFloatConstant(0.0f), resPtr); // NAN } mIsZeroBlk.makeEndIf(); } eLessThan31Blk.makeEndIf(); } eIsZeroBlock.makeEndIf(); return createLoad(resPtr, spv::NoPrecision); } protected: std::unordered_map<unsigned int, spv::Id> mUintConstants; std::unordered_map<uint32_t, spv::Id> mFloatConstants; spv::Id mSamplerType = spv::NoResult; spv::Id mBoolType = spv::NoResult; spv::Id mFloatType = spv::NoResult; spv::Id mUbyteType = spv::NoResult; spv::Id mUshortType = spv::NoResult; spv::Id mIntType = spv::NoResult; spv::Id mUintType = spv::NoResult; std::map<std::pair<spv::Id, int>, spv::Id> mVecType; std::map<std::pair<spv::Id, int>, spv::Id> mArrType; spv::Id mGlslStd450 = spv::NoResult; }; } // namespace gpu #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_tex.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" namespace spirv { void Transpiler::translateGenericSample(const ControlFlowInst &cf, const TextureFetchInst &inst, uint32_t sampleMode) { // inst.word0.FETCH_WHOLE_QUAD(); - Optimization which can be ignored // inst.word1.LOD_BIAS(); - Only used by certain SAMPLE instructions decaf_check(!inst.word0.BC_FRAC_MODE()); if (mType != ShaderParser::Type::Pixel) { // If we are not in the pixel shader, we need to automatically use // LOD zero, even if implicit LOD is specified by the instruction. if (!(sampleMode & SampleMode::Gather)) { sampleMode |= SampleMode::LodZero; } } auto textureId = inst.word0.RESOURCE_ID(); auto samplerId = inst.word2.SAMPLER_ID(); auto texDim = mTexDims[textureId]; auto texFormat = mTexFormats[textureId]; auto expectedCoordType = SQ_TEX_COORD_TYPE::NORMALIZED; if (sampleMode & SampleMode::Load) { expectedCoordType = SQ_TEX_COORD_TYPE::UNNORMALIZED; } switch (texDim) { case latte::SQ_TEX_DIM::DIM_1D: decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType); break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType); break; case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: break; case latte::SQ_TEX_DIM::DIM_3D: case latte::SQ_TEX_DIM::DIM_CUBEMAP: decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType); decaf_check(inst.word1.COORD_TYPE_Y() == expectedCoordType); decaf_check(inst.word1.COORD_TYPE_Z() == expectedCoordType); break; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType); decaf_check(inst.word1.COORD_TYPE_Y() == expectedCoordType); break; default: decaf_abort("Unexpected texture sample dim"); } GprMaskRef srcGpr; srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP); srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X(); srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y(); srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z(); srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W(); GprMaskRef destGpr; destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP); destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X(); destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y(); destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z(); destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W(); auto lodBias = static_cast<float>(inst.word1.LOD_BIAS()); auto offsetX = static_cast<int>(inst.word2.OFFSET_X()); auto offsetY = static_cast<int>(inst.word2.OFFSET_Y()); auto offsetZ = static_cast<int>(inst.word2.OFFSET_Z()); auto srcGprVal = mSpv->readGprMaskRef(srcGpr); uint32_t imageOperands = 0; std::vector<unsigned int> operandParams; if (sampleMode & SampleMode::LodBias) { imageOperands |= spv::ImageOperandsBiasMask; auto lodSource = mSpv->makeFloatConstant(lodBias); operandParams.push_back(lodSource); } if (sampleMode & SampleMode::Lod) { imageOperands |= spv::ImageOperandsLodMask; auto lodSource = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcGprVal, 3 }); operandParams.push_back(lodSource); } else if (sampleMode & SampleMode::LodZero) { imageOperands |= spv::ImageOperandsLodMask; auto lodSource = mSpv->makeFloatConstant(0.0f); operandParams.push_back(lodSource); } if (sampleMode & SampleMode::Gradient) { decaf_abort("We do not currently support gradient instructions"); } if (offsetX > 0 || offsetY > 0 || offsetZ > 0) { imageOperands |= spv::ImageOperandsConstOffsetMask; spv::Id offsetVal = spv::NoResult; switch (texDim) { case latte::SQ_TEX_DIM::DIM_1D: offsetVal = mSpv->makeIntConstant(offsetX); break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_CUBEMAP: offsetVal = mSpv->makeCompositeConstant(mSpv->int2Type(), { mSpv->makeIntConstant(offsetX), mSpv->makeIntConstant(offsetY) }); break; case latte::SQ_TEX_DIM::DIM_3D: offsetVal = mSpv->makeCompositeConstant(mSpv->int3Type(), { mSpv->makeIntConstant(offsetX), mSpv->makeIntConstant(offsetY), mSpv->makeIntConstant(offsetZ) }); break; default: decaf_abort("Unexpected texture sample dim"); } operandParams.push_back(offsetVal); } auto texVarType = mSpv->textureVarType(textureId, texDim, texFormat); auto texVar = mSpv->textureVar(textureId, texDim, texFormat); auto texVal = mSpv->createLoad(texVar, spv::NoPrecision); // Lets build our actual operation spv::Op sampleOp = spv::OpNop; std::vector<unsigned int> sampleParams; if (!(sampleMode & SampleMode::Load)) { auto sampVar = mSpv->samplerVar(samplerId); auto sampVal = mSpv->createLoad(sampVar, spv::NoPrecision); auto sampledType = mSpv->makeSampledImageType(texVarType); auto sampledVal = mSpv->createOp(spv::OpSampledImage, sampledType, { texVal, sampVal }); sampleParams.push_back(sampledVal); spv::Id srcCoords = srcGprVal; switch (texDim) { case latte::SQ_TEX_DIM::DIM_1D: srcCoords = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcCoords, 0 }); break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->float2Type(), { srcCoords, srcCoords, 0, 1 }); break; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_3D: case latte::SQ_TEX_DIM::DIM_CUBEMAP: srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->float3Type(), { srcCoords, srcCoords, 0, 1, 2 }); break; default: decaf_abort("Unexpected texture sample dim"); } sampleParams.push_back(srcCoords); // Pick the operation, and potentially add the comparison. if (sampleMode & SampleMode::Gather) { // It is not legal to combine this with any other kind of operation decaf_check(sampleMode == SampleMode::Gather); sampleOp = spv::OpImageGather; auto compToFetch = mSpv->makeUintConstant(0); sampleParams.push_back(compToFetch); } else if (sampleMode & SampleMode::Compare) { auto drefVal = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcGprVal, 3 }); if (imageOperands & spv::ImageOperandsLodMask) { sampleOp = spv::OpImageSampleDrefExplicitLod; } else { sampleOp = spv::OpImageSampleDrefImplicitLod; } sampleParams.push_back(drefVal); } else { if (imageOperands & spv::ImageOperandsLodMask) { sampleOp = spv::OpImageSampleExplicitLod; } else { sampleOp = spv::OpImageSampleImplicitLod; } } } else { // It is not legal to combine this with any other kind of operation decaf_check(sampleMode == SampleMode::Load); spv::Id srcCoords = srcGprVal; srcCoords = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uint4Type(), srcCoords); switch (texDim) { case latte::SQ_TEX_DIM::DIM_1D: srcCoords = mSpv->createOp(spv::OpCompositeExtract, mSpv->uintType(), { srcCoords, 0 }); break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->uint2Type(), { srcCoords, srcCoords, 0, 1 }); break; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_3D: case latte::SQ_TEX_DIM::DIM_CUBEMAP: srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->uint3Type(), { srcCoords, srcCoords, 0, 1, 2 }); break; default: decaf_abort("Unexpected texture sample dim"); } sampleParams.push_back(texVal); sampleParams.push_back(srcCoords); sampleOp = spv::OpImageFetch; } decaf_check(sampleOp != spv::OpNop); // Merge all the image operands in now. if (imageOperands != 0) { sampleParams.push_back(imageOperands); decaf_check(!operandParams.empty()); for (auto& param : operandParams) { sampleParams.push_back(param); } } else { decaf_check(operandParams.empty()); } auto zeroFVal = mSpv->makeFloatConstant(0.0f); auto oneFVal = mSpv->makeFloatConstant(1.0f); spv::Id output = spv::NoResult; if (sampleMode & SampleMode::Compare) { // We do not support doing depth comparison on a UINT depth buffer decaf_check(texFormat == TextureInputType::FLOAT); auto compareVal = mSpv->createOp(sampleOp, mSpv->floatType(), sampleParams); // TODO: This is really cheating, we should just check the write mask // and do a single component write instead... output = mSpv->createCompositeConstruct(mSpv->float4Type(), { compareVal, zeroFVal, zeroFVal, oneFVal }); } else { if (texFormat == TextureInputType::FLOAT) { output = mSpv->createOp(sampleOp, mSpv->float4Type(), sampleParams); } else if (texFormat == TextureInputType::INT) { output = mSpv->createOp(sampleOp, mSpv->uint4Type(), sampleParams); } else { decaf_abort("Unexpected texture format type"); } } mSpv->writeGprMaskRef(destGpr, output); } void Transpiler::translateTex_LD(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Load); } void Transpiler::translateTex_FETCH4(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Gather); } void Transpiler::translateTex_SAMPLE(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::None); } void Transpiler::translateTex_SAMPLE_L(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Lod); } void Transpiler::translateTex_SAMPLE_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::LodBias); } void Transpiler::translateTex_SAMPLE_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::LodZero); } void Transpiler::translateTex_SAMPLE_G(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Gradient); } void Transpiler::translateTex_SAMPLE_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Gradient | SampleMode::Lod); } void Transpiler::translateTex_SAMPLE_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Gradient | SampleMode::LodBias); } void Transpiler::translateTex_SAMPLE_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Gradient | SampleMode::LodZero); } void Transpiler::translateTex_SAMPLE_C(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare); } void Transpiler::translateTex_SAMPLE_C_L(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Lod); } void Transpiler::translateTex_SAMPLE_C_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::LodBias); } void Transpiler::translateTex_SAMPLE_C_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::LodZero); } void Transpiler::translateTex_SAMPLE_C_G(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient); } void Transpiler::translateTex_SAMPLE_C_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient | SampleMode::Lod); } void Transpiler::translateTex_SAMPLE_C_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient | SampleMode::LodBias); } void Transpiler::translateTex_SAMPLE_C_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) { translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient | SampleMode::LodZero); } void Transpiler::translateTex_GET_TEXTURE_INFO(const ControlFlowInst &cf, const TextureFetchInst &inst) { auto textureId = inst.word0.RESOURCE_ID(); // inst.word2.SAMPLER_ID(); auto texDim = mTexDims[textureId]; auto texFormat = mTexFormats[textureId]; GprMaskRef srcGpr; srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP); srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X(); srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y(); srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z(); srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W(); GprMaskRef destGpr; destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP); destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X(); destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y(); destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z(); destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W(); // We have to register the image-query capability in order to query texture data. mSpv->addCapability(spv::CapabilityImageQuery); auto texVar = mSpv->textureVar(textureId, texDim, texFormat); auto image = mSpv->createLoad(texVar, spv::NoPrecision); auto srcGprVal = mSpv->readGprMaskRef(srcGpr); auto srcLodValFloat = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcGprVal, 0 }); auto srcLodVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), srcLodValFloat); auto oneIConst = mSpv->makeIntConstant(1); auto sizeIConst = mSpv->makeIntConstant(6); spv::Id sizeInfo; switch (texDim) { case latte::SQ_TEX_DIM::DIM_1D: { auto sizeInfo1d = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->intType(), image, srcLodVal); sizeInfo = mSpv->createOp(spv::OpCompositeConstruct, mSpv->int3Type(), { sizeInfo1d, oneIConst, oneIConst }); break; } case latte::SQ_TEX_DIM::DIM_1D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: { auto sizeInfo2d = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->int2Type(), image, srcLodVal); sizeInfo = mSpv->createOp(spv::OpCompositeConstruct, mSpv->int3Type(), { sizeInfo2d, oneIConst }); break; } case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_3D: { sizeInfo = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->int3Type(), image, srcLodVal); break; } case latte::SQ_TEX_DIM::DIM_CUBEMAP: { auto sizeInfoCube = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->int3Type(), image, srcLodVal); auto cubemapArrSideSize = mSpv->createOp(spv::OpCompositeExtract, mSpv->intType(), { sizeInfoCube, 3 }); auto cubemapArrSize = mSpv->createBinOp(spv::OpSDiv, mSpv->intType(), cubemapArrSideSize, sizeIConst); sizeInfo = mSpv->createOp(spv::OpCompositeInsert, mSpv->int3Type(), { cubemapArrSize, sizeInfoCube, 3 }); break; } default: decaf_abort("Unexpected texture sample dim"); } auto levelInfo = mSpv->createUnaryOp(spv::OpImageQueryLevels, mSpv->intType(), image); auto output = mSpv->createOp(spv::OpCompositeConstruct, mSpv->int4Type(), { sizeInfo, levelInfo }); mSpv->writeGprMaskRef(destGpr, output); } void Transpiler::translateTex_SET_CUBEMAP_INDEX(const ControlFlowInst &cf, const TextureFetchInst &inst) { // TODO: It is possible that we are supposed to somehow force // a specific face to be used in spite of the coordinates. //decaf_abort("Unsupported SET_CUBEMAP_INDEX") } void Transpiler::translateTex_GET_GRADIENTS_H(const ControlFlowInst &cf, const TextureFetchInst &inst) { auto textureId = inst.word0.RESOURCE_ID(); auto samplerId = inst.word2.SAMPLER_ID(); decaf_check(textureId == 0); decaf_check(samplerId == 0); // TODO: Make a latte decoder for reading these registers... GprMaskRef srcGpr; srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP); srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X(); srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y(); srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z(); srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W(); GprMaskRef destGpr; destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP); destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X(); destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y(); destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z(); destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W(); auto srcGprVal = mSpv->readGprMaskRef(srcGpr); mSpv->addCapability(spv::CapabilityDerivativeControl); auto output = mSpv->createUnaryOp(spv::OpDPdx, mSpv->float4Type(), srcGprVal); mSpv->writeGprMaskRef(destGpr, output); } void Transpiler::translateTex_GET_GRADIENTS_V(const ControlFlowInst &cf, const TextureFetchInst &inst) { auto textureId = inst.word0.RESOURCE_ID(); auto samplerId = inst.word2.SAMPLER_ID(); decaf_check(textureId == 0); decaf_check(samplerId == 0); // TODO: Make a latte decoder for reading these registers... GprMaskRef srcGpr; srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP); srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X(); srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y(); srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z(); srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W(); GprMaskRef destGpr; destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP); destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X(); destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y(); destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z(); destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W(); auto srcGprVal = mSpv->readGprMaskRef(srcGpr); mSpv->addCapability(spv::CapabilityDerivativeControl); auto output = mSpv->createUnaryOp(spv::OpDPdy, mSpv->float4Type(), srcGprVal); mSpv->writeGprMaskRef(destGpr, output); } void Transpiler::translateTex_VTX_FETCH(const ControlFlowInst &cf, const TextureFetchInst &inst) { // The TextureFetchInst perfectly matches the VertexFetchInst translateVtx_FETCH(cf, *reinterpret_cast<const VertexFetchInst*>(&inst)); } void Transpiler::translateTex_VTX_SEMANTIC(const ControlFlowInst &cf, const TextureFetchInst &inst) { // The TextureFetchInst perfectly matches the VertexFetchInst translateVtx_SEMANTIC(cf, *reinterpret_cast<const VertexFetchInst*>(&inst)); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_translate.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "latte/latte_constants.h" #include "latte/latte_registers_cb.h" #include "latte/latte_registers_db.h" #include "latte/latte_registers_sq.h" #include "latte/latte_registers_spi.h" #include "latte/latte_registers_pa.h" #include "latte/latte_registers_vgt.h" #include <common/datahash.h> #include <gsl/gsl-lite.hpp> namespace spirv { enum class ShaderType : uint32_t { Unknown, Vertex, Geometry, Pixel }; enum class TextureInputType : uint32_t { NONE, FLOAT, INT }; enum class PixelOutputType : uint32_t { FLOAT, SINT, UINT }; struct AttribBuffer { enum class IndexMode : uint32_t { PerVertex, PerInstance, }; enum class DivisorMode : uint32_t { CONST_1, REGISTER_0, REGISTER_1 }; bool isUsed = false; IndexMode indexMode = IndexMode::PerVertex; DivisorMode divisorMode = DivisorMode::CONST_1; }; struct AttribElem { uint32_t bufferIndex = 0; uint32_t offset = 0; uint32_t elemWidth = 0; uint32_t elemCount = 0; }; #pragma pack(push, 1) struct ShaderDesc { ShaderType type = ShaderType::Unknown; gsl::span<const uint8_t> binary; bool aluInstPreferVector = true; std::array<latte::SQ_TEX_DIM, latte::MaxTextures> texDims; std::array<TextureInputType, latte::MaxTextures> texFormat; DataHash hash() const { return DataHash {}.write(*this); } }; struct VertexShaderDesc : public ShaderDesc { gsl::span<const uint8_t> fsBinary; std::array<uint32_t, latte::MaxStreamOutBuffers> streamOutStride; struct { latte::SQ_PGM_RESOURCES_VS sq_pgm_resources_vs; std::array<latte::SQ_VTX_SEMANTIC_N, 32> sq_vtx_semantics; latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl; } regs; DataHash hash() const { return DataHash {}.write(*this); } }; struct GeometryShaderDesc : public ShaderDesc { gsl::span<const uint8_t> dcBinary; std::array<uint32_t, latte::MaxStreamOutBuffers> streamOutStride; struct { latte::SQ_GS_VERT_ITEMSIZE sq_gs_vert_itemsize; latte::VGT_GS_OUT_PRIMITIVE_TYPE vgt_gs_out_prim_type; latte::VGT_GS_MODE vgt_gs_mode; uint32_t sq_gsvs_ring_itemsize; latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl; } regs; DataHash hash() const { return DataHash {}.write(*this); } }; struct PixelShaderDesc : public ShaderDesc { std::array<PixelOutputType, latte::MaxRenderTargets> pixelOutType; struct { latte::SQ_PGM_RESOURCES_PS sq_pgm_resources_ps; latte::SQ_PGM_EXPORTS_PS sq_pgm_exports_ps; latte::SPI_VS_OUT_CONFIG spi_vs_out_config; std::array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_ids; latte::SPI_PS_IN_CONTROL_0 spi_ps_in_control_0; latte::SPI_PS_IN_CONTROL_1 spi_ps_in_control_1; std::array<latte::SPI_PS_INPUT_CNTL_N, 32> spi_ps_input_cntls; latte::CB_SHADER_CONTROL cb_shader_control; latte::CB_SHADER_MASK cb_shader_mask; latte::DB_SHADER_CONTROL db_shader_control; } regs; DataHash hash() const { return DataHash {}.write(*this); } }; #pragma pack(pop) struct RectStubShaderDesc { uint32_t numVsExports; DataHash hash() const { return DataHash {}.write(*this); } }; struct ShaderMeta { std::array<bool, latte::MaxSamplers> samplerUsed; std::array<bool, latte::MaxTextures> textureUsed; std::array<bool, latte::MaxUniformBlocks> cbufferUsed; uint32_t cfileUsed; }; struct VertexShaderMeta : public ShaderMeta { uint32_t numExports; std::array<bool, latte::MaxStreamOutBuffers> streamOutUsed; std::array<AttribBuffer, latte::MaxAttribBuffers> attribBuffers; std::vector<AttribElem> attribElems; }; struct GeometryShaderMeta : public ShaderMeta { std::array<bool, latte::MaxStreamOutBuffers> streamOutUsed; }; struct PixelShaderMeta : public ShaderMeta { std::array<bool, latte::MaxRenderTargets> pixelOutUsed; }; struct Shader { std::vector<unsigned int> binary; }; struct VertexShader : public Shader { VertexShaderMeta meta; }; struct GeometryShader : public Shader { GeometryShaderMeta meta; }; struct PixelShader : public Shader { PixelShaderMeta meta; }; struct RectStubShader { std::vector<unsigned int> binary; }; bool translate(const ShaderDesc& shaderDesc, Shader *shader); RectStubShaderDesc generateRectSubShaderDesc(VertexShader *vertexShader); bool generateRectStub(const RectStubShaderDesc& shaderDesc, RectStubShader *shader); std::string shaderToString(const Shader *shader); } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_transpiler.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" #include "latte/latte_disassembler.h" #include <SPIRV/disassemble.h> #include <regex> namespace spirv { using namespace latte; void Transpiler::writeCfAluUnitLine(ShaderParser::Type shaderType, int cfId, int groupId, int unitId) { int shaderId; switch (shaderType) { case ShaderParser::Type::Fetch: shaderId = 1; break; case ShaderParser::Type::Vertex: shaderId = 2; break; case ShaderParser::Type::DataCache: shaderId = 3; break; case ShaderParser::Type::Geometry: shaderId = 4; break; case ShaderParser::Type::Pixel: shaderId = 5; break; default: decaf_abort("unexpected shader type.") } if (cfId < 0) { cfId = 999; } if (groupId < 0) { groupId = 999; } if (unitId < 0) { unitId = 9; } mSpv->setLine(shaderId * 10000000 + cfId * 10000 + groupId * 10 + unitId); } void Transpiler::translateTexInst(const ControlFlowInst &cf, const TextureFetchInst &inst) { writeCfAluUnitLine(mType, mCfPC, mTexVtxPC, -1); ShaderParser::translateTexInst(cf, inst); } void Transpiler::translateVtxInst(const ControlFlowInst &cf, const VertexFetchInst &inst) { writeCfAluUnitLine(mType, mCfPC, mTexVtxPC, -1); ShaderParser::translateVtxInst(cf, inst); } void Transpiler::translateAluInst(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { writeCfAluUnitLine(mType, mCfPC, mGroupPC, static_cast<uint32_t>(unit)); ShaderParser::translateAluInst(cf, group, unit, inst); } void Transpiler::translateAluGroup(const ControlFlowInst &cf, const AluInstructionGroup &group) { writeCfAluUnitLine(mType, mCfPC, mGroupPC, -1); ShaderParser::translateAluGroup(cf, group); mSpv->flushAluGroupWrites(); mSpv->swapPrevRes(); } void Transpiler::translateCfNormalInst(const ControlFlowInst& cf) { // cf.word1.BARRIER(); - No need to handle coherency in SPIRV // cf.word1.WHOLE_QUAD_MODE(); - No need to handle optimization // cf.word1.VALID_PIXEL_MODE(); - No need to handle optimization writeCfAluUnitLine(mType, mCfPC, -1, -1); ShaderParser::translateCfNormalInst(cf); } void Transpiler::translateCfExportInst(const ControlFlowInst& cf) { // cf.word1.BARRIER(); - No need to handle coherency in SPIRV // cf.word1.WHOLE_QUAD_MODE(); - No need to handle optimization // cf.word1.VALID_PIXEL_MODE(); - No need to handle optimization writeCfAluUnitLine(mType, mCfPC, -1, -1); ShaderParser::translateCfExportInst(cf); } void Transpiler::translateCfAluInst(const ControlFlowInst& cf) { // cf.word1.BARRIER(); - No need to handle coherency in SPIRV // cf.word1.WHOLE_QUAD_MODE(); - No need to handle optimization writeCfAluUnitLine(mType, mCfPC, -1, -1); ShaderParser::translateCfAluInst(cf); mSpv->resetAr(); } void Transpiler::translate() { ShaderParser::reset(); writeCfAluUnitLine(mType, -1, -1, -1); ShaderParser::translate(); } int findVsOutputLocation(const std::array<uint8_t, 40>& semantics, uint32_t semanticId) { for (auto i = 0u; i < semantics.size(); ++i) { if (semantics[i] == semanticId) { return static_cast<int>(i); } } return -1; } void Transpiler::writeGenericProlog(ShaderSpvBuilder &spvGen) { spvGen.createStore(spvGen.makeIntConstant(0), spvGen.stackIndexVar()); spvGen.createStore(spvGen.stateActive(), spvGen.stateVar()); } void Transpiler::writeVertexProlog(ShaderSpvBuilder &spvGen, const VertexShaderDesc& desc) { // This is implied as being enabled if we see a write to POS_1 //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA(); // I don't quite understand the semantics of this. //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA(); // We do not currently support these cases: decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()); decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()); writeGenericProlog(spvGen); auto oneConst = spvGen.makeIntConstant(1); auto twoConst = spvGen.makeIntConstant(2); if (desc.regs.pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE()) { auto pointSizePtr = spvGen.createAccessChain(spv::StorageClass::StorageClassPushConstant, spvGen.vsPushConstVar(), { twoConst }); auto pointSizeVal = spvGen.createLoad(pointSizePtr, spv::NoPrecision); spvGen.createStore(pointSizeVal, spvGen.pointSizeVar()); } // Note that because we use VertexIndex, we have to subtrack the base value // away from gl_VertexIndex to receive the same value we received in GL. auto zSpaceMulPtr = spvGen.createAccessChain(spv::StorageClass::StorageClassPushConstant, spvGen.vsPushConstVar(), { oneConst }); auto zSpaceMulVal = spvGen.createLoad(zSpaceMulPtr, spv::NoPrecision); auto vertexBaseFlt = spvGen.createOp(spv::OpCompositeExtract, spvGen.floatType(), { zSpaceMulVal, 2 }); auto vertexBaseVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.intType(), vertexBaseFlt); auto instanceBaseFlt = spvGen.createOp(spv::OpCompositeExtract, spvGen.floatType(), { zSpaceMulVal, 3 }); auto instanceBaseVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.intType(), instanceBaseFlt); auto vertexIdVal = spvGen.createLoad(spvGen.vertexIdVar(), spv::NoPrecision); vertexIdVal = spvGen.createBinOp(spv::OpISub, spvGen.intType(), vertexIdVal, vertexBaseVal); vertexIdVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), vertexIdVal); auto instanceIdVal = spvGen.createLoad(spvGen.instanceIdVar(), spv::NoPrecision); instanceIdVal = spvGen.createBinOp(spv::OpISub, spvGen.intType(), instanceIdVal, instanceBaseVal); instanceIdVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), instanceIdVal); auto zeroFConst = spvGen.makeFloatConstant(0.0f); auto initialR0 = spvGen.createOp(spv::OpCompositeConstruct, spvGen.float4Type(), { vertexIdVal, instanceIdVal, zeroFConst, zeroFConst }); GprMaskRef gpr0; gpr0.gpr.number = 0; gpr0.gpr.indexMode = GprIndexMode::None; gpr0.mask[0] = latte::SQ_SEL::SEL_X; gpr0.mask[1] = latte::SQ_SEL::SEL_Y; gpr0.mask[2] = latte::SQ_SEL::SEL_Z; gpr0.mask[3] = latte::SQ_SEL::SEL_W; spvGen.writeGprMaskRef(gpr0, initialR0); } void Transpiler::writeGeometryProlog(ShaderSpvBuilder &spvGen, const GeometryShaderDesc& desc) { // This is implied as being enabled if we see a write to POS_1 //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA(); // I don't quite understand the semantics of this. //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA(); // We do not currently support these cases: decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()); decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()); spvGen.addCapability(spv::CapabilityGeometry); auto mainFn = spvGen.getFunction("main"); spvGen.addExecutionMode(mainFn, spv::ExecutionModeInvocations, 1); spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputVertices, 64); spvGen.addExecutionMode(mainFn, spv::ExecutionModeTriangles); switch (desc.regs.vgt_gs_out_prim_type) { case latte::VGT_GS_OUT_PRIMITIVE_TYPE::POINTLIST: spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputPoints); break; case latte::VGT_GS_OUT_PRIMITIVE_TYPE::LINESTRIP: spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputLineStrip); break; case latte::VGT_GS_OUT_PRIMITIVE_TYPE::TRISTRIP: spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputTriangleStrip); break; default: decaf_abort("Unexpected geometry shader primitive type"); } writeGenericProlog(spvGen); // Initialize the ring index to 0 spvGen.createStore(spvGen.makeUintConstant(0), spvGen.ringOffsetVar()); auto zeroFConst = spvGen.makeFloatConstant(0.0f); auto zeroConstAsF = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), spvGen.makeUintConstant(0)); auto oneConstAsF = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), spvGen.makeUintConstant(1)); auto twoConstAsF = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), spvGen.makeUintConstant(2)); auto initialR0 = spvGen.createOp(spv::OpCompositeConstruct, spvGen.float4Type(), { zeroConstAsF, oneConstAsF, zeroFConst, twoConstAsF }); GprMaskRef gpr0; gpr0.gpr.number = 0; gpr0.gpr.indexMode = GprIndexMode::None; gpr0.mask[0] = latte::SQ_SEL::SEL_X; gpr0.mask[1] = latte::SQ_SEL::SEL_Y; gpr0.mask[2] = latte::SQ_SEL::SEL_Z; gpr0.mask[3] = latte::SQ_SEL::SEL_W; spvGen.writeGprMaskRef(gpr0, initialR0); } void Transpiler::writePixelProlog(ShaderSpvBuilder &spvGen, const PixelShaderDesc& desc) { writeGenericProlog(spvGen); auto mainFn = spvGen.getFunction("main"); spvGen.addExecutionMode(mainFn, spv::ExecutionModeOriginUpperLeft); // We don't currently support baryocentric sampling decaf_check(!desc.regs.spi_ps_in_control_0.BARYC_AT_SAMPLE_ENA()); // These simply control feature enablement for the interpolation // of the PS inputs down below: // psDesc.regs.spi_ps_in_control_0.BARYC_SAMPLE_CNTL() // psDesc.regs.spi_ps_in_control_0.LINEAR_GRADIENT_ENA() // psDesc.regs.spi_ps_in_control_0.PERSP_GRADIENT_ENA() // We do not currently support fixed point positions decaf_check(!desc.regs.spi_ps_in_control_1.FIXED_PT_POSITION_ENA()); //psDesc.regs.spi_ps_in_control_1.FIXED_PT_POSITION_ADDR(); // I don't even know how to handle this being off!? //psDesc.regs.spi_ps_in_control_1.FOG_ADDR(); // We do not currently support pixel indexing decaf_check(!desc.regs.spi_ps_in_control_1.GEN_INDEX_PIX()); //psDesc.regs.spi_ps_in_control_1.GEN_INDEX_PIX_ADDR(); // I don't believe this is used on our GPU... //psDesc.regs.spi_ps_in_control_1.POSITION_ULC(); std::map<uint32_t, latte::SPI_PS_INPUT_CNTL_N*> inputCntlMap; auto numInputs = desc.regs.spi_ps_in_control_0.NUM_INTERP(); for (auto inputIdx = 0u; inputIdx < numInputs; ++inputIdx) { auto &spi_ps_input_cntl = desc.regs.spi_ps_input_cntls[inputIdx]; auto gprRef = spvGen.getGprRef({ inputIdx, latte::GprIndexMode::None }); auto inputSemantic = spi_ps_input_cntl.SEMANTIC(); // Locate the vertex shader output that matches int semLocation = -1; for (auto semIdx = 0; semIdx < 10; ++semIdx) { if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_0()) { semLocation = semIdx * 4 + 0; break; } else if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_1()) { semLocation = semIdx * 4 + 1; break; } else if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_2()) { semLocation = semIdx * 4 + 2; break; } else if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_3()) { semLocation = semIdx * 4 + 3; break; } } if (desc.regs.spi_ps_in_control_0.POSITION_ENA() && desc.regs.spi_ps_in_control_0.POSITION_ADDR() == inputIdx) { // TODO: Handle desc.regs.spi_ps_in_control_0.POSITION_CENTROID(); // TODO: Handle desc.regs.spi_ps_in_control_0.POSITION_SAMPLE(); spvGen.createStore(spvGen.createLoad(spvGen.fragCoordVar(), spv::NoPrecision), gprRef); continue; } if (semLocation < 0) { // There was no matching semantic output from the VS... auto zeroConst = spvGen.makeFloatConstant(0.0f); auto oneConst = spvGen.makeFloatConstant(1.0f); spv::Id defaultVal; switch (spi_ps_input_cntl.DEFAULT_VAL()) { case 0: defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(), { zeroConst, zeroConst, zeroConst, zeroConst }); break; case 1: defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(), { zeroConst, zeroConst, zeroConst, oneConst }); break; case 2: defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(), { oneConst, oneConst, oneConst, zeroConst }); break; case 3: defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(), { oneConst, oneConst, oneConst, oneConst }); break; default: decaf_abort("Unexpected PS input semantic default"); } spvGen.createStore(defaultVal, gprRef); continue; } auto inputVar = spvGen.inputParamVar(static_cast<uint32_t>(semLocation)); decaf_check(!spi_ps_input_cntl.CYL_WRAP()); decaf_check(!spi_ps_input_cntl.PT_SPRITE_TEX()); if (spi_ps_input_cntl.FLAT_SHADE()) { decaf_check(!spi_ps_input_cntl.SEL_LINEAR()); decaf_check(!spi_ps_input_cntl.SEL_SAMPLE()); // Using centroid qualifier with flat shading doesn't make sense decaf_check(!spi_ps_input_cntl.SEL_CENTROID()); spvGen.addDecoration(inputVar, spv::DecorationFlat); } else if (spi_ps_input_cntl.SEL_LINEAR()) { decaf_check(!spi_ps_input_cntl.FLAT_SHADE()); decaf_check(!spi_ps_input_cntl.SEL_SAMPLE()); spvGen.addDecoration(inputVar, spv::DecorationNoPerspective); if (spi_ps_input_cntl.SEL_CENTROID()) { spvGen.addDecoration(inputVar, spv::DecorationCentroid); } } else if (spi_ps_input_cntl.SEL_SAMPLE()) { decaf_check(!spi_ps_input_cntl.FLAT_SHADE()); decaf_check(!spi_ps_input_cntl.SEL_LINEAR()); // Using centroid qualifier with sample shading doesn't make sense decaf_check(!spi_ps_input_cntl.SEL_CENTROID()); spvGen.addDecoration(inputVar, spv::DecorationSample); } else { // The default will be fine... if (spi_ps_input_cntl.SEL_CENTROID()) { spvGen.addDecoration(inputVar, spv::DecorationCentroid); } } auto inputVal = spvGen.createLoad(inputVar, spv::NoPrecision); spvGen.createStore(inputVal, gprRef); } if (desc.regs.spi_ps_in_control_1.FRONT_FACE_ENA()) { auto ffGprIdx = desc.regs.spi_ps_in_control_1.FRONT_FACE_ADDR(); auto ffChanIdx = desc.regs.spi_ps_in_control_1.FRONT_FACE_CHAN(); latte::GprChanRef ffRef; ffRef.gpr = latte::makeGprRef(ffGprIdx); ffRef.chan = static_cast<SQ_CHAN>(ffChanIdx); auto frontFacingVar = spvGen.frontFacingVar(); auto frontFacingVal = spvGen.createLoad(frontFacingVar, spv::NoPrecision); spv::Id output = spv::NoResult; auto ffBitMode = desc.regs.spi_ps_in_control_1.FRONT_FACE_ALL_BITS(); if (ffBitMode == 0) { // Sign bit represents front facing (-1.0f Back, +1.0f Front) auto backConst = spvGen.makeFloatConstant(-1.0f); auto frontConst = spvGen.makeFloatConstant(+1.0f); output = spvGen.createTriOp(spv::OpSelect, spvGen.floatType(), frontFacingVal, frontConst, backConst); } else if (ffBitMode == 1) { // Full value represents front facing (0 Back, 1 Front) auto backConst = spvGen.makeUintConstant(0); auto frontConst = spvGen.makeUintConstant(1); output = spvGen.createTriOp(spv::OpSelect, spvGen.uintType(), frontFacingVal, frontConst, backConst); } else { decaf_abort("Unexpected front face bit mode."); } spvGen.writeGprChanRef(ffRef, output); } } bool Transpiler::translate(const ShaderDesc& shaderDesc, Shader *shader) { auto state = Transpiler {}; if (shaderDesc.type == ShaderType::Vertex) { state.mType = Transpiler::Type::Vertex; } else if (shaderDesc.type == ShaderType::Geometry) { state.mType = Transpiler::Type::Geometry; } else if (shaderDesc.type == ShaderType::Pixel) { state.mType = Transpiler::Type::Pixel; } else { decaf_abort("Unexpected shader type"); } spv::ExecutionModel spvExecModel; if (shaderDesc.type == ShaderType::Vertex) { spvExecModel = spv::ExecutionModel::ExecutionModelVertex; } else if (shaderDesc.type == ShaderType::Geometry) { spvExecModel = spv::ExecutionModel::ExecutionModelGeometry; } else if (shaderDesc.type == ShaderType::Pixel) { spvExecModel = spv::ExecutionModel::ExecutionModelFragment; } else { decaf_abort("Unexpected shader type"); } auto spvGen = ShaderSpvBuilder(spvExecModel); spvGen.setSource(spv::SourceLanguageOpenCL_CPP, 0); spvGen.setSourceFile("none"); state.mSpv = &spvGen; state.mBinary = shaderDesc.binary; state.mAluInstPreferVector = shaderDesc.aluInstPreferVector; state.mTexDims = shaderDesc.texDims; state.mTexFormats = shaderDesc.texFormat; if (shaderDesc.type == ShaderType::Vertex) { auto &vsDesc = *reinterpret_cast<const VertexShaderDesc*>(&shaderDesc); spvGen.setSourceText(latte::disassemble(shaderDesc.binary) + "\n\n" + latte::disassemble(vsDesc.fsBinary, true)); spvGen.setBindingBase(32 * 0); state.mSqVtxSemantics = vsDesc.regs.sq_vtx_semantics; state.mPaClVsOutCntl = vsDesc.regs.pa_cl_vs_out_cntl; state.mStreamOutStride = vsDesc.streamOutStride; Transpiler::writeVertexProlog(spvGen, vsDesc); } else if (shaderDesc.type == ShaderType::Geometry) { auto &gsDesc = *reinterpret_cast<const GeometryShaderDesc*>(&shaderDesc); spvGen.setSourceText(latte::disassemble(shaderDesc.binary) + "\n\n" + latte::disassemble(gsDesc.dcBinary)); spvGen.setBindingBase(32 * 1); state.mPaClVsOutCntl = gsDesc.regs.pa_cl_vs_out_cntl; state.mStreamOutStride = gsDesc.streamOutStride; Transpiler::writeGeometryProlog(spvGen, gsDesc); } else if (shaderDesc.type == ShaderType::Pixel) { auto &psDesc = *reinterpret_cast<const PixelShaderDesc*>(&shaderDesc); spvGen.setSourceText(latte::disassemble(shaderDesc.binary)); spvGen.setBindingBase(32 * 2); state.mPixelOutType = psDesc.pixelOutType; state.mSqPgmExportsPs = psDesc.regs.sq_pgm_exports_ps; state.mCbShaderControl = psDesc.regs.cb_shader_control; state.mCbShaderMask = psDesc.regs.cb_shader_mask; state.mDbShaderControl = psDesc.regs.db_shader_control; Transpiler::writePixelProlog(spvGen, psDesc); } state.translate(); spvGen.makeReturn(true); if (shaderDesc.type != ShaderType::Vertex) { if (spvGen.hasFunction("fs_main")) { decaf_abort("Non-vertex-shader called into a FS function, wat?"); } } if (shaderDesc.type != ShaderType::Geometry) { if (spvGen.hasFunction("dc_main")) { decaf_abort("Non-vertex-shader called into a DC function, wat?"); } } ShaderMeta genericMeta; for (auto i = 0u; i < latte::MaxSamplers; ++i) { genericMeta.samplerUsed[i] = spvGen.isSamplerUsed(i); } for (auto i = 0u; i < latte::MaxTextures; ++i) { genericMeta.textureUsed[i] = spvGen.isTextureUsed(i); } genericMeta.cfileUsed = spvGen.isConstantFileUsed(); for (auto i = 0u; i < latte::MaxUniformBlocks; ++i) { auto cbufferUsed = spvGen.isUniformBufferUsed(i); genericMeta.cbufferUsed[i] = cbufferUsed; if (genericMeta.cfileUsed && cbufferUsed) { decaf_abort("Shader used both constant buffers and the constants file"); } } if (shaderDesc.type == ShaderType::Vertex) { auto& vsDesc = *reinterpret_cast<const VertexShaderDesc*>(&shaderDesc); auto vsShader = reinterpret_cast<VertexShader*>(shader); if (spvGen.hasFunction("fs_main")) { auto fsFunc = spvGen.getFunction("fs_main"); spvGen.setBuildPoint(fsFunc->getEntryBlock()); state.mType = ShaderParser::Type::Fetch; state.mBinary = vsDesc.fsBinary; state.translate(); // Write the return statement at the end of the function spvGen.makeReturn(true); } static_cast<ShaderMeta&>(vsShader->meta) = genericMeta; vsShader->meta.numExports = spvGen.getNumParamExports(); vsShader->meta.attribBuffers = state.mAttribBuffers; vsShader->meta.attribElems = state.mAttribElems; for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { vsShader->meta.streamOutUsed[i] = spvGen.isStreamOutUsed(i); } } else if (shaderDesc.type == ShaderType::Geometry) { auto& gsDesc = *reinterpret_cast<const GeometryShaderDesc*>(&shaderDesc); auto gsShader = reinterpret_cast<GeometryShader*>(shader); if (spvGen.hasFunction("dc_main")) { auto dcFunc = spvGen.getFunction("dc_main"); spvGen.setBuildPoint(dcFunc->getEntryBlock()); // Need to save our GPRs first auto gprType = spvGen.arrayType(spvGen.float4Type(), 16, 128); auto gprSaveVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, gprType, "RVarSave"); spvGen.createNoResultOp(spv::OpCopyMemory, { gprSaveVar, spvGen.gprVar() }); // Translate the shader state.mType = ShaderParser::Type::DataCache; state.mBinary = gsDesc.dcBinary; state.translate(); // We need to increment the ring offset at the end of each. Note that we only // support striding in vec4 intervals. auto ringItemStride = gsDesc.regs.sq_gs_vert_itemsize.ITEMSIZE(); decaf_check(ringItemStride % 4 == 0); auto ringStride = ringItemStride / 4; auto ringStrideConst = spvGen.makeIntConstant(ringStride); auto ringOffsetVal = spvGen.createLoad(spvGen.ringOffsetVar(), spv::NoPrecision); auto newRingOffset = spvGen.createBinOp(spv::OpIAdd, spvGen.uintType(), ringOffsetVal, ringStrideConst); spvGen.createStore(newRingOffset, spvGen.ringOffsetVar()); // Restore our GPRs spvGen.createNoResultOp(spv::OpCopyMemory, { spvGen.gprVar(), gprSaveVar }); // Write the return statement at the end of the function spvGen.makeReturn(true); } static_cast<ShaderMeta&>(gsShader->meta) = genericMeta; for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { gsShader->meta.streamOutUsed[i] = spvGen.isStreamOutUsed(i); } } else if (shaderDesc.type == ShaderType::Pixel) { auto psShader = reinterpret_cast<PixelShader*>(shader); static_cast<ShaderMeta&>(psShader->meta) = genericMeta; for (auto i = 0; i < latte::MaxRenderTargets; ++i) { psShader->meta.pixelOutUsed[i] = spvGen.isPixelOutUsed(i); } } shader->binary.clear(); spvGen.dump(shader->binary); return true; } bool translate(const ShaderDesc& shaderDesc, Shader *shader) { return Transpiler::translate(shaderDesc, shader); } RectStubShaderDesc generateRectSubShaderDesc(VertexShader *vertexShader) { RectStubShaderDesc desc; desc.numVsExports = vertexShader->meta.numExports; return desc; } bool generateRectStub(const RectStubShaderDesc& shaderDesc, RectStubShader *shader) { SpvBuilder spvGen; spvGen.setMemoryModel(spv::AddressingModel::AddressingModelLogical, spv::MemoryModel::MemoryModelGLSL450); spvGen.addCapability(spv::CapabilityShader); spvGen.addCapability(spv::CapabilityGeometry); auto mainFn = spvGen.makeEntryPoint("main"); auto entry = spvGen.addEntryPoint(spv::ExecutionModelGeometry, mainFn, "main"); spvGen.addExecutionMode(mainFn, spv::ExecutionModeTriangles); spvGen.addExecutionMode(mainFn, spv::ExecutionModeInvocations, 1); spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputTriangleStrip); spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputVertices, 6); auto zeroConst = spvGen.makeIntConstant(0); auto oneConst = spvGen.makeIntConstant(1); auto twoConst = spvGen.makeIntConstant(2); auto twoFConst = spvGen.vectorizeConstant(spvGen.makeFloatConstant(2.0f), 4); auto glInType = spvGen.makeStructType({ spvGen.float4Type() }, "gl_in"); spvGen.addDecoration(glInType, spv::DecorationBlock); spvGen.addMemberDecoration(glInType, 0, spv::DecorationBuiltIn, spv::BuiltInPosition); spvGen.addMemberName(glInType, 0, "gl_Position"); auto glInArrType = spvGen.arrayType(glInType, 16, 3); auto glInArrVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassInput, glInArrType, "gl_in"); entry->addIdOperand(glInArrVar); auto perVertexType = spvGen.makeStructType({ spvGen.float4Type() }, "gl_PerVertex"); spvGen.addDecoration(perVertexType, spv::DecorationBlock); spvGen.addMemberDecoration(perVertexType, 0, spv::DecorationBuiltIn, spv::BuiltInPosition); spvGen.addMemberName(perVertexType, 0, "gl_Position"); auto perVertexVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassOutput, perVertexType, "gl_PerVertex"); entry->addIdOperand(perVertexVar); std::array<spv::Id, 3> posInVals; std::vector<std::array<spv::Id, 3>> paramInVals; spv::Id posOutPtr; std::vector<spv::Id> paramOutPtrs; auto posInPtr0 = spvGen.createAccessChain(spv::StorageClassInput, glInArrVar, { zeroConst, zeroConst }); auto posInPtr1 = spvGen.createAccessChain(spv::StorageClassInput, glInArrVar, { oneConst, zeroConst }); auto posInPtr2 = spvGen.createAccessChain(spv::StorageClassInput, glInArrVar, { twoConst, zeroConst }); posInVals[0] = spvGen.createLoad(posInPtr0, spv::NoPrecision); posInVals[1] = spvGen.createLoad(posInPtr1, spv::NoPrecision); posInVals[2] = spvGen.createLoad(posInPtr2, spv::NoPrecision); posOutPtr = spvGen.createAccessChain(spv::StorageClassOutput, perVertexVar, { zeroConst }); for (auto i = 0u; i < shaderDesc.numVsExports; ++i) { auto paramType = spvGen.float4Type(); auto paramInVarType = spvGen.arrayType(paramType, 16, 3); auto paramInVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassInput, paramInVarType, fmt::format("PARAM_{}_IN", i).c_str()); spvGen.addDecoration(paramInVar, spv::DecorationLocation, i); entry->addIdOperand(paramInVar); std::array<spv::Id, 3> paramInVal; auto paramInPtr0 = spvGen.createAccessChain(spv::StorageClassInput, paramInVar, { zeroConst }); auto paramInPtr1 = spvGen.createAccessChain(spv::StorageClassInput, paramInVar, { oneConst }); auto paramInPtr2 = spvGen.createAccessChain(spv::StorageClassInput, paramInVar, { twoConst }); paramInVal[0] = spvGen.createLoad(paramInPtr0, spv::NoPrecision); paramInVal[1] = spvGen.createLoad(paramInPtr1, spv::NoPrecision); paramInVal[2] = spvGen.createLoad(paramInPtr2, spv::NoPrecision); paramInVals.emplace_back(paramInVal); auto paramOutVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassOutput, paramType, fmt::format("PARAM_{}_OUT", i).c_str()); spvGen.addDecoration(paramOutVar, spv::DecorationLocation, i); entry->addIdOperand(paramOutVar); paramOutPtrs.push_back(paramOutVar); } // Reflects v across tangent created by t1/t2 auto reflectVec4 = [&](spv::Id v, spv::Id t1, spv::Id t2) { auto midPointAdd = spvGen.createBinOp(spv::OpFAdd, spvGen.float4Type(), t1, t2); auto midPoint = spvGen.createBinOp(spv::OpFDiv, spvGen.float4Type(), midPointAdd, twoFConst); auto cornerToMidPoint = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), midPoint, v); return spvGen.createBinOp(spv::OpFAdd, spvGen.float4Type(), midPoint, cornerToMidPoint); }; // Emits a vertex that was sent as input auto emitVertex = [&](int vertexId) { spvGen.createStore(posInVals[vertexId], posOutPtr); for (auto paramIdx = 0u; paramIdx < shaderDesc.numVsExports; ++paramIdx) { spvGen.createStore(paramInVals[paramIdx][vertexId], paramOutPtrs[paramIdx]); } spvGen.createNoResultOp(spv::OpEmitVertex); }; // Emits a vertex generated by flipping v accross the tangent created by t1/t2 auto emitGenVertex = [&](int vId, int t1Id, int t2Id) { auto posVal = reflectVec4(posInVals[vId], posInVals[t1Id], posInVals[t2Id]); spvGen.createStore(posVal, posOutPtr); for (auto paramIdx = 0u; paramIdx < shaderDesc.numVsExports; ++paramIdx) { auto paramVal = reflectVec4(paramInVals[paramIdx][vId], paramInVals[paramIdx][t1Id], paramInVals[paramIdx][t2Id]); spvGen.createStore(paramVal, paramOutPtrs[paramIdx]); } spvGen.createNoResultOp(spv::OpEmitVertex); }; /* len0 = | in[0] - in[1] | len1 = | in[0] - in[2] | len2 = | in[1] - in[2] | if (len0 > len1 && len0 > len2) { // 0 -> 1 is the hypotenuse } else if (len1 > len2) { // 0 -> 2 is the hypotenuse } else { // 1 -> 2 is the hypotenuse } */ // Generate the simplest initial triangle emitVertex(0); emitVertex(1); emitVertex(2); spvGen.createNoResultOp(spv::OpEndPrimitive); // Identify the hypotenuse and generate the opposing triangle auto posDiff0to1 = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), posInVals[0], posInVals[1]); auto posDiff0to2 = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), posInVals[0], posInVals[2]); auto posDiff1to2 = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), posInVals[1], posInVals[2]); auto posLen0to1 = spvGen.createBuiltinCall(spvGen.floatType(), spvGen.glslStd450(), GLSLstd450::GLSLstd450Length, { posDiff0to1 }); auto posLen0to2 = spvGen.createBuiltinCall(spvGen.floatType(), spvGen.glslStd450(), GLSLstd450::GLSLstd450Length, { posDiff0to2 }); auto posLen1to2 = spvGen.createBuiltinCall(spvGen.floatType(), spvGen.glslStd450(), GLSLstd450::GLSLstd450Length, { posDiff1to2 }); auto len0gt1 = spvGen.createBinOp(spv::OpFOrdGreaterThan, spvGen.boolType(), posLen0to1, posLen0to2); auto len0gt2 = spvGen.createBinOp(spv::OpFOrdGreaterThan, spvGen.boolType(), posLen0to1, posLen1to2); auto len0to1Longest = spvGen.createBinOp(spv::OpLogicalAnd, spvGen.boolType(), len0gt1, len0gt2); auto len0to1Block = spv::Builder::If { len0to1Longest, spv::SelectionControlMaskNone, spvGen }; { emitVertex(0); emitGenVertex(2, 0, 1); emitVertex(1); } len0to1Block.makeBeginElse(); { auto len0to2Longest = spvGen.createBinOp(spv::OpFOrdGreaterThan, spvGen.boolType(), posLen0to2, posLen1to2); auto len0to2Block = spv::Builder::If { len0to2Longest, spv::SelectionControlMaskNone, spvGen }; { emitVertex(0); emitGenVertex(1, 0, 2); emitVertex(2); } len0to2Block.makeBeginElse(); { emitVertex(1); emitGenVertex(0, 1, 2); emitVertex(2); } len0to2Block.makeEndIf(); } len0to1Block.makeEndIf(); spvGen.createNoResultOp(spv::OpEndPrimitive); spvGen.makeReturn(true); shader->binary.clear(); spvGen.dump(shader->binary); return true; } std::string shaderToString(const Shader *shader) { std::ostringstream outputAssemblyStream; spv::Disassemble(outputAssemblyStream, shader->binary); auto outputAssembly = outputAssemblyStream.str(); // To improve readability, translate the OpLine instructions to something readable... // This code assumes that all OpLine lines are in the format of `c0aaa0uu` // where c is the CF unit, a is the ALU and u is the unit. Note that c can be longer // than a single character. outputAssembly = std::regex_replace(outputAssembly, std::regex("(Line [^ ]+ (\\d{1})(\\d{3})(\\d{3})((\\d{1})) 0)"), "// %IMATYPE$2%; CF: %IMACF$3%; GROUP: %IMAGROUP$4%; UNIT: %IMAUNIT$5%;"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMATYPE1%"), "FS"); // Convert shader type to text outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMATYPE2%"), "VS"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMATYPE3%"), "DC"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMATYPE4%"), "GS"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMATYPE5%"), "PS"); outputAssembly = std::regex_replace(outputAssembly, std::regex("CF: %IMACF999%;"), ""); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMACF(0*)(\\d+)%"), "$2"); // Drop trailing 0's outputAssembly = std::regex_replace(outputAssembly, std::regex("GROUP: %IMAGROUP999%;"), ""); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMAGROUP(0*)(\\d+)%"), "$2"); // Drop trailing 0's outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMAUNIT0%"), "X"); // Convert unit numbers to text outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMAUNIT1%"), "Y"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMAUNIT2%"), "Z"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMAUNIT3%"), "W"); outputAssembly = std::regex_replace(outputAssembly, std::regex("%IMAUNIT4%"), "T"); outputAssembly = std::regex_replace(outputAssembly, std::regex("UNIT: %IMAUNIT9%;"), ""); return outputAssembly; } } // namespace hlsl2 #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_transpiler.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "latte/latte_shaderparser.h" #include "spirv_translate.h" #include "spirv_shaderspvbuilder.h" namespace spirv { using namespace latte; class Transpiler : public latte::ShaderParser { enum SampleMode : uint32_t { None = 0, Lod = 1 << 0, LodBias = 1 << 1, LodZero = 1 << 2, Gradient = 1 << 3, Compare = 1 << 4, Gather = 1 << 5, Load = 1 << 6 }; public: void translateTex_LD(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_FETCH4(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_G(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_G(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SAMPLE_C_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_GET_TEXTURE_INFO(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_SET_CUBEMAP_INDEX(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_GET_GRADIENTS_H(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_GET_GRADIENTS_V(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_VTX_FETCH(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateTex_VTX_SEMANTIC(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateVtx_FETCH(const ControlFlowInst &cf, const VertexFetchInst &inst) override; void translateVtx_SEMANTIC(const ControlFlowInst &cf, const VertexFetchInst &inst) override; void translateAluOp2_ADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_ADD_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_AND_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_ASHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_COS(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_EXP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_FLT_TO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_FLT_TO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_FRACT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_INT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_KILLGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_LOG_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_LOG_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_LSHL_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_LSHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MAX(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MAX_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MAX_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MAX_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MIN_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MIN_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MIN_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MOV(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MOVA_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MOVA_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MUL(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MUL_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MULLO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_MULLO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_NOP(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_NOT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_OR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_PRED_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIP_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIP_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIP_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIP_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIPSQRT_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIPSQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RECIPSQRT_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_RNDNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGT_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETNE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_SUB_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_UINT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_TRUNC(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_XOR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_CUBE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_DOT4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp2_DOT4_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_CNDE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_CNDGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_CNDGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_CNDE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_CNDGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_CNDGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_MULADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_MULADD_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_MULADD_M2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_MULADD_M4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluOp3_MULADD_D2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateTexInst(const ControlFlowInst &cf, const TextureFetchInst &inst) override; void translateVtxInst(const ControlFlowInst &cf, const VertexFetchInst &inst) override; void translateAluInst(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override; void translateAluGroup(const ControlFlowInst &cf, const AluInstructionGroup &group) override; void translateCf_ALU(const ControlFlowInst &cf) override; void translateCf_ALU_PUSH_BEFORE(const ControlFlowInst &cf) override; void translateCf_ALU_POP_AFTER(const ControlFlowInst &cf) override; void translateCf_ALU_POP2_AFTER(const ControlFlowInst &cf) override; void translateCf_ALU_EXT(const ControlFlowInst &cf) override; void translateCf_ALU_CONTINUE(const ControlFlowInst &cf) override; void translateCf_ALU_BREAK(const ControlFlowInst &cf) override; void translateCf_ALU_ELSE_AFTER(const ControlFlowInst &cf) override; void translateCf_CALL_FS(const ControlFlowInst &cf) override; void translateCf_ELSE(const ControlFlowInst &cf) override; void translateCf_JUMP(const ControlFlowInst &cf) override; void translateCf_KILL(const ControlFlowInst &cf) override; void translateCf_NOP(const ControlFlowInst &cf) override; void translateCf_PUSH(const ControlFlowInst &cf) override; void translateCf_POP(const ControlFlowInst &cf) override; void translateCf_RETURN(const ControlFlowInst &cf) override; void translateCf_LOOP_START(const ControlFlowInst &cf) override; void translateCf_LOOP_START_DX10(const ControlFlowInst &cf) override; void translateCf_LOOP_START_NO_AL(const ControlFlowInst &cf) override; void translateCf_LOOP_END(const ControlFlowInst &cf) override; void translateCf_LOOP_CONTINUE(const ControlFlowInst &cf) override; void translateCf_LOOP_BREAK(const ControlFlowInst &cf) override; void translateCf_TEX(const ControlFlowInst &cf) override; void translateCf_VTX(const ControlFlowInst &cf) override; void translateCf_VTX_TC(const ControlFlowInst &cf) override; void translateCf_EMIT_VERTEX(const ControlFlowInst &cf) override; void translateCf_EMIT_CUT_VERTEX(const ControlFlowInst &cf) override; void translateCf_CUT_VERTEX(const ControlFlowInst &cf) override; void translateCf_EXP(const ControlFlowInst &cf) override; void translateCf_EXP_DONE(const ControlFlowInst &cf) override; void translateCf_MEM_STREAM0(const ControlFlowInst &cf) override; void translateCf_MEM_STREAM1(const ControlFlowInst &cf) override; void translateCf_MEM_STREAM2(const ControlFlowInst &cf) override; void translateCf_MEM_STREAM3(const ControlFlowInst &cf) override; void translateCf_MEM_RING(const ControlFlowInst &cf) override; void translateCfNormalInst(const ControlFlowInst& cf) override; void translateCfExportInst(const ControlFlowInst& cf) override; void translateCfAluInst(const ControlFlowInst& cf) override; // Our own helpers spv::Id genAluCondOp(spv::Op predOp, spv::Id lhsVal, spv::Id trueVal, spv::Id falseVal); spv::Id genPredSetOp(const AluInst &inst, spv::Op predOp, spv::Id typeId, spv::Id lhsVal, spv::Id rhsVal, bool updatesPredicate = false); void translateGenericStream(const ControlFlowInst &cf, int streamIdx); void translateGenericExport(const ControlFlowInst &cf); void translateGenericSample(const ControlFlowInst &cf, const TextureFetchInst &inst, uint32_t sampleMode); void translateAluOp2RecipCommon(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst, bool isRecipSqrt, bool isFF); static void writeGenericProlog(ShaderSpvBuilder &spv); static void writeVertexProlog(ShaderSpvBuilder &spv, const VertexShaderDesc& desc); static void writeGeometryProlog(ShaderSpvBuilder &spv, const GeometryShaderDesc& desc); static void writePixelProlog(ShaderSpvBuilder &spv, const PixelShaderDesc& desc); void translate() override; static bool translate(const ShaderDesc& shaderDesc, Shader *shader); protected: void writeCfAluUnitLine(ShaderParser::Type shaderType, int cfId, int groupId, int unitId); ShaderSpvBuilder *mSpv; // Inputs std::array<latte::SQ_TEX_DIM, latte::MaxTextures> mTexDims; std::array<TextureInputType, latte::MaxTextures> mTexFormats; std::array<latte::SQ_VTX_SEMANTIC_N, 32> mSqVtxSemantics; std::array<uint32_t, latte::MaxStreamOutBuffers> mStreamOutStride; latte::PA_CL_VS_OUT_CNTL mPaClVsOutCntl; std::array<PixelOutputType, latte::MaxRenderTargets> mPixelOutType; latte::SQ_PGM_EXPORTS_PS mSqPgmExportsPs; latte::CB_SHADER_CONTROL mCbShaderControl; latte::CB_SHADER_MASK mCbShaderMask; latte::DB_SHADER_CONTROL mDbShaderControl; // Outputs std::array<AttribBuffer, latte::MaxAttribBuffers> mAttribBuffers; std::vector<AttribElem> mAttribElems; std::array<bool, latte::MaxRenderTargets> mPixelOutUsed; struct LoopState { uint32_t startPC; uint32_t endPC; spv::Block *head; spv::Block *body; spv::Block *continue_target; spv::Block *merge; }; std::vector<LoopState> mLoopStack; }; } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/spirv/spirv_vtx.cpp ================================================ #ifdef DECAF_VULKAN #include "spirv_transpiler.h" #include "latte/latte_formats.h" namespace spirv { void Transpiler::translateVtx_FETCH(const ControlFlowInst &cf, const VertexFetchInst &inst) { // MEGA fetches are an optimization on the GPU cache. There is // no need to do any particular work during translation. //inst.word2.MEGA_FETCH() //inst.word0.MEGA_FETCH_COUNT() // Let's only support a very expected set of values decaf_check(inst.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::NO_INDEX_OFFSET); decaf_check(inst.word1.USE_CONST_FIELDS() == 1); // Grab the source register information GprSelRef srcGpr; srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP); srcGpr.sel = inst.word0.SRC_SEL_X(); // Grab the destination register information GprMaskRef destGpr; destGpr.gpr = makeGprRef(inst.gpr.DST_GPR(), inst.gpr.DST_REL(), SQ_INDEX_MODE::LOOP); destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X(); destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y(); destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z(); destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W(); // Intercept a GSIN resource fetch, and process it specially. if (mType == ShaderParser::Type::Geometry) { auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::GS_TEX_RESOURCE_0; if (id == SQ_RES_OFFSET::GS_GSIN_RESOURCE) { // We only support vec4 ring buffer items decaf_check(inst.word2.OFFSET() % 16 == 0); auto ringOffset = inst.word2.OFFSET() / 16; auto inputVar = mSpv->inputRingVar(ringOffset); // We check to make sure everything makes sense... decaf_check(srcGpr.gpr.indexMode == GprIndexMode::None); spv::Id vertIdxVal; if (srcGpr.gpr.number == 0 && srcGpr.sel == latte::SQ_SEL::SEL_X) { vertIdxVal = mSpv->makeIntConstant(0); } else if (srcGpr.gpr.number == 0 && srcGpr.sel == latte::SQ_SEL::SEL_Y) { vertIdxVal = mSpv->makeIntConstant(1); } else if (srcGpr.gpr.number == 0 && srcGpr.sel == latte::SQ_SEL::SEL_W) { vertIdxVal = mSpv->makeIntConstant(2); } else if (srcGpr.gpr.number == 1 && srcGpr.sel == latte::SQ_SEL::SEL_X) { vertIdxVal = mSpv->makeIntConstant(3); } else if (srcGpr.gpr.number == 1 && srcGpr.sel == latte::SQ_SEL::SEL_Y) { vertIdxVal = mSpv->makeIntConstant(4); } else if (srcGpr.gpr.number == 1 && srcGpr.sel == latte::SQ_SEL::SEL_W) { vertIdxVal = mSpv->makeIntConstant(5); } else { decaf_abort("Unexpected vertex index selection") } auto outputPtr = mSpv->createAccessChain(spv::StorageClassInput, inputVar, { vertIdxVal }); auto outputVal = mSpv->createLoad(outputPtr, spv::NoPrecision); auto gprRef = mSpv->getGprRef(destGpr.gpr); auto destVal = spv::NoResult; if (!latte::isSwizzleFullyUnmasked(destGpr.mask)) { destVal = mSpv->createLoad(gprRef, spv::NoPrecision); } auto maskedVal = mSpv->applySelMask(destVal, outputVal, destGpr.mask); mSpv->createStore(maskedVal, gprRef); return; } } else if (mType == ShaderParser::Type::DataCache) { auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::GS_TEX_RESOURCE_0; if (id == SQ_RES_OFFSET::GS_GSIN_RESOURCE) { auto ringOffsetVal = mSpv->createLoad(mSpv->ringOffsetVar(), spv::NoPrecision); // We check to make sure everything makes sense... decaf_check(srcGpr.gpr.number == 0); decaf_check(srcGpr.gpr.indexMode == GprIndexMode::None); decaf_check(srcGpr.sel == latte::SQ_SEL::SEL_X); // Note that this is blocked out above, so this code may not be correct... decaf_check(inst.word2.OFFSET() % 16 == 0); auto itemOffsetVal = mSpv->makeUintConstant(inst.word2.OFFSET() / 16); auto realOffset = mSpv->createBinOp(spv::OpIAdd, mSpv->uintType(), ringOffsetVal, itemOffsetVal); auto outputPtr = mSpv->createAccessChain(spv::StorageClassPrivate, mSpv->ringVar(), { realOffset }); auto outputVal = mSpv->createLoad(outputPtr, spv::NoPrecision); auto gprRef = mSpv->getGprRef(destGpr.gpr); // TODO: This logic is duplicated a bunch of places, we should converge // this into a centralized function in mSpv... auto destVal = spv::NoResult; if (!latte::isSwizzleFullyUnmasked(destGpr.mask)) { destVal = mSpv->createLoad(gprRef, spv::NoPrecision); } auto maskedVal = mSpv->applySelMask(destVal, outputVal, destGpr.mask); mSpv->createStore(maskedVal, gprRef); return; } } decaf_check(inst.word2.OFFSET() == 0); uint32_t cbufferIdx; if (mType == ShaderParser::Type::Vertex) { auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::VS_TEX_RESOURCE_0; if (id >= SQ_RES_OFFSET::VS_BUF_RESOURCE_0 && id <= SQ_RES_OFFSET::VS_BUF_RESOURCE_0 + 16) { cbufferIdx = id - SQ_RES_OFFSET::VS_BUF_RESOURCE_0; } else { decaf_abort("Unsupported vertex shader VTX_FETCH vertex resource"); } } else if (mType == ShaderParser::Type::Geometry) { auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::GS_TEX_RESOURCE_0; if (id >= SQ_RES_OFFSET::GS_BUF_RESOURCE_0 && id <= SQ_RES_OFFSET::GS_BUF_RESOURCE_0 + 16) { cbufferIdx = id - SQ_RES_OFFSET::GS_BUF_RESOURCE_0; } else { decaf_abort("Unsupported vertex shader VTX_FETCH geometry resource"); } } else if (mType == ShaderParser::Type::Pixel) { auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::PS_TEX_RESOURCE_0; if (id >= SQ_RES_OFFSET::PS_BUF_RESOURCE_0 && id <= SQ_RES_OFFSET::PS_BUF_RESOURCE_0 + 16) { cbufferIdx = id - SQ_RES_OFFSET::PS_BUF_RESOURCE_0; } else { decaf_abort("Unsupported vertex shader VTX_FETCH pixel resource"); } } else { decaf_abort("Unsupported shader type for VTX_FETCH"); } auto indexValFloat = mSpv->readGprSelRef(srcGpr); auto indexVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uintType(), indexValFloat); auto cbufferVar = mSpv->cbufferVar(cbufferIdx); // TODO: Should probably move this into SPV auto zeroConst = mSpv->makeUintConstant(0); auto cbufferPtr = mSpv->createAccessChain(spv::StorageClassUniform, cbufferVar, { zeroConst, indexVal }); auto outputVal = mSpv->createLoad(cbufferPtr, spv::NoPrecision); auto gprRef = mSpv->getGprRef(destGpr.gpr); // TODO: This logic is duplicated a bunch of places, we should converge // this into a centralized function in mSpv... auto destVal = spv::NoResult; if (!latte::isSwizzleFullyUnmasked(destGpr.mask)) { destVal = mSpv->createLoad(gprRef, spv::NoPrecision); } auto maskedVal = mSpv->applySelMask(destVal, outputVal, destGpr.mask); mSpv->createStore(maskedVal, gprRef); } int findVtxSemanticGpr(const std::array<latte::SQ_VTX_SEMANTIC_N, 32>& vtxSemantics, uint8_t semanticId) { for (auto i = 0; i < 32; ++i) { auto foundSemanticId = vtxSemantics[i].SEMANTIC_ID(); if (semanticId == foundSemanticId) { return i; } } return -1; } void Transpiler::translateVtx_SEMANTIC(const ControlFlowInst &cf, const VertexFetchInst &inst) { // Because this instruction has an SRC register to determine where to source // the indexing data from, but we need a constant, we assume that it will always // use R0, and that it will contain what it originally starts with during shader // startup. In order to ensure this is the case, we only allow SEMANTIC inst's // to execute within a Fetch Shader for now, and we also ensure that the fetch // shader is always invoked as the first instruction of a vertex shader. We // hope that this never needs to be fixed, otherwise things are going to get // EXTREMELY complicated (need to do register expression propagation). decaf_check(mType == ShaderParser::Type::Fetch); // We do not support fetch constant fields as I don't even // know what they are at the moment! decaf_check(!inst.word1.USE_CONST_FIELDS()); // More stuff I have no clue about... decaf_check(!inst.word1.SRF_MODE_ALL()); // We know what this one does, but I don't know how to implement it, // and I do not think it will be needed right now... decaf_check(!inst.word2.CONST_BUF_NO_STRIDE()); // We use the DATA_FORMAT to determine the fetch sizing. This allows us // to more easily handle the splitting of XYZW groups below. //inst.word2.MEGA_FETCH(); //inst.word0.MEGA_FETCH_COUNT(); GprSelRef srcGpr; srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP); srcGpr.sel = inst.word0.SRC_SEL_X(); // We do not support indexing for fetch from buffers... decaf_check(srcGpr.gpr.indexMode == GprIndexMode::None); // Try and locate a matching semantic from the semantic table auto semanticGprIdx = findVtxSemanticGpr(mSqVtxSemantics, inst.sem.SEMANTIC_ID()); if (semanticGprIdx < 0) { // We didn't find anythign in the semantic table, we can simply // ignore this instruction in that case. return; } GprMaskRef destGpr; destGpr.gpr.indexMode = GprIndexMode::None; destGpr.gpr.number = static_cast<uint32_t>(semanticGprIdx + 1); destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X(); destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y(); destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z(); destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W(); if (destGpr.gpr.number == 0xffffffff) { // This is not semantically mapped, so we can actually skip it entirely! return; } auto dataFormat = inst.word1.DATA_FORMAT(); auto fmtMeta = latte::getDataFormatMeta(dataFormat); // We currently only support vertex fetches inside a fetch shader... decaf_check(mType == ShaderParser::Type::Fetch); // Figure out which attribute buffer this is referencing auto bufferId = inst.word0.BUFFER_ID(); auto attribBase = latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 - latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0; decaf_check(attribBase <= bufferId && bufferId <= attribBase + 16); auto attribBufferId = bufferId - attribBase; auto bufferOffset = inst.word2.OFFSET(); AttribBuffer::IndexMode indexMode; AttribBuffer::DivisorMode divisorMode; if (inst.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::VERTEX_DATA) { indexMode = AttribBuffer::IndexMode::PerVertex; divisorMode = AttribBuffer::DivisorMode::CONST_1; } else if (inst.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::INSTANCE_DATA) { indexMode = AttribBuffer::IndexMode::PerInstance; if (srcGpr.sel == SQ_SEL::SEL_Y) { divisorMode = AttribBuffer::DivisorMode::REGISTER_0; } else if (srcGpr.sel == SQ_SEL::SEL_Z) { divisorMode = AttribBuffer::DivisorMode::REGISTER_1; } else if (srcGpr.sel == SQ_SEL::SEL_W) { divisorMode = AttribBuffer::DivisorMode::CONST_1; } else { decaf_abort("Unexpected vertex fetch divisor selector"); } } else { decaf_abort("Unexpected vertex fetch type"); } // Set up our buffer information auto& inputBuffer = mAttribBuffers[attribBufferId]; if (inputBuffer.isUsed) { // If its already in use, confirm matching indexing decaf_check(inputBuffer.indexMode == indexMode); decaf_check(inputBuffer.divisorMode == divisorMode); } else { // If its not in use, lets set this up ourselves. inputBuffer = AttribBuffer { true, indexMode, divisorMode }; } // Set up our attribute information AttribElem inputAttrib; inputAttrib.bufferIndex = attribBufferId; inputAttrib.offset = bufferOffset; inputAttrib.elemWidth = fmtMeta.inputWidth; inputAttrib.elemCount = fmtMeta.inputCount; mAttribElems.push_back(inputAttrib); auto inputId = mAttribElems.size() - 1; auto swapMode = inst.word2.ENDIAN_SWAP(); auto formatComp = inst.word1.FORMAT_COMP_ALL(); auto numFormat = inst.word1.NUM_FORMAT_ALL(); spv::Id sourceElemType = mSpv->uintType(); // Get a vector of the source element type auto sourceType = mSpv->vecType(sourceElemType, fmtMeta.inputCount); // Create the input variable auto sourceVar = mSpv->inputAttribVar(static_cast<uint32_t>(inputId), sourceType); // Load the input data auto source = mSpv->createLoad(sourceVar, spv::NoPrecision); // Perform byte swapping on the input data, where appropriate if (swapMode == SQ_ENDIAN::SWAP_8IN16) { decaf_check(fmtMeta.inputWidth == 16 || fmtMeta.inputWidth == 32); source = mSpv->bswap8in16(source); } else if (swapMode == SQ_ENDIAN::SWAP_8IN32) { decaf_check(fmtMeta.inputWidth == 32); source = mSpv->bswap8in32(source); } else if (swapMode != SQ_ENDIAN::NONE) { decaf_abort("Encountered unexpected endian swap mode"); } // Store each element for operating on independantly... std::array<spv::Id, 4> inputElems = { spv::NoResult, spv::NoResult, spv::NoResult, spv::NoResult }; if (fmtMeta.inputCount > 1) { for (auto i = 0u; i < fmtMeta.inputCount; ++i) { inputElems[i] = mSpv->createOp(spv::OpCompositeExtract, sourceElemType, { source, i }); } } else { for (auto i = 0u; i < fmtMeta.inputCount; ++i) { inputElems[i] = source; } } // Upcast each element to a uint for bitshift extraction if (sourceElemType != mSpv->uintType()) { for (auto i = 0u; i < fmtMeta.inputCount; ++i) { inputElems[i] = mSpv->createUnaryOp(spv::OpUConvert, mSpv->uintType(), inputElems[i]); } } // Calculate the number of resulting elements based on the lengths int outputElemCount = 0; for (auto i = 0u; i < 4u; ++i) { if (fmtMeta.elems[i].length > 0) { outputElemCount = i + 1; } } // Extract the appropriate bits if needed... std::array<spv::Id, 4> elems = { spv::NoResult, spv::NoResult, spv::NoResult, spv::NoResult }; for (auto i = 0; i < outputElemCount; ++i) { auto &elem = fmtMeta.elems[i]; // If the element width matches perfectly, we can just use it directly if (elem.length == fmtMeta.inputWidth) { elems[i] = inputElems[elem.index]; continue; } auto startConst = mSpv->makeIntConstant(elem.start); auto lengthConst = mSpv->makeIntConstant(elem.length); elems[i] = mSpv->createTriOp(spv::OpBitFieldUExtract, mSpv->uintType(), inputElems[elem.index], startConst, lengthConst); } for (auto i = 0; i < outputElemCount; ++i) { auto &elem = fmtMeta.elems[i]; auto fieldMax = static_cast<uint64_t>(1u) << elem.length; if (fmtMeta.type == DataFormatMetaType::FLOAT) { if (elem.length == 16) { // Bitcast the data to a float from half-float data // elem = unpackHalf2x16(elem).x auto unpackedFloat2 = mSpv->createBuiltinCall(mSpv->float2Type(), mSpv->glslStd450(), GLSLstd450::GLSLstd450UnpackHalf2x16, { elems[i] }); elems[i] = mSpv->createOp(spv::Op::OpCompositeExtract, mSpv->floatType(), { unpackedFloat2, 0 }); } else if (elem.length == 32) { // Bitcast the data to a float // elem = *(float*)&elem elems[i] = mSpv->createUnaryOp(spv::Op::OpBitcast, mSpv->floatType(), elems[i]); } else if (elem.length == 10) { elems[i] = mSpv->unpackFloat10(elems[i]); } else if (elem.length == 11) { elems[i] = mSpv->unpackFloat11(elems[i]); } else { decaf_abort("Unexpected float data width"); } } else { if (formatComp == latte::SQ_FORMAT_COMP::SIGNED && elem.length > 2) { // Perform sign-extension and conversion to a signed integer if (elem.length == 32) { // If its 32 bits, we don't need to perform sign extension, just bitcast it // elem = *(int*)&elem elems[i] = mSpv->createUnaryOp(spv::Op::OpBitcast, mSpv->intType(), elems[i]); } else { // If its less than 32 bits, we use bitfield extraction to sign extend auto offsetConst = mSpv->makeIntConstant(0); auto lengthConst = mSpv->makeIntConstant(elem.length); auto signedElem = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), elems[i]); elems[i] = mSpv->createTriOp(spv::Op::OpBitFieldSExtract, mSpv->intType(), signedElem, offsetConst, lengthConst); } } else { // We are already in UINT format as we needed } } if (numFormat == latte::SQ_NUM_FORMAT::NORM) { decaf_check(fmtMeta.type == DataFormatMetaType::UINT); auto fieldMask = fieldMax - 1; if (formatComp == latte::SQ_FORMAT_COMP::SIGNED && elem.length > 2) { // Type must already be a signed type from above decaf_check(mSpv->getTypeId(elems[i]) == mSpv->intType()); // elem = clamp((float)(elem) / float(FIELD_MASK/2), -1.0f, 1.0f) auto normMaxConst = mSpv->makeFloatConstant(float(fieldMask / 2)); auto normNegConst = mSpv->makeFloatConstant(-1.0f); auto normPosConst = mSpv->makeFloatConstant(+1.0f); auto floatElem = mSpv->createUnaryOp(spv::OpConvertSToF, mSpv->floatType(), elems[i]); auto normElem = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), floatElem, normMaxConst); elems[i] = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FClamp, { normElem, normNegConst, normPosConst }); } else { // Type must already be a unsigned type from above decaf_check(mSpv->getTypeId(elems[i]) == mSpv->uintType()); // elem = float(elem) / float(FIELD_MASK) auto normMaxConst = mSpv->makeFloatConstant(float(fieldMask)); auto floatElem = mSpv->createUnaryOp(spv::OpConvertSToF, mSpv->floatType(), elems[i]); elems[i] = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), floatElem, normMaxConst); } } else if (numFormat == latte::SQ_NUM_FORMAT::INT) { auto origTypeId = mSpv->getTypeId(elems[i]); if (formatComp == latte::SQ_FORMAT_COMP::SIGNED) { // elem = int(elem) if (origTypeId == mSpv->floatType()) { elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertFToS, mSpv->intType(), elems[i]); } else if (origTypeId == mSpv->intType()) { // We are already an int type, no need to convert } else if (origTypeId == mSpv->uintType()) { elems[i] = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), elems[i]); } else { decaf_abort("Unexpected format conversion type."); } } else { // elem = uint(elem) if (origTypeId == mSpv->floatType()) { elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertFToU, mSpv->uintType(), elems[i]); } else if (origTypeId == mSpv->intType()) { elems[i] = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uintType(), elems[i]); } else if (origTypeId == mSpv->uintType()) { // We are already a uint type, no need to convert } else { decaf_abort("Unexpected format conversion type."); } } } else if (numFormat == latte::SQ_NUM_FORMAT::SCALED) { // formatComp ignored as SIGNED/UNSIGNED makes no sense at this stage auto origTypeId = mSpv->getTypeId(elems[i]); // elem = float(elem) if (origTypeId == mSpv->floatType()) { // We are already a float type, no need to convert } else if (origTypeId == mSpv->intType()) { elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertSToF, mSpv->floatType(), elems[i]); } else if (origTypeId == mSpv->uintType()) { elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertUToF, mSpv->floatType(), elems[i]); } else { decaf_abort("Unexpected format conversion type."); } } else { decaf_abort("Unexpected vertex fetch numFormat"); } } // Figure out what format of elements we have as output auto elemsType = mSpv->getTypeId(elems[0]); for (auto i = 1; i < outputElemCount; ++i) { decaf_check(mSpv->getTypeId(elems[i]) == elemsType); } // Fill remaining values with defaults for (auto i = outputElemCount; i < 4; ++i) { if (elemsType == mSpv->floatType()) { if (i != 3) { elems[i] = mSpv->makeFloatConstant(0.0f); } else { elems[i] = mSpv->makeFloatConstant(1.0f); } } else if (elemsType == mSpv->intType()) { if (i != 3) { elems[i] = mSpv->makeIntConstant(0); } else { elems[i] = mSpv->makeIntConstant(1); } } else if (elemsType == mSpv->uintType()) { if (i != 3) { elems[i] = mSpv->makeUintConstant(0); } else { elems[i] = mSpv->makeUintConstant(1); } } else { decaf_abort("Unexpected element format output in fetch") } } auto outputType = mSpv->vecType(elemsType, 4); auto outputVal = mSpv->createOp(spv::Op::OpCompositeConstruct, outputType, { elems[0], elems[1], elems[2], elems[3] }); mSpv->writeGprMaskRef(destGpr, outputVal); } } // namespace spirv #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vk_mem_alloc.cpp ================================================ #ifdef DECAF_VULKAN #define VMA_IMPLEMENTATION #include "vk_mem_alloc_decaf.h" #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vk_mem_alloc.h ================================================ // // Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #ifndef AMD_VULKAN_MEMORY_ALLOCATOR_H #define AMD_VULKAN_MEMORY_ALLOCATOR_H #ifdef __cplusplus extern "C" { #endif /** \mainpage Vulkan Memory Allocator <b>Version 2.3.0</b> (2019-12-04) Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. \n License: MIT Documentation of all members: vk_mem_alloc.h \section main_table_of_contents Table of contents - <b>User guide</b> - \subpage quick_start - [Project setup](@ref quick_start_project_setup) - [Initialization](@ref quick_start_initialization) - [Resource allocation](@ref quick_start_resource_allocation) - \subpage choosing_memory_type - [Usage](@ref choosing_memory_type_usage) - [Required and preferred flags](@ref choosing_memory_type_required_preferred_flags) - [Explicit memory types](@ref choosing_memory_type_explicit_memory_types) - [Custom memory pools](@ref choosing_memory_type_custom_memory_pools) - [Dedicated allocations](@ref choosing_memory_type_dedicated_allocations) - \subpage memory_mapping - [Mapping functions](@ref memory_mapping_mapping_functions) - [Persistently mapped memory](@ref memory_mapping_persistently_mapped_memory) - [Cache flush and invalidate](@ref memory_mapping_cache_control) - [Finding out if memory is mappable](@ref memory_mapping_finding_if_memory_mappable) - \subpage staying_within_budget - [Querying for budget](@ref staying_within_budget_querying_for_budget) - [Controlling memory usage](@ref staying_within_budget_controlling_memory_usage) - \subpage custom_memory_pools - [Choosing memory type index](@ref custom_memory_pools_MemTypeIndex) - [Linear allocation algorithm](@ref linear_algorithm) - [Free-at-once](@ref linear_algorithm_free_at_once) - [Stack](@ref linear_algorithm_stack) - [Double stack](@ref linear_algorithm_double_stack) - [Ring buffer](@ref linear_algorithm_ring_buffer) - [Buddy allocation algorithm](@ref buddy_algorithm) - \subpage defragmentation - [Defragmenting CPU memory](@ref defragmentation_cpu) - [Defragmenting GPU memory](@ref defragmentation_gpu) - [Additional notes](@ref defragmentation_additional_notes) - [Writing custom allocation algorithm](@ref defragmentation_custom_algorithm) - \subpage lost_allocations - \subpage statistics - [Numeric statistics](@ref statistics_numeric_statistics) - [JSON dump](@ref statistics_json_dump) - \subpage allocation_annotation - [Allocation user data](@ref allocation_user_data) - [Allocation names](@ref allocation_names) - \subpage debugging_memory_usage - [Memory initialization](@ref debugging_memory_usage_initialization) - [Margins](@ref debugging_memory_usage_margins) - [Corruption detection](@ref debugging_memory_usage_corruption_detection) - \subpage record_and_replay - \subpage usage_patterns - [Common mistakes](@ref usage_patterns_common_mistakes) - [Simple patterns](@ref usage_patterns_simple) - [Advanced patterns](@ref usage_patterns_advanced) - \subpage configuration - [Pointers to Vulkan functions](@ref config_Vulkan_functions) - [Custom host memory allocator](@ref custom_memory_allocator) - [Device memory allocation callbacks](@ref allocation_callbacks) - [Device heap memory limit](@ref heap_memory_limit) - \subpage vk_khr_dedicated_allocation - \subpage general_considerations - [Thread safety](@ref general_considerations_thread_safety) - [Validation layer warnings](@ref general_considerations_validation_layer_warnings) - [Allocation algorithm](@ref general_considerations_allocation_algorithm) - [Features not supported](@ref general_considerations_features_not_supported) \section main_see_also See also - [Product page on GPUOpen](https://gpuopen.com/gaming-product/vulkan-memory-allocator/) - [Source repository on GitHub](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) \page quick_start Quick start \section quick_start_project_setup Project setup Vulkan Memory Allocator comes in form of a "stb-style" single header file. You don't need to build it as a separate library project. You can add this file directly to your project and submit it to code repository next to your other source files. "Single header" doesn't mean that everything is contained in C/C++ declarations, like it tends to be in case of inline functions or C++ templates. It means that implementation is bundled with interface in a single file and needs to be extracted using preprocessor macro. If you don't do it properly, you will get linker errors. To do it properly: -# Include "vk_mem_alloc.h" file in each CPP file where you want to use the library. This includes declarations of all members of the library. -# In exacly one CPP file define following macro before this include. It enables also internal definitions. \code #define VMA_IMPLEMENTATION #include "vk_mem_alloc.h" \endcode It may be a good idea to create dedicated CPP file just for this purpose. Note on language: This library is written in C++, but has C-compatible interface. Thus you can include and use vk_mem_alloc.h in C or C++ code, but full implementation with `VMA_IMPLEMENTATION` macro must be compiled as C++, NOT as C. Please note that this library includes header `<vulkan/vulkan.h>`, which in turn includes `<windows.h>` on Windows. If you need some specific macros defined before including these headers (like `WIN32_LEAN_AND_MEAN` or `WINVER` for Windows, `VK_USE_PLATFORM_WIN32_KHR` for Vulkan), you must define them before every `#include` of this library. \section quick_start_initialization Initialization At program startup: -# Initialize Vulkan to have `VkPhysicalDevice` and `VkDevice` object. -# Fill VmaAllocatorCreateInfo structure and create #VmaAllocator object by calling vmaCreateAllocator(). \code VmaAllocatorCreateInfo allocatorInfo = {}; allocatorInfo.physicalDevice = physicalDevice; allocatorInfo.device = device; VmaAllocator allocator; vmaCreateAllocator(&allocatorInfo, &allocator); \endcode \section quick_start_resource_allocation Resource allocation When you want to create a buffer or image: -# Fill `VkBufferCreateInfo` / `VkImageCreateInfo` structure. -# Fill VmaAllocationCreateInfo structure. -# Call vmaCreateBuffer() / vmaCreateImage() to get `VkBuffer`/`VkImage` with memory already allocated and bound to it. \code VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufferInfo.size = 65536; bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; VkBuffer buffer; VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); \endcode Don't forget to destroy your objects when no longer needed: \code vmaDestroyBuffer(allocator, buffer, allocation); vmaDestroyAllocator(allocator); \endcode \page choosing_memory_type Choosing memory type Physical devices in Vulkan support various combinations of memory heaps and types. Help with choosing correct and optimal memory type for your specific resource is one of the key features of this library. You can use it by filling appropriate members of VmaAllocationCreateInfo structure, as described below. You can also combine multiple methods. -# If you just want to find memory type index that meets your requirements, you can use function: vmaFindMemoryTypeIndex(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo(). -# If you want to allocate a region of device memory without association with any specific image or buffer, you can use function vmaAllocateMemory(). Usage of this function is not recommended and usually not needed. vmaAllocateMemoryPages() function is also provided for creating multiple allocations at once, which may be useful for sparse binding. -# If you already have a buffer or an image created, you want to allocate memory for it and then you will bind it yourself, you can use function vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(). For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory() or their extended versions: vmaBindBufferMemory2(), vmaBindImageMemory2(). -# If you want to create a buffer or an image, allocate memory for it and bind them together, all in one call, you can use function vmaCreateBuffer(), vmaCreateImage(). This is the easiest and recommended way to use this library. When using 3. or 4., the library internally queries Vulkan for memory types supported for that buffer or image (function `vkGetBufferMemoryRequirements()`) and uses only one of these types. If no memory type can be found that meets all the requirements, these functions return `VK_ERROR_FEATURE_NOT_PRESENT`. You can leave VmaAllocationCreateInfo structure completely filled with zeros. It means no requirements are specified for memory type. It is valid, although not very useful. \section choosing_memory_type_usage Usage The easiest way to specify memory requirements is to fill member VmaAllocationCreateInfo::usage using one of the values of enum #VmaMemoryUsage. It defines high level, common usage types. For more details, see description of this enum. For example, if you want to create a uniform buffer that will be filled using transfer only once or infrequently and used for rendering every frame, you can do it using following code: \code VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufferInfo.size = 65536; bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; VkBuffer buffer; VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); \endcode \section choosing_memory_type_required_preferred_flags Required and preferred flags You can specify more detailed requirements by filling members VmaAllocationCreateInfo::requiredFlags and VmaAllocationCreateInfo::preferredFlags with a combination of bits from enum `VkMemoryPropertyFlags`. For example, if you want to create a buffer that will be persistently mapped on host (so it must be `HOST_VISIBLE`) and preferably will also be `HOST_COHERENT` and `HOST_CACHED`, use following code: \code VmaAllocationCreateInfo allocInfo = {}; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; allocInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; VkBuffer buffer; VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); \endcode A memory type is chosen that has all the required flags and as many preferred flags set as possible. If you use VmaAllocationCreateInfo::usage, it is just internally converted to a set of required and preferred flags. \section choosing_memory_type_explicit_memory_types Explicit memory types If you inspected memory types available on the physical device and you have a preference for memory types that you want to use, you can fill member VmaAllocationCreateInfo::memoryTypeBits. It is a bit mask, where each bit set means that a memory type with that index is allowed to be used for the allocation. Special value 0, just like `UINT32_MAX`, means there are no restrictions to memory type index. Please note that this member is NOT just a memory type index. Still you can use it to choose just one, specific memory type. For example, if you already determined that your buffer should be created in memory type 2, use following code: \code uint32_t memoryTypeIndex = 2; VmaAllocationCreateInfo allocInfo = {}; allocInfo.memoryTypeBits = 1u << memoryTypeIndex; VkBuffer buffer; VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); \endcode \section choosing_memory_type_custom_memory_pools Custom memory pools If you allocate from custom memory pool, all the ways of specifying memory requirements described above are not applicable and the aforementioned members of VmaAllocationCreateInfo structure are ignored. Memory type is selected explicitly when creating the pool and then used to make all the allocations from that pool. For further details, see \ref custom_memory_pools. \section choosing_memory_type_dedicated_allocations Dedicated allocations Memory for allocations is reserved out of larger block of `VkDeviceMemory` allocated from Vulkan internally. That's the main feature of this whole library. You can still request a separate memory block to be created for an allocation, just like you would do in a trivial solution without using any allocator. In that case, a buffer or image is always bound to that memory at offset 0. This is called a "dedicated allocation". You can explicitly request it by using flag #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. The library can also internally decide to use dedicated allocation in some cases, e.g.: - When the size of the allocation is large. - When [VK_KHR_dedicated_allocation](@ref vk_khr_dedicated_allocation) extension is enabled and it reports that dedicated allocation is required or recommended for the resource. - When allocation of next big memory block fails due to not enough device memory, but allocation with the exact requested size succeeds. \page memory_mapping Memory mapping To "map memory" in Vulkan means to obtain a CPU pointer to `VkDeviceMemory`, to be able to read from it or write to it in CPU code. Mapping is possible only of memory allocated from a memory type that has `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag. Functions `vkMapMemory()`, `vkUnmapMemory()` are designed for this purpose. You can use them directly with memory allocated by this library, but it is not recommended because of following issue: Mapping the same `VkDeviceMemory` block multiple times is illegal - only one mapping at a time is allowed. This includes mapping disjoint regions. Mapping is not reference-counted internally by Vulkan. Because of this, Vulkan Memory Allocator provides following facilities: \section memory_mapping_mapping_functions Mapping functions The library provides following functions for mapping of a specific #VmaAllocation: vmaMapMemory(), vmaUnmapMemory(). They are safer and more convenient to use than standard Vulkan functions. You can map an allocation multiple times simultaneously - mapping is reference-counted internally. You can also map different allocations simultaneously regardless of whether they use the same `VkDeviceMemory` block. The way it's implemented is that the library always maps entire memory block, not just region of the allocation. For further details, see description of vmaMapMemory() function. Example: \code // Having these objects initialized: struct ConstantBuffer { ... }; ConstantBuffer constantBufferData; VmaAllocator allocator; VkBuffer constantBuffer; VmaAllocation constantBufferAllocation; // You can map and fill your buffer using following code: void* mappedData; vmaMapMemory(allocator, constantBufferAllocation, &mappedData); memcpy(mappedData, &constantBufferData, sizeof(constantBufferData)); vmaUnmapMemory(allocator, constantBufferAllocation); \endcode When mapping, you may see a warning from Vulkan validation layer similar to this one: <i>Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used.</i> It happens because the library maps entire `VkDeviceMemory` block, where different types of images and buffers may end up together, especially on GPUs with unified memory like Intel. You can safely ignore it if you are sure you access only memory of the intended object that you wanted to map. \section memory_mapping_persistently_mapped_memory Persistently mapped memory Kepping your memory persistently mapped is generally OK in Vulkan. You don't need to unmap it before using its data on the GPU. The library provides a special feature designed for that: Allocations made with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag set in VmaAllocationCreateInfo::flags stay mapped all the time, so you can just access CPU pointer to it any time without a need to call any "map" or "unmap" function. Example: \code VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = sizeof(ConstantBuffer); bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); // Buffer is already mapped. You can access its memory. memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData)); \endcode There are some exceptions though, when you should consider mapping memory only for a short period of time: - When operating system is Windows 7 or 8.x (Windows 10 is not affected because it uses WDDM2), device is discrete AMD GPU, and memory type is the special 256 MiB pool of `DEVICE_LOCAL + HOST_VISIBLE` memory (selected when you use #VMA_MEMORY_USAGE_CPU_TO_GPU), then whenever a memory block allocated from this memory type stays mapped for the time of any call to `vkQueueSubmit()` or `vkQueuePresentKHR()`, this block is migrated by WDDM to system RAM, which degrades performance. It doesn't matter if that particular memory block is actually used by the command buffer being submitted. - On Mac/MoltenVK there is a known bug - [Issue #175](https://github.com/KhronosGroup/MoltenVK/issues/175) which requires unmapping before GPU can see updated texture. - Keeping many large memory blocks mapped may impact performance or stability of some debugging tools. \section memory_mapping_cache_control Cache flush and invalidate Memory in Vulkan doesn't need to be unmapped before using it on GPU, but unless a memory types has `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set, you need to manually **invalidate** cache before reading of mapped pointer and **flush** cache after writing to mapped pointer. Map/unmap operations don't do that automatically. Vulkan provides following functions for this purpose `vkFlushMappedMemoryRanges()`, `vkInvalidateMappedMemoryRanges()`, but this library provides more convenient functions that refer to given allocation object: vmaFlushAllocation(), vmaInvalidateAllocation(). Regions of memory specified for flush/invalidate must be aligned to `VkPhysicalDeviceLimits::nonCoherentAtomSize`. This is automatically ensured by the library. In any memory type that is `HOST_VISIBLE` but not `HOST_COHERENT`, all allocations within blocks are aligned to this value, so their offsets are always multiply of `nonCoherentAtomSize` and two different allocations never share same "line" of this size. Please note that memory allocated with #VMA_MEMORY_USAGE_CPU_ONLY is guaranteed to be `HOST_COHERENT`. Also, Windows drivers from all 3 **PC** GPU vendors (AMD, Intel, NVIDIA) currently provide `HOST_COHERENT` flag on all memory types that are `HOST_VISIBLE`, so on this platform you may not need to bother. \section memory_mapping_finding_if_memory_mappable Finding out if memory is mappable It may happen that your allocation ends up in memory that is `HOST_VISIBLE` (available for mapping) despite it wasn't explicitly requested. For example, application may work on integrated graphics with unified memory (like Intel) or allocation from video memory might have failed, so the library chose system memory as fallback. You can detect this case and map such allocation to access its memory on CPU directly, instead of launching a transfer operation. In order to do that: inspect `allocInfo.memoryType`, call vmaGetMemoryTypeProperties(), and look for `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag in properties of that memory type. \code VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = sizeof(ConstantBuffer); bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); VkMemoryPropertyFlags memFlags; vmaGetMemoryTypeProperties(allocator, allocInfo.memoryType, &memFlags); if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) { // Allocation ended up in mappable memory. You can map it and access it directly. void* mappedData; vmaMapMemory(allocator, alloc, &mappedData); memcpy(mappedData, &constantBufferData, sizeof(constantBufferData)); vmaUnmapMemory(allocator, alloc); } else { // Allocation ended up in non-mappable memory. // You need to create CPU-side buffer in VMA_MEMORY_USAGE_CPU_ONLY and make a transfer. } \endcode You can even use #VMA_ALLOCATION_CREATE_MAPPED_BIT flag while creating allocations that are not necessarily `HOST_VISIBLE` (e.g. using #VMA_MEMORY_USAGE_GPU_ONLY). If the allocation ends up in memory type that is `HOST_VISIBLE`, it will be persistently mapped and you can use it directly. If not, the flag is just ignored. Example: \code VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = sizeof(ConstantBuffer); bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); if(allocInfo.pUserData != nullptr) { // Allocation ended up in mappable memory. // It's persistently mapped. You can access it directly. memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData)); } else { // Allocation ended up in non-mappable memory. // You need to create CPU-side buffer in VMA_MEMORY_USAGE_CPU_ONLY and make a transfer. } \endcode \page staying_within_budget Staying within budget When developing a graphics-intensive game or program, it is important to avoid allocating more GPU memory than it's physically available. When the memory is over-committed, various bad things can happen, depending on the specific GPU, graphics driver, and operating system: - It may just work without any problems. - The application may slow down because some memory blocks are moved to system RAM and the GPU has to access them through PCI Express bus. - A new allocation may take very long time to complete, even few seconds, and possibly freeze entire system. - The new allocation may fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. - It may even result in GPU crash (TDR), observed as `VK_ERROR_DEVICE_LOST` returned somewhere later. \section staying_within_budget_querying_for_budget Querying for budget To query for current memory usage and available budget, use function vmaGetBudget(). Returned structure #VmaBudget contains quantities expressed in bytes, per Vulkan memory heap. Please note that this function returns different information and works faster than vmaCalculateStats(). vmaGetBudget() can be called every frame or even before every allocation, while vmaCalculateStats() is intended to be used rarely, only to obtain statistical information, e.g. for debugging purposes. It is recommended to use <b>VK_EXT_memory_budget</b> device extension to obtain information about the budget from Vulkan device. VMA is able to use this extension automatically. When not enabled, the allocator behaves same way, but then it estimates current usage and available budget based on its internal information and Vulkan memory heap sizes, which may be less precise. In order to use this extension: 1. Make sure extensions VK_EXT_memory_budget and VK_KHR_get_physical_device_properties2 required by it are available and enable them. Please note that the first is a device extension and the second is instance extension! 2. Use flag #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT when creating #VmaAllocator object. 3. Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from Vulkan inside of it to avoid overhead of querying it with every allocation. \section staying_within_budget_controlling_memory_usage Controlling memory usage There are many ways in which you can try to stay within the budget. First, when making new allocation requires allocating a new memory block, the library tries not to exceed the budget automatically. If a block with default recommended size (e.g. 256 MB) would go over budget, a smaller block is allocated, possibly even dedicated memory for just this resource. If the size of the requested resource plus current memory usage is more than the budget, by default the library still tries to create it, leaving it to the Vulkan implementation whether the allocation succeeds or fails. You can change this behavior by using #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag. With it, the allocation is not made if it would exceed the budget or if the budget is already exceeded. Some other allocations become lost instead to make room for it, if the mechanism of [lost allocations](@ref lost_allocations) is used. If that is not possible, the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. Example usage pattern may be to pass the #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag when creating resources that are not essential for the application (e.g. the texture of a specific object) and not to pass it when creating critically important resources (e.g. render targets). Finally, you can also use #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT flag to make sure a new allocation is created only when it fits inside one of the existing memory blocks. If it would require to allocate a new block, if fails instead with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. This also ensures that the function call is very fast because it never goes to Vulkan to obtain a new block. Please note that creating \ref custom_memory_pools with VmaPoolCreateInfo::minBlockCount set to more than 0 will try to allocate memory blocks without checking whether they fit within budget. \page custom_memory_pools Custom memory pools A memory pool contains a number of `VkDeviceMemory` blocks. The library automatically creates and manages default pool for each memory type available on the device. Default memory pool automatically grows in size. Size of allocated blocks is also variable and managed automatically. You can create custom pool and allocate memory out of it. It can be useful if you want to: - Keep certain kind of allocations separate from others. - Enforce particular, fixed size of Vulkan memory blocks. - Limit maximum amount of Vulkan memory allocated for that pool. - Reserve minimum or fixed amount of Vulkan memory always preallocated for that pool. To use custom memory pools: -# Fill VmaPoolCreateInfo structure. -# Call vmaCreatePool() to obtain #VmaPool handle. -# When making an allocation, set VmaAllocationCreateInfo::pool to this handle. You don't need to specify any other parameters of this structure, like `usage`. Example: \code // Create a pool that can have at most 2 blocks, 128 MiB each. VmaPoolCreateInfo poolCreateInfo = {}; poolCreateInfo.memoryTypeIndex = ... poolCreateInfo.blockSize = 128ull * 1024 * 1024; poolCreateInfo.maxBlockCount = 2; VmaPool pool; vmaCreatePool(allocator, &poolCreateInfo, &pool); // Allocate a buffer out of it. VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = 1024; bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.pool = pool; VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); \endcode You have to free all allocations made from this pool before destroying it. \code vmaDestroyBuffer(allocator, buf, alloc); vmaDestroyPool(allocator, pool); \endcode \section custom_memory_pools_MemTypeIndex Choosing memory type index When creating a pool, you must explicitly specify memory type index. To find the one suitable for your buffers or images, you can use helper functions vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo(). You need to provide structures with example parameters of buffers or images that you are going to create in that pool. \code VkBufferCreateInfo exampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; exampleBufCreateInfo.size = 1024; // Whatever. exampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; // Change if needed. VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; // Change if needed. uint32_t memTypeIndex; vmaFindMemoryTypeIndexForBufferInfo(allocator, &exampleBufCreateInfo, &allocCreateInfo, &memTypeIndex); VmaPoolCreateInfo poolCreateInfo = {}; poolCreateInfo.memoryTypeIndex = memTypeIndex; // ... \endcode When creating buffers/images allocated in that pool, provide following parameters: - `VkBufferCreateInfo`: Prefer to pass same parameters as above. Otherwise you risk creating resources in a memory type that is not suitable for them, which may result in undefined behavior. Using different `VK_BUFFER_USAGE_` flags may work, but you shouldn't create images in a pool intended for buffers or the other way around. - VmaAllocationCreateInfo: You don't need to pass same parameters. Fill only `pool` member. Other members are ignored anyway. \section linear_algorithm Linear allocation algorithm Each Vulkan memory block managed by this library has accompanying metadata that keeps track of used and unused regions. By default, the metadata structure and algorithm tries to find best place for new allocations among free regions to optimize memory usage. This way you can allocate and free objects in any order. ![Default allocation algorithm](../gfx/Linear_allocator_1_algo_default.png) Sometimes there is a need to use simpler, linear allocation algorithm. You can create custom pool that uses such algorithm by adding flag #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT to VmaPoolCreateInfo::flags while creating #VmaPool object. Then an alternative metadata management is used. It always creates new allocations after last one and doesn't reuse free regions after allocations freed in the middle. It results in better allocation performance and less memory consumed by metadata. ![Linear allocation algorithm](../gfx/Linear_allocator_2_algo_linear.png) With this one flag, you can create a custom pool that can be used in many ways: free-at-once, stack, double stack, and ring buffer. See below for details. \subsection linear_algorithm_free_at_once Free-at-once In a pool that uses linear algorithm, you still need to free all the allocations individually, e.g. by using vmaFreeMemory() or vmaDestroyBuffer(). You can free them in any order. New allocations are always made after last one - free space in the middle is not reused. However, when you release all the allocation and the pool becomes empty, allocation starts from the beginning again. This way you can use linear algorithm to speed up creation of allocations that you are going to release all at once. ![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png) This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount value that allows multiple memory blocks. \subsection linear_algorithm_stack Stack When you free an allocation that was created last, its space can be reused. Thanks to this, if you always release allocations in the order opposite to their creation (LIFO - Last In First Out), you can achieve behavior of a stack. ![Stack](../gfx/Linear_allocator_4_stack.png) This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount value that allows multiple memory blocks. \subsection linear_algorithm_double_stack Double stack The space reserved by a custom pool with linear algorithm may be used by two stacks: - First, default one, growing up from offset 0. - Second, "upper" one, growing down from the end towards lower offsets. To make allocation from upper stack, add flag #VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT to VmaAllocationCreateInfo::flags. ![Double stack](../gfx/Linear_allocator_7_double_stack.png) Double stack is available only in pools with one memory block - VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined. When the two stacks' ends meet so there is not enough space between them for a new allocation, such allocation fails with usual `VK_ERROR_OUT_OF_DEVICE_MEMORY` error. \subsection linear_algorithm_ring_buffer Ring buffer When you free some allocations from the beginning and there is not enough free space for a new one at the end of a pool, allocator's "cursor" wraps around to the beginning and starts allocation there. Thanks to this, if you always release allocations in the same order as you created them (FIFO - First In First Out), you can achieve behavior of a ring buffer / queue. ![Ring buffer](../gfx/Linear_allocator_5_ring_buffer.png) Pools with linear algorithm support [lost allocations](@ref lost_allocations) when used as ring buffer. If there is not enough free space for a new allocation, but existing allocations from the front of the queue can become lost, they become lost and the allocation succeeds. ![Ring buffer with lost allocations](../gfx/Linear_allocator_6_ring_buffer_lost.png) Ring buffer is available only in pools with one memory block - VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined. \section buddy_algorithm Buddy allocation algorithm There is another allocation algorithm that can be used with custom pools, called "buddy". Its internal data structure is based on a tree of blocks, each having size that is a power of two and a half of its parent's size. When you want to allocate memory of certain size, a free node in the tree is located. If it's too large, it is recursively split into two halves (called "buddies"). However, if requested allocation size is not a power of two, the size of a tree node is aligned up to the nearest power of two and the remaining space is wasted. When two buddy nodes become free, they are merged back into one larger node. ![Buddy allocator](../gfx/Buddy_allocator.png) The advantage of buddy allocation algorithm over default algorithm is faster allocation and deallocation, as well as smaller external fragmentation. The disadvantage is more wasted space (internal fragmentation). For more information, please read ["Buddy memory allocation" on Wikipedia](https://en.wikipedia.org/wiki/Buddy_memory_allocation) or other sources that describe this concept in general. To use buddy allocation algorithm with a custom pool, add flag #VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT to VmaPoolCreateInfo::flags while creating #VmaPool object. Several limitations apply to pools that use buddy algorithm: - It is recommended to use VmaPoolCreateInfo::blockSize that is a power of two. Otherwise, only largest power of two smaller than the size is used for allocations. The remaining space always stays unused. - [Margins](@ref debugging_memory_usage_margins) and [corruption detection](@ref debugging_memory_usage_corruption_detection) don't work in such pools. - [Lost allocations](@ref lost_allocations) don't work in such pools. You can use them, but they never become lost. Support may be added in the future. - [Defragmentation](@ref defragmentation) doesn't work with allocations made from such pool. \page defragmentation Defragmentation Interleaved allocations and deallocations of many objects of varying size can cause fragmentation over time, which can lead to a situation where the library is unable to find a continuous range of free memory for a new allocation despite there is enough free space, just scattered across many small free ranges between existing allocations. To mitigate this problem, you can use defragmentation feature: structure #VmaDefragmentationInfo2, function vmaDefragmentationBegin(), vmaDefragmentationEnd(). Given set of allocations, this function can move them to compact used memory, ensure more continuous free space and possibly also free some `VkDeviceMemory` blocks. What the defragmentation does is: - Updates #VmaAllocation objects to point to new `VkDeviceMemory` and offset. After allocation has been moved, its VmaAllocationInfo::deviceMemory and/or VmaAllocationInfo::offset changes. You must query them again using vmaGetAllocationInfo() if you need them. - Moves actual data in memory. What it doesn't do, so you need to do it yourself: - Recreate buffers and images that were bound to allocations that were defragmented and bind them with their new places in memory. You must use `vkDestroyBuffer()`, `vkDestroyImage()`, `vkCreateBuffer()`, `vkCreateImage()`, vmaBindBufferMemory(), vmaBindImageMemory() for that purpose and NOT vmaDestroyBuffer(), vmaDestroyImage(), vmaCreateBuffer(), vmaCreateImage(), because you don't need to destroy or create allocation objects! - Recreate views and update descriptors that point to these buffers and images. \section defragmentation_cpu Defragmenting CPU memory Following example demonstrates how you can run defragmentation on CPU. Only allocations created in memory types that are `HOST_VISIBLE` can be defragmented. Others are ignored. The way it works is: - It temporarily maps entire memory blocks when necessary. - It moves data using `memmove()` function. \code // Given following variables already initialized: VkDevice device; VmaAllocator allocator; std::vector<VkBuffer> buffers; std::vector<VmaAllocation> allocations; const uint32_t allocCount = (uint32_t)allocations.size(); std::vector<VkBool32> allocationsChanged(allocCount); VmaDefragmentationInfo2 defragInfo = {}; defragInfo.allocationCount = allocCount; defragInfo.pAllocations = allocations.data(); defragInfo.pAllocationsChanged = allocationsChanged.data(); defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; // No limit. defragInfo.maxCpuAllocationsToMove = UINT32_MAX; // No limit. VmaDefragmentationContext defragCtx; vmaDefragmentationBegin(allocator, &defragInfo, nullptr, &defragCtx); vmaDefragmentationEnd(allocator, defragCtx); for(uint32_t i = 0; i < allocCount; ++i) { if(allocationsChanged[i]) { // Destroy buffer that is immutably bound to memory region which is no longer valid. vkDestroyBuffer(device, buffers[i], nullptr); // Create new buffer with same parameters. VkBufferCreateInfo bufferInfo = ...; vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]); // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning. // Bind new buffer to new memory region. Data contained in it is already moved. VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocations[i], &allocInfo); vmaBindBufferMemory(allocator, allocations[i], buffers[i]); } } \endcode Setting VmaDefragmentationInfo2::pAllocationsChanged is optional. This output array tells whether particular allocation in VmaDefragmentationInfo2::pAllocations at the same index has been modified during defragmentation. You can pass null, but you then need to query every allocation passed to defragmentation for new parameters using vmaGetAllocationInfo() if you might need to recreate and rebind a buffer or image associated with it. If you use [Custom memory pools](@ref choosing_memory_type_custom_memory_pools), you can fill VmaDefragmentationInfo2::poolCount and VmaDefragmentationInfo2::pPools instead of VmaDefragmentationInfo2::allocationCount and VmaDefragmentationInfo2::pAllocations to defragment all allocations in given pools. You cannot use VmaDefragmentationInfo2::pAllocationsChanged in that case. You can also combine both methods. \section defragmentation_gpu Defragmenting GPU memory It is also possible to defragment allocations created in memory types that are not `HOST_VISIBLE`. To do that, you need to pass a command buffer that meets requirements as described in VmaDefragmentationInfo2::commandBuffer. The way it works is: - It creates temporary buffers and binds them to entire memory blocks when necessary. - It issues `vkCmdCopyBuffer()` to passed command buffer. Example: \code // Given following variables already initialized: VkDevice device; VmaAllocator allocator; VkCommandBuffer commandBuffer; std::vector<VkBuffer> buffers; std::vector<VmaAllocation> allocations; const uint32_t allocCount = (uint32_t)allocations.size(); std::vector<VkBool32> allocationsChanged(allocCount); VkCommandBufferBeginInfo cmdBufBeginInfo = ...; vkBeginCommandBuffer(commandBuffer, &cmdBufBeginInfo); VmaDefragmentationInfo2 defragInfo = {}; defragInfo.allocationCount = allocCount; defragInfo.pAllocations = allocations.data(); defragInfo.pAllocationsChanged = allocationsChanged.data(); defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; // Notice it's "GPU" this time. defragInfo.maxGpuAllocationsToMove = UINT32_MAX; // Notice it's "GPU" this time. defragInfo.commandBuffer = commandBuffer; VmaDefragmentationContext defragCtx; vmaDefragmentationBegin(allocator, &defragInfo, nullptr, &defragCtx); vkEndCommandBuffer(commandBuffer); // Submit commandBuffer. // Wait for a fence that ensures commandBuffer execution finished. vmaDefragmentationEnd(allocator, defragCtx); for(uint32_t i = 0; i < allocCount; ++i) { if(allocationsChanged[i]) { // Destroy buffer that is immutably bound to memory region which is no longer valid. vkDestroyBuffer(device, buffers[i], nullptr); // Create new buffer with same parameters. VkBufferCreateInfo bufferInfo = ...; vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]); // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning. // Bind new buffer to new memory region. Data contained in it is already moved. VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocations[i], &allocInfo); vmaBindBufferMemory(allocator, allocations[i], buffers[i]); } } \endcode You can combine these two methods by specifying non-zero `maxGpu*` as well as `maxCpu*` parameters. The library automatically chooses best method to defragment each memory pool. You may try not to block your entire program to wait until defragmentation finishes, but do it in the background, as long as you carefully fullfill requirements described in function vmaDefragmentationBegin(). \section defragmentation_additional_notes Additional notes It is only legal to defragment allocations bound to: - buffers - images created with `VK_IMAGE_CREATE_ALIAS_BIT`, `VK_IMAGE_TILING_LINEAR`, and being currently in `VK_IMAGE_LAYOUT_GENERAL` or `VK_IMAGE_LAYOUT_PREINITIALIZED`. Defragmentation of images created with `VK_IMAGE_TILING_OPTIMAL` or in any other layout may give undefined results. If you defragment allocations bound to images, new images to be bound to new memory region after defragmentation should be created with `VK_IMAGE_LAYOUT_PREINITIALIZED` and then transitioned to their original layout from before defragmentation if needed using an image memory barrier. While using defragmentation, you may experience validation layer warnings, which you just need to ignore. See [Validation layer warnings](@ref general_considerations_validation_layer_warnings). Please don't expect memory to be fully compacted after defragmentation. Algorithms inside are based on some heuristics that try to maximize number of Vulkan memory blocks to make totally empty to release them, as well as to maximimze continuous empty space inside remaining blocks, while minimizing the number and size of allocations that need to be moved. Some fragmentation may still remain - this is normal. \section defragmentation_custom_algorithm Writing custom defragmentation algorithm If you want to implement your own, custom defragmentation algorithm, there is infrastructure prepared for that, but it is not exposed through the library API - you need to hack its source code. Here are steps needed to do this: -# Main thing you need to do is to define your own class derived from base abstract class `VmaDefragmentationAlgorithm` and implement your version of its pure virtual methods. See definition and comments of this class for details. -# Your code needs to interact with device memory block metadata. If you need more access to its data than it's provided by its public interface, declare your new class as a friend class e.g. in class `VmaBlockMetadata_Generic`. -# If you want to create a flag that would enable your algorithm or pass some additional flags to configure it, add them to `VmaDefragmentationFlagBits` and use them in VmaDefragmentationInfo2::flags. -# Modify function `VmaBlockVectorDefragmentationContext::Begin` to create object of your new class whenever needed. \page lost_allocations Lost allocations If your game oversubscribes video memory, if may work OK in previous-generation graphics APIs (DirectX 9, 10, 11, OpenGL) because resources are automatically paged to system RAM. In Vulkan you can't do it because when you run out of memory, an allocation just fails. If you have more data (e.g. textures) that can fit into VRAM and you don't need it all at once, you may want to upload them to GPU on demand and "push out" ones that are not used for a long time to make room for the new ones, effectively using VRAM (or a cartain memory pool) as a form of cache. Vulkan Memory Allocator can help you with that by supporting a concept of "lost allocations". To create an allocation that can become lost, include #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag in VmaAllocationCreateInfo::flags. Before using a buffer or image bound to such allocation in every new frame, you need to query it if it's not lost. To check it, call vmaTouchAllocation(). If the allocation is lost, you should not use it or buffer/image bound to it. You mustn't forget to destroy this allocation and this buffer/image. vmaGetAllocationInfo() can also be used for checking status of the allocation. Allocation is lost when returned VmaAllocationInfo::deviceMemory == `VK_NULL_HANDLE`. To create an allocation that can make some other allocations lost to make room for it, use #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag. You will usually use both flags #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT and #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT at the same time. Warning! Current implementation uses quite naive, brute force algorithm, which can make allocation calls that use #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag quite slow. A new, more optimal algorithm and data structure to speed this up is planned for the future. <b>Q: When interleaving creation of new allocations with usage of existing ones, how do you make sure that an allocation won't become lost while it's used in the current frame?</b> It is ensured because vmaTouchAllocation() / vmaGetAllocationInfo() not only returns allocation status/parameters and checks whether it's not lost, but when it's not, it also atomically marks it as used in the current frame, which makes it impossible to become lost in that frame. It uses lockless algorithm, so it works fast and doesn't involve locking any internal mutex. <b>Q: What if my allocation may still be in use by the GPU when it's rendering a previous frame while I already submit new frame on the CPU?</b> You can make sure that allocations "touched" by vmaTouchAllocation() / vmaGetAllocationInfo() will not become lost for a number of additional frames back from the current one by specifying this number as VmaAllocatorCreateInfo::frameInUseCount (for default memory pool) and VmaPoolCreateInfo::frameInUseCount (for custom pool). <b>Q: How do you inform the library when new frame starts?</b> You need to call function vmaSetCurrentFrameIndex(). Example code: \code struct MyBuffer { VkBuffer m_Buf = nullptr; VmaAllocation m_Alloc = nullptr; // Called when the buffer is really needed in the current frame. void EnsureBuffer(); }; void MyBuffer::EnsureBuffer() { // Buffer has been created. if(m_Buf != VK_NULL_HANDLE) { // Check if its allocation is not lost + mark it as used in current frame. if(vmaTouchAllocation(allocator, m_Alloc)) { // It's all OK - safe to use m_Buf. return; } } // Buffer not yet exists or lost - destroy and recreate it. vmaDestroyBuffer(allocator, m_Buf, m_Alloc); VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufCreateInfo.size = 1024; bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &m_Buf, &m_Alloc, nullptr); } \endcode When using lost allocations, you may see some Vulkan validation layer warnings about overlapping regions of memory bound to different kinds of buffers and images. This is still valid as long as you implement proper handling of lost allocations (like in the example above) and don't use them. You can create an allocation that is already in lost state from the beginning using function vmaCreateLostAllocation(). It may be useful if you need a "dummy" allocation that is not null. You can call function vmaMakePoolAllocationsLost() to set all eligible allocations in a specified custom pool to lost state. Allocations that have been "touched" in current frame or VmaPoolCreateInfo::frameInUseCount frames back cannot become lost. <b>Q: Can I touch allocation that cannot become lost?</b> Yes, although it has no visible effect. Calls to vmaGetAllocationInfo() and vmaTouchAllocation() update last use frame index also for allocations that cannot become lost, but the only way to observe it is to dump internal allocator state using vmaBuildStatsString(). You can use this feature for debugging purposes to explicitly mark allocations that you use in current frame and then analyze JSON dump to see for how long each allocation stays unused. \page statistics Statistics This library contains functions that return information about its internal state, especially the amount of memory allocated from Vulkan. Please keep in mind that these functions need to traverse all internal data structures to gather these information, so they may be quite time-consuming. Don't call them too often. \section statistics_numeric_statistics Numeric statistics You can query for overall statistics of the allocator using function vmaCalculateStats(). Information are returned using structure #VmaStats. It contains #VmaStatInfo - number of allocated blocks, number of allocations (occupied ranges in these blocks), number of unused (free) ranges in these blocks, number of bytes used and unused (but still allocated from Vulkan) and other information. They are summed across memory heaps, memory types and total for whole allocator. You can query for statistics of a custom pool using function vmaGetPoolStats(). Information are returned using structure #VmaPoolStats. You can query for information about specific allocation using function vmaGetAllocationInfo(). It fill structure #VmaAllocationInfo. \section statistics_json_dump JSON dump You can dump internal state of the allocator to a string in JSON format using function vmaBuildStatsString(). The result is guaranteed to be correct JSON. It uses ANSI encoding. Any strings provided by user (see [Allocation names](@ref allocation_names)) are copied as-is and properly escaped for JSON, so if they use UTF-8, ISO-8859-2 or any other encoding, this JSON string can be treated as using this encoding. It must be freed using function vmaFreeStatsString(). The format of this JSON string is not part of official documentation of the library, but it will not change in backward-incompatible way without increasing library major version number and appropriate mention in changelog. The JSON string contains all the data that can be obtained using vmaCalculateStats(). It can also contain detailed map of allocated memory blocks and their regions - free and occupied by allocations. This allows e.g. to visualize the memory or assess fragmentation. \page allocation_annotation Allocation names and user data \section allocation_user_data Allocation user data You can annotate allocations with your own information, e.g. for debugging purposes. To do that, fill VmaAllocationCreateInfo::pUserData field when creating an allocation. It's an opaque `void*` pointer. You can use it e.g. as a pointer, some handle, index, key, ordinal number or any other value that would associate the allocation with your custom metadata. \code VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; // Fill bufferInfo... MyBufferMetadata* pMetadata = CreateBufferMetadata(); VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.pUserData = pMetadata; VkBuffer buffer; VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, nullptr); \endcode The pointer may be later retrieved as VmaAllocationInfo::pUserData: \code VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocation, &allocInfo); MyBufferMetadata* pMetadata = (MyBufferMetadata*)allocInfo.pUserData; \endcode It can also be changed using function vmaSetAllocationUserData(). Values of (non-zero) allocations' `pUserData` are printed in JSON report created by vmaBuildStatsString(), in hexadecimal form. \section allocation_names Allocation names There is alternative mode available where `pUserData` pointer is used to point to a null-terminated string, giving a name to the allocation. To use this mode, set #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT flag in VmaAllocationCreateInfo::flags. Then `pUserData` passed as VmaAllocationCreateInfo::pUserData or argument to vmaSetAllocationUserData() must be either null or pointer to a null-terminated string. The library creates internal copy of the string, so the pointer you pass doesn't need to be valid for whole lifetime of the allocation. You can free it after the call. \code VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; // Fill imageInfo... std::string imageName = "Texture: "; imageName += fileName; VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT; allocCreateInfo.pUserData = imageName.c_str(); VkImage image; VmaAllocation allocation; vmaCreateImage(allocator, &imageInfo, &allocCreateInfo, &image, &allocation, nullptr); \endcode The value of `pUserData` pointer of the allocation will be different than the one you passed when setting allocation's name - pointing to a buffer managed internally that holds copy of the string. \code VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocation, &allocInfo); const char* imageName = (const char*)allocInfo.pUserData; printf("Image name: %s\n", imageName); \endcode That string is also printed in JSON report created by vmaBuildStatsString(). \page debugging_memory_usage Debugging incorrect memory usage If you suspect a bug with memory usage, like usage of uninitialized memory or memory being overwritten out of bounds of an allocation, you can use debug features of this library to verify this. \section debugging_memory_usage_initialization Memory initialization If you experience a bug with incorrect and nondeterministic data in your program and you suspect uninitialized memory to be used, you can enable automatic memory initialization to verify this. To do it, define macro `VMA_DEBUG_INITIALIZE_ALLOCATIONS` to 1. \code #define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 #include "vk_mem_alloc.h" \endcode It makes memory of all new allocations initialized to bit pattern `0xDCDCDCDC`. Before an allocation is destroyed, its memory is filled with bit pattern `0xEFEFEFEF`. Memory is automatically mapped and unmapped if necessary. If you find these values while debugging your program, good chances are that you incorrectly read Vulkan memory that is allocated but not initialized, or already freed, respectively. Memory initialization works only with memory types that are `HOST_VISIBLE`. It works also with dedicated allocations. It doesn't work with allocations created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, as they cannot be mapped. \section debugging_memory_usage_margins Margins By default, allocations are laid out in memory blocks next to each other if possible (considering required alignment, `bufferImageGranularity`, and `nonCoherentAtomSize`). ![Allocations without margin](../gfx/Margins_1.png) Define macro `VMA_DEBUG_MARGIN` to some non-zero value (e.g. 16) to enforce specified number of bytes as a margin before and after every allocation. \code #define VMA_DEBUG_MARGIN 16 #include "vk_mem_alloc.h" \endcode ![Allocations with margin](../gfx/Margins_2.png) If your bug goes away after enabling margins, it means it may be caused by memory being overwritten outside of allocation boundaries. It is not 100% certain though. Change in application behavior may also be caused by different order and distribution of allocations across memory blocks after margins are applied. The margin is applied also before first and after last allocation in a block. It may occur only once between two adjacent allocations. Margins work with all types of memory. Margin is applied only to allocations made out of memory blocks and not to dedicated allocations, which have their own memory block of specific size. It is thus not applied to allocations made using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT flag or those automatically decided to put into dedicated allocations, e.g. due to its large size or recommended by VK_KHR_dedicated_allocation extension. Margins are also not active in custom pools created with #VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT flag. Margins appear in [JSON dump](@ref statistics_json_dump) as part of free space. Note that enabling margins increases memory usage and fragmentation. \section debugging_memory_usage_corruption_detection Corruption detection You can additionally define macro `VMA_DEBUG_DETECT_CORRUPTION` to 1 to enable validation of contents of the margins. \code #define VMA_DEBUG_MARGIN 16 #define VMA_DEBUG_DETECT_CORRUPTION 1 #include "vk_mem_alloc.h" \endcode When this feature is enabled, number of bytes specified as `VMA_DEBUG_MARGIN` (it must be multiply of 4) before and after every allocation is filled with a magic number. This idea is also know as "canary". Memory is automatically mapped and unmapped if necessary. This number is validated automatically when the allocation is destroyed. If it's not equal to the expected value, `VMA_ASSERT()` is executed. It clearly means that either CPU or GPU overwritten the memory outside of boundaries of the allocation, which indicates a serious bug. You can also explicitly request checking margins of all allocations in all memory blocks that belong to specified memory types by using function vmaCheckCorruption(), or in memory blocks that belong to specified custom pool, by using function vmaCheckPoolCorruption(). Margin validation (corruption detection) works only for memory types that are `HOST_VISIBLE` and `HOST_COHERENT`. \page record_and_replay Record and replay \section record_and_replay_introduction Introduction While using the library, sequence of calls to its functions together with their parameters can be recorded to a file and later replayed using standalone player application. It can be useful to: - Test correctness - check if same sequence of calls will not cause crash or failures on a target platform. - Gather statistics - see number of allocations, peak memory usage, number of calls etc. - Benchmark performance - see how much time it takes to replay the whole sequence. \section record_and_replay_usage Usage Recording functionality is disabled by default. To enable it, define following macro before every include of this library: \code #define VMA_RECORDING_ENABLED 1 \endcode <b>To record sequence of calls to a file:</b> Fill in VmaAllocatorCreateInfo::pRecordSettings member while creating #VmaAllocator object. File is opened and written during whole lifetime of the allocator. <b>To replay file:</b> Use VmaReplay - standalone command-line program. Precompiled binary can be found in "bin" directory. Its source can be found in "src/VmaReplay" directory. Its project is generated by Premake. Command line syntax is printed when the program is launched without parameters. Basic usage: VmaReplay.exe MyRecording.csv <b>Documentation of file format</b> can be found in file: "docs/Recording file format.md". It's a human-readable, text file in CSV format (Comma Separated Values). \section record_and_replay_additional_considerations Additional considerations - Replaying file that was recorded on a different GPU (with different parameters like `bufferImageGranularity`, `nonCoherentAtomSize`, and especially different set of memory heaps and types) may give different performance and memory usage results, as well as issue some warnings and errors. - Current implementation of recording in VMA, as well as VmaReplay application, is coded and tested only on Windows. Inclusion of recording code is driven by `VMA_RECORDING_ENABLED` macro. Support for other platforms should be easy to add. Contributions are welcomed. \page usage_patterns Recommended usage patterns See also slides from talk: [Sawicki, Adam. Advanced Graphics Techniques Tutorial: Memory management in Vulkan and DX12. Game Developers Conference, 2018](https://www.gdcvault.com/play/1025458/Advanced-Graphics-Techniques-Tutorial-New) \section usage_patterns_common_mistakes Common mistakes <b>Use of CPU_TO_GPU instead of CPU_ONLY memory</b> #VMA_MEMORY_USAGE_CPU_TO_GPU is recommended only for resources that will be mapped and written by the CPU, as well as read directly by the GPU - like some buffers or textures updated every frame (dynamic). If you create a staging copy of a resource to be written by CPU and then used as a source of transfer to another resource placed in the GPU memory, that staging resource should be created with #VMA_MEMORY_USAGE_CPU_ONLY. Please read the descriptions of these enums carefully for details. <b>Unnecessary use of custom pools</b> \ref custom_memory_pools may be useful for special purposes - when you want to keep certain type of resources separate e.g. to reserve minimum amount of memory for them, limit maximum amount of memory they can occupy, or make some of them push out the other through the mechanism of \ref lost_allocations. For most resources this is not needed and so it is not recommended to create #VmaPool objects and allocations out of them. Allocating from the default pool is sufficient. \section usage_patterns_simple Simple patterns \subsection usage_patterns_simple_render_targets Render targets <b>When:</b> Any resources that you frequently write and read on GPU, e.g. images used as color attachments (aka "render targets"), depth-stencil attachments, images/buffers used as storage image/buffer (aka "Unordered Access View (UAV)"). <b>What to do:</b> Create them in video memory that is fastest to access from GPU using #VMA_MEMORY_USAGE_GPU_ONLY. Consider using [VK_KHR_dedicated_allocation](@ref vk_khr_dedicated_allocation) extension and/or manually creating them as dedicated allocations using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, especially if they are large or if you plan to destroy and recreate them e.g. when display resolution changes. Prefer to create such resources first and all other GPU resources (like textures and vertex buffers) later. \subsection usage_patterns_simple_immutable_resources Immutable resources <b>When:</b> Any resources that you fill on CPU only once (aka "immutable") or infrequently and then read frequently on GPU, e.g. textures, vertex and index buffers, constant buffers that don't change often. <b>What to do:</b> Create them in video memory that is fastest to access from GPU using #VMA_MEMORY_USAGE_GPU_ONLY. To initialize content of such resource, create a CPU-side (aka "staging") copy of it in system memory - #VMA_MEMORY_USAGE_CPU_ONLY, map it, fill it, and submit a transfer from it to the GPU resource. You can keep the staging copy if you need it for another upload transfer in the future. If you don't, you can destroy it or reuse this buffer for uploading different resource after the transfer finishes. Prefer to create just buffers in system memory rather than images, even for uploading textures. Use `vkCmdCopyBufferToImage()`. Dont use images with `VK_IMAGE_TILING_LINEAR`. \subsection usage_patterns_dynamic_resources Dynamic resources <b>When:</b> Any resources that change frequently (aka "dynamic"), e.g. every frame or every draw call, written on CPU, read on GPU. <b>What to do:</b> Create them using #VMA_MEMORY_USAGE_CPU_TO_GPU. You can map it and write to it directly on CPU, as well as read from it on GPU. This is a more complex situation. Different solutions are possible, and the best one depends on specific GPU type, but you can use this simple approach for the start. Prefer to write to such resource sequentially (e.g. using `memcpy`). Don't perform random access or any reads from it on CPU, as it may be very slow. \subsection usage_patterns_readback Readback <b>When:</b> Resources that contain data written by GPU that you want to read back on CPU, e.g. results of some computations. <b>What to do:</b> Create them using #VMA_MEMORY_USAGE_GPU_TO_CPU. You can write to them directly on GPU, as well as map and read them on CPU. \section usage_patterns_advanced Advanced patterns \subsection usage_patterns_integrated_graphics Detecting integrated graphics You can support integrated graphics (like Intel HD Graphics, AMD APU) better by detecting it in Vulkan. To do it, call `vkGetPhysicalDeviceProperties()`, inspect `VkPhysicalDeviceProperties::deviceType` and look for `VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU`. When you find it, you can assume that memory is unified and all memory types are comparably fast to access from GPU, regardless of `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`. You can then sum up sizes of all available memory heaps and treat them as useful for your GPU resources, instead of only `DEVICE_LOCAL` ones. You can also prefer to create your resources in memory types that are `HOST_VISIBLE` to map them directly instead of submitting explicit transfer (see below). \subsection usage_patterns_direct_vs_transfer Direct access versus transfer For resources that you frequently write on CPU and read on GPU, many solutions are possible: -# Create one copy in video memory using #VMA_MEMORY_USAGE_GPU_ONLY, second copy in system memory using #VMA_MEMORY_USAGE_CPU_ONLY and submit explicit tranfer each time. -# Create just single copy using #VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU, read it directly on GPU. -# Create just single copy using #VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU, read it directly on GPU. Which solution is the most efficient depends on your resource and especially on the GPU. It is best to measure it and then make the decision. Some general recommendations: - On integrated graphics use (2) or (3) to avoid unnecesary time and memory overhead related to using a second copy and making transfer. - For small resources (e.g. constant buffers) use (2). Discrete AMD cards have special 256 MiB pool of video memory that is directly mappable. Even if the resource ends up in system memory, its data may be cached on GPU after first fetch over PCIe bus. - For larger resources (e.g. textures), decide between (1) and (2). You may want to differentiate NVIDIA and AMD, e.g. by looking for memory type that is both `DEVICE_LOCAL` and `HOST_VISIBLE`. When you find it, use (2), otherwise use (1). Similarly, for resources that you frequently write on GPU and read on CPU, multiple solutions are possible: -# Create one copy in video memory using #VMA_MEMORY_USAGE_GPU_ONLY, second copy in system memory using #VMA_MEMORY_USAGE_GPU_TO_CPU and submit explicit tranfer each time. -# Create just single copy using #VMA_MEMORY_USAGE_GPU_TO_CPU, write to it directly on GPU, map it and read it on CPU. You should take some measurements to decide which option is faster in case of your specific resource. If you don't want to specialize your code for specific types of GPUs, you can still make an simple optimization for cases when your resource ends up in mappable memory to use it directly in this case instead of creating CPU-side staging copy. For details see [Finding out if memory is mappable](@ref memory_mapping_finding_if_memory_mappable). \page configuration Configuration Please check "CONFIGURATION SECTION" in the code to find macros that you can define before each include of this file or change directly in this file to provide your own implementation of basic facilities like assert, `min()` and `max()` functions, mutex, atomic etc. The library uses its own implementation of containers by default, but you can switch to using STL containers instead. For example, define `VMA_ASSERT(expr)` before including the library to provide custom implementation of the assertion, compatible with your project. By default it is defined to standard C `assert(expr)` in `_DEBUG` configuration and empty otherwise. \section config_Vulkan_functions Pointers to Vulkan functions The library uses Vulkan functions straight from the `vulkan.h` header by default. If you want to provide your own pointers to these functions, e.g. fetched using `vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`: -# Define `VMA_STATIC_VULKAN_FUNCTIONS 0`. -# Provide valid pointers through VmaAllocatorCreateInfo::pVulkanFunctions. \section custom_memory_allocator Custom host memory allocator If you use custom allocator for CPU memory rather than default operator `new` and `delete` from C++, you can make this library using your allocator as well by filling optional member VmaAllocatorCreateInfo::pAllocationCallbacks. These functions will be passed to Vulkan, as well as used by the library itself to make any CPU-side allocations. \section allocation_callbacks Device memory allocation callbacks The library makes calls to `vkAllocateMemory()` and `vkFreeMemory()` internally. You can setup callbacks to be informed about these calls, e.g. for the purpose of gathering some statistics. To do it, fill optional member VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. \section heap_memory_limit Device heap memory limit When device memory of certain heap runs out of free space, new allocations may fail (returning error code) or they may succeed, silently pushing some existing memory blocks from GPU VRAM to system RAM (which degrades performance). This behavior is implementation-dependant - it depends on GPU vendor and graphics driver. On AMD cards it can be controlled while creating Vulkan device object by using VK_AMD_memory_overallocation_behavior extension, if available. Alternatively, if you want to test how your program behaves with limited amount of Vulkan device memory available without switching your graphics card to one that really has smaller VRAM, you can use a feature of this library intended for this purpose. To do it, fill optional member VmaAllocatorCreateInfo::pHeapSizeLimit. \page vk_khr_dedicated_allocation VK_KHR_dedicated_allocation VK_KHR_dedicated_allocation is a Vulkan extension which can be used to improve performance on some GPUs. It augments Vulkan API with possibility to query driver whether it prefers particular buffer or image to have its own, dedicated allocation (separate `VkDeviceMemory` block) for better efficiency - to be able to do some internal optimizations. The extension is supported by this library. It will be used automatically when enabled. To enable it: 1 . When creating Vulkan device, check if following 2 device extensions are supported (call `vkEnumerateDeviceExtensionProperties()`). If yes, enable them (fill `VkDeviceCreateInfo::ppEnabledExtensionNames`). - VK_KHR_get_memory_requirements2 - VK_KHR_dedicated_allocation If you enabled these extensions: 2 . Use #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag when creating your #VmaAllocator`to inform the library that you enabled required extensions and you want the library to use them. \code allocatorInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; vmaCreateAllocator(&allocatorInfo, &allocator); \endcode That's all. The extension will be automatically used whenever you create a buffer using vmaCreateBuffer() or image using vmaCreateImage(). When using the extension together with Vulkan Validation Layer, you will receive warnings like this: vkBindBufferMemory(): Binding memory to buffer 0x33 but vkGetBufferMemoryRequirements() has not been called on that buffer. It is OK, you should just ignore it. It happens because you use function `vkGetBufferMemoryRequirements2KHR()` instead of standard `vkGetBufferMemoryRequirements()`, while the validation layer seems to be unaware of it. To learn more about this extension, see: - [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html#VK_KHR_dedicated_allocation) - [VK_KHR_dedicated_allocation unofficial manual](http://asawicki.info/articles/VK_KHR_dedicated_allocation.php5) \page general_considerations General considerations \section general_considerations_thread_safety Thread safety - The library has no global state, so separate #VmaAllocator objects can be used independently. There should be no need to create multiple such objects though - one per `VkDevice` is enough. - By default, all calls to functions that take #VmaAllocator as first parameter are safe to call from multiple threads simultaneously because they are synchronized internally when needed. - When the allocator is created with #VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT flag, calls to functions that take such #VmaAllocator object must be synchronized externally. - Access to a #VmaAllocation object must be externally synchronized. For example, you must not call vmaGetAllocationInfo() and vmaMapMemory() from different threads at the same time if you pass the same #VmaAllocation object to these functions. \section general_considerations_validation_layer_warnings Validation layer warnings When using this library, you can meet following types of warnings issued by Vulkan validation layer. They don't necessarily indicate a bug, so you may need to just ignore them. - *vkBindBufferMemory(): Binding memory to buffer 0xeb8e4 but vkGetBufferMemoryRequirements() has not been called on that buffer.* - It happens when VK_KHR_dedicated_allocation extension is enabled. `vkGetBufferMemoryRequirements2KHR` function is used instead, while validation layer seems to be unaware of it. - *Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used.* - It happens when you map a buffer or image, because the library maps entire `VkDeviceMemory` block, where different types of images and buffers may end up together, especially on GPUs with unified memory like Intel. - *Non-linear image 0xebc91 is aliased with linear buffer 0xeb8e4 which may indicate a bug.* - It happens when you use lost allocations, and a new image or buffer is created in place of an existing object that bacame lost. - It may happen also when you use [defragmentation](@ref defragmentation). \section general_considerations_allocation_algorithm Allocation algorithm The library uses following algorithm for allocation, in order: -# Try to find free range of memory in existing blocks. -# If failed, try to create a new block of `VkDeviceMemory`, with preferred block size. -# If failed, try to create such block with size/2, size/4, size/8. -# If failed and #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag was specified, try to find space in existing blocks, possilby making some other allocations lost. -# If failed, try to allocate separate `VkDeviceMemory` for this allocation, just like when you use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. -# If failed, choose other memory type that meets the requirements specified in VmaAllocationCreateInfo and go to point 1. -# If failed, return `VK_ERROR_OUT_OF_DEVICE_MEMORY`. \section general_considerations_features_not_supported Features not supported Features deliberately excluded from the scope of this library: - Data transfer. Uploading (straming) and downloading data of buffers and images between CPU and GPU memory and related synchronization is responsibility of the user. Defining some "texture" object that would automatically stream its data from a staging copy in CPU memory to GPU memory would rather be a feature of another, higher-level library implemented on top of VMA. - Allocations for imported/exported external memory. They tend to require explicit memory type index and dedicated allocation anyway, so they don't interact with main features of this library. Such special purpose allocations should be made manually, using `vkCreateBuffer()` and `vkAllocateMemory()`. - Recreation of buffers and images. Although the library has functions for buffer and image creation (vmaCreateBuffer(), vmaCreateImage()), you need to recreate these objects yourself after defragmentation. That's because the big structures `VkBufferCreateInfo`, `VkImageCreateInfo` are not stored in #VmaAllocation object. - Handling CPU memory allocation failures. When dynamically creating small C++ objects in CPU memory (not Vulkan memory), allocation failures are not checked and handled gracefully, because that would complicate code significantly and is usually not needed in desktop PC applications anyway. - Code free of any compiler warnings. Maintaining the library to compile and work correctly on so many different platforms is hard enough. Being free of any warnings, on any version of any compiler, is simply not feasible. - This is a C++ library with C interface. Bindings or ports to any other programming languages are welcomed as external projects and are not going to be included into this repository. */ /* Define this macro to 0/1 to disable/enable support for recording functionality, available through VmaAllocatorCreateInfo::pRecordSettings. */ #ifndef VMA_RECORDING_ENABLED #define VMA_RECORDING_ENABLED 0 #endif #ifndef NOMINMAX #define NOMINMAX // For windows.h #endif #ifndef VULKAN_H_ #include <vulkan/vulkan.h> #endif #if VMA_RECORDING_ENABLED #include <windows.h> #endif // Define this macro to declare maximum supported Vulkan version in format AAABBBCCC, // where AAA = major, BBB = minor, CCC = patch. // If you want to use version > 1.0, it still needs to be enabled via VmaAllocatorCreateInfo::vulkanApiVersion. #if !defined(VMA_VULKAN_VERSION) #if defined(VK_VERSION_1_1) #define VMA_VULKAN_VERSION 1001000 #else #define VMA_VULKAN_VERSION 1000000 #endif #endif #if !defined(VMA_DEDICATED_ALLOCATION) #if VK_KHR_get_memory_requirements2 && VK_KHR_dedicated_allocation #define VMA_DEDICATED_ALLOCATION 1 #else #define VMA_DEDICATED_ALLOCATION 0 #endif #endif #if !defined(VMA_BIND_MEMORY2) #if VK_KHR_bind_memory2 #define VMA_BIND_MEMORY2 1 #else #define VMA_BIND_MEMORY2 0 #endif #endif #if !defined(VMA_MEMORY_BUDGET) #if VK_EXT_memory_budget && (VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000) #define VMA_MEMORY_BUDGET 1 #else #define VMA_MEMORY_BUDGET 0 #endif #endif // Define these macros to decorate all public functions with additional code, // before and after returned type, appropriately. This may be useful for // exporing the functions when compiling VMA as a separate library. Example: // #define VMA_CALL_PRE __declspec(dllexport) // #define VMA_CALL_POST __cdecl #ifndef VMA_CALL_PRE #define VMA_CALL_PRE #endif #ifndef VMA_CALL_POST #define VMA_CALL_POST #endif /** \struct VmaAllocator \brief Represents main object of this library initialized. Fill structure #VmaAllocatorCreateInfo and call function vmaCreateAllocator() to create it. Call function vmaDestroyAllocator() to destroy it. It is recommended to create just one object of this type per `VkDevice` object, right after Vulkan is initialized and keep it alive until before Vulkan device is destroyed. */ VK_DEFINE_HANDLE(VmaAllocator) /// Callback function called after successful vkAllocateMemory. typedef void (VKAPI_PTR* PFN_vmaAllocateDeviceMemoryFunction)( VmaAllocator allocator, uint32_t memoryType, VkDeviceMemory memory, VkDeviceSize size); /// Callback function called before vkFreeMemory. typedef void (VKAPI_PTR* PFN_vmaFreeDeviceMemoryFunction)( VmaAllocator allocator, uint32_t memoryType, VkDeviceMemory memory, VkDeviceSize size); /** \brief Set of callbacks that the library will call for `vkAllocateMemory` and `vkFreeMemory`. Provided for informative purpose, e.g. to gather statistics about number of allocations or total amount of memory allocated in Vulkan. Used in VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. */ typedef struct VmaDeviceMemoryCallbacks { /// Optional, can be null. PFN_vmaAllocateDeviceMemoryFunction pfnAllocate; /// Optional, can be null. PFN_vmaFreeDeviceMemoryFunction pfnFree; } VmaDeviceMemoryCallbacks; /// Flags for created #VmaAllocator. typedef enum VmaAllocatorCreateFlagBits { /** \brief Allocator and all objects created from it will not be synchronized internally, so you must guarantee they are used from only one thread at a time or synchronized externally by you. Using this flag may increase performance because internal mutexes are not used. */ VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT = 0x00000001, /** \brief Enables usage of VK_KHR_dedicated_allocation extension. The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. Using this extenion will automatically allocate dedicated blocks of memory for some buffers and images instead of suballocating place for them out of bigger memory blocks (as if you explicitly used #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT flag) when it is recommended by the driver. It may improve performance on some GPUs. You may set this flag only if you found out that following device extensions are supported, you enabled them while creating Vulkan device passed as VmaAllocatorCreateInfo::device, and you want them to be used internally by this library: - VK_KHR_get_memory_requirements2 (device extension) - VK_KHR_dedicated_allocation (device extension) When this flag is set, you can experience following warnings reported by Vulkan validation layer. You can ignore them. > vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer. */ VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT = 0x00000002, /** Enables usage of VK_KHR_bind_memory2 extension. The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. You may set this flag only if you found out that this device extension is supported, you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, and you want it to be used internally by this library. The extension provides functions `vkBindBufferMemory2KHR` and `vkBindImageMemory2KHR`, which allow to pass a chain of `pNext` structures while binding. This flag is required if you use `pNext` parameter in vmaBindBufferMemory2() or vmaBindImageMemory2(). */ VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT = 0x00000004, /** Enables usage of VK_EXT_memory_budget extension. You may set this flag only if you found out that this device extension is supported, you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, and you want it to be used internally by this library, along with another instance extension VK_KHR_get_physical_device_properties2, which is required by it (or Vulkan 1.1, where this extension is promoted). The extension provides query for current memory usage and budget, which will probably be more accurate than an estimation used by the library otherwise. */ VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT = 0x00000008, VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaAllocatorCreateFlagBits; typedef VkFlags VmaAllocatorCreateFlags; /** \brief Pointers to some Vulkan functions - a subset used by the library. Used in VmaAllocatorCreateInfo::pVulkanFunctions. */ typedef struct VmaVulkanFunctions { PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; PFN_vkAllocateMemory vkAllocateMemory; PFN_vkFreeMemory vkFreeMemory; PFN_vkMapMemory vkMapMemory; PFN_vkUnmapMemory vkUnmapMemory; PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; PFN_vkBindBufferMemory vkBindBufferMemory; PFN_vkBindImageMemory vkBindImageMemory; PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; PFN_vkCreateBuffer vkCreateBuffer; PFN_vkDestroyBuffer vkDestroyBuffer; PFN_vkCreateImage vkCreateImage; PFN_vkDestroyImage vkDestroyImage; PFN_vkCmdCopyBuffer vkCmdCopyBuffer; #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR; PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR; #endif #if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 PFN_vkBindBufferMemory2KHR vkBindBufferMemory2KHR; PFN_vkBindImageMemory2KHR vkBindImageMemory2KHR; #endif #if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 PFN_vkGetPhysicalDeviceMemoryProperties2KHR vkGetPhysicalDeviceMemoryProperties2KHR; #endif } VmaVulkanFunctions; /// Flags to be used in VmaRecordSettings::flags. typedef enum VmaRecordFlagBits { /** \brief Enables flush after recording every function call. Enable it if you expect your application to crash, which may leave recording file truncated. It may degrade performance though. */ VMA_RECORD_FLUSH_AFTER_CALL_BIT = 0x00000001, VMA_RECORD_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaRecordFlagBits; typedef VkFlags VmaRecordFlags; /// Parameters for recording calls to VMA functions. To be used in VmaAllocatorCreateInfo::pRecordSettings. typedef struct VmaRecordSettings { /// Flags for recording. Use #VmaRecordFlagBits enum. VmaRecordFlags flags; /** \brief Path to the file that should be written by the recording. Suggested extension: "csv". If the file already exists, it will be overwritten. It will be opened for the whole time #VmaAllocator object is alive. If opening this file fails, creation of the whole allocator object fails. */ const char* pFilePath; } VmaRecordSettings; /// Description of a Allocator to be created. typedef struct VmaAllocatorCreateInfo { /// Flags for created allocator. Use #VmaAllocatorCreateFlagBits enum. VmaAllocatorCreateFlags flags; /// Vulkan physical device. /** It must be valid throughout whole lifetime of created allocator. */ VkPhysicalDevice physicalDevice; /// Vulkan device. /** It must be valid throughout whole lifetime of created allocator. */ VkDevice device; /// Preferred size of a single `VkDeviceMemory` block to be allocated from large heaps > 1 GiB. Optional. /** Set to 0 to use default, which is currently 256 MiB. */ VkDeviceSize preferredLargeHeapBlockSize; /// Custom CPU memory allocation callbacks. Optional. /** Optional, can be null. When specified, will also be used for all CPU-side memory allocations. */ const VkAllocationCallbacks* pAllocationCallbacks; /// Informative callbacks for `vkAllocateMemory`, `vkFreeMemory`. Optional. /** Optional, can be null. */ const VmaDeviceMemoryCallbacks* pDeviceMemoryCallbacks; /** \brief Maximum number of additional frames that are in use at the same time as current frame. This value is used only when you make allocations with VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocation cannot become lost if allocation.lastUseFrameIndex >= allocator.currentFrameIndex - frameInUseCount. For example, if you double-buffer your command buffers, so resources used for rendering in previous frame may still be in use by the GPU at the moment you allocate resources needed for the current frame, set this value to 1. If you want to allow any allocations other than used in the current frame to become lost, set this value to 0. */ uint32_t frameInUseCount; /** \brief Either null or a pointer to an array of limits on maximum number of bytes that can be allocated out of particular Vulkan memory heap. If not NULL, it must be a pointer to an array of `VkPhysicalDeviceMemoryProperties::memoryHeapCount` elements, defining limit on maximum number of bytes that can be allocated out of particular Vulkan memory heap. Any of the elements may be equal to `VK_WHOLE_SIZE`, which means no limit on that heap. This is also the default in case of `pHeapSizeLimit` = NULL. If there is a limit defined for a heap: - If user tries to allocate more memory from that heap using this allocator, the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. - If the limit is smaller than heap size reported in `VkMemoryHeap::size`, the value of this limit will be reported instead when using vmaGetMemoryProperties(). Warning! Using this feature may not be equivalent to installing a GPU with smaller amount of memory, because graphics driver doesn't necessary fail new allocations with `VK_ERROR_OUT_OF_DEVICE_MEMORY` result when memory capacity is exceeded. It may return success and just silently migrate some device memory blocks to system RAM. This driver behavior can also be controlled using VK_AMD_memory_overallocation_behavior extension. */ const VkDeviceSize* pHeapSizeLimit; /** \brief Pointers to Vulkan functions. Can be null if you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1`. If you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1` in configuration section, you can pass null as this member, because the library will fetch pointers to Vulkan functions internally in a static way, like: vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; Fill this member if you want to provide your own pointers to Vulkan functions, e.g. fetched using `vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`. */ const VmaVulkanFunctions* pVulkanFunctions; /** \brief Parameters for recording of VMA calls. Can be null. If not null, it enables recording of calls to VMA functions to a file. If support for recording is not enabled using `VMA_RECORDING_ENABLED` macro, creation of the allocator object fails with `VK_ERROR_FEATURE_NOT_PRESENT`. */ const VmaRecordSettings* pRecordSettings; /** \brief Optional handle to Vulkan instance object. Optional, can be null. Must be set if #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT flas is used or if `vulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)`. */ VkInstance instance; /** \brief Optional. The highest version of Vulkan that the application is designed to use. It must be a value in the format as created by macro `VK_MAKE_VERSION` or a constant like: `VK_API_VERSION_1_1`, `VK_API_VERSION_1_0`. The patch version number specified is ignored. Only the major and minor versions are considered. It must be less or euqal (preferably equal) to value as passed to `vkCreateInstance` as `VkApplicationInfo::apiVersion`. Only versions 1.0 and 1.1 are supported by the current implementation. Leaving it initialized to zero is equivalent to `VK_API_VERSION_1_0`. */ uint32_t vulkanApiVersion; } VmaAllocatorCreateInfo; /// Creates Allocator object. VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( const VmaAllocatorCreateInfo* pCreateInfo, VmaAllocator* pAllocator); /// Destroys allocator object. VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( VmaAllocator allocator); /** PhysicalDeviceProperties are fetched from physicalDevice by the allocator. You can access it here, without fetching it again on your own. */ VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( VmaAllocator allocator, const VkPhysicalDeviceProperties** ppPhysicalDeviceProperties); /** PhysicalDeviceMemoryProperties are fetched from physicalDevice by the allocator. You can access it here, without fetching it again on your own. */ VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( VmaAllocator allocator, const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties); /** \brief Given Memory Type Index, returns Property Flags of this memory type. This is just a convenience function. Same information can be obtained using vmaGetMemoryProperties(). */ VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( VmaAllocator allocator, uint32_t memoryTypeIndex, VkMemoryPropertyFlags* pFlags); /** \brief Sets index of the current frame. This function must be used if you make allocations with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT and #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flags to inform the allocator when a new frame begins. Allocations queried using vmaGetAllocationInfo() cannot become lost in the current frame. */ VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( VmaAllocator allocator, uint32_t frameIndex); /** \brief Calculated statistics of memory usage in entire allocator. */ typedef struct VmaStatInfo { /// Number of `VkDeviceMemory` Vulkan memory blocks allocated. uint32_t blockCount; /// Number of #VmaAllocation allocation objects allocated. uint32_t allocationCount; /// Number of free ranges of memory between allocations. uint32_t unusedRangeCount; /// Total number of bytes occupied by all allocations. VkDeviceSize usedBytes; /// Total number of bytes occupied by unused ranges. VkDeviceSize unusedBytes; VkDeviceSize allocationSizeMin, allocationSizeAvg, allocationSizeMax; VkDeviceSize unusedRangeSizeMin, unusedRangeSizeAvg, unusedRangeSizeMax; } VmaStatInfo; /// General statistics from current state of Allocator. typedef struct VmaStats { VmaStatInfo memoryType[VK_MAX_MEMORY_TYPES]; VmaStatInfo memoryHeap[VK_MAX_MEMORY_HEAPS]; VmaStatInfo total; } VmaStats; /** \brief Retrieves statistics from current state of the Allocator. This function is called "calculate" not "get" because it has to traverse all internal data structures, so it may be quite slow. For faster but more brief statistics suitable to be called every frame or every allocation, use vmaGetBudget(). Note that when using allocator from multiple threads, returned information may immediately become outdated. */ VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats( VmaAllocator allocator, VmaStats* pStats); /** \brief Statistics of current memory usage and available budget, in bytes, for specific memory heap. */ typedef struct VmaBudget { /** \brief Sum size of all `VkDeviceMemory` blocks allocated from particular heap, in bytes. */ VkDeviceSize blockBytes; /** \brief Sum size of all allocations created in particular heap, in bytes. Usually less or equal than `blockBytes`. Difference `blockBytes - allocationBytes` is the amount of memory allocated but unused - available for new allocations or wasted due to fragmentation. It might be greater than `blockBytes` if there are some allocations in lost state, as they account to this value as well. */ VkDeviceSize allocationBytes; /** \brief Estimated current memory usage of the program, in bytes. Fetched from system using `VK_EXT_memory_budget` extension if enabled. It might be different than `blockBytes` (usually higher) due to additional implicit objects also occupying the memory, like swapchain, pipelines, descriptor heaps, command buffers, or `VkDeviceMemory` blocks allocated outside of this library, if any. */ VkDeviceSize usage; /** \brief Estimated amount of memory available to the program, in bytes. Fetched from system using `VK_EXT_memory_budget` extension if enabled. It might be different (most probably smaller) than `VkMemoryHeap::size[heapIndex]` due to factors external to the program, like other programs also consuming system resources. Difference `budget - usage` is the amount of additional memory that can probably be allocated without problems. Exceeding the budget may result in various problems. */ VkDeviceSize budget; } VmaBudget; /** \brief Retrieves information about current memory budget for all memory heaps. \param[out] pBudget Must point to array with number of elements at least equal to number of memory heaps in physical device used. This function is called "get" not "calculate" because it is very fast, suitable to be called every frame or every allocation. For more detailed statistics use vmaCalculateStats(). Note that when using allocator from multiple threads, returned information may immediately become outdated. */ VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget( VmaAllocator allocator, VmaBudget* pBudget); #ifndef VMA_STATS_STRING_ENABLED #define VMA_STATS_STRING_ENABLED 1 #endif #if VMA_STATS_STRING_ENABLED /// Builds and returns statistics as string in JSON format. /** @param[out] ppStatsString Must be freed using vmaFreeStatsString() function. */ VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( VmaAllocator allocator, char** ppStatsString, VkBool32 detailedMap); VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( VmaAllocator allocator, char* pStatsString); #endif // #if VMA_STATS_STRING_ENABLED /** \struct VmaPool \brief Represents custom memory pool Fill structure VmaPoolCreateInfo and call function vmaCreatePool() to create it. Call function vmaDestroyPool() to destroy it. For more information see [Custom memory pools](@ref choosing_memory_type_custom_memory_pools). */ VK_DEFINE_HANDLE(VmaPool) typedef enum VmaMemoryUsage { /** No intended memory usage specified. Use other members of VmaAllocationCreateInfo to specify your requirements. */ VMA_MEMORY_USAGE_UNKNOWN = 0, /** Memory will be used on device only, so fast access from the device is preferred. It usually means device-local GPU (video) memory. No need to be mappable on host. It is roughly equivalent of `D3D12_HEAP_TYPE_DEFAULT`. Usage: - Resources written and read by device, e.g. images used as attachments. - Resources transferred from host once (immutable) or infrequently and read by device multiple times, e.g. textures to be sampled, vertex buffers, uniform (constant) buffers, and majority of other types of resources used on GPU. Allocation may still end up in `HOST_VISIBLE` memory on some implementations. In such case, you are free to map it. You can use #VMA_ALLOCATION_CREATE_MAPPED_BIT with this usage type. */ VMA_MEMORY_USAGE_GPU_ONLY = 1, /** Memory will be mappable on host. It usually means CPU (system) memory. Guarantees to be `HOST_VISIBLE` and `HOST_COHERENT`. CPU access is typically uncached. Writes may be write-combined. Resources created in this pool may still be accessible to the device, but access to them can be slow. It is roughly equivalent of `D3D12_HEAP_TYPE_UPLOAD`. Usage: Staging copy of resources used as transfer source. */ VMA_MEMORY_USAGE_CPU_ONLY = 2, /** Memory that is both mappable on host (guarantees to be `HOST_VISIBLE`) and preferably fast to access by GPU. CPU access is typically uncached. Writes may be write-combined. Usage: Resources written frequently by host (dynamic), read by device. E.g. textures, vertex buffers, uniform buffers updated every frame or every draw call. */ VMA_MEMORY_USAGE_CPU_TO_GPU = 3, /** Memory mappable on host (guarantees to be `HOST_VISIBLE`) and cached. It is roughly equivalent of `D3D12_HEAP_TYPE_READBACK`. Usage: - Resources written by device, read by host - results of some computations, e.g. screen capture, average scene luminance for HDR tone mapping. - Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection. */ VMA_MEMORY_USAGE_GPU_TO_CPU = 4, /** CPU memory - memory that is preferably not `DEVICE_LOCAL`, but also not guaranteed to be `HOST_VISIBLE`. Usage: Staging copy of resources moved from GPU memory to CPU memory as part of custom paging/residency mechanism, to be moved back to GPU memory when needed. */ VMA_MEMORY_USAGE_CPU_COPY = 5, /** Lazily allocated GPU memory having `VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT`. Exists mostly on mobile platforms. Using it on desktop PC or other GPUs with no such memory type present will fail the allocation. Usage: Memory for transient attachment images (color attachments, depth attachments etc.), created with `VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`. Allocations with this usage are always created as dedicated - it implies #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. */ VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED = 6, VMA_MEMORY_USAGE_MAX_ENUM = 0x7FFFFFFF } VmaMemoryUsage; /// Flags to be passed as VmaAllocationCreateInfo::flags. typedef enum VmaAllocationCreateFlagBits { /** \brief Set this flag if the allocation should have its own memory block. Use it for special, big resources, like fullscreen images used as attachments. You should not use this flag if VmaAllocationCreateInfo::pool is not null. */ VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT = 0x00000001, /** \brief Set this flag to only try to allocate from existing `VkDeviceMemory` blocks and never create new such block. If new allocation cannot be placed in any of the existing blocks, allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY` error. You should not use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT and #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT at the same time. It makes no sense. If VmaAllocationCreateInfo::pool is not null, this flag is implied and ignored. */ VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT = 0x00000002, /** \brief Set this flag to use a memory that will be persistently mapped and retrieve pointer to it. Pointer to mapped memory will be returned through VmaAllocationInfo::pMappedData. Is it valid to use this flag for allocation made from memory type that is not `HOST_VISIBLE`. This flag is then ignored and memory is not mapped. This is useful if you need an allocation that is efficient to use on GPU (`DEVICE_LOCAL`) and still want to map it directly if possible on platforms that support it (e.g. Intel GPU). You should not use this flag together with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT. */ VMA_ALLOCATION_CREATE_MAPPED_BIT = 0x00000004, /** Allocation created with this flag can become lost as a result of another allocation with #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag, so you must check it before use. To check if allocation is not lost, call vmaGetAllocationInfo() and check if VmaAllocationInfo::deviceMemory is not `VK_NULL_HANDLE`. For details about supporting lost allocations, see Lost Allocations chapter of User Guide on Main Page. You should not use this flag together with #VMA_ALLOCATION_CREATE_MAPPED_BIT. */ VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT = 0x00000008, /** While creating allocation using this flag, other allocations that were created with flag #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT can become lost. For details about supporting lost allocations, see Lost Allocations chapter of User Guide on Main Page. */ VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT = 0x00000010, /** Set this flag to treat VmaAllocationCreateInfo::pUserData as pointer to a null-terminated string. Instead of copying pointer value, a local copy of the string is made and stored in allocation's `pUserData`. The string is automatically freed together with the allocation. It is also used in vmaBuildStatsString(). */ VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT = 0x00000020, /** Allocation will be created from upper stack in a double stack pool. This flag is only allowed for custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT flag. */ VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = 0x00000040, /** Create both buffer/image and allocation, but don't bind them together. It is useful when you want to bind yourself to do some more advanced binding, e.g. using some extensions. The flag is meaningful only with functions that bind by default: vmaCreateBuffer(), vmaCreateImage(). Otherwise it is ignored. */ VMA_ALLOCATION_CREATE_DONT_BIND_BIT = 0x00000080, /** Create allocation only if additional device memory required for it, if any, won't exceed memory budget. Otherwise return `VK_ERROR_OUT_OF_DEVICE_MEMORY`. */ VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT = 0x00000100, /** Allocation strategy that chooses smallest possible free range for the allocation. */ VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT = 0x00010000, /** Allocation strategy that chooses biggest possible free range for the allocation. */ VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT = 0x00020000, /** Allocation strategy that chooses first suitable free range for the allocation. "First" doesn't necessarily means the one with smallest offset in memory, but rather the one that is easiest and fastest to find. */ VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT = 0x00040000, /** Allocation strategy that tries to minimize memory usage. */ VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT, /** Allocation strategy that tries to minimize allocation time. */ VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT, /** Allocation strategy that tries to minimize memory fragmentation. */ VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT, /** A bit mask to extract only `STRATEGY` bits from entire set of flags. */ VMA_ALLOCATION_CREATE_STRATEGY_MASK = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT | VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT | VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT, VMA_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaAllocationCreateFlagBits; typedef VkFlags VmaAllocationCreateFlags; typedef struct VmaAllocationCreateInfo { /// Use #VmaAllocationCreateFlagBits enum. VmaAllocationCreateFlags flags; /** \brief Intended usage of memory. You can leave #VMA_MEMORY_USAGE_UNKNOWN if you specify memory requirements in other way. \n If `pool` is not null, this member is ignored. */ VmaMemoryUsage usage; /** \brief Flags that must be set in a Memory Type chosen for an allocation. Leave 0 if you specify memory requirements in other way. \n If `pool` is not null, this member is ignored.*/ VkMemoryPropertyFlags requiredFlags; /** \brief Flags that preferably should be set in a memory type chosen for an allocation. Set to 0 if no additional flags are prefered. \n If `pool` is not null, this member is ignored. */ VkMemoryPropertyFlags preferredFlags; /** \brief Bitmask containing one bit set for every memory type acceptable for this allocation. Value 0 is equivalent to `UINT32_MAX` - it means any memory type is accepted if it meets other requirements specified by this structure, with no further restrictions on memory type index. \n If `pool` is not null, this member is ignored. */ uint32_t memoryTypeBits; /** \brief Pool that this allocation should be created in. Leave `VK_NULL_HANDLE` to allocate from default pool. If not null, members: `usage`, `requiredFlags`, `preferredFlags`, `memoryTypeBits` are ignored. */ VmaPool pool; /** \brief Custom general-purpose pointer that will be stored in #VmaAllocation, can be read as VmaAllocationInfo::pUserData and changed using vmaSetAllocationUserData(). If #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is used, it must be either null or pointer to a null-terminated string. The string will be then copied to internal buffer, so it doesn't need to be valid after allocation call. */ void* pUserData; } VmaAllocationCreateInfo; /** \brief Helps to find memoryTypeIndex, given memoryTypeBits and VmaAllocationCreateInfo. This algorithm tries to find a memory type that: - Is allowed by memoryTypeBits. - Contains all the flags from pAllocationCreateInfo->requiredFlags. - Matches intended usage. - Has as many flags from pAllocationCreateInfo->preferredFlags as possible. \return Returns VK_ERROR_FEATURE_NOT_PRESENT if not found. Receiving such result from this function or any other allocating function probably means that your device doesn't support any memory type with requested features for the specific type of resource you want to use it for. Please check parameters of your resource, like image layout (OPTIMAL versus LINEAR) or mip level count. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( VmaAllocator allocator, uint32_t memoryTypeBits, const VmaAllocationCreateInfo* pAllocationCreateInfo, uint32_t* pMemoryTypeIndex); /** \brief Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo. It can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex. It internally creates a temporary, dummy buffer that never has memory bound. It is just a convenience function, equivalent to calling: - `vkCreateBuffer` - `vkGetBufferMemoryRequirements` - `vmaFindMemoryTypeIndex` - `vkDestroyBuffer` */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, uint32_t* pMemoryTypeIndex); /** \brief Helps to find memoryTypeIndex, given VkImageCreateInfo and VmaAllocationCreateInfo. It can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex. It internally creates a temporary, dummy image that never has memory bound. It is just a convenience function, equivalent to calling: - `vkCreateImage` - `vkGetImageMemoryRequirements` - `vmaFindMemoryTypeIndex` - `vkDestroyImage` */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, uint32_t* pMemoryTypeIndex); /// Flags to be passed as VmaPoolCreateInfo::flags. typedef enum VmaPoolCreateFlagBits { /** \brief Use this flag if you always allocate only buffers and linear images or only optimal images out of this pool and so Buffer-Image Granularity can be ignored. This is an optional optimization flag. If you always allocate using vmaCreateBuffer(), vmaCreateImage(), vmaAllocateMemoryForBuffer(), then you don't need to use it because allocator knows exact type of your allocations so it can handle Buffer-Image Granularity in the optimal way. If you also allocate using vmaAllocateMemoryForImage() or vmaAllocateMemory(), exact type of such allocations is not known, so allocator must be conservative in handling Buffer-Image Granularity, which can lead to suboptimal allocation (wasted memory). In that case, if you can make sure you always allocate only buffers and linear images or only optimal images out of this pool, use this flag to make allocator disregard Buffer-Image Granularity and so make allocations faster and more optimal. */ VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT = 0x00000002, /** \brief Enables alternative, linear allocation algorithm in this pool. Specify this flag to enable linear allocation algorithm, which always creates new allocations after last one and doesn't reuse space from allocations freed in between. It trades memory consumption for simplified algorithm and data structure, which has better performance and uses less memory for metadata. By using this flag, you can achieve behavior of free-at-once, stack, ring buffer, and double stack. For details, see documentation chapter \ref linear_algorithm. When using this flag, you must specify VmaPoolCreateInfo::maxBlockCount == 1 (or 0 for default). For more details, see [Linear allocation algorithm](@ref linear_algorithm). */ VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT = 0x00000004, /** \brief Enables alternative, buddy allocation algorithm in this pool. It operates on a tree of blocks, each having size that is a power of two and a half of its parent's size. Comparing to default algorithm, this one provides faster allocation and deallocation and decreased external fragmentation, at the expense of more memory wasted (internal fragmentation). For more details, see [Buddy allocation algorithm](@ref buddy_algorithm). */ VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT = 0x00000008, /** Bit mask to extract only `ALGORITHM` bits from entire set of flags. */ VMA_POOL_CREATE_ALGORITHM_MASK = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT | VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT, VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaPoolCreateFlagBits; typedef VkFlags VmaPoolCreateFlags; /** \brief Describes parameter of created #VmaPool. */ typedef struct VmaPoolCreateInfo { /** \brief Vulkan memory type index to allocate this pool from. */ uint32_t memoryTypeIndex; /** \brief Use combination of #VmaPoolCreateFlagBits. */ VmaPoolCreateFlags flags; /** \brief Size of a single `VkDeviceMemory` block to be allocated as part of this pool, in bytes. Optional. Specify nonzero to set explicit, constant size of memory blocks used by this pool. Leave 0 to use default and let the library manage block sizes automatically. Sizes of particular blocks may vary. */ VkDeviceSize blockSize; /** \brief Minimum number of blocks to be always allocated in this pool, even if they stay empty. Set to 0 to have no preallocated blocks and allow the pool be completely empty. */ size_t minBlockCount; /** \brief Maximum number of blocks that can be allocated in this pool. Optional. Set to 0 to use default, which is `SIZE_MAX`, which means no limit. Set to same value as VmaPoolCreateInfo::minBlockCount to have fixed amount of memory allocated throughout whole lifetime of this pool. */ size_t maxBlockCount; /** \brief Maximum number of additional frames that are in use at the same time as current frame. This value is used only when you make allocations with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocation cannot become lost if allocation.lastUseFrameIndex >= allocator.currentFrameIndex - frameInUseCount. For example, if you double-buffer your command buffers, so resources used for rendering in previous frame may still be in use by the GPU at the moment you allocate resources needed for the current frame, set this value to 1. If you want to allow any allocations other than used in the current frame to become lost, set this value to 0. */ uint32_t frameInUseCount; } VmaPoolCreateInfo; /** \brief Describes parameter of existing #VmaPool. */ typedef struct VmaPoolStats { /** \brief Total amount of `VkDeviceMemory` allocated from Vulkan for this pool, in bytes. */ VkDeviceSize size; /** \brief Total number of bytes in the pool not used by any #VmaAllocation. */ VkDeviceSize unusedSize; /** \brief Number of #VmaAllocation objects created from this pool that were not destroyed or lost. */ size_t allocationCount; /** \brief Number of continuous memory ranges in the pool not used by any #VmaAllocation. */ size_t unusedRangeCount; /** \brief Size of the largest continuous free memory region available for new allocation. Making a new allocation of that size is not guaranteed to succeed because of possible additional margin required to respect alignment and buffer/image granularity. */ VkDeviceSize unusedRangeSizeMax; /** \brief Number of `VkDeviceMemory` blocks allocated for this pool. */ size_t blockCount; } VmaPoolStats; /** \brief Allocates Vulkan device memory and creates #VmaPool object. @param allocator Allocator object. @param pCreateInfo Parameters of pool to create. @param[out] pPool Handle to created pool. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( VmaAllocator allocator, const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool); /** \brief Destroys #VmaPool object and frees Vulkan device memory. */ VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( VmaAllocator allocator, VmaPool pool); /** \brief Retrieves statistics of existing #VmaPool object. @param allocator Allocator object. @param pool Pool object. @param[out] pPoolStats Statistics of specified pool. */ VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats( VmaAllocator allocator, VmaPool pool, VmaPoolStats* pPoolStats); /** \brief Marks all allocations in given pool as lost if they are not used in current frame or VmaPoolCreateInfo::frameInUseCount back from now. @param allocator Allocator object. @param pool Pool. @param[out] pLostAllocationCount Number of allocations marked as lost. Optional - pass null if you don't need this information. */ VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost( VmaAllocator allocator, VmaPool pool, size_t* pLostAllocationCount); /** \brief Checks magic number in margins around all allocations in given memory pool in search for corruptions. Corruption detection is enabled only when `VMA_DEBUG_DETECT_CORRUPTION` macro is defined to nonzero, `VMA_DEBUG_MARGIN` is defined to nonzero and the pool is created in memory type that is `HOST_VISIBLE` and `HOST_COHERENT`. For more information, see [Corruption detection](@ref debugging_memory_usage_corruption_detection). Possible return values: - `VK_ERROR_FEATURE_NOT_PRESENT` - corruption detection is not enabled for specified pool. - `VK_SUCCESS` - corruption detection has been performed and succeeded. - `VK_ERROR_VALIDATION_FAILED_EXT` - corruption detection has been performed and found memory corruptions around one of the allocations. `VMA_ASSERT` is also fired in that case. - Other value: Error returned by Vulkan, e.g. memory mapping failure. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool); /** \brief Retrieves name of a custom pool. After the call `ppName` is either null or points to an internally-owned null-terminated string containing name of the pool that was previously set. The pointer becomes invalid when the pool is destroyed or its name is changed using vmaSetPoolName(). */ VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( VmaAllocator allocator, VmaPool pool, const char** ppName); /** \brief Sets name of a custom pool. `pName` can be either null or pointer to a null-terminated string with new name for the pool. Function makes internal copy of the string, so it can be changed or freed immediately after this call. */ VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( VmaAllocator allocator, VmaPool pool, const char* pName); /** \struct VmaAllocation \brief Represents single memory allocation. It may be either dedicated block of `VkDeviceMemory` or a specific region of a bigger block of this type plus unique offset. There are multiple ways to create such object. You need to fill structure VmaAllocationCreateInfo. For more information see [Choosing memory type](@ref choosing_memory_type). Although the library provides convenience functions that create Vulkan buffer or image, allocate memory for it and bind them together, binding of the allocation to a buffer or an image is out of scope of the allocation itself. Allocation object can exist without buffer/image bound, binding can be done manually by the user, and destruction of it can be done independently of destruction of the allocation. The object also remembers its size and some other information. To retrieve this information, use function vmaGetAllocationInfo() and inspect returned structure VmaAllocationInfo. Some kinds allocations can be in lost state. For more information, see [Lost allocations](@ref lost_allocations). */ VK_DEFINE_HANDLE(VmaAllocation) /** \brief Parameters of #VmaAllocation objects, that can be retrieved using function vmaGetAllocationInfo(). */ typedef struct VmaAllocationInfo { /** \brief Memory type index that this allocation was allocated from. It never changes. */ uint32_t memoryType; /** \brief Handle to Vulkan memory object. Same memory object can be shared by multiple allocations. It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. If the allocation is lost, it is equal to `VK_NULL_HANDLE`. */ VkDeviceMemory deviceMemory; /** \brief Offset into deviceMemory object to the beginning of this allocation, in bytes. (deviceMemory, offset) pair is unique to this allocation. It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. */ VkDeviceSize offset; /** \brief Size of this allocation, in bytes. It never changes, unless allocation is lost. */ VkDeviceSize size; /** \brief Pointer to the beginning of this allocation as mapped data. If the allocation hasn't been mapped using vmaMapMemory() and hasn't been created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value null. It can change after call to vmaMapMemory(), vmaUnmapMemory(). It can also change after call to vmaDefragment() if this allocation is passed to the function. */ void* pMappedData; /** \brief Custom general-purpose pointer that was passed as VmaAllocationCreateInfo::pUserData or set using vmaSetAllocationUserData(). It can change after call to vmaSetAllocationUserData() for this allocation. */ void* pUserData; } VmaAllocationInfo; /** \brief General purpose memory allocation. @param[out] pAllocation Handle to allocated memory. @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(), vmaCreateBuffer(), vmaCreateImage() instead whenever possible. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); /** \brief General purpose memory allocation for multiple allocation objects at once. @param allocator Allocator object. @param pVkMemoryRequirements Memory requirements for each allocation. @param pCreateInfo Creation parameters for each alloction. @param allocationCount Number of allocations to make. @param[out] pAllocations Pointer to array that will be filled with handles to created allocations. @param[out] pAllocationInfo Optional. Pointer to array that will be filled with parameters of created allocations. You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). Word "pages" is just a suggestion to use this function to allocate pieces of memory needed for sparse binding. It is just a general purpose allocation function able to make multiple allocations at once. It may be internally optimized to be more efficient than calling vmaAllocateMemory() `allocationCount` times. All allocations are made using same parameters. All of them are created out of the same memory pool and type. If any allocation fails, all allocations already made within this function call are also freed, so that when returned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, size_t allocationCount, VmaAllocation* pAllocations, VmaAllocationInfo* pAllocationInfo); /** @param[out] pAllocation Handle to allocated memory. @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). You should free the memory using vmaFreeMemory(). */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( VmaAllocator allocator, VkBuffer buffer, const VmaAllocationCreateInfo* pCreateInfo, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); /// Function similar to vmaAllocateMemoryForBuffer(). VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( VmaAllocator allocator, VkImage image, const VmaAllocationCreateInfo* pCreateInfo, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); /** \brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). Passing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped. */ VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( VmaAllocator allocator, VmaAllocation allocation); /** \brief Frees memory and destroys multiple allocations. Word "pages" is just a suggestion to use this function to free pieces of memory used for sparse binding. It is just a general purpose function to free memory and destroy allocations made using e.g. vmaAllocateMemory(), vmaAllocateMemoryPages() and other functions. It may be internally optimized to be more efficient than calling vmaFreeMemory() `allocationCount` times. Allocations in `pAllocations` array can come from any memory pools and types. Passing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped. */ VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( VmaAllocator allocator, size_t allocationCount, VmaAllocation* pAllocations); /** \brief Deprecated. In version 2.2.0 it used to try to change allocation's size without moving or reallocating it. In current version it returns `VK_SUCCESS` only if `newSize` equals current allocation's size. Otherwise returns `VK_ERROR_OUT_OF_POOL_MEMORY`, indicating that allocation's size could not be changed. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize newSize); /** \brief Returns current information about specified allocation and atomically marks it as used in current frame. Current paramters of given allocation are returned in `pAllocationInfo`. This function also atomically "touches" allocation - marks it as used in current frame, just like vmaTouchAllocation(). If the allocation is in lost state, `pAllocationInfo->deviceMemory == VK_NULL_HANDLE`. Although this function uses atomics and doesn't lock any mutex, so it should be quite efficient, you can avoid calling it too often. - You can retrieve same VmaAllocationInfo structure while creating your resource, from function vmaCreateBuffer(), vmaCreateImage(). You can remember it if you are sure parameters don't change (e.g. due to defragmentation or allocation becoming lost). - If you just want to check if allocation is not lost, vmaTouchAllocation() will work faster. */ VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( VmaAllocator allocator, VmaAllocation allocation, VmaAllocationInfo* pAllocationInfo); /** \brief Returns `VK_TRUE` if allocation is not lost and atomically marks it as used in current frame. If the allocation has been created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function returns `VK_TRUE` if it's not in lost state, so it can still be used. It then also atomically "touches" the allocation - marks it as used in current frame, so that you can be sure it won't become lost in current frame or next `frameInUseCount` frames. If the allocation is in lost state, the function returns `VK_FALSE`. Memory of such allocation, as well as buffer or image bound to it, should not be used. Lost allocation and the buffer/image still need to be destroyed. If the allocation has been created without #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function always returns `VK_TRUE`. */ VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation( VmaAllocator allocator, VmaAllocation allocation); /** \brief Sets pUserData in given allocation to new value. If the allocation was created with VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT, pUserData must be either null, or pointer to a null-terminated string. The function makes local copy of the string and sets it as allocation's `pUserData`. String passed as pUserData doesn't need to be valid for whole lifetime of the allocation - you can free it after this call. String previously pointed by allocation's pUserData is freed from memory. If the flag was not used, the value of pointer `pUserData` is just copied to allocation's `pUserData`. It is opaque, so you can use it however you want - e.g. as a pointer, ordinal number or some handle to you own data. */ VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( VmaAllocator allocator, VmaAllocation allocation, void* pUserData); /** \brief Creates new allocation that is in lost state from the beginning. It can be useful if you need a dummy, non-null allocation. You still need to destroy created object using vmaFreeMemory(). Returned allocation is not tied to any specific memory pool or memory type and not bound to any image or buffer. It has size = 0. It cannot be turned into a real, non-empty allocation. */ VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation( VmaAllocator allocator, VmaAllocation* pAllocation); /** \brief Maps memory represented by given allocation and returns pointer to it. Maps memory represented by given allocation to make it accessible to CPU code. When succeeded, `*ppData` contains pointer to first byte of this memory. If the allocation is part of bigger `VkDeviceMemory` block, the pointer is correctly offseted to the beginning of region assigned to this particular allocation. Mapping is internally reference-counted and synchronized, so despite raw Vulkan function `vkMapMemory()` cannot be used to map same block of `VkDeviceMemory` multiple times simultaneously, it is safe to call this function on allocations assigned to the same memory block. Actual Vulkan memory will be mapped on first mapping and unmapped on last unmapping. If the function succeeded, you must call vmaUnmapMemory() to unmap the allocation when mapping is no longer needed or before freeing the allocation, at the latest. It also safe to call this function multiple times on the same allocation. You must call vmaUnmapMemory() same number of times as you called vmaMapMemory(). It is also safe to call this function on allocation created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag. Its memory stays mapped all the time. You must still call vmaUnmapMemory() same number of times as you called vmaMapMemory(). You must not call vmaUnmapMemory() additional time to free the "0-th" mapping made automatically due to #VMA_ALLOCATION_CREATE_MAPPED_BIT flag. This function fails when used on allocation made in memory type that is not `HOST_VISIBLE`. This function always fails when called for allocation that was created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocations cannot be mapped. This function doesn't automatically flush or invalidate caches. If the allocation is made from a memory types that is not `HOST_COHERENT`, you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( VmaAllocator allocator, VmaAllocation allocation, void** ppData); /** \brief Unmaps memory represented by given allocation, mapped previously using vmaMapMemory(). For details, see description of vmaMapMemory(). This function doesn't automatically flush or invalidate caches. If the allocation is made from a memory types that is not `HOST_COHERENT`, you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. */ VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( VmaAllocator allocator, VmaAllocation allocation); /** \brief Flushes memory of given allocation. Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of given allocation. It needs to be called after writing to a mapped memory for memory types that are not `HOST_COHERENT`. Unmap operation doesn't do that automatically. - `offset` must be relative to the beginning of allocation. - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. - `offset` and `size` don't have to be aligned. They are internally rounded down/up to multiply of `nonCoherentAtomSize`. - If `size` is 0, this call is ignored. - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, this call is ignored. Warning! `offset` and `size` are relative to the contents of given `allocation`. If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. Do not pass allocation's offset as `offset`!!! */ VMA_CALL_PRE void VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); /** \brief Invalidates memory of given allocation. Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range of given allocation. It needs to be called before reading from a mapped memory for memory types that are not `HOST_COHERENT`. Map operation doesn't do that automatically. - `offset` must be relative to the beginning of allocation. - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. - `offset` and `size` don't have to be aligned. They are internally rounded down/up to multiply of `nonCoherentAtomSize`. - If `size` is 0, this call is ignored. - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, this call is ignored. Warning! `offset` and `size` are relative to the contents of given `allocation`. If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. Do not pass allocation's offset as `offset`!!! */ VMA_CALL_PRE void VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); /** \brief Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions. @param memoryTypeBits Bit mask, where each bit set means that a memory type with that index should be checked. Corruption detection is enabled only when `VMA_DEBUG_DETECT_CORRUPTION` macro is defined to nonzero, `VMA_DEBUG_MARGIN` is defined to nonzero and only for memory types that are `HOST_VISIBLE` and `HOST_COHERENT`. For more information, see [Corruption detection](@ref debugging_memory_usage_corruption_detection). Possible return values: - `VK_ERROR_FEATURE_NOT_PRESENT` - corruption detection is not enabled for any of specified memory types. - `VK_SUCCESS` - corruption detection has been performed and succeeded. - `VK_ERROR_VALIDATION_FAILED_EXT` - corruption detection has been performed and found memory corruptions around one of the allocations. `VMA_ASSERT` is also fired in that case. - Other value: Error returned by Vulkan, e.g. memory mapping failure. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits); /** \struct VmaDefragmentationContext \brief Represents Opaque object that represents started defragmentation process. Fill structure #VmaDefragmentationInfo2 and call function vmaDefragmentationBegin() to create it. Call function vmaDefragmentationEnd() to destroy it. */ VK_DEFINE_HANDLE(VmaDefragmentationContext) /// Flags to be used in vmaDefragmentationBegin(). None at the moment. Reserved for future use. typedef enum VmaDefragmentationFlagBits { VMA_DEFRAGMENTATION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaDefragmentationFlagBits; typedef VkFlags VmaDefragmentationFlags; /** \brief Parameters for defragmentation. To be used with function vmaDefragmentationBegin(). */ typedef struct VmaDefragmentationInfo2 { /** \brief Reserved for future use. Should be 0. */ VmaDefragmentationFlags flags; /** \brief Number of allocations in `pAllocations` array. */ uint32_t allocationCount; /** \brief Pointer to array of allocations that can be defragmented. The array should have `allocationCount` elements. The array should not contain nulls. Elements in the array should be unique - same allocation cannot occur twice. It is safe to pass allocations that are in the lost state - they are ignored. All allocations not present in this array are considered non-moveable during this defragmentation. */ VmaAllocation* pAllocations; /** \brief Optional, output. Pointer to array that will be filled with information whether the allocation at certain index has been changed during defragmentation. The array should have `allocationCount` elements. You can pass null if you are not interested in this information. */ VkBool32* pAllocationsChanged; /** \brief Numer of pools in `pPools` array. */ uint32_t poolCount; /** \brief Either null or pointer to array of pools to be defragmented. All the allocations in the specified pools can be moved during defragmentation and there is no way to check if they were really moved as in `pAllocationsChanged`, so you must query all the allocations in all these pools for new `VkDeviceMemory` and offset using vmaGetAllocationInfo() if you might need to recreate buffers and images bound to them. The array should have `poolCount` elements. The array should not contain nulls. Elements in the array should be unique - same pool cannot occur twice. Using this array is equivalent to specifying all allocations from the pools in `pAllocations`. It might be more efficient. */ VmaPool* pPools; /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on CPU side, like `memcpy()`, `memmove()`. `VK_WHOLE_SIZE` means no limit. */ VkDeviceSize maxCpuBytesToMove; /** \brief Maximum number of allocations that can be moved to a different place using transfers on CPU side, like `memcpy()`, `memmove()`. `UINT32_MAX` means no limit. */ uint32_t maxCpuAllocationsToMove; /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on GPU side, posted to `commandBuffer`. `VK_WHOLE_SIZE` means no limit. */ VkDeviceSize maxGpuBytesToMove; /** \brief Maximum number of allocations that can be moved to a different place using transfers on GPU side, posted to `commandBuffer`. `UINT32_MAX` means no limit. */ uint32_t maxGpuAllocationsToMove; /** \brief Optional. Command buffer where GPU copy commands will be posted. If not null, it must be a valid command buffer handle that supports Transfer queue type. It must be in the recording state and outside of a render pass instance. You need to submit it and make sure it finished execution before calling vmaDefragmentationEnd(). Passing null means that only CPU defragmentation will be performed. */ VkCommandBuffer commandBuffer; } VmaDefragmentationInfo2; /** \brief Deprecated. Optional configuration parameters to be passed to function vmaDefragment(). \deprecated This is a part of the old interface. It is recommended to use structure #VmaDefragmentationInfo2 and function vmaDefragmentationBegin() instead. */ typedef struct VmaDefragmentationInfo { /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places. Default is `VK_WHOLE_SIZE`, which means no limit. */ VkDeviceSize maxBytesToMove; /** \brief Maximum number of allocations that can be moved to different place. Default is `UINT32_MAX`, which means no limit. */ uint32_t maxAllocationsToMove; } VmaDefragmentationInfo; /** \brief Statistics returned by function vmaDefragment(). */ typedef struct VmaDefragmentationStats { /// Total number of bytes that have been copied while moving allocations to different places. VkDeviceSize bytesMoved; /// Total number of bytes that have been released to the system by freeing empty `VkDeviceMemory` objects. VkDeviceSize bytesFreed; /// Number of allocations that have been moved to different places. uint32_t allocationsMoved; /// Number of empty `VkDeviceMemory` objects that have been released to the system. uint32_t deviceMemoryBlocksFreed; } VmaDefragmentationStats; /** \brief Begins defragmentation process. @param allocator Allocator object. @param pInfo Structure filled with parameters of defragmentation. @param[out] pStats Optional. Statistics of defragmentation. You can pass null if you are not interested in this information. @param[out] pContext Context object that must be passed to vmaDefragmentationEnd() to finish defragmentation. @return `VK_SUCCESS` and `*pContext == null` if defragmentation finished within this function call. `VK_NOT_READY` and `*pContext != null` if defragmentation has been started and you need to call vmaDefragmentationEnd() to finish it. Negative value in case of error. Use this function instead of old, deprecated vmaDefragment(). Warning! Between the call to vmaDefragmentationBegin() and vmaDefragmentationEnd(): - You should not use any of allocations passed as `pInfo->pAllocations` or any allocations that belong to pools passed as `pInfo->pPools`, including calling vmaGetAllocationInfo(), vmaTouchAllocation(), or access their data. - Some mutexes protecting internal data structures may be locked, so trying to make or free any allocations, bind buffers or images, map memory, or launch another simultaneous defragmentation in between may cause stall (when done on another thread) or deadlock (when done on the same thread), unless you are 100% sure that defragmented allocations are in different pools. - Information returned via `pStats` and `pInfo->pAllocationsChanged` are undefined. They become valid after call to vmaDefragmentationEnd(). - If `pInfo->commandBuffer` is not null, you must submit that command buffer and make sure it finished execution before calling vmaDefragmentationEnd(). For more information and important limitations regarding defragmentation, see documentation chapter: [Defragmentation](@ref defragmentation). */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin( VmaAllocator allocator, const VmaDefragmentationInfo2* pInfo, VmaDefragmentationStats* pStats, VmaDefragmentationContext* pContext); /** \brief Ends defragmentation process. Use this function to finish defragmentation started by vmaDefragmentationBegin(). It is safe to pass `context == null`. The function then does nothing. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd( VmaAllocator allocator, VmaDefragmentationContext context); /** \brief Deprecated. Compacts memory by moving allocations. @param pAllocations Array of allocations that can be moved during this compation. @param allocationCount Number of elements in pAllocations and pAllocationsChanged arrays. @param[out] pAllocationsChanged Array of boolean values that will indicate whether matching allocation in pAllocations array has been moved. This parameter is optional. Pass null if you don't need this information. @param pDefragmentationInfo Configuration parameters. Optional - pass null to use default values. @param[out] pDefragmentationStats Statistics returned by the function. Optional - pass null if you don't need this information. @return `VK_SUCCESS` if completed, negative error code in case of error. \deprecated This is a part of the old interface. It is recommended to use structure #VmaDefragmentationInfo2 and function vmaDefragmentationBegin() instead. This function works by moving allocations to different places (different `VkDeviceMemory` objects and/or different offsets) in order to optimize memory usage. Only allocations that are in `pAllocations` array can be moved. All other allocations are considered nonmovable in this call. Basic rules: - Only allocations made in memory types that have `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` and `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flags can be compacted. You may pass other allocations but it makes no sense - these will never be moved. - Custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT or #VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT flag are not defragmented. Allocations passed to this function that come from such pools are ignored. - Allocations created with #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT or created as dedicated allocations for any other reason are also ignored. - Both allocations made with or without #VMA_ALLOCATION_CREATE_MAPPED_BIT flag can be compacted. If not persistently mapped, memory will be mapped temporarily inside this function if needed. - You must not pass same #VmaAllocation object multiple times in `pAllocations` array. The function also frees empty `VkDeviceMemory` blocks. Warning: This function may be time-consuming, so you shouldn't call it too often (like after every resource creation/destruction). You can call it on special occasions (like when reloading a game level or when you just destroyed a lot of objects). Calling it every frame may be OK, but you should measure that on your platform. For more information, see [Defragmentation](@ref defragmentation) chapter. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment( VmaAllocator allocator, VmaAllocation* pAllocations, size_t allocationCount, VkBool32* pAllocationsChanged, const VmaDefragmentationInfo* pDefragmentationInfo, VmaDefragmentationStats* pDefragmentationStats); /** \brief Binds buffer to allocation. Binds specified buffer to region of memory represented by specified allocation. Gets `VkDeviceMemory` handle and offset from the allocation. If you want to create a buffer, allocate memory for it and bind them together separately, you should use this function for binding instead of standard `vkBindBufferMemory()`, because it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously (which is illegal in Vulkan). It is recommended to use function vmaCreateBuffer() instead of this one. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( VmaAllocator allocator, VmaAllocation allocation, VkBuffer buffer); /** \brief Binds buffer to allocation with additional parameters. @param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0. @param pNext A chain of structures to be attached to `VkBindBufferMemoryInfoKHR` structure used internally. Normally it should be null. This function is similar to vmaBindBufferMemory(), but it provides additional parameters. If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag or with VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_1`. Otherwise the call fails. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize allocationLocalOffset, VkBuffer buffer, const void* pNext); /** \brief Binds image to allocation. Binds specified image to region of memory represented by specified allocation. Gets `VkDeviceMemory` handle and offset from the allocation. If you want to create an image, allocate memory for it and bind them together separately, you should use this function for binding instead of standard `vkBindImageMemory()`, because it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously (which is illegal in Vulkan). It is recommended to use function vmaCreateImage() instead of this one. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( VmaAllocator allocator, VmaAllocation allocation, VkImage image); /** \brief Binds image to allocation with additional parameters. @param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0. @param pNext A chain of structures to be attached to `VkBindImageMemoryInfoKHR` structure used internally. Normally it should be null. This function is similar to vmaBindImageMemory(), but it provides additional parameters. If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag or with VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_1`. Otherwise the call fails. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize allocationLocalOffset, VkImage image, const void* pNext); /** @param[out] pBuffer Buffer that was created. @param[out] pAllocation Allocation that was created. @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). This function automatically: -# Creates buffer. -# Allocates appropriate memory for it. -# Binds the buffer with the memory. If any of these operations fail, buffer and allocation are not created, returned value is negative error code, *pBuffer and *pAllocation are null. If the function succeeded, you must destroy both buffer and allocation when you no longer need them using either convenience function vmaDestroyBuffer() or separately, using `vkDestroyBuffer()` and vmaFreeMemory(). If VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used, VK_KHR_dedicated_allocation extension is used internally to query driver whether it requires or prefers the new buffer to have dedicated allocation. If yes, and if dedicated allocation is possible (VmaAllocationCreateInfo::pool is null and VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated allocation for this buffer, just like when using VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, VkBuffer* pBuffer, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); /** \brief Destroys Vulkan buffer and frees allocated memory. This is just a convenience function equivalent to: \code vkDestroyBuffer(device, buffer, allocationCallbacks); vmaFreeMemory(allocator, allocation); \endcode It it safe to pass null as buffer and/or allocation. */ VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( VmaAllocator allocator, VkBuffer buffer, VmaAllocation allocation); /// Function similar to vmaCreateBuffer(). VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, VkImage* pImage, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); /** \brief Destroys Vulkan image and frees allocated memory. This is just a convenience function equivalent to: \code vkDestroyImage(device, image, allocationCallbacks); vmaFreeMemory(allocator, allocation); \endcode It it safe to pass null as image and/or allocation. */ VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( VmaAllocator allocator, VkImage image, VmaAllocation allocation); #ifdef __cplusplus } #endif #endif // AMD_VULKAN_MEMORY_ALLOCATOR_H // For Visual Studio IntelliSense. #if defined(__cplusplus) && defined(__INTELLISENSE__) #define VMA_IMPLEMENTATION #endif #ifdef VMA_IMPLEMENTATION #undef VMA_IMPLEMENTATION #include <cstdint> #include <cstdlib> #include <cstring> /******************************************************************************* CONFIGURATION SECTION Define some of these macros before each #include of this header or change them here if you need other then default behavior depending on your environment. */ /* Define this macro to 1 to make the library fetch pointers to Vulkan functions internally, like: vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; Define to 0 if you are going to provide you own pointers to Vulkan functions via VmaAllocatorCreateInfo::pVulkanFunctions. */ #if !defined(VMA_STATIC_VULKAN_FUNCTIONS) && !defined(VK_NO_PROTOTYPES) #define VMA_STATIC_VULKAN_FUNCTIONS 1 #endif // Define this macro to 1 to make the library use STL containers instead of its own implementation. //#define VMA_USE_STL_CONTAINERS 1 /* Set this macro to 1 to make the library including and using STL containers: std::pair, std::vector, std::list, std::unordered_map. Set it to 0 or undefined to make the library using its own implementation of the containers. */ #if VMA_USE_STL_CONTAINERS #define VMA_USE_STL_VECTOR 1 #define VMA_USE_STL_UNORDERED_MAP 1 #define VMA_USE_STL_LIST 1 #endif #ifndef VMA_USE_STL_SHARED_MUTEX // Compiler conforms to C++17. #if __cplusplus >= 201703L #define VMA_USE_STL_SHARED_MUTEX 1 // Visual studio defines __cplusplus properly only when passed additional parameter: /Zc:__cplusplus // Otherwise it's always 199711L, despite shared_mutex works since Visual Studio 2015 Update 2. // See: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/ #elif defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023918 && __cplusplus == 199711L && _MSVC_LANG >= 201703L #define VMA_USE_STL_SHARED_MUTEX 1 #else #define VMA_USE_STL_SHARED_MUTEX 0 #endif #endif /* THESE INCLUDES ARE NOT ENABLED BY DEFAULT. Library has its own container implementation. */ #if VMA_USE_STL_VECTOR #include <vector> #endif #if VMA_USE_STL_UNORDERED_MAP #include <unordered_map> #endif #if VMA_USE_STL_LIST #include <list> #endif /* Following headers are used in this CONFIGURATION section only, so feel free to remove them if not needed. */ #include <cassert> // for assert #include <algorithm> // for min, max #include <mutex> #ifndef VMA_NULL // Value used as null pointer. Define it to e.g.: nullptr, NULL, 0, (void*)0. #define VMA_NULL nullptr #endif #if defined(__ANDROID_API__) && (__ANDROID_API__ < 16) #include <cstdlib> void* aligned_alloc(size_t alignment, size_t size) { // alignment must be >= sizeof(void*) if (alignment < sizeof(void*)) { alignment = sizeof(void*); } return memalign(alignment, size); } #elif defined(__APPLE__) || defined(__ANDROID__) || (defined(__linux__) && defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC)) #include <cstdlib> void* aligned_alloc(size_t alignment, size_t size) { // alignment must be >= sizeof(void*) if (alignment < sizeof(void*)) { alignment = sizeof(void*); } void* pointer; if (posix_memalign(&pointer, alignment, size) == 0) return pointer; return VMA_NULL; } #endif // If your compiler is not compatible with C++11 and definition of // aligned_alloc() function is missing, uncommeting following line may help: //#include <malloc.h> // Normal assert to check for programmer's errors, especially in Debug configuration. #ifndef VMA_ASSERT #ifdef _DEBUG #define VMA_ASSERT(expr) assert(expr) #else #define VMA_ASSERT(expr) #endif #endif // Assert that will be called very often, like inside data structures e.g. operator[]. // Making it non-empty can make program slow. #ifndef VMA_HEAVY_ASSERT #ifdef _DEBUG #define VMA_HEAVY_ASSERT(expr) //VMA_ASSERT(expr) #else #define VMA_HEAVY_ASSERT(expr) #endif #endif #ifndef VMA_ALIGN_OF #define VMA_ALIGN_OF(type) (__alignof(type)) #endif #ifndef VMA_SYSTEM_ALIGNED_MALLOC #if defined(_WIN32) #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (_aligned_malloc((size), (alignment))) #else #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (aligned_alloc((alignment), (size) )) #endif #endif #ifndef VMA_SYSTEM_FREE #if defined(_WIN32) #define VMA_SYSTEM_FREE(ptr) _aligned_free(ptr) #else #define VMA_SYSTEM_FREE(ptr) free(ptr) #endif #endif #ifndef VMA_MIN #define VMA_MIN(v1, v2) (std::min((v1), (v2))) #endif #ifndef VMA_MAX #define VMA_MAX(v1, v2) (std::max((v1), (v2))) #endif #ifndef VMA_SWAP #define VMA_SWAP(v1, v2) std::swap((v1), (v2)) #endif #ifndef VMA_SORT #define VMA_SORT(beg, end, cmp) std::sort(beg, end, cmp) #endif #ifndef VMA_DEBUG_LOG #define VMA_DEBUG_LOG(format, ...) /* #define VMA_DEBUG_LOG(format, ...) do { \ printf(format, __VA_ARGS__); \ printf("\n"); \ } while(false) */ #endif // Define this macro to 1 to enable functions: vmaBuildStatsString, vmaFreeStatsString. #if VMA_STATS_STRING_ENABLED static inline void VmaUint32ToStr(char* outStr, size_t strLen, uint32_t num) { snprintf(outStr, strLen, "%u", static_cast<unsigned int>(num)); } static inline void VmaUint64ToStr(char* outStr, size_t strLen, uint64_t num) { snprintf(outStr, strLen, "%llu", static_cast<unsigned long long>(num)); } static inline void VmaPtrToStr(char* outStr, size_t strLen, const void* ptr) { snprintf(outStr, strLen, "%p", ptr); } #endif #ifndef VMA_MUTEX class VmaMutex { public: void Lock() { m_Mutex.lock(); } void Unlock() { m_Mutex.unlock(); } private: std::mutex m_Mutex; }; #define VMA_MUTEX VmaMutex #endif // Read-write mutex, where "read" is shared access, "write" is exclusive access. #ifndef VMA_RW_MUTEX #if VMA_USE_STL_SHARED_MUTEX // Use std::shared_mutex from C++17. #include <shared_mutex> class VmaRWMutex { public: void LockRead() { m_Mutex.lock_shared(); } void UnlockRead() { m_Mutex.unlock_shared(); } void LockWrite() { m_Mutex.lock(); } void UnlockWrite() { m_Mutex.unlock(); } private: std::shared_mutex m_Mutex; }; #define VMA_RW_MUTEX VmaRWMutex #elif defined(_WIN32) && defined(WINVER) && WINVER >= 0x0600 // Use SRWLOCK from WinAPI. // Minimum supported client = Windows Vista, server = Windows Server 2008. class VmaRWMutex { public: VmaRWMutex() { InitializeSRWLock(&m_Lock); } void LockRead() { AcquireSRWLockShared(&m_Lock); } void UnlockRead() { ReleaseSRWLockShared(&m_Lock); } void LockWrite() { AcquireSRWLockExclusive(&m_Lock); } void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); } private: SRWLOCK m_Lock; }; #define VMA_RW_MUTEX VmaRWMutex #else // Less efficient fallback: Use normal mutex. class VmaRWMutex { public: void LockRead() { m_Mutex.Lock(); } void UnlockRead() { m_Mutex.Unlock(); } void LockWrite() { m_Mutex.Lock(); } void UnlockWrite() { m_Mutex.Unlock(); } private: VMA_MUTEX m_Mutex; }; #define VMA_RW_MUTEX VmaRWMutex #endif // #if VMA_USE_STL_SHARED_MUTEX #endif // #ifndef VMA_RW_MUTEX /* If providing your own implementation, you need to implement a subset of std::atomic. */ #ifndef VMA_ATOMIC_UINT32 #include <atomic> #define VMA_ATOMIC_UINT32 std::atomic<uint32_t> #endif #ifndef VMA_ATOMIC_UINT64 #include <atomic> #define VMA_ATOMIC_UINT64 std::atomic<uint64_t> #endif #ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY /** Every allocation will have its own memory block. Define to 1 for debugging purposes only. */ #define VMA_DEBUG_ALWAYS_DEDICATED_MEMORY (0) #endif #ifndef VMA_DEBUG_ALIGNMENT /** Minimum alignment of all allocations, in bytes. Set to more than 1 for debugging purposes only. Must be power of two. */ #define VMA_DEBUG_ALIGNMENT (1) #endif #ifndef VMA_DEBUG_MARGIN /** Minimum margin before and after every allocation, in bytes. Set nonzero for debugging purposes only. */ #define VMA_DEBUG_MARGIN (0) #endif #ifndef VMA_DEBUG_INITIALIZE_ALLOCATIONS /** Define this macro to 1 to automatically fill new allocations and destroyed allocations with some bit pattern. */ #define VMA_DEBUG_INITIALIZE_ALLOCATIONS (0) #endif #ifndef VMA_DEBUG_DETECT_CORRUPTION /** Define this macro to 1 together with non-zero value of VMA_DEBUG_MARGIN to enable writing magic value to the margin before and after every allocation and validating it, so that memory corruptions (out-of-bounds writes) are detected. */ #define VMA_DEBUG_DETECT_CORRUPTION (0) #endif #ifndef VMA_DEBUG_GLOBAL_MUTEX /** Set this to 1 for debugging purposes only, to enable single mutex protecting all entry calls to the library. Can be useful for debugging multithreading issues. */ #define VMA_DEBUG_GLOBAL_MUTEX (0) #endif #ifndef VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY /** Minimum value for VkPhysicalDeviceLimits::bufferImageGranularity. Set to more than 1 for debugging purposes only. Must be power of two. */ #define VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY (1) #endif #ifndef VMA_SMALL_HEAP_MAX_SIZE /// Maximum size of a memory heap in Vulkan to consider it "small". #define VMA_SMALL_HEAP_MAX_SIZE (1024ull * 1024 * 1024) #endif #ifndef VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE /// Default size of a block allocated as single VkDeviceMemory from a "large" heap. #define VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE (256ull * 1024 * 1024) #endif #ifndef VMA_CLASS_NO_COPY #define VMA_CLASS_NO_COPY(className) \ private: \ className(const className&) = delete; \ className& operator=(const className&) = delete; #endif static const uint32_t VMA_FRAME_INDEX_LOST = UINT32_MAX; // Decimal 2139416166, float NaN, little-endian binary 66 E6 84 7F. static const uint32_t VMA_CORRUPTION_DETECTION_MAGIC_VALUE = 0x7F84E666; static const uint8_t VMA_ALLOCATION_FILL_PATTERN_CREATED = 0xDC; static const uint8_t VMA_ALLOCATION_FILL_PATTERN_DESTROYED = 0xEF; /******************************************************************************* END OF CONFIGURATION */ static const uint32_t VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET = 0x10000000u; static VkAllocationCallbacks VmaEmptyAllocationCallbacks = { VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL }; // Returns number of bits set to 1 in (v). static inline uint32_t VmaCountBitsSet(uint32_t v) { uint32_t c = v - ((v >> 1) & 0x55555555); c = ((c >> 2) & 0x33333333) + (c & 0x33333333); c = ((c >> 4) + c) & 0x0F0F0F0F; c = ((c >> 8) + c) & 0x00FF00FF; c = ((c >> 16) + c) & 0x0000FFFF; return c; } // Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16. // Use types like uint32_t, uint64_t as T. template <typename T> static inline T VmaAlignUp(T val, T align) { return (val + align - 1) / align * align; } // Aligns given value down to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 8. // Use types like uint32_t, uint64_t as T. template <typename T> static inline T VmaAlignDown(T val, T align) { return val / align * align; } // Division with mathematical rounding to nearest number. template <typename T> static inline T VmaRoundDiv(T x, T y) { return (x + (y / (T)2)) / y; } /* Returns true if given number is a power of two. T must be unsigned integer number or signed integer but always nonnegative. For 0 returns true. */ template <typename T> inline bool VmaIsPow2(T x) { return (x & (x - 1)) == 0; } // Returns smallest power of 2 greater or equal to v. static inline uint32_t VmaNextPow2(uint32_t v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } static inline uint64_t VmaNextPow2(uint64_t v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v |= v >> 32; v++; return v; } // Returns largest power of 2 less or equal to v. static inline uint32_t VmaPrevPow2(uint32_t v) { v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v = v ^ (v >> 1); return v; } static inline uint64_t VmaPrevPow2(uint64_t v) { v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v |= v >> 32; v = v ^ (v >> 1); return v; } static inline bool VmaStrIsEmpty(const char* pStr) { return pStr == VMA_NULL || *pStr == '\0'; } #if VMA_STATS_STRING_ENABLED static const char* VmaAlgorithmToStr(uint32_t algorithm) { switch (algorithm) { case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: return "Linear"; case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT: return "Buddy"; case 0: return "Default"; default: VMA_ASSERT(0); return ""; } } #endif // #if VMA_STATS_STRING_ENABLED #ifndef VMA_SORT template<typename Iterator, typename Compare> Iterator VmaQuickSortPartition(Iterator beg, Iterator end, Compare cmp) { Iterator centerValue = end; --centerValue; Iterator insertIndex = beg; for (Iterator memTypeIndex = beg; memTypeIndex < centerValue; ++memTypeIndex) { if (cmp(*memTypeIndex, *centerValue)) { if (insertIndex != memTypeIndex) { VMA_SWAP(*memTypeIndex, *insertIndex); } ++insertIndex; } } if (insertIndex != centerValue) { VMA_SWAP(*insertIndex, *centerValue); } return insertIndex; } template<typename Iterator, typename Compare> void VmaQuickSort(Iterator beg, Iterator end, Compare cmp) { if (beg < end) { Iterator it = VmaQuickSortPartition<Iterator, Compare>(beg, end, cmp); VmaQuickSort<Iterator, Compare>(beg, it, cmp); VmaQuickSort<Iterator, Compare>(it + 1, end, cmp); } } #define VMA_SORT(beg, end, cmp) VmaQuickSort(beg, end, cmp) #endif // #ifndef VMA_SORT /* Returns true if two memory blocks occupy overlapping pages. ResourceA must be in less memory offset than ResourceB. Algorithm is based on "Vulkan 1.0.39 - A Specification (with all registered Vulkan extensions)" chapter 11.6 "Resource Memory Association", paragraph "Buffer-Image Granularity". */ static inline bool VmaBlocksOnSamePage( VkDeviceSize resourceAOffset, VkDeviceSize resourceASize, VkDeviceSize resourceBOffset, VkDeviceSize pageSize) { VMA_ASSERT(resourceAOffset + resourceASize <= resourceBOffset && resourceASize > 0 && pageSize > 0); VkDeviceSize resourceAEnd = resourceAOffset + resourceASize - 1; VkDeviceSize resourceAEndPage = resourceAEnd & ~(pageSize - 1); VkDeviceSize resourceBStart = resourceBOffset; VkDeviceSize resourceBStartPage = resourceBStart & ~(pageSize - 1); return resourceAEndPage == resourceBStartPage; } enum VmaSuballocationType { VMA_SUBALLOCATION_TYPE_FREE = 0, VMA_SUBALLOCATION_TYPE_UNKNOWN = 1, VMA_SUBALLOCATION_TYPE_BUFFER = 2, VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN = 3, VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR = 4, VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL = 5, VMA_SUBALLOCATION_TYPE_MAX_ENUM = 0x7FFFFFFF }; /* Returns true if given suballocation types could conflict and must respect VkPhysicalDeviceLimits::bufferImageGranularity. They conflict if one is buffer or linear image and another one is optimal image. If type is unknown, behave conservatively. */ static inline bool VmaIsBufferImageGranularityConflict( VmaSuballocationType suballocType1, VmaSuballocationType suballocType2) { if (suballocType1 > suballocType2) { VMA_SWAP(suballocType1, suballocType2); } switch (suballocType1) { case VMA_SUBALLOCATION_TYPE_FREE: return false; case VMA_SUBALLOCATION_TYPE_UNKNOWN: return true; case VMA_SUBALLOCATION_TYPE_BUFFER: return suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN || suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL; case VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN: return suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN || suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR || suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL; case VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR: return suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL; case VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL: return false; default: VMA_ASSERT(0); return true; } } static void VmaWriteMagicValue(void* pData, VkDeviceSize offset) { #if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION uint32_t* pDst = (uint32_t*)((char*)pData + offset); const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); for (size_t i = 0; i < numberCount; ++i, ++pDst) { *pDst = VMA_CORRUPTION_DETECTION_MAGIC_VALUE; } #else // no-op #endif } static bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset) { #if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION const uint32_t* pSrc = (const uint32_t*)((const char*)pData + offset); const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); for (size_t i = 0; i < numberCount; ++i, ++pSrc) { if (*pSrc != VMA_CORRUPTION_DETECTION_MAGIC_VALUE) { return false; } } #endif return true; } /* Fills structure with parameters of an example buffer to be used for transfers during GPU memory defragmentation. */ static void VmaFillGpuDefragmentationBufferCreateInfo(VkBufferCreateInfo& outBufCreateInfo) { memset(&outBufCreateInfo, 0, sizeof(outBufCreateInfo)); outBufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; outBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; outBufCreateInfo.size = (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE; // Example size. } // Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope). struct VmaMutexLock { VMA_CLASS_NO_COPY(VmaMutexLock) public: VmaMutexLock(VMA_MUTEX& mutex, bool useMutex = true) : m_pMutex(useMutex ? &mutex : VMA_NULL) { if (m_pMutex) { m_pMutex->Lock(); } } ~VmaMutexLock() { if (m_pMutex) { m_pMutex->Unlock(); } } private: VMA_MUTEX* m_pMutex; }; // Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for reading. struct VmaMutexLockRead { VMA_CLASS_NO_COPY(VmaMutexLockRead) public: VmaMutexLockRead(VMA_RW_MUTEX& mutex, bool useMutex) : m_pMutex(useMutex ? &mutex : VMA_NULL) { if (m_pMutex) { m_pMutex->LockRead(); } } ~VmaMutexLockRead() { if (m_pMutex) { m_pMutex->UnlockRead(); } } private: VMA_RW_MUTEX* m_pMutex; }; // Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for writing. struct VmaMutexLockWrite { VMA_CLASS_NO_COPY(VmaMutexLockWrite) public: VmaMutexLockWrite(VMA_RW_MUTEX& mutex, bool useMutex) : m_pMutex(useMutex ? &mutex : VMA_NULL) { if (m_pMutex) { m_pMutex->LockWrite(); } } ~VmaMutexLockWrite() { if (m_pMutex) { m_pMutex->UnlockWrite(); } } private: VMA_RW_MUTEX* m_pMutex; }; #if VMA_DEBUG_GLOBAL_MUTEX static VMA_MUTEX gDebugGlobalMutex; #define VMA_DEBUG_GLOBAL_MUTEX_LOCK VmaMutexLock debugGlobalMutexLock(gDebugGlobalMutex, true); #else #define VMA_DEBUG_GLOBAL_MUTEX_LOCK #endif // Minimum size of a free suballocation to register it in the free suballocation collection. static const VkDeviceSize VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER = 16; /* Performs binary search and returns iterator to first element that is greater or equal to (key), according to comparison (cmp). Cmp should return true if first argument is less than second argument. Returned value is the found element, if present in the collection or place where new element with value (key) should be inserted. */ template <typename CmpLess, typename IterT, typename KeyT> static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp) { size_t down = 0, up = (end - beg); while (down < up) { const size_t mid = (down + up) / 2; if (cmp(*(beg + mid), key)) { down = mid + 1; } else { up = mid; } } return beg + down; } template<typename CmpLess, typename IterT, typename KeyT> IterT VmaBinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp) { IterT it = VmaBinaryFindFirstNotLess<CmpLess, IterT, KeyT>( beg, end, value, cmp); if (it == end || (!cmp(*it, value) && !cmp(value, *it))) { return it; } return end; } /* Returns true if all pointers in the array are not-null and unique. Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT. T must be pointer type, e.g. VmaAllocation, VmaPool. */ template<typename T> static bool VmaValidatePointerArray(uint32_t count, const T* arr) { for (uint32_t i = 0; i < count; ++i) { const T iPtr = arr[i]; if (iPtr == VMA_NULL) { return false; } for (uint32_t j = i + 1; j < count; ++j) { if (iPtr == arr[j]) { return false; } } } return true; } //////////////////////////////////////////////////////////////////////////////// // Memory allocation static void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t size, size_t alignment) { if ((pAllocationCallbacks != VMA_NULL) && (pAllocationCallbacks->pfnAllocation != VMA_NULL)) { return (*pAllocationCallbacks->pfnAllocation)( pAllocationCallbacks->pUserData, size, alignment, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); } else { return VMA_SYSTEM_ALIGNED_MALLOC(size, alignment); } } static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr) { if ((pAllocationCallbacks != VMA_NULL) && (pAllocationCallbacks->pfnFree != VMA_NULL)) { (*pAllocationCallbacks->pfnFree)(pAllocationCallbacks->pUserData, ptr); } else { VMA_SYSTEM_FREE(ptr); } } template<typename T> static T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks) { return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T), VMA_ALIGN_OF(T)); } template<typename T> static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count) { return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T) * count, VMA_ALIGN_OF(T)); } #define vma_new(allocator, type) new(VmaAllocate<type>(allocator))(type) #define vma_new_array(allocator, type, count) new(VmaAllocateArray<type>((allocator), (count)))(type) template<typename T> static void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr) { ptr->~T(); VmaFree(pAllocationCallbacks, ptr); } template<typename T> static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count) { if (ptr != VMA_NULL) { for (size_t i = count; i--; ) { ptr[i].~T(); } VmaFree(pAllocationCallbacks, ptr); } } static char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr) { if (srcStr != VMA_NULL) { const size_t len = strlen(srcStr); char* const result = vma_new_array(allocs, char, len + 1); memcpy(result, srcStr, len + 1); return result; } else { return VMA_NULL; } } static void VmaFreeString(const VkAllocationCallbacks* allocs, char* str) { if (str != VMA_NULL) { const size_t len = strlen(str); vma_delete_array(allocs, str, len + 1); } } // STL-compatible allocator. template<typename T> class VmaStlAllocator { public: const VkAllocationCallbacks* const m_pCallbacks; typedef T value_type; VmaStlAllocator(const VkAllocationCallbacks* pCallbacks) : m_pCallbacks(pCallbacks) { } template<typename U> VmaStlAllocator(const VmaStlAllocator<U>& src) : m_pCallbacks(src.m_pCallbacks) { } T* allocate(size_t n) { return VmaAllocateArray<T>(m_pCallbacks, n); } void deallocate(T* p, size_t n) { VmaFree(m_pCallbacks, p); } template<typename U> bool operator==(const VmaStlAllocator<U>& rhs) const { return m_pCallbacks == rhs.m_pCallbacks; } template<typename U> bool operator!=(const VmaStlAllocator<U>& rhs) const { return m_pCallbacks != rhs.m_pCallbacks; } VmaStlAllocator& operator=(const VmaStlAllocator& x) = delete; }; #if VMA_USE_STL_VECTOR #define VmaVector std::vector template<typename T, typename allocatorT> static void VmaVectorInsert(std::vector<T, allocatorT>& vec, size_t index, const T& item) { vec.insert(vec.begin() + index, item); } template<typename T, typename allocatorT> static void VmaVectorRemove(std::vector<T, allocatorT>& vec, size_t index) { vec.erase(vec.begin() + index); } #else // #if VMA_USE_STL_VECTOR /* Class with interface compatible with subset of std::vector. T must be POD because constructors and destructors are not called and memcpy is used for these objects. */ template<typename T, typename AllocatorT> class VmaVector { public: typedef T value_type; VmaVector(const AllocatorT& allocator) : m_Allocator(allocator), m_pArray(VMA_NULL), m_Count(0), m_Capacity(0) { } VmaVector(size_t count, const AllocatorT& allocator) : m_Allocator(allocator), m_pArray(count ? (T*)VmaAllocateArray<T>(allocator.m_pCallbacks, count) : VMA_NULL), m_Count(count), m_Capacity(count) { } // This version of the constructor is here for compatibility with pre-C++14 std::vector. // value is unused. VmaVector(size_t count, const T& value, const AllocatorT& allocator) : VmaVector(count, allocator) { } VmaVector(const VmaVector<T, AllocatorT>& src) : m_Allocator(src.m_Allocator), m_pArray(src.m_Count ? (T*)VmaAllocateArray<T>(src.m_Allocator.m_pCallbacks, src.m_Count) : VMA_NULL), m_Count(src.m_Count), m_Capacity(src.m_Count) { if (m_Count != 0) { memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T)); } } ~VmaVector() { VmaFree(m_Allocator.m_pCallbacks, m_pArray); } VmaVector& operator=(const VmaVector<T, AllocatorT>& rhs) { if (&rhs != this) { resize(rhs.m_Count); if (m_Count != 0) { memcpy(m_pArray, rhs.m_pArray, m_Count * sizeof(T)); } } return *this; } bool empty() const { return m_Count == 0; } size_t size() const { return m_Count; } T* data() { return m_pArray; } const T* data() const { return m_pArray; } T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); return m_pArray[index]; } const T& operator[](size_t index) const { VMA_HEAVY_ASSERT(index < m_Count); return m_pArray[index]; } T& front() { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[0]; } const T& front() const { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[0]; } T& back() { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[m_Count - 1]; } const T& back() const { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[m_Count - 1]; } void reserve(size_t newCapacity, bool freeMemory = false) { newCapacity = VMA_MAX(newCapacity, m_Count); if ((newCapacity < m_Capacity) && !freeMemory) { newCapacity = m_Capacity; } if (newCapacity != m_Capacity) { T* const newArray = newCapacity ? VmaAllocateArray<T>(m_Allocator, newCapacity) : VMA_NULL; if (m_Count != 0) { memcpy(newArray, m_pArray, m_Count * sizeof(T)); } VmaFree(m_Allocator.m_pCallbacks, m_pArray); m_Capacity = newCapacity; m_pArray = newArray; } } void resize(size_t newCount, bool freeMemory = false) { size_t newCapacity = m_Capacity; if (newCount > m_Capacity) { newCapacity = VMA_MAX(newCount, VMA_MAX(m_Capacity * 3 / 2, (size_t)8)); } else if (freeMemory) { newCapacity = newCount; } if (newCapacity != m_Capacity) { T* const newArray = newCapacity ? VmaAllocateArray<T>(m_Allocator.m_pCallbacks, newCapacity) : VMA_NULL; const size_t elementsToCopy = VMA_MIN(m_Count, newCount); if (elementsToCopy != 0) { memcpy(newArray, m_pArray, elementsToCopy * sizeof(T)); } VmaFree(m_Allocator.m_pCallbacks, m_pArray); m_Capacity = newCapacity; m_pArray = newArray; } m_Count = newCount; } void clear(bool freeMemory = false) { resize(0, freeMemory); } void insert(size_t index, const T& src) { VMA_HEAVY_ASSERT(index <= m_Count); const size_t oldCount = size(); resize(oldCount + 1); if (index < oldCount) { memmove(m_pArray + (index + 1), m_pArray + index, (oldCount - index) * sizeof(T)); } m_pArray[index] = src; } void remove(size_t index) { VMA_HEAVY_ASSERT(index < m_Count); const size_t oldCount = size(); if (index < oldCount - 1) { memmove(m_pArray + index, m_pArray + (index + 1), (oldCount - index - 1) * sizeof(T)); } resize(oldCount - 1); } void push_back(const T& src) { const size_t newIndex = size(); resize(newIndex + 1); m_pArray[newIndex] = src; } void pop_back() { VMA_HEAVY_ASSERT(m_Count > 0); resize(size() - 1); } void push_front(const T& src) { insert(0, src); } void pop_front() { VMA_HEAVY_ASSERT(m_Count > 0); remove(0); } typedef T* iterator; iterator begin() { return m_pArray; } iterator end() { return m_pArray + m_Count; } private: AllocatorT m_Allocator; T* m_pArray; size_t m_Count; size_t m_Capacity; }; template<typename T, typename allocatorT> static void VmaVectorInsert(VmaVector<T, allocatorT>& vec, size_t index, const T& item) { vec.insert(index, item); } template<typename T, typename allocatorT> static void VmaVectorRemove(VmaVector<T, allocatorT>& vec, size_t index) { vec.remove(index); } #endif // #if VMA_USE_STL_VECTOR template<typename CmpLess, typename VectorT> size_t VmaVectorInsertSorted(VectorT& vector, const typename VectorT::value_type& value) { const size_t indexToInsert = VmaBinaryFindFirstNotLess( vector.data(), vector.data() + vector.size(), value, CmpLess()) - vector.data(); VmaVectorInsert(vector, indexToInsert, value); return indexToInsert; } template<typename CmpLess, typename VectorT> bool VmaVectorRemoveSorted(VectorT& vector, const typename VectorT::value_type& value) { CmpLess comparator; typename VectorT::iterator it = VmaBinaryFindFirstNotLess( vector.begin(), vector.end(), value, comparator); if ((it != vector.end()) && !comparator(*it, value) && !comparator(value, *it)) { size_t indexToRemove = it - vector.begin(); VmaVectorRemove(vector, indexToRemove); return true; } return false; } //////////////////////////////////////////////////////////////////////////////// // class VmaPoolAllocator /* Allocator for objects of type T using a list of arrays (pools) to speed up allocation. Number of elements that can be allocated is not bounded because allocator can create multiple blocks. */ template<typename T> class VmaPoolAllocator { VMA_CLASS_NO_COPY(VmaPoolAllocator) public: VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity); ~VmaPoolAllocator(); T* Alloc(); void Free(T* ptr); private: union Item { uint32_t NextFreeIndex; alignas(T) char Value[sizeof(T)]; }; struct ItemBlock { Item* pItems; uint32_t Capacity; uint32_t FirstFreeIndex; }; const VkAllocationCallbacks* m_pAllocationCallbacks; const uint32_t m_FirstBlockCapacity; VmaVector< ItemBlock, VmaStlAllocator<ItemBlock> > m_ItemBlocks; ItemBlock& CreateNewBlock(); }; template<typename T> VmaPoolAllocator<T>::VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity) : m_pAllocationCallbacks(pAllocationCallbacks), m_FirstBlockCapacity(firstBlockCapacity), m_ItemBlocks(VmaStlAllocator<ItemBlock>(pAllocationCallbacks)) { VMA_ASSERT(m_FirstBlockCapacity > 1); } template<typename T> VmaPoolAllocator<T>::~VmaPoolAllocator() { for (size_t i = m_ItemBlocks.size(); i--; ) vma_delete_array(m_pAllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity); m_ItemBlocks.clear(); } template<typename T> T* VmaPoolAllocator<T>::Alloc() { for (size_t i = m_ItemBlocks.size(); i--; ) { ItemBlock& block = m_ItemBlocks[i]; // This block has some free items: Use first one. if (block.FirstFreeIndex != UINT32_MAX) { Item* const pItem = &block.pItems[block.FirstFreeIndex]; block.FirstFreeIndex = pItem->NextFreeIndex; T* result = (T*)&pItem->Value; new(result)T(); // Explicit constructor call. return result; } } // No block has free item: Create new one and use it. ItemBlock& newBlock = CreateNewBlock(); Item* const pItem = &newBlock.pItems[0]; newBlock.FirstFreeIndex = pItem->NextFreeIndex; T* result = (T*)&pItem->Value; new(result)T(); // Explicit constructor call. return result; } template<typename T> void VmaPoolAllocator<T>::Free(T* ptr) { // Search all memory blocks to find ptr. for (size_t i = m_ItemBlocks.size(); i--; ) { ItemBlock& block = m_ItemBlocks[i]; // Casting to union. Item* pItemPtr; memcpy(&pItemPtr, &ptr, sizeof(pItemPtr)); // Check if pItemPtr is in address range of this block. if ((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) { ptr->~T(); // Explicit destructor call. const uint32_t index = static_cast<uint32_t>(pItemPtr - block.pItems); pItemPtr->NextFreeIndex = block.FirstFreeIndex; block.FirstFreeIndex = index; return; } } VMA_ASSERT(0 && "Pointer doesn't belong to this memory pool."); } template<typename T> typename VmaPoolAllocator<T>::ItemBlock& VmaPoolAllocator<T>::CreateNewBlock() { const uint32_t newBlockCapacity = m_ItemBlocks.empty() ? m_FirstBlockCapacity : m_ItemBlocks.back().Capacity * 3 / 2; const ItemBlock newBlock = { vma_new_array(m_pAllocationCallbacks, Item, newBlockCapacity), newBlockCapacity, 0 }; m_ItemBlocks.push_back(newBlock); // Setup singly-linked list of all free items in this block. for (uint32_t i = 0; i < newBlockCapacity - 1; ++i) newBlock.pItems[i].NextFreeIndex = i + 1; newBlock.pItems[newBlockCapacity - 1].NextFreeIndex = UINT32_MAX; return m_ItemBlocks.back(); } //////////////////////////////////////////////////////////////////////////////// // class VmaRawList, VmaList #if VMA_USE_STL_LIST #define VmaList std::list #else // #if VMA_USE_STL_LIST template<typename T> struct VmaListItem { VmaListItem* pPrev; VmaListItem* pNext; T Value; }; // Doubly linked list. template<typename T> class VmaRawList { VMA_CLASS_NO_COPY(VmaRawList) public: typedef VmaListItem<T> ItemType; VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks); ~VmaRawList(); void Clear(); size_t GetCount() const { return m_Count; } bool IsEmpty() const { return m_Count == 0; } ItemType* Front() { return m_pFront; } const ItemType* Front() const { return m_pFront; } ItemType* Back() { return m_pBack; } const ItemType* Back() const { return m_pBack; } ItemType* PushBack(); ItemType* PushFront(); ItemType* PushBack(const T& value); ItemType* PushFront(const T& value); void PopBack(); void PopFront(); // Item can be null - it means PushBack. ItemType* InsertBefore(ItemType* pItem); // Item can be null - it means PushFront. ItemType* InsertAfter(ItemType* pItem); ItemType* InsertBefore(ItemType* pItem, const T& value); ItemType* InsertAfter(ItemType* pItem, const T& value); void Remove(ItemType* pItem); private: const VkAllocationCallbacks* const m_pAllocationCallbacks; VmaPoolAllocator<ItemType> m_ItemAllocator; ItemType* m_pFront; ItemType* m_pBack; size_t m_Count; }; template<typename T> VmaRawList<T>::VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks) : m_pAllocationCallbacks(pAllocationCallbacks), m_ItemAllocator(pAllocationCallbacks, 128), m_pFront(VMA_NULL), m_pBack(VMA_NULL), m_Count(0) { } template<typename T> VmaRawList<T>::~VmaRawList() { // Intentionally not calling Clear, because that would be unnecessary // computations to return all items to m_ItemAllocator as free. } template<typename T> void VmaRawList<T>::Clear() { if (IsEmpty() == false) { ItemType* pItem = m_pBack; while (pItem != VMA_NULL) { ItemType* const pPrevItem = pItem->pPrev; m_ItemAllocator.Free(pItem); pItem = pPrevItem; } m_pFront = VMA_NULL; m_pBack = VMA_NULL; m_Count = 0; } } template<typename T> VmaListItem<T>* VmaRawList<T>::PushBack() { ItemType* const pNewItem = m_ItemAllocator.Alloc(); pNewItem->pNext = VMA_NULL; if (IsEmpty()) { pNewItem->pPrev = VMA_NULL; m_pFront = pNewItem; m_pBack = pNewItem; m_Count = 1; } else { pNewItem->pPrev = m_pBack; m_pBack->pNext = pNewItem; m_pBack = pNewItem; ++m_Count; } return pNewItem; } template<typename T> VmaListItem<T>* VmaRawList<T>::PushFront() { ItemType* const pNewItem = m_ItemAllocator.Alloc(); pNewItem->pPrev = VMA_NULL; if (IsEmpty()) { pNewItem->pNext = VMA_NULL; m_pFront = pNewItem; m_pBack = pNewItem; m_Count = 1; } else { pNewItem->pNext = m_pFront; m_pFront->pPrev = pNewItem; m_pFront = pNewItem; ++m_Count; } return pNewItem; } template<typename T> VmaListItem<T>* VmaRawList<T>::PushBack(const T& value) { ItemType* const pNewItem = PushBack(); pNewItem->Value = value; return pNewItem; } template<typename T> VmaListItem<T>* VmaRawList<T>::PushFront(const T& value) { ItemType* const pNewItem = PushFront(); pNewItem->Value = value; return pNewItem; } template<typename T> void VmaRawList<T>::PopBack() { VMA_HEAVY_ASSERT(m_Count > 0); ItemType* const pBackItem = m_pBack; ItemType* const pPrevItem = pBackItem->pPrev; if (pPrevItem != VMA_NULL) { pPrevItem->pNext = VMA_NULL; } m_pBack = pPrevItem; m_ItemAllocator.Free(pBackItem); --m_Count; } template<typename T> void VmaRawList<T>::PopFront() { VMA_HEAVY_ASSERT(m_Count > 0); ItemType* const pFrontItem = m_pFront; ItemType* const pNextItem = pFrontItem->pNext; if (pNextItem != VMA_NULL) { pNextItem->pPrev = VMA_NULL; } m_pFront = pNextItem; m_ItemAllocator.Free(pFrontItem); --m_Count; } template<typename T> void VmaRawList<T>::Remove(ItemType* pItem) { VMA_HEAVY_ASSERT(pItem != VMA_NULL); VMA_HEAVY_ASSERT(m_Count > 0); if (pItem->pPrev != VMA_NULL) { pItem->pPrev->pNext = pItem->pNext; } else { VMA_HEAVY_ASSERT(m_pFront == pItem); m_pFront = pItem->pNext; } if (pItem->pNext != VMA_NULL) { pItem->pNext->pPrev = pItem->pPrev; } else { VMA_HEAVY_ASSERT(m_pBack == pItem); m_pBack = pItem->pPrev; } m_ItemAllocator.Free(pItem); --m_Count; } template<typename T> VmaListItem<T>* VmaRawList<T>::InsertBefore(ItemType* pItem) { if (pItem != VMA_NULL) { ItemType* const prevItem = pItem->pPrev; ItemType* const newItem = m_ItemAllocator.Alloc(); newItem->pPrev = prevItem; newItem->pNext = pItem; pItem->pPrev = newItem; if (prevItem != VMA_NULL) { prevItem->pNext = newItem; } else { VMA_HEAVY_ASSERT(m_pFront == pItem); m_pFront = newItem; } ++m_Count; return newItem; } else return PushBack(); } template<typename T> VmaListItem<T>* VmaRawList<T>::InsertAfter(ItemType* pItem) { if (pItem != VMA_NULL) { ItemType* const nextItem = pItem->pNext; ItemType* const newItem = m_ItemAllocator.Alloc(); newItem->pNext = nextItem; newItem->pPrev = pItem; pItem->pNext = newItem; if (nextItem != VMA_NULL) { nextItem->pPrev = newItem; } else { VMA_HEAVY_ASSERT(m_pBack == pItem); m_pBack = newItem; } ++m_Count; return newItem; } else return PushFront(); } template<typename T> VmaListItem<T>* VmaRawList<T>::InsertBefore(ItemType* pItem, const T& value) { ItemType* const newItem = InsertBefore(pItem); newItem->Value = value; return newItem; } template<typename T> VmaListItem<T>* VmaRawList<T>::InsertAfter(ItemType* pItem, const T& value) { ItemType* const newItem = InsertAfter(pItem); newItem->Value = value; return newItem; } template<typename T, typename AllocatorT> class VmaList { VMA_CLASS_NO_COPY(VmaList) public: class iterator { public: iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) { } T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; } T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; } iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pNext; return *this; } iterator& operator--() { if (m_pItem != VMA_NULL) { m_pItem = m_pItem->pPrev; } else { VMA_HEAVY_ASSERT(!m_pList->IsEmpty()); m_pItem = m_pList->Back(); } return *this; } iterator operator++(int) { iterator result = *this; ++* this; return result; } iterator operator--(int) { iterator result = *this; --* this; return result; } bool operator==(const iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; } bool operator!=(const iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } private: VmaRawList<T>* m_pList; VmaListItem<T>* m_pItem; iterator(VmaRawList<T>* pList, VmaListItem<T>* pItem) : m_pList(pList), m_pItem(pItem) { } friend class VmaList<T, AllocatorT>; }; class const_iterator { public: const_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) { } const_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) { } const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; } const T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; } const_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pNext; return *this; } const_iterator& operator--() { if (m_pItem != VMA_NULL) { m_pItem = m_pItem->pPrev; } else { VMA_HEAVY_ASSERT(!m_pList->IsEmpty()); m_pItem = m_pList->Back(); } return *this; } const_iterator operator++(int) { const_iterator result = *this; ++* this; return result; } const_iterator operator--(int) { const_iterator result = *this; --* this; return result; } bool operator==(const const_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; } bool operator!=(const const_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } private: const_iterator(const VmaRawList<T>* pList, const VmaListItem<T>* pItem) : m_pList(pList), m_pItem(pItem) { } const VmaRawList<T>* m_pList; const VmaListItem<T>* m_pItem; friend class VmaList<T, AllocatorT>; }; VmaList(const AllocatorT& allocator) : m_RawList(allocator.m_pCallbacks) { } bool empty() const { return m_RawList.IsEmpty(); } size_t size() const { return m_RawList.GetCount(); } iterator begin() { return iterator(&m_RawList, m_RawList.Front()); } iterator end() { return iterator(&m_RawList, VMA_NULL); } const_iterator cbegin() const { return const_iterator(&m_RawList, m_RawList.Front()); } const_iterator cend() const { return const_iterator(&m_RawList, VMA_NULL); } void clear() { m_RawList.Clear(); } void push_back(const T& value) { m_RawList.PushBack(value); } void erase(iterator it) { m_RawList.Remove(it.m_pItem); } iterator insert(iterator it, const T& value) { return iterator(&m_RawList, m_RawList.InsertBefore(it.m_pItem, value)); } private: VmaRawList<T> m_RawList; }; #endif // #if VMA_USE_STL_LIST //////////////////////////////////////////////////////////////////////////////// // class VmaMap // Unused in this version. #if 0 #if VMA_USE_STL_UNORDERED_MAP #define VmaPair std::pair #define VMA_MAP_TYPE(KeyT, ValueT) \ std::unordered_map< KeyT, ValueT, std::hash<KeyT>, std::equal_to<KeyT>, VmaStlAllocator< std::pair<KeyT, ValueT> > > #else // #if VMA_USE_STL_UNORDERED_MAP template<typename T1, typename T2> struct VmaPair { T1 first; T2 second; VmaPair() : first(), second() { } VmaPair(const T1& firstSrc, const T2& secondSrc) : first(firstSrc), second(secondSrc) { } }; /* Class compatible with subset of interface of std::unordered_map. KeyT, ValueT must be POD because they will be stored in VmaVector. */ template<typename KeyT, typename ValueT> class VmaMap { public: typedef VmaPair<KeyT, ValueT> PairType; typedef PairType* iterator; VmaMap(const VmaStlAllocator<PairType>& allocator) : m_Vector(allocator) { } iterator begin() { return m_Vector.begin(); } iterator end() { return m_Vector.end(); } void insert(const PairType& pair); iterator find(const KeyT& key); void erase(iterator it); private: VmaVector< PairType, VmaStlAllocator<PairType> > m_Vector; }; #define VMA_MAP_TYPE(KeyT, ValueT) VmaMap<KeyT, ValueT> template<typename FirstT, typename SecondT> struct VmaPairFirstLess { bool operator()(const VmaPair<FirstT, SecondT>& lhs, const VmaPair<FirstT, SecondT>& rhs) const { return lhs.first < rhs.first; } bool operator()(const VmaPair<FirstT, SecondT>& lhs, const FirstT& rhsFirst) const { return lhs.first < rhsFirst; } }; template<typename KeyT, typename ValueT> void VmaMap<KeyT, ValueT>::insert(const PairType& pair) { const size_t indexToInsert = VmaBinaryFindFirstNotLess( m_Vector.data(), m_Vector.data() + m_Vector.size(), pair, VmaPairFirstLess<KeyT, ValueT>()) - m_Vector.data(); VmaVectorInsert(m_Vector, indexToInsert, pair); } template<typename KeyT, typename ValueT> VmaPair<KeyT, ValueT>* VmaMap<KeyT, ValueT>::find(const KeyT& key) { PairType* it = VmaBinaryFindFirstNotLess( m_Vector.data(), m_Vector.data() + m_Vector.size(), key, VmaPairFirstLess<KeyT, ValueT>()); if ((it != m_Vector.end()) && (it->first == key)) { return it; } else { return m_Vector.end(); } } template<typename KeyT, typename ValueT> void VmaMap<KeyT, ValueT>::erase(iterator it) { VmaVectorRemove(m_Vector, it - m_Vector.begin()); } #endif // #if VMA_USE_STL_UNORDERED_MAP #endif // #if 0 //////////////////////////////////////////////////////////////////////////////// class VmaDeviceMemoryBlock; enum VMA_CACHE_OPERATION { VMA_CACHE_FLUSH, VMA_CACHE_INVALIDATE }; struct VmaAllocation_T { private: static const uint8_t MAP_COUNT_FLAG_PERSISTENT_MAP = 0x80; enum FLAGS { FLAG_USER_DATA_STRING = 0x01, }; public: enum ALLOCATION_TYPE { ALLOCATION_TYPE_NONE, ALLOCATION_TYPE_BLOCK, ALLOCATION_TYPE_DEDICATED, }; /* This struct is allocated using VmaPoolAllocator. */ void Ctor(uint32_t currentFrameIndex, bool userDataString) { m_Alignment = 1; m_Size = 0; m_MemoryTypeIndex = 0; m_pUserData = VMA_NULL; m_LastUseFrameIndex = currentFrameIndex; m_Type = (uint8_t)ALLOCATION_TYPE_NONE; m_SuballocationType = (uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN; m_MapCount = 0; m_Flags = userDataString ? (uint8_t)FLAG_USER_DATA_STRING : 0; #if VMA_STATS_STRING_ENABLED m_CreationFrameIndex = currentFrameIndex; m_BufferImageUsage = 0; #endif } void Dtor() { VMA_ASSERT((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) == 0 && "Allocation was not unmapped before destruction."); // Check if owned string was freed. VMA_ASSERT(m_pUserData == VMA_NULL); } void InitBlockAllocation( VmaDeviceMemoryBlock* block, VkDeviceSize offset, VkDeviceSize alignment, VkDeviceSize size, uint32_t memoryTypeIndex, VmaSuballocationType suballocationType, bool mapped, bool canBecomeLost) { VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); VMA_ASSERT(block != VMA_NULL); m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; m_Alignment = alignment; m_Size = size; m_MemoryTypeIndex = memoryTypeIndex; m_MapCount = mapped ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0; m_SuballocationType = (uint8_t)suballocationType; m_BlockAllocation.m_Block = block; m_BlockAllocation.m_Offset = offset; m_BlockAllocation.m_CanBecomeLost = canBecomeLost; } void InitLost() { VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); VMA_ASSERT(m_LastUseFrameIndex.load() == VMA_FRAME_INDEX_LOST); m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; m_MemoryTypeIndex = 0; m_BlockAllocation.m_Block = VMA_NULL; m_BlockAllocation.m_Offset = 0; m_BlockAllocation.m_CanBecomeLost = true; } void ChangeBlockAllocation( VmaAllocator hAllocator, VmaDeviceMemoryBlock* block, VkDeviceSize offset); void ChangeOffset(VkDeviceSize newOffset); // pMappedData not null means allocation is created with MAPPED flag. void InitDedicatedAllocation( uint32_t memoryTypeIndex, VkDeviceMemory hMemory, VmaSuballocationType suballocationType, void* pMappedData, VkDeviceSize size) { VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); VMA_ASSERT(hMemory != VK_NULL_HANDLE); m_Type = (uint8_t)ALLOCATION_TYPE_DEDICATED; m_Alignment = 0; m_Size = size; m_MemoryTypeIndex = memoryTypeIndex; m_SuballocationType = (uint8_t)suballocationType; m_MapCount = (pMappedData != VMA_NULL) ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0; m_DedicatedAllocation.m_hMemory = hMemory; m_DedicatedAllocation.m_pMappedData = pMappedData; } ALLOCATION_TYPE GetType() const { return (ALLOCATION_TYPE)m_Type; } VkDeviceSize GetAlignment() const { return m_Alignment; } VkDeviceSize GetSize() const { return m_Size; } bool IsUserDataString() const { return (m_Flags & FLAG_USER_DATA_STRING) != 0; } void* GetUserData() const { return m_pUserData; } void SetUserData(VmaAllocator hAllocator, void* pUserData); VmaSuballocationType GetSuballocationType() const { return (VmaSuballocationType)m_SuballocationType; } VmaDeviceMemoryBlock* GetBlock() const { VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); return m_BlockAllocation.m_Block; } VkDeviceSize GetOffset() const; VkDeviceMemory GetMemory() const; uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } bool IsPersistentMap() const { return (m_MapCount & MAP_COUNT_FLAG_PERSISTENT_MAP) != 0; } void* GetMappedData() const; bool CanBecomeLost() const; uint32_t GetLastUseFrameIndex() const { return m_LastUseFrameIndex.load(); } bool CompareExchangeLastUseFrameIndex(uint32_t& expected, uint32_t desired) { return m_LastUseFrameIndex.compare_exchange_weak(expected, desired); } /* - If hAllocation.LastUseFrameIndex + frameInUseCount < allocator.CurrentFrameIndex, makes it lost by setting LastUseFrameIndex = VMA_FRAME_INDEX_LOST and returns true. - Else, returns false. If hAllocation is already lost, assert - you should not call it then. If hAllocation was not created with CAN_BECOME_LOST_BIT, assert. */ bool MakeLost(uint32_t currentFrameIndex, uint32_t frameInUseCount); void DedicatedAllocCalcStatsInfo(VmaStatInfo& outInfo) { VMA_ASSERT(m_Type == ALLOCATION_TYPE_DEDICATED); outInfo.blockCount = 1; outInfo.allocationCount = 1; outInfo.unusedRangeCount = 0; outInfo.usedBytes = m_Size; outInfo.unusedBytes = 0; outInfo.allocationSizeMin = outInfo.allocationSizeMax = m_Size; outInfo.unusedRangeSizeMin = UINT64_MAX; outInfo.unusedRangeSizeMax = 0; } void BlockAllocMap(); void BlockAllocUnmap(); VkResult DedicatedAllocMap(VmaAllocator hAllocator, void** ppData); void DedicatedAllocUnmap(VmaAllocator hAllocator); #if VMA_STATS_STRING_ENABLED uint32_t GetCreationFrameIndex() const { return m_CreationFrameIndex; } uint32_t GetBufferImageUsage() const { return m_BufferImageUsage; } void InitBufferImageUsage(uint32_t bufferImageUsage) { VMA_ASSERT(m_BufferImageUsage == 0); m_BufferImageUsage = bufferImageUsage; } void PrintParameters(class VmaJsonWriter& json) const; #endif private: VkDeviceSize m_Alignment; VkDeviceSize m_Size; void* m_pUserData; VMA_ATOMIC_UINT32 m_LastUseFrameIndex; uint32_t m_MemoryTypeIndex; uint8_t m_Type; // ALLOCATION_TYPE uint8_t m_SuballocationType; // VmaSuballocationType // Bit 0x80 is set when allocation was created with VMA_ALLOCATION_CREATE_MAPPED_BIT. // Bits with mask 0x7F are reference counter for vmaMapMemory()/vmaUnmapMemory(). uint8_t m_MapCount; uint8_t m_Flags; // enum FLAGS // Allocation out of VmaDeviceMemoryBlock. struct BlockAllocation { VmaDeviceMemoryBlock* m_Block; VkDeviceSize m_Offset; bool m_CanBecomeLost; }; // Allocation for an object that has its own private VkDeviceMemory. struct DedicatedAllocation { VkDeviceMemory m_hMemory; void* m_pMappedData; // Not null means memory is mapped. }; union { // Allocation out of VmaDeviceMemoryBlock. BlockAllocation m_BlockAllocation; // Allocation for an object that has its own private VkDeviceMemory. DedicatedAllocation m_DedicatedAllocation; }; #if VMA_STATS_STRING_ENABLED uint32_t m_CreationFrameIndex; uint32_t m_BufferImageUsage; // 0 if unknown. #endif void FreeUserDataString(VmaAllocator hAllocator); }; /* Represents a region of VmaDeviceMemoryBlock that is either assigned and returned as allocated memory block or free. */ struct VmaSuballocation { VkDeviceSize offset; VkDeviceSize size; VmaAllocation hAllocation; VmaSuballocationType type; }; // Comparator for offsets. struct VmaSuballocationOffsetLess { bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const { return lhs.offset < rhs.offset; } }; struct VmaSuballocationOffsetGreater { bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const { return lhs.offset > rhs.offset; } }; typedef VmaList< VmaSuballocation, VmaStlAllocator<VmaSuballocation> > VmaSuballocationList; // Cost of one additional allocation lost, as equivalent in bytes. static const VkDeviceSize VMA_LOST_ALLOCATION_COST = 1048576; enum class VmaAllocationRequestType { Normal, // Used by "Linear" algorithm. UpperAddress, EndOf1st, EndOf2nd, }; /* Parameters of planned allocation inside a VmaDeviceMemoryBlock. If canMakeOtherLost was false: - item points to a FREE suballocation. - itemsToMakeLostCount is 0. If canMakeOtherLost was true: - item points to first of sequence of suballocations, which are either FREE, or point to VmaAllocations that can become lost. - itemsToMakeLostCount is the number of VmaAllocations that need to be made lost for the requested allocation to succeed. */ struct VmaAllocationRequest { VkDeviceSize offset; VkDeviceSize sumFreeSize; // Sum size of free items that overlap with proposed allocation. VkDeviceSize sumItemSize; // Sum size of items to make lost that overlap with proposed allocation. VmaSuballocationList::iterator item; size_t itemsToMakeLostCount; void* customData; VmaAllocationRequestType type; VkDeviceSize CalcCost() const { return sumItemSize + itemsToMakeLostCount * VMA_LOST_ALLOCATION_COST; } }; /* Data structure used for bookkeeping of allocations and unused ranges of memory in a single VkDeviceMemory block. */ class VmaBlockMetadata { public: VmaBlockMetadata(VmaAllocator hAllocator); virtual ~VmaBlockMetadata() { } virtual void Init(VkDeviceSize size) { m_Size = size; } // Validates all data structures inside this object. If not valid, returns false. virtual bool Validate() const = 0; VkDeviceSize GetSize() const { return m_Size; } virtual size_t GetAllocationCount() const = 0; virtual VkDeviceSize GetSumFreeSize() const = 0; virtual VkDeviceSize GetUnusedRangeSizeMax() const = 0; // Returns true if this block is empty - contains only single free suballocation. virtual bool IsEmpty() const = 0; virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const = 0; // Shouldn't modify blockCount. virtual void AddPoolStats(VmaPoolStats& inoutStats) const = 0; #if VMA_STATS_STRING_ENABLED virtual void PrintDetailedMap(class VmaJsonWriter& json) const = 0; #endif // Tries to find a place for suballocation with given parameters inside this block. // If succeeded, fills pAllocationRequest and returns true. // If failed, returns false. virtual bool CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, // Always one of VMA_ALLOCATION_CREATE_STRATEGY_* or VMA_ALLOCATION_INTERNAL_STRATEGY_* flags. uint32_t strategy, VmaAllocationRequest* pAllocationRequest) = 0; virtual bool MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest) = 0; virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) = 0; virtual VkResult CheckCorruption(const void* pBlockData) = 0; // Makes actual allocation based on request. Request must already be checked and valid. virtual void Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation) = 0; // Frees suballocation assigned to given memory region. virtual void Free(const VmaAllocation allocation) = 0; virtual void FreeAtOffset(VkDeviceSize offset) = 0; protected: const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; } #if VMA_STATS_STRING_ENABLED void PrintDetailedMap_Begin(class VmaJsonWriter& json, VkDeviceSize unusedBytes, size_t allocationCount, size_t unusedRangeCount) const; void PrintDetailedMap_Allocation(class VmaJsonWriter& json, VkDeviceSize offset, VmaAllocation hAllocation) const; void PrintDetailedMap_UnusedRange(class VmaJsonWriter& json, VkDeviceSize offset, VkDeviceSize size) const; void PrintDetailedMap_End(class VmaJsonWriter& json) const; #endif private: VkDeviceSize m_Size; const VkAllocationCallbacks* m_pAllocationCallbacks; }; #define VMA_VALIDATE(cond) do { if(!(cond)) { \ VMA_ASSERT(0 && "Validation failed: " #cond); \ return false; \ } } while(false) class VmaBlockMetadata_Generic : public VmaBlockMetadata { VMA_CLASS_NO_COPY(VmaBlockMetadata_Generic) public: VmaBlockMetadata_Generic(VmaAllocator hAllocator); virtual ~VmaBlockMetadata_Generic(); virtual void Init(VkDeviceSize size); virtual bool Validate() const; virtual size_t GetAllocationCount() const { return m_Suballocations.size() - m_FreeCount; } virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize; } virtual VkDeviceSize GetUnusedRangeSizeMax() const; virtual bool IsEmpty() const; virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const; virtual void AddPoolStats(VmaPoolStats& inoutStats) const; #if VMA_STATS_STRING_ENABLED virtual void PrintDetailedMap(class VmaJsonWriter& json) const; #endif virtual bool CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest); virtual bool MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest); virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount); virtual VkResult CheckCorruption(const void* pBlockData); virtual void Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation); virtual void Free(const VmaAllocation allocation); virtual void FreeAtOffset(VkDeviceSize offset); //////////////////////////////////////////////////////////////////////////////// // For defragmentation bool IsBufferImageGranularityConflictPossible( VkDeviceSize bufferImageGranularity, VmaSuballocationType& inOutPrevSuballocType) const; private: friend class VmaDefragmentationAlgorithm_Generic; friend class VmaDefragmentationAlgorithm_Fast; uint32_t m_FreeCount; VkDeviceSize m_SumFreeSize; VmaSuballocationList m_Suballocations; // Suballocations that are free and have size greater than certain threshold. // Sorted by size, ascending. VmaVector< VmaSuballocationList::iterator, VmaStlAllocator< VmaSuballocationList::iterator > > m_FreeSuballocationsBySize; bool ValidateFreeSuballocationList() const; // Checks if requested suballocation with given parameters can be placed in given pFreeSuballocItem. // If yes, fills pOffset and returns true. If no, returns false. bool CheckAllocation( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, VmaSuballocationType allocType, VmaSuballocationList::const_iterator suballocItem, bool canMakeOtherLost, VkDeviceSize* pOffset, size_t* itemsToMakeLostCount, VkDeviceSize* pSumFreeSize, VkDeviceSize* pSumItemSize) const; // Given free suballocation, it merges it with following one, which must also be free. void MergeFreeWithNext(VmaSuballocationList::iterator item); // Releases given suballocation, making it free. // Merges it with adjacent free suballocations if applicable. // Returns iterator to new free suballocation at this place. VmaSuballocationList::iterator FreeSuballocation(VmaSuballocationList::iterator suballocItem); // Given free suballocation, it inserts it into sorted list of // m_FreeSuballocationsBySize if it's suitable. void RegisterFreeSuballocation(VmaSuballocationList::iterator item); // Given free suballocation, it removes it from sorted list of // m_FreeSuballocationsBySize if it's suitable. void UnregisterFreeSuballocation(VmaSuballocationList::iterator item); }; /* Allocations and their references in internal data structure look like this: if(m_2ndVectorMode == SECOND_VECTOR_EMPTY): 0 +-------+ | | | | | | +-------+ | Alloc | 1st[m_1stNullItemsBeginCount] +-------+ | Alloc | 1st[m_1stNullItemsBeginCount + 1] +-------+ | ... | +-------+ | Alloc | 1st[1st.size() - 1] +-------+ | | | | | | GetSize() +-------+ if(m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER): 0 +-------+ | Alloc | 2nd[0] +-------+ | Alloc | 2nd[1] +-------+ | ... | +-------+ | Alloc | 2nd[2nd.size() - 1] +-------+ | | | | | | +-------+ | Alloc | 1st[m_1stNullItemsBeginCount] +-------+ | Alloc | 1st[m_1stNullItemsBeginCount + 1] +-------+ | ... | +-------+ | Alloc | 1st[1st.size() - 1] +-------+ | | GetSize() +-------+ if(m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK): 0 +-------+ | | | | | | +-------+ | Alloc | 1st[m_1stNullItemsBeginCount] +-------+ | Alloc | 1st[m_1stNullItemsBeginCount + 1] +-------+ | ... | +-------+ | Alloc | 1st[1st.size() - 1] +-------+ | | | | | | +-------+ | Alloc | 2nd[2nd.size() - 1] +-------+ | ... | +-------+ | Alloc | 2nd[1] +-------+ | Alloc | 2nd[0] GetSize() +-------+ */ class VmaBlockMetadata_Linear : public VmaBlockMetadata { VMA_CLASS_NO_COPY(VmaBlockMetadata_Linear) public: VmaBlockMetadata_Linear(VmaAllocator hAllocator); virtual ~VmaBlockMetadata_Linear(); virtual void Init(VkDeviceSize size); virtual bool Validate() const; virtual size_t GetAllocationCount() const; virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize; } virtual VkDeviceSize GetUnusedRangeSizeMax() const; virtual bool IsEmpty() const { return GetAllocationCount() == 0; } virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const; virtual void AddPoolStats(VmaPoolStats& inoutStats) const; #if VMA_STATS_STRING_ENABLED virtual void PrintDetailedMap(class VmaJsonWriter& json) const; #endif virtual bool CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest); virtual bool MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest); virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount); virtual VkResult CheckCorruption(const void* pBlockData); virtual void Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation); virtual void Free(const VmaAllocation allocation); virtual void FreeAtOffset(VkDeviceSize offset); private: /* There are two suballocation vectors, used in ping-pong way. The one with index m_1stVectorIndex is called 1st. The one with index (m_1stVectorIndex ^ 1) is called 2nd. 2nd can be non-empty only when 1st is not empty. When 2nd is not empty, m_2ndVectorMode indicates its mode of operation. */ typedef VmaVector< VmaSuballocation, VmaStlAllocator<VmaSuballocation> > SuballocationVectorType; enum SECOND_VECTOR_MODE { SECOND_VECTOR_EMPTY, /* Suballocations in 2nd vector are created later than the ones in 1st, but they all have smaller offset. */ SECOND_VECTOR_RING_BUFFER, /* Suballocations in 2nd vector are upper side of double stack. They all have offsets higher than those in 1st vector. Top of this stack means smaller offsets, but higher indices in this vector. */ SECOND_VECTOR_DOUBLE_STACK, }; VkDeviceSize m_SumFreeSize; SuballocationVectorType m_Suballocations0, m_Suballocations1; uint32_t m_1stVectorIndex; SECOND_VECTOR_MODE m_2ndVectorMode; SuballocationVectorType& AccessSuballocations1st() { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } // Number of items in 1st vector with hAllocation = null at the beginning. size_t m_1stNullItemsBeginCount; // Number of other items in 1st vector with hAllocation = null somewhere in the middle. size_t m_1stNullItemsMiddleCount; // Number of items in 2nd vector with hAllocation = null. size_t m_2ndNullItemsCount; bool ShouldCompact1st() const; void CleanupAfterFree(); bool CreateAllocationRequest_LowerAddress( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest); bool CreateAllocationRequest_UpperAddress( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest); }; /* - GetSize() is the original size of allocated memory block. - m_UsableSize is this size aligned down to a power of two. All allocations and calculations happen relative to m_UsableSize. - GetUnusableSize() is the difference between them. It is repoted as separate, unused range, not available for allocations. Node at level 0 has size = m_UsableSize. Each next level contains nodes with size 2 times smaller than current level. m_LevelCount is the maximum number of levels to use in the current object. */ class VmaBlockMetadata_Buddy : public VmaBlockMetadata { VMA_CLASS_NO_COPY(VmaBlockMetadata_Buddy) public: VmaBlockMetadata_Buddy(VmaAllocator hAllocator); virtual ~VmaBlockMetadata_Buddy(); virtual void Init(VkDeviceSize size); virtual bool Validate() const; virtual size_t GetAllocationCount() const { return m_AllocationCount; } virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize + GetUnusableSize(); } virtual VkDeviceSize GetUnusedRangeSizeMax() const; virtual bool IsEmpty() const { return m_Root->type == Node::TYPE_FREE; } virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const; virtual void AddPoolStats(VmaPoolStats& inoutStats) const; #if VMA_STATS_STRING_ENABLED virtual void PrintDetailedMap(class VmaJsonWriter& json) const; #endif virtual bool CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest); virtual bool MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest); virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount); virtual VkResult CheckCorruption(const void* pBlockData) { return VK_ERROR_FEATURE_NOT_PRESENT; } virtual void Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation); virtual void Free(const VmaAllocation allocation) { FreeAtOffset(allocation, allocation->GetOffset()); } virtual void FreeAtOffset(VkDeviceSize offset) { FreeAtOffset(VMA_NULL, offset); } private: static const VkDeviceSize MIN_NODE_SIZE = 32; static const size_t MAX_LEVELS = 30; struct ValidationContext { size_t calculatedAllocationCount; size_t calculatedFreeCount; VkDeviceSize calculatedSumFreeSize; ValidationContext() : calculatedAllocationCount(0), calculatedFreeCount(0), calculatedSumFreeSize(0) { } }; struct Node { VkDeviceSize offset; enum TYPE { TYPE_FREE, TYPE_ALLOCATION, TYPE_SPLIT, TYPE_COUNT } type; Node* parent; Node* buddy; union { struct { Node* prev; Node* next; } free; struct { VmaAllocation alloc; } allocation; struct { Node* leftChild; } split; }; }; // Size of the memory block aligned down to a power of two. VkDeviceSize m_UsableSize; uint32_t m_LevelCount; Node* m_Root; struct { Node* front; Node* back; } m_FreeList[MAX_LEVELS]; // Number of nodes in the tree with type == TYPE_ALLOCATION. size_t m_AllocationCount; // Number of nodes in the tree with type == TYPE_FREE. size_t m_FreeCount; // This includes space wasted due to internal fragmentation. Doesn't include unusable size. VkDeviceSize m_SumFreeSize; VkDeviceSize GetUnusableSize() const { return GetSize() - m_UsableSize; } void DeleteNode(Node* node); bool ValidateNode(ValidationContext& ctx, const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const; uint32_t AllocSizeToLevel(VkDeviceSize allocSize) const; inline VkDeviceSize LevelToNodeSize(uint32_t level) const { return m_UsableSize >> level; } // Alloc passed just for validation. Can be null. void FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset); void CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const; // Adds node to the front of FreeList at given level. // node->type must be FREE. // node->free.prev, next can be undefined. void AddToFreeListFront(uint32_t level, Node* node); // Removes node from FreeList at given level. // node->type must be FREE. // node->free.prev, next stay untouched. void RemoveFromFreeList(uint32_t level, Node* node); #if VMA_STATS_STRING_ENABLED void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const; #endif }; /* Represents a single block of device memory (`VkDeviceMemory`) with all the data about its regions (aka suballocations, #VmaAllocation), assigned and free. Thread-safety: This class must be externally synchronized. */ class VmaDeviceMemoryBlock { VMA_CLASS_NO_COPY(VmaDeviceMemoryBlock) public: VmaBlockMetadata* m_pMetadata; VmaDeviceMemoryBlock(VmaAllocator hAllocator); ~VmaDeviceMemoryBlock() { VMA_ASSERT(m_MapCount == 0 && "VkDeviceMemory block is being destroyed while it is still mapped."); VMA_ASSERT(m_hMemory == VK_NULL_HANDLE); } // Always call after construction. void Init( VmaAllocator hAllocator, VmaPool hParentPool, uint32_t newMemoryTypeIndex, VkDeviceMemory newMemory, VkDeviceSize newSize, uint32_t id, uint32_t algorithm); // Always call before destruction. void Destroy(VmaAllocator allocator); VmaPool GetParentPool() const { return m_hParentPool; } VkDeviceMemory GetDeviceMemory() const { return m_hMemory; } uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } uint32_t GetId() const { return m_Id; } void* GetMappedData() const { return m_pMappedData; } // Validates all data structures inside this object. If not valid, returns false. bool Validate() const; VkResult CheckCorruption(VmaAllocator hAllocator); // ppData can be null. VkResult Map(VmaAllocator hAllocator, uint32_t count, void** ppData); void Unmap(VmaAllocator hAllocator, uint32_t count); VkResult WriteMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize); VkResult ValidateMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize); VkResult BindBufferMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkBuffer hBuffer, const void* pNext); VkResult BindImageMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkImage hImage, const void* pNext); private: VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool. uint32_t m_MemoryTypeIndex; uint32_t m_Id; VkDeviceMemory m_hMemory; /* Protects access to m_hMemory so it's not used by multiple threads simultaneously, e.g. vkMapMemory, vkBindBufferMemory. Also protects m_MapCount, m_pMappedData. Allocations, deallocations, any change in m_pMetadata is protected by parent's VmaBlockVector::m_Mutex. */ VMA_MUTEX m_Mutex; uint32_t m_MapCount; void* m_pMappedData; }; struct VmaPointerLess { bool operator()(const void* lhs, const void* rhs) const { return lhs < rhs; } }; struct VmaDefragmentationMove { size_t srcBlockIndex; size_t dstBlockIndex; VkDeviceSize srcOffset; VkDeviceSize dstOffset; VkDeviceSize size; }; class VmaDefragmentationAlgorithm; /* Sequence of VmaDeviceMemoryBlock. Represents memory blocks allocated for a specific Vulkan memory type. Synchronized internally with a mutex. */ struct VmaBlockVector { VMA_CLASS_NO_COPY(VmaBlockVector) public: VmaBlockVector( VmaAllocator hAllocator, VmaPool hParentPool, uint32_t memoryTypeIndex, VkDeviceSize preferredBlockSize, size_t minBlockCount, size_t maxBlockCount, VkDeviceSize bufferImageGranularity, uint32_t frameInUseCount, bool explicitBlockSize, uint32_t algorithm); ~VmaBlockVector(); VkResult CreateMinBlocks(); VmaAllocator GetAllocator() const { return m_hAllocator; } VmaPool GetParentPool() const { return m_hParentPool; } bool IsCustomPool() const { return m_hParentPool != VMA_NULL; } uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } VkDeviceSize GetPreferredBlockSize() const { return m_PreferredBlockSize; } VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; } uint32_t GetFrameInUseCount() const { return m_FrameInUseCount; } uint32_t GetAlgorithm() const { return m_Algorithm; } void GetPoolStats(VmaPoolStats* pStats); bool IsEmpty(); bool IsCorruptionDetectionEnabled() const; VkResult Allocate( uint32_t currentFrameIndex, VkDeviceSize size, VkDeviceSize alignment, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, size_t allocationCount, VmaAllocation* pAllocations); void Free(const VmaAllocation hAllocation); // Adds statistics of this BlockVector to pStats. void AddStats(VmaStats* pStats); #if VMA_STATS_STRING_ENABLED void PrintDetailedMap(class VmaJsonWriter& json); #endif void MakePoolAllocationsLost( uint32_t currentFrameIndex, size_t* pLostAllocationCount); VkResult CheckCorruption(); // Saves results in pCtx->res. void Defragment( class VmaBlockVectorDefragmentationContext* pCtx, VmaDefragmentationStats* pStats, VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove, VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove, VkCommandBuffer commandBuffer); void DefragmentationEnd( class VmaBlockVectorDefragmentationContext* pCtx, VmaDefragmentationStats* pStats); //////////////////////////////////////////////////////////////////////////////// // To be used only while the m_Mutex is locked. Used during defragmentation. size_t GetBlockCount() const { return m_Blocks.size(); } VmaDeviceMemoryBlock* GetBlock(size_t index) const { return m_Blocks[index]; } size_t CalcAllocationCount() const; bool IsBufferImageGranularityConflictPossible() const; private: friend class VmaDefragmentationAlgorithm_Generic; const VmaAllocator m_hAllocator; const VmaPool m_hParentPool; const uint32_t m_MemoryTypeIndex; const VkDeviceSize m_PreferredBlockSize; const size_t m_MinBlockCount; const size_t m_MaxBlockCount; const VkDeviceSize m_BufferImageGranularity; const uint32_t m_FrameInUseCount; const bool m_ExplicitBlockSize; const uint32_t m_Algorithm; VMA_RW_MUTEX m_Mutex; /* There can be at most one allocation that is completely empty (except when minBlockCount > 0) - a hysteresis to avoid pessimistic case of alternating creation and destruction of a VkDeviceMemory. */ bool m_HasEmptyBlock; // Incrementally sorted by sumFreeSize, ascending. VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator<VmaDeviceMemoryBlock*> > m_Blocks; uint32_t m_NextBlockId; VkDeviceSize CalcMaxBlockSize() const; // Finds and removes given block from vector. void Remove(VmaDeviceMemoryBlock* pBlock); // Performs single step in sorting m_Blocks. They may not be fully sorted // after this call. void IncrementallySortBlocks(); VkResult AllocatePage( uint32_t currentFrameIndex, VkDeviceSize size, VkDeviceSize alignment, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, VmaAllocation* pAllocation); // To be used only without CAN_MAKE_OTHER_LOST flag. VkResult AllocateFromBlock( VmaDeviceMemoryBlock* pBlock, uint32_t currentFrameIndex, VkDeviceSize size, VkDeviceSize alignment, VmaAllocationCreateFlags allocFlags, void* pUserData, VmaSuballocationType suballocType, uint32_t strategy, VmaAllocation* pAllocation); VkResult CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex); // Saves result to pCtx->res. void ApplyDefragmentationMovesCpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves); // Saves result to pCtx->res. void ApplyDefragmentationMovesGpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkCommandBuffer commandBuffer); /* Used during defragmentation. pDefragmentationStats is optional. It's in/out - updated with new data. */ void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats); void UpdateHasEmptyBlock(); }; struct VmaPool_T { VMA_CLASS_NO_COPY(VmaPool_T) public: VmaBlockVector m_BlockVector; VmaPool_T( VmaAllocator hAllocator, const VmaPoolCreateInfo& createInfo, VkDeviceSize preferredBlockSize); ~VmaPool_T(); uint32_t GetId() const { return m_Id; } void SetId(uint32_t id) { VMA_ASSERT(m_Id == 0); m_Id = id; } const char* GetName() const { return m_Name; } void SetName(const char* pName); #if VMA_STATS_STRING_ENABLED //void PrintDetailedMap(class VmaStringBuilder& sb); #endif private: uint32_t m_Id; char* m_Name; }; /* Performs defragmentation: - Updates `pBlockVector->m_pMetadata`. - Updates allocations by calling ChangeBlockAllocation() or ChangeOffset(). - Does not move actual data, only returns requested moves as `moves`. */ class VmaDefragmentationAlgorithm { VMA_CLASS_NO_COPY(VmaDefragmentationAlgorithm) public: VmaDefragmentationAlgorithm( VmaAllocator hAllocator, VmaBlockVector* pBlockVector, uint32_t currentFrameIndex) : m_hAllocator(hAllocator), m_pBlockVector(pBlockVector), m_CurrentFrameIndex(currentFrameIndex) { } virtual ~VmaDefragmentationAlgorithm() { } virtual void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged) = 0; virtual void AddAll() = 0; virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove) = 0; virtual VkDeviceSize GetBytesMoved() const = 0; virtual uint32_t GetAllocationsMoved() const = 0; protected: VmaAllocator const m_hAllocator; VmaBlockVector* const m_pBlockVector; const uint32_t m_CurrentFrameIndex; struct AllocationInfo { VmaAllocation m_hAllocation; VkBool32* m_pChanged; AllocationInfo() : m_hAllocation(VK_NULL_HANDLE), m_pChanged(VMA_NULL) { } AllocationInfo(VmaAllocation hAlloc, VkBool32* pChanged) : m_hAllocation(hAlloc), m_pChanged(pChanged) { } }; }; class VmaDefragmentationAlgorithm_Generic : public VmaDefragmentationAlgorithm { VMA_CLASS_NO_COPY(VmaDefragmentationAlgorithm_Generic) public: VmaDefragmentationAlgorithm_Generic( VmaAllocator hAllocator, VmaBlockVector* pBlockVector, uint32_t currentFrameIndex, bool overlappingMoveSupported); virtual ~VmaDefragmentationAlgorithm_Generic(); virtual void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged); virtual void AddAll() { m_AllAllocations = true; } virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove); virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; } virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; } private: uint32_t m_AllocationCount; bool m_AllAllocations; VkDeviceSize m_BytesMoved; uint32_t m_AllocationsMoved; struct AllocationInfoSizeGreater { bool operator()(const AllocationInfo& lhs, const AllocationInfo& rhs) const { return lhs.m_hAllocation->GetSize() > rhs.m_hAllocation->GetSize(); } }; struct AllocationInfoOffsetGreater { bool operator()(const AllocationInfo& lhs, const AllocationInfo& rhs) const { return lhs.m_hAllocation->GetOffset() > rhs.m_hAllocation->GetOffset(); } }; struct BlockInfo { size_t m_OriginalBlockIndex; VmaDeviceMemoryBlock* m_pBlock; bool m_HasNonMovableAllocations; VmaVector< AllocationInfo, VmaStlAllocator<AllocationInfo> > m_Allocations; BlockInfo(const VkAllocationCallbacks* pAllocationCallbacks) : m_OriginalBlockIndex(SIZE_MAX), m_pBlock(VMA_NULL), m_HasNonMovableAllocations(true), m_Allocations(pAllocationCallbacks) { } void CalcHasNonMovableAllocations() { const size_t blockAllocCount = m_pBlock->m_pMetadata->GetAllocationCount(); const size_t defragmentAllocCount = m_Allocations.size(); m_HasNonMovableAllocations = blockAllocCount != defragmentAllocCount; } void SortAllocationsBySizeDescending() { VMA_SORT(m_Allocations.begin(), m_Allocations.end(), AllocationInfoSizeGreater()); } void SortAllocationsByOffsetDescending() { VMA_SORT(m_Allocations.begin(), m_Allocations.end(), AllocationInfoOffsetGreater()); } }; struct BlockPointerLess { bool operator()(const BlockInfo* pLhsBlockInfo, const VmaDeviceMemoryBlock* pRhsBlock) const { return pLhsBlockInfo->m_pBlock < pRhsBlock; } bool operator()(const BlockInfo* pLhsBlockInfo, const BlockInfo* pRhsBlockInfo) const { return pLhsBlockInfo->m_pBlock < pRhsBlockInfo->m_pBlock; } }; // 1. Blocks with some non-movable allocations go first. // 2. Blocks with smaller sumFreeSize go first. struct BlockInfoCompareMoveDestination { bool operator()(const BlockInfo* pLhsBlockInfo, const BlockInfo* pRhsBlockInfo) const { if (pLhsBlockInfo->m_HasNonMovableAllocations && !pRhsBlockInfo->m_HasNonMovableAllocations) { return true; } if (!pLhsBlockInfo->m_HasNonMovableAllocations && pRhsBlockInfo->m_HasNonMovableAllocations) { return false; } if (pLhsBlockInfo->m_pBlock->m_pMetadata->GetSumFreeSize() < pRhsBlockInfo->m_pBlock->m_pMetadata->GetSumFreeSize()) { return true; } return false; } }; typedef VmaVector< BlockInfo*, VmaStlAllocator<BlockInfo*> > BlockInfoVector; BlockInfoVector m_Blocks; VkResult DefragmentRound( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove); size_t CalcBlocksWithNonMovableCount() const; static bool MoveMakesSense( size_t dstBlockIndex, VkDeviceSize dstOffset, size_t srcBlockIndex, VkDeviceSize srcOffset); }; class VmaDefragmentationAlgorithm_Fast : public VmaDefragmentationAlgorithm { VMA_CLASS_NO_COPY(VmaDefragmentationAlgorithm_Fast) public: VmaDefragmentationAlgorithm_Fast( VmaAllocator hAllocator, VmaBlockVector* pBlockVector, uint32_t currentFrameIndex, bool overlappingMoveSupported); virtual ~VmaDefragmentationAlgorithm_Fast(); virtual void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged) { ++m_AllocationCount; } virtual void AddAll() { m_AllAllocations = true; } virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove); virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; } virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; } private: struct BlockInfo { size_t origBlockIndex; }; class FreeSpaceDatabase { public: FreeSpaceDatabase() { FreeSpace s = {}; s.blockInfoIndex = SIZE_MAX; for (size_t i = 0; i < MAX_COUNT; ++i) { m_FreeSpaces[i] = s; } } void Register(size_t blockInfoIndex, VkDeviceSize offset, VkDeviceSize size) { if (size < VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { return; } // Find first invalid or the smallest structure. size_t bestIndex = SIZE_MAX; for (size_t i = 0; i < MAX_COUNT; ++i) { // Empty structure. if (m_FreeSpaces[i].blockInfoIndex == SIZE_MAX) { bestIndex = i; break; } if (m_FreeSpaces[i].size < size && (bestIndex == SIZE_MAX || m_FreeSpaces[bestIndex].size > m_FreeSpaces[i].size)) { bestIndex = i; } } if (bestIndex != SIZE_MAX) { m_FreeSpaces[bestIndex].blockInfoIndex = blockInfoIndex; m_FreeSpaces[bestIndex].offset = offset; m_FreeSpaces[bestIndex].size = size; } } bool Fetch(VkDeviceSize alignment, VkDeviceSize size, size_t& outBlockInfoIndex, VkDeviceSize& outDstOffset) { size_t bestIndex = SIZE_MAX; VkDeviceSize bestFreeSpaceAfter = 0; for (size_t i = 0; i < MAX_COUNT; ++i) { // Structure is valid. if (m_FreeSpaces[i].blockInfoIndex != SIZE_MAX) { const VkDeviceSize dstOffset = VmaAlignUp(m_FreeSpaces[i].offset, alignment); // Allocation fits into this structure. if (dstOffset + size <= m_FreeSpaces[i].offset + m_FreeSpaces[i].size) { const VkDeviceSize freeSpaceAfter = (m_FreeSpaces[i].offset + m_FreeSpaces[i].size) - (dstOffset + size); if (bestIndex == SIZE_MAX || freeSpaceAfter > bestFreeSpaceAfter) { bestIndex = i; bestFreeSpaceAfter = freeSpaceAfter; } } } } if (bestIndex != SIZE_MAX) { outBlockInfoIndex = m_FreeSpaces[bestIndex].blockInfoIndex; outDstOffset = VmaAlignUp(m_FreeSpaces[bestIndex].offset, alignment); if (bestFreeSpaceAfter >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { // Leave this structure for remaining empty space. const VkDeviceSize alignmentPlusSize = (outDstOffset - m_FreeSpaces[bestIndex].offset) + size; m_FreeSpaces[bestIndex].offset += alignmentPlusSize; m_FreeSpaces[bestIndex].size -= alignmentPlusSize; } else { // This structure becomes invalid. m_FreeSpaces[bestIndex].blockInfoIndex = SIZE_MAX; } return true; } return false; } private: static const size_t MAX_COUNT = 4; struct FreeSpace { size_t blockInfoIndex; // SIZE_MAX means this structure is invalid. VkDeviceSize offset; VkDeviceSize size; } m_FreeSpaces[MAX_COUNT]; }; const bool m_OverlappingMoveSupported; uint32_t m_AllocationCount; bool m_AllAllocations; VkDeviceSize m_BytesMoved; uint32_t m_AllocationsMoved; VmaVector< BlockInfo, VmaStlAllocator<BlockInfo> > m_BlockInfos; void PreprocessMetadata(); void PostprocessMetadata(); void InsertSuballoc(VmaBlockMetadata_Generic* pMetadata, const VmaSuballocation& suballoc); }; struct VmaBlockDefragmentationContext { enum BLOCK_FLAG { BLOCK_FLAG_USED = 0x00000001, }; uint32_t flags; VkBuffer hBuffer; }; class VmaBlockVectorDefragmentationContext { VMA_CLASS_NO_COPY(VmaBlockVectorDefragmentationContext) public: VkResult res; bool mutexLocked; VmaVector< VmaBlockDefragmentationContext, VmaStlAllocator<VmaBlockDefragmentationContext> > blockContexts; VmaBlockVectorDefragmentationContext( VmaAllocator hAllocator, VmaPool hCustomPool, // Optional. VmaBlockVector* pBlockVector, uint32_t currFrameIndex); ~VmaBlockVectorDefragmentationContext(); VmaPool GetCustomPool() const { return m_hCustomPool; } VmaBlockVector* GetBlockVector() const { return m_pBlockVector; } VmaDefragmentationAlgorithm* GetAlgorithm() const { return m_pAlgorithm; } void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged); void AddAll() { m_AllAllocations = true; } void Begin(bool overlappingMoveSupported); private: const VmaAllocator m_hAllocator; // Null if not from custom pool. const VmaPool m_hCustomPool; // Redundant, for convenience not to fetch from m_hCustomPool->m_BlockVector or m_hAllocator->m_pBlockVectors. VmaBlockVector* const m_pBlockVector; const uint32_t m_CurrFrameIndex; // Owner of this object. VmaDefragmentationAlgorithm* m_pAlgorithm; struct AllocInfo { VmaAllocation hAlloc; VkBool32* pChanged; }; // Used between constructor and Begin. VmaVector< AllocInfo, VmaStlAllocator<AllocInfo> > m_Allocations; bool m_AllAllocations; }; struct VmaDefragmentationContext_T { private: VMA_CLASS_NO_COPY(VmaDefragmentationContext_T) public: VmaDefragmentationContext_T( VmaAllocator hAllocator, uint32_t currFrameIndex, uint32_t flags, VmaDefragmentationStats* pStats); ~VmaDefragmentationContext_T(); void AddPools(uint32_t poolCount, VmaPool* pPools); void AddAllocations( uint32_t allocationCount, VmaAllocation* pAllocations, VkBool32* pAllocationsChanged); /* Returns: - `VK_SUCCESS` if succeeded and object can be destroyed immediately. - `VK_NOT_READY` if succeeded but the object must remain alive until vmaDefragmentationEnd(). - Negative value if error occured and object can be destroyed immediately. */ VkResult Defragment( VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove, VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove, VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats); private: const VmaAllocator m_hAllocator; const uint32_t m_CurrFrameIndex; const uint32_t m_Flags; VmaDefragmentationStats* const m_pStats; // Owner of these objects. VmaBlockVectorDefragmentationContext* m_DefaultPoolContexts[VK_MAX_MEMORY_TYPES]; // Owner of these objects. VmaVector< VmaBlockVectorDefragmentationContext*, VmaStlAllocator<VmaBlockVectorDefragmentationContext*> > m_CustomPoolContexts; }; #if VMA_RECORDING_ENABLED class VmaRecorder { public: VmaRecorder(); VkResult Init(const VmaRecordSettings& settings, bool useMutex); void WriteConfiguration( const VkPhysicalDeviceProperties& devProps, const VkPhysicalDeviceMemoryProperties& memProps, uint32_t vulkanApiVersion, bool dedicatedAllocationExtensionEnabled, bool bindMemory2ExtensionEnabled, bool memoryBudgetExtensionEnabled); ~VmaRecorder(); void RecordCreateAllocator(uint32_t frameIndex); void RecordDestroyAllocator(uint32_t frameIndex); void RecordCreatePool(uint32_t frameIndex, const VmaPoolCreateInfo& createInfo, VmaPool pool); void RecordDestroyPool(uint32_t frameIndex, VmaPool pool); void RecordAllocateMemory(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation); void RecordAllocateMemoryPages(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, const VmaAllocationCreateInfo& createInfo, uint64_t allocationCount, const VmaAllocation* pAllocations); void RecordAllocateMemoryForBuffer(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation); void RecordAllocateMemoryForImage(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation); void RecordFreeMemory(uint32_t frameIndex, VmaAllocation allocation); void RecordFreeMemoryPages(uint32_t frameIndex, uint64_t allocationCount, const VmaAllocation* pAllocations); void RecordSetAllocationUserData(uint32_t frameIndex, VmaAllocation allocation, const void* pUserData); void RecordCreateLostAllocation(uint32_t frameIndex, VmaAllocation allocation); void RecordMapMemory(uint32_t frameIndex, VmaAllocation allocation); void RecordUnmapMemory(uint32_t frameIndex, VmaAllocation allocation); void RecordFlushAllocation(uint32_t frameIndex, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); void RecordInvalidateAllocation(uint32_t frameIndex, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); void RecordCreateBuffer(uint32_t frameIndex, const VkBufferCreateInfo& bufCreateInfo, const VmaAllocationCreateInfo& allocCreateInfo, VmaAllocation allocation); void RecordCreateImage(uint32_t frameIndex, const VkImageCreateInfo& imageCreateInfo, const VmaAllocationCreateInfo& allocCreateInfo, VmaAllocation allocation); void RecordDestroyBuffer(uint32_t frameIndex, VmaAllocation allocation); void RecordDestroyImage(uint32_t frameIndex, VmaAllocation allocation); void RecordTouchAllocation(uint32_t frameIndex, VmaAllocation allocation); void RecordGetAllocationInfo(uint32_t frameIndex, VmaAllocation allocation); void RecordMakePoolAllocationsLost(uint32_t frameIndex, VmaPool pool); void RecordDefragmentationBegin(uint32_t frameIndex, const VmaDefragmentationInfo2& info, VmaDefragmentationContext ctx); void RecordDefragmentationEnd(uint32_t frameIndex, VmaDefragmentationContext ctx); void RecordSetPoolName(uint32_t frameIndex, VmaPool pool, const char* name); private: struct CallParams { uint32_t threadId; double time; }; class UserDataString { public: UserDataString(VmaAllocationCreateFlags allocFlags, const void* pUserData); const char* GetString() const { return m_Str; } private: char m_PtrStr[17]; const char* m_Str; }; bool m_UseMutex; VmaRecordFlags m_Flags; FILE* m_File; VMA_MUTEX m_FileMutex; int64_t m_Freq; int64_t m_StartCounter; void GetBasicParams(CallParams& outParams); // T must be a pointer type, e.g. VmaAllocation, VmaPool. template<typename T> void PrintPointerList(uint64_t count, const T* pItems) { if (count) { fprintf(m_File, "%p", pItems[0]); for (uint64_t i = 1; i < count; ++i) { fprintf(m_File, " %p", pItems[i]); } } } void PrintPointerList(uint64_t count, const VmaAllocation* pItems); void Flush(); }; #endif // #if VMA_RECORDING_ENABLED /* Thread-safe wrapper over VmaPoolAllocator free list, for allocation of VmaAllocation_T objects. */ class VmaAllocationObjectAllocator { VMA_CLASS_NO_COPY(VmaAllocationObjectAllocator) public: VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks); VmaAllocation Allocate(); void Free(VmaAllocation hAlloc); private: VMA_MUTEX m_Mutex; VmaPoolAllocator<VmaAllocation_T> m_Allocator; }; struct VmaCurrentBudgetData { VMA_ATOMIC_UINT64 m_BlockBytes[VK_MAX_MEMORY_HEAPS]; VMA_ATOMIC_UINT64 m_AllocationBytes[VK_MAX_MEMORY_HEAPS]; #if VMA_MEMORY_BUDGET VMA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch; VMA_RW_MUTEX m_BudgetMutex; uint64_t m_VulkanUsage[VK_MAX_MEMORY_HEAPS]; uint64_t m_VulkanBudget[VK_MAX_MEMORY_HEAPS]; uint64_t m_BlockBytesAtBudgetFetch[VK_MAX_MEMORY_HEAPS]; #endif // #if VMA_MEMORY_BUDGET VmaCurrentBudgetData() { for (uint32_t heapIndex = 0; heapIndex < VK_MAX_MEMORY_HEAPS; ++heapIndex) { m_BlockBytes[heapIndex] = 0; m_AllocationBytes[heapIndex] = 0; #if VMA_MEMORY_BUDGET m_VulkanUsage[heapIndex] = 0; m_VulkanBudget[heapIndex] = 0; m_BlockBytesAtBudgetFetch[heapIndex] = 0; #endif } #if VMA_MEMORY_BUDGET m_OperationsSinceBudgetFetch = 0; #endif } void AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) { m_AllocationBytes[heapIndex] += allocationSize; #if VMA_MEMORY_BUDGET ++m_OperationsSinceBudgetFetch; #endif } void RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) { VMA_ASSERT(m_AllocationBytes[heapIndex] >= allocationSize); // DELME m_AllocationBytes[heapIndex] -= allocationSize; #if VMA_MEMORY_BUDGET ++m_OperationsSinceBudgetFetch; #endif } }; // Main allocator object. struct VmaAllocator_T { VMA_CLASS_NO_COPY(VmaAllocator_T) public: bool m_UseMutex; uint32_t m_VulkanApiVersion; bool m_UseKhrDedicatedAllocation; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). bool m_UseKhrBindMemory2; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). bool m_UseExtMemoryBudget; VkDevice m_hDevice; VkInstance m_hInstance; bool m_AllocationCallbacksSpecified; VkAllocationCallbacks m_AllocationCallbacks; VmaDeviceMemoryCallbacks m_DeviceMemoryCallbacks; VmaAllocationObjectAllocator m_AllocationObjectAllocator; // Each bit (1 << i) is set if HeapSizeLimit is enabled for that heap, so cannot allocate more than the heap size. uint32_t m_HeapSizeLimitMask; VkPhysicalDeviceProperties m_PhysicalDeviceProperties; VkPhysicalDeviceMemoryProperties m_MemProps; // Default pools. VmaBlockVector* m_pBlockVectors[VK_MAX_MEMORY_TYPES]; // Each vector is sorted by memory (handle value). typedef VmaVector< VmaAllocation, VmaStlAllocator<VmaAllocation> > AllocationVectorType; AllocationVectorType* m_pDedicatedAllocations[VK_MAX_MEMORY_TYPES]; VMA_RW_MUTEX m_DedicatedAllocationsMutex[VK_MAX_MEMORY_TYPES]; VmaCurrentBudgetData m_Budget; VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo); VkResult Init(const VmaAllocatorCreateInfo* pCreateInfo); ~VmaAllocator_T(); const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_AllocationCallbacksSpecified ? &m_AllocationCallbacks : 0; } const VmaVulkanFunctions& GetVulkanFunctions() const { return m_VulkanFunctions; } VkDeviceSize GetBufferImageGranularity() const { return VMA_MAX( static_cast<VkDeviceSize>(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY), m_PhysicalDeviceProperties.limits.bufferImageGranularity); } uint32_t GetMemoryHeapCount() const { return m_MemProps.memoryHeapCount; } uint32_t GetMemoryTypeCount() const { return m_MemProps.memoryTypeCount; } uint32_t MemoryTypeIndexToHeapIndex(uint32_t memTypeIndex) const { VMA_ASSERT(memTypeIndex < m_MemProps.memoryTypeCount); return m_MemProps.memoryTypes[memTypeIndex].heapIndex; } // True when specific memory type is HOST_VISIBLE but not HOST_COHERENT. bool IsMemoryTypeNonCoherent(uint32_t memTypeIndex) const { return (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) == VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; } // Minimum alignment for all allocations in specific memory type. VkDeviceSize GetMemoryTypeMinAlignment(uint32_t memTypeIndex) const { return IsMemoryTypeNonCoherent(memTypeIndex) ? VMA_MAX((VkDeviceSize)VMA_DEBUG_ALIGNMENT, m_PhysicalDeviceProperties.limits.nonCoherentAtomSize) : (VkDeviceSize)VMA_DEBUG_ALIGNMENT; } bool IsIntegratedGpu() const { return m_PhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; } #if VMA_RECORDING_ENABLED VmaRecorder* GetRecorder() const { return m_pRecorder; } #endif void GetBufferMemoryRequirements( VkBuffer hBuffer, VkMemoryRequirements& memReq, bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const; void GetImageMemoryRequirements( VkImage hImage, VkMemoryRequirements& memReq, bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const; // Main allocation function. VkResult AllocateMemory( const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, VkBuffer dedicatedBuffer, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, size_t allocationCount, VmaAllocation* pAllocations); // Main deallocation function. void FreeMemory( size_t allocationCount, const VmaAllocation* pAllocations); VkResult ResizeAllocation( const VmaAllocation alloc, VkDeviceSize newSize); void CalculateStats(VmaStats* pStats); void GetBudget( VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount); #if VMA_STATS_STRING_ENABLED void PrintDetailedMap(class VmaJsonWriter& json); #endif VkResult DefragmentationBegin( const VmaDefragmentationInfo2& info, VmaDefragmentationStats* pStats, VmaDefragmentationContext* pContext); VkResult DefragmentationEnd( VmaDefragmentationContext context); void GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo); bool TouchAllocation(VmaAllocation hAllocation); VkResult CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool); void DestroyPool(VmaPool pool); void GetPoolStats(VmaPool pool, VmaPoolStats* pPoolStats); void SetCurrentFrameIndex(uint32_t frameIndex); uint32_t GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); } void MakePoolAllocationsLost( VmaPool hPool, size_t* pLostAllocationCount); VkResult CheckPoolCorruption(VmaPool hPool); VkResult CheckCorruption(uint32_t memoryTypeBits); void CreateLostAllocation(VmaAllocation* pAllocation); // Call to Vulkan function vkAllocateMemory with accompanying bookkeeping. VkResult AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory); // Call to Vulkan function vkFreeMemory with accompanying bookkeeping. void FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory); // Call to Vulkan function vkBindBufferMemory or vkBindBufferMemory2KHR. VkResult BindVulkanBuffer( VkDeviceMemory memory, VkDeviceSize memoryOffset, VkBuffer buffer, const void* pNext); // Call to Vulkan function vkBindImageMemory or vkBindImageMemory2KHR. VkResult BindVulkanImage( VkDeviceMemory memory, VkDeviceSize memoryOffset, VkImage image, const void* pNext); VkResult Map(VmaAllocation hAllocation, void** ppData); void Unmap(VmaAllocation hAllocation); VkResult BindBufferMemory( VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkBuffer hBuffer, const void* pNext); VkResult BindImageMemory( VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkImage hImage, const void* pNext); void FlushOrInvalidateAllocation( VmaAllocation hAllocation, VkDeviceSize offset, VkDeviceSize size, VMA_CACHE_OPERATION op); void FillAllocation(const VmaAllocation hAllocation, uint8_t pattern); /* Returns bit mask of memory types that can support defragmentation on GPU as they support creation of required buffer for copy operations. */ uint32_t GetGpuDefragmentationMemoryTypeBits(); private: VkDeviceSize m_PreferredLargeHeapBlockSize; VkPhysicalDevice m_PhysicalDevice; VMA_ATOMIC_UINT32 m_CurrentFrameIndex; VMA_ATOMIC_UINT32 m_GpuDefragmentationMemoryTypeBits; // UINT32_MAX means uninitialized. VMA_RW_MUTEX m_PoolsMutex; // Protected by m_PoolsMutex. Sorted by pointer value. VmaVector<VmaPool, VmaStlAllocator<VmaPool> > m_Pools; uint32_t m_NextPoolId; VmaVulkanFunctions m_VulkanFunctions; #if VMA_RECORDING_ENABLED VmaRecorder* m_pRecorder; #endif void ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions); VkDeviceSize CalcPreferredBlockSize(uint32_t memTypeIndex); VkResult AllocateMemoryOfType( VkDeviceSize size, VkDeviceSize alignment, bool dedicatedAllocation, VkBuffer dedicatedBuffer, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, VmaSuballocationType suballocType, size_t allocationCount, VmaAllocation* pAllocations); // Helper function only to be used inside AllocateDedicatedMemory. VkResult AllocateDedicatedMemoryPage( VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, const VkMemoryAllocateInfo& allocInfo, bool map, bool isUserDataString, void* pUserData, VmaAllocation* pAllocation); // Allocates and registers new VkDeviceMemory specifically for dedicated allocations. VkResult AllocateDedicatedMemory( VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, bool withinBudget, bool map, bool isUserDataString, void* pUserData, VkBuffer dedicatedBuffer, VkImage dedicatedImage, size_t allocationCount, VmaAllocation* pAllocations); void FreeDedicatedMemory(const VmaAllocation allocation); /* Calculates and returns bit mask of memory types that can support defragmentation on GPU as they support creation of required buffer for copy operations. */ uint32_t CalculateGpuDefragmentationMemoryTypeBits() const; #if VMA_MEMORY_BUDGET void UpdateVulkanBudget(); #endif // #if VMA_MEMORY_BUDGET }; //////////////////////////////////////////////////////////////////////////////// // Memory allocation #2 after VmaAllocator_T definition static void* VmaMalloc(VmaAllocator hAllocator, size_t size, size_t alignment) { return VmaMalloc(&hAllocator->m_AllocationCallbacks, size, alignment); } static void VmaFree(VmaAllocator hAllocator, void* ptr) { VmaFree(&hAllocator->m_AllocationCallbacks, ptr); } template<typename T> static T* VmaAllocate(VmaAllocator hAllocator) { return (T*)VmaMalloc(hAllocator, sizeof(T), VMA_ALIGN_OF(T)); } template<typename T> static T* VmaAllocateArray(VmaAllocator hAllocator, size_t count) { return (T*)VmaMalloc(hAllocator, sizeof(T) * count, VMA_ALIGN_OF(T)); } template<typename T> static void vma_delete(VmaAllocator hAllocator, T* ptr) { if (ptr != VMA_NULL) { ptr->~T(); VmaFree(hAllocator, ptr); } } template<typename T> static void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count) { if (ptr != VMA_NULL) { for (size_t i = count; i--; ) ptr[i].~T(); VmaFree(hAllocator, ptr); } } //////////////////////////////////////////////////////////////////////////////// // VmaStringBuilder #if VMA_STATS_STRING_ENABLED class VmaStringBuilder { public: VmaStringBuilder(VmaAllocator alloc) : m_Data(VmaStlAllocator<char>(alloc->GetAllocationCallbacks())) { } size_t GetLength() const { return m_Data.size(); } const char* GetData() const { return m_Data.data(); } void Add(char ch) { m_Data.push_back(ch); } void Add(const char* pStr); void AddNewLine() { Add('\n'); } void AddNumber(uint32_t num); void AddNumber(uint64_t num); void AddPointer(const void* ptr); private: VmaVector< char, VmaStlAllocator<char> > m_Data; }; void VmaStringBuilder::Add(const char* pStr) { const size_t strLen = strlen(pStr); if (strLen > 0) { const size_t oldCount = m_Data.size(); m_Data.resize(oldCount + strLen); memcpy(m_Data.data() + oldCount, pStr, strLen); } } void VmaStringBuilder::AddNumber(uint32_t num) { char buf[11]; buf[10] = '\0'; char* p = &buf[10]; do { *--p = '0' + (num % 10); num /= 10; } while (num); Add(p); } void VmaStringBuilder::AddNumber(uint64_t num) { char buf[21]; buf[20] = '\0'; char* p = &buf[20]; do { *--p = '0' + (num % 10); num /= 10; } while (num); Add(p); } void VmaStringBuilder::AddPointer(const void* ptr) { char buf[21]; VmaPtrToStr(buf, sizeof(buf), ptr); Add(buf); } #endif // #if VMA_STATS_STRING_ENABLED //////////////////////////////////////////////////////////////////////////////// // VmaJsonWriter #if VMA_STATS_STRING_ENABLED class VmaJsonWriter { VMA_CLASS_NO_COPY(VmaJsonWriter) public: VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb); ~VmaJsonWriter(); void BeginObject(bool singleLine = false); void EndObject(); void BeginArray(bool singleLine = false); void EndArray(); void WriteString(const char* pStr); void BeginString(const char* pStr = VMA_NULL); void ContinueString(const char* pStr); void ContinueString(uint32_t n); void ContinueString(uint64_t n); void ContinueString_Pointer(const void* ptr); void EndString(const char* pStr = VMA_NULL); void WriteNumber(uint32_t n); void WriteNumber(uint64_t n); void WriteBool(bool b); void WriteNull(); private: static const char* const INDENT; enum COLLECTION_TYPE { COLLECTION_TYPE_OBJECT, COLLECTION_TYPE_ARRAY, }; struct StackItem { COLLECTION_TYPE type; uint32_t valueCount; bool singleLineMode; }; VmaStringBuilder& m_SB; VmaVector< StackItem, VmaStlAllocator<StackItem> > m_Stack; bool m_InsideString; void BeginValue(bool isString); void WriteIndent(bool oneLess = false); }; const char* const VmaJsonWriter::INDENT = " "; VmaJsonWriter::VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb) : m_SB(sb), m_Stack(VmaStlAllocator<StackItem>(pAllocationCallbacks)), m_InsideString(false) { } VmaJsonWriter::~VmaJsonWriter() { VMA_ASSERT(!m_InsideString); VMA_ASSERT(m_Stack.empty()); } void VmaJsonWriter::BeginObject(bool singleLine) { VMA_ASSERT(!m_InsideString); BeginValue(false); m_SB.Add('{'); StackItem item; item.type = COLLECTION_TYPE_OBJECT; item.valueCount = 0; item.singleLineMode = singleLine; m_Stack.push_back(item); } void VmaJsonWriter::EndObject() { VMA_ASSERT(!m_InsideString); WriteIndent(true); m_SB.Add('}'); VMA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_OBJECT); m_Stack.pop_back(); } void VmaJsonWriter::BeginArray(bool singleLine) { VMA_ASSERT(!m_InsideString); BeginValue(false); m_SB.Add('['); StackItem item; item.type = COLLECTION_TYPE_ARRAY; item.valueCount = 0; item.singleLineMode = singleLine; m_Stack.push_back(item); } void VmaJsonWriter::EndArray() { VMA_ASSERT(!m_InsideString); WriteIndent(true); m_SB.Add(']'); VMA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_ARRAY); m_Stack.pop_back(); } void VmaJsonWriter::WriteString(const char* pStr) { BeginString(pStr); EndString(); } void VmaJsonWriter::BeginString(const char* pStr) { VMA_ASSERT(!m_InsideString); BeginValue(true); m_SB.Add('"'); m_InsideString = true; if (pStr != VMA_NULL && pStr[0] != '\0') { ContinueString(pStr); } } void VmaJsonWriter::ContinueString(const char* pStr) { VMA_ASSERT(m_InsideString); const size_t strLen = strlen(pStr); for (size_t i = 0; i < strLen; ++i) { char ch = pStr[i]; if (ch == '\\') { m_SB.Add("\\\\"); } else if (ch == '"') { m_SB.Add("\\\""); } else if (ch >= 32) { m_SB.Add(ch); } else switch (ch) { case '\b': m_SB.Add("\\b"); break; case '\f': m_SB.Add("\\f"); break; case '\n': m_SB.Add("\\n"); break; case '\r': m_SB.Add("\\r"); break; case '\t': m_SB.Add("\\t"); break; default: VMA_ASSERT(0 && "Character not currently supported."); break; } } } void VmaJsonWriter::ContinueString(uint32_t n) { VMA_ASSERT(m_InsideString); m_SB.AddNumber(n); } void VmaJsonWriter::ContinueString(uint64_t n) { VMA_ASSERT(m_InsideString); m_SB.AddNumber(n); } void VmaJsonWriter::ContinueString_Pointer(const void* ptr) { VMA_ASSERT(m_InsideString); m_SB.AddPointer(ptr); } void VmaJsonWriter::EndString(const char* pStr) { VMA_ASSERT(m_InsideString); if (pStr != VMA_NULL && pStr[0] != '\0') { ContinueString(pStr); } m_SB.Add('"'); m_InsideString = false; } void VmaJsonWriter::WriteNumber(uint32_t n) { VMA_ASSERT(!m_InsideString); BeginValue(false); m_SB.AddNumber(n); } void VmaJsonWriter::WriteNumber(uint64_t n) { VMA_ASSERT(!m_InsideString); BeginValue(false); m_SB.AddNumber(n); } void VmaJsonWriter::WriteBool(bool b) { VMA_ASSERT(!m_InsideString); BeginValue(false); m_SB.Add(b ? "true" : "false"); } void VmaJsonWriter::WriteNull() { VMA_ASSERT(!m_InsideString); BeginValue(false); m_SB.Add("null"); } void VmaJsonWriter::BeginValue(bool isString) { if (!m_Stack.empty()) { StackItem& currItem = m_Stack.back(); if (currItem.type == COLLECTION_TYPE_OBJECT && currItem.valueCount % 2 == 0) { VMA_ASSERT(isString); } if (currItem.type == COLLECTION_TYPE_OBJECT && currItem.valueCount % 2 != 0) { m_SB.Add(": "); } else if (currItem.valueCount > 0) { m_SB.Add(", "); WriteIndent(); } else { WriteIndent(); } ++currItem.valueCount; } } void VmaJsonWriter::WriteIndent(bool oneLess) { if (!m_Stack.empty() && !m_Stack.back().singleLineMode) { m_SB.AddNewLine(); size_t count = m_Stack.size(); if (count > 0 && oneLess) { --count; } for (size_t i = 0; i < count; ++i) { m_SB.Add(INDENT); } } } #endif // #if VMA_STATS_STRING_ENABLED //////////////////////////////////////////////////////////////////////////////// void VmaAllocation_T::SetUserData(VmaAllocator hAllocator, void* pUserData) { if (IsUserDataString()) { VMA_ASSERT(pUserData == VMA_NULL || pUserData != m_pUserData); FreeUserDataString(hAllocator); if (pUserData != VMA_NULL) { m_pUserData = VmaCreateStringCopy(hAllocator->GetAllocationCallbacks(), (const char*)pUserData); } } else { m_pUserData = pUserData; } } void VmaAllocation_T::ChangeBlockAllocation( VmaAllocator hAllocator, VmaDeviceMemoryBlock* block, VkDeviceSize offset) { VMA_ASSERT(block != VMA_NULL); VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); // Move mapping reference counter from old block to new block. if (block != m_BlockAllocation.m_Block) { uint32_t mapRefCount = m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP; if (IsPersistentMap()) ++mapRefCount; m_BlockAllocation.m_Block->Unmap(hAllocator, mapRefCount); block->Map(hAllocator, mapRefCount, VMA_NULL); } m_BlockAllocation.m_Block = block; m_BlockAllocation.m_Offset = offset; } void VmaAllocation_T::ChangeOffset(VkDeviceSize newOffset) { VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); m_BlockAllocation.m_Offset = newOffset; } VkDeviceSize VmaAllocation_T::GetOffset() const { switch (m_Type) { case ALLOCATION_TYPE_BLOCK: return m_BlockAllocation.m_Offset; case ALLOCATION_TYPE_DEDICATED: return 0; default: VMA_ASSERT(0); return 0; } } VkDeviceMemory VmaAllocation_T::GetMemory() const { switch (m_Type) { case ALLOCATION_TYPE_BLOCK: return m_BlockAllocation.m_Block->GetDeviceMemory(); case ALLOCATION_TYPE_DEDICATED: return m_DedicatedAllocation.m_hMemory; default: VMA_ASSERT(0); return VK_NULL_HANDLE; } } void* VmaAllocation_T::GetMappedData() const { switch (m_Type) { case ALLOCATION_TYPE_BLOCK: if (m_MapCount != 0) { void* pBlockData = m_BlockAllocation.m_Block->GetMappedData(); VMA_ASSERT(pBlockData != VMA_NULL); return (char*)pBlockData + m_BlockAllocation.m_Offset; } else { return VMA_NULL; } break; case ALLOCATION_TYPE_DEDICATED: VMA_ASSERT((m_DedicatedAllocation.m_pMappedData != VMA_NULL) == (m_MapCount != 0)); return m_DedicatedAllocation.m_pMappedData; default: VMA_ASSERT(0); return VMA_NULL; } } bool VmaAllocation_T::CanBecomeLost() const { switch (m_Type) { case ALLOCATION_TYPE_BLOCK: return m_BlockAllocation.m_CanBecomeLost; case ALLOCATION_TYPE_DEDICATED: return false; default: VMA_ASSERT(0); return false; } } bool VmaAllocation_T::MakeLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) { VMA_ASSERT(CanBecomeLost()); /* Warning: This is a carefully designed algorithm. Do not modify unless you really know what you're doing :) */ uint32_t localLastUseFrameIndex = GetLastUseFrameIndex(); for (;;) { if (localLastUseFrameIndex == VMA_FRAME_INDEX_LOST) { VMA_ASSERT(0); return false; } else if (localLastUseFrameIndex + frameInUseCount >= currentFrameIndex) { return false; } else // Last use time earlier than current time. { if (CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, VMA_FRAME_INDEX_LOST)) { // Setting hAllocation.LastUseFrameIndex atomic to VMA_FRAME_INDEX_LOST is enough to mark it as LOST. // Calling code just needs to unregister this allocation in owning VmaDeviceMemoryBlock. return true; } } } } #if VMA_STATS_STRING_ENABLED // Correspond to values of enum VmaSuballocationType. static const char* VMA_SUBALLOCATION_TYPE_NAMES[] = { "FREE", "UNKNOWN", "BUFFER", "IMAGE_UNKNOWN", "IMAGE_LINEAR", "IMAGE_OPTIMAL", }; void VmaAllocation_T::PrintParameters(class VmaJsonWriter& json) const { json.WriteString("Type"); json.WriteString(VMA_SUBALLOCATION_TYPE_NAMES[m_SuballocationType]); json.WriteString("Size"); json.WriteNumber(m_Size); if (m_pUserData != VMA_NULL) { json.WriteString("UserData"); if (IsUserDataString()) { json.WriteString((const char*)m_pUserData); } else { json.BeginString(); json.ContinueString_Pointer(m_pUserData); json.EndString(); } } json.WriteString("CreationFrameIndex"); json.WriteNumber(m_CreationFrameIndex); json.WriteString("LastUseFrameIndex"); json.WriteNumber(GetLastUseFrameIndex()); if (m_BufferImageUsage != 0) { json.WriteString("Usage"); json.WriteNumber(m_BufferImageUsage); } } #endif void VmaAllocation_T::FreeUserDataString(VmaAllocator hAllocator) { VMA_ASSERT(IsUserDataString()); VmaFreeString(hAllocator->GetAllocationCallbacks(), (char*)m_pUserData); m_pUserData = VMA_NULL; } void VmaAllocation_T::BlockAllocMap() { VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK); if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) < 0x7F) { ++m_MapCount; } else { VMA_ASSERT(0 && "Allocation mapped too many times simultaneously."); } } void VmaAllocation_T::BlockAllocUnmap() { VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK); if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) != 0) { --m_MapCount; } else { VMA_ASSERT(0 && "Unmapping allocation not previously mapped."); } } VkResult VmaAllocation_T::DedicatedAllocMap(VmaAllocator hAllocator, void** ppData) { VMA_ASSERT(GetType() == ALLOCATION_TYPE_DEDICATED); if (m_MapCount != 0) { if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) < 0x7F) { VMA_ASSERT(m_DedicatedAllocation.m_pMappedData != VMA_NULL); *ppData = m_DedicatedAllocation.m_pMappedData; ++m_MapCount; return VK_SUCCESS; } else { VMA_ASSERT(0 && "Dedicated allocation mapped too many times simultaneously."); return VK_ERROR_MEMORY_MAP_FAILED; } } else { VkResult result = (*hAllocator->GetVulkanFunctions().vkMapMemory)( hAllocator->m_hDevice, m_DedicatedAllocation.m_hMemory, 0, // offset VK_WHOLE_SIZE, 0, // flags ppData); if (result == VK_SUCCESS) { m_DedicatedAllocation.m_pMappedData = *ppData; m_MapCount = 1; } return result; } } void VmaAllocation_T::DedicatedAllocUnmap(VmaAllocator hAllocator) { VMA_ASSERT(GetType() == ALLOCATION_TYPE_DEDICATED); if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) != 0) { --m_MapCount; if (m_MapCount == 0) { m_DedicatedAllocation.m_pMappedData = VMA_NULL; (*hAllocator->GetVulkanFunctions().vkUnmapMemory)( hAllocator->m_hDevice, m_DedicatedAllocation.m_hMemory); } } else { VMA_ASSERT(0 && "Unmapping dedicated allocation not previously mapped."); } } #if VMA_STATS_STRING_ENABLED static void VmaPrintStatInfo(VmaJsonWriter& json, const VmaStatInfo& stat) { json.BeginObject(); json.WriteString("Blocks"); json.WriteNumber(stat.blockCount); json.WriteString("Allocations"); json.WriteNumber(stat.allocationCount); json.WriteString("UnusedRanges"); json.WriteNumber(stat.unusedRangeCount); json.WriteString("UsedBytes"); json.WriteNumber(stat.usedBytes); json.WriteString("UnusedBytes"); json.WriteNumber(stat.unusedBytes); if (stat.allocationCount > 1) { json.WriteString("AllocationSize"); json.BeginObject(true); json.WriteString("Min"); json.WriteNumber(stat.allocationSizeMin); json.WriteString("Avg"); json.WriteNumber(stat.allocationSizeAvg); json.WriteString("Max"); json.WriteNumber(stat.allocationSizeMax); json.EndObject(); } if (stat.unusedRangeCount > 1) { json.WriteString("UnusedRangeSize"); json.BeginObject(true); json.WriteString("Min"); json.WriteNumber(stat.unusedRangeSizeMin); json.WriteString("Avg"); json.WriteNumber(stat.unusedRangeSizeAvg); json.WriteString("Max"); json.WriteNumber(stat.unusedRangeSizeMax); json.EndObject(); } json.EndObject(); } #endif // #if VMA_STATS_STRING_ENABLED struct VmaSuballocationItemSizeLess { bool operator()( const VmaSuballocationList::iterator lhs, const VmaSuballocationList::iterator rhs) const { return lhs->size < rhs->size; } bool operator()( const VmaSuballocationList::iterator lhs, VkDeviceSize rhsSize) const { return lhs->size < rhsSize; } }; //////////////////////////////////////////////////////////////////////////////// // class VmaBlockMetadata VmaBlockMetadata::VmaBlockMetadata(VmaAllocator hAllocator) : m_Size(0), m_pAllocationCallbacks(hAllocator->GetAllocationCallbacks()) { } #if VMA_STATS_STRING_ENABLED void VmaBlockMetadata::PrintDetailedMap_Begin(class VmaJsonWriter& json, VkDeviceSize unusedBytes, size_t allocationCount, size_t unusedRangeCount) const { json.BeginObject(); json.WriteString("TotalBytes"); json.WriteNumber(GetSize()); json.WriteString("UnusedBytes"); json.WriteNumber(unusedBytes); json.WriteString("Allocations"); json.WriteNumber((uint64_t)allocationCount); json.WriteString("UnusedRanges"); json.WriteNumber((uint64_t)unusedRangeCount); json.WriteString("Suballocations"); json.BeginArray(); } void VmaBlockMetadata::PrintDetailedMap_Allocation(class VmaJsonWriter& json, VkDeviceSize offset, VmaAllocation hAllocation) const { json.BeginObject(true); json.WriteString("Offset"); json.WriteNumber(offset); hAllocation->PrintParameters(json); json.EndObject(); } void VmaBlockMetadata::PrintDetailedMap_UnusedRange(class VmaJsonWriter& json, VkDeviceSize offset, VkDeviceSize size) const { json.BeginObject(true); json.WriteString("Offset"); json.WriteNumber(offset); json.WriteString("Type"); json.WriteString(VMA_SUBALLOCATION_TYPE_NAMES[VMA_SUBALLOCATION_TYPE_FREE]); json.WriteString("Size"); json.WriteNumber(size); json.EndObject(); } void VmaBlockMetadata::PrintDetailedMap_End(class VmaJsonWriter& json) const { json.EndArray(); json.EndObject(); } #endif // #if VMA_STATS_STRING_ENABLED //////////////////////////////////////////////////////////////////////////////// // class VmaBlockMetadata_Generic VmaBlockMetadata_Generic::VmaBlockMetadata_Generic(VmaAllocator hAllocator) : VmaBlockMetadata(hAllocator), m_FreeCount(0), m_SumFreeSize(0), m_Suballocations(VmaStlAllocator<VmaSuballocation>(hAllocator->GetAllocationCallbacks())), m_FreeSuballocationsBySize(VmaStlAllocator<VmaSuballocationList::iterator>(hAllocator->GetAllocationCallbacks())) { } VmaBlockMetadata_Generic::~VmaBlockMetadata_Generic() { } void VmaBlockMetadata_Generic::Init(VkDeviceSize size) { VmaBlockMetadata::Init(size); m_FreeCount = 1; m_SumFreeSize = size; VmaSuballocation suballoc = {}; suballoc.offset = 0; suballoc.size = size; suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; VMA_ASSERT(size > VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER); m_Suballocations.push_back(suballoc); VmaSuballocationList::iterator suballocItem = m_Suballocations.end(); --suballocItem; m_FreeSuballocationsBySize.push_back(suballocItem); } bool VmaBlockMetadata_Generic::Validate() const { VMA_VALIDATE(!m_Suballocations.empty()); // Expected offset of new suballocation as calculated from previous ones. VkDeviceSize calculatedOffset = 0; // Expected number of free suballocations as calculated from traversing their list. uint32_t calculatedFreeCount = 0; // Expected sum size of free suballocations as calculated from traversing their list. VkDeviceSize calculatedSumFreeSize = 0; // Expected number of free suballocations that should be registered in // m_FreeSuballocationsBySize calculated from traversing their list. size_t freeSuballocationsToRegister = 0; // True if previous visited suballocation was free. bool prevFree = false; for (VmaSuballocationList::const_iterator suballocItem = m_Suballocations.cbegin(); suballocItem != m_Suballocations.cend(); ++suballocItem) { const VmaSuballocation& subAlloc = *suballocItem; // Actual offset of this suballocation doesn't match expected one. VMA_VALIDATE(subAlloc.offset == calculatedOffset); const bool currFree = (subAlloc.type == VMA_SUBALLOCATION_TYPE_FREE); // Two adjacent free suballocations are invalid. They should be merged. VMA_VALIDATE(!prevFree || !currFree); VMA_VALIDATE(currFree == (subAlloc.hAllocation == VK_NULL_HANDLE)); if (currFree) { calculatedSumFreeSize += subAlloc.size; ++calculatedFreeCount; if (subAlloc.size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { ++freeSuballocationsToRegister; } // Margin required between allocations - every free space must be at least that large. VMA_VALIDATE(subAlloc.size >= VMA_DEBUG_MARGIN); } else { VMA_VALIDATE(subAlloc.hAllocation->GetOffset() == subAlloc.offset); VMA_VALIDATE(subAlloc.hAllocation->GetSize() == subAlloc.size); // Margin required between allocations - previous allocation must be free. VMA_VALIDATE(VMA_DEBUG_MARGIN == 0 || prevFree); } calculatedOffset += subAlloc.size; prevFree = currFree; } // Number of free suballocations registered in m_FreeSuballocationsBySize doesn't // match expected one. VMA_VALIDATE(m_FreeSuballocationsBySize.size() == freeSuballocationsToRegister); VkDeviceSize lastSize = 0; for (size_t i = 0; i < m_FreeSuballocationsBySize.size(); ++i) { VmaSuballocationList::iterator suballocItem = m_FreeSuballocationsBySize[i]; // Only free suballocations can be registered in m_FreeSuballocationsBySize. VMA_VALIDATE(suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE); // They must be sorted by size ascending. VMA_VALIDATE(suballocItem->size >= lastSize); lastSize = suballocItem->size; } // Check if totals match calculacted values. VMA_VALIDATE(ValidateFreeSuballocationList()); VMA_VALIDATE(calculatedOffset == GetSize()); VMA_VALIDATE(calculatedSumFreeSize == m_SumFreeSize); VMA_VALIDATE(calculatedFreeCount == m_FreeCount); return true; } VkDeviceSize VmaBlockMetadata_Generic::GetUnusedRangeSizeMax() const { if (!m_FreeSuballocationsBySize.empty()) { return m_FreeSuballocationsBySize.back()->size; } else { return 0; } } bool VmaBlockMetadata_Generic::IsEmpty() const { return (m_Suballocations.size() == 1) && (m_FreeCount == 1); } void VmaBlockMetadata_Generic::CalcAllocationStatInfo(VmaStatInfo& outInfo) const { outInfo.blockCount = 1; const uint32_t rangeCount = (uint32_t)m_Suballocations.size(); outInfo.allocationCount = rangeCount - m_FreeCount; outInfo.unusedRangeCount = m_FreeCount; outInfo.unusedBytes = m_SumFreeSize; outInfo.usedBytes = GetSize() - outInfo.unusedBytes; outInfo.allocationSizeMin = UINT64_MAX; outInfo.allocationSizeMax = 0; outInfo.unusedRangeSizeMin = UINT64_MAX; outInfo.unusedRangeSizeMax = 0; for (VmaSuballocationList::const_iterator suballocItem = m_Suballocations.cbegin(); suballocItem != m_Suballocations.cend(); ++suballocItem) { const VmaSuballocation& suballoc = *suballocItem; if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) { outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MAX(outInfo.allocationSizeMax, suballoc.size); } else { outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, suballoc.size); outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, suballoc.size); } } } void VmaBlockMetadata_Generic::AddPoolStats(VmaPoolStats& inoutStats) const { const uint32_t rangeCount = (uint32_t)m_Suballocations.size(); inoutStats.size += GetSize(); inoutStats.unusedSize += m_SumFreeSize; inoutStats.allocationCount += rangeCount - m_FreeCount; inoutStats.unusedRangeCount += m_FreeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, GetUnusedRangeSizeMax()); } #if VMA_STATS_STRING_ENABLED void VmaBlockMetadata_Generic::PrintDetailedMap(class VmaJsonWriter& json) const { PrintDetailedMap_Begin(json, m_SumFreeSize, // unusedBytes m_Suballocations.size() - (size_t)m_FreeCount, // allocationCount m_FreeCount); // unusedRangeCount size_t i = 0; for (VmaSuballocationList::const_iterator suballocItem = m_Suballocations.cbegin(); suballocItem != m_Suballocations.cend(); ++suballocItem, ++i) { if (suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE) { PrintDetailedMap_UnusedRange(json, suballocItem->offset, suballocItem->size); } else { PrintDetailedMap_Allocation(json, suballocItem->offset, suballocItem->hAllocation); } } PrintDetailedMap_End(json); } #endif // #if VMA_STATS_STRING_ENABLED bool VmaBlockMetadata_Generic::CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { VMA_ASSERT(allocSize > 0); VMA_ASSERT(!upperAddress); VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(pAllocationRequest != VMA_NULL); VMA_HEAVY_ASSERT(Validate()); pAllocationRequest->type = VmaAllocationRequestType::Normal; // There is not enough total free space in this block to fullfill the request: Early return. if (canMakeOtherLost == false && m_SumFreeSize < allocSize + 2 * VMA_DEBUG_MARGIN) { return false; } // New algorithm, efficiently searching freeSuballocationsBySize. const size_t freeSuballocCount = m_FreeSuballocationsBySize.size(); if (freeSuballocCount > 0) { if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) { // Find first free suballocation with size not less than allocSize + 2 * VMA_DEBUG_MARGIN. VmaSuballocationList::iterator* const it = VmaBinaryFindFirstNotLess( m_FreeSuballocationsBySize.data(), m_FreeSuballocationsBySize.data() + freeSuballocCount, allocSize + 2 * VMA_DEBUG_MARGIN, VmaSuballocationItemSizeLess()); size_t index = it - m_FreeSuballocationsBySize.data(); for (; index < freeSuballocCount; ++index) { if (CheckAllocation( currentFrameIndex, frameInUseCount, bufferImageGranularity, allocSize, allocAlignment, allocType, m_FreeSuballocationsBySize[index], false, // canMakeOtherLost &pAllocationRequest->offset, &pAllocationRequest->itemsToMakeLostCount, &pAllocationRequest->sumFreeSize, &pAllocationRequest->sumItemSize)) { pAllocationRequest->item = m_FreeSuballocationsBySize[index]; return true; } } } else if (strategy == VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET) { for (VmaSuballocationList::iterator it = m_Suballocations.begin(); it != m_Suballocations.end(); ++it) { if (it->type == VMA_SUBALLOCATION_TYPE_FREE && CheckAllocation( currentFrameIndex, frameInUseCount, bufferImageGranularity, allocSize, allocAlignment, allocType, it, false, // canMakeOtherLost &pAllocationRequest->offset, &pAllocationRequest->itemsToMakeLostCount, &pAllocationRequest->sumFreeSize, &pAllocationRequest->sumItemSize)) { pAllocationRequest->item = it; return true; } } } else // WORST_FIT, FIRST_FIT { // Search staring from biggest suballocations. for (size_t index = freeSuballocCount; index--; ) { if (CheckAllocation( currentFrameIndex, frameInUseCount, bufferImageGranularity, allocSize, allocAlignment, allocType, m_FreeSuballocationsBySize[index], false, // canMakeOtherLost &pAllocationRequest->offset, &pAllocationRequest->itemsToMakeLostCount, &pAllocationRequest->sumFreeSize, &pAllocationRequest->sumItemSize)) { pAllocationRequest->item = m_FreeSuballocationsBySize[index]; return true; } } } } if (canMakeOtherLost) { // Brute-force algorithm. TODO: Come up with something better. bool found = false; VmaAllocationRequest tmpAllocRequest = {}; tmpAllocRequest.type = VmaAllocationRequestType::Normal; for (VmaSuballocationList::iterator suballocIt = m_Suballocations.begin(); suballocIt != m_Suballocations.end(); ++suballocIt) { if (suballocIt->type == VMA_SUBALLOCATION_TYPE_FREE || suballocIt->hAllocation->CanBecomeLost()) { if (CheckAllocation( currentFrameIndex, frameInUseCount, bufferImageGranularity, allocSize, allocAlignment, allocType, suballocIt, canMakeOtherLost, &tmpAllocRequest.offset, &tmpAllocRequest.itemsToMakeLostCount, &tmpAllocRequest.sumFreeSize, &tmpAllocRequest.sumItemSize)) { if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) { *pAllocationRequest = tmpAllocRequest; pAllocationRequest->item = suballocIt; break; } if (!found || tmpAllocRequest.CalcCost() < pAllocationRequest->CalcCost()) { *pAllocationRequest = tmpAllocRequest; pAllocationRequest->item = suballocIt; found = true; } } } } return found; } return false; } bool VmaBlockMetadata_Generic::MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest) { VMA_ASSERT(pAllocationRequest && pAllocationRequest->type == VmaAllocationRequestType::Normal); while (pAllocationRequest->itemsToMakeLostCount > 0) { if (pAllocationRequest->item->type == VMA_SUBALLOCATION_TYPE_FREE) { ++pAllocationRequest->item; } VMA_ASSERT(pAllocationRequest->item != m_Suballocations.end()); VMA_ASSERT(pAllocationRequest->item->hAllocation != VK_NULL_HANDLE); VMA_ASSERT(pAllocationRequest->item->hAllocation->CanBecomeLost()); if (pAllocationRequest->item->hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) { pAllocationRequest->item = FreeSuballocation(pAllocationRequest->item); --pAllocationRequest->itemsToMakeLostCount; } else { return false; } } VMA_HEAVY_ASSERT(Validate()); VMA_ASSERT(pAllocationRequest->item != m_Suballocations.end()); VMA_ASSERT(pAllocationRequest->item->type == VMA_SUBALLOCATION_TYPE_FREE); return true; } uint32_t VmaBlockMetadata_Generic::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) { uint32_t lostAllocationCount = 0; for (VmaSuballocationList::iterator it = m_Suballocations.begin(); it != m_Suballocations.end(); ++it) { if (it->type != VMA_SUBALLOCATION_TYPE_FREE && it->hAllocation->CanBecomeLost() && it->hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) { it = FreeSuballocation(it); ++lostAllocationCount; } } return lostAllocationCount; } VkResult VmaBlockMetadata_Generic::CheckCorruption(const void* pBlockData) { for (VmaSuballocationList::iterator it = m_Suballocations.begin(); it != m_Suballocations.end(); ++it) { if (it->type != VMA_SUBALLOCATION_TYPE_FREE) { if (!VmaValidateMagicValue(pBlockData, it->offset - VMA_DEBUG_MARGIN)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED BEFORE VALIDATED ALLOCATION!"); return VK_ERROR_VALIDATION_FAILED_EXT; } if (!VmaValidateMagicValue(pBlockData, it->offset + it->size)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!"); return VK_ERROR_VALIDATION_FAILED_EXT; } } } return VK_SUCCESS; } void VmaBlockMetadata_Generic::Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation) { VMA_ASSERT(request.type == VmaAllocationRequestType::Normal); VMA_ASSERT(request.item != m_Suballocations.end()); VmaSuballocation& suballoc = *request.item; // Given suballocation is a free block. VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); // Given offset is inside this suballocation. VMA_ASSERT(request.offset >= suballoc.offset); const VkDeviceSize paddingBegin = request.offset - suballoc.offset; VMA_ASSERT(suballoc.size >= paddingBegin + allocSize); const VkDeviceSize paddingEnd = suballoc.size - paddingBegin - allocSize; // Unregister this free suballocation from m_FreeSuballocationsBySize and update // it to become used. UnregisterFreeSuballocation(request.item); suballoc.offset = request.offset; suballoc.size = allocSize; suballoc.type = type; suballoc.hAllocation = hAllocation; // If there are any free bytes remaining at the end, insert new free suballocation after current one. if (paddingEnd) { VmaSuballocation paddingSuballoc = {}; paddingSuballoc.offset = request.offset + allocSize; paddingSuballoc.size = paddingEnd; paddingSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; VmaSuballocationList::iterator next = request.item; ++next; const VmaSuballocationList::iterator paddingEndItem = m_Suballocations.insert(next, paddingSuballoc); RegisterFreeSuballocation(paddingEndItem); } // If there are any free bytes remaining at the beginning, insert new free suballocation before current one. if (paddingBegin) { VmaSuballocation paddingSuballoc = {}; paddingSuballoc.offset = request.offset - paddingBegin; paddingSuballoc.size = paddingBegin; paddingSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; const VmaSuballocationList::iterator paddingBeginItem = m_Suballocations.insert(request.item, paddingSuballoc); RegisterFreeSuballocation(paddingBeginItem); } // Update totals. m_FreeCount = m_FreeCount - 1; if (paddingBegin > 0) { ++m_FreeCount; } if (paddingEnd > 0) { ++m_FreeCount; } m_SumFreeSize -= allocSize; } void VmaBlockMetadata_Generic::Free(const VmaAllocation allocation) { for (VmaSuballocationList::iterator suballocItem = m_Suballocations.begin(); suballocItem != m_Suballocations.end(); ++suballocItem) { VmaSuballocation& suballoc = *suballocItem; if (suballoc.hAllocation == allocation) { FreeSuballocation(suballocItem); VMA_HEAVY_ASSERT(Validate()); return; } } VMA_ASSERT(0 && "Not found!"); } void VmaBlockMetadata_Generic::FreeAtOffset(VkDeviceSize offset) { for (VmaSuballocationList::iterator suballocItem = m_Suballocations.begin(); suballocItem != m_Suballocations.end(); ++suballocItem) { VmaSuballocation& suballoc = *suballocItem; if (suballoc.offset == offset) { FreeSuballocation(suballocItem); return; } } VMA_ASSERT(0 && "Not found!"); } bool VmaBlockMetadata_Generic::ValidateFreeSuballocationList() const { VkDeviceSize lastSize = 0; for (size_t i = 0, count = m_FreeSuballocationsBySize.size(); i < count; ++i) { const VmaSuballocationList::iterator it = m_FreeSuballocationsBySize[i]; VMA_VALIDATE(it->type == VMA_SUBALLOCATION_TYPE_FREE); VMA_VALIDATE(it->size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER); VMA_VALIDATE(it->size >= lastSize); lastSize = it->size; } return true; } bool VmaBlockMetadata_Generic::CheckAllocation( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, VmaSuballocationType allocType, VmaSuballocationList::const_iterator suballocItem, bool canMakeOtherLost, VkDeviceSize* pOffset, size_t* itemsToMakeLostCount, VkDeviceSize* pSumFreeSize, VkDeviceSize* pSumItemSize) const { VMA_ASSERT(allocSize > 0); VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(suballocItem != m_Suballocations.cend()); VMA_ASSERT(pOffset != VMA_NULL); *itemsToMakeLostCount = 0; *pSumFreeSize = 0; *pSumItemSize = 0; if (canMakeOtherLost) { if (suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE) { *pSumFreeSize = suballocItem->size; } else { if (suballocItem->hAllocation->CanBecomeLost() && suballocItem->hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) { ++* itemsToMakeLostCount; *pSumItemSize = suballocItem->size; } else { return false; } } // Remaining size is too small for this request: Early return. if (GetSize() - suballocItem->offset < allocSize) { return false; } // Start from offset equal to beginning of this suballocation. *pOffset = suballocItem->offset; // Apply VMA_DEBUG_MARGIN at the beginning. if (VMA_DEBUG_MARGIN > 0) { *pOffset += VMA_DEBUG_MARGIN; } // Apply alignment. *pOffset = VmaAlignUp(*pOffset, allocAlignment); // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. if (bufferImageGranularity > 1) { bool bufferImageGranularityConflict = false; VmaSuballocationList::const_iterator prevSuballocItem = suballocItem; while (prevSuballocItem != m_Suballocations.cbegin()) { --prevSuballocItem; const VmaSuballocation& prevSuballoc = *prevSuballocItem; if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, *pOffset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) { bufferImageGranularityConflict = true; break; } } else // Already on previous page. break; } if (bufferImageGranularityConflict) { *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity); } } // Now that we have final *pOffset, check if we are past suballocItem. // If yes, return false - this function should be called for another suballocItem as starting point. if (*pOffset >= suballocItem->offset + suballocItem->size) { return false; } // Calculate padding at the beginning based on current offset. const VkDeviceSize paddingBegin = *pOffset - suballocItem->offset; // Calculate required margin at the end. const VkDeviceSize requiredEndMargin = VMA_DEBUG_MARGIN; const VkDeviceSize totalSize = paddingBegin + allocSize + requiredEndMargin; // Another early return check. if (suballocItem->offset + totalSize > GetSize()) { return false; } // Advance lastSuballocItem until desired size is reached. // Update itemsToMakeLostCount. VmaSuballocationList::const_iterator lastSuballocItem = suballocItem; if (totalSize > suballocItem->size) { VkDeviceSize remainingSize = totalSize - suballocItem->size; while (remainingSize > 0) { ++lastSuballocItem; if (lastSuballocItem == m_Suballocations.cend()) { return false; } if (lastSuballocItem->type == VMA_SUBALLOCATION_TYPE_FREE) { *pSumFreeSize += lastSuballocItem->size; } else { VMA_ASSERT(lastSuballocItem->hAllocation != VK_NULL_HANDLE); if (lastSuballocItem->hAllocation->CanBecomeLost() && lastSuballocItem->hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) { ++* itemsToMakeLostCount; *pSumItemSize += lastSuballocItem->size; } else { return false; } } remainingSize = (lastSuballocItem->size < remainingSize) ? remainingSize - lastSuballocItem->size : 0; } } // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, we must mark more allocations lost or fail. if (bufferImageGranularity > 1) { VmaSuballocationList::const_iterator nextSuballocItem = lastSuballocItem; ++nextSuballocItem; while (nextSuballocItem != m_Suballocations.cend()) { const VmaSuballocation& nextSuballoc = *nextSuballocItem; if (VmaBlocksOnSamePage(*pOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) { VMA_ASSERT(nextSuballoc.hAllocation != VK_NULL_HANDLE); if (nextSuballoc.hAllocation->CanBecomeLost() && nextSuballoc.hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) { ++* itemsToMakeLostCount; } else { return false; } } } else { // Already on next page. break; } ++nextSuballocItem; } } } else { const VmaSuballocation& suballoc = *suballocItem; VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); *pSumFreeSize = suballoc.size; // Size of this suballocation is too small for this request: Early return. if (suballoc.size < allocSize) { return false; } // Start from offset equal to beginning of this suballocation. *pOffset = suballoc.offset; // Apply VMA_DEBUG_MARGIN at the beginning. if (VMA_DEBUG_MARGIN > 0) { *pOffset += VMA_DEBUG_MARGIN; } // Apply alignment. *pOffset = VmaAlignUp(*pOffset, allocAlignment); // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. if (bufferImageGranularity > 1) { bool bufferImageGranularityConflict = false; VmaSuballocationList::const_iterator prevSuballocItem = suballocItem; while (prevSuballocItem != m_Suballocations.cbegin()) { --prevSuballocItem; const VmaSuballocation& prevSuballoc = *prevSuballocItem; if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, *pOffset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) { bufferImageGranularityConflict = true; break; } } else // Already on previous page. break; } if (bufferImageGranularityConflict) { *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity); } } // Calculate padding at the beginning based on current offset. const VkDeviceSize paddingBegin = *pOffset - suballoc.offset; // Calculate required margin at the end. const VkDeviceSize requiredEndMargin = VMA_DEBUG_MARGIN; // Fail if requested size plus margin before and after is bigger than size of this suballocation. if (paddingBegin + allocSize + requiredEndMargin > suballoc.size) { return false; } // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. if (bufferImageGranularity > 1) { VmaSuballocationList::const_iterator nextSuballocItem = suballocItem; ++nextSuballocItem; while (nextSuballocItem != m_Suballocations.cend()) { const VmaSuballocation& nextSuballoc = *nextSuballocItem; if (VmaBlocksOnSamePage(*pOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) { return false; } } else { // Already on next page. break; } ++nextSuballocItem; } } } // All tests passed: Success. pOffset is already filled. return true; } void VmaBlockMetadata_Generic::MergeFreeWithNext(VmaSuballocationList::iterator item) { VMA_ASSERT(item != m_Suballocations.end()); VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE); VmaSuballocationList::iterator nextItem = item; ++nextItem; VMA_ASSERT(nextItem != m_Suballocations.end()); VMA_ASSERT(nextItem->type == VMA_SUBALLOCATION_TYPE_FREE); item->size += nextItem->size; --m_FreeCount; m_Suballocations.erase(nextItem); } VmaSuballocationList::iterator VmaBlockMetadata_Generic::FreeSuballocation(VmaSuballocationList::iterator suballocItem) { // Change this suballocation to be marked as free. VmaSuballocation& suballoc = *suballocItem; suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; // Update totals. ++m_FreeCount; m_SumFreeSize += suballoc.size; // Merge with previous and/or next suballocation if it's also free. bool mergeWithNext = false; bool mergeWithPrev = false; VmaSuballocationList::iterator nextItem = suballocItem; ++nextItem; if ((nextItem != m_Suballocations.end()) && (nextItem->type == VMA_SUBALLOCATION_TYPE_FREE)) { mergeWithNext = true; } VmaSuballocationList::iterator prevItem = suballocItem; if (suballocItem != m_Suballocations.begin()) { --prevItem; if (prevItem->type == VMA_SUBALLOCATION_TYPE_FREE) { mergeWithPrev = true; } } if (mergeWithNext) { UnregisterFreeSuballocation(nextItem); MergeFreeWithNext(suballocItem); } if (mergeWithPrev) { UnregisterFreeSuballocation(prevItem); MergeFreeWithNext(prevItem); RegisterFreeSuballocation(prevItem); return prevItem; } else { RegisterFreeSuballocation(suballocItem); return suballocItem; } } void VmaBlockMetadata_Generic::RegisterFreeSuballocation(VmaSuballocationList::iterator item) { VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(item->size > 0); // You may want to enable this validation at the beginning or at the end of // this function, depending on what do you want to check. VMA_HEAVY_ASSERT(ValidateFreeSuballocationList()); if (item->size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { if (m_FreeSuballocationsBySize.empty()) { m_FreeSuballocationsBySize.push_back(item); } else { VmaVectorInsertSorted<VmaSuballocationItemSizeLess>(m_FreeSuballocationsBySize, item); } } //VMA_HEAVY_ASSERT(ValidateFreeSuballocationList()); } void VmaBlockMetadata_Generic::UnregisterFreeSuballocation(VmaSuballocationList::iterator item) { VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(item->size > 0); // You may want to enable this validation at the beginning or at the end of // this function, depending on what do you want to check. VMA_HEAVY_ASSERT(ValidateFreeSuballocationList()); if (item->size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { VmaSuballocationList::iterator* const it = VmaBinaryFindFirstNotLess( m_FreeSuballocationsBySize.data(), m_FreeSuballocationsBySize.data() + m_FreeSuballocationsBySize.size(), item, VmaSuballocationItemSizeLess()); for (size_t index = it - m_FreeSuballocationsBySize.data(); index < m_FreeSuballocationsBySize.size(); ++index) { if (m_FreeSuballocationsBySize[index] == item) { VmaVectorRemove(m_FreeSuballocationsBySize, index); return; } VMA_ASSERT((m_FreeSuballocationsBySize[index]->size == item->size) && "Not found."); } VMA_ASSERT(0 && "Not found."); } //VMA_HEAVY_ASSERT(ValidateFreeSuballocationList()); } bool VmaBlockMetadata_Generic::IsBufferImageGranularityConflictPossible( VkDeviceSize bufferImageGranularity, VmaSuballocationType& inOutPrevSuballocType) const { if (bufferImageGranularity == 1 || IsEmpty()) { return false; } VkDeviceSize minAlignment = VK_WHOLE_SIZE; bool typeConflictFound = false; for (VmaSuballocationList::const_iterator it = m_Suballocations.cbegin(); it != m_Suballocations.cend(); ++it) { const VmaSuballocationType suballocType = it->type; if (suballocType != VMA_SUBALLOCATION_TYPE_FREE) { minAlignment = VMA_MIN(minAlignment, it->hAllocation->GetAlignment()); if (VmaIsBufferImageGranularityConflict(inOutPrevSuballocType, suballocType)) { typeConflictFound = true; } inOutPrevSuballocType = suballocType; } } return typeConflictFound || minAlignment >= bufferImageGranularity; } //////////////////////////////////////////////////////////////////////////////// // class VmaBlockMetadata_Linear VmaBlockMetadata_Linear::VmaBlockMetadata_Linear(VmaAllocator hAllocator) : VmaBlockMetadata(hAllocator), m_SumFreeSize(0), m_Suballocations0(VmaStlAllocator<VmaSuballocation>(hAllocator->GetAllocationCallbacks())), m_Suballocations1(VmaStlAllocator<VmaSuballocation>(hAllocator->GetAllocationCallbacks())), m_1stVectorIndex(0), m_2ndVectorMode(SECOND_VECTOR_EMPTY), m_1stNullItemsBeginCount(0), m_1stNullItemsMiddleCount(0), m_2ndNullItemsCount(0) { } VmaBlockMetadata_Linear::~VmaBlockMetadata_Linear() { } void VmaBlockMetadata_Linear::Init(VkDeviceSize size) { VmaBlockMetadata::Init(size); m_SumFreeSize = size; } bool VmaBlockMetadata_Linear::Validate() const { const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); VMA_VALIDATE(suballocations2nd.empty() == (m_2ndVectorMode == SECOND_VECTOR_EMPTY)); VMA_VALIDATE(!suballocations1st.empty() || suballocations2nd.empty() || m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER); if (!suballocations1st.empty()) { // Null item at the beginning should be accounted into m_1stNullItemsBeginCount. VMA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].hAllocation != VK_NULL_HANDLE); // Null item at the end should be just pop_back(). VMA_VALIDATE(suballocations1st.back().hAllocation != VK_NULL_HANDLE); } if (!suballocations2nd.empty()) { // Null item at the end should be just pop_back(). VMA_VALIDATE(suballocations2nd.back().hAllocation != VK_NULL_HANDLE); } VMA_VALIDATE(m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount <= suballocations1st.size()); VMA_VALIDATE(m_2ndNullItemsCount <= suballocations2nd.size()); VkDeviceSize sumUsedSize = 0; const size_t suballoc1stCount = suballocations1st.size(); VkDeviceSize offset = VMA_DEBUG_MARGIN; if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { const size_t suballoc2ndCount = suballocations2nd.size(); size_t nullItem2ndCount = 0; for (size_t i = 0; i < suballoc2ndCount; ++i) { const VmaSuballocation& suballoc = suballocations2nd[i]; const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); VMA_VALIDATE(currFree == (suballoc.hAllocation == VK_NULL_HANDLE)); VMA_VALIDATE(suballoc.offset >= offset); if (!currFree) { VMA_VALIDATE(suballoc.hAllocation->GetOffset() == suballoc.offset); VMA_VALIDATE(suballoc.hAllocation->GetSize() == suballoc.size); sumUsedSize += suballoc.size; } else { ++nullItem2ndCount; } offset = suballoc.offset + suballoc.size + VMA_DEBUG_MARGIN; } VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); } for (size_t i = 0; i < m_1stNullItemsBeginCount; ++i) { const VmaSuballocation& suballoc = suballocations1st[i]; VMA_VALIDATE(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE && suballoc.hAllocation == VK_NULL_HANDLE); } size_t nullItem1stCount = m_1stNullItemsBeginCount; for (size_t i = m_1stNullItemsBeginCount; i < suballoc1stCount; ++i) { const VmaSuballocation& suballoc = suballocations1st[i]; const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); VMA_VALIDATE(currFree == (suballoc.hAllocation == VK_NULL_HANDLE)); VMA_VALIDATE(suballoc.offset >= offset); VMA_VALIDATE(i >= m_1stNullItemsBeginCount || currFree); if (!currFree) { VMA_VALIDATE(suballoc.hAllocation->GetOffset() == suballoc.offset); VMA_VALIDATE(suballoc.hAllocation->GetSize() == suballoc.size); sumUsedSize += suballoc.size; } else { ++nullItem1stCount; } offset = suballoc.offset + suballoc.size + VMA_DEBUG_MARGIN; } VMA_VALIDATE(nullItem1stCount == m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount); if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { const size_t suballoc2ndCount = suballocations2nd.size(); size_t nullItem2ndCount = 0; for (size_t i = suballoc2ndCount; i--; ) { const VmaSuballocation& suballoc = suballocations2nd[i]; const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); VMA_VALIDATE(currFree == (suballoc.hAllocation == VK_NULL_HANDLE)); VMA_VALIDATE(suballoc.offset >= offset); if (!currFree) { VMA_VALIDATE(suballoc.hAllocation->GetOffset() == suballoc.offset); VMA_VALIDATE(suballoc.hAllocation->GetSize() == suballoc.size); sumUsedSize += suballoc.size; } else { ++nullItem2ndCount; } offset = suballoc.offset + suballoc.size + VMA_DEBUG_MARGIN; } VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); } VMA_VALIDATE(offset <= GetSize()); VMA_VALIDATE(m_SumFreeSize == GetSize() - sumUsedSize); return true; } size_t VmaBlockMetadata_Linear::GetAllocationCount() const { return AccessSuballocations1st().size() - (m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount) + AccessSuballocations2nd().size() - m_2ndNullItemsCount; } VkDeviceSize VmaBlockMetadata_Linear::GetUnusedRangeSizeMax() const { const VkDeviceSize size = GetSize(); /* We don't consider gaps inside allocation vectors with freed allocations because they are not suitable for reuse in linear allocator. We consider only space that is available for new allocations. */ if (IsEmpty()) { return size; } const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); switch (m_2ndVectorMode) { case SECOND_VECTOR_EMPTY: /* Available space is after end of 1st, as well as before beginning of 1st (which whould make it a ring buffer). */ { const size_t suballocations1stCount = suballocations1st.size(); VMA_ASSERT(suballocations1stCount > m_1stNullItemsBeginCount); const VmaSuballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount]; const VmaSuballocation& lastSuballoc = suballocations1st[suballocations1stCount - 1]; return VMA_MAX( firstSuballoc.offset, size - (lastSuballoc.offset + lastSuballoc.size)); } break; case SECOND_VECTOR_RING_BUFFER: /* Available space is only between end of 2nd and beginning of 1st. */ { const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); const VmaSuballocation& lastSuballoc2nd = suballocations2nd.back(); const VmaSuballocation& firstSuballoc1st = suballocations1st[m_1stNullItemsBeginCount]; return firstSuballoc1st.offset - (lastSuballoc2nd.offset + lastSuballoc2nd.size); } break; case SECOND_VECTOR_DOUBLE_STACK: /* Available space is only between end of 1st and top of 2nd. */ { const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); const VmaSuballocation& topSuballoc2nd = suballocations2nd.back(); const VmaSuballocation& lastSuballoc1st = suballocations1st.back(); return topSuballoc2nd.offset - (lastSuballoc1st.offset + lastSuballoc1st.size); } break; default: VMA_ASSERT(0); return 0; } } void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const { const VkDeviceSize size = GetSize(); const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); const size_t suballoc1stCount = suballocations1st.size(); const size_t suballoc2ndCount = suballocations2nd.size(); outInfo.blockCount = 1; outInfo.allocationCount = (uint32_t)GetAllocationCount(); outInfo.unusedRangeCount = 0; outInfo.usedBytes = 0; outInfo.allocationSizeMin = UINT64_MAX; outInfo.allocationSizeMax = 0; outInfo.unusedRangeSizeMin = UINT64_MAX; outInfo.unusedRangeSizeMax = 0; VkDeviceSize lastOffset = 0; if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; size_t nextAlloc2ndIndex = 0; while (lastOffset < freeSpace2ndTo1stEnd) { // Find next non-null allocation or move nextAllocIndex to the end. while (nextAlloc2ndIndex < suballoc2ndCount && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; } // We are at the end. else { // There is free space from lastOffset to freeSpace2ndTo1stEnd. if (lastOffset < freeSpace2ndTo1stEnd) { const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } // End of loop. lastOffset = freeSpace2ndTo1stEnd; } } } size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; const VkDeviceSize freeSpace1stTo2ndEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; while (lastOffset < freeSpace1stTo2ndEnd) { // Find next non-null allocation or move nextAllocIndex to the end. while (nextAlloc1stIndex < suballoc1stCount && suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc1stIndex; } // Found non-null allocation. if (nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; } // We are at the end. else { // There is free space from lastOffset to freeSpace1stTo2ndEnd. if (lastOffset < freeSpace1stTo2ndEnd) { const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } // End of loop. lastOffset = freeSpace1stTo2ndEnd; } } if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; while (lastOffset < size) { // Find next non-null allocation or move nextAllocIndex to the end. while (nextAlloc2ndIndex != SIZE_MAX && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { --nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; } // We are at the end. else { // There is free space from lastOffset to size. if (lastOffset < size) { const VkDeviceSize unusedRangeSize = size - lastOffset; ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } // End of loop. lastOffset = size; } } } outInfo.unusedBytes = size - outInfo.usedBytes; } void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const { const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); const VkDeviceSize size = GetSize(); const size_t suballoc1stCount = suballocations1st.size(); const size_t suballoc2ndCount = suballocations2nd.size(); inoutStats.size += size; VkDeviceSize lastOffset = 0; if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; size_t nextAlloc2ndIndex = m_1stNullItemsBeginCount; while (lastOffset < freeSpace2ndTo1stEnd) { // Find next non-null allocation or move nextAlloc2ndIndex to the end. while (nextAlloc2ndIndex < suballoc2ndCount && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; inoutStats.unusedSize += unusedRangeSize; ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; } // We are at the end. else { if (lastOffset < freeSpace2ndTo1stEnd) { // There is free space from lastOffset to freeSpace2ndTo1stEnd. const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; inoutStats.unusedSize += unusedRangeSize; ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } // End of loop. lastOffset = freeSpace2ndTo1stEnd; } } } size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; const VkDeviceSize freeSpace1stTo2ndEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; while (lastOffset < freeSpace1stTo2ndEnd) { // Find next non-null allocation or move nextAllocIndex to the end. while (nextAlloc1stIndex < suballoc1stCount && suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc1stIndex; } // Found non-null allocation. if (nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; inoutStats.unusedSize += unusedRangeSize; ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; } // We are at the end. else { if (lastOffset < freeSpace1stTo2ndEnd) { // There is free space from lastOffset to freeSpace1stTo2ndEnd. const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; inoutStats.unusedSize += unusedRangeSize; ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } // End of loop. lastOffset = freeSpace1stTo2ndEnd; } } if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; while (lastOffset < size) { // Find next non-null allocation or move nextAlloc2ndIndex to the end. while (nextAlloc2ndIndex != SIZE_MAX && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { --nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; inoutStats.unusedSize += unusedRangeSize; ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; } // We are at the end. else { if (lastOffset < size) { // There is free space from lastOffset to size. const VkDeviceSize unusedRangeSize = size - lastOffset; inoutStats.unusedSize += unusedRangeSize; ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } // End of loop. lastOffset = size; } } } } #if VMA_STATS_STRING_ENABLED void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const { const VkDeviceSize size = GetSize(); const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); const size_t suballoc1stCount = suballocations1st.size(); const size_t suballoc2ndCount = suballocations2nd.size(); // FIRST PASS size_t unusedRangeCount = 0; VkDeviceSize usedBytes = 0; VkDeviceSize lastOffset = 0; size_t alloc2ndCount = 0; if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; size_t nextAlloc2ndIndex = 0; while (lastOffset < freeSpace2ndTo1stEnd) { // Find next non-null allocation or move nextAlloc2ndIndex to the end. while (nextAlloc2ndIndex < suballoc2ndCount && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc2ndCount; usedBytes += suballoc.size; // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; } // We are at the end. else { if (lastOffset < freeSpace2ndTo1stEnd) { // There is free space from lastOffset to freeSpace2ndTo1stEnd. ++unusedRangeCount; } // End of loop. lastOffset = freeSpace2ndTo1stEnd; } } } size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; size_t alloc1stCount = 0; const VkDeviceSize freeSpace1stTo2ndEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; while (lastOffset < freeSpace1stTo2ndEnd) { // Find next non-null allocation or move nextAllocIndex to the end. while (nextAlloc1stIndex < suballoc1stCount && suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc1stIndex; } // Found non-null allocation. if (nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc1stCount; usedBytes += suballoc.size; // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; } // We are at the end. else { if (lastOffset < size) { // There is free space from lastOffset to freeSpace1stTo2ndEnd. ++unusedRangeCount; } // End of loop. lastOffset = freeSpace1stTo2ndEnd; } } if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; while (lastOffset < size) { // Find next non-null allocation or move nextAlloc2ndIndex to the end. while (nextAlloc2ndIndex != SIZE_MAX && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { --nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc2ndCount; usedBytes += suballoc.size; // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; } // We are at the end. else { if (lastOffset < size) { // There is free space from lastOffset to size. ++unusedRangeCount; } // End of loop. lastOffset = size; } } } const VkDeviceSize unusedBytes = size - usedBytes; PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount); // SECOND PASS lastOffset = 0; if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; size_t nextAlloc2ndIndex = 0; while (lastOffset < freeSpace2ndTo1stEnd) { // Find next non-null allocation or move nextAlloc2ndIndex to the end. while (nextAlloc2ndIndex < suballoc2ndCount && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; } // We are at the end. else { if (lastOffset < freeSpace2ndTo1stEnd) { // There is free space from lastOffset to freeSpace2ndTo1stEnd. const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } // End of loop. lastOffset = freeSpace2ndTo1stEnd; } } } nextAlloc1stIndex = m_1stNullItemsBeginCount; while (lastOffset < freeSpace1stTo2ndEnd) { // Find next non-null allocation or move nextAllocIndex to the end. while (nextAlloc1stIndex < suballoc1stCount && suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) { ++nextAlloc1stIndex; } // Found non-null allocation. if (nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; } // We are at the end. else { if (lastOffset < freeSpace1stTo2ndEnd) { // There is free space from lastOffset to freeSpace1stTo2ndEnd. const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } // End of loop. lastOffset = freeSpace1stTo2ndEnd; } } if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; while (lastOffset < size) { // Find next non-null allocation or move nextAlloc2ndIndex to the end. while (nextAlloc2ndIndex != SIZE_MAX && suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) { --nextAlloc2ndIndex; } // Found non-null allocation. if (nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; // 1. Process free space before this allocation. if (lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; } // We are at the end. else { if (lastOffset < size) { // There is free space from lastOffset to size. const VkDeviceSize unusedRangeSize = size - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } // End of loop. lastOffset = size; } } } PrintDetailedMap_End(json); } #endif // #if VMA_STATS_STRING_ENABLED bool VmaBlockMetadata_Linear::CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { VMA_ASSERT(allocSize > 0); VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(pAllocationRequest != VMA_NULL); VMA_HEAVY_ASSERT(Validate()); return upperAddress ? CreateAllocationRequest_UpperAddress( currentFrameIndex, frameInUseCount, bufferImageGranularity, allocSize, allocAlignment, allocType, canMakeOtherLost, strategy, pAllocationRequest) : CreateAllocationRequest_LowerAddress( currentFrameIndex, frameInUseCount, bufferImageGranularity, allocSize, allocAlignment, allocType, canMakeOtherLost, strategy, pAllocationRequest); } bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { const VkDeviceSize size = GetSize(); SuballocationVectorType& suballocations1st = AccessSuballocations1st(); SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { VMA_ASSERT(0 && "Trying to use pool with linear algorithm as double stack, while it is already being used as ring buffer."); return false; } // Try to allocate before 2nd.back(), or end of block if 2nd.empty(). if (allocSize > size) { return false; } VkDeviceSize resultBaseOffset = size - allocSize; if (!suballocations2nd.empty()) { const VmaSuballocation& lastSuballoc = suballocations2nd.back(); resultBaseOffset = lastSuballoc.offset - allocSize; if (allocSize > lastSuballoc.offset) { return false; } } // Start from offset equal to end of free space. VkDeviceSize resultOffset = resultBaseOffset; // Apply VMA_DEBUG_MARGIN at the end. if (VMA_DEBUG_MARGIN > 0) { if (resultOffset < VMA_DEBUG_MARGIN) { return false; } resultOffset -= VMA_DEBUG_MARGIN; } // Apply alignment. resultOffset = VmaAlignDown(resultOffset, allocAlignment); // Check next suballocations from 2nd for BufferImageGranularity conflicts. // Make bigger alignment if necessary. if (bufferImageGranularity > 1 && !suballocations2nd.empty()) { bool bufferImageGranularityConflict = false; for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) { const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex]; if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(nextSuballoc.type, allocType)) { bufferImageGranularityConflict = true; break; } } else // Already on previous page. break; } if (bufferImageGranularityConflict) { resultOffset = VmaAlignDown(resultOffset, bufferImageGranularity); } } // There is enough free space. const VkDeviceSize endOf1st = !suballocations1st.empty() ? suballocations1st.back().offset + suballocations1st.back().size : 0; if (endOf1st + VMA_DEBUG_MARGIN <= resultOffset) { // Check previous suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. if (bufferImageGranularity > 1) { for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) { const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex]; if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(allocType, prevSuballoc.type)) { return false; } } else { // Already on next page. break; } } } // All tests passed: Success. pAllocationRequest->offset = resultOffset; pAllocationRequest->sumFreeSize = resultBaseOffset + allocSize - endOf1st; pAllocationRequest->sumItemSize = 0; // pAllocationRequest->item unused. pAllocationRequest->itemsToMakeLostCount = 0; pAllocationRequest->type = VmaAllocationRequestType::UpperAddress; return true; } return false; } bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { const VkDeviceSize size = GetSize(); SuballocationVectorType& suballocations1st = AccessSuballocations1st(); SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { // Try to allocate at the end of 1st vector. VkDeviceSize resultBaseOffset = 0; if (!suballocations1st.empty()) { const VmaSuballocation& lastSuballoc = suballocations1st.back(); resultBaseOffset = lastSuballoc.offset + lastSuballoc.size; } // Start from offset equal to beginning of free space. VkDeviceSize resultOffset = resultBaseOffset; // Apply VMA_DEBUG_MARGIN at the beginning. if (VMA_DEBUG_MARGIN > 0) { resultOffset += VMA_DEBUG_MARGIN; } // Apply alignment. resultOffset = VmaAlignUp(resultOffset, allocAlignment); // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. if (bufferImageGranularity > 1 && !suballocations1st.empty()) { bool bufferImageGranularityConflict = false; for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) { const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex]; if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) { bufferImageGranularityConflict = true; break; } } else // Already on previous page. break; } if (bufferImageGranularityConflict) { resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity); } } const VkDeviceSize freeSpaceEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; // There is enough free space at the end after alignment. if (resultOffset + allocSize + VMA_DEBUG_MARGIN <= freeSpaceEnd) { // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. if (bufferImageGranularity > 1 && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) { const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex]; if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) { return false; } } else { // Already on previous page. break; } } } // All tests passed: Success. pAllocationRequest->offset = resultOffset; pAllocationRequest->sumFreeSize = freeSpaceEnd - resultBaseOffset; pAllocationRequest->sumItemSize = 0; // pAllocationRequest->item, customData unused. pAllocationRequest->type = VmaAllocationRequestType::EndOf1st; pAllocationRequest->itemsToMakeLostCount = 0; return true; } } // Wrap-around to end of 2nd vector. Try to allocate there, watching for the // beginning of 1st vector as the end of free space. if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { VMA_ASSERT(!suballocations1st.empty()); VkDeviceSize resultBaseOffset = 0; if (!suballocations2nd.empty()) { const VmaSuballocation& lastSuballoc = suballocations2nd.back(); resultBaseOffset = lastSuballoc.offset + lastSuballoc.size; } // Start from offset equal to beginning of free space. VkDeviceSize resultOffset = resultBaseOffset; // Apply VMA_DEBUG_MARGIN at the beginning. if (VMA_DEBUG_MARGIN > 0) { resultOffset += VMA_DEBUG_MARGIN; } // Apply alignment. resultOffset = VmaAlignUp(resultOffset, allocAlignment); // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. if (bufferImageGranularity > 1 && !suballocations2nd.empty()) { bool bufferImageGranularityConflict = false; for (size_t prevSuballocIndex = suballocations2nd.size(); prevSuballocIndex--; ) { const VmaSuballocation& prevSuballoc = suballocations2nd[prevSuballocIndex]; if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) { bufferImageGranularityConflict = true; break; } } else // Already on previous page. break; } if (bufferImageGranularityConflict) { resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity); } } pAllocationRequest->itemsToMakeLostCount = 0; pAllocationRequest->sumItemSize = 0; size_t index1st = m_1stNullItemsBeginCount; if (canMakeOtherLost) { while (index1st < suballocations1st.size() && resultOffset + allocSize + VMA_DEBUG_MARGIN > suballocations1st[index1st].offset) { // Next colliding allocation at the beginning of 1st vector found. Try to make it lost. const VmaSuballocation& suballoc = suballocations1st[index1st]; if (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE) { // No problem. } else { VMA_ASSERT(suballoc.hAllocation != VK_NULL_HANDLE); if (suballoc.hAllocation->CanBecomeLost() && suballoc.hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) { ++pAllocationRequest->itemsToMakeLostCount; pAllocationRequest->sumItemSize += suballoc.size; } else { return false; } } ++index1st; } // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, we must mark more allocations lost or fail. if (bufferImageGranularity > 1) { while (index1st < suballocations1st.size()) { const VmaSuballocation& suballoc = suballocations1st[index1st]; if (VmaBlocksOnSamePage(resultOffset, allocSize, suballoc.offset, bufferImageGranularity)) { if (suballoc.hAllocation != VK_NULL_HANDLE) { // Not checking actual VmaIsBufferImageGranularityConflict(allocType, suballoc.type). if (suballoc.hAllocation->CanBecomeLost() && suballoc.hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) { ++pAllocationRequest->itemsToMakeLostCount; pAllocationRequest->sumItemSize += suballoc.size; } else { return false; } } } else { // Already on next page. break; } ++index1st; } } // Special case: There is not enough room at the end for this allocation, even after making all from the 1st lost. if (index1st == suballocations1st.size() && resultOffset + allocSize + VMA_DEBUG_MARGIN > size) { // TODO: This is a known bug that it's not yet implemented and the allocation is failing. VMA_DEBUG_LOG("Unsupported special case in custom pool with linear allocation algorithm used as ring buffer with allocations that can be lost."); } } // There is enough free space at the end after alignment. if ((index1st == suballocations1st.size() && resultOffset + allocSize + VMA_DEBUG_MARGIN <= size) || (index1st < suballocations1st.size() && resultOffset + allocSize + VMA_DEBUG_MARGIN <= suballocations1st[index1st].offset)) { // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. if (bufferImageGranularity > 1) { for (size_t nextSuballocIndex = index1st; nextSuballocIndex < suballocations1st.size(); nextSuballocIndex++) { const VmaSuballocation& nextSuballoc = suballocations1st[nextSuballocIndex]; if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) { if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) { return false; } } else { // Already on next page. break; } } } // All tests passed: Success. pAllocationRequest->offset = resultOffset; pAllocationRequest->sumFreeSize = (index1st < suballocations1st.size() ? suballocations1st[index1st].offset : size) - resultBaseOffset - pAllocationRequest->sumItemSize; pAllocationRequest->type = VmaAllocationRequestType::EndOf2nd; // pAllocationRequest->item, customData unused. return true; } } return false; } bool VmaBlockMetadata_Linear::MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest) { if (pAllocationRequest->itemsToMakeLostCount == 0) { return true; } VMA_ASSERT(m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER); // We always start from 1st. SuballocationVectorType* suballocations = &AccessSuballocations1st(); size_t index = m_1stNullItemsBeginCount; size_t madeLostCount = 0; while (madeLostCount < pAllocationRequest->itemsToMakeLostCount) { if (index == suballocations->size()) { index = 0; // If we get to the end of 1st, we wrap around to beginning of 2nd of 1st. if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { suballocations = &AccessSuballocations2nd(); } // else: m_2ndVectorMode == SECOND_VECTOR_EMPTY: // suballocations continues pointing at AccessSuballocations1st(). VMA_ASSERT(!suballocations->empty()); } VmaSuballocation& suballoc = (*suballocations)[index]; if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) { VMA_ASSERT(suballoc.hAllocation != VK_NULL_HANDLE); VMA_ASSERT(suballoc.hAllocation->CanBecomeLost()); if (suballoc.hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) { suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; m_SumFreeSize += suballoc.size; if (suballocations == &AccessSuballocations1st()) { ++m_1stNullItemsMiddleCount; } else { ++m_2ndNullItemsCount; } ++madeLostCount; } else { return false; } } ++index; } CleanupAfterFree(); //VMA_HEAVY_ASSERT(Validate()); // Already called by ClanupAfterFree(). return true; } uint32_t VmaBlockMetadata_Linear::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) { uint32_t lostAllocationCount = 0; SuballocationVectorType& suballocations1st = AccessSuballocations1st(); for (size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) { VmaSuballocation& suballoc = suballocations1st[i]; if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE && suballoc.hAllocation->CanBecomeLost() && suballoc.hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) { suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; ++m_1stNullItemsMiddleCount; m_SumFreeSize += suballoc.size; ++lostAllocationCount; } } SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); for (size_t i = 0, count = suballocations2nd.size(); i < count; ++i) { VmaSuballocation& suballoc = suballocations2nd[i]; if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE && suballoc.hAllocation->CanBecomeLost() && suballoc.hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) { suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; ++m_2ndNullItemsCount; m_SumFreeSize += suballoc.size; ++lostAllocationCount; } } if (lostAllocationCount) { CleanupAfterFree(); } return lostAllocationCount; } VkResult VmaBlockMetadata_Linear::CheckCorruption(const void* pBlockData) { SuballocationVectorType& suballocations1st = AccessSuballocations1st(); for (size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) { const VmaSuballocation& suballoc = suballocations1st[i]; if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) { if (!VmaValidateMagicValue(pBlockData, suballoc.offset - VMA_DEBUG_MARGIN)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED BEFORE VALIDATED ALLOCATION!"); return VK_ERROR_VALIDATION_FAILED_EXT; } if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!"); return VK_ERROR_VALIDATION_FAILED_EXT; } } } SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); for (size_t i = 0, count = suballocations2nd.size(); i < count; ++i) { const VmaSuballocation& suballoc = suballocations2nd[i]; if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) { if (!VmaValidateMagicValue(pBlockData, suballoc.offset - VMA_DEBUG_MARGIN)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED BEFORE VALIDATED ALLOCATION!"); return VK_ERROR_VALIDATION_FAILED_EXT; } if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!"); return VK_ERROR_VALIDATION_FAILED_EXT; } } } return VK_SUCCESS; } void VmaBlockMetadata_Linear::Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation) { const VmaSuballocation newSuballoc = { request.offset, allocSize, hAllocation, type }; switch (request.type) { case VmaAllocationRequestType::UpperAddress: { VMA_ASSERT(m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER && "CRITICAL ERROR: Trying to use linear allocator as double stack while it was already used as ring buffer."); SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); suballocations2nd.push_back(newSuballoc); m_2ndVectorMode = SECOND_VECTOR_DOUBLE_STACK; } break; case VmaAllocationRequestType::EndOf1st: { SuballocationVectorType& suballocations1st = AccessSuballocations1st(); VMA_ASSERT(suballocations1st.empty() || request.offset >= suballocations1st.back().offset + suballocations1st.back().size); // Check if it fits before the end of the block. VMA_ASSERT(request.offset + allocSize <= GetSize()); suballocations1st.push_back(newSuballoc); } break; case VmaAllocationRequestType::EndOf2nd: { SuballocationVectorType& suballocations1st = AccessSuballocations1st(); // New allocation at the end of 2-part ring buffer, so before first allocation from 1st vector. VMA_ASSERT(!suballocations1st.empty() && request.offset + allocSize <= suballocations1st[m_1stNullItemsBeginCount].offset); SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); switch (m_2ndVectorMode) { case SECOND_VECTOR_EMPTY: // First allocation from second part ring buffer. VMA_ASSERT(suballocations2nd.empty()); m_2ndVectorMode = SECOND_VECTOR_RING_BUFFER; break; case SECOND_VECTOR_RING_BUFFER: // 2-part ring buffer is already started. VMA_ASSERT(!suballocations2nd.empty()); break; case SECOND_VECTOR_DOUBLE_STACK: VMA_ASSERT(0 && "CRITICAL ERROR: Trying to use linear allocator as ring buffer while it was already used as double stack."); break; default: VMA_ASSERT(0); } suballocations2nd.push_back(newSuballoc); } break; default: VMA_ASSERT(0 && "CRITICAL INTERNAL ERROR."); } m_SumFreeSize -= newSuballoc.size; } void VmaBlockMetadata_Linear::Free(const VmaAllocation allocation) { FreeAtOffset(allocation->GetOffset()); } void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset) { SuballocationVectorType& suballocations1st = AccessSuballocations1st(); SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); if (!suballocations1st.empty()) { // First allocation: Mark it as next empty at the beginning. VmaSuballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount]; if (firstSuballoc.offset == offset) { firstSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; firstSuballoc.hAllocation = VK_NULL_HANDLE; m_SumFreeSize += firstSuballoc.size; ++m_1stNullItemsBeginCount; CleanupAfterFree(); return; } } // Last allocation in 2-part ring buffer or top of upper stack (same logic). if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { VmaSuballocation& lastSuballoc = suballocations2nd.back(); if (lastSuballoc.offset == offset) { m_SumFreeSize += lastSuballoc.size; suballocations2nd.pop_back(); CleanupAfterFree(); return; } } // Last allocation in 1st vector. else if (m_2ndVectorMode == SECOND_VECTOR_EMPTY) { VmaSuballocation& lastSuballoc = suballocations1st.back(); if (lastSuballoc.offset == offset) { m_SumFreeSize += lastSuballoc.size; suballocations1st.pop_back(); CleanupAfterFree(); return; } } // Item from the middle of 1st vector. { VmaSuballocation refSuballoc; refSuballoc.offset = offset; // Rest of members stays uninitialized intentionally for better performance. SuballocationVectorType::iterator it = VmaBinaryFindSorted( suballocations1st.begin() + m_1stNullItemsBeginCount, suballocations1st.end(), refSuballoc, VmaSuballocationOffsetLess()); if (it != suballocations1st.end()) { it->type = VMA_SUBALLOCATION_TYPE_FREE; it->hAllocation = VK_NULL_HANDLE; ++m_1stNullItemsMiddleCount; m_SumFreeSize += it->size; CleanupAfterFree(); return; } } if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) { // Item from the middle of 2nd vector. VmaSuballocation refSuballoc; refSuballoc.offset = offset; // Rest of members stays uninitialized intentionally for better performance. SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetLess()) : VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetGreater()); if (it != suballocations2nd.end()) { it->type = VMA_SUBALLOCATION_TYPE_FREE; it->hAllocation = VK_NULL_HANDLE; ++m_2ndNullItemsCount; m_SumFreeSize += it->size; CleanupAfterFree(); return; } } VMA_ASSERT(0 && "Allocation to free not found in linear allocator!"); } bool VmaBlockMetadata_Linear::ShouldCompact1st() const { const size_t nullItemCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; const size_t suballocCount = AccessSuballocations1st().size(); return suballocCount > 32 && nullItemCount * 2 >= (suballocCount - nullItemCount) * 3; } void VmaBlockMetadata_Linear::CleanupAfterFree() { SuballocationVectorType& suballocations1st = AccessSuballocations1st(); SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); if (IsEmpty()) { suballocations1st.clear(); suballocations2nd.clear(); m_1stNullItemsBeginCount = 0; m_1stNullItemsMiddleCount = 0; m_2ndNullItemsCount = 0; m_2ndVectorMode = SECOND_VECTOR_EMPTY; } else { const size_t suballoc1stCount = suballocations1st.size(); const size_t nullItem1stCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; VMA_ASSERT(nullItem1stCount <= suballoc1stCount); // Find more null items at the beginning of 1st vector. while (m_1stNullItemsBeginCount < suballoc1stCount && suballocations1st[m_1stNullItemsBeginCount].hAllocation == VK_NULL_HANDLE) { ++m_1stNullItemsBeginCount; --m_1stNullItemsMiddleCount; } // Find more null items at the end of 1st vector. while (m_1stNullItemsMiddleCount > 0 && suballocations1st.back().hAllocation == VK_NULL_HANDLE) { --m_1stNullItemsMiddleCount; suballocations1st.pop_back(); } // Find more null items at the end of 2nd vector. while (m_2ndNullItemsCount > 0 && suballocations2nd.back().hAllocation == VK_NULL_HANDLE) { --m_2ndNullItemsCount; suballocations2nd.pop_back(); } // Find more null items at the beginning of 2nd vector. while (m_2ndNullItemsCount > 0 && suballocations2nd[0].hAllocation == VK_NULL_HANDLE) { --m_2ndNullItemsCount; VmaVectorRemove(suballocations2nd, 0); } if (ShouldCompact1st()) { const size_t nonNullItemCount = suballoc1stCount - nullItem1stCount; size_t srcIndex = m_1stNullItemsBeginCount; for (size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex) { while (suballocations1st[srcIndex].hAllocation == VK_NULL_HANDLE) { ++srcIndex; } if (dstIndex != srcIndex) { suballocations1st[dstIndex] = suballocations1st[srcIndex]; } ++srcIndex; } suballocations1st.resize(nonNullItemCount); m_1stNullItemsBeginCount = 0; m_1stNullItemsMiddleCount = 0; } // 2nd vector became empty. if (suballocations2nd.empty()) { m_2ndVectorMode = SECOND_VECTOR_EMPTY; } // 1st vector became empty. if (suballocations1st.size() - m_1stNullItemsBeginCount == 0) { suballocations1st.clear(); m_1stNullItemsBeginCount = 0; if (!suballocations2nd.empty() && m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) { // Swap 1st with 2nd. Now 2nd is empty. m_2ndVectorMode = SECOND_VECTOR_EMPTY; m_1stNullItemsMiddleCount = m_2ndNullItemsCount; while (m_1stNullItemsBeginCount < suballocations2nd.size() && suballocations2nd[m_1stNullItemsBeginCount].hAllocation == VK_NULL_HANDLE) { ++m_1stNullItemsBeginCount; --m_1stNullItemsMiddleCount; } m_2ndNullItemsCount = 0; m_1stVectorIndex ^= 1; } } } VMA_HEAVY_ASSERT(Validate()); } //////////////////////////////////////////////////////////////////////////////// // class VmaBlockMetadata_Buddy VmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy(VmaAllocator hAllocator) : VmaBlockMetadata(hAllocator), m_Root(VMA_NULL), m_AllocationCount(0), m_FreeCount(1), m_SumFreeSize(0) { memset(m_FreeList, 0, sizeof(m_FreeList)); } VmaBlockMetadata_Buddy::~VmaBlockMetadata_Buddy() { DeleteNode(m_Root); } void VmaBlockMetadata_Buddy::Init(VkDeviceSize size) { VmaBlockMetadata::Init(size); m_UsableSize = VmaPrevPow2(size); m_SumFreeSize = m_UsableSize; // Calculate m_LevelCount. m_LevelCount = 1; while (m_LevelCount < MAX_LEVELS && LevelToNodeSize(m_LevelCount) >= MIN_NODE_SIZE) { ++m_LevelCount; } Node* rootNode = vma_new(GetAllocationCallbacks(), Node)(); rootNode->offset = 0; rootNode->type = Node::TYPE_FREE; rootNode->parent = VMA_NULL; rootNode->buddy = VMA_NULL; m_Root = rootNode; AddToFreeListFront(0, rootNode); } bool VmaBlockMetadata_Buddy::Validate() const { // Validate tree. ValidationContext ctx; if (!ValidateNode(ctx, VMA_NULL, m_Root, 0, LevelToNodeSize(0))) { VMA_VALIDATE(false && "ValidateNode failed."); } VMA_VALIDATE(m_AllocationCount == ctx.calculatedAllocationCount); VMA_VALIDATE(m_SumFreeSize == ctx.calculatedSumFreeSize); // Validate free node lists. for (uint32_t level = 0; level < m_LevelCount; ++level) { VMA_VALIDATE(m_FreeList[level].front == VMA_NULL || m_FreeList[level].front->free.prev == VMA_NULL); for (Node* node = m_FreeList[level].front; node != VMA_NULL; node = node->free.next) { VMA_VALIDATE(node->type == Node::TYPE_FREE); if (node->free.next == VMA_NULL) { VMA_VALIDATE(m_FreeList[level].back == node); } else { VMA_VALIDATE(node->free.next->free.prev == node); } } } // Validate that free lists ar higher levels are empty. for (uint32_t level = m_LevelCount; level < MAX_LEVELS; ++level) { VMA_VALIDATE(m_FreeList[level].front == VMA_NULL && m_FreeList[level].back == VMA_NULL); } return true; } VkDeviceSize VmaBlockMetadata_Buddy::GetUnusedRangeSizeMax() const { for (uint32_t level = 0; level < m_LevelCount; ++level) { if (m_FreeList[level].front != VMA_NULL) { return LevelToNodeSize(level); } } return 0; } void VmaBlockMetadata_Buddy::CalcAllocationStatInfo(VmaStatInfo& outInfo) const { const VkDeviceSize unusableSize = GetUnusableSize(); outInfo.blockCount = 1; outInfo.allocationCount = outInfo.unusedRangeCount = 0; outInfo.usedBytes = outInfo.unusedBytes = 0; outInfo.allocationSizeMax = outInfo.unusedRangeSizeMax = 0; outInfo.allocationSizeMin = outInfo.unusedRangeSizeMin = UINT64_MAX; outInfo.allocationSizeAvg = outInfo.unusedRangeSizeAvg = 0; // Unused. CalcAllocationStatInfoNode(outInfo, m_Root, LevelToNodeSize(0)); if (unusableSize > 0) { ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusableSize; outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, unusableSize); outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusableSize); } } void VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const { const VkDeviceSize unusableSize = GetUnusableSize(); inoutStats.size += GetSize(); inoutStats.unusedSize += m_SumFreeSize + unusableSize; inoutStats.allocationCount += m_AllocationCount; inoutStats.unusedRangeCount += m_FreeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, GetUnusedRangeSizeMax()); if (unusableSize > 0) { ++inoutStats.unusedRangeCount; // Not updating inoutStats.unusedRangeSizeMax with unusableSize because this space is not available for allocations. } } #if VMA_STATS_STRING_ENABLED void VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const { // TODO optimize VmaStatInfo stat; CalcAllocationStatInfo(stat); PrintDetailedMap_Begin( json, stat.unusedBytes, stat.allocationCount, stat.unusedRangeCount); PrintDetailedMapNode(json, m_Root, LevelToNodeSize(0)); const VkDeviceSize unusableSize = GetUnusableSize(); if (unusableSize > 0) { PrintDetailedMap_UnusedRange(json, m_UsableSize, // offset unusableSize); // size } PrintDetailedMap_End(json); } #endif // #if VMA_STATS_STRING_ENABLED bool VmaBlockMetadata_Buddy::CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, VkDeviceSize bufferImageGranularity, VkDeviceSize allocSize, VkDeviceSize allocAlignment, bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { VMA_ASSERT(!upperAddress && "VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT can be used only with linear algorithm."); // Simple way to respect bufferImageGranularity. May be optimized some day. // Whenever it might be an OPTIMAL image... if (allocType == VMA_SUBALLOCATION_TYPE_UNKNOWN || allocType == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN || allocType == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL) { allocAlignment = VMA_MAX(allocAlignment, bufferImageGranularity); allocSize = VMA_MAX(allocSize, bufferImageGranularity); } if (allocSize > m_UsableSize) { return false; } const uint32_t targetLevel = AllocSizeToLevel(allocSize); for (uint32_t level = targetLevel + 1; level--; ) { for (Node* freeNode = m_FreeList[level].front; freeNode != VMA_NULL; freeNode = freeNode->free.next) { if (freeNode->offset % allocAlignment == 0) { pAllocationRequest->type = VmaAllocationRequestType::Normal; pAllocationRequest->offset = freeNode->offset; pAllocationRequest->sumFreeSize = LevelToNodeSize(level); pAllocationRequest->sumItemSize = 0; pAllocationRequest->itemsToMakeLostCount = 0; pAllocationRequest->customData = (void*)(uintptr_t)level; return true; } } } return false; } bool VmaBlockMetadata_Buddy::MakeRequestedAllocationsLost( uint32_t currentFrameIndex, uint32_t frameInUseCount, VmaAllocationRequest* pAllocationRequest) { /* Lost allocations are not supported in buddy allocator at the moment. Support might be added in the future. */ return pAllocationRequest->itemsToMakeLostCount == 0; } uint32_t VmaBlockMetadata_Buddy::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) { /* Lost allocations are not supported in buddy allocator at the moment. Support might be added in the future. */ return 0; } void VmaBlockMetadata_Buddy::Alloc( const VmaAllocationRequest& request, VmaSuballocationType type, VkDeviceSize allocSize, VmaAllocation hAllocation) { VMA_ASSERT(request.type == VmaAllocationRequestType::Normal); const uint32_t targetLevel = AllocSizeToLevel(allocSize); uint32_t currLevel = (uint32_t)(uintptr_t)request.customData; Node* currNode = m_FreeList[currLevel].front; VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); while (currNode->offset != request.offset) { currNode = currNode->free.next; VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); } // Go down, splitting free nodes. while (currLevel < targetLevel) { // currNode is already first free node at currLevel. // Remove it from list of free nodes at this currLevel. RemoveFromFreeList(currLevel, currNode); const uint32_t childrenLevel = currLevel + 1; // Create two free sub-nodes. Node* leftChild = vma_new(GetAllocationCallbacks(), Node)(); Node* rightChild = vma_new(GetAllocationCallbacks(), Node)(); leftChild->offset = currNode->offset; leftChild->type = Node::TYPE_FREE; leftChild->parent = currNode; leftChild->buddy = rightChild; rightChild->offset = currNode->offset + LevelToNodeSize(childrenLevel); rightChild->type = Node::TYPE_FREE; rightChild->parent = currNode; rightChild->buddy = leftChild; // Convert current currNode to split type. currNode->type = Node::TYPE_SPLIT; currNode->split.leftChild = leftChild; // Add child nodes to free list. Order is important! AddToFreeListFront(childrenLevel, rightChild); AddToFreeListFront(childrenLevel, leftChild); ++m_FreeCount; //m_SumFreeSize -= LevelToNodeSize(currLevel) % 2; // Useful only when level node sizes can be non power of 2. ++currLevel; currNode = m_FreeList[currLevel].front; /* We can be sure that currNode, as left child of node previously split, also fullfills the alignment requirement. */ } // Remove from free list. VMA_ASSERT(currLevel == targetLevel && currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); RemoveFromFreeList(currLevel, currNode); // Convert to allocation node. currNode->type = Node::TYPE_ALLOCATION; currNode->allocation.alloc = hAllocation; ++m_AllocationCount; --m_FreeCount; m_SumFreeSize -= allocSize; } void VmaBlockMetadata_Buddy::DeleteNode(Node* node) { if (node->type == Node::TYPE_SPLIT) { DeleteNode(node->split.leftChild->buddy); DeleteNode(node->split.leftChild); } vma_delete(GetAllocationCallbacks(), node); } bool VmaBlockMetadata_Buddy::ValidateNode(ValidationContext& ctx, const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const { VMA_VALIDATE(level < m_LevelCount); VMA_VALIDATE(curr->parent == parent); VMA_VALIDATE((curr->buddy == VMA_NULL) == (parent == VMA_NULL)); VMA_VALIDATE(curr->buddy == VMA_NULL || curr->buddy->buddy == curr); switch (curr->type) { case Node::TYPE_FREE: // curr->free.prev, next are validated separately. ctx.calculatedSumFreeSize += levelNodeSize; ++ctx.calculatedFreeCount; break; case Node::TYPE_ALLOCATION: ++ctx.calculatedAllocationCount; ctx.calculatedSumFreeSize += levelNodeSize - curr->allocation.alloc->GetSize(); VMA_VALIDATE(curr->allocation.alloc != VK_NULL_HANDLE); break; case Node::TYPE_SPLIT: { const uint32_t childrenLevel = level + 1; const VkDeviceSize childrenLevelNodeSize = levelNodeSize / 2; const Node* const leftChild = curr->split.leftChild; VMA_VALIDATE(leftChild != VMA_NULL); VMA_VALIDATE(leftChild->offset == curr->offset); if (!ValidateNode(ctx, curr, leftChild, childrenLevel, childrenLevelNodeSize)) { VMA_VALIDATE(false && "ValidateNode for left child failed."); } const Node* const rightChild = leftChild->buddy; VMA_VALIDATE(rightChild->offset == curr->offset + childrenLevelNodeSize); if (!ValidateNode(ctx, curr, rightChild, childrenLevel, childrenLevelNodeSize)) { VMA_VALIDATE(false && "ValidateNode for right child failed."); } } break; default: return false; } return true; } uint32_t VmaBlockMetadata_Buddy::AllocSizeToLevel(VkDeviceSize allocSize) const { // I know this could be optimized somehow e.g. by using std::log2p1 from C++20. uint32_t level = 0; VkDeviceSize currLevelNodeSize = m_UsableSize; VkDeviceSize nextLevelNodeSize = currLevelNodeSize >> 1; while (allocSize <= nextLevelNodeSize && level + 1 < m_LevelCount) { ++level; currLevelNodeSize = nextLevelNodeSize; nextLevelNodeSize = currLevelNodeSize >> 1; } return level; } void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset) { // Find node and level. Node* node = m_Root; VkDeviceSize nodeOffset = 0; uint32_t level = 0; VkDeviceSize levelNodeSize = LevelToNodeSize(0); while (node->type == Node::TYPE_SPLIT) { const VkDeviceSize nextLevelSize = levelNodeSize >> 1; if (offset < nodeOffset + nextLevelSize) { node = node->split.leftChild; } else { node = node->split.leftChild->buddy; nodeOffset += nextLevelSize; } ++level; levelNodeSize = nextLevelSize; } VMA_ASSERT(node != VMA_NULL && node->type == Node::TYPE_ALLOCATION); VMA_ASSERT(alloc == VK_NULL_HANDLE || node->allocation.alloc == alloc); ++m_FreeCount; --m_AllocationCount; m_SumFreeSize += alloc->GetSize(); node->type = Node::TYPE_FREE; // Join free nodes if possible. while (level > 0 && node->buddy->type == Node::TYPE_FREE) { RemoveFromFreeList(level, node->buddy); Node* const parent = node->parent; vma_delete(GetAllocationCallbacks(), node->buddy); vma_delete(GetAllocationCallbacks(), node); parent->type = Node::TYPE_FREE; node = parent; --level; //m_SumFreeSize += LevelToNodeSize(level) % 2; // Useful only when level node sizes can be non power of 2. --m_FreeCount; } AddToFreeListFront(level, node); } void VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const { switch (node->type) { case Node::TYPE_FREE: ++outInfo.unusedRangeCount; outInfo.unusedBytes += levelNodeSize; outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, levelNodeSize); outInfo.unusedRangeSizeMin = VMA_MAX(outInfo.unusedRangeSizeMin, levelNodeSize); break; case Node::TYPE_ALLOCATION: { const VkDeviceSize allocSize = node->allocation.alloc->GetSize(); ++outInfo.allocationCount; outInfo.usedBytes += allocSize; outInfo.allocationSizeMax = VMA_MAX(outInfo.allocationSizeMax, allocSize); outInfo.allocationSizeMin = VMA_MAX(outInfo.allocationSizeMin, allocSize); const VkDeviceSize unusedRangeSize = levelNodeSize - allocSize; if (unusedRangeSize > 0) { ++outInfo.unusedRangeCount; outInfo.unusedBytes += unusedRangeSize; outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, unusedRangeSize); outInfo.unusedRangeSizeMin = VMA_MAX(outInfo.unusedRangeSizeMin, unusedRangeSize); } } break; case Node::TYPE_SPLIT: { const VkDeviceSize childrenNodeSize = levelNodeSize / 2; const Node* const leftChild = node->split.leftChild; CalcAllocationStatInfoNode(outInfo, leftChild, childrenNodeSize); const Node* const rightChild = leftChild->buddy; CalcAllocationStatInfoNode(outInfo, rightChild, childrenNodeSize); } break; default: VMA_ASSERT(0); } } void VmaBlockMetadata_Buddy::AddToFreeListFront(uint32_t level, Node* node) { VMA_ASSERT(node->type == Node::TYPE_FREE); // List is empty. Node* const frontNode = m_FreeList[level].front; if (frontNode == VMA_NULL) { VMA_ASSERT(m_FreeList[level].back == VMA_NULL); node->free.prev = node->free.next = VMA_NULL; m_FreeList[level].front = m_FreeList[level].back = node; } else { VMA_ASSERT(frontNode->free.prev == VMA_NULL); node->free.prev = VMA_NULL; node->free.next = frontNode; frontNode->free.prev = node; m_FreeList[level].front = node; } } void VmaBlockMetadata_Buddy::RemoveFromFreeList(uint32_t level, Node* node) { VMA_ASSERT(m_FreeList[level].front != VMA_NULL); // It is at the front. if (node->free.prev == VMA_NULL) { VMA_ASSERT(m_FreeList[level].front == node); m_FreeList[level].front = node->free.next; } else { Node* const prevFreeNode = node->free.prev; VMA_ASSERT(prevFreeNode->free.next == node); prevFreeNode->free.next = node->free.next; } // It is at the back. if (node->free.next == VMA_NULL) { VMA_ASSERT(m_FreeList[level].back == node); m_FreeList[level].back = node->free.prev; } else { Node* const nextFreeNode = node->free.next; VMA_ASSERT(nextFreeNode->free.prev == node); nextFreeNode->free.prev = node->free.prev; } } #if VMA_STATS_STRING_ENABLED void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const { switch (node->type) { case Node::TYPE_FREE: PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize); break; case Node::TYPE_ALLOCATION: { PrintDetailedMap_Allocation(json, node->offset, node->allocation.alloc); const VkDeviceSize allocSize = node->allocation.alloc->GetSize(); if (allocSize < levelNodeSize) { PrintDetailedMap_UnusedRange(json, node->offset + allocSize, levelNodeSize - allocSize); } } break; case Node::TYPE_SPLIT: { const VkDeviceSize childrenNodeSize = levelNodeSize / 2; const Node* const leftChild = node->split.leftChild; PrintDetailedMapNode(json, leftChild, childrenNodeSize); const Node* const rightChild = leftChild->buddy; PrintDetailedMapNode(json, rightChild, childrenNodeSize); } break; default: VMA_ASSERT(0); } } #endif // #if VMA_STATS_STRING_ENABLED //////////////////////////////////////////////////////////////////////////////// // class VmaDeviceMemoryBlock VmaDeviceMemoryBlock::VmaDeviceMemoryBlock(VmaAllocator hAllocator) : m_pMetadata(VMA_NULL), m_MemoryTypeIndex(UINT32_MAX), m_Id(0), m_hMemory(VK_NULL_HANDLE), m_MapCount(0), m_pMappedData(VMA_NULL) { } void VmaDeviceMemoryBlock::Init( VmaAllocator hAllocator, VmaPool hParentPool, uint32_t newMemoryTypeIndex, VkDeviceMemory newMemory, VkDeviceSize newSize, uint32_t id, uint32_t algorithm) { VMA_ASSERT(m_hMemory == VK_NULL_HANDLE); m_hParentPool = hParentPool; m_MemoryTypeIndex = newMemoryTypeIndex; m_Id = id; m_hMemory = newMemory; switch (algorithm) { case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Linear)(hAllocator); break; case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT: m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Buddy)(hAllocator); break; default: VMA_ASSERT(0); // Fall-through. case 0: m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Generic)(hAllocator); } m_pMetadata->Init(newSize); } void VmaDeviceMemoryBlock::Destroy(VmaAllocator allocator) { // This is the most important assert in the entire library. // Hitting it means you have some memory leak - unreleased VmaAllocation objects. VMA_ASSERT(m_pMetadata->IsEmpty() && "Some allocations were not freed before destruction of this memory block!"); VMA_ASSERT(m_hMemory != VK_NULL_HANDLE); allocator->FreeVulkanMemory(m_MemoryTypeIndex, m_pMetadata->GetSize(), m_hMemory); m_hMemory = VK_NULL_HANDLE; vma_delete(allocator, m_pMetadata); m_pMetadata = VMA_NULL; } bool VmaDeviceMemoryBlock::Validate() const { VMA_VALIDATE((m_hMemory != VK_NULL_HANDLE) && (m_pMetadata->GetSize() != 0)); return m_pMetadata->Validate(); } VkResult VmaDeviceMemoryBlock::CheckCorruption(VmaAllocator hAllocator) { void* pData = nullptr; VkResult res = Map(hAllocator, 1, &pData); if (res != VK_SUCCESS) { return res; } res = m_pMetadata->CheckCorruption(pData); Unmap(hAllocator, 1); return res; } VkResult VmaDeviceMemoryBlock::Map(VmaAllocator hAllocator, uint32_t count, void** ppData) { if (count == 0) { return VK_SUCCESS; } VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); if (m_MapCount != 0) { m_MapCount += count; VMA_ASSERT(m_pMappedData != VMA_NULL); if (ppData != VMA_NULL) { *ppData = m_pMappedData; } return VK_SUCCESS; } else { VkResult result = (*hAllocator->GetVulkanFunctions().vkMapMemory)( hAllocator->m_hDevice, m_hMemory, 0, // offset VK_WHOLE_SIZE, 0, // flags &m_pMappedData); if (result == VK_SUCCESS) { if (ppData != VMA_NULL) { *ppData = m_pMappedData; } m_MapCount = count; } return result; } } void VmaDeviceMemoryBlock::Unmap(VmaAllocator hAllocator, uint32_t count) { if (count == 0) { return; } VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); if (m_MapCount >= count) { m_MapCount -= count; if (m_MapCount == 0) { m_pMappedData = VMA_NULL; (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory); } } else { VMA_ASSERT(0 && "VkDeviceMemory block is being unmapped while it was not previously mapped."); } } VkResult VmaDeviceMemoryBlock::WriteMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize) { VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION); VMA_ASSERT(allocOffset >= VMA_DEBUG_MARGIN); void* pData; VkResult res = Map(hAllocator, 1, &pData); if (res != VK_SUCCESS) { return res; } VmaWriteMagicValue(pData, allocOffset - VMA_DEBUG_MARGIN); VmaWriteMagicValue(pData, allocOffset + allocSize); Unmap(hAllocator, 1); return VK_SUCCESS; } VkResult VmaDeviceMemoryBlock::ValidateMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize) { VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION); VMA_ASSERT(allocOffset >= VMA_DEBUG_MARGIN); void* pData; VkResult res = Map(hAllocator, 1, &pData); if (res != VK_SUCCESS) { return res; } if (!VmaValidateMagicValue(pData, allocOffset - VMA_DEBUG_MARGIN)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED BEFORE FREED ALLOCATION!"); } else if (!VmaValidateMagicValue(pData, allocOffset + allocSize)) { VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER FREED ALLOCATION!"); } Unmap(hAllocator, 1); return VK_SUCCESS; } VkResult VmaDeviceMemoryBlock::BindBufferMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkBuffer hBuffer, const void* pNext) { VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && hAllocation->GetBlock() == this); VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); return hAllocator->BindVulkanBuffer(m_hMemory, memoryOffset, hBuffer, pNext); } VkResult VmaDeviceMemoryBlock::BindImageMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkImage hImage, const void* pNext) { VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && hAllocation->GetBlock() == this); VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext); } static void InitStatInfo(VmaStatInfo& outInfo) { memset(&outInfo, 0, sizeof(outInfo)); outInfo.allocationSizeMin = UINT64_MAX; outInfo.unusedRangeSizeMin = UINT64_MAX; } // Adds statistics srcInfo into inoutInfo, like: inoutInfo += srcInfo. static void VmaAddStatInfo(VmaStatInfo& inoutInfo, const VmaStatInfo& srcInfo) { inoutInfo.blockCount += srcInfo.blockCount; inoutInfo.allocationCount += srcInfo.allocationCount; inoutInfo.unusedRangeCount += srcInfo.unusedRangeCount; inoutInfo.usedBytes += srcInfo.usedBytes; inoutInfo.unusedBytes += srcInfo.unusedBytes; inoutInfo.allocationSizeMin = VMA_MIN(inoutInfo.allocationSizeMin, srcInfo.allocationSizeMin); inoutInfo.allocationSizeMax = VMA_MAX(inoutInfo.allocationSizeMax, srcInfo.allocationSizeMax); inoutInfo.unusedRangeSizeMin = VMA_MIN(inoutInfo.unusedRangeSizeMin, srcInfo.unusedRangeSizeMin); inoutInfo.unusedRangeSizeMax = VMA_MAX(inoutInfo.unusedRangeSizeMax, srcInfo.unusedRangeSizeMax); } static void VmaPostprocessCalcStatInfo(VmaStatInfo& inoutInfo) { inoutInfo.allocationSizeAvg = (inoutInfo.allocationCount > 0) ? VmaRoundDiv<VkDeviceSize>(inoutInfo.usedBytes, inoutInfo.allocationCount) : 0; inoutInfo.unusedRangeSizeAvg = (inoutInfo.unusedRangeCount > 0) ? VmaRoundDiv<VkDeviceSize>(inoutInfo.unusedBytes, inoutInfo.unusedRangeCount) : 0; } VmaPool_T::VmaPool_T( VmaAllocator hAllocator, const VmaPoolCreateInfo& createInfo, VkDeviceSize preferredBlockSize) : m_BlockVector( hAllocator, this, // hParentPool createInfo.memoryTypeIndex, createInfo.blockSize != 0 ? createInfo.blockSize : preferredBlockSize, createInfo.minBlockCount, createInfo.maxBlockCount, (createInfo.flags& VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT) != 0 ? 1 : hAllocator->GetBufferImageGranularity(), createInfo.frameInUseCount, createInfo.blockSize != 0, // explicitBlockSize createInfo.flags& VMA_POOL_CREATE_ALGORITHM_MASK), // algorithm m_Id(0), m_Name(VMA_NULL) { } VmaPool_T::~VmaPool_T() { } void VmaPool_T::SetName(const char* pName) { const VkAllocationCallbacks* allocs = m_BlockVector.GetAllocator()->GetAllocationCallbacks(); VmaFreeString(allocs, m_Name); if (pName != VMA_NULL) { m_Name = VmaCreateStringCopy(allocs, pName); } else { m_Name = VMA_NULL; } } #if VMA_STATS_STRING_ENABLED #endif // #if VMA_STATS_STRING_ENABLED VmaBlockVector::VmaBlockVector( VmaAllocator hAllocator, VmaPool hParentPool, uint32_t memoryTypeIndex, VkDeviceSize preferredBlockSize, size_t minBlockCount, size_t maxBlockCount, VkDeviceSize bufferImageGranularity, uint32_t frameInUseCount, bool explicitBlockSize, uint32_t algorithm) : m_hAllocator(hAllocator), m_hParentPool(hParentPool), m_MemoryTypeIndex(memoryTypeIndex), m_PreferredBlockSize(preferredBlockSize), m_MinBlockCount(minBlockCount), m_MaxBlockCount(maxBlockCount), m_BufferImageGranularity(bufferImageGranularity), m_FrameInUseCount(frameInUseCount), m_ExplicitBlockSize(explicitBlockSize), m_Algorithm(algorithm), m_HasEmptyBlock(false), m_Blocks(VmaStlAllocator<VmaDeviceMemoryBlock*>(hAllocator->GetAllocationCallbacks())), m_NextBlockId(0) { } VmaBlockVector::~VmaBlockVector() { for (size_t i = m_Blocks.size(); i--; ) { m_Blocks[i]->Destroy(m_hAllocator); vma_delete(m_hAllocator, m_Blocks[i]); } } VkResult VmaBlockVector::CreateMinBlocks() { for (size_t i = 0; i < m_MinBlockCount; ++i) { VkResult res = CreateBlock(m_PreferredBlockSize, VMA_NULL); if (res != VK_SUCCESS) { return res; } } return VK_SUCCESS; } void VmaBlockVector::GetPoolStats(VmaPoolStats* pStats) { VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); const size_t blockCount = m_Blocks.size(); pStats->size = 0; pStats->unusedSize = 0; pStats->allocationCount = 0; pStats->unusedRangeCount = 0; pStats->unusedRangeSizeMax = 0; pStats->blockCount = blockCount; for (uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) { const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; VMA_ASSERT(pBlock); VMA_HEAVY_ASSERT(pBlock->Validate()); pBlock->m_pMetadata->AddPoolStats(*pStats); } } bool VmaBlockVector::IsEmpty() { VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); return m_Blocks.empty(); } bool VmaBlockVector::IsCorruptionDetectionEnabled() const { const uint32_t requiredMemFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; return (VMA_DEBUG_DETECT_CORRUPTION != 0) && (VMA_DEBUG_MARGIN > 0) && (m_Algorithm == 0 || m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) && (m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags & requiredMemFlags) == requiredMemFlags; } static const uint32_t VMA_ALLOCATION_TRY_COUNT = 32; VkResult VmaBlockVector::Allocate( uint32_t currentFrameIndex, VkDeviceSize size, VkDeviceSize alignment, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, size_t allocationCount, VmaAllocation* pAllocations) { size_t allocIndex; VkResult res = VK_SUCCESS; if (IsCorruptionDetectionEnabled()) { size = VmaAlignUp<VkDeviceSize>(size, sizeof(VMA_CORRUPTION_DETECTION_MAGIC_VALUE)); alignment = VmaAlignUp<VkDeviceSize>(alignment, sizeof(VMA_CORRUPTION_DETECTION_MAGIC_VALUE)); } { VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) { res = AllocatePage( currentFrameIndex, size, alignment, createInfo, suballocType, pAllocations + allocIndex); if (res != VK_SUCCESS) { break; } } } if (res != VK_SUCCESS) { // Free all already created allocations. while (allocIndex--) { Free(pAllocations[allocIndex]); } memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); } return res; } VkResult VmaBlockVector::AllocatePage( uint32_t currentFrameIndex, VkDeviceSize size, VkDeviceSize alignment, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, VmaAllocation* pAllocation) { const bool isUpperAddress = (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0; bool canMakeOtherLost = (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) != 0; const bool mapped = (createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; const bool isUserDataString = (createInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0; const bool withinBudget = (createInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0; VkDeviceSize freeMemory; { const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); VmaBudget heapBudget = {}; m_hAllocator->GetBudget(&heapBudget, heapIndex, 1); freeMemory = (heapBudget.usage < heapBudget.budget) ? (heapBudget.budget - heapBudget.usage) : 0; } const bool canFallbackToDedicated = !IsCustomPool(); const bool canCreateNewBlock = ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) && (m_Blocks.size() < m_MaxBlockCount) && (freeMemory >= size || !canFallbackToDedicated); uint32_t strategy = createInfo.flags & VMA_ALLOCATION_CREATE_STRATEGY_MASK; // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer. // Which in turn is available only when maxBlockCount = 1. if (m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT && m_MaxBlockCount > 1) { canMakeOtherLost = false; } // Upper address can only be used with linear allocator and within single memory block. if (isUpperAddress && (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT || m_MaxBlockCount > 1)) { return VK_ERROR_FEATURE_NOT_PRESENT; } // Validate strategy. switch (strategy) { case 0: strategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; break; case VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT: case VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT: case VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT: break; default: return VK_ERROR_FEATURE_NOT_PRESENT; } // Early reject: requested allocation size is larger that maximum block size for this block vector. if (size + 2 * VMA_DEBUG_MARGIN > m_PreferredBlockSize) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } /* Under certain condition, this whole section can be skipped for optimization, so we move on directly to trying to allocate with canMakeOtherLost. That's the case e.g. for custom pools with linear algorithm. */ if (!canMakeOtherLost || canCreateNewBlock) { // 1. Search existing allocations. Try to allocate without making other allocations lost. VmaAllocationCreateFlags allocFlagsCopy = createInfo.flags; allocFlagsCopy &= ~VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; if (m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) { // Use only last block. if (!m_Blocks.empty()) { VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks.back(); VMA_ASSERT(pCurrBlock); VkResult res = AllocateFromBlock( pCurrBlock, currentFrameIndex, size, alignment, allocFlagsCopy, createInfo.pUserData, suballocType, strategy, pAllocation); if (res == VK_SUCCESS) { VMA_DEBUG_LOG(" Returned from last block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } } else { if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) { // Forward order in m_Blocks - prefer blocks with smallest amount of free space. for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) { VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; VMA_ASSERT(pCurrBlock); VkResult res = AllocateFromBlock( pCurrBlock, currentFrameIndex, size, alignment, allocFlagsCopy, createInfo.pUserData, suballocType, strategy, pAllocation); if (res == VK_SUCCESS) { VMA_DEBUG_LOG(" Returned from existing block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } } else // WORST_FIT, FIRST_FIT { // Backward order in m_Blocks - prefer blocks with largest amount of free space. for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) { VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; VMA_ASSERT(pCurrBlock); VkResult res = AllocateFromBlock( pCurrBlock, currentFrameIndex, size, alignment, allocFlagsCopy, createInfo.pUserData, suballocType, strategy, pAllocation); if (res == VK_SUCCESS) { VMA_DEBUG_LOG(" Returned from existing block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } } } // 2. Try to create new block. if (canCreateNewBlock) { // Calculate optimal size for new block. VkDeviceSize newBlockSize = m_PreferredBlockSize; uint32_t newBlockSizeShift = 0; const uint32_t NEW_BLOCK_SIZE_SHIFT_MAX = 3; if (!m_ExplicitBlockSize) { // Allocate 1/8, 1/4, 1/2 as first blocks. const VkDeviceSize maxExistingBlockSize = CalcMaxBlockSize(); for (uint32_t i = 0; i < NEW_BLOCK_SIZE_SHIFT_MAX; ++i) { const VkDeviceSize smallerNewBlockSize = newBlockSize / 2; if (smallerNewBlockSize > maxExistingBlockSize&& smallerNewBlockSize >= size * 2) { newBlockSize = smallerNewBlockSize; ++newBlockSizeShift; } else { break; } } } size_t newBlockIndex = 0; VkResult res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. if (!m_ExplicitBlockSize) { while (res < 0 && newBlockSizeShift < NEW_BLOCK_SIZE_SHIFT_MAX) { const VkDeviceSize smallerNewBlockSize = newBlockSize / 2; if (smallerNewBlockSize >= size) { newBlockSize = smallerNewBlockSize; ++newBlockSizeShift; res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; } else { break; } } } if (res == VK_SUCCESS) { VmaDeviceMemoryBlock* const pBlock = m_Blocks[newBlockIndex]; VMA_ASSERT(pBlock->m_pMetadata->GetSize() >= size); res = AllocateFromBlock( pBlock, currentFrameIndex, size, alignment, allocFlagsCopy, createInfo.pUserData, suballocType, strategy, pAllocation); if (res == VK_SUCCESS) { VMA_DEBUG_LOG(" Created new block #%u Size=%llu", pBlock->GetId(), newBlockSize); return VK_SUCCESS; } else { // Allocation from new block failed, possibly due to VMA_DEBUG_MARGIN or alignment. return VK_ERROR_OUT_OF_DEVICE_MEMORY; } } } } // 3. Try to allocate from existing blocks with making other allocations lost. if (canMakeOtherLost) { uint32_t tryIndex = 0; for (; tryIndex < VMA_ALLOCATION_TRY_COUNT; ++tryIndex) { VmaDeviceMemoryBlock* pBestRequestBlock = VMA_NULL; VmaAllocationRequest bestRequest = {}; VkDeviceSize bestRequestCost = VK_WHOLE_SIZE; // 1. Search existing allocations. if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) { // Forward order in m_Blocks - prefer blocks with smallest amount of free space. for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) { VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; VMA_ASSERT(pCurrBlock); VmaAllocationRequest currRequest = {}; if (pCurrBlock->m_pMetadata->CreateAllocationRequest( currentFrameIndex, m_FrameInUseCount, m_BufferImageGranularity, size, alignment, (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, suballocType, canMakeOtherLost, strategy, &currRequest)) { const VkDeviceSize currRequestCost = currRequest.CalcCost(); if (pBestRequestBlock == VMA_NULL || currRequestCost < bestRequestCost) { pBestRequestBlock = pCurrBlock; bestRequest = currRequest; bestRequestCost = currRequestCost; if (bestRequestCost == 0) { break; } } } } } else // WORST_FIT, FIRST_FIT { // Backward order in m_Blocks - prefer blocks with largest amount of free space. for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) { VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; VMA_ASSERT(pCurrBlock); VmaAllocationRequest currRequest = {}; if (pCurrBlock->m_pMetadata->CreateAllocationRequest( currentFrameIndex, m_FrameInUseCount, m_BufferImageGranularity, size, alignment, (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, suballocType, canMakeOtherLost, strategy, &currRequest)) { const VkDeviceSize currRequestCost = currRequest.CalcCost(); if (pBestRequestBlock == VMA_NULL || currRequestCost < bestRequestCost || strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) { pBestRequestBlock = pCurrBlock; bestRequest = currRequest; bestRequestCost = currRequestCost; if (bestRequestCost == 0 || strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) { break; } } } } } if (pBestRequestBlock != VMA_NULL) { if (mapped) { VkResult res = pBestRequestBlock->Map(m_hAllocator, 1, VMA_NULL); if (res != VK_SUCCESS) { return res; } } if (pBestRequestBlock->m_pMetadata->MakeRequestedAllocationsLost( currentFrameIndex, m_FrameInUseCount, &bestRequest)) { // Allocate from this pBlock. *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation); UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBestRequestBlock, bestRequest.offset, alignment, size, m_MemoryTypeIndex, suballocType, mapped, (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0); VMA_HEAVY_ASSERT(pBestRequestBlock->Validate()); VMA_DEBUG_LOG(" Returned from existing block"); (*pAllocation)->SetUserData(m_hAllocator, createInfo.pUserData); m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size); if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) { m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); } if (IsCorruptionDetectionEnabled()) { VkResult res = pBestRequestBlock->WriteMagicValueAroundAllocation(m_hAllocator, bestRequest.offset, size); VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value."); } return VK_SUCCESS; } // else: Some allocations must have been touched while we are here. Next try. } else { // Could not find place in any of the blocks - break outer loop. break; } } /* Maximum number of tries exceeded - a very unlike event when many other threads are simultaneously touching allocations making it impossible to make lost at the same time as we try to allocate. */ if (tryIndex == VMA_ALLOCATION_TRY_COUNT) { return VK_ERROR_TOO_MANY_OBJECTS; } } return VK_ERROR_OUT_OF_DEVICE_MEMORY; } void VmaBlockVector::Free( const VmaAllocation hAllocation) { VmaDeviceMemoryBlock* pBlockToDelete = VMA_NULL; bool budgetExceeded = false; { const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); VmaBudget heapBudget = {}; m_hAllocator->GetBudget(&heapBudget, heapIndex, 1); budgetExceeded = heapBudget.usage >= heapBudget.budget; } // Scope for lock. { VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); if (IsCorruptionDetectionEnabled()) { VkResult res = pBlock->ValidateMagicValueAroundAllocation(m_hAllocator, hAllocation->GetOffset(), hAllocation->GetSize()); VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to validate magic value."); } if (hAllocation->IsPersistentMap()) { pBlock->Unmap(m_hAllocator, 1); } pBlock->m_pMetadata->Free(hAllocation); VMA_HEAVY_ASSERT(pBlock->Validate()); VMA_DEBUG_LOG(" Freed from MemoryTypeIndex=%u", m_MemoryTypeIndex); const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount; // pBlock became empty after this deallocation. if (pBlock->m_pMetadata->IsEmpty()) { // Already has empty block. We don't want to have two, so delete this one. if ((m_HasEmptyBlock || budgetExceeded) && canDeleteBlock) { pBlockToDelete = pBlock; Remove(pBlock); } // else: We now have an empty block - leave it. } // pBlock didn't become empty, but we have another empty block - find and free that one. // (This is optional, heuristics.) else if (m_HasEmptyBlock && canDeleteBlock) { VmaDeviceMemoryBlock* pLastBlock = m_Blocks.back(); if (pLastBlock->m_pMetadata->IsEmpty()) { pBlockToDelete = pLastBlock; m_Blocks.pop_back(); } } UpdateHasEmptyBlock(); IncrementallySortBlocks(); } // Destruction of a free block. Deferred until this point, outside of mutex // lock, for performance reason. if (pBlockToDelete != VMA_NULL) { VMA_DEBUG_LOG(" Deleted empty block"); pBlockToDelete->Destroy(m_hAllocator); vma_delete(m_hAllocator, pBlockToDelete); } } VkDeviceSize VmaBlockVector::CalcMaxBlockSize() const { VkDeviceSize result = 0; for (size_t i = m_Blocks.size(); i--; ) { result = VMA_MAX(result, m_Blocks[i]->m_pMetadata->GetSize()); if (result >= m_PreferredBlockSize) { break; } } return result; } void VmaBlockVector::Remove(VmaDeviceMemoryBlock* pBlock) { for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) { if (m_Blocks[blockIndex] == pBlock) { VmaVectorRemove(m_Blocks, blockIndex); return; } } VMA_ASSERT(0); } void VmaBlockVector::IncrementallySortBlocks() { if (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) { // Bubble sort only until first swap. for (size_t i = 1; i < m_Blocks.size(); ++i) { if (m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize()) { VMA_SWAP(m_Blocks[i - 1], m_Blocks[i]); return; } } } } VkResult VmaBlockVector::AllocateFromBlock( VmaDeviceMemoryBlock* pBlock, uint32_t currentFrameIndex, VkDeviceSize size, VkDeviceSize alignment, VmaAllocationCreateFlags allocFlags, void* pUserData, VmaSuballocationType suballocType, uint32_t strategy, VmaAllocation* pAllocation) { VMA_ASSERT((allocFlags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) == 0); const bool isUpperAddress = (allocFlags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0; const bool mapped = (allocFlags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; const bool isUserDataString = (allocFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0; VmaAllocationRequest currRequest = {}; if (pBlock->m_pMetadata->CreateAllocationRequest( currentFrameIndex, m_FrameInUseCount, m_BufferImageGranularity, size, alignment, isUpperAddress, suballocType, false, // canMakeOtherLost strategy, &currRequest)) { // Allocate from pCurrBlock. VMA_ASSERT(currRequest.itemsToMakeLostCount == 0); if (mapped) { VkResult res = pBlock->Map(m_hAllocator, 1, VMA_NULL); if (res != VK_SUCCESS) { return res; } } *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation); UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBlock, currRequest.offset, alignment, size, m_MemoryTypeIndex, suballocType, mapped, (allocFlags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0); VMA_HEAVY_ASSERT(pBlock->Validate()); (*pAllocation)->SetUserData(m_hAllocator, pUserData); m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size); if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) { m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); } if (IsCorruptionDetectionEnabled()) { VkResult res = pBlock->WriteMagicValueAroundAllocation(m_hAllocator, currRequest.offset, size); VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value."); } return VK_SUCCESS; } return VK_ERROR_OUT_OF_DEVICE_MEMORY; } VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex) { VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = m_MemoryTypeIndex; allocInfo.allocationSize = blockSize; VkDeviceMemory mem = VK_NULL_HANDLE; VkResult res = m_hAllocator->AllocateVulkanMemory(&allocInfo, &mem); if (res < 0) { return res; } // New VkDeviceMemory successfully created. // Create new Allocation for it. VmaDeviceMemoryBlock* const pBlock = vma_new(m_hAllocator, VmaDeviceMemoryBlock)(m_hAllocator); pBlock->Init( m_hAllocator, m_hParentPool, m_MemoryTypeIndex, mem, allocInfo.allocationSize, m_NextBlockId++, m_Algorithm); m_Blocks.push_back(pBlock); if (pNewBlockIndex != VMA_NULL) { *pNewBlockIndex = m_Blocks.size() - 1; } return VK_SUCCESS; } void VmaBlockVector::ApplyDefragmentationMovesCpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves) { const size_t blockCount = m_Blocks.size(); const bool isNonCoherent = m_hAllocator->IsMemoryTypeNonCoherent(m_MemoryTypeIndex); enum BLOCK_FLAG { BLOCK_FLAG_USED = 0x00000001, BLOCK_FLAG_MAPPED_FOR_DEFRAGMENTATION = 0x00000002, }; struct BlockInfo { uint32_t flags; void* pMappedData; }; VmaVector< BlockInfo, VmaStlAllocator<BlockInfo> > blockInfo(blockCount, BlockInfo(), VmaStlAllocator<BlockInfo>(m_hAllocator->GetAllocationCallbacks())); memset(blockInfo.data(), 0, blockCount * sizeof(BlockInfo)); // Go over all moves. Mark blocks that are used with BLOCK_FLAG_USED. const size_t moveCount = moves.size(); for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; blockInfo[move.srcBlockIndex].flags |= BLOCK_FLAG_USED; blockInfo[move.dstBlockIndex].flags |= BLOCK_FLAG_USED; } VMA_ASSERT(pDefragCtx->res == VK_SUCCESS); // Go over all blocks. Get mapped pointer or map if necessary. for (size_t blockIndex = 0; pDefragCtx->res == VK_SUCCESS && blockIndex < blockCount; ++blockIndex) { BlockInfo& currBlockInfo = blockInfo[blockIndex]; VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; if ((currBlockInfo.flags & BLOCK_FLAG_USED) != 0) { currBlockInfo.pMappedData = pBlock->GetMappedData(); // It is not originally mapped - map it. if (currBlockInfo.pMappedData == VMA_NULL) { pDefragCtx->res = pBlock->Map(m_hAllocator, 1, &currBlockInfo.pMappedData); if (pDefragCtx->res == VK_SUCCESS) { currBlockInfo.flags |= BLOCK_FLAG_MAPPED_FOR_DEFRAGMENTATION; } } } } // Go over all moves. Do actual data transfer. if (pDefragCtx->res == VK_SUCCESS) { const VkDeviceSize nonCoherentAtomSize = m_hAllocator->m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; const BlockInfo& srcBlockInfo = blockInfo[move.srcBlockIndex]; const BlockInfo& dstBlockInfo = blockInfo[move.dstBlockIndex]; VMA_ASSERT(srcBlockInfo.pMappedData && dstBlockInfo.pMappedData); // Invalidate source. if (isNonCoherent) { VmaDeviceMemoryBlock* const pSrcBlock = m_Blocks[move.srcBlockIndex]; memRange.memory = pSrcBlock->GetDeviceMemory(); memRange.offset = VmaAlignDown(move.srcOffset, nonCoherentAtomSize); memRange.size = VMA_MIN( VmaAlignUp(move.size + (move.srcOffset - memRange.offset), nonCoherentAtomSize), pSrcBlock->m_pMetadata->GetSize() - memRange.offset); (*m_hAllocator->GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hAllocator->m_hDevice, 1, &memRange); } // THE PLACE WHERE ACTUAL DATA COPY HAPPENS. memmove( reinterpret_cast<char*>(dstBlockInfo.pMappedData) + move.dstOffset, reinterpret_cast<char*>(srcBlockInfo.pMappedData) + move.srcOffset, static_cast<size_t>(move.size)); if (IsCorruptionDetectionEnabled()) { VmaWriteMagicValue(dstBlockInfo.pMappedData, move.dstOffset - VMA_DEBUG_MARGIN); VmaWriteMagicValue(dstBlockInfo.pMappedData, move.dstOffset + move.size); } // Flush destination. if (isNonCoherent) { VmaDeviceMemoryBlock* const pDstBlock = m_Blocks[move.dstBlockIndex]; memRange.memory = pDstBlock->GetDeviceMemory(); memRange.offset = VmaAlignDown(move.dstOffset, nonCoherentAtomSize); memRange.size = VMA_MIN( VmaAlignUp(move.size + (move.dstOffset - memRange.offset), nonCoherentAtomSize), pDstBlock->m_pMetadata->GetSize() - memRange.offset); (*m_hAllocator->GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hAllocator->m_hDevice, 1, &memRange); } } } // Go over all blocks in reverse order. Unmap those that were mapped just for defragmentation. // Regardless of pCtx->res == VK_SUCCESS. for (size_t blockIndex = blockCount; blockIndex--; ) { const BlockInfo& currBlockInfo = blockInfo[blockIndex]; if ((currBlockInfo.flags & BLOCK_FLAG_MAPPED_FOR_DEFRAGMENTATION) != 0) { VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; pBlock->Unmap(m_hAllocator, 1); } } } void VmaBlockVector::ApplyDefragmentationMovesGpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkCommandBuffer commandBuffer) { const size_t blockCount = m_Blocks.size(); pDefragCtx->blockContexts.resize(blockCount); memset(pDefragCtx->blockContexts.data(), 0, blockCount * sizeof(VmaBlockDefragmentationContext)); // Go over all moves. Mark blocks that are used with BLOCK_FLAG_USED. const size_t moveCount = moves.size(); for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; pDefragCtx->blockContexts[move.srcBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; pDefragCtx->blockContexts[move.dstBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; } VMA_ASSERT(pDefragCtx->res == VK_SUCCESS); // Go over all blocks. Create and bind buffer for whole block if necessary. { VkBufferCreateInfo bufCreateInfo; VmaFillGpuDefragmentationBufferCreateInfo(bufCreateInfo); for (size_t blockIndex = 0; pDefragCtx->res == VK_SUCCESS && blockIndex < blockCount; ++blockIndex) { VmaBlockDefragmentationContext& currBlockCtx = pDefragCtx->blockContexts[blockIndex]; VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; if ((currBlockCtx.flags & VmaBlockDefragmentationContext::BLOCK_FLAG_USED) != 0) { bufCreateInfo.size = pBlock->m_pMetadata->GetSize(); pDefragCtx->res = (*m_hAllocator->GetVulkanFunctions().vkCreateBuffer)( m_hAllocator->m_hDevice, &bufCreateInfo, m_hAllocator->GetAllocationCallbacks(), &currBlockCtx.hBuffer); if (pDefragCtx->res == VK_SUCCESS) { pDefragCtx->res = (*m_hAllocator->GetVulkanFunctions().vkBindBufferMemory)( m_hAllocator->m_hDevice, currBlockCtx.hBuffer, pBlock->GetDeviceMemory(), 0); } } } } // Go over all moves. Post data transfer commands to command buffer. if (pDefragCtx->res == VK_SUCCESS) { for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; const VmaBlockDefragmentationContext& srcBlockCtx = pDefragCtx->blockContexts[move.srcBlockIndex]; const VmaBlockDefragmentationContext& dstBlockCtx = pDefragCtx->blockContexts[move.dstBlockIndex]; VMA_ASSERT(srcBlockCtx.hBuffer && dstBlockCtx.hBuffer); VkBufferCopy region = { move.srcOffset, move.dstOffset, move.size }; (*m_hAllocator->GetVulkanFunctions().vkCmdCopyBuffer)( commandBuffer, srcBlockCtx.hBuffer, dstBlockCtx.hBuffer, 1, ®ion); } } // Save buffers to defrag context for later destruction. if (pDefragCtx->res == VK_SUCCESS && moveCount > 0) { pDefragCtx->res = VK_NOT_READY; } } void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats) { for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) { VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; if (pBlock->m_pMetadata->IsEmpty()) { if (m_Blocks.size() > m_MinBlockCount) { if (pDefragmentationStats != VMA_NULL) { ++pDefragmentationStats->deviceMemoryBlocksFreed; pDefragmentationStats->bytesFreed += pBlock->m_pMetadata->GetSize(); } VmaVectorRemove(m_Blocks, blockIndex); pBlock->Destroy(m_hAllocator); vma_delete(m_hAllocator, pBlock); } else { break; } } } UpdateHasEmptyBlock(); } void VmaBlockVector::UpdateHasEmptyBlock() { m_HasEmptyBlock = false; for (size_t index = 0, count = m_Blocks.size(); index < count; ++index) { VmaDeviceMemoryBlock* const pBlock = m_Blocks[index]; if (pBlock->m_pMetadata->IsEmpty()) { m_HasEmptyBlock = true; break; } } } #if VMA_STATS_STRING_ENABLED void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) { VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); json.BeginObject(); if (IsCustomPool()) { const char* poolName = m_hParentPool->GetName(); if (poolName != VMA_NULL && poolName[0] != '\0') { json.WriteString("Name"); json.WriteString(poolName); } json.WriteString("MemoryTypeIndex"); json.WriteNumber(m_MemoryTypeIndex); json.WriteString("BlockSize"); json.WriteNumber(m_PreferredBlockSize); json.WriteString("BlockCount"); json.BeginObject(true); if (m_MinBlockCount > 0) { json.WriteString("Min"); json.WriteNumber((uint64_t)m_MinBlockCount); } if (m_MaxBlockCount < SIZE_MAX) { json.WriteString("Max"); json.WriteNumber((uint64_t)m_MaxBlockCount); } json.WriteString("Cur"); json.WriteNumber((uint64_t)m_Blocks.size()); json.EndObject(); if (m_FrameInUseCount > 0) { json.WriteString("FrameInUseCount"); json.WriteNumber(m_FrameInUseCount); } if (m_Algorithm != 0) { json.WriteString("Algorithm"); json.WriteString(VmaAlgorithmToStr(m_Algorithm)); } } else { json.WriteString("PreferredBlockSize"); json.WriteNumber(m_PreferredBlockSize); } json.WriteString("Blocks"); json.BeginObject(); for (size_t i = 0; i < m_Blocks.size(); ++i) { json.BeginString(); json.ContinueString(m_Blocks[i]->GetId()); json.EndString(); m_Blocks[i]->m_pMetadata->PrintDetailedMap(json); } json.EndObject(); json.EndObject(); } #endif // #if VMA_STATS_STRING_ENABLED void VmaBlockVector::Defragment( class VmaBlockVectorDefragmentationContext* pCtx, VmaDefragmentationStats* pStats, VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove, VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove, VkCommandBuffer commandBuffer) { pCtx->res = VK_SUCCESS; const VkMemoryPropertyFlags memPropFlags = m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags; const bool isHostVisible = (memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0; const bool canDefragmentOnCpu = maxCpuBytesToMove > 0 && maxCpuAllocationsToMove > 0 && isHostVisible; const bool canDefragmentOnGpu = maxGpuBytesToMove > 0 && maxGpuAllocationsToMove > 0 && !IsCorruptionDetectionEnabled() && ((1u << m_MemoryTypeIndex) & m_hAllocator->GetGpuDefragmentationMemoryTypeBits()) != 0; // There are options to defragment this memory type. if (canDefragmentOnCpu || canDefragmentOnGpu) { bool defragmentOnGpu; // There is only one option to defragment this memory type. if (canDefragmentOnGpu != canDefragmentOnCpu) { defragmentOnGpu = canDefragmentOnGpu; } // Both options are available: Heuristics to choose the best one. else { defragmentOnGpu = (memPropFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0 || m_hAllocator->IsIntegratedGpu(); } bool overlappingMoveSupported = !defragmentOnGpu; if (m_hAllocator->m_UseMutex) { m_Mutex.LockWrite(); pCtx->mutexLocked = true; } pCtx->Begin(overlappingMoveSupported); // Defragment. const VkDeviceSize maxBytesToMove = defragmentOnGpu ? maxGpuBytesToMove : maxCpuBytesToMove; const uint32_t maxAllocationsToMove = defragmentOnGpu ? maxGpuAllocationsToMove : maxCpuAllocationsToMove; VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> > moves = VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >(VmaStlAllocator<VmaDefragmentationMove>(m_hAllocator->GetAllocationCallbacks())); pCtx->res = pCtx->GetAlgorithm()->Defragment(moves, maxBytesToMove, maxAllocationsToMove); // Accumulate statistics. if (pStats != VMA_NULL) { const VkDeviceSize bytesMoved = pCtx->GetAlgorithm()->GetBytesMoved(); const uint32_t allocationsMoved = pCtx->GetAlgorithm()->GetAllocationsMoved(); pStats->bytesMoved += bytesMoved; pStats->allocationsMoved += allocationsMoved; VMA_ASSERT(bytesMoved <= maxBytesToMove); VMA_ASSERT(allocationsMoved <= maxAllocationsToMove); if (defragmentOnGpu) { maxGpuBytesToMove -= bytesMoved; maxGpuAllocationsToMove -= allocationsMoved; } else { maxCpuBytesToMove -= bytesMoved; maxCpuAllocationsToMove -= allocationsMoved; } } if (pCtx->res >= VK_SUCCESS) { if (defragmentOnGpu) { ApplyDefragmentationMovesGpu(pCtx, moves, commandBuffer); } else { ApplyDefragmentationMovesCpu(pCtx, moves); } } } } void VmaBlockVector::DefragmentationEnd( class VmaBlockVectorDefragmentationContext* pCtx, VmaDefragmentationStats* pStats) { // Destroy buffers. for (size_t blockIndex = pCtx->blockContexts.size(); blockIndex--; ) { VmaBlockDefragmentationContext& blockCtx = pCtx->blockContexts[blockIndex]; if (blockCtx.hBuffer) { (*m_hAllocator->GetVulkanFunctions().vkDestroyBuffer)( m_hAllocator->m_hDevice, blockCtx.hBuffer, m_hAllocator->GetAllocationCallbacks()); } } if (pCtx->res >= VK_SUCCESS) { FreeEmptyBlocks(pStats); } if (pCtx->mutexLocked) { VMA_ASSERT(m_hAllocator->m_UseMutex); m_Mutex.UnlockWrite(); } } size_t VmaBlockVector::CalcAllocationCount() const { size_t result = 0; for (size_t i = 0; i < m_Blocks.size(); ++i) { result += m_Blocks[i]->m_pMetadata->GetAllocationCount(); } return result; } bool VmaBlockVector::IsBufferImageGranularityConflictPossible() const { if (m_BufferImageGranularity == 1) { return false; } VmaSuballocationType lastSuballocType = VMA_SUBALLOCATION_TYPE_FREE; for (size_t i = 0, count = m_Blocks.size(); i < count; ++i) { VmaDeviceMemoryBlock* const pBlock = m_Blocks[i]; VMA_ASSERT(m_Algorithm == 0); VmaBlockMetadata_Generic* const pMetadata = (VmaBlockMetadata_Generic*)pBlock->m_pMetadata; if (pMetadata->IsBufferImageGranularityConflictPossible(m_BufferImageGranularity, lastSuballocType)) { return true; } } return false; } void VmaBlockVector::MakePoolAllocationsLost( uint32_t currentFrameIndex, size_t* pLostAllocationCount) { VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); size_t lostAllocationCount = 0; for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) { VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; VMA_ASSERT(pBlock); lostAllocationCount += pBlock->m_pMetadata->MakeAllocationsLost(currentFrameIndex, m_FrameInUseCount); } if (pLostAllocationCount != VMA_NULL) { *pLostAllocationCount = lostAllocationCount; } } VkResult VmaBlockVector::CheckCorruption() { if (!IsCorruptionDetectionEnabled()) { return VK_ERROR_FEATURE_NOT_PRESENT; } VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) { VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; VMA_ASSERT(pBlock); VkResult res = pBlock->CheckCorruption(m_hAllocator); if (res != VK_SUCCESS) { return res; } } return VK_SUCCESS; } void VmaBlockVector::AddStats(VmaStats* pStats) { const uint32_t memTypeIndex = m_MemoryTypeIndex; const uint32_t memHeapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(memTypeIndex); VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) { const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; VMA_ASSERT(pBlock); VMA_HEAVY_ASSERT(pBlock->Validate()); VmaStatInfo allocationStatInfo; pBlock->m_pMetadata->CalcAllocationStatInfo(allocationStatInfo); VmaAddStatInfo(pStats->total, allocationStatInfo); VmaAddStatInfo(pStats->memoryType[memTypeIndex], allocationStatInfo); VmaAddStatInfo(pStats->memoryHeap[memHeapIndex], allocationStatInfo); } } //////////////////////////////////////////////////////////////////////////////// // VmaDefragmentationAlgorithm_Generic members definition VmaDefragmentationAlgorithm_Generic::VmaDefragmentationAlgorithm_Generic( VmaAllocator hAllocator, VmaBlockVector* pBlockVector, uint32_t currentFrameIndex, bool overlappingMoveSupported) : VmaDefragmentationAlgorithm(hAllocator, pBlockVector, currentFrameIndex), m_AllocationCount(0), m_AllAllocations(false), m_BytesMoved(0), m_AllocationsMoved(0), m_Blocks(VmaStlAllocator<BlockInfo*>(hAllocator->GetAllocationCallbacks())) { // Create block info for each block. const size_t blockCount = m_pBlockVector->m_Blocks.size(); for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) { BlockInfo* pBlockInfo = vma_new(m_hAllocator, BlockInfo)(m_hAllocator->GetAllocationCallbacks()); pBlockInfo->m_OriginalBlockIndex = blockIndex; pBlockInfo->m_pBlock = m_pBlockVector->m_Blocks[blockIndex]; m_Blocks.push_back(pBlockInfo); } // Sort them by m_pBlock pointer value. VMA_SORT(m_Blocks.begin(), m_Blocks.end(), BlockPointerLess()); } VmaDefragmentationAlgorithm_Generic::~VmaDefragmentationAlgorithm_Generic() { for (size_t i = m_Blocks.size(); i--; ) { vma_delete(m_hAllocator, m_Blocks[i]); } } void VmaDefragmentationAlgorithm_Generic::AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged) { // Now as we are inside VmaBlockVector::m_Mutex, we can make final check if this allocation was not lost. if (hAlloc->GetLastUseFrameIndex() != VMA_FRAME_INDEX_LOST) { VmaDeviceMemoryBlock* pBlock = hAlloc->GetBlock(); BlockInfoVector::iterator it = VmaBinaryFindFirstNotLess(m_Blocks.begin(), m_Blocks.end(), pBlock, BlockPointerLess()); if (it != m_Blocks.end() && (*it)->m_pBlock == pBlock) { AllocationInfo allocInfo = AllocationInfo(hAlloc, pChanged); (*it)->m_Allocations.push_back(allocInfo); } else { VMA_ASSERT(0); } ++m_AllocationCount; } } VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove) { if (m_Blocks.empty()) { return VK_SUCCESS; } // This is a choice based on research. // Option 1: uint32_t strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; // Option 2: //uint32_t strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; // Option 3: //uint32_t strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT; size_t srcBlockMinIndex = 0; // When FAST_ALGORITHM, move allocations from only last out of blocks that contain non-movable allocations. /* if(m_AlgorithmFlags & VMA_DEFRAGMENTATION_FAST_ALGORITHM_BIT) { const size_t blocksWithNonMovableCount = CalcBlocksWithNonMovableCount(); if(blocksWithNonMovableCount > 0) { srcBlockMinIndex = blocksWithNonMovableCount - 1; } } */ size_t srcBlockIndex = m_Blocks.size() - 1; size_t srcAllocIndex = SIZE_MAX; for (;;) { // 1. Find next allocation to move. // 1.1. Start from last to first m_Blocks - they are sorted from most "destination" to most "source". // 1.2. Then start from last to first m_Allocations. while (srcAllocIndex >= m_Blocks[srcBlockIndex]->m_Allocations.size()) { if (m_Blocks[srcBlockIndex]->m_Allocations.empty()) { // Finished: no more allocations to process. if (srcBlockIndex == srcBlockMinIndex) { return VK_SUCCESS; } else { --srcBlockIndex; srcAllocIndex = SIZE_MAX; } } else { srcAllocIndex = m_Blocks[srcBlockIndex]->m_Allocations.size() - 1; } } BlockInfo* pSrcBlockInfo = m_Blocks[srcBlockIndex]; AllocationInfo& allocInfo = pSrcBlockInfo->m_Allocations[srcAllocIndex]; const VkDeviceSize size = allocInfo.m_hAllocation->GetSize(); const VkDeviceSize srcOffset = allocInfo.m_hAllocation->GetOffset(); const VkDeviceSize alignment = allocInfo.m_hAllocation->GetAlignment(); const VmaSuballocationType suballocType = allocInfo.m_hAllocation->GetSuballocationType(); // 2. Try to find new place for this allocation in preceding or current block. for (size_t dstBlockIndex = 0; dstBlockIndex <= srcBlockIndex; ++dstBlockIndex) { BlockInfo* pDstBlockInfo = m_Blocks[dstBlockIndex]; VmaAllocationRequest dstAllocRequest; if (pDstBlockInfo->m_pBlock->m_pMetadata->CreateAllocationRequest( m_CurrentFrameIndex, m_pBlockVector->GetFrameInUseCount(), m_pBlockVector->GetBufferImageGranularity(), size, alignment, false, // upperAddress suballocType, false, // canMakeOtherLost strategy, &dstAllocRequest) && MoveMakesSense( dstBlockIndex, dstAllocRequest.offset, srcBlockIndex, srcOffset)) { VMA_ASSERT(dstAllocRequest.itemsToMakeLostCount == 0); // Reached limit on number of allocations or bytes to move. if ((m_AllocationsMoved + 1 > maxAllocationsToMove) || (m_BytesMoved + size > maxBytesToMove)) { return VK_SUCCESS; } VmaDefragmentationMove move; move.srcBlockIndex = pSrcBlockInfo->m_OriginalBlockIndex; move.dstBlockIndex = pDstBlockInfo->m_OriginalBlockIndex; move.srcOffset = srcOffset; move.dstOffset = dstAllocRequest.offset; move.size = size; moves.push_back(move); pDstBlockInfo->m_pBlock->m_pMetadata->Alloc( dstAllocRequest, suballocType, size, allocInfo.m_hAllocation); pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset); allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset); if (allocInfo.m_pChanged != VMA_NULL) { *allocInfo.m_pChanged = VK_TRUE; } ++m_AllocationsMoved; m_BytesMoved += size; VmaVectorRemove(pSrcBlockInfo->m_Allocations, srcAllocIndex); break; } } // If not processed, this allocInfo remains in pBlockInfo->m_Allocations for next round. if (srcAllocIndex > 0) { --srcAllocIndex; } else { if (srcBlockIndex > 0) { --srcBlockIndex; srcAllocIndex = SIZE_MAX; } else { return VK_SUCCESS; } } } } size_t VmaDefragmentationAlgorithm_Generic::CalcBlocksWithNonMovableCount() const { size_t result = 0; for (size_t i = 0; i < m_Blocks.size(); ++i) { if (m_Blocks[i]->m_HasNonMovableAllocations) { ++result; } } return result; } VkResult VmaDefragmentationAlgorithm_Generic::Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove) { if (!m_AllAllocations && m_AllocationCount == 0) { return VK_SUCCESS; } const size_t blockCount = m_Blocks.size(); for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) { BlockInfo* pBlockInfo = m_Blocks[blockIndex]; if (m_AllAllocations) { VmaBlockMetadata_Generic* pMetadata = (VmaBlockMetadata_Generic*)pBlockInfo->m_pBlock->m_pMetadata; for (VmaSuballocationList::const_iterator it = pMetadata->m_Suballocations.begin(); it != pMetadata->m_Suballocations.end(); ++it) { if (it->type != VMA_SUBALLOCATION_TYPE_FREE) { AllocationInfo allocInfo = AllocationInfo(it->hAllocation, VMA_NULL); pBlockInfo->m_Allocations.push_back(allocInfo); } } } pBlockInfo->CalcHasNonMovableAllocations(); // This is a choice based on research. // Option 1: pBlockInfo->SortAllocationsByOffsetDescending(); // Option 2: //pBlockInfo->SortAllocationsBySizeDescending(); } // Sort m_Blocks this time by the main criterium, from most "destination" to most "source" blocks. VMA_SORT(m_Blocks.begin(), m_Blocks.end(), BlockInfoCompareMoveDestination()); // This is a choice based on research. const uint32_t roundCount = 2; // Execute defragmentation rounds (the main part). VkResult result = VK_SUCCESS; for (uint32_t round = 0; (round < roundCount) && (result == VK_SUCCESS); ++round) { result = DefragmentRound(moves, maxBytesToMove, maxAllocationsToMove); } return result; } bool VmaDefragmentationAlgorithm_Generic::MoveMakesSense( size_t dstBlockIndex, VkDeviceSize dstOffset, size_t srcBlockIndex, VkDeviceSize srcOffset) { if (dstBlockIndex < srcBlockIndex) { return true; } if (dstBlockIndex > srcBlockIndex) { return false; } if (dstOffset < srcOffset) { return true; } return false; } //////////////////////////////////////////////////////////////////////////////// // VmaDefragmentationAlgorithm_Fast VmaDefragmentationAlgorithm_Fast::VmaDefragmentationAlgorithm_Fast( VmaAllocator hAllocator, VmaBlockVector* pBlockVector, uint32_t currentFrameIndex, bool overlappingMoveSupported) : VmaDefragmentationAlgorithm(hAllocator, pBlockVector, currentFrameIndex), m_OverlappingMoveSupported(overlappingMoveSupported), m_AllocationCount(0), m_AllAllocations(false), m_BytesMoved(0), m_AllocationsMoved(0), m_BlockInfos(VmaStlAllocator<BlockInfo>(hAllocator->GetAllocationCallbacks())) { VMA_ASSERT(VMA_DEBUG_MARGIN == 0); } VmaDefragmentationAlgorithm_Fast::~VmaDefragmentationAlgorithm_Fast() { } VkResult VmaDefragmentationAlgorithm_Fast::Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves, VkDeviceSize maxBytesToMove, uint32_t maxAllocationsToMove) { VMA_ASSERT(m_AllAllocations || m_pBlockVector->CalcAllocationCount() == m_AllocationCount); const size_t blockCount = m_pBlockVector->GetBlockCount(); if (blockCount == 0 || maxBytesToMove == 0 || maxAllocationsToMove == 0) { return VK_SUCCESS; } PreprocessMetadata(); // Sort blocks in order from most destination. m_BlockInfos.resize(blockCount); for (size_t i = 0; i < blockCount; ++i) { m_BlockInfos[i].origBlockIndex = i; } VMA_SORT(m_BlockInfos.begin(), m_BlockInfos.end(), [this](const BlockInfo& lhs, const BlockInfo& rhs) -> bool { return m_pBlockVector->GetBlock(lhs.origBlockIndex)->m_pMetadata->GetSumFreeSize() < m_pBlockVector->GetBlock(rhs.origBlockIndex)->m_pMetadata->GetSumFreeSize(); }); // THE MAIN ALGORITHM FreeSpaceDatabase freeSpaceDb; size_t dstBlockInfoIndex = 0; size_t dstOrigBlockIndex = m_BlockInfos[dstBlockInfoIndex].origBlockIndex; VmaDeviceMemoryBlock* pDstBlock = m_pBlockVector->GetBlock(dstOrigBlockIndex); VmaBlockMetadata_Generic* pDstMetadata = (VmaBlockMetadata_Generic*)pDstBlock->m_pMetadata; VkDeviceSize dstBlockSize = pDstMetadata->GetSize(); VkDeviceSize dstOffset = 0; bool end = false; for (size_t srcBlockInfoIndex = 0; !end && srcBlockInfoIndex < blockCount; ++srcBlockInfoIndex) { const size_t srcOrigBlockIndex = m_BlockInfos[srcBlockInfoIndex].origBlockIndex; VmaDeviceMemoryBlock* const pSrcBlock = m_pBlockVector->GetBlock(srcOrigBlockIndex); VmaBlockMetadata_Generic* const pSrcMetadata = (VmaBlockMetadata_Generic*)pSrcBlock->m_pMetadata; for (VmaSuballocationList::iterator srcSuballocIt = pSrcMetadata->m_Suballocations.begin(); !end && srcSuballocIt != pSrcMetadata->m_Suballocations.end(); ) { VmaAllocation_T* const pAlloc = srcSuballocIt->hAllocation; const VkDeviceSize srcAllocAlignment = pAlloc->GetAlignment(); const VkDeviceSize srcAllocSize = srcSuballocIt->size; if (m_AllocationsMoved == maxAllocationsToMove || m_BytesMoved + srcAllocSize > maxBytesToMove) { end = true; break; } const VkDeviceSize srcAllocOffset = srcSuballocIt->offset; // Try to place it in one of free spaces from the database. size_t freeSpaceInfoIndex; VkDeviceSize dstAllocOffset; if (freeSpaceDb.Fetch(srcAllocAlignment, srcAllocSize, freeSpaceInfoIndex, dstAllocOffset)) { size_t freeSpaceOrigBlockIndex = m_BlockInfos[freeSpaceInfoIndex].origBlockIndex; VmaDeviceMemoryBlock* pFreeSpaceBlock = m_pBlockVector->GetBlock(freeSpaceOrigBlockIndex); VmaBlockMetadata_Generic* pFreeSpaceMetadata = (VmaBlockMetadata_Generic*)pFreeSpaceBlock->m_pMetadata; // Same block if (freeSpaceInfoIndex == srcBlockInfoIndex) { VMA_ASSERT(dstAllocOffset <= srcAllocOffset); // MOVE OPTION 1: Move the allocation inside the same block by decreasing offset. VmaSuballocation suballoc = *srcSuballocIt; suballoc.offset = dstAllocOffset; suballoc.hAllocation->ChangeOffset(dstAllocOffset); m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt; ++nextSuballocIt; pSrcMetadata->m_Suballocations.erase(srcSuballocIt); srcSuballocIt = nextSuballocIt; InsertSuballoc(pFreeSpaceMetadata, suballoc); VmaDefragmentationMove move = { srcOrigBlockIndex, freeSpaceOrigBlockIndex, srcAllocOffset, dstAllocOffset, srcAllocSize }; moves.push_back(move); } // Different block else { // MOVE OPTION 2: Move the allocation to a different block. VMA_ASSERT(freeSpaceInfoIndex < srcBlockInfoIndex); VmaSuballocation suballoc = *srcSuballocIt; suballoc.offset = dstAllocOffset; suballoc.hAllocation->ChangeBlockAllocation(m_hAllocator, pFreeSpaceBlock, dstAllocOffset); m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt; ++nextSuballocIt; pSrcMetadata->m_Suballocations.erase(srcSuballocIt); srcSuballocIt = nextSuballocIt; InsertSuballoc(pFreeSpaceMetadata, suballoc); VmaDefragmentationMove move = { srcOrigBlockIndex, freeSpaceOrigBlockIndex, srcAllocOffset, dstAllocOffset, srcAllocSize }; moves.push_back(move); } } else { dstAllocOffset = VmaAlignUp(dstOffset, srcAllocAlignment); // If the allocation doesn't fit before the end of dstBlock, forward to next block. while (dstBlockInfoIndex < srcBlockInfoIndex && dstAllocOffset + srcAllocSize > dstBlockSize) { // But before that, register remaining free space at the end of dst block. freeSpaceDb.Register(dstBlockInfoIndex, dstOffset, dstBlockSize - dstOffset); ++dstBlockInfoIndex; dstOrigBlockIndex = m_BlockInfos[dstBlockInfoIndex].origBlockIndex; pDstBlock = m_pBlockVector->GetBlock(dstOrigBlockIndex); pDstMetadata = (VmaBlockMetadata_Generic*)pDstBlock->m_pMetadata; dstBlockSize = pDstMetadata->GetSize(); dstOffset = 0; dstAllocOffset = 0; } // Same block if (dstBlockInfoIndex == srcBlockInfoIndex) { VMA_ASSERT(dstAllocOffset <= srcAllocOffset); const bool overlap = dstAllocOffset + srcAllocSize > srcAllocOffset; bool skipOver = overlap; if (overlap && m_OverlappingMoveSupported && dstAllocOffset < srcAllocOffset) { // If destination and source place overlap, skip if it would move it // by only < 1/64 of its size. skipOver = (srcAllocOffset - dstAllocOffset) * 64 < srcAllocSize; } if (skipOver) { freeSpaceDb.Register(dstBlockInfoIndex, dstOffset, srcAllocOffset - dstOffset); dstOffset = srcAllocOffset + srcAllocSize; ++srcSuballocIt; } // MOVE OPTION 1: Move the allocation inside the same block by decreasing offset. else { srcSuballocIt->offset = dstAllocOffset; srcSuballocIt->hAllocation->ChangeOffset(dstAllocOffset); dstOffset = dstAllocOffset + srcAllocSize; m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; ++srcSuballocIt; VmaDefragmentationMove move = { srcOrigBlockIndex, dstOrigBlockIndex, srcAllocOffset, dstAllocOffset, srcAllocSize }; moves.push_back(move); } } // Different block else { // MOVE OPTION 2: Move the allocation to a different block. VMA_ASSERT(dstBlockInfoIndex < srcBlockInfoIndex); VMA_ASSERT(dstAllocOffset + srcAllocSize <= dstBlockSize); VmaSuballocation suballoc = *srcSuballocIt; suballoc.offset = dstAllocOffset; suballoc.hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlock, dstAllocOffset); dstOffset = dstAllocOffset + srcAllocSize; m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt; ++nextSuballocIt; pSrcMetadata->m_Suballocations.erase(srcSuballocIt); srcSuballocIt = nextSuballocIt; pDstMetadata->m_Suballocations.push_back(suballoc); VmaDefragmentationMove move = { srcOrigBlockIndex, dstOrigBlockIndex, srcAllocOffset, dstAllocOffset, srcAllocSize }; moves.push_back(move); } } } } m_BlockInfos.clear(); PostprocessMetadata(); return VK_SUCCESS; } void VmaDefragmentationAlgorithm_Fast::PreprocessMetadata() { const size_t blockCount = m_pBlockVector->GetBlockCount(); for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) { VmaBlockMetadata_Generic* const pMetadata = (VmaBlockMetadata_Generic*)m_pBlockVector->GetBlock(blockIndex)->m_pMetadata; pMetadata->m_FreeCount = 0; pMetadata->m_SumFreeSize = pMetadata->GetSize(); pMetadata->m_FreeSuballocationsBySize.clear(); for (VmaSuballocationList::iterator it = pMetadata->m_Suballocations.begin(); it != pMetadata->m_Suballocations.end(); ) { if (it->type == VMA_SUBALLOCATION_TYPE_FREE) { VmaSuballocationList::iterator nextIt = it; ++nextIt; pMetadata->m_Suballocations.erase(it); it = nextIt; } else { ++it; } } } } void VmaDefragmentationAlgorithm_Fast::PostprocessMetadata() { const size_t blockCount = m_pBlockVector->GetBlockCount(); for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) { VmaBlockMetadata_Generic* const pMetadata = (VmaBlockMetadata_Generic*)m_pBlockVector->GetBlock(blockIndex)->m_pMetadata; const VkDeviceSize blockSize = pMetadata->GetSize(); // No allocations in this block - entire area is free. if (pMetadata->m_Suballocations.empty()) { pMetadata->m_FreeCount = 1; //pMetadata->m_SumFreeSize is already set to blockSize. VmaSuballocation suballoc = { 0, // offset blockSize, // size VMA_NULL, // hAllocation VMA_SUBALLOCATION_TYPE_FREE }; pMetadata->m_Suballocations.push_back(suballoc); pMetadata->RegisterFreeSuballocation(pMetadata->m_Suballocations.begin()); } // There are some allocations in this block. else { VkDeviceSize offset = 0; VmaSuballocationList::iterator it; for (it = pMetadata->m_Suballocations.begin(); it != pMetadata->m_Suballocations.end(); ++it) { VMA_ASSERT(it->type != VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(it->offset >= offset); // Need to insert preceding free space. if (it->offset > offset) { ++pMetadata->m_FreeCount; const VkDeviceSize freeSize = it->offset - offset; VmaSuballocation suballoc = { offset, // offset freeSize, // size VMA_NULL, // hAllocation VMA_SUBALLOCATION_TYPE_FREE }; VmaSuballocationList::iterator precedingFreeIt = pMetadata->m_Suballocations.insert(it, suballoc); if (freeSize >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { pMetadata->m_FreeSuballocationsBySize.push_back(precedingFreeIt); } } pMetadata->m_SumFreeSize -= it->size; offset = it->offset + it->size; } // Need to insert trailing free space. if (offset < blockSize) { ++pMetadata->m_FreeCount; const VkDeviceSize freeSize = blockSize - offset; VmaSuballocation suballoc = { offset, // offset freeSize, // size VMA_NULL, // hAllocation VMA_SUBALLOCATION_TYPE_FREE }; VMA_ASSERT(it == pMetadata->m_Suballocations.end()); VmaSuballocationList::iterator trailingFreeIt = pMetadata->m_Suballocations.insert(it, suballoc); if (freeSize > VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) { pMetadata->m_FreeSuballocationsBySize.push_back(trailingFreeIt); } } VMA_SORT( pMetadata->m_FreeSuballocationsBySize.begin(), pMetadata->m_FreeSuballocationsBySize.end(), VmaSuballocationItemSizeLess()); } VMA_HEAVY_ASSERT(pMetadata->Validate()); } } void VmaDefragmentationAlgorithm_Fast::InsertSuballoc(VmaBlockMetadata_Generic* pMetadata, const VmaSuballocation& suballoc) { // TODO: Optimize somehow. Remember iterator instead of searching for it linearly. VmaSuballocationList::iterator it = pMetadata->m_Suballocations.begin(); while (it != pMetadata->m_Suballocations.end()) { if (it->offset < suballoc.offset) { ++it; } } pMetadata->m_Suballocations.insert(it, suballoc); } //////////////////////////////////////////////////////////////////////////////// // VmaBlockVectorDefragmentationContext VmaBlockVectorDefragmentationContext::VmaBlockVectorDefragmentationContext( VmaAllocator hAllocator, VmaPool hCustomPool, VmaBlockVector* pBlockVector, uint32_t currFrameIndex) : res(VK_SUCCESS), mutexLocked(false), blockContexts(VmaStlAllocator<VmaBlockDefragmentationContext>(hAllocator->GetAllocationCallbacks())), m_hAllocator(hAllocator), m_hCustomPool(hCustomPool), m_pBlockVector(pBlockVector), m_CurrFrameIndex(currFrameIndex), m_pAlgorithm(VMA_NULL), m_Allocations(VmaStlAllocator<AllocInfo>(hAllocator->GetAllocationCallbacks())), m_AllAllocations(false) { } VmaBlockVectorDefragmentationContext::~VmaBlockVectorDefragmentationContext() { vma_delete(m_hAllocator, m_pAlgorithm); } void VmaBlockVectorDefragmentationContext::AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged) { AllocInfo info = { hAlloc, pChanged }; m_Allocations.push_back(info); } void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported) { const bool allAllocations = m_AllAllocations || m_Allocations.size() == m_pBlockVector->CalcAllocationCount(); /******************************** HERE IS THE CHOICE OF DEFRAGMENTATION ALGORITHM. ********************************/ /* Fast algorithm is supported only when certain criteria are met: - VMA_DEBUG_MARGIN is 0. - All allocations in this block vector are moveable. - There is no possibility of image/buffer granularity conflict. */ if (VMA_DEBUG_MARGIN == 0 && allAllocations && !m_pBlockVector->IsBufferImageGranularityConflictPossible()) { m_pAlgorithm = vma_new(m_hAllocator, VmaDefragmentationAlgorithm_Fast)( m_hAllocator, m_pBlockVector, m_CurrFrameIndex, overlappingMoveSupported); } else { m_pAlgorithm = vma_new(m_hAllocator, VmaDefragmentationAlgorithm_Generic)( m_hAllocator, m_pBlockVector, m_CurrFrameIndex, overlappingMoveSupported); } if (allAllocations) { m_pAlgorithm->AddAll(); } else { for (size_t i = 0, count = m_Allocations.size(); i < count; ++i) { m_pAlgorithm->AddAllocation(m_Allocations[i].hAlloc, m_Allocations[i].pChanged); } } } //////////////////////////////////////////////////////////////////////////////// // VmaDefragmentationContext VmaDefragmentationContext_T::VmaDefragmentationContext_T( VmaAllocator hAllocator, uint32_t currFrameIndex, uint32_t flags, VmaDefragmentationStats* pStats) : m_hAllocator(hAllocator), m_CurrFrameIndex(currFrameIndex), m_Flags(flags), m_pStats(pStats), m_CustomPoolContexts(VmaStlAllocator<VmaBlockVectorDefragmentationContext*>(hAllocator->GetAllocationCallbacks())) { memset(m_DefaultPoolContexts, 0, sizeof(m_DefaultPoolContexts)); } VmaDefragmentationContext_T::~VmaDefragmentationContext_T() { for (size_t i = m_CustomPoolContexts.size(); i--; ) { VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_CustomPoolContexts[i]; pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats); vma_delete(m_hAllocator, pBlockVectorCtx); } for (size_t i = m_hAllocator->m_MemProps.memoryTypeCount; i--; ) { VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_DefaultPoolContexts[i]; if (pBlockVectorCtx) { pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats); vma_delete(m_hAllocator, pBlockVectorCtx); } } } void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) { for (uint32_t poolIndex = 0; poolIndex < poolCount; ++poolIndex) { VmaPool pool = pPools[poolIndex]; VMA_ASSERT(pool); // Pools with algorithm other than default are not defragmented. if (pool->m_BlockVector.GetAlgorithm() == 0) { VmaBlockVectorDefragmentationContext* pBlockVectorDefragCtx = VMA_NULL; for (size_t i = m_CustomPoolContexts.size(); i--; ) { if (m_CustomPoolContexts[i]->GetCustomPool() == pool) { pBlockVectorDefragCtx = m_CustomPoolContexts[i]; break; } } if (!pBlockVectorDefragCtx) { pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)( m_hAllocator, pool, &pool->m_BlockVector, m_CurrFrameIndex); m_CustomPoolContexts.push_back(pBlockVectorDefragCtx); } pBlockVectorDefragCtx->AddAll(); } } } void VmaDefragmentationContext_T::AddAllocations( uint32_t allocationCount, VmaAllocation* pAllocations, VkBool32* pAllocationsChanged) { // Dispatch pAllocations among defragmentators. Create them when necessary. for (uint32_t allocIndex = 0; allocIndex < allocationCount; ++allocIndex) { const VmaAllocation hAlloc = pAllocations[allocIndex]; VMA_ASSERT(hAlloc); // DedicatedAlloc cannot be defragmented. if ((hAlloc->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK) && // Lost allocation cannot be defragmented. (hAlloc->GetLastUseFrameIndex() != VMA_FRAME_INDEX_LOST)) { VmaBlockVectorDefragmentationContext* pBlockVectorDefragCtx = VMA_NULL; const VmaPool hAllocPool = hAlloc->GetBlock()->GetParentPool(); // This allocation belongs to custom pool. if (hAllocPool != VK_NULL_HANDLE) { // Pools with algorithm other than default are not defragmented. if (hAllocPool->m_BlockVector.GetAlgorithm() == 0) { for (size_t i = m_CustomPoolContexts.size(); i--; ) { if (m_CustomPoolContexts[i]->GetCustomPool() == hAllocPool) { pBlockVectorDefragCtx = m_CustomPoolContexts[i]; break; } } if (!pBlockVectorDefragCtx) { pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)( m_hAllocator, hAllocPool, &hAllocPool->m_BlockVector, m_CurrFrameIndex); m_CustomPoolContexts.push_back(pBlockVectorDefragCtx); } } } // This allocation belongs to default pool. else { const uint32_t memTypeIndex = hAlloc->GetMemoryTypeIndex(); pBlockVectorDefragCtx = m_DefaultPoolContexts[memTypeIndex]; if (!pBlockVectorDefragCtx) { pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)( m_hAllocator, VMA_NULL, // hCustomPool m_hAllocator->m_pBlockVectors[memTypeIndex], m_CurrFrameIndex); m_DefaultPoolContexts[memTypeIndex] = pBlockVectorDefragCtx; } } if (pBlockVectorDefragCtx) { VkBool32* const pChanged = (pAllocationsChanged != VMA_NULL) ? &pAllocationsChanged[allocIndex] : VMA_NULL; pBlockVectorDefragCtx->AddAllocation(hAlloc, pChanged); } } } } VkResult VmaDefragmentationContext_T::Defragment( VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove, VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove, VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats) { if (pStats) { memset(pStats, 0, sizeof(VmaDefragmentationStats)); } if (commandBuffer == VK_NULL_HANDLE) { maxGpuBytesToMove = 0; maxGpuAllocationsToMove = 0; } VkResult res = VK_SUCCESS; // Process default pools. for (uint32_t memTypeIndex = 0; memTypeIndex < m_hAllocator->GetMemoryTypeCount() && res >= VK_SUCCESS; ++memTypeIndex) { VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_DefaultPoolContexts[memTypeIndex]; if (pBlockVectorCtx) { VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); pBlockVectorCtx->GetBlockVector()->Defragment( pBlockVectorCtx, pStats, maxCpuBytesToMove, maxCpuAllocationsToMove, maxGpuBytesToMove, maxGpuAllocationsToMove, commandBuffer); if (pBlockVectorCtx->res != VK_SUCCESS) { res = pBlockVectorCtx->res; } } } // Process custom pools. for (size_t customCtxIndex = 0, customCtxCount = m_CustomPoolContexts.size(); customCtxIndex < customCtxCount && res >= VK_SUCCESS; ++customCtxIndex) { VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_CustomPoolContexts[customCtxIndex]; VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); pBlockVectorCtx->GetBlockVector()->Defragment( pBlockVectorCtx, pStats, maxCpuBytesToMove, maxCpuAllocationsToMove, maxGpuBytesToMove, maxGpuAllocationsToMove, commandBuffer); if (pBlockVectorCtx->res != VK_SUCCESS) { res = pBlockVectorCtx->res; } } return res; } //////////////////////////////////////////////////////////////////////////////// // VmaRecorder #if VMA_RECORDING_ENABLED VmaRecorder::VmaRecorder() : m_UseMutex(true), m_Flags(0), m_File(VMA_NULL), m_Freq(INT64_MAX), m_StartCounter(INT64_MAX) { } VkResult VmaRecorder::Init(const VmaRecordSettings& settings, bool useMutex) { m_UseMutex = useMutex; m_Flags = settings.flags; QueryPerformanceFrequency((LARGE_INTEGER*)&m_Freq); QueryPerformanceCounter((LARGE_INTEGER*)&m_StartCounter); // Open file for writing. errno_t err = fopen_s(&m_File, settings.pFilePath, "wb"); if (err != 0) { return VK_ERROR_INITIALIZATION_FAILED; } // Write header. fprintf(m_File, "%s\n", "Vulkan Memory Allocator,Calls recording"); fprintf(m_File, "%s\n", "1,8"); return VK_SUCCESS; } VmaRecorder::~VmaRecorder() { if (m_File != VMA_NULL) { fclose(m_File); } } void VmaRecorder::RecordCreateAllocator(uint32_t frameIndex) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaCreateAllocator\n", callParams.threadId, callParams.time, frameIndex); Flush(); } void VmaRecorder::RecordDestroyAllocator(uint32_t frameIndex) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaDestroyAllocator\n", callParams.threadId, callParams.time, frameIndex); Flush(); } void VmaRecorder::RecordCreatePool(uint32_t frameIndex, const VmaPoolCreateInfo& createInfo, VmaPool pool) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaCreatePool,%u,%u,%llu,%llu,%llu,%u,%p\n", callParams.threadId, callParams.time, frameIndex, createInfo.memoryTypeIndex, createInfo.flags, createInfo.blockSize, (uint64_t)createInfo.minBlockCount, (uint64_t)createInfo.maxBlockCount, createInfo.frameInUseCount, pool); Flush(); } void VmaRecorder::RecordDestroyPool(uint32_t frameIndex, VmaPool pool) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaDestroyPool,%p\n", callParams.threadId, callParams.time, frameIndex, pool); Flush(); } void VmaRecorder::RecordAllocateMemory(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr(createInfo.flags, createInfo.pUserData); fprintf(m_File, "%u,%.3f,%u,vmaAllocateMemory,%llu,%llu,%u,%u,%u,%u,%u,%u,%p,%p,%s\n", callParams.threadId, callParams.time, frameIndex, vkMemReq.size, vkMemReq.alignment, vkMemReq.memoryTypeBits, createInfo.flags, createInfo.usage, createInfo.requiredFlags, createInfo.preferredFlags, createInfo.memoryTypeBits, createInfo.pool, allocation, userDataStr.GetString()); Flush(); } void VmaRecorder::RecordAllocateMemoryPages(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, const VmaAllocationCreateInfo& createInfo, uint64_t allocationCount, const VmaAllocation* pAllocations) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr(createInfo.flags, createInfo.pUserData); fprintf(m_File, "%u,%.3f,%u,vmaAllocateMemoryPages,%llu,%llu,%u,%u,%u,%u,%u,%u,%p,", callParams.threadId, callParams.time, frameIndex, vkMemReq.size, vkMemReq.alignment, vkMemReq.memoryTypeBits, createInfo.flags, createInfo.usage, createInfo.requiredFlags, createInfo.preferredFlags, createInfo.memoryTypeBits, createInfo.pool); PrintPointerList(allocationCount, pAllocations); fprintf(m_File, ",%s\n", userDataStr.GetString()); Flush(); } void VmaRecorder::RecordAllocateMemoryForBuffer(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr(createInfo.flags, createInfo.pUserData); fprintf(m_File, "%u,%.3f,%u,vmaAllocateMemoryForBuffer,%llu,%llu,%u,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\n", callParams.threadId, callParams.time, frameIndex, vkMemReq.size, vkMemReq.alignment, vkMemReq.memoryTypeBits, requiresDedicatedAllocation ? 1 : 0, prefersDedicatedAllocation ? 1 : 0, createInfo.flags, createInfo.usage, createInfo.requiredFlags, createInfo.preferredFlags, createInfo.memoryTypeBits, createInfo.pool, allocation, userDataStr.GetString()); Flush(); } void VmaRecorder::RecordAllocateMemoryForImage(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr(createInfo.flags, createInfo.pUserData); fprintf(m_File, "%u,%.3f,%u,vmaAllocateMemoryForImage,%llu,%llu,%u,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\n", callParams.threadId, callParams.time, frameIndex, vkMemReq.size, vkMemReq.alignment, vkMemReq.memoryTypeBits, requiresDedicatedAllocation ? 1 : 0, prefersDedicatedAllocation ? 1 : 0, createInfo.flags, createInfo.usage, createInfo.requiredFlags, createInfo.preferredFlags, createInfo.memoryTypeBits, createInfo.pool, allocation, userDataStr.GetString()); Flush(); } void VmaRecorder::RecordFreeMemory(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaFreeMemory,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordFreeMemoryPages(uint32_t frameIndex, uint64_t allocationCount, const VmaAllocation* pAllocations) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaFreeMemoryPages,", callParams.threadId, callParams.time, frameIndex); PrintPointerList(allocationCount, pAllocations); fprintf(m_File, "\n"); Flush(); } void VmaRecorder::RecordSetAllocationUserData(uint32_t frameIndex, VmaAllocation allocation, const void* pUserData) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr( allocation->IsUserDataString() ? VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT : 0, pUserData); fprintf(m_File, "%u,%.3f,%u,vmaSetAllocationUserData,%p,%s\n", callParams.threadId, callParams.time, frameIndex, allocation, userDataStr.GetString()); Flush(); } void VmaRecorder::RecordCreateLostAllocation(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaCreateLostAllocation,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordMapMemory(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaMapMemory,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordUnmapMemory(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaUnmapMemory,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordFlushAllocation(uint32_t frameIndex, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaFlushAllocation,%p,%llu,%llu\n", callParams.threadId, callParams.time, frameIndex, allocation, offset, size); Flush(); } void VmaRecorder::RecordInvalidateAllocation(uint32_t frameIndex, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaInvalidateAllocation,%p,%llu,%llu\n", callParams.threadId, callParams.time, frameIndex, allocation, offset, size); Flush(); } void VmaRecorder::RecordCreateBuffer(uint32_t frameIndex, const VkBufferCreateInfo& bufCreateInfo, const VmaAllocationCreateInfo& allocCreateInfo, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr(allocCreateInfo.flags, allocCreateInfo.pUserData); fprintf(m_File, "%u,%.3f,%u,vmaCreateBuffer,%u,%llu,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\n", callParams.threadId, callParams.time, frameIndex, bufCreateInfo.flags, bufCreateInfo.size, bufCreateInfo.usage, bufCreateInfo.sharingMode, allocCreateInfo.flags, allocCreateInfo.usage, allocCreateInfo.requiredFlags, allocCreateInfo.preferredFlags, allocCreateInfo.memoryTypeBits, allocCreateInfo.pool, allocation, userDataStr.GetString()); Flush(); } void VmaRecorder::RecordCreateImage(uint32_t frameIndex, const VkImageCreateInfo& imageCreateInfo, const VmaAllocationCreateInfo& allocCreateInfo, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); UserDataString userDataStr(allocCreateInfo.flags, allocCreateInfo.pUserData); fprintf(m_File, "%u,%.3f,%u,vmaCreateImage,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\n", callParams.threadId, callParams.time, frameIndex, imageCreateInfo.flags, imageCreateInfo.imageType, imageCreateInfo.format, imageCreateInfo.extent.width, imageCreateInfo.extent.height, imageCreateInfo.extent.depth, imageCreateInfo.mipLevels, imageCreateInfo.arrayLayers, imageCreateInfo.samples, imageCreateInfo.tiling, imageCreateInfo.usage, imageCreateInfo.sharingMode, imageCreateInfo.initialLayout, allocCreateInfo.flags, allocCreateInfo.usage, allocCreateInfo.requiredFlags, allocCreateInfo.preferredFlags, allocCreateInfo.memoryTypeBits, allocCreateInfo.pool, allocation, userDataStr.GetString()); Flush(); } void VmaRecorder::RecordDestroyBuffer(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaDestroyBuffer,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordDestroyImage(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaDestroyImage,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordTouchAllocation(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaTouchAllocation,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordGetAllocationInfo(uint32_t frameIndex, VmaAllocation allocation) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaGetAllocationInfo,%p\n", callParams.threadId, callParams.time, frameIndex, allocation); Flush(); } void VmaRecorder::RecordMakePoolAllocationsLost(uint32_t frameIndex, VmaPool pool) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaMakePoolAllocationsLost,%p\n", callParams.threadId, callParams.time, frameIndex, pool); Flush(); } void VmaRecorder::RecordDefragmentationBegin(uint32_t frameIndex, const VmaDefragmentationInfo2& info, VmaDefragmentationContext ctx) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaDefragmentationBegin,%u,", callParams.threadId, callParams.time, frameIndex, info.flags); PrintPointerList(info.allocationCount, info.pAllocations); fprintf(m_File, ","); PrintPointerList(info.poolCount, info.pPools); fprintf(m_File, ",%llu,%u,%llu,%u,%p,%p\n", info.maxCpuBytesToMove, info.maxCpuAllocationsToMove, info.maxGpuBytesToMove, info.maxGpuAllocationsToMove, info.commandBuffer, ctx); Flush(); } void VmaRecorder::RecordDefragmentationEnd(uint32_t frameIndex, VmaDefragmentationContext ctx) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaDefragmentationEnd,%p\n", callParams.threadId, callParams.time, frameIndex, ctx); Flush(); } void VmaRecorder::RecordSetPoolName(uint32_t frameIndex, VmaPool pool, const char* name) { CallParams callParams; GetBasicParams(callParams); VmaMutexLock lock(m_FileMutex, m_UseMutex); fprintf(m_File, "%u,%.3f,%u,vmaSetPoolName,%p,%s\n", callParams.threadId, callParams.time, frameIndex, pool, name != VMA_NULL ? name : ""); Flush(); } VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, const void* pUserData) { if (pUserData != VMA_NULL) { if ((allocFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0) { m_Str = (const char*)pUserData; } else { sprintf_s(m_PtrStr, "%p", pUserData); m_Str = m_PtrStr; } } else { m_Str = ""; } } void VmaRecorder::WriteConfiguration( const VkPhysicalDeviceProperties& devProps, const VkPhysicalDeviceMemoryProperties& memProps, uint32_t vulkanApiVersion, bool dedicatedAllocationExtensionEnabled, bool bindMemory2ExtensionEnabled, bool memoryBudgetExtensionEnabled) { fprintf(m_File, "Config,Begin\n"); fprintf(m_File, "VulkanApiVersion,%u,%u\n", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion)); fprintf(m_File, "PhysicalDevice,apiVersion,%u\n", devProps.apiVersion); fprintf(m_File, "PhysicalDevice,driverVersion,%u\n", devProps.driverVersion); fprintf(m_File, "PhysicalDevice,vendorID,%u\n", devProps.vendorID); fprintf(m_File, "PhysicalDevice,deviceID,%u\n", devProps.deviceID); fprintf(m_File, "PhysicalDevice,deviceType,%u\n", devProps.deviceType); fprintf(m_File, "PhysicalDevice,deviceName,%s\n", devProps.deviceName); fprintf(m_File, "PhysicalDeviceLimits,maxMemoryAllocationCount,%u\n", devProps.limits.maxMemoryAllocationCount); fprintf(m_File, "PhysicalDeviceLimits,bufferImageGranularity,%llu\n", devProps.limits.bufferImageGranularity); fprintf(m_File, "PhysicalDeviceLimits,nonCoherentAtomSize,%llu\n", devProps.limits.nonCoherentAtomSize); fprintf(m_File, "PhysicalDeviceMemory,HeapCount,%u\n", memProps.memoryHeapCount); for (uint32_t i = 0; i < memProps.memoryHeapCount; ++i) { fprintf(m_File, "PhysicalDeviceMemory,Heap,%u,size,%llu\n", i, memProps.memoryHeaps[i].size); fprintf(m_File, "PhysicalDeviceMemory,Heap,%u,flags,%u\n", i, memProps.memoryHeaps[i].flags); } fprintf(m_File, "PhysicalDeviceMemory,TypeCount,%u\n", memProps.memoryTypeCount); for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { fprintf(m_File, "PhysicalDeviceMemory,Type,%u,heapIndex,%u\n", i, memProps.memoryTypes[i].heapIndex); fprintf(m_File, "PhysicalDeviceMemory,Type,%u,propertyFlags,%u\n", i, memProps.memoryTypes[i].propertyFlags); } fprintf(m_File, "Extension,VK_KHR_dedicated_allocation,%u\n", dedicatedAllocationExtensionEnabled ? 1 : 0); fprintf(m_File, "Extension,VK_KHR_bind_memory2,%u\n", bindMemory2ExtensionEnabled ? 1 : 0); fprintf(m_File, "Extension,VK_EXT_memory_budget,%u\n", memoryBudgetExtensionEnabled ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_ALWAYS_DEDICATED_MEMORY,%u\n", VMA_DEBUG_ALWAYS_DEDICATED_MEMORY ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_ALIGNMENT,%llu\n", (VkDeviceSize)VMA_DEBUG_ALIGNMENT); fprintf(m_File, "Macro,VMA_DEBUG_MARGIN,%llu\n", (VkDeviceSize)VMA_DEBUG_MARGIN); fprintf(m_File, "Macro,VMA_DEBUG_INITIALIZE_ALLOCATIONS,%u\n", VMA_DEBUG_INITIALIZE_ALLOCATIONS ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_DETECT_CORRUPTION,%u\n", VMA_DEBUG_DETECT_CORRUPTION ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_GLOBAL_MUTEX,%u\n", VMA_DEBUG_GLOBAL_MUTEX ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY,%llu\n", (VkDeviceSize)VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY); fprintf(m_File, "Macro,VMA_SMALL_HEAP_MAX_SIZE,%llu\n", (VkDeviceSize)VMA_SMALL_HEAP_MAX_SIZE); fprintf(m_File, "Macro,VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE,%llu\n", (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE); fprintf(m_File, "Config,End\n"); } void VmaRecorder::GetBasicParams(CallParams& outParams) { outParams.threadId = GetCurrentThreadId(); LARGE_INTEGER counter; QueryPerformanceCounter(&counter); outParams.time = (double)(counter.QuadPart - m_StartCounter) / (double)m_Freq; } void VmaRecorder::PrintPointerList(uint64_t count, const VmaAllocation* pItems) { if (count) { fprintf(m_File, "%p", pItems[0]); for (uint64_t i = 1; i < count; ++i) { fprintf(m_File, " %p", pItems[i]); } } } void VmaRecorder::Flush() { if ((m_Flags & VMA_RECORD_FLUSH_AFTER_CALL_BIT) != 0) { fflush(m_File); } } #endif // #if VMA_RECORDING_ENABLED //////////////////////////////////////////////////////////////////////////////// // VmaAllocationObjectAllocator VmaAllocationObjectAllocator::VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks) : m_Allocator(pAllocationCallbacks, 1024) { } VmaAllocation VmaAllocationObjectAllocator::Allocate() { VmaMutexLock mutexLock(m_Mutex); return m_Allocator.Alloc(); } void VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc) { VmaMutexLock mutexLock(m_Mutex); m_Allocator.Free(hAlloc); } //////////////////////////////////////////////////////////////////////////////// // VmaAllocator_T VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : m_UseMutex((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT) == 0), m_VulkanApiVersion(pCreateInfo->vulkanApiVersion != 0 ? pCreateInfo->vulkanApiVersion : VK_API_VERSION_1_0), m_UseKhrDedicatedAllocation((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0), m_UseKhrBindMemory2((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0), m_UseExtMemoryBudget((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0), m_hDevice(pCreateInfo->device), m_hInstance(pCreateInfo->instance), m_AllocationCallbacksSpecified(pCreateInfo->pAllocationCallbacks != VMA_NULL), m_AllocationCallbacks(pCreateInfo->pAllocationCallbacks ? *pCreateInfo->pAllocationCallbacks : VmaEmptyAllocationCallbacks), m_AllocationObjectAllocator(&m_AllocationCallbacks), m_HeapSizeLimitMask(0), m_PreferredLargeHeapBlockSize(0), m_PhysicalDevice(pCreateInfo->physicalDevice), m_CurrentFrameIndex(0), m_GpuDefragmentationMemoryTypeBits(UINT32_MAX), m_Pools(VmaStlAllocator<VmaPool>(GetAllocationCallbacks())), m_NextPoolId(0) #if VMA_RECORDING_ENABLED , m_pRecorder(VMA_NULL) #endif { if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { m_UseKhrDedicatedAllocation = false; m_UseKhrBindMemory2 = false; } if (VMA_DEBUG_DETECT_CORRUPTION) { // Needs to be multiply of uint32_t size because we are going to write VMA_CORRUPTION_DETECTION_MAGIC_VALUE to it. VMA_ASSERT(VMA_DEBUG_MARGIN % sizeof(uint32_t) == 0); } VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device); if (m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) { #if !(VMA_DEDICATED_ALLOCATION) if ((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) { VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros."); } #endif #if !(VMA_BIND_MEMORY2) if ((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0) { VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT set but required extension is disabled by preprocessor macros."); } #endif } #if !(VMA_MEMORY_BUDGET) if ((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) { VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT set but required extension is disabled by preprocessor macros."); } #endif #if VMA_VULKAN_VERSION < 1001000 if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_1 but required Vulkan version is disabled by preprocessor macros."); } #endif memset(&m_DeviceMemoryCallbacks, 0, sizeof(m_DeviceMemoryCallbacks)); memset(&m_PhysicalDeviceProperties, 0, sizeof(m_PhysicalDeviceProperties)); memset(&m_MemProps, 0, sizeof(m_MemProps)); memset(&m_pBlockVectors, 0, sizeof(m_pBlockVectors)); memset(&m_pDedicatedAllocations, 0, sizeof(m_pDedicatedAllocations)); memset(&m_VulkanFunctions, 0, sizeof(m_VulkanFunctions)); if (pCreateInfo->pDeviceMemoryCallbacks != VMA_NULL) { m_DeviceMemoryCallbacks.pfnAllocate = pCreateInfo->pDeviceMemoryCallbacks->pfnAllocate; m_DeviceMemoryCallbacks.pfnFree = pCreateInfo->pDeviceMemoryCallbacks->pfnFree; } ImportVulkanFunctions(pCreateInfo->pVulkanFunctions); (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties); (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps); VMA_ASSERT(VmaIsPow2(VMA_DEBUG_ALIGNMENT)); VMA_ASSERT(VmaIsPow2(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY)); VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.bufferImageGranularity)); VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.nonCoherentAtomSize)); m_PreferredLargeHeapBlockSize = (pCreateInfo->preferredLargeHeapBlockSize != 0) ? pCreateInfo->preferredLargeHeapBlockSize : static_cast<VkDeviceSize>(VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE); if (pCreateInfo->pHeapSizeLimit != VMA_NULL) { for (uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) { const VkDeviceSize limit = pCreateInfo->pHeapSizeLimit[heapIndex]; if (limit != VK_WHOLE_SIZE) { m_HeapSizeLimitMask |= 1u << heapIndex; if (limit < m_MemProps.memoryHeaps[heapIndex].size) { m_MemProps.memoryHeaps[heapIndex].size = limit; } } } } for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(memTypeIndex); m_pBlockVectors[memTypeIndex] = vma_new(this, VmaBlockVector)( this, VK_NULL_HANDLE, // hParentPool memTypeIndex, preferredBlockSize, 0, SIZE_MAX, GetBufferImageGranularity(), pCreateInfo->frameInUseCount, false, // explicitBlockSize false); // linearAlgorithm // No need to call m_pBlockVectors[memTypeIndex][blockVectorTypeIndex]->CreateMinBlocks here, // becase minBlockCount is 0. m_pDedicatedAllocations[memTypeIndex] = vma_new(this, AllocationVectorType)(VmaStlAllocator<VmaAllocation>(GetAllocationCallbacks())); } } VkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo) { VkResult res = VK_SUCCESS; if (pCreateInfo->pRecordSettings != VMA_NULL && !VmaStrIsEmpty(pCreateInfo->pRecordSettings->pFilePath)) { #if VMA_RECORDING_ENABLED m_pRecorder = vma_new(this, VmaRecorder)(); res = m_pRecorder->Init(*pCreateInfo->pRecordSettings, m_UseMutex); if (res != VK_SUCCESS) { return res; } m_pRecorder->WriteConfiguration( m_PhysicalDeviceProperties, m_MemProps, m_VulkanApiVersion, m_UseKhrDedicatedAllocation, m_UseKhrBindMemory2, m_UseExtMemoryBudget); m_pRecorder->RecordCreateAllocator(GetCurrentFrameIndex()); #else VMA_ASSERT(0 && "VmaAllocatorCreateInfo::pRecordSettings used, but not supported due to VMA_RECORDING_ENABLED not defined to 1."); return VK_ERROR_FEATURE_NOT_PRESENT; #endif } #if VMA_MEMORY_BUDGET if (m_UseExtMemoryBudget) { UpdateVulkanBudget(); } #endif // #if VMA_MEMORY_BUDGET return res; } VmaAllocator_T::~VmaAllocator_T() { #if VMA_RECORDING_ENABLED if (m_pRecorder != VMA_NULL) { m_pRecorder->RecordDestroyAllocator(GetCurrentFrameIndex()); vma_delete(this, m_pRecorder); } #endif VMA_ASSERT(m_Pools.empty()); for (size_t i = GetMemoryTypeCount(); i--; ) { if (m_pDedicatedAllocations[i] != VMA_NULL && !m_pDedicatedAllocations[i]->empty()) { VMA_ASSERT(0 && "Unfreed dedicated allocations found."); } vma_delete(this, m_pDedicatedAllocations[i]); vma_delete(this, m_pBlockVectors[i]); } } void VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions) { #if VMA_STATIC_VULKAN_FUNCTIONS == 1 m_VulkanFunctions.vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetPhysicalDeviceProperties; m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)vkGetPhysicalDeviceMemoryProperties; m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; m_VulkanFunctions.vkFreeMemory = (PFN_vkFreeMemory)vkFreeMemory; m_VulkanFunctions.vkMapMemory = (PFN_vkMapMemory)vkMapMemory; m_VulkanFunctions.vkUnmapMemory = (PFN_vkUnmapMemory)vkUnmapMemory; m_VulkanFunctions.vkFlushMappedMemoryRanges = (PFN_vkFlushMappedMemoryRanges)vkFlushMappedMemoryRanges; m_VulkanFunctions.vkInvalidateMappedMemoryRanges = (PFN_vkInvalidateMappedMemoryRanges)vkInvalidateMappedMemoryRanges; m_VulkanFunctions.vkBindBufferMemory = (PFN_vkBindBufferMemory)vkBindBufferMemory; m_VulkanFunctions.vkBindImageMemory = (PFN_vkBindImageMemory)vkBindImageMemory; m_VulkanFunctions.vkGetBufferMemoryRequirements = (PFN_vkGetBufferMemoryRequirements)vkGetBufferMemoryRequirements; m_VulkanFunctions.vkGetImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)vkGetImageMemoryRequirements; m_VulkanFunctions.vkCreateBuffer = (PFN_vkCreateBuffer)vkCreateBuffer; m_VulkanFunctions.vkDestroyBuffer = (PFN_vkDestroyBuffer)vkDestroyBuffer; m_VulkanFunctions.vkCreateImage = (PFN_vkCreateImage)vkCreateImage; m_VulkanFunctions.vkDestroyImage = (PFN_vkDestroyImage)vkDestroyImage; m_VulkanFunctions.vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)vkCmdCopyBuffer; #if VMA_VULKAN_VERSION >= 1001000 if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VMA_ASSERT(m_hInstance != VK_NULL_HANDLE); m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = (PFN_vkGetBufferMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetBufferMemoryRequirements2"); m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetImageMemoryRequirements2"); m_VulkanFunctions.vkBindBufferMemory2KHR = (PFN_vkBindBufferMemory2KHR)vkGetDeviceProcAddr(m_hDevice, "vkBindBufferMemory2"); m_VulkanFunctions.vkBindImageMemory2KHR = (PFN_vkBindImageMemory2KHR)vkGetDeviceProcAddr(m_hDevice, "vkBindImageMemory2"); m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2KHR)vkGetInstanceProcAddr(m_hInstance, "vkGetPhysicalDeviceMemoryProperties2"); } #endif #if VMA_DEDICATED_ALLOCATION if (m_UseKhrDedicatedAllocation) { m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = (PFN_vkGetBufferMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetBufferMemoryRequirements2KHR"); m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetImageMemoryRequirements2KHR"); } #endif #if VMA_BIND_MEMORY2 if (m_UseKhrBindMemory2) { m_VulkanFunctions.vkBindBufferMemory2KHR = (PFN_vkBindBufferMemory2KHR)vkGetDeviceProcAddr(m_hDevice, "vkBindBufferMemory2KHR"); m_VulkanFunctions.vkBindImageMemory2KHR = (PFN_vkBindImageMemory2KHR)vkGetDeviceProcAddr(m_hDevice, "vkBindImageMemory2KHR"); } #endif // #if VMA_BIND_MEMORY2 #if VMA_MEMORY_BUDGET if (m_UseExtMemoryBudget && m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) { VMA_ASSERT(m_hInstance != VK_NULL_HANDLE); m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2KHR)vkGetInstanceProcAddr(m_hInstance, "vkGetPhysicalDeviceMemoryProperties2KHR"); } #endif // #if VMA_MEMORY_BUDGET #endif // #if VMA_STATIC_VULKAN_FUNCTIONS == 1 #define VMA_COPY_IF_NOT_NULL(funcName) \ if(pVulkanFunctions->funcName != VMA_NULL) m_VulkanFunctions.funcName = pVulkanFunctions->funcName; if (pVulkanFunctions != VMA_NULL) { VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties); VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties); VMA_COPY_IF_NOT_NULL(vkAllocateMemory); VMA_COPY_IF_NOT_NULL(vkFreeMemory); VMA_COPY_IF_NOT_NULL(vkMapMemory); VMA_COPY_IF_NOT_NULL(vkUnmapMemory); VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges); VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges); VMA_COPY_IF_NOT_NULL(vkBindBufferMemory); VMA_COPY_IF_NOT_NULL(vkBindImageMemory); VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements); VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements); VMA_COPY_IF_NOT_NULL(vkCreateBuffer); VMA_COPY_IF_NOT_NULL(vkDestroyBuffer); VMA_COPY_IF_NOT_NULL(vkCreateImage); VMA_COPY_IF_NOT_NULL(vkDestroyImage); VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer); #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR); VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR); #endif #if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 VMA_COPY_IF_NOT_NULL(vkBindBufferMemory2KHR); VMA_COPY_IF_NOT_NULL(vkBindImageMemory2KHR); #endif #if VMA_MEMORY_BUDGET VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR); #endif } #undef VMA_COPY_IF_NOT_NULL // If these asserts are hit, you must either #define VMA_STATIC_VULKAN_FUNCTIONS 1 // or pass valid pointers as VmaAllocatorCreateInfo::pVulkanFunctions. VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkAllocateMemory != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkFreeMemory != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkMapMemory != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkUnmapMemory != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkFlushMappedMemoryRanges != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkInvalidateMappedMemoryRanges != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkCreateBuffer != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkDestroyBuffer != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkCreateImage != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkDestroyImage != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkCmdCopyBuffer != VMA_NULL); #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrDedicatedAllocation) { VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements2KHR != VMA_NULL); } #endif #if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrBindMemory2) { VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL); } #endif #if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 if (m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL); } #endif } VkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex) { const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; const bool isSmallHeap = heapSize <= VMA_SMALL_HEAP_MAX_SIZE; return VmaAlignUp(isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize, (VkDeviceSize)32); } VkResult VmaAllocator_T::AllocateMemoryOfType( VkDeviceSize size, VkDeviceSize alignment, bool dedicatedAllocation, VkBuffer dedicatedBuffer, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, VmaSuballocationType suballocType, size_t allocationCount, VmaAllocation* pAllocations) { VMA_ASSERT(pAllocations != VMA_NULL); VMA_DEBUG_LOG(" AllocateMemory: MemoryTypeIndex=%u, AllocationCount=%zu, Size=%llu", memTypeIndex, allocationCount, size); VmaAllocationCreateInfo finalCreateInfo = createInfo; // If memory type is not HOST_VISIBLE, disable MAPPED. if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 && (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) { finalCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; } // If memory is lazily allocated, it should be always dedicated. if (finalCreateInfo.usage == VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED) { finalCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; } VmaBlockVector* const blockVector = m_pBlockVectors[memTypeIndex]; VMA_ASSERT(blockVector); const VkDeviceSize preferredBlockSize = blockVector->GetPreferredBlockSize(); bool preferDedicatedMemory = VMA_DEBUG_ALWAYS_DEDICATED_MEMORY || dedicatedAllocation || // Heuristics: Allocate dedicated memory if requested size if greater than half of preferred block size. size > preferredBlockSize / 2; if (preferDedicatedMemory && (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0 && finalCreateInfo.pool == VK_NULL_HANDLE) { finalCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; } if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0) { if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } else { return AllocateDedicatedMemory( size, suballocType, memTypeIndex, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, finalCreateInfo.pUserData, dedicatedBuffer, dedicatedImage, allocationCount, pAllocations); } } else { VkResult res = blockVector->Allocate( m_CurrentFrameIndex.load(), size, alignment, finalCreateInfo, suballocType, allocationCount, pAllocations); if (res == VK_SUCCESS) { return res; } // 5. Try dedicated memory. if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } else { res = AllocateDedicatedMemory( size, suballocType, memTypeIndex, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, finalCreateInfo.pUserData, dedicatedBuffer, dedicatedImage, allocationCount, pAllocations); if (res == VK_SUCCESS) { // Succeeded: AllocateDedicatedMemory function already filld pMemory, nothing more to do here. VMA_DEBUG_LOG(" Allocated as DedicatedMemory"); return VK_SUCCESS; } else { // Everything failed: Return error code. VMA_DEBUG_LOG(" vkAllocateMemory FAILED"); return res; } } } } VkResult VmaAllocator_T::AllocateDedicatedMemory( VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, bool withinBudget, bool map, bool isUserDataString, void* pUserData, VkBuffer dedicatedBuffer, VkImage dedicatedImage, size_t allocationCount, VmaAllocation* pAllocations) { VMA_ASSERT(allocationCount > 0 && pAllocations); if (withinBudget) { const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); VmaBudget heapBudget = {}; GetBudget(&heapBudget, heapIndex, 1); if (heapBudget.usage + size * allocationCount > heapBudget.budget) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } } VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = memTypeIndex; allocInfo.allocationSize = size; #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 VkMemoryDedicatedAllocateInfoKHR dedicatedAllocInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; if (m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { if (dedicatedBuffer != VK_NULL_HANDLE) { VMA_ASSERT(dedicatedImage == VK_NULL_HANDLE); dedicatedAllocInfo.buffer = dedicatedBuffer; allocInfo.pNext = &dedicatedAllocInfo; } else if (dedicatedImage != VK_NULL_HANDLE) { dedicatedAllocInfo.image = dedicatedImage; allocInfo.pNext = &dedicatedAllocInfo; } } #endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 size_t allocIndex; VkResult res = VK_SUCCESS; for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) { res = AllocateDedicatedMemoryPage( size, suballocType, memTypeIndex, allocInfo, map, isUserDataString, pUserData, pAllocations + allocIndex); if (res != VK_SUCCESS) { break; } } if (res == VK_SUCCESS) { // Register them in m_pDedicatedAllocations. { VmaMutexLockWrite lock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex); AllocationVectorType* pDedicatedAllocations = m_pDedicatedAllocations[memTypeIndex]; VMA_ASSERT(pDedicatedAllocations); for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) { VmaVectorInsertSorted<VmaPointerLess>(*pDedicatedAllocations, pAllocations[allocIndex]); } } VMA_DEBUG_LOG(" Allocated DedicatedMemory Count=%zu, MemoryTypeIndex=#%u", allocationCount, memTypeIndex); } else { // Free all already created allocations. while (allocIndex--) { VmaAllocation currAlloc = pAllocations[allocIndex]; VkDeviceMemory hMemory = currAlloc->GetMemory(); /* There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory before vkFreeMemory. if(currAlloc->GetMappedData() != VMA_NULL) { (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); } */ FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), currAlloc->GetSize()); currAlloc->SetUserData(this, VMA_NULL); currAlloc->Dtor(); m_AllocationObjectAllocator.Free(currAlloc); } memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); } return res; } VkResult VmaAllocator_T::AllocateDedicatedMemoryPage( VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, const VkMemoryAllocateInfo& allocInfo, bool map, bool isUserDataString, void* pUserData, VmaAllocation* pAllocation) { VkDeviceMemory hMemory = VK_NULL_HANDLE; VkResult res = AllocateVulkanMemory(&allocInfo, &hMemory); if (res < 0) { VMA_DEBUG_LOG(" vkAllocateMemory FAILED"); return res; } void* pMappedData = VMA_NULL; if (map) { res = (*m_VulkanFunctions.vkMapMemory)( m_hDevice, hMemory, 0, VK_WHOLE_SIZE, 0, &pMappedData); if (res < 0) { VMA_DEBUG_LOG(" vkMapMemory FAILED"); FreeVulkanMemory(memTypeIndex, size, hMemory); return res; } } *pAllocation = m_AllocationObjectAllocator.Allocate(); (*pAllocation)->Ctor(m_CurrentFrameIndex.load(), isUserDataString); (*pAllocation)->InitDedicatedAllocation(memTypeIndex, hMemory, suballocType, pMappedData, size); (*pAllocation)->SetUserData(this, pUserData); m_Budget.AddAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), size); if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) { FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); } return VK_SUCCESS; } void VmaAllocator_T::GetBufferMemoryRequirements( VkBuffer hBuffer, VkMemoryRequirements& memReq, bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const { #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 if (m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkBufferMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR }; memReqInfo.buffer = hBuffer; VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; memReq2.pNext = &memDedicatedReq; (*m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); memReq = memReq2.memoryRequirements; requiresDedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE); prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else #endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 { (*m_VulkanFunctions.vkGetBufferMemoryRequirements)(m_hDevice, hBuffer, &memReq); requiresDedicatedAllocation = false; prefersDedicatedAllocation = false; } } void VmaAllocator_T::GetImageMemoryRequirements( VkImage hImage, VkMemoryRequirements& memReq, bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const { #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 if (m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkImageMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR }; memReqInfo.image = hImage; VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; memReq2.pNext = &memDedicatedReq; (*m_VulkanFunctions.vkGetImageMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); memReq = memReq2.memoryRequirements; requiresDedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE); prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else #endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 { (*m_VulkanFunctions.vkGetImageMemoryRequirements)(m_hDevice, hImage, &memReq); requiresDedicatedAllocation = false; prefersDedicatedAllocation = false; } } VkResult VmaAllocator_T::AllocateMemory( const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, VkBuffer dedicatedBuffer, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, size_t allocationCount, VmaAllocation* pAllocations) { memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); VMA_ASSERT(VmaIsPow2(vkMemReq.alignment)); if (vkMemReq.size == 0) { return VK_ERROR_VALIDATION_FAILED_EXT; } if ((createInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 && (createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) { VMA_ASSERT(0 && "Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT together with VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT makes no sense."); return VK_ERROR_OUT_OF_DEVICE_MEMORY; } if ((createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 && (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0) { VMA_ASSERT(0 && "Specifying VMA_ALLOCATION_CREATE_MAPPED_BIT together with VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT is invalid."); return VK_ERROR_OUT_OF_DEVICE_MEMORY; } if (requiresDedicatedAllocation) { if ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) { VMA_ASSERT(0 && "VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT specified while dedicated allocation is required."); return VK_ERROR_OUT_OF_DEVICE_MEMORY; } if (createInfo.pool != VK_NULL_HANDLE) { VMA_ASSERT(0 && "Pool specified while dedicated allocation is required."); return VK_ERROR_OUT_OF_DEVICE_MEMORY; } } if ((createInfo.pool != VK_NULL_HANDLE) && ((createInfo.flags & (VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT)) != 0)) { VMA_ASSERT(0 && "Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT when pool != null is invalid."); return VK_ERROR_OUT_OF_DEVICE_MEMORY; } if (createInfo.pool != VK_NULL_HANDLE) { const VkDeviceSize alignmentForPool = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(createInfo.pool->m_BlockVector.GetMemoryTypeIndex())); VmaAllocationCreateInfo createInfoForPool = createInfo; // If memory type is not HOST_VISIBLE, disable MAPPED. if ((createInfoForPool.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 && (m_MemProps.memoryTypes[createInfo.pool->m_BlockVector.GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) { createInfoForPool.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; } return createInfo.pool->m_BlockVector.Allocate( m_CurrentFrameIndex.load(), vkMemReq.size, alignmentForPool, createInfoForPool, suballocType, allocationCount, pAllocations); } else { // Bit mask of memory Vulkan types acceptable for this allocation. uint32_t memoryTypeBits = vkMemReq.memoryTypeBits; uint32_t memTypeIndex = UINT32_MAX; VkResult res = vmaFindMemoryTypeIndex(this, memoryTypeBits, &createInfo, &memTypeIndex); if (res == VK_SUCCESS) { VkDeviceSize alignmentForMemType = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(memTypeIndex)); res = AllocateMemoryOfType( vkMemReq.size, alignmentForMemType, requiresDedicatedAllocation || prefersDedicatedAllocation, dedicatedBuffer, dedicatedImage, createInfo, memTypeIndex, suballocType, allocationCount, pAllocations); // Succeeded on first try. if (res == VK_SUCCESS) { return res; } // Allocation from this memory type failed. Try other compatible memory types. else { for (;;) { // Remove old memTypeIndex from list of possibilities. memoryTypeBits &= ~(1u << memTypeIndex); // Find alternative memTypeIndex. res = vmaFindMemoryTypeIndex(this, memoryTypeBits, &createInfo, &memTypeIndex); if (res == VK_SUCCESS) { alignmentForMemType = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(memTypeIndex)); res = AllocateMemoryOfType( vkMemReq.size, alignmentForMemType, requiresDedicatedAllocation || prefersDedicatedAllocation, dedicatedBuffer, dedicatedImage, createInfo, memTypeIndex, suballocType, allocationCount, pAllocations); // Allocation from this alternative memory type succeeded. if (res == VK_SUCCESS) { return res; } // else: Allocation from this memory type failed. Try next one - next loop iteration. } // No other matching memory type index could be found. else { // Not returning res, which is VK_ERROR_FEATURE_NOT_PRESENT, because we already failed to allocate once. return VK_ERROR_OUT_OF_DEVICE_MEMORY; } } } } // Can't find any single memory type maching requirements. res is VK_ERROR_FEATURE_NOT_PRESENT. else return res; } } void VmaAllocator_T::FreeMemory( size_t allocationCount, const VmaAllocation* pAllocations) { VMA_ASSERT(pAllocations); for (size_t allocIndex = allocationCount; allocIndex--; ) { VmaAllocation allocation = pAllocations[allocIndex]; if (allocation != VK_NULL_HANDLE) { if (TouchAllocation(allocation)) { if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) { FillAllocation(allocation, VMA_ALLOCATION_FILL_PATTERN_DESTROYED); } switch (allocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaBlockVector* pBlockVector = VMA_NULL; VmaPool hPool = allocation->GetBlock()->GetParentPool(); if (hPool != VK_NULL_HANDLE) { pBlockVector = &hPool->m_BlockVector; } else { const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); pBlockVector = m_pBlockVectors[memTypeIndex]; } pBlockVector->Free(allocation); } break; case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: FreeDedicatedMemory(allocation); break; default: VMA_ASSERT(0); } } // Do this regardless of whether the allocation is lost. Lost allocations still account to Budget.AllocationBytes. m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(allocation->GetMemoryTypeIndex()), allocation->GetSize()); allocation->SetUserData(this, VMA_NULL); allocation->Dtor(); m_AllocationObjectAllocator.Free(allocation); } } } VkResult VmaAllocator_T::ResizeAllocation( const VmaAllocation alloc, VkDeviceSize newSize) { // This function is deprecated and so it does nothing. It's left for backward compatibility. if (newSize == 0 || alloc->GetLastUseFrameIndex() == VMA_FRAME_INDEX_LOST) { return VK_ERROR_VALIDATION_FAILED_EXT; } if (newSize == alloc->GetSize()) { return VK_SUCCESS; } return VK_ERROR_OUT_OF_POOL_MEMORY; } void VmaAllocator_T::CalculateStats(VmaStats* pStats) { // Initialize. InitStatInfo(pStats->total); for (size_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) InitStatInfo(pStats->memoryType[i]); for (size_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) InitStatInfo(pStats->memoryHeap[i]); // Process default pools. for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex]; VMA_ASSERT(pBlockVector); pBlockVector->AddStats(pStats); } // Process custom pools. { VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex); for (size_t poolIndex = 0, poolCount = m_Pools.size(); poolIndex < poolCount; ++poolIndex) { m_Pools[poolIndex]->m_BlockVector.AddStats(pStats); } } // Process dedicated allocations. for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { const uint32_t memHeapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); VmaMutexLockRead dedicatedAllocationsLock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex); AllocationVectorType* const pDedicatedAllocVector = m_pDedicatedAllocations[memTypeIndex]; VMA_ASSERT(pDedicatedAllocVector); for (size_t allocIndex = 0, allocCount = pDedicatedAllocVector->size(); allocIndex < allocCount; ++allocIndex) { VmaStatInfo allocationStatInfo; (*pDedicatedAllocVector)[allocIndex]->DedicatedAllocCalcStatsInfo(allocationStatInfo); VmaAddStatInfo(pStats->total, allocationStatInfo); VmaAddStatInfo(pStats->memoryType[memTypeIndex], allocationStatInfo); VmaAddStatInfo(pStats->memoryHeap[memHeapIndex], allocationStatInfo); } } // Postprocess. VmaPostprocessCalcStatInfo(pStats->total); for (size_t i = 0; i < GetMemoryTypeCount(); ++i) VmaPostprocessCalcStatInfo(pStats->memoryType[i]); for (size_t i = 0; i < GetMemoryHeapCount(); ++i) VmaPostprocessCalcStatInfo(pStats->memoryHeap[i]); } void VmaAllocator_T::GetBudget(VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount) { #if VMA_MEMORY_BUDGET if (m_UseExtMemoryBudget) { if (m_Budget.m_OperationsSinceBudgetFetch < 30) { VmaMutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex); for (uint32_t i = 0; i < heapCount; ++i, ++outBudget) { const uint32_t heapIndex = firstHeap + i; outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex]; outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; if (m_Budget.m_VulkanUsage[heapIndex] + outBudget->blockBytes > m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]) { outBudget->usage = m_Budget.m_VulkanUsage[heapIndex] + outBudget->blockBytes - m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; } else { outBudget->usage = 0; } // Have to take MIN with heap size because explicit HeapSizeLimit is included in it. outBudget->budget = VMA_MIN( m_Budget.m_VulkanBudget[heapIndex], m_MemProps.memoryHeaps[heapIndex].size); } } else { UpdateVulkanBudget(); // Outside of mutex lock GetBudget(outBudget, firstHeap, heapCount); // Recursion } } else #endif { for (uint32_t i = 0; i < heapCount; ++i, ++outBudget) { const uint32_t heapIndex = firstHeap + i; outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex]; outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; outBudget->usage = outBudget->blockBytes; outBudget->budget = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. } } } static const uint32_t VMA_VENDOR_ID_AMD = 4098; VkResult VmaAllocator_T::DefragmentationBegin( const VmaDefragmentationInfo2& info, VmaDefragmentationStats* pStats, VmaDefragmentationContext* pContext) { if (info.pAllocationsChanged != VMA_NULL) { memset(info.pAllocationsChanged, 0, info.allocationCount * sizeof(VkBool32)); } *pContext = vma_new(this, VmaDefragmentationContext_T)( this, m_CurrentFrameIndex.load(), info.flags, pStats); (*pContext)->AddPools(info.poolCount, info.pPools); (*pContext)->AddAllocations( info.allocationCount, info.pAllocations, info.pAllocationsChanged); VkResult res = (*pContext)->Defragment( info.maxCpuBytesToMove, info.maxCpuAllocationsToMove, info.maxGpuBytesToMove, info.maxGpuAllocationsToMove, info.commandBuffer, pStats); if (res != VK_NOT_READY) { vma_delete(this, *pContext); *pContext = VMA_NULL; } return res; } VkResult VmaAllocator_T::DefragmentationEnd( VmaDefragmentationContext context) { vma_delete(this, context); return VK_SUCCESS; } void VmaAllocator_T::GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo) { if (hAllocation->CanBecomeLost()) { /* Warning: This is a carefully designed algorithm. Do not modify unless you really know what you're doing :) */ const uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load(); uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex(); for (;;) { if (localLastUseFrameIndex == VMA_FRAME_INDEX_LOST) { pAllocationInfo->memoryType = UINT32_MAX; pAllocationInfo->deviceMemory = VK_NULL_HANDLE; pAllocationInfo->offset = 0; pAllocationInfo->size = hAllocation->GetSize(); pAllocationInfo->pMappedData = VMA_NULL; pAllocationInfo->pUserData = hAllocation->GetUserData(); return; } else if (localLastUseFrameIndex == localCurrFrameIndex) { pAllocationInfo->memoryType = hAllocation->GetMemoryTypeIndex(); pAllocationInfo->deviceMemory = hAllocation->GetMemory(); pAllocationInfo->offset = hAllocation->GetOffset(); pAllocationInfo->size = hAllocation->GetSize(); pAllocationInfo->pMappedData = VMA_NULL; pAllocationInfo->pUserData = hAllocation->GetUserData(); return; } else // Last use time earlier than current time. { if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) { localLastUseFrameIndex = localCurrFrameIndex; } } } } else { #if VMA_STATS_STRING_ENABLED uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load(); uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex(); for (;;) { VMA_ASSERT(localLastUseFrameIndex != VMA_FRAME_INDEX_LOST); if (localLastUseFrameIndex == localCurrFrameIndex) { break; } else // Last use time earlier than current time. { if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) { localLastUseFrameIndex = localCurrFrameIndex; } } } #endif pAllocationInfo->memoryType = hAllocation->GetMemoryTypeIndex(); pAllocationInfo->deviceMemory = hAllocation->GetMemory(); pAllocationInfo->offset = hAllocation->GetOffset(); pAllocationInfo->size = hAllocation->GetSize(); pAllocationInfo->pMappedData = hAllocation->GetMappedData(); pAllocationInfo->pUserData = hAllocation->GetUserData(); } } bool VmaAllocator_T::TouchAllocation(VmaAllocation hAllocation) { // This is a stripped-down version of VmaAllocator_T::GetAllocationInfo. if (hAllocation->CanBecomeLost()) { uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load(); uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex(); for (;;) { if (localLastUseFrameIndex == VMA_FRAME_INDEX_LOST) { return false; } else if (localLastUseFrameIndex == localCurrFrameIndex) { return true; } else // Last use time earlier than current time. { if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) { localLastUseFrameIndex = localCurrFrameIndex; } } } } else { #if VMA_STATS_STRING_ENABLED uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load(); uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex(); for (;;) { VMA_ASSERT(localLastUseFrameIndex != VMA_FRAME_INDEX_LOST); if (localLastUseFrameIndex == localCurrFrameIndex) { break; } else // Last use time earlier than current time. { if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) { localLastUseFrameIndex = localCurrFrameIndex; } } } #endif return true; } } VkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool) { VMA_DEBUG_LOG(" CreatePool: MemoryTypeIndex=%u, flags=%u", pCreateInfo->memoryTypeIndex, pCreateInfo->flags); VmaPoolCreateInfo newCreateInfo = *pCreateInfo; if (newCreateInfo.maxBlockCount == 0) { newCreateInfo.maxBlockCount = SIZE_MAX; } if (newCreateInfo.minBlockCount > newCreateInfo.maxBlockCount) { return VK_ERROR_INITIALIZATION_FAILED; } const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(newCreateInfo.memoryTypeIndex); *pPool = vma_new(this, VmaPool_T)(this, newCreateInfo, preferredBlockSize); VkResult res = (*pPool)->m_BlockVector.CreateMinBlocks(); if (res != VK_SUCCESS) { vma_delete(this, *pPool); *pPool = VMA_NULL; return res; } // Add to m_Pools. { VmaMutexLockWrite lock(m_PoolsMutex, m_UseMutex); (*pPool)->SetId(m_NextPoolId++); VmaVectorInsertSorted<VmaPointerLess>(m_Pools, *pPool); } return VK_SUCCESS; } void VmaAllocator_T::DestroyPool(VmaPool pool) { // Remove from m_Pools. { VmaMutexLockWrite lock(m_PoolsMutex, m_UseMutex); bool success = VmaVectorRemoveSorted<VmaPointerLess>(m_Pools, pool); VMA_ASSERT(success && "Pool not found in Allocator."); } vma_delete(this, pool); } void VmaAllocator_T::GetPoolStats(VmaPool pool, VmaPoolStats* pPoolStats) { pool->m_BlockVector.GetPoolStats(pPoolStats); } void VmaAllocator_T::SetCurrentFrameIndex(uint32_t frameIndex) { m_CurrentFrameIndex.store(frameIndex); #if VMA_MEMORY_BUDGET if (m_UseExtMemoryBudget) { UpdateVulkanBudget(); } #endif // #if VMA_MEMORY_BUDGET } void VmaAllocator_T::MakePoolAllocationsLost( VmaPool hPool, size_t* pLostAllocationCount) { hPool->m_BlockVector.MakePoolAllocationsLost( m_CurrentFrameIndex.load(), pLostAllocationCount); } VkResult VmaAllocator_T::CheckPoolCorruption(VmaPool hPool) { return hPool->m_BlockVector.CheckCorruption(); } VkResult VmaAllocator_T::CheckCorruption(uint32_t memoryTypeBits) { VkResult finalRes = VK_ERROR_FEATURE_NOT_PRESENT; // Process default pools. for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { if (((1u << memTypeIndex) & memoryTypeBits) != 0) { VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex]; VMA_ASSERT(pBlockVector); VkResult localRes = pBlockVector->CheckCorruption(); switch (localRes) { case VK_ERROR_FEATURE_NOT_PRESENT: break; case VK_SUCCESS: finalRes = VK_SUCCESS; break; default: return localRes; } } } // Process custom pools. { VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex); for (size_t poolIndex = 0, poolCount = m_Pools.size(); poolIndex < poolCount; ++poolIndex) { if (((1u << m_Pools[poolIndex]->m_BlockVector.GetMemoryTypeIndex()) & memoryTypeBits) != 0) { VkResult localRes = m_Pools[poolIndex]->m_BlockVector.CheckCorruption(); switch (localRes) { case VK_ERROR_FEATURE_NOT_PRESENT: break; case VK_SUCCESS: finalRes = VK_SUCCESS; break; default: return localRes; } } } } return finalRes; } void VmaAllocator_T::CreateLostAllocation(VmaAllocation* pAllocation) { *pAllocation = m_AllocationObjectAllocator.Allocate(); (*pAllocation)->Ctor(VMA_FRAME_INDEX_LOST, false); (*pAllocation)->InitLost(); } VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory) { const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(pAllocateInfo->memoryTypeIndex); // HeapSizeLimit is in effect for this heap. if ((m_HeapSizeLimitMask & (1u << heapIndex)) != 0) { const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; VkDeviceSize blockBytes = m_Budget.m_BlockBytes[heapIndex]; for (;;) { const VkDeviceSize blockBytesAfterAllocation = blockBytes + pAllocateInfo->allocationSize; if (blockBytesAfterAllocation > heapSize) { return VK_ERROR_OUT_OF_DEVICE_MEMORY; } if (m_Budget.m_BlockBytes[heapIndex].compare_exchange_strong(blockBytes, blockBytesAfterAllocation)) { break; } } } else { m_Budget.m_BlockBytes[heapIndex] += pAllocateInfo->allocationSize; } // VULKAN CALL vkAllocateMemory. VkResult res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); if (res == VK_SUCCESS) { #if VMA_MEMORY_BUDGET ++m_Budget.m_OperationsSinceBudgetFetch; #endif // Informative callback. if (m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) { (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize); } } else { m_Budget.m_BlockBytes[heapIndex] -= pAllocateInfo->allocationSize; } return res; } void VmaAllocator_T::FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory) { // Informative callback. if (m_DeviceMemoryCallbacks.pfnFree != VMA_NULL) { (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size); } // VULKAN CALL vkFreeMemory. (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks()); m_Budget.m_BlockBytes[MemoryTypeIndexToHeapIndex(memoryType)] -= size; } VkResult VmaAllocator_T::BindVulkanBuffer( VkDeviceMemory memory, VkDeviceSize memoryOffset, VkBuffer buffer, const void* pNext) { if (pNext != VMA_NULL) { #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 if ((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL) { VkBindBufferMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR }; bindBufferMemoryInfo.pNext = pNext; bindBufferMemoryInfo.buffer = buffer; bindBufferMemoryInfo.memory = memory; bindBufferMemoryInfo.memoryOffset = memoryOffset; return (*m_VulkanFunctions.vkBindBufferMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); } else #endif // #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 { return VK_ERROR_EXTENSION_NOT_PRESENT; } } else { return (*m_VulkanFunctions.vkBindBufferMemory)(m_hDevice, buffer, memory, memoryOffset); } } VkResult VmaAllocator_T::BindVulkanImage( VkDeviceMemory memory, VkDeviceSize memoryOffset, VkImage image, const void* pNext) { if (pNext != VMA_NULL) { #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 if ((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL) { VkBindImageMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR }; bindBufferMemoryInfo.pNext = pNext; bindBufferMemoryInfo.image = image; bindBufferMemoryInfo.memory = memory; bindBufferMemoryInfo.memoryOffset = memoryOffset; return (*m_VulkanFunctions.vkBindImageMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); } else #endif // #if VMA_BIND_MEMORY2 { return VK_ERROR_EXTENSION_NOT_PRESENT; } } else { return (*m_VulkanFunctions.vkBindImageMemory)(m_hDevice, image, memory, memoryOffset); } } VkResult VmaAllocator_T::Map(VmaAllocation hAllocation, void** ppData) { if (hAllocation->CanBecomeLost()) { return VK_ERROR_MEMORY_MAP_FAILED; } switch (hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); char* pBytes = VMA_NULL; VkResult res = pBlock->Map(this, 1, (void**)&pBytes); if (res == VK_SUCCESS) { *ppData = pBytes + (ptrdiff_t)hAllocation->GetOffset(); hAllocation->BlockAllocMap(); } return res; } case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: return hAllocation->DedicatedAllocMap(this, ppData); default: VMA_ASSERT(0); return VK_ERROR_MEMORY_MAP_FAILED; } } void VmaAllocator_T::Unmap(VmaAllocation hAllocation) { switch (hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); hAllocation->BlockAllocUnmap(); pBlock->Unmap(this, 1); } break; case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: hAllocation->DedicatedAllocUnmap(this); break; default: VMA_ASSERT(0); } } VkResult VmaAllocator_T::BindBufferMemory( VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkBuffer hBuffer, const void* pNext) { VkResult res = VK_SUCCESS; switch (hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: res = BindVulkanBuffer(hAllocation->GetMemory(), allocationLocalOffset, hBuffer, pNext); break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); VMA_ASSERT(pBlock && "Binding buffer to allocation that doesn't belong to any block. Is the allocation lost?"); res = pBlock->BindBufferMemory(this, hAllocation, allocationLocalOffset, hBuffer, pNext); break; } default: VMA_ASSERT(0); } return res; } VkResult VmaAllocator_T::BindImageMemory( VmaAllocation hAllocation, VkDeviceSize allocationLocalOffset, VkImage hImage, const void* pNext) { VkResult res = VK_SUCCESS; switch (hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: res = BindVulkanImage(hAllocation->GetMemory(), allocationLocalOffset, hImage, pNext); break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); VMA_ASSERT(pBlock && "Binding image to allocation that doesn't belong to any block. Is the allocation lost?"); res = pBlock->BindImageMemory(this, hAllocation, allocationLocalOffset, hImage, pNext); break; } default: VMA_ASSERT(0); } return res; } void VmaAllocator_T::FlushOrInvalidateAllocation( VmaAllocation hAllocation, VkDeviceSize offset, VkDeviceSize size, VMA_CACHE_OPERATION op) { const uint32_t memTypeIndex = hAllocation->GetMemoryTypeIndex(); if (size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) { const VkDeviceSize allocationSize = hAllocation->GetSize(); VMA_ASSERT(offset <= allocationSize); const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; memRange.memory = hAllocation->GetMemory(); switch (hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); if (size == VK_WHOLE_SIZE) { memRange.size = allocationSize - memRange.offset; } else { VMA_ASSERT(offset + size <= allocationSize); memRange.size = VMA_MIN( VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize), allocationSize - memRange.offset); } break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { // 1. Still within this allocation. memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); if (size == VK_WHOLE_SIZE) { size = allocationSize - offset; } else { VMA_ASSERT(offset + size <= allocationSize); } memRange.size = VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize); // 2. Adjust to whole block. const VkDeviceSize allocationOffset = hAllocation->GetOffset(); VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0); const VkDeviceSize blockSize = hAllocation->GetBlock()->m_pMetadata->GetSize(); memRange.offset += allocationOffset; memRange.size = VMA_MIN(memRange.size, blockSize - memRange.offset); break; } default: VMA_ASSERT(0); } switch (op) { case VMA_CACHE_FLUSH: (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange); break; case VMA_CACHE_INVALIDATE: (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange); break; default: VMA_ASSERT(0); } } // else: Just ignore this call. } void VmaAllocator_T::FreeDedicatedMemory(const VmaAllocation allocation) { VMA_ASSERT(allocation && allocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); { VmaMutexLockWrite lock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex); AllocationVectorType* const pDedicatedAllocations = m_pDedicatedAllocations[memTypeIndex]; VMA_ASSERT(pDedicatedAllocations); bool success = VmaVectorRemoveSorted<VmaPointerLess>(*pDedicatedAllocations, allocation); VMA_ASSERT(success); } VkDeviceMemory hMemory = allocation->GetMemory(); /* There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory before vkFreeMemory. if(allocation->GetMappedData() != VMA_NULL) { (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); } */ FreeVulkanMemory(memTypeIndex, allocation->GetSize(), hMemory); VMA_DEBUG_LOG(" Freed DedicatedMemory MemoryTypeIndex=%u", memTypeIndex); } uint32_t VmaAllocator_T::CalculateGpuDefragmentationMemoryTypeBits() const { VkBufferCreateInfo dummyBufCreateInfo; VmaFillGpuDefragmentationBufferCreateInfo(dummyBufCreateInfo); uint32_t memoryTypeBits = 0; // Create buffer. VkBuffer buf = VK_NULL_HANDLE; VkResult res = (*GetVulkanFunctions().vkCreateBuffer)( m_hDevice, &dummyBufCreateInfo, GetAllocationCallbacks(), &buf); if (res == VK_SUCCESS) { // Query for supported memory types. VkMemoryRequirements memReq; (*GetVulkanFunctions().vkGetBufferMemoryRequirements)(m_hDevice, buf, &memReq); memoryTypeBits = memReq.memoryTypeBits; // Destroy buffer. (*GetVulkanFunctions().vkDestroyBuffer)(m_hDevice, buf, GetAllocationCallbacks()); } return memoryTypeBits; } #if VMA_MEMORY_BUDGET void VmaAllocator_T::UpdateVulkanBudget() { VMA_ASSERT(m_UseExtMemoryBudget); VkPhysicalDeviceMemoryProperties2KHR memProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR }; VkPhysicalDeviceMemoryBudgetPropertiesEXT budgetProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT }; memProps.pNext = &budgetProps; GetVulkanFunctions().vkGetPhysicalDeviceMemoryProperties2KHR(m_PhysicalDevice, &memProps); { VmaMutexLockWrite lockWrite(m_Budget.m_BudgetMutex, m_UseMutex); for (uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) { m_Budget.m_VulkanUsage[heapIndex] = budgetProps.heapUsage[heapIndex]; m_Budget.m_VulkanBudget[heapIndex] = budgetProps.heapBudget[heapIndex]; m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] = m_Budget.m_BlockBytes[heapIndex].load(); } m_Budget.m_OperationsSinceBudgetFetch = 0; } } #endif // #if VMA_MEMORY_BUDGET void VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pattern) { if (VMA_DEBUG_INITIALIZE_ALLOCATIONS && !hAllocation->CanBecomeLost() && (m_MemProps.memoryTypes[hAllocation->GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) { void* pData = VMA_NULL; VkResult res = Map(hAllocation, &pData); if (res == VK_SUCCESS) { memset(pData, (int)pattern, (size_t)hAllocation->GetSize()); FlushOrInvalidateAllocation(hAllocation, 0, VK_WHOLE_SIZE, VMA_CACHE_FLUSH); Unmap(hAllocation); } else { VMA_ASSERT(0 && "VMA_DEBUG_INITIALIZE_ALLOCATIONS is enabled, but couldn't map memory to fill allocation."); } } } uint32_t VmaAllocator_T::GetGpuDefragmentationMemoryTypeBits() { uint32_t memoryTypeBits = m_GpuDefragmentationMemoryTypeBits.load(); if (memoryTypeBits == UINT32_MAX) { memoryTypeBits = CalculateGpuDefragmentationMemoryTypeBits(); m_GpuDefragmentationMemoryTypeBits.store(memoryTypeBits); } return memoryTypeBits; } #if VMA_STATS_STRING_ENABLED void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) { bool dedicatedAllocationsStarted = false; for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { VmaMutexLockRead dedicatedAllocationsLock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex); AllocationVectorType* const pDedicatedAllocVector = m_pDedicatedAllocations[memTypeIndex]; VMA_ASSERT(pDedicatedAllocVector); if (pDedicatedAllocVector->empty() == false) { if (dedicatedAllocationsStarted == false) { dedicatedAllocationsStarted = true; json.WriteString("DedicatedAllocations"); json.BeginObject(); } json.BeginString("Type "); json.ContinueString(memTypeIndex); json.EndString(); json.BeginArray(); for (size_t i = 0; i < pDedicatedAllocVector->size(); ++i) { json.BeginObject(true); const VmaAllocation hAlloc = (*pDedicatedAllocVector)[i]; hAlloc->PrintParameters(json); json.EndObject(); } json.EndArray(); } } if (dedicatedAllocationsStarted) { json.EndObject(); } { bool allocationsStarted = false; for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { if (m_pBlockVectors[memTypeIndex]->IsEmpty() == false) { if (allocationsStarted == false) { allocationsStarted = true; json.WriteString("DefaultPools"); json.BeginObject(); } json.BeginString("Type "); json.ContinueString(memTypeIndex); json.EndString(); m_pBlockVectors[memTypeIndex]->PrintDetailedMap(json); } } if (allocationsStarted) { json.EndObject(); } } // Custom pools { VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex); const size_t poolCount = m_Pools.size(); if (poolCount > 0) { json.WriteString("Pools"); json.BeginObject(); for (size_t poolIndex = 0; poolIndex < poolCount; ++poolIndex) { json.BeginString(); json.ContinueString(m_Pools[poolIndex]->GetId()); json.EndString(); m_Pools[poolIndex]->m_BlockVector.PrintDetailedMap(json); } json.EndObject(); } } } #endif // #if VMA_STATS_STRING_ENABLED //////////////////////////////////////////////////////////////////////////////// // Public interface VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( const VmaAllocatorCreateInfo* pCreateInfo, VmaAllocator* pAllocator) { VMA_ASSERT(pCreateInfo && pAllocator); VMA_ASSERT(pCreateInfo->vulkanApiVersion == 0 || (VK_VERSION_MAJOR(pCreateInfo->vulkanApiVersion) == 1 && VK_VERSION_MINOR(pCreateInfo->vulkanApiVersion) <= 1)); VMA_DEBUG_LOG("vmaCreateAllocator"); *pAllocator = vma_new(pCreateInfo->pAllocationCallbacks, VmaAllocator_T)(pCreateInfo); return (*pAllocator)->Init(pCreateInfo); } VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( VmaAllocator allocator) { if (allocator != VK_NULL_HANDLE) { VMA_DEBUG_LOG("vmaDestroyAllocator"); VkAllocationCallbacks allocationCallbacks = allocator->m_AllocationCallbacks; vma_delete(&allocationCallbacks, allocator); } } VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( VmaAllocator allocator, const VkPhysicalDeviceProperties** ppPhysicalDeviceProperties) { VMA_ASSERT(allocator && ppPhysicalDeviceProperties); *ppPhysicalDeviceProperties = &allocator->m_PhysicalDeviceProperties; } VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( VmaAllocator allocator, const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties) { VMA_ASSERT(allocator && ppPhysicalDeviceMemoryProperties); *ppPhysicalDeviceMemoryProperties = &allocator->m_MemProps; } VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( VmaAllocator allocator, uint32_t memoryTypeIndex, VkMemoryPropertyFlags* pFlags) { VMA_ASSERT(allocator && pFlags); VMA_ASSERT(memoryTypeIndex < allocator->GetMemoryTypeCount()); *pFlags = allocator->m_MemProps.memoryTypes[memoryTypeIndex].propertyFlags; } VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( VmaAllocator allocator, uint32_t frameIndex) { VMA_ASSERT(allocator); VMA_ASSERT(frameIndex != VMA_FRAME_INDEX_LOST); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocator->SetCurrentFrameIndex(frameIndex); } VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats( VmaAllocator allocator, VmaStats* pStats) { VMA_ASSERT(allocator && pStats); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocator->CalculateStats(pStats); } VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget( VmaAllocator allocator, VmaBudget* pBudget) { VMA_ASSERT(allocator && pBudget); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocator->GetBudget(pBudget, 0, allocator->GetMemoryHeapCount()); } #if VMA_STATS_STRING_ENABLED VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( VmaAllocator allocator, char** ppStatsString, VkBool32 detailedMap) { VMA_ASSERT(allocator && ppStatsString); VMA_DEBUG_GLOBAL_MUTEX_LOCK VmaStringBuilder sb(allocator); { VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb); json.BeginObject(); VmaBudget budget[VK_MAX_MEMORY_HEAPS]; allocator->GetBudget(budget, 0, allocator->GetMemoryHeapCount()); VmaStats stats; allocator->CalculateStats(&stats); json.WriteString("Total"); VmaPrintStatInfo(json, stats.total); for (uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex) { json.BeginString("Heap "); json.ContinueString(heapIndex); json.EndString(); json.BeginObject(); json.WriteString("Size"); json.WriteNumber(allocator->m_MemProps.memoryHeaps[heapIndex].size); json.WriteString("Flags"); json.BeginArray(true); if ((allocator->m_MemProps.memoryHeaps[heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { json.WriteString("DEVICE_LOCAL"); } json.EndArray(); json.WriteString("Budget"); json.BeginObject(); { json.WriteString("BlockBytes"); json.WriteNumber(budget[heapIndex].blockBytes); json.WriteString("AllocationBytes"); json.WriteNumber(budget[heapIndex].allocationBytes); json.WriteString("Usage"); json.WriteNumber(budget[heapIndex].usage); json.WriteString("Budget"); json.WriteNumber(budget[heapIndex].budget); } json.EndObject(); if (stats.memoryHeap[heapIndex].blockCount > 0) { json.WriteString("Stats"); VmaPrintStatInfo(json, stats.memoryHeap[heapIndex]); } for (uint32_t typeIndex = 0; typeIndex < allocator->GetMemoryTypeCount(); ++typeIndex) { if (allocator->MemoryTypeIndexToHeapIndex(typeIndex) == heapIndex) { json.BeginString("Type "); json.ContinueString(typeIndex); json.EndString(); json.BeginObject(); json.WriteString("Flags"); json.BeginArray(true); VkMemoryPropertyFlags flags = allocator->m_MemProps.memoryTypes[typeIndex].propertyFlags; if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) { json.WriteString("DEVICE_LOCAL"); } if ((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) { json.WriteString("HOST_VISIBLE"); } if ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0) { json.WriteString("HOST_COHERENT"); } if ((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0) { json.WriteString("HOST_CACHED"); } if ((flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) != 0) { json.WriteString("LAZILY_ALLOCATED"); } json.EndArray(); if (stats.memoryType[typeIndex].blockCount > 0) { json.WriteString("Stats"); VmaPrintStatInfo(json, stats.memoryType[typeIndex]); } json.EndObject(); } } json.EndObject(); } if (detailedMap == VK_TRUE) { allocator->PrintDetailedMap(json); } json.EndObject(); } const size_t len = sb.GetLength(); char* const pChars = vma_new_array(allocator, char, len + 1); if (len > 0) { memcpy(pChars, sb.GetData(), len); } pChars[len] = '\0'; *ppStatsString = pChars; } VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( VmaAllocator allocator, char* pStatsString) { if (pStatsString != VMA_NULL) { VMA_ASSERT(allocator); size_t len = strlen(pStatsString); vma_delete_array(allocator, pStatsString, len + 1); } } #endif // #if VMA_STATS_STRING_ENABLED /* This function is not protected by any mutex because it just reads immutable data. */ VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( VmaAllocator allocator, uint32_t memoryTypeBits, const VmaAllocationCreateInfo* pAllocationCreateInfo, uint32_t* pMemoryTypeIndex) { VMA_ASSERT(allocator != VK_NULL_HANDLE); VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); if (pAllocationCreateInfo->memoryTypeBits != 0) { memoryTypeBits &= pAllocationCreateInfo->memoryTypeBits; } uint32_t requiredFlags = pAllocationCreateInfo->requiredFlags; uint32_t preferredFlags = pAllocationCreateInfo->preferredFlags; uint32_t notPreferredFlags = 0; // Convert usage to requiredFlags and preferredFlags. switch (pAllocationCreateInfo->usage) { case VMA_MEMORY_USAGE_UNKNOWN: break; case VMA_MEMORY_USAGE_GPU_ONLY: if (!allocator->IsIntegratedGpu() || (preferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) { preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } break; case VMA_MEMORY_USAGE_CPU_ONLY: requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; break; case VMA_MEMORY_USAGE_CPU_TO_GPU: requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; if (!allocator->IsIntegratedGpu() || (preferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) { preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } break; case VMA_MEMORY_USAGE_GPU_TO_CPU: requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; break; case VMA_MEMORY_USAGE_CPU_COPY: notPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; break; case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; break; default: VMA_ASSERT(0); break; } *pMemoryTypeIndex = UINT32_MAX; uint32_t minCost = UINT32_MAX; for (uint32_t memTypeIndex = 0, memTypeBit = 1; memTypeIndex < allocator->GetMemoryTypeCount(); ++memTypeIndex, memTypeBit <<= 1) { // This memory type is acceptable according to memoryTypeBits bitmask. if ((memTypeBit & memoryTypeBits) != 0) { const VkMemoryPropertyFlags currFlags = allocator->m_MemProps.memoryTypes[memTypeIndex].propertyFlags; // This memory type contains requiredFlags. if ((requiredFlags & ~currFlags) == 0) { // Calculate cost as number of bits from preferredFlags not present in this memory type. uint32_t currCost = VmaCountBitsSet(preferredFlags & ~currFlags) + VmaCountBitsSet(currFlags & notPreferredFlags); // Remember memory type with lowest cost. if (currCost < minCost) { *pMemoryTypeIndex = memTypeIndex; if (currCost == 0) { return VK_SUCCESS; } minCost = currCost; } } } } return (*pMemoryTypeIndex != UINT32_MAX) ? VK_SUCCESS : VK_ERROR_FEATURE_NOT_PRESENT; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, uint32_t* pMemoryTypeIndex) { VMA_ASSERT(allocator != VK_NULL_HANDLE); VMA_ASSERT(pBufferCreateInfo != VMA_NULL); VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); const VkDevice hDev = allocator->m_hDevice; VkBuffer hBuffer = VK_NULL_HANDLE; VkResult res = allocator->GetVulkanFunctions().vkCreateBuffer( hDev, pBufferCreateInfo, allocator->GetAllocationCallbacks(), &hBuffer); if (res == VK_SUCCESS) { VkMemoryRequirements memReq = {}; allocator->GetVulkanFunctions().vkGetBufferMemoryRequirements( hDev, hBuffer, &memReq); res = vmaFindMemoryTypeIndex( allocator, memReq.memoryTypeBits, pAllocationCreateInfo, pMemoryTypeIndex); allocator->GetVulkanFunctions().vkDestroyBuffer( hDev, hBuffer, allocator->GetAllocationCallbacks()); } return res; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, uint32_t* pMemoryTypeIndex) { VMA_ASSERT(allocator != VK_NULL_HANDLE); VMA_ASSERT(pImageCreateInfo != VMA_NULL); VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); const VkDevice hDev = allocator->m_hDevice; VkImage hImage = VK_NULL_HANDLE; VkResult res = allocator->GetVulkanFunctions().vkCreateImage( hDev, pImageCreateInfo, allocator->GetAllocationCallbacks(), &hImage); if (res == VK_SUCCESS) { VkMemoryRequirements memReq = {}; allocator->GetVulkanFunctions().vkGetImageMemoryRequirements( hDev, hImage, &memReq); res = vmaFindMemoryTypeIndex( allocator, memReq.memoryTypeBits, pAllocationCreateInfo, pMemoryTypeIndex); allocator->GetVulkanFunctions().vkDestroyImage( hDev, hImage, allocator->GetAllocationCallbacks()); } return res; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( VmaAllocator allocator, const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool) { VMA_ASSERT(allocator && pCreateInfo && pPool); VMA_DEBUG_LOG("vmaCreatePool"); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkResult res = allocator->CreatePool(pCreateInfo, pPool); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordCreatePool(allocator->GetCurrentFrameIndex(), *pCreateInfo, *pPool); } #endif return res; } VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( VmaAllocator allocator, VmaPool pool) { VMA_ASSERT(allocator); if (pool == VK_NULL_HANDLE) { return; } VMA_DEBUG_LOG("vmaDestroyPool"); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordDestroyPool(allocator->GetCurrentFrameIndex(), pool); } #endif allocator->DestroyPool(pool); } VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats( VmaAllocator allocator, VmaPool pool, VmaPoolStats* pPoolStats) { VMA_ASSERT(allocator && pool && pPoolStats); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocator->GetPoolStats(pool, pPoolStats); } VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost( VmaAllocator allocator, VmaPool pool, size_t* pLostAllocationCount) { VMA_ASSERT(allocator && pool); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordMakePoolAllocationsLost(allocator->GetCurrentFrameIndex(), pool); } #endif allocator->MakePoolAllocationsLost(pool, pLostAllocationCount); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) { VMA_ASSERT(allocator && pool); VMA_DEBUG_GLOBAL_MUTEX_LOCK VMA_DEBUG_LOG("vmaCheckPoolCorruption"); return allocator->CheckPoolCorruption(pool); } VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( VmaAllocator allocator, VmaPool pool, const char** ppName) { VMA_ASSERT(allocator && pool); VMA_DEBUG_LOG("vmaGetPoolName"); VMA_DEBUG_GLOBAL_MUTEX_LOCK * ppName = pool->GetName(); } VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( VmaAllocator allocator, VmaPool pool, const char* pName) { VMA_ASSERT(allocator && pool); VMA_DEBUG_LOG("vmaSetPoolName"); VMA_DEBUG_GLOBAL_MUTEX_LOCK pool->SetName(pName); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordSetPoolName(allocator->GetCurrentFrameIndex(), pool, pName); } #endif } VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo) { VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocation); VMA_DEBUG_LOG("vmaAllocateMemory"); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkResult result = allocator->AllocateMemory( *pVkMemoryRequirements, false, // requiresDedicatedAllocation false, // prefersDedicatedAllocation VK_NULL_HANDLE, // dedicatedBuffer VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordAllocateMemory( allocator->GetCurrentFrameIndex(), *pVkMemoryRequirements, *pCreateInfo, *pAllocation); } #endif if (pAllocationInfo != VMA_NULL && result == VK_SUCCESS) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } return result; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, size_t allocationCount, VmaAllocation* pAllocations, VmaAllocationInfo* pAllocationInfo) { if (allocationCount == 0) { return VK_SUCCESS; } VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocations); VMA_DEBUG_LOG("vmaAllocateMemoryPages"); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkResult result = allocator->AllocateMemory( *pVkMemoryRequirements, false, // requiresDedicatedAllocation false, // prefersDedicatedAllocation VK_NULL_HANDLE, // dedicatedBuffer VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, allocationCount, pAllocations); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordAllocateMemoryPages( allocator->GetCurrentFrameIndex(), *pVkMemoryRequirements, *pCreateInfo, (uint64_t)allocationCount, pAllocations); } #endif if (pAllocationInfo != VMA_NULL && result == VK_SUCCESS) { for (size_t i = 0; i < allocationCount; ++i) { allocator->GetAllocationInfo(pAllocations[i], pAllocationInfo + i); } } return result; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( VmaAllocator allocator, VkBuffer buffer, const VmaAllocationCreateInfo* pCreateInfo, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo) { VMA_ASSERT(allocator && buffer != VK_NULL_HANDLE && pCreateInfo && pAllocation); VMA_DEBUG_LOG("vmaAllocateMemoryForBuffer"); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkMemoryRequirements vkMemReq = {}; bool requiresDedicatedAllocation = false; bool prefersDedicatedAllocation = false; allocator->GetBufferMemoryRequirements(buffer, vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation); VkResult result = allocator->AllocateMemory( vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, buffer, // dedicatedBuffer VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordAllocateMemoryForBuffer( allocator->GetCurrentFrameIndex(), vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, *pCreateInfo, *pAllocation); } #endif if (pAllocationInfo && result == VK_SUCCESS) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } return result; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( VmaAllocator allocator, VkImage image, const VmaAllocationCreateInfo* pCreateInfo, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo) { VMA_ASSERT(allocator && image != VK_NULL_HANDLE && pCreateInfo && pAllocation); VMA_DEBUG_LOG("vmaAllocateMemoryForImage"); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkMemoryRequirements vkMemReq = {}; bool requiresDedicatedAllocation = false; bool prefersDedicatedAllocation = false; allocator->GetImageMemoryRequirements(image, vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation); VkResult result = allocator->AllocateMemory( vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, VK_NULL_HANDLE, // dedicatedBuffer image, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN, 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordAllocateMemoryForImage( allocator->GetCurrentFrameIndex(), vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, *pCreateInfo, *pAllocation); } #endif if (pAllocationInfo && result == VK_SUCCESS) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } return result; } VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( VmaAllocator allocator, VmaAllocation allocation) { VMA_ASSERT(allocator); if (allocation == VK_NULL_HANDLE) { return; } VMA_DEBUG_LOG("vmaFreeMemory"); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordFreeMemory( allocator->GetCurrentFrameIndex(), allocation); } #endif allocator->FreeMemory( 1, // allocationCount &allocation); } VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( VmaAllocator allocator, size_t allocationCount, VmaAllocation* pAllocations) { if (allocationCount == 0) { return; } VMA_ASSERT(allocator); VMA_DEBUG_LOG("vmaFreeMemoryPages"); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordFreeMemoryPages( allocator->GetCurrentFrameIndex(), (uint64_t)allocationCount, pAllocations); } #endif allocator->FreeMemory(allocationCount, pAllocations); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize newSize) { VMA_ASSERT(allocator && allocation); VMA_DEBUG_LOG("vmaResizeAllocation"); VMA_DEBUG_GLOBAL_MUTEX_LOCK return allocator->ResizeAllocation(allocation, newSize); } VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( VmaAllocator allocator, VmaAllocation allocation, VmaAllocationInfo* pAllocationInfo) { VMA_ASSERT(allocator && allocation && pAllocationInfo); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordGetAllocationInfo( allocator->GetCurrentFrameIndex(), allocation); } #endif allocator->GetAllocationInfo(allocation, pAllocationInfo); } VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation( VmaAllocator allocator, VmaAllocation allocation) { VMA_ASSERT(allocator && allocation); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordTouchAllocation( allocator->GetCurrentFrameIndex(), allocation); } #endif return allocator->TouchAllocation(allocation); } VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( VmaAllocator allocator, VmaAllocation allocation, void* pUserData) { VMA_ASSERT(allocator && allocation); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocation->SetUserData(allocator, pUserData); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordSetAllocationUserData( allocator->GetCurrentFrameIndex(), allocation, pUserData); } #endif } VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation( VmaAllocator allocator, VmaAllocation* pAllocation) { VMA_ASSERT(allocator && pAllocation); VMA_DEBUG_GLOBAL_MUTEX_LOCK; allocator->CreateLostAllocation(pAllocation); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordCreateLostAllocation( allocator->GetCurrentFrameIndex(), *pAllocation); } #endif } VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( VmaAllocator allocator, VmaAllocation allocation, void** ppData) { VMA_ASSERT(allocator && allocation && ppData); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkResult res = allocator->Map(allocation, ppData); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordMapMemory( allocator->GetCurrentFrameIndex(), allocation); } #endif return res; } VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( VmaAllocator allocator, VmaAllocation allocation) { VMA_ASSERT(allocator && allocation); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordUnmapMemory( allocator->GetCurrentFrameIndex(), allocation); } #endif allocator->Unmap(allocation); } VMA_CALL_PRE void VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { VMA_ASSERT(allocator && allocation); VMA_DEBUG_LOG("vmaFlushAllocation"); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordFlushAllocation( allocator->GetCurrentFrameIndex(), allocation, offset, size); } #endif } VMA_CALL_PRE void VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { VMA_ASSERT(allocator && allocation); VMA_DEBUG_LOG("vmaInvalidateAllocation"); VMA_DEBUG_GLOBAL_MUTEX_LOCK allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordInvalidateAllocation( allocator->GetCurrentFrameIndex(), allocation, offset, size); } #endif } VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) { VMA_ASSERT(allocator); VMA_DEBUG_LOG("vmaCheckCorruption"); VMA_DEBUG_GLOBAL_MUTEX_LOCK return allocator->CheckCorruption(memoryTypeBits); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment( VmaAllocator allocator, VmaAllocation* pAllocations, size_t allocationCount, VkBool32* pAllocationsChanged, const VmaDefragmentationInfo* pDefragmentationInfo, VmaDefragmentationStats* pDefragmentationStats) { // Deprecated interface, reimplemented using new one. VmaDefragmentationInfo2 info2 = {}; info2.allocationCount = (uint32_t)allocationCount; info2.pAllocations = pAllocations; info2.pAllocationsChanged = pAllocationsChanged; if (pDefragmentationInfo != VMA_NULL) { info2.maxCpuAllocationsToMove = pDefragmentationInfo->maxAllocationsToMove; info2.maxCpuBytesToMove = pDefragmentationInfo->maxBytesToMove; } else { info2.maxCpuAllocationsToMove = UINT32_MAX; info2.maxCpuBytesToMove = VK_WHOLE_SIZE; } // info2.flags, maxGpuAllocationsToMove, maxGpuBytesToMove, commandBuffer deliberately left zero. VmaDefragmentationContext ctx; VkResult res = vmaDefragmentationBegin(allocator, &info2, pDefragmentationStats, &ctx); if (res == VK_NOT_READY) { res = vmaDefragmentationEnd(allocator, ctx); } return res; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin( VmaAllocator allocator, const VmaDefragmentationInfo2* pInfo, VmaDefragmentationStats* pStats, VmaDefragmentationContext* pContext) { VMA_ASSERT(allocator && pInfo && pContext); // Degenerate case: Nothing to defragment. if (pInfo->allocationCount == 0 && pInfo->poolCount == 0) { return VK_SUCCESS; } VMA_ASSERT(pInfo->allocationCount == 0 || pInfo->pAllocations != VMA_NULL); VMA_ASSERT(pInfo->poolCount == 0 || pInfo->pPools != VMA_NULL); VMA_HEAVY_ASSERT(VmaValidatePointerArray(pInfo->allocationCount, pInfo->pAllocations)); VMA_HEAVY_ASSERT(VmaValidatePointerArray(pInfo->poolCount, pInfo->pPools)); VMA_DEBUG_LOG("vmaDefragmentationBegin"); VMA_DEBUG_GLOBAL_MUTEX_LOCK VkResult res = allocator->DefragmentationBegin(*pInfo, pStats, pContext); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordDefragmentationBegin( allocator->GetCurrentFrameIndex(), *pInfo, *pContext); } #endif return res; } VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd( VmaAllocator allocator, VmaDefragmentationContext context) { VMA_ASSERT(allocator); VMA_DEBUG_LOG("vmaDefragmentationEnd"); if (context != VK_NULL_HANDLE) { VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordDefragmentationEnd( allocator->GetCurrentFrameIndex(), context); } #endif return allocator->DefragmentationEnd(context); } else { return VK_SUCCESS; } } VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( VmaAllocator allocator, VmaAllocation allocation, VkBuffer buffer) { VMA_ASSERT(allocator && allocation && buffer); VMA_DEBUG_LOG("vmaBindBufferMemory"); VMA_DEBUG_GLOBAL_MUTEX_LOCK return allocator->BindBufferMemory(allocation, 0, buffer, VMA_NULL); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize allocationLocalOffset, VkBuffer buffer, const void* pNext) { VMA_ASSERT(allocator && allocation && buffer); VMA_DEBUG_LOG("vmaBindBufferMemory2"); VMA_DEBUG_GLOBAL_MUTEX_LOCK return allocator->BindBufferMemory(allocation, allocationLocalOffset, buffer, pNext); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( VmaAllocator allocator, VmaAllocation allocation, VkImage image) { VMA_ASSERT(allocator && allocation && image); VMA_DEBUG_LOG("vmaBindImageMemory"); VMA_DEBUG_GLOBAL_MUTEX_LOCK return allocator->BindImageMemory(allocation, 0, image, VMA_NULL); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize allocationLocalOffset, VkImage image, const void* pNext) { VMA_ASSERT(allocator && allocation && image); VMA_DEBUG_LOG("vmaBindImageMemory2"); VMA_DEBUG_GLOBAL_MUTEX_LOCK return allocator->BindImageMemory(allocation, allocationLocalOffset, image, pNext); } VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, VkBuffer* pBuffer, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo) { VMA_ASSERT(allocator && pBufferCreateInfo && pAllocationCreateInfo && pBuffer && pAllocation); if (pBufferCreateInfo->size == 0) { return VK_ERROR_VALIDATION_FAILED_EXT; } VMA_DEBUG_LOG("vmaCreateBuffer"); VMA_DEBUG_GLOBAL_MUTEX_LOCK * pBuffer = VK_NULL_HANDLE; *pAllocation = VK_NULL_HANDLE; // 1. Create VkBuffer. VkResult res = (*allocator->GetVulkanFunctions().vkCreateBuffer)( allocator->m_hDevice, pBufferCreateInfo, allocator->GetAllocationCallbacks(), pBuffer); if (res >= 0) { // 2. vkGetBufferMemoryRequirements. VkMemoryRequirements vkMemReq = {}; bool requiresDedicatedAllocation = false; bool prefersDedicatedAllocation = false; allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation); // Make sure alignment requirements for specific buffer usages reported // in Physical Device Properties are included in alignment reported by memory requirements. if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0) { VMA_ASSERT(vkMemReq.alignment % allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0); } if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0) { VMA_ASSERT(vkMemReq.alignment % allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0); } if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) { VMA_ASSERT(vkMemReq.alignment % allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0); } // 3. Allocate memory using allocator. res = allocator->AllocateMemory( vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, *pBuffer, // dedicatedBuffer VK_NULL_HANDLE, // dedicatedImage *pAllocationCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordCreateBuffer( allocator->GetCurrentFrameIndex(), *pBufferCreateInfo, *pAllocationCreateInfo, *pAllocation); } #endif if (res >= 0) { // 3. Bind buffer with memory. if ((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) { res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL); } if (res >= 0) { // All steps succeeded. #if VMA_STATS_STRING_ENABLED (*pAllocation)->InitBufferImageUsage(pBufferCreateInfo->usage); #endif if (pAllocationInfo != VMA_NULL) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } return VK_SUCCESS; } allocator->FreeMemory( 1, // allocationCount pAllocation); *pAllocation = VK_NULL_HANDLE; (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); *pBuffer = VK_NULL_HANDLE; return res; } (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); *pBuffer = VK_NULL_HANDLE; return res; } return res; } VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( VmaAllocator allocator, VkBuffer buffer, VmaAllocation allocation) { VMA_ASSERT(allocator); if (buffer == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE) { return; } VMA_DEBUG_LOG("vmaDestroyBuffer"); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordDestroyBuffer( allocator->GetCurrentFrameIndex(), allocation); } #endif if (buffer != VK_NULL_HANDLE) { (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, buffer, allocator->GetAllocationCallbacks()); } if (allocation != VK_NULL_HANDLE) { allocator->FreeMemory( 1, // allocationCount &allocation); } } VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, VkImage* pImage, VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo) { VMA_ASSERT(allocator && pImageCreateInfo && pAllocationCreateInfo && pImage && pAllocation); if (pImageCreateInfo->extent.width == 0 || pImageCreateInfo->extent.height == 0 || pImageCreateInfo->extent.depth == 0 || pImageCreateInfo->mipLevels == 0 || pImageCreateInfo->arrayLayers == 0) { return VK_ERROR_VALIDATION_FAILED_EXT; } VMA_DEBUG_LOG("vmaCreateImage"); VMA_DEBUG_GLOBAL_MUTEX_LOCK * pImage = VK_NULL_HANDLE; *pAllocation = VK_NULL_HANDLE; // 1. Create VkImage. VkResult res = (*allocator->GetVulkanFunctions().vkCreateImage)( allocator->m_hDevice, pImageCreateInfo, allocator->GetAllocationCallbacks(), pImage); if (res >= 0) { VmaSuballocationType suballocType = pImageCreateInfo->tiling == VK_IMAGE_TILING_OPTIMAL ? VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL : VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR; // 2. Allocate memory using allocator. VkMemoryRequirements vkMemReq = {}; bool requiresDedicatedAllocation = false; bool prefersDedicatedAllocation = false; allocator->GetImageMemoryRequirements(*pImage, vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation); res = allocator->AllocateMemory( vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, VK_NULL_HANDLE, // dedicatedBuffer *pImage, // dedicatedImage *pAllocationCreateInfo, suballocType, 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordCreateImage( allocator->GetCurrentFrameIndex(), *pImageCreateInfo, *pAllocationCreateInfo, *pAllocation); } #endif if (res >= 0) { // 3. Bind image with memory. if ((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) { res = allocator->BindImageMemory(*pAllocation, 0, *pImage, VMA_NULL); } if (res >= 0) { // All steps succeeded. #if VMA_STATS_STRING_ENABLED (*pAllocation)->InitBufferImageUsage(pImageCreateInfo->usage); #endif if (pAllocationInfo != VMA_NULL) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } return VK_SUCCESS; } allocator->FreeMemory( 1, // allocationCount pAllocation); *pAllocation = VK_NULL_HANDLE; (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks()); *pImage = VK_NULL_HANDLE; return res; } (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks()); *pImage = VK_NULL_HANDLE; return res; } return res; } VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( VmaAllocator allocator, VkImage image, VmaAllocation allocation) { VMA_ASSERT(allocator); if (image == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE) { return; } VMA_DEBUG_LOG("vmaDestroyImage"); VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED if (allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordDestroyImage( allocator->GetCurrentFrameIndex(), allocation); } #endif if (image != VK_NULL_HANDLE) { (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, image, allocator->GetAllocationCallbacks()); } if (allocation != VK_NULL_HANDLE) { allocator->FreeMemory( 1, // allocationCount &allocation); } } #endif // #ifdef VMA_IMPLEMENTATION ================================================ FILE: src/libgpu/src/vulkan/vk_mem_alloc_decaf.h ================================================ #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4127) # pragma warning(disable:4189) #endif #include "vk_mem_alloc.h" #ifdef _MSC_VER # pragma warning(pop) #endif ================================================ FILE: src/libgpu/src/vulkan/vulkan_attribbuffers.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { VertexBufferDesc Driver::getAttribBufferDesc(uint32_t bufferIndex) { VertexBufferDesc desc; auto resourceOffset = (latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + bufferIndex) * 7; auto sq_vtx_constant_word0 = getRegister<latte::SQ_VTX_CONSTANT_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_vtx_constant_word1 = getRegister<latte::SQ_VTX_CONSTANT_WORD1_N>(latte::Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset); auto sq_vtx_constant_word2 = getRegister<latte::SQ_VTX_CONSTANT_WORD2_N>(latte::Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset); auto sq_vtx_constant_word6 = getRegister<latte::SQ_VTX_CONSTANT_WORD6_N>(latte::Register::SQ_RESOURCE_WORD6_0 + 4 * resourceOffset); decaf_check(sq_vtx_constant_word2.BASE_ADDRESS_HI() == 0); if (sq_vtx_constant_word6.TYPE() != latte::SQ_TEX_VTX_TYPE::VALID_BUFFER) { desc.baseAddress = phys_addr(0); desc.size = 0; desc.stride = 0; return desc; } desc.baseAddress = phys_addr(sq_vtx_constant_word0.BASE_ADDRESS()); desc.size = sq_vtx_constant_word1.SIZE() + 1; desc.stride = sq_vtx_constant_word2.STRIDE(); return desc; } bool Driver::checkCurrentAttribBuffers() { // Must have a vertex shader to describe what to upload... decaf_check(mCurrentDraw->vertexShader); for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) { if (!mCurrentDraw->vertexShader->shader.meta.attribBuffers[i].isUsed) { mCurrentDraw->attribBuffers[i] = nullptr; continue; } auto desc = getAttribBufferDesc(i); if (!desc.baseAddress || !desc.size) { // If the vertex shader takes this as an input, but there is no // actual buffer specified, we should fail our draw entirely. return false; } auto ¤tAttribBuffer = mCurrentDraw->attribBuffers[i]; if (currentAttribBuffer && currentAttribBuffer->address == desc.baseAddress && currentAttribBuffer->size == desc.size) { // If we are already set to the correct attribute buffer, we only need // to check that the buffer has not changed since we last looked. continue; } auto memCache = getDataMemCache(desc.baseAddress, desc.size); transitionMemCache(memCache, ResourceUsage::AttributeBuffer); currentAttribBuffer = memCache; } return true; } void Driver::bindAttribBuffers() { for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) { auto buffer = mCurrentDraw->attribBuffers[i]; if (buffer) { mActiveCommandBuffer.bindVertexBuffers(i, { buffer->buffer }, { 0 }); } } } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_debug.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { gpu::GraphicsDriverType Driver::type() { return gpu::GraphicsDriverType::Vulkan; } gpu::GraphicsDriverDebugInfo * Driver::getDebugInfo() { // TODO: This is not thread safe wrt updateDebuggerInfo, maybe it should // be some sort of double buffered thing with a std atomic pointer to the // latest filled out one return &mDebugInfo; } void Driver::updateDebuggerInfo() { auto averageFrameTime = std::chrono::duration_cast<duration_ms>(mAverageFrameTime).count(); mDebugInfo.averageFrameTimeMS = averageFrameTime; if (averageFrameTime > 0.0) { mDebugInfo.averageFps = 1000.0 / mDebugInfo.averageFrameTimeMS; } else { mDebugInfo.averageFps = 0.0; } mDebugInfo.numVertexShaders = mVertexShaders.size(); mDebugInfo.numGeometryShaders = mVertexShaders.size(); mDebugInfo.numPixelShaders = mPixelShaders.size(); mDebugInfo.numRenderPasses = mRenderPasses.size(); mDebugInfo.numPipelines = mPipelines.size(); mDebugInfo.numSamplers = mSamplers.size(); mDebugInfo.numSurfaces = mSurfaceGroups.size(); mDebugInfo.numDataBuffers = mMemCaches.size(); } void Driver::insertVkMarker(const std::string& text) { if (mVkDynLoader.vkCmdDebugMarkerInsertEXT) { vk::DebugMarkerMarkerInfoEXT testMarker; testMarker.pMarkerName = text.c_str(); mActiveCommandBuffer.debugMarkerInsertEXT(testMarker, mVkDynLoader); } } template <typename ObjType> static void _setVkObjectName(vk::Device device, ObjType object, vk::DebugReportObjectTypeEXT type, const char *name, const vk::DispatchLoaderDynamic& dispatch) { if (dispatch.vkDebugMarkerSetObjectNameEXT) { vk::DebugMarkerObjectNameInfoEXT nameInfo; nameInfo.object = *reinterpret_cast<uint64_t*>(&object); nameInfo.objectType = type; nameInfo.pObjectName = name; device.debugMarkerSetObjectNameEXT(nameInfo, dispatch); } } void Driver::setVkObjectName(VkBuffer object, const char *name) { _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eBuffer, name, mVkDynLoader); } void Driver::setVkObjectName(VkSampler object, const char *name) { _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eSampler, name, mVkDynLoader); } void Driver::setVkObjectName(VkImage object, const char *name) { _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eImage, name, mVkDynLoader); } void Driver::setVkObjectName(VkImageView object, const char *name) { _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eImageView, name, mVkDynLoader); } void Driver::setVkObjectName(VkShaderModule object, const char *name) { _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eShaderModule, name, mVkDynLoader); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_descs.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "latte/latte_formats.h" #include "latte/latte_constants.h" #include "latte/latte_registers_pa.h" #include "latte/latte_registers_sq.h" #include "latte/latte_registers_vgt.h" #include <common/datahash.h> #include <common/vulkan_hpp.h> #include <libcpu/be2_struct.h> namespace vulkan { struct RenderPassObject; struct VertexShaderObject; struct GeometryShaderObject; struct PixelShaderObject; struct RectStubShaderObject; #pragma pack(push, 1) // This is a specialized class for holding floats inside our hashable structures. // This allows us to pull float data out of our registers and store it in our // description structures without breaking the hashability required by DataHash. struct hashableFloat { static_assert(sizeof(float) == sizeof(uint32_t), "hashable float implementation requires uint32_t and float to be the same size"); hashableFloat() : value(0) { } hashableFloat(float val) : value(*reinterpret_cast<uint32_t*>(&val)) { } operator float() const { return *reinterpret_cast<const float*>(&value); } float & operator=(const float & rhs) { return *reinterpret_cast<float*>(&value) = rhs; } uint32_t value; }; struct ColorBufferDesc { uint32_t base256b; uint32_t pitchTileMax; uint32_t sliceTileMax; latte::CB_FORMAT format; latte::CB_NUMBER_TYPE numberType; latte::BUFFER_ARRAY_MODE arrayMode; uint32_t sliceStart; uint32_t sliceEnd; }; struct DepthStencilBufferDesc { uint32_t base256b; uint32_t pitchTileMax; uint32_t sliceTileMax; latte::DB_FORMAT format; latte::BUFFER_ARRAY_MODE arrayMode; uint32_t sliceStart; uint32_t sliceEnd; }; struct VertexBufferDesc { phys_addr baseAddress; uint32_t size; uint32_t stride; }; struct SurfaceDesc { // BaseAddress is a uint32 rather than a phys_addr as it actually // encodes information other than just the base address (swizzle). uint32_t baseAddress; uint32_t pitch; uint32_t width; uint32_t height; uint32_t depth; uint32_t samples; latte::SQ_TEX_DIM dim; latte::SQ_TILE_TYPE tileType; latte::SQ_TILE_MODE tileMode; latte::SurfaceFormat format; inline uint32_t calcAlignedBaseAddress() const { if (tileMode >= latte::SQ_TILE_MODE::TILED_2D_THIN1) { return baseAddress & ~(0x800 - 1); } else { return baseAddress & ~(0x100 - 1); } } inline uint32_t calcSwizzle() const { return baseAddress & 0x00000F00; } inline DataHash hash(bool byCompat = false) const { // tileMode and swizzle are intentionally omited as they // do not affect data placement or size, but only upload/downloads. // It is possible that a tile-type switch may invalidate // old data though... // TODO: Handle tiling changes, major memory reuse issues... struct { uint32_t address; uint32_t format; uint32_t dim; uint32_t samples; uint32_t tileType; uint32_t tileMode; uint32_t width; uint32_t pitch; uint32_t height; uint32_t depth; } _dataHash; memset(&_dataHash, 0xFF, sizeof(_dataHash)); _dataHash.address = calcAlignedBaseAddress(); _dataHash.format = format; _dataHash.samples = samples; _dataHash.tileType = tileType; _dataHash.tileMode = tileMode; if (!byCompat) { // TODO: Figure out if we can emulate 2D_ARRAY surfaces // as 3D surfaces so we can get both kinds of views from // them? // Figure out which surfaces are compatible // at a DIM level... Basically, anything we // can generate a view of. switch (dim) { case latte::SQ_TEX_DIM::DIM_3D: _dataHash.dim = 3; break; case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_CUBEMAP: _dataHash.dim = 2; break; case latte::SQ_TEX_DIM::DIM_1D: case latte::SQ_TEX_DIM::DIM_1D_ARRAY: _dataHash.dim = 1; break; default: decaf_abort(fmt::format("Unsupported texture dim: {}", dim)); } //_dataHash.dim = dim; } switch (dim) { case latte::SQ_TEX_DIM::DIM_3D: if (!byCompat) { _dataHash.depth = depth; } // fallthrough case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: case latte::SQ_TEX_DIM::DIM_CUBEMAP: _dataHash.height = height; // fallthrough case latte::SQ_TEX_DIM::DIM_1D: case latte::SQ_TEX_DIM::DIM_1D_ARRAY: _dataHash.pitch = pitch; if (!byCompat) { _dataHash.width = width; } break; default: decaf_abort(fmt::format("Unsupported texture dim: {}", dim)); } return DataHash {}.write(_dataHash); } }; struct SurfaceViewDesc { SurfaceDesc surfaceDesc; uint32_t sliceStart; uint32_t sliceEnd; std::array<latte::SQ_SEL, 4> channels; inline DataHash hash() const { struct { uint32_t sliceStart; uint32_t sliceEnd; std::array<latte::SQ_SEL, 4> channels; } _dataHash; memset(&_dataHash, 0xFF, sizeof(_dataHash)); _dataHash.sliceStart = sliceStart; _dataHash.sliceEnd = sliceEnd; _dataHash.channels = channels; return surfaceDesc.hash().write(_dataHash); } }; struct FramebufferDesc { std::array<ColorBufferDesc, latte::MaxRenderTargets> colorTargets; DepthStencilBufferDesc depthTarget; inline DataHash hash() const { return DataHash {}.write(*this); } }; struct SamplerDesc { latte::SQ_TEX_SAMPLER_WORD0_N texSamplerWord0; latte::SQ_TEX_SAMPLER_WORD1_N texSamplerWord1; latte::SQ_TEX_SAMPLER_WORD2_N texSamplerWord2; inline DataHash hash() const { return DataHash {}.write(*this); } }; struct SwapChainDesc { phys_addr baseAddress; uint32_t width; uint32_t height; }; struct RenderPassDesc { struct ColorTarget { bool isEnabled; latte::CB_FORMAT format; latte::CB_NUMBER_TYPE numberType; uint32_t samples; }; struct DepthStencilTarget { bool isEnabled; latte::DB_FORMAT format; }; std::array<ColorTarget, 8> colorTargets; DepthStencilTarget depthTarget; inline DataHash hash() const { return DataHash {}.write(*this); } }; struct PipelineLayoutDesc { std::array<bool, latte::MaxSamplers> vsSamplerUsed; std::array<bool, latte::MaxTextures> vsTextureUsed; std::array<bool, latte::MaxUniformBlocks> vsBufferUsed; std::array<bool, latte::MaxSamplers> gsSamplerUsed; std::array<bool, latte::MaxTextures> gsTextureUsed; std::array<bool, latte::MaxUniformBlocks> gsBufferUsed; std::array<bool, latte::MaxSamplers> psSamplerUsed; std::array<bool, latte::MaxTextures> psTextureUsed; std::array<bool, latte::MaxUniformBlocks> psBufferUsed; uint32_t numDescriptors = 0; inline DataHash hash() const { return DataHash {}.write(*this); } }; struct PipelineDesc { RenderPassObject *renderPass; VertexShaderObject *vertexShader; GeometryShaderObject *geometryShader; PixelShaderObject *pixelShader; RectStubShaderObject *rectStubShader; std::array<uint32_t, latte::MaxAttribBuffers> attribBufferStride; std::array<uint32_t, 2> attribBufferDivisor; latte::VGT_DI_PRIMITIVE_TYPE primitiveType; bool primitiveResetEnabled; uint32_t primitiveResetIndex; bool dx9Consts; struct StencilOpState { latte::REF_FUNC compareFunc; latte::DB_STENCIL_FUNC failOp; latte::DB_STENCIL_FUNC zPassOp; latte::DB_STENCIL_FUNC zFailOp; uint8_t ref; uint8_t mask; uint8_t writeMask; }; bool stencilEnable; StencilOpState stencilFront; StencilOpState stencilBack; bool zEnable; bool zWriteEnable; latte::REF_FUNC zFunc; bool rasteriserDisable; uint32_t lineWidth; latte::PA_FACE paFace; bool cullFront; bool cullBack; uint32_t polyPType; bool polyBiasEnabled; hashableFloat polyBiasClamp; hashableFloat polyBiasOffset; hashableFloat polyBiasScale; bool zclipDisabled; uint32_t rop3; struct BlendControl { uint8_t targetMask; bool blendingEnabled; bool opacityWeight; latte::CB_COMB_FUNC colorCombFcn; latte::CB_BLEND_FUNC colorSrcBlend; latte::CB_BLEND_FUNC colorDstBlend; latte::CB_COMB_FUNC alphaCombFcn; latte::CB_BLEND_FUNC alphaSrcBlend; latte::CB_BLEND_FUNC alphaDstBlend; }; std::array<BlendControl, latte::MaxRenderTargets> cbBlendControls; std::array<hashableFloat, 4> cbBlendConstants; latte::REF_FUNC alphaFunc; hashableFloat alphaRef; inline DataHash hash() const { return DataHash {}.write(*this); } }; struct StreamOutBufferDesc { phys_addr baseAddress; uint32_t size; uint32_t stride; }; #pragma pack(pop) } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_display.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_displayshaders.h" #include "gpu_config.h" #include "gpu7_displaylayout.h" #include <common/log.h> #include <common/platform_debug.h> #include <common/strutils.h> #include <cstring> #include <fmt/format.h> #include <iterator> #include <optional> #include <string_view> #include <tuple> namespace vulkan { static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void *pUserData) { // Consider doing additional debugger behaviours based on various attributes. // This is to improve the chances that we don't accidentally miss incorrect // Vulkan-specific behaviours. // We keep track of known issues so we can log slightly differently, and also // avoid breaking to the debugger here. bool isKnownIssue = false; // There is currently a bug where the validation layers report issues with using // VkPipelineColorBlendStateCreateInfo in spite of our legal usage of it. // TODO: Remove this once validation correctly supports VkPipelineColorBlendAdvancedStateCreateInfoEXT if (strstr(pMessage, "VkPipelineColorBlendStateCreateInfo-pNext") != nullptr) { static uint64_t seenAdvancedBlendWarning = 0; if (seenAdvancedBlendWarning++) { return VK_FALSE; } isKnownIssue = true; } // We intentionally mirror the behaviour of GPU7 where a shader writes to an attachement which is not bound. // The validation layer gives us a warning, but we should ignore it for this known case. if (strstr(pMessage, "Shader-OutputNotConsumed") != nullptr) { static uint64_t seenOutputNotConsumed = 0; if (seenOutputNotConsumed++) { return VK_FALSE; } isKnownIssue = true; } // Some games rebind the same texture as an input and output at the same time. This // is technically illegal, even for GPU7, but it works... so... if (strstr(pMessage, "VkDescriptorImageInfo-imageLayout") != nullptr) { static uint64_t seenImageLayoutError = 0; if (seenImageLayoutError++) { return VK_FALSE; } isKnownIssue = true; } if (strstr(pMessage, "DrawState-DescriptorSetNotUpdated") != nullptr) { static uint64_t seenDescriptorSetError = 0; if (seenDescriptorSetError++) { return VK_FALSE; } isKnownIssue = true; } // There is an issue with the validation layers and handling of transform feedback. if (strstr(pMessage, "VUID-vkCmdPipelineBarrier-pMemoryBarriers-01184") != nullptr) { static uint64_t seenXfbBarrier01184Error = 0; if (seenXfbBarrier01184Error++) { return VK_FALSE; } isKnownIssue = true; } if (strstr(pMessage, "VUID-vkCmdPipelineBarrier-pMemoryBarriers-01185") != nullptr) { static uint64_t seenXfbBarrier01185Error = 0; if (seenXfbBarrier01185Error++) { return VK_FALSE; } isKnownIssue = true; } // Write this message to our normal logging if (!isKnownIssue) { gLog->warn("Vulkan Debug Report: {}, {}, {}, {}, {}, {}, {}", vk::to_string(static_cast<vk::DebugReportFlagsEXT>(flags)), vk::to_string(static_cast<vk::DebugReportObjectTypeEXT>(objectType)), object, location, messageCode, pLayerPrefix, pMessage); } else { gLog->warn("Vulkan Debug Report (Known Case): {}", pMessage); } if (!isKnownIssue) { platform::debugLog(fmt::format("vk-dbg: {}\n", pMessage)); } else { platform::debugLog(fmt::format("vk-dbg-ignored: {}\n", pMessage)); } // We should break to the debugger on unexpected situations. if (flags == VK_DEBUG_REPORT_WARNING_BIT_EXT || flags == VK_DEBUG_REPORT_ERROR_BIT_EXT) { if (!isKnownIssue) { platform::debugBreak(); } } return VK_FALSE; } static void registerDebugCallback(vk::Instance &instance, vk::DispatchLoaderDynamic &dispatchLoaderDynamic, void *pUserData) { if (!dispatchLoaderDynamic.vkCreateDebugReportCallbackEXT) { return; } auto dbgReportDesc = vk::DebugReportCallbackCreateInfoEXT { }; dbgReportDesc.flags = vk::DebugReportFlagBitsEXT::eDebug | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::ePerformanceWarning; dbgReportDesc.pfnCallback = debugMessageCallback; dbgReportDesc.pUserData = pUserData; instance.createDebugReportCallbackEXT(dbgReportDesc, nullptr, dispatchLoaderDynamic); } static bool getWindowSystemExtensions(gpu::WindowSystemType wsiType, std::vector<const char*> &extensions) { /* VK_USE_PLATFORM_ANDROID_KHR - Android VK_USE_PLATFORM_MIR_KHR - Mir */ switch (wsiType) { #if defined(VK_USE_PLATFORM_WIN32_KHR) case gpu::WindowSystemType::Windows: extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); break; #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) case gpu::WindowSystemType::X11: extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); break; #endif #if defined(VK_USE_PLATFORM_XCB_KHR) case gpu::WindowSystemType::Xcb: extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); break; #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) case gpu::WindowSystemType::Wayland: extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); break; #endif #if defined(VK_USE_PLATFORM_MACOS_MVK) case gpu::WindowSystemType::Cocoa: extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); break; #endif default: return false; } return true; } static vk::Instance createVulkanInstance(const gpu::WindowSystemInfo &wsi) { auto appInfo = vk::ApplicationInfo { "Decaf", VK_MAKE_VERSION(1, 0, 0), "DecafSDL", VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_0 }; std::vector<const char*> layers = { }; std::vector<const char*> extensions = { VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME }; if (gpu::config()->debug.debug_enabled) { layers.push_back("VK_LAYER_KHRONOS_validation"); extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); } if (!getWindowSystemExtensions(wsi.type, extensions)) { gLog->error("createVulkanInstance: Failed to get window system extensions for type {}", wsi.type); return { }; } if (!extensions.empty()) { fmt::memory_buffer msg; fmt::format_to(std::back_inserter(msg), "Creating instance with extensions:"); for (auto ext : extensions) { fmt::format_to(std::back_inserter(msg), " {}", ext); } gLog->debug({ msg.data(), msg.size() }); } if (!layers.empty()) { fmt::memory_buffer msg; fmt::format_to(std::back_inserter(msg), "Creating instance with layers:"); for (auto layer : layers) { fmt::format_to(std::back_inserter(msg), " {}", layer); } gLog->debug({ msg.data(), msg.size() }); } auto instanceCreateInfo = vk::InstanceCreateInfo { }; instanceCreateInfo.pApplicationInfo = &appInfo; instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(layers.size()); instanceCreateInfo.ppEnabledLayerNames = layers.data(); instanceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); instanceCreateInfo.ppEnabledExtensionNames = extensions.data(); return vk::createInstance(instanceCreateInfo); } static vk::PhysicalDevice choosePhysicalDevice(vk::Instance &instance) { auto physicalDevices = instance.enumeratePhysicalDevices(); return physicalDevices[0]; } static vk::SurfaceKHR createVulkanSurface(vk::Instance &instance, const gpu::WindowSystemInfo &wsi) { #if defined(VK_USE_PLATFORM_WIN32_KHR) if (wsi.type == gpu::WindowSystemType::Windows) { auto surfaceCreateInfo = vk::Win32SurfaceCreateInfoKHR { }; surfaceCreateInfo.hinstance = nullptr; surfaceCreateInfo.hwnd = reinterpret_cast<HWND>(wsi.renderSurface); return instance.createWin32SurfaceKHR(surfaceCreateInfo); } #endif #if defined(VK_USE_PLATFORM_XCB_KHR) if (wsi.type == gpu::WindowSystemType::Xcb) { auto surfaceCreateInfo = vk::XcbSurfaceCreateInfoKHR { }; surfaceCreateInfo.connection = static_cast<xcb_connection_t *>(wsi.displayConnection); surfaceCreateInfo.window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(wsi.renderSurface)); return instance.createXcbSurfaceKHR(surfaceCreateInfo); } #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) if (wsi.type == gpu::WindowSystemType::X11) { auto surfaceCreateInfo = vk::XlibSurfaceCreateInfoKHR { }; surfaceCreateInfo.dpy = static_cast<Display *>(wsi.displayConnection); surfaceCreateInfo.window = reinterpret_cast<Window>(wsi.renderSurface); return instance.createXlibSurfaceKHR(surfaceCreateInfo); } #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) if (wsi.type == gpu::WindowSystemType::Wayland) { auto surfaceCreateInfo = vk::WaylandSurfaceCreateInfoKHR { }; surfaceCreateInfo.display = static_cast<wl_display *>(wsi.displayConnection); surfaceCreateInfo.surface = static_cast<wl_surface *>(wsi.renderSurface); return instance.createWaylandSurfaceKHR(surfaceCreateInfo); } #endif #if defined(VK_USE_PLATFORM_MACOS_MVK) if (wsi.type == gpu::WindowSystemType::Cocoa) { auto surfaceCreateInfo = vk::MacOSSurfaceCreateInfoMVK { }; surfaceCreateInfo.pView = static_cast<const void *>(wsi.renderSurface); return instance.createMacOSSurfaceMVK(surfaceCreateInfo); } #endif return { }; } static vk::Format chooseSurfaceFormat(vk::PhysicalDevice &physicalDevice, vk::SurfaceKHR &surface) { auto formats = physicalDevice.getSurfaceFormatsKHR(surface); auto selected = formats[0]; for (auto &format : formats) { switch (format.format) { case vk::Format::eR8G8B8A8Srgb: case vk::Format::eB8G8R8A8Srgb: selected = format; break; } } return selected.format; } static std::tuple<vk::Device, uint32_t, vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTransformFeedbackFeaturesEXT> createDevice(vk::PhysicalDevice &physicalDevice, vk::SurfaceKHR &surface) { std::vector<const char*> deviceLayers = { }; std::vector<const char *> requiredExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, VK_KHR_MAINTENANCE1_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME, }; std::vector<const char *> missingRequiredExtensions = {}; std::vector<const char *> optionalExtensions = { VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, }; std::vector<const char *> missingOptionalExtensions = {}; std::vector<const char *> deviceExtensions = requiredExtensions; if (gpu::config()->debug.debug_enabled) { deviceLayers.push_back("VK_LAYER_KHRONOS_validation"); optionalExtensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); } auto supportedDeviceExtensions = physicalDevice.enumerateDeviceExtensionProperties(); for (auto &name : requiredExtensions) { auto hasExtension = false; for (auto &ext : supportedDeviceExtensions) { if (iequals(name, ext.extensionName.data())) { hasExtension = true; break; } } if (!hasExtension) { missingRequiredExtensions.push_back(name); } } for (auto name : optionalExtensions) { auto hasExtension = false; for (auto &ext : supportedDeviceExtensions) { if (iequals(name, ext.extensionName.data())) { hasExtension = true; break; } } if (hasExtension) { deviceExtensions.push_back(name); } else { missingOptionalExtensions.push_back(name); } } if (!missingRequiredExtensions.empty() || !missingOptionalExtensions.empty()) { fmt::memory_buffer msg; fmt::format_to(std::back_inserter(msg), "Not all Vulkan {} extensions supported:\n", !missingRequiredExtensions.empty() ? "optional" : "required"); fmt::format_to(std::back_inserter(msg), " Required:\n"); for (auto name : requiredExtensions) { fmt::format_to(std::back_inserter(msg), " - {}: {}\n", name, std::find(missingRequiredExtensions.begin(), missingRequiredExtensions.end(), name) == missingRequiredExtensions.end()); } fmt::format_to(std::back_inserter(msg), " Optional:\n"); for (auto name : optionalExtensions) { fmt::format_to(std::back_inserter(msg), " - {}: {}\n", name, std::find(missingOptionalExtensions.begin(), missingOptionalExtensions.end(), name) == missingOptionalExtensions.end()); } if (!missingRequiredExtensions.empty()) { gLog->error(std::string_view{ msg.data(), msg.size() }); } else { gLog->warn(std::string_view{ msg.data(), msg.size() }); } } auto features = physicalDevice.getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTransformFeedbackFeaturesEXT>(); auto supportedFeatures = features.get<vk::PhysicalDeviceFeatures2>(); auto supportedFeaturesTransformFeedback = features.get<vk::PhysicalDeviceTransformFeedbackFeaturesEXT>(); auto hasRequiredFeatures = supportedFeatures.features.depthClamp && supportedFeatures.features.textureCompressionBC && supportedFeatures.features.independentBlend && supportedFeatures.features.fillModeNonSolid && supportedFeatures.features.samplerAnisotropy; auto hasOptionalFeatures = supportedFeatures.features.geometryShader && supportedFeatures.features.wideLines && supportedFeatures.features.logicOp && supportedFeaturesTransformFeedback.transformFeedback && supportedFeaturesTransformFeedback.geometryStreams; if (!hasRequiredFeatures || !hasOptionalFeatures) { fmt::memory_buffer msg; fmt::format_to(std::back_inserter(msg), "Not all Vulkan {} features supported:\n", hasRequiredFeatures ? "optional" : "required"); fmt::format_to(std::back_inserter(msg), " Required:\n"); fmt::format_to(std::back_inserter(msg), " - depthClamp: {}\n", supportedFeatures.features.depthClamp); fmt::format_to(std::back_inserter(msg), " - textureCompressionBC: {}\n", supportedFeatures.features.textureCompressionBC); fmt::format_to(std::back_inserter(msg), " - independentBlend: {}\n", supportedFeatures.features.independentBlend); fmt::format_to(std::back_inserter(msg), " - fillModeNonSolid: {}\n", supportedFeatures.features.fillModeNonSolid); fmt::format_to(std::back_inserter(msg), " - samplerAnisotropy: {}\n", supportedFeatures.features.samplerAnisotropy); fmt::format_to(std::back_inserter(msg), " Optional:\n"); fmt::format_to(std::back_inserter(msg), " - geometryShader: {}\n", supportedFeatures.features.geometryShader); fmt::format_to(std::back_inserter(msg), " - wideLines: {}\n", supportedFeatures.features.wideLines); fmt::format_to(std::back_inserter(msg), " - logicOp: {}\n", supportedFeatures.features.logicOp); fmt::format_to(std::back_inserter(msg), " - transformFeedback: {}\n", supportedFeaturesTransformFeedback.transformFeedback); fmt::format_to(std::back_inserter(msg), " - geometryStreams: {}\n", supportedFeaturesTransformFeedback.geometryStreams); if (!hasRequiredFeatures) { gLog->error(std::string_view { msg.data(), msg.size() }); } else { gLog->warn(std::string_view{ msg.data(), msg.size() }); } } if (!missingRequiredExtensions.empty() || !hasRequiredFeatures) { return {}; } auto queueFamilyProps = physicalDevice.getQueueFamilyProperties(); auto queueFamilyIndex = uint32_t { 0 }; for (; queueFamilyIndex < queueFamilyProps.size(); ++queueFamilyIndex) { auto &qfp = queueFamilyProps[queueFamilyIndex]; if (!physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface)) { continue; } if (!(qfp.queueFlags & (vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer | vk::QueueFlagBits::eCompute))) { continue; } break; } if (queueFamilyIndex >= queueFamilyProps.size()) { gLog->error("Could not find compatible vulkan queue family"); return { }; } auto queuePriorities = std::array<float, 1> { 0.0f }; auto deviceQueueCreateInfo = vk::DeviceQueueCreateInfo { vk::DeviceQueueCreateFlags { }, queueFamilyIndex, static_cast<uint32_t>(queuePriorities.size()), queuePriorities.data() }; auto createDeviceChain = vk::StructureChain<vk::DeviceCreateInfo, vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTransformFeedbackFeaturesEXT> { }; auto &deviceCreateInfo = createDeviceChain.get<vk::DeviceCreateInfo>(); deviceCreateInfo.queueCreateInfoCount = 1; deviceCreateInfo.pQueueCreateInfos = &deviceQueueCreateInfo; deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(deviceLayers.size()); deviceCreateInfo.ppEnabledLayerNames = deviceLayers.data(); deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); auto &deviceCreateFeatures = createDeviceChain.get<vk::PhysicalDeviceFeatures2>(); deviceCreateFeatures.features.depthClamp = true; deviceCreateFeatures.features.textureCompressionBC = true; deviceCreateFeatures.features.independentBlend = true; deviceCreateFeatures.features.fillModeNonSolid = true; deviceCreateFeatures.features.samplerAnisotropy = true; deviceCreateFeatures.features.geometryShader = supportedFeatures.features.geometryShader; deviceCreateFeatures.features.wideLines = supportedFeatures.features.wideLines; deviceCreateFeatures.features.logicOp = supportedFeatures.features.logicOp; auto &deviceCreateFeaturesTransformFeedback = createDeviceChain.get<vk::PhysicalDeviceTransformFeedbackFeaturesEXT>(); deviceCreateFeaturesTransformFeedback.transformFeedback = supportedFeaturesTransformFeedback.transformFeedback; deviceCreateFeaturesTransformFeedback.geometryStreams = supportedFeaturesTransformFeedback.geometryStreams; auto device = physicalDevice.createDevice(deviceCreateInfo); return { device, queueFamilyIndex, supportedFeatures, supportedFeaturesTransformFeedback }; } static bool createRenderPass(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { // Create our render pass that targets this attachement auto colorAttachmentDesc = vk::AttachmentDescription { }; colorAttachmentDesc.format = displayPipeline.windowSurfaceFormat; colorAttachmentDesc.samples = vk::SampleCountFlagBits::e1; colorAttachmentDesc.loadOp = vk::AttachmentLoadOp::eClear; colorAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore; colorAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; colorAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; colorAttachmentDesc.initialLayout = vk::ImageLayout::eUndefined; colorAttachmentDesc.finalLayout = vk::ImageLayout::ePresentSrcKHR; auto colorAttachmentRef = vk::AttachmentReference { }; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; auto genericSubpass = vk::SubpassDescription { }; genericSubpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; genericSubpass.inputAttachmentCount = 0; genericSubpass.pInputAttachments = nullptr; genericSubpass.colorAttachmentCount = 1; genericSubpass.pColorAttachments = &colorAttachmentRef; genericSubpass.pResolveAttachments = 0; genericSubpass.pDepthStencilAttachment = nullptr; genericSubpass.preserveAttachmentCount = 0; genericSubpass.pPreserveAttachments = nullptr; auto renderPassDesc = vk::RenderPassCreateInfo { }; renderPassDesc.attachmentCount = 1; renderPassDesc.pAttachments = &colorAttachmentDesc; renderPassDesc.subpassCount = 1; renderPassDesc.pSubpasses = &genericSubpass; renderPassDesc.dependencyCount = 0; renderPassDesc.pDependencies = nullptr; displayPipeline.renderPass = device.createRenderPass(renderPassDesc); return !!displayPipeline.renderPass; } static vk::PresentModeKHR choosePresentMode(vk::PhysicalDevice &physicalDevice, vk::SurfaceKHR &surface) { auto presentModes = physicalDevice.getSurfacePresentModesKHR(surface); auto hasPresentMode = [&](vk::PresentModeKHR mode) { return std::find(presentModes.begin(), presentModes.end(), mode) != presentModes.end(); }; if (hasPresentMode(vk::PresentModeKHR::eMailbox)) { return vk::PresentModeKHR::eMailbox; } if (hasPresentMode(vk::PresentModeKHR::eImmediate)) { return vk::PresentModeKHR::eImmediate; } if (hasPresentMode(vk::PresentModeKHR::eFifo)) { return vk::PresentModeKHR::eFifo; } return presentModes[0]; } static bool createSwapchain(VulkanDisplayPipeline &displayPipeline, vk::PhysicalDevice &physicalDevice, vk::Device &device) { auto surfaceCaps = physicalDevice.getSurfaceCapabilitiesKHR(displayPipeline.windowSurface); displayPipeline.swapchainExtents = surfaceCaps.currentExtent; // Create the swap chain itself auto swapchainCreateInfo = vk::SwapchainCreateInfoKHR { }; swapchainCreateInfo.surface = displayPipeline.windowSurface; swapchainCreateInfo.minImageCount = surfaceCaps.minImageCount; swapchainCreateInfo.imageFormat = displayPipeline.windowSurfaceFormat; swapchainCreateInfo.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; swapchainCreateInfo.imageExtent = displayPipeline.swapchainExtents; swapchainCreateInfo.imageArrayLayers = 1; swapchainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc; swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive; swapchainCreateInfo.queueFamilyIndexCount = 0; swapchainCreateInfo.pQueueFamilyIndices = nullptr; swapchainCreateInfo.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; swapchainCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; swapchainCreateInfo.presentMode = displayPipeline.presentMode; swapchainCreateInfo.clipped = true; swapchainCreateInfo.oldSwapchain = nullptr; displayPipeline.swapchain = device.createSwapchainKHR(swapchainCreateInfo); // Create our framebuffers auto swapChainImages = device.getSwapchainImagesKHR(displayPipeline.swapchain); displayPipeline.swapchainImageViews.resize(swapChainImages.size()); displayPipeline.framebuffers.resize(swapChainImages.size()); for (auto i = 0u; i < swapChainImages.size(); ++i) { auto imageViewCreateInfo = vk::ImageViewCreateInfo { }; imageViewCreateInfo.image = swapChainImages[i]; imageViewCreateInfo.viewType = vk::ImageViewType::e2D; imageViewCreateInfo.format = displayPipeline.windowSurfaceFormat; imageViewCreateInfo.components = vk::ComponentMapping(); imageViewCreateInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; imageViewCreateInfo.subresourceRange.baseMipLevel = 0; imageViewCreateInfo.subresourceRange.levelCount = 1; imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; imageViewCreateInfo.subresourceRange.layerCount = 1; displayPipeline.swapchainImageViews[i] = device.createImageView(imageViewCreateInfo); auto framebufferCreateInfo = vk::FramebufferCreateInfo { }; framebufferCreateInfo.renderPass = displayPipeline.renderPass; framebufferCreateInfo.attachmentCount = 1; framebufferCreateInfo.pAttachments = &displayPipeline.swapchainImageViews[i]; framebufferCreateInfo.width = displayPipeline.swapchainExtents.width; framebufferCreateInfo.height = displayPipeline.swapchainExtents.height; framebufferCreateInfo.layers = 1; displayPipeline.framebuffers[i] = device.createFramebuffer(framebufferCreateInfo); } return true; } static void destroySwapchain(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { for (const auto &framebuffer : displayPipeline.framebuffers) { device.destroyFramebuffer(framebuffer); } displayPipeline.framebuffers.clear(); for (const auto &imageView : displayPipeline.swapchainImageViews) { device.destroyImageView(imageView); } displayPipeline.swapchainImageViews.clear(); device.destroySwapchainKHR(displayPipeline.swapchain); displayPipeline.swapchain = vk::SwapchainKHR { }; } static void recreateSwapchain(VulkanDisplayPipeline &displayPipeline, vk::PhysicalDevice &physicalDevice, vk::Device &device) { device.waitIdle(); destroySwapchain(displayPipeline, device); createSwapchain(displayPipeline, physicalDevice, device); } static bool createPipelineLayout(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { auto samplerCreateInfo = vk::SamplerCreateInfo { }; samplerCreateInfo.magFilter = vk::Filter::eLinear; samplerCreateInfo.minFilter = vk::Filter::eLinear; samplerCreateInfo.mipmapMode = vk::SamplerMipmapMode::eNearest; samplerCreateInfo.addressModeU = vk::SamplerAddressMode::eRepeat; samplerCreateInfo.addressModeV = vk::SamplerAddressMode::eRepeat; samplerCreateInfo.addressModeW = vk::SamplerAddressMode::eRepeat; samplerCreateInfo.mipLodBias = 0.0f; samplerCreateInfo.anisotropyEnable = false; samplerCreateInfo.maxAnisotropy = 0.0f; samplerCreateInfo.compareEnable = false; samplerCreateInfo.compareOp = vk::CompareOp::eAlways; samplerCreateInfo.minLod = 0.0f; samplerCreateInfo.maxLod = 0.0f; samplerCreateInfo.borderColor = vk::BorderColor::eFloatTransparentBlack; samplerCreateInfo.unnormalizedCoordinates = false; displayPipeline.trivialSampler = device.createSampler(samplerCreateInfo); auto immutableSamplers = std::array<vk::Sampler, 1> { displayPipeline.trivialSampler, }; auto bindings = std::array<vk::DescriptorSetLayoutBinding, 2> { vk::DescriptorSetLayoutBinding { 0, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment, immutableSamplers.data() }, vk::DescriptorSetLayoutBinding { 1, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eFragment, nullptr }, }; auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo { }; descriptorSetLayoutCreateInfo.bindingCount = static_cast<uint32_t>(bindings.size()); descriptorSetLayoutCreateInfo.pBindings = bindings.data(); displayPipeline.descriptorSetLayout = device.createDescriptorSetLayout(descriptorSetLayoutCreateInfo); auto layoutBindings = std::array<vk::DescriptorSetLayout, 1> { displayPipeline.descriptorSetLayout }; auto pipelineLayoutCreateInfo = vk::PipelineLayoutCreateInfo { }; pipelineLayoutCreateInfo.setLayoutCount = static_cast<uint32_t>(layoutBindings.size()); pipelineLayoutCreateInfo.pSetLayouts = layoutBindings.data(); pipelineLayoutCreateInfo.pushConstantRangeCount = 0; pipelineLayoutCreateInfo.pPushConstantRanges = nullptr; displayPipeline.pipelineLayout = device.createPipelineLayout(pipelineLayoutCreateInfo); return true; } static bool createRenderPipeline(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { auto scanbufferVertBytesSize = sizeof(scanbufferVertBytes) / sizeof(scanbufferVertBytes[0]); displayPipeline.vertexShader = device.createShaderModule( vk::ShaderModuleCreateInfo { {}, scanbufferVertBytesSize, reinterpret_cast<const uint32_t*>(scanbufferVertBytes) }); auto scanbufferFragBytesSize = sizeof(scanbufferFragBytes) / sizeof(scanbufferFragBytes[0]); displayPipeline.fragmentShader = device.createShaderModule( vk::ShaderModuleCreateInfo { {}, scanbufferFragBytesSize, reinterpret_cast<const uint32_t*>(scanbufferFragBytes) }); auto shaderStages = std::array<vk::PipelineShaderStageCreateInfo, 2> { vk::PipelineShaderStageCreateInfo { {}, vk::ShaderStageFlagBits::eVertex, displayPipeline.vertexShader, "main", nullptr, }, vk::PipelineShaderStageCreateInfo { {}, vk::ShaderStageFlagBits::eFragment, displayPipeline.fragmentShader, "main", nullptr, }, }; auto vtxBindings = std::array<vk::VertexInputBindingDescription, 1> { vk::VertexInputBindingDescription { 0, 16, vk::VertexInputRate::eVertex }, }; auto vtxAttribs = std::array<vk::VertexInputAttributeDescription, 2> { vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32Sfloat, 0 }, vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32Sfloat, 8 }, }; // Vertex input stage, we store all our vertices in the actual shaders auto vertexInputInfo = vk::PipelineVertexInputStateCreateInfo { }; vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(vtxBindings.size()); vertexInputInfo.pVertexBindingDescriptions = vtxBindings.data(); vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(vtxAttribs.size()); vertexInputInfo.pVertexAttributeDescriptions = vtxAttribs.data(); auto inputAssembly = vk::PipelineInputAssemblyStateCreateInfo { }; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; inputAssembly.primitiveRestartEnable = false; auto viewport = vk::Viewport { 0.0f, 0.0f, static_cast<float>(displayPipeline.swapchainExtents.width), static_cast<float>(displayPipeline.swapchainExtents.height), 0.0f, 0.0f, }; auto scissor = vk::Rect2D { { 0,0 }, displayPipeline.swapchainExtents }; auto viewportState = vk::PipelineViewportStateCreateInfo { }; viewportState.viewportCount = 1; viewportState.pViewports = &viewport; viewportState.scissorCount = 1; viewportState.pScissors = &scissor; auto rasterizer = vk::PipelineRasterizationStateCreateInfo { }; rasterizer.depthClampEnable = false; rasterizer.rasterizerDiscardEnable = false; rasterizer.polygonMode = vk::PolygonMode::eFill; rasterizer.cullMode = vk::CullModeFlagBits::eNone; rasterizer.frontFace = vk::FrontFace::eClockwise; rasterizer.depthBiasEnable = false; rasterizer.depthBiasConstantFactor = 0.0f; rasterizer.depthBiasClamp = 0.0f; rasterizer.depthBiasSlopeFactor = 0.0f; rasterizer.lineWidth = 1.0f; auto multisampling = vk::PipelineMultisampleStateCreateInfo { }; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; multisampling.sampleShadingEnable = false; multisampling.minSampleShading = 1.0f; multisampling.pSampleMask = nullptr; multisampling.alphaToCoverageEnable = false; multisampling.alphaToOneEnable = false; auto colorBlendAttachement0 = vk::PipelineColorBlendAttachmentState { }; colorBlendAttachement0.blendEnable = false; colorBlendAttachement0.srcColorBlendFactor = vk::BlendFactor::eOne; colorBlendAttachement0.dstColorBlendFactor = vk::BlendFactor::eZero; colorBlendAttachement0.colorBlendOp = vk::BlendOp::eAdd; colorBlendAttachement0.srcAlphaBlendFactor = vk::BlendFactor::eOne; colorBlendAttachement0.dstAlphaBlendFactor = vk::BlendFactor::eZero; colorBlendAttachement0.alphaBlendOp = vk::BlendOp::eAdd; colorBlendAttachement0.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; auto colorBlendAttachments = std::vector<vk::PipelineColorBlendAttachmentState> { colorBlendAttachement0 }; auto colorBlendState = vk::PipelineColorBlendStateCreateInfo { }; colorBlendState.logicOpEnable = false; colorBlendState.logicOp = vk::LogicOp::eCopy; colorBlendState.attachmentCount = static_cast<uint32_t>(colorBlendAttachments.size()); colorBlendState.pAttachments = colorBlendAttachments.data(); colorBlendState.blendConstants[0] = 0.0f; colorBlendState.blendConstants[1] = 0.0f; colorBlendState.blendConstants[2] = 0.0f; colorBlendState.blendConstants[3] = 0.0f; auto dynamicStates = std::vector<vk::DynamicState> { vk::DynamicState::eViewport, vk::DynamicState::eScissor, }; auto dynamicState = vk::PipelineDynamicStateCreateInfo { }; dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size()); dynamicState.pDynamicStates = dynamicStates.data(); auto pipelineInfo = vk::GraphicsPipelineCreateInfo { }; pipelineInfo.pStages = shaderStages.data(); pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size()); pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pTessellationState = nullptr; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = nullptr; pipelineInfo.pColorBlendState = &colorBlendState; pipelineInfo.pDynamicState = &dynamicState; pipelineInfo.layout = displayPipeline.pipelineLayout; pipelineInfo.renderPass = displayPipeline.renderPass; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = vk::Pipeline { }; pipelineInfo.basePipelineIndex = -1; auto result = device.createGraphicsPipeline(vk::PipelineCache { }, pipelineInfo); if (result.result != vk::Result::eSuccess) { return false; } displayPipeline.graphicsPipeline = result.value; return true; } static bool createDescriptorPools(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { auto descriptorPoolSizes = std::vector<vk::DescriptorPoolSize> { vk::DescriptorPoolSize { vk::DescriptorType::eSampler, 100 }, vk::DescriptorPoolSize { vk::DescriptorType::eSampledImage, 100 }, vk::DescriptorPoolSize { vk::DescriptorType::eCombinedImageSampler, 100 }, }; auto createInfo = vk::DescriptorPoolCreateInfo { }; createInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size()); createInfo.pPoolSizes = descriptorPoolSizes.data(); createInfo.maxSets = static_cast<uint32_t>(descriptorPoolSizes.size() * 100); displayPipeline.descriptorPool = device.createDescriptorPool(createInfo); return true; } static std::optional<uint32_t> chooseMemoryTypeIndex(vk::PhysicalDevice &physicalDevice, uint32_t typeFilter, vk::MemoryPropertyFlags propertyFlags) { auto memoryProperties = physicalDevice.getMemoryProperties(); for (auto i = uint32_t { 0 }; i < memoryProperties.memoryTypeCount; i++) { if ((typeFilter & (1 << i)) == 0) { continue; } if ((memoryProperties.memoryTypes[i].propertyFlags & propertyFlags) != propertyFlags) { continue; } return i; } return { }; } static bool createBuffers(VulkanDisplayPipeline &displayPipeline, vk::PhysicalDevice &physicalDevice, vk::Device &device) { static constexpr std::array<float, 24> vertices = { -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, }; // Allocate buffer auto bufferDesc = vk::BufferCreateInfo { }; bufferDesc.size = static_cast<uint32_t>(sizeof(float) * vertices.size()); bufferDesc.usage = vk::BufferUsageFlagBits::eVertexBuffer; bufferDesc.sharingMode = vk::SharingMode::eExclusive; bufferDesc.queueFamilyIndexCount = 1; bufferDesc.pQueueFamilyIndices = &displayPipeline.queueFamilyIndex; displayPipeline.vertexBuffer = device.createBuffer(bufferDesc); auto bufferMemoryRequirements = device.getBufferMemoryRequirements(displayPipeline.vertexBuffer); auto memoryTypeIndex = chooseMemoryTypeIndex(physicalDevice, bufferMemoryRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible); if (!memoryTypeIndex) { return false; } auto allocateInfo = vk::MemoryAllocateInfo { }; allocateInfo.allocationSize = bufferMemoryRequirements.size; allocateInfo.memoryTypeIndex = *memoryTypeIndex; displayPipeline.vertexBufferMemory = device.allocateMemory(allocateInfo); device.bindBufferMemory(displayPipeline.vertexBuffer, displayPipeline.vertexBufferMemory, 0); // Upload vertices auto mappedMemory = device.mapMemory(displayPipeline.vertexBufferMemory, 0, VK_WHOLE_SIZE); std::memcpy(mappedMemory, vertices.data(), bufferMemoryRequirements.size); device.flushMappedMemoryRanges({ vk::MappedMemoryRange { displayPipeline.vertexBufferMemory, 0, VK_WHOLE_SIZE } }); device.unmapMemory(displayPipeline.vertexBufferMemory); return true; } static bool createDescriptorSets(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { displayPipeline.descriptorSets.resize(displayPipeline.framebuffers.size() * 2); for (auto i = 0u; i < displayPipeline.descriptorSets.size(); ++i) { auto allocateInfo = vk::DescriptorSetAllocateInfo { }; allocateInfo.descriptorPool = displayPipeline.descriptorPool; allocateInfo.descriptorSetCount = 1; allocateInfo.pSetLayouts = &displayPipeline.descriptorSetLayout; displayPipeline.descriptorSets[i] = device.allocateDescriptorSets(allocateInfo)[0]; } return true; } static bool createFences(VulkanDisplayPipeline &displayPipeline, vk::Device &device) { displayPipeline.imageAvailableSemaphores.resize(displayPipeline.framebuffers.size()); displayPipeline.renderFinishedSemaphores.resize(displayPipeline.framebuffers.size()); displayPipeline.renderFences.resize(displayPipeline.framebuffers.size()); for (auto i = 0u; i < displayPipeline.framebuffers.size(); ++i) { displayPipeline.imageAvailableSemaphores[i] = device.createSemaphore(vk::SemaphoreCreateInfo { }); displayPipeline.renderFinishedSemaphores[i] = device.createSemaphore(vk::SemaphoreCreateInfo { }); displayPipeline.renderFences[i] = device.createFence(vk::FenceCreateInfo { vk::FenceCreateFlagBits::eSignaled }); } displayPipeline.frameIndex = 0; return true; } void Driver::setWindowSystemInfo(const gpu::WindowSystemInfo &wsi) { auto instance = createVulkanInstance(wsi); if (!instance) { decaf_abort("createVulkanInstance failed"); } mVkDynLoader.init(instance, ::vkGetInstanceProcAddr); registerDebugCallback(instance, mVkDynLoader, reinterpret_cast<void *>(this)); auto physicalDevice = choosePhysicalDevice(instance); if (!physicalDevice) { decaf_abort("choosePhysicalDevice failed"); } auto windowSurface = createVulkanSurface(instance, wsi); if (!windowSurface) { decaf_abort("createVulkanSurface failed"); } auto surfaceFormat = chooseSurfaceFormat(physicalDevice, windowSurface); if (!windowSurface) { decaf_abort("chooseSurfaceFormat failed"); } auto [device, queueFamilyIndex, supportedFeatures, supportedFeaturesTransformFeedback] = createDevice(physicalDevice, windowSurface); if (!device) { decaf_abort("createDevice failed"); } auto queue = device.getQueue(queueFamilyIndex, 0); if (!queue) { decaf_abort("device.getQueue failed"); } mSupportedFeatures = supportedFeatures; mSupportedFeaturesTransformFeedback = supportedFeaturesTransformFeedback; initialise(instance, physicalDevice, device, queue, queueFamilyIndex); // Create our full display pipeline mDisplayPipeline.windowSurface = windowSurface; mDisplayPipeline.windowSurfaceFormat = surfaceFormat; mDisplayPipeline.queueFamilyIndex = queueFamilyIndex; mDisplayPipeline.presentMode = choosePresentMode(physicalDevice, windowSurface); if (!createRenderPass(mDisplayPipeline, device)) { decaf_abort("createRenderPass failed"); } if (!createSwapchain(mDisplayPipeline, physicalDevice, device)) { decaf_abort("createSwapchain failed"); } if (!createPipelineLayout(mDisplayPipeline, device)) { decaf_abort("createPipelineLayout failed"); } if (!createRenderPipeline(mDisplayPipeline, device)) { decaf_abort("createRenderPipeline failed"); } if (!createDescriptorPools(mDisplayPipeline, device)) { decaf_abort("createDescriptorPools failed"); } if (!createBuffers(mDisplayPipeline, physicalDevice, device)) { decaf_abort("createBuffers failed"); } if (!createDescriptorSets(mDisplayPipeline, device)) { decaf_abort("createDescriptorSets failed"); } if (!createFences(mDisplayPipeline, device)) { decaf_abort("createFences failed"); } } void Driver::destroyDisplayPipeline() { // createFences for (auto &semaphore : mDisplayPipeline.imageAvailableSemaphores) { mDevice.destroySemaphore(semaphore); } mDisplayPipeline.imageAvailableSemaphores.clear(); for (auto &semaphore : mDisplayPipeline.renderFinishedSemaphores) { mDevice.destroySemaphore(semaphore); } mDisplayPipeline.renderFinishedSemaphores.clear(); for (auto &fence : mDisplayPipeline.renderFences) { mDevice.destroyFence(fence); } mDisplayPipeline.renderFences.clear(); // createDescriptorSets mDevice.freeDescriptorSets(mDisplayPipeline.descriptorPool, mDisplayPipeline.descriptorSets); mDisplayPipeline.descriptorSets.clear(); // createBuffers mDevice.destroyBuffer(mDisplayPipeline.vertexBuffer); mDevice.freeMemory(mDisplayPipeline.vertexBufferMemory); mDisplayPipeline.vertexBuffer = nullptr; mDisplayPipeline.vertexBufferMemory = nullptr; // createDescriptorPools mDevice.destroyDescriptorPool(mDisplayPipeline.descriptorPool); mDisplayPipeline.descriptorPool = nullptr; // createRenderPipeline mDevice.destroyShaderModule(mDisplayPipeline.vertexShader); mDevice.destroyShaderModule(mDisplayPipeline.fragmentShader); mDevice.destroyPipeline(mDisplayPipeline.graphicsPipeline); mDisplayPipeline.vertexShader = nullptr; mDisplayPipeline.fragmentShader = nullptr; mDisplayPipeline.graphicsPipeline = nullptr; // createPipelineLayout mDevice.destroySampler(mDisplayPipeline.trivialSampler); mDevice.destroyDescriptorSetLayout(mDisplayPipeline.descriptorSetLayout); mDevice.destroyPipelineLayout(mDisplayPipeline.pipelineLayout); mDisplayPipeline.trivialSampler = nullptr; mDisplayPipeline.descriptorSetLayout = nullptr; mDisplayPipeline.pipelineLayout = nullptr; // createSwapchain destroySwapchain(mDisplayPipeline, mDevice); // createRenderPass mDevice.destroyRenderPass(mDisplayPipeline.renderPass); mDisplayPipeline.renderPass = nullptr; } static void acquireScanBuffer(vk::Device &device, vk::CommandBuffer cmdBuffer, vk::DescriptorSet descriptorSet, vk::Image image, vk::ImageView imageView) { auto imageBarrier = vk::ImageMemoryBarrier { }; imageBarrier.srcAccessMask = vk::AccessFlags { }; imageBarrier.dstAccessMask = vk::AccessFlags { }; imageBarrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; imageBarrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.image = image; imageBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; imageBarrier.subresourceRange.baseMipLevel = 0; imageBarrier.subresourceRange.levelCount = 1; imageBarrier.subresourceRange.baseArrayLayer = 0; imageBarrier.subresourceRange.layerCount = 1; cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, vk::DependencyFlagBits::eByRegion, {}, {}, { imageBarrier }); auto descriptorImageInfo = vk::DescriptorImageInfo { }; descriptorImageInfo.imageView = imageView; descriptorImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; auto writeDescriptorSet = vk::WriteDescriptorSet { }; writeDescriptorSet.dstSet = descriptorSet; writeDescriptorSet.dstBinding = 1; writeDescriptorSet.dstArrayElement = 0; writeDescriptorSet.descriptorCount = 1; writeDescriptorSet.descriptorType = vk::DescriptorType::eSampledImage; writeDescriptorSet.pImageInfo = &descriptorImageInfo; device.updateDescriptorSets({ writeDescriptorSet }, {}); } static void renderScanBuffer(VulkanDisplayPipeline &displayPipeline, vk::Viewport viewport, vk::CommandBuffer cmdBuffer, vk::DescriptorSet descriptorSet, vk::Image image, vk::ImageView imageView) { cmdBuffer.setViewport(0, { viewport }); cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, displayPipeline.pipelineLayout, 0, { descriptorSet }, {}); cmdBuffer.bindVertexBuffers(0, { displayPipeline.vertexBuffer }, { 0 }); cmdBuffer.draw(6, 1, 0, 0); } static void releaseScanBuffer(vk::CommandBuffer cmdBuffer, vk::DescriptorSet descriptorSet, vk::Image image, vk::ImageView imageView) { auto imageBarrier = vk::ImageMemoryBarrier { }; imageBarrier.srcAccessMask = vk::AccessFlags { }; imageBarrier.dstAccessMask = vk::AccessFlags { }; imageBarrier.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal; imageBarrier.newLayout = vk::ImageLayout::eTransferDstOptimal; imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.image = image; imageBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; imageBarrier.subresourceRange.baseMipLevel = 0; imageBarrier.subresourceRange.levelCount = 1; imageBarrier.subresourceRange.baseArrayLayer = 0; imageBarrier.subresourceRange.layerCount = 1; cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, vk::DependencyFlagBits::eByRegion, {}, {}, { imageBarrier }); } void Driver::renderDisplay() { auto frameIndex = mDisplayPipeline.frameIndex; auto &renderFence = mDisplayPipeline.renderFences[frameIndex]; auto &imageAvailableSemaphore = mDisplayPipeline.imageAvailableSemaphores[frameIndex]; auto &renderFinishedSemaphore = mDisplayPipeline.renderFinishedSemaphores[frameIndex]; // Wait for the previous frame to finish auto result = mDevice.waitForFences({ renderFence }, true, std::numeric_limits<uint64_t>::max()); mDevice.resetFences({ renderFence }); // Acquire the next frame to render into auto nextSwapImage = uint32_t { 0 }; try { result = mDevice.acquireNextImageKHR(mDisplayPipeline.swapchain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, vk::Fence {}, &nextSwapImage); } catch (vk::OutOfDateKHRError err) { result = vk::Result::eErrorOutOfDateKHR; } if (result == vk::Result::eErrorOutOfDateKHR) { // Recreate swapchain recreateSwapchain(mDisplayPipeline, mPhysDevice, mDevice); // Try acquire again, this one will die if it fails :D result = mDevice.acquireNextImageKHR(mDisplayPipeline.swapchain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, vk::Fence {}, &nextSwapImage); } // Allocate a command buffer to use auto renderCmdBuf = mDevice.allocateCommandBuffers( vk::CommandBufferAllocateInfo { mCommandPool, vk::CommandBufferLevel::ePrimary, 1 })[0]; // Select some descriptors to use auto descriptorSetTv = mDisplayPipeline.descriptorSets[frameIndex * 2 + 0]; auto descriptorSetDrc = mDisplayPipeline.descriptorSets[frameIndex * 2 + 1]; // Setup render layout const auto displayWidth = mDisplayPipeline.swapchainExtents.width; const auto displayHeight = mDisplayPipeline.swapchainExtents.height; auto layout = gpu7::DisplayLayout { }; gpu7::updateDisplayLayout( layout, static_cast<float>(displayWidth), static_cast<float>(displayHeight)); if (layout.tv.visible) { layout.tv.visible = (mTvSwapChain && mTvSwapChain->presentable); } if (layout.drc.visible) { layout.drc.visible = (mDrcSwapChain && mDrcSwapChain->presentable); } renderCmdBuf.begin(vk::CommandBufferBeginInfo({}, nullptr)); { renderCmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, mDisplayPipeline.graphicsPipeline); renderCmdBuf.setScissor(0, { vk::Rect2D { { 0, 0 }, mDisplayPipeline.swapchainExtents } }); if (layout.tv.visible) { acquireScanBuffer(mDevice, renderCmdBuf, descriptorSetTv, mTvSwapChain->image, mTvSwapChain->imageView); } if (layout.drc.visible) { acquireScanBuffer(mDevice, renderCmdBuf, descriptorSetDrc, mDrcSwapChain->image, mDrcSwapChain->imageView); } auto renderPassBeginInfo = vk::RenderPassBeginInfo { }; renderPassBeginInfo.renderPass = mDisplayPipeline.renderPass; renderPassBeginInfo.framebuffer = mDisplayPipeline.framebuffers[nextSwapImage]; renderPassBeginInfo.renderArea = vk::Rect2D { { 0, 0 }, { displayWidth, displayHeight }, }; auto clearValue = vk::ClearValue { layout.backgroundColour }; renderPassBeginInfo.clearValueCount = 1; renderPassBeginInfo.pClearValues = &clearValue; renderCmdBuf.beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline); if (layout.tv.visible) { auto tvViewport = vk::Viewport { layout.tv.x, layout.tv.y, layout.tv.width, layout.tv.height }; renderScanBuffer(mDisplayPipeline, tvViewport, renderCmdBuf, descriptorSetTv, mTvSwapChain->image, mTvSwapChain->imageView); } if (layout.drc.visible) { auto drcViewport = vk::Viewport { layout.drc.x, layout.drc.y, layout.drc.width, layout.drc.height }; renderScanBuffer(mDisplayPipeline, drcViewport, renderCmdBuf, descriptorSetDrc, mDrcSwapChain->image, mDrcSwapChain->imageView); } renderCmdBuf.endRenderPass(); if (layout.tv.visible) { releaseScanBuffer(renderCmdBuf, descriptorSetTv, mTvSwapChain->image, mTvSwapChain->imageView); } if (layout.drc.visible) { releaseScanBuffer(renderCmdBuf, descriptorSetDrc, mDrcSwapChain->image, mDrcSwapChain->imageView); } } renderCmdBuf.end(); { auto submitInfo = vk::SubmitInfo { }; auto waitSemaphores = std::array<vk::Semaphore, 1> { imageAvailableSemaphore }; submitInfo.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()); submitInfo.pWaitSemaphores = waitSemaphores.data(); auto waitStage = vk::PipelineStageFlags { vk::PipelineStageFlagBits::eColorAttachmentOutput }; submitInfo.pWaitDstStageMask = &waitStage; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &renderCmdBuf; auto signalSemaphores = std::array<vk::Semaphore, 1> { renderFinishedSemaphore }; submitInfo.signalSemaphoreCount = static_cast<uint32_t>(signalSemaphores.size()); submitInfo.pSignalSemaphores = signalSemaphores.data(); mQueue.submit({ submitInfo }, renderFence); } { auto presentInfo = vk::PresentInfoKHR { }; auto waitSemaphores = std::array<vk::Semaphore, 1> { renderFinishedSemaphore }; presentInfo.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()); presentInfo.pWaitSemaphores = waitSemaphores.data(); presentInfo.pImageIndices = &nextSwapImage; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &mDisplayPipeline.swapchain; try { result = mQueue.presentKHR(presentInfo); } catch (vk::OutOfDateKHRError err) { result = vk::Result::eErrorOutOfDateKHR; } if (result == vk::Result::eErrorOutOfDateKHR) { recreateSwapchain(mDisplayPipeline, mPhysDevice, mDevice); } } mDisplayPipeline.frameIndex = (frameIndex + 1) % 2; } void Driver::windowHandleChanged(void *handle) { } void Driver::windowSizeChanged(int width, int height) { // Nothing to do, this will be handled during drawing } } // namespace vulkan #endif ================================================ FILE: src/libgpu/src/vulkan/vulkan_displayshaders.h ================================================ #pragma once #include <array> /* #version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec2 inPosition; layout(location = 1) in vec2 inTexCoord; out gl_PerVertex { vec4 gl_Position; }; layout(location = 0) out vec2 fragTexCoord; void main() { gl_Position = vec4(inPosition, 0.0, 1.0); fragTexCoord = inTexCoord; } */ static constexpr unsigned char scanbufferVertBytes[] = { 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x09, 0x00, 0x47, 0x4C, 0x5F, 0x41, 0x52, 0x42, 0x5F, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5F, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x73, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x19, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x20, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x17, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, }; /* #version 450 #extension GL_ARB_separate_shader_objects : enable layout(binding = 0) uniform sampler texSampler; layout(binding = 1) uniform texture2D colorTex; layout(location = 0) in vec2 fragTexCoord; layout(location = 0) out vec4 outFragColor; void main() { outFragColor = texture(sampler2D(colorTex, texSampler), fragTexCoord); } */ static constexpr unsigned char scanbufferFragBytes[] = { 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x09, 0x00, 0x47, 0x4C, 0x5F, 0x41, 0x52, 0x42, 0x5F, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5F, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x73, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x54, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x02, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, }; ================================================ FILE: src/libgpu/src/vulkan/vulkan_draw.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include <common/log.h> namespace vulkan { void Driver::bindDescriptors() { bool dSetHasValues = false; std::array<std::array<vk::DescriptorImageInfo, latte::MaxTextures>, 3> texSampInfos; std::array<std::array<vk::DescriptorBufferInfo, latte::MaxUniformBlocks>, 3 > bufferInfos; for (auto shaderStage = 0u; shaderStage < 3u; ++shaderStage) { auto shaderStageTyped = static_cast<ShaderStage>(shaderStage); const spirv::ShaderMeta *shaderMeta; if (shaderStageTyped == ShaderStage::Vertex) { if (!mCurrentDraw->vertexShader) { continue; } shaderMeta = &mCurrentDraw->vertexShader->shader.meta; } else if (shaderStageTyped == ShaderStage::Geometry) { if (!mCurrentDraw->geometryShader) { continue; } shaderMeta = &mCurrentDraw->geometryShader->shader.meta; } else if (shaderStageTyped == ShaderStage::Pixel) { if (!mCurrentDraw->pixelShader) { continue; } shaderMeta = &mCurrentDraw->pixelShader->shader.meta; } else { decaf_abort("Unexpected shader stage during descriptor build"); } for (auto i = 0u; i < latte::MaxSamplers; ++i) { if (shaderMeta->samplerUsed[i]) { auto &sampler = mCurrentDraw->samplers[shaderStage][i]; if (sampler) { texSampInfos[shaderStage][i].sampler = sampler->sampler; } else { texSampInfos[shaderStage][i].sampler = mBlankSampler; } dSetHasValues = true; } } for (auto i = 0u; i < latte::MaxTextures; ++i) { if (shaderMeta->textureUsed[i]) { auto &texture = mCurrentDraw->textures[shaderStage][i]; if (texture) { texSampInfos[shaderStage][i].imageView = texture->imageView; } else { texSampInfos[shaderStage][i].imageView = mBlankImageView; } texSampInfos[shaderStage][i].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; dSetHasValues = true; } } if (mCurrentDraw->gprBuffers[shaderStage]) { if (shaderMeta->cfileUsed) { auto gprBuffer = mCurrentDraw->gprBuffers[shaderStage]; bufferInfos[shaderStage][0].buffer = gprBuffer->buffer; bufferInfos[shaderStage][0].offset = 0; bufferInfos[shaderStage][0].range = gprBuffer->size; dSetHasValues = true; } } else { for (auto i = 0u; i < latte::MaxUniformBlocks; ++i) { if (shaderMeta->cbufferUsed[i]) { auto& uniformBuffer = mCurrentDraw->uniformBlocks[shaderStage][i]; if (uniformBuffer) { bufferInfos[shaderStage][i].buffer = uniformBuffer->buffer; bufferInfos[shaderStage][i].offset = 0; bufferInfos[shaderStage][i].range = uniformBuffer->size; } else { bufferInfos[shaderStage][i].buffer = mBlankBuffer; bufferInfos[shaderStage][i].offset = 0; bufferInfos[shaderStage][i].range = 1024; } dSetHasValues = true; } } } } // If this shader stage has nothing bound, there is no need to // actually generate our descriptor sets or anything. if (!dSetHasValues) { return; } /* for (auto shaderStage = 0u; shaderStage < 3u; ++shaderStage) { for (auto &samplerInfo : samplerInfos[shaderStage]) { if (!samplerInfo.sampler) { samplerInfo.sampler = mBlankSampler; } } for (auto &textureInfo : textureInfos[shaderStage]) { if (!textureInfo.imageView) { textureInfo.imageView = mBlankImageView; textureInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; } } for (auto &bufferInfo : bufferInfos[shaderStage]) { if (!bufferInfo.buffer) { bufferInfo.buffer = mBlankBuffer; bufferInfo.offset = 0; bufferInfo.range = 1; } } } */ vk::DescriptorSet dSet; if (!mCurrentDraw->pipeline->pipelineLayout) { // If there is no custom pipeline layout configured, this means we have to use // standard descriptor sets rather than being able to take advantage of push. dSet = allocateGenericDescriptorSet(); } mScratchDescriptorWrites.clear(); auto &descWrites = mScratchDescriptorWrites; for (auto shaderStage = 0u; shaderStage < 3u; ++shaderStage) { auto bindingBase = 32 * shaderStage; for (auto i = 0u; i < latte::MaxTextures; ++i) { if (!texSampInfos[shaderStage][i].sampler && !texSampInfos[shaderStage][i].imageView) { continue; } descWrites.push_back({}); vk::WriteDescriptorSet& writeDesc = descWrites.back(); writeDesc.dstSet = dSet; writeDesc.dstBinding = bindingBase + i; writeDesc.dstArrayElement = 0; writeDesc.descriptorCount = 1; if (texSampInfos[shaderStage][i].sampler && texSampInfos[shaderStage][i].imageView) { writeDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler; } else if (texSampInfos[shaderStage][i].sampler) { writeDesc.descriptorType = vk::DescriptorType::eSampler; } else if (texSampInfos[shaderStage][i].imageView) { writeDesc.descriptorType = vk::DescriptorType::eSampledImage; } writeDesc.pImageInfo = &texSampInfos[shaderStage][i]; writeDesc.pBufferInfo = nullptr; writeDesc.pTexelBufferView = nullptr; } bindingBase += latte::MaxTextures; for (auto i = 0u; i < latte::MaxUniformBlocks; ++i) { if (i >= 15) { // Refer to the descriptor layout creation code for information // on why this code is neccessary... break; } if (!bufferInfos[shaderStage][i].buffer) { continue; } descWrites.push_back({}); vk::WriteDescriptorSet& writeDesc = descWrites.back(); writeDesc.dstSet = dSet; writeDesc.dstBinding = bindingBase + i; writeDesc.dstArrayElement = 0; writeDesc.descriptorCount = 1; writeDesc.descriptorType = vk::DescriptorType::eStorageBuffer; writeDesc.pImageInfo = nullptr; writeDesc.pBufferInfo = &bufferInfos[shaderStage][i]; writeDesc.pTexelBufferView = nullptr; } } if (!dSet) { mActiveCommandBuffer.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, mCurrentDraw->pipeline->pipelineLayout->pipelineLayout, 0, descWrites, mVkDynLoader); } else { mDevice.updateDescriptorSets(descWrites, {}); mActiveCommandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayout, 0, { dSet }, {}); } } void Driver::bindShaderParams() { // This should probably be split to its own function if (mCurrentDraw->vertexShader) { spirv::VertexPushConstants vsConstData; vsConstData.posMulAdd.x = mCurrentDraw->shaderViewportData.xMul; vsConstData.posMulAdd.y = mCurrentDraw->shaderViewportData.yMul; vsConstData.posMulAdd.z = mCurrentDraw->shaderViewportData.xAdd; vsConstData.posMulAdd.w = mCurrentDraw->shaderViewportData.yAdd; vsConstData.zSpaceMul.x = mCurrentDraw->shaderViewportData.zAdd; vsConstData.zSpaceMul.y = mCurrentDraw->shaderViewportData.zMul; *reinterpret_cast<uint32_t*>(&vsConstData.zSpaceMul.z) = mCurrentDraw->baseVertex; *reinterpret_cast<uint32_t*>(&vsConstData.zSpaceMul.w) = mCurrentDraw->baseInstance; vsConstData.pointSize = mCurrentDraw->pointSize / 8.0f; if (!mActiveVsConstantsSet || memcmp(&vsConstData, &mActiveVsConstants, sizeof(vsConstData)) != 0) { mActiveVsConstants = vsConstData; mActiveVsConstantsSet = true; mActiveCommandBuffer.pushConstants<spirv::VertexPushConstants>( mPipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eGeometry, spirv::VertexPushConstantsOffset, { vsConstData }); } } if (mCurrentDraw->pixelShader) { auto lopMode = mCurrentDraw->pipeline->shaderLopMode; auto alphaFunc = mCurrentDraw->pipeline->shaderAlphaFunc; auto alphaRef = mCurrentDraw->pipeline->shaderAlphaRef; spirv::FragmentPushConstants psConstData; psConstData.alphaFunc = (lopMode << 8) | static_cast<uint32_t>(alphaFunc); psConstData.alphaRef = alphaRef; psConstData.needsPremultiply = 0; for (auto i = 0; i < latte::MaxRenderTargets; ++i) { if (mCurrentDraw->pipeline->needsPremultipliedTargets && !mCurrentDraw->pipeline->targetIsPremultiplied[i]) { psConstData.needsPremultiply |= (1 << i); } } if (!mActivePsConstantsSet || memcmp(&psConstData, &mActivePsConstants, sizeof(psConstData)) != 0) { mActivePsConstants = psConstData; mActivePsConstantsSet = true; mActiveCommandBuffer.pushConstants<spirv::FragmentPushConstants>( mPipelineLayout, vk::ShaderStageFlagBits::eFragment, spirv::FragmentPushConstantsOffset, { psConstData }); } } } void Driver::drawGenericIndexed(latte::VGT_DRAW_INITIATOR drawInit, uint32_t numIndices, void *indices) { // First lets set up our draw description for everyone auto pa_su_point_size = getRegister<latte::PA_SU_POINT_SIZE>(latte::Register::PA_SU_POINT_SIZE); auto vgt_index_type = getRegister<latte::VGT_NODMA_INDEX_TYPE>(latte::Register::VGT_INDEX_TYPE); auto vgt_primitive_type = getRegister<latte::VGT_PRIMITIVE_TYPE>(latte::Register::VGT_PRIMITIVE_TYPE); auto sq_vtx_base_vtx_loc = getRegister<latte::SQ_VTX_BASE_VTX_LOC>(latte::Register::SQ_VTX_BASE_VTX_LOC); auto sq_vtx_start_inst_loc = getRegister<latte::SQ_VTX_START_INST_LOC>(latte::Register::SQ_VTX_START_INST_LOC); auto vgt_dma_num_instances = getRegister<latte::VGT_DMA_NUM_INSTANCES>(latte::Register::VGT_DMA_NUM_INSTANCES); auto vgt_dma_index_type = getRegister<latte::VGT_DMA_INDEX_TYPE>(latte::Register::VGT_DMA_INDEX_TYPE); auto vgt_strmout_en = getRegister<latte::VGT_STRMOUT_EN>(latte::Register::VGT_STRMOUT_EN); bool useStreamOut = vgt_strmout_en.STREAMOUT(); bool useOpaque = drawInit.USE_OPAQUE(); if (useOpaque) { decaf_check(numIndices == 0); decaf_check(!indices); } DrawDesc& drawDesc = mDrawCache; drawDesc.indices = indices; drawDesc.indexType = vgt_index_type.INDEX_TYPE(); drawDesc.indexSwapMode = latte::VGT_DMA_SWAP::NONE; drawDesc.primitiveType = vgt_primitive_type.PRIM_TYPE(); drawDesc.numIndices = numIndices; drawDesc.baseVertex = sq_vtx_base_vtx_loc.OFFSET(); drawDesc.numInstances = 1; drawDesc.baseInstance = sq_vtx_start_inst_loc.OFFSET(); drawDesc.streamOutEnabled = useStreamOut; drawDesc.streamOutContext = mStreamOutContext; drawDesc.pointSize = pa_su_point_size.WIDTH(); if (drawInit.SOURCE_SELECT() == latte::VGT_DI_SRC_SEL::DMA) { drawDesc.indexType = vgt_dma_index_type.INDEX_TYPE(); drawDesc.indexSwapMode = vgt_dma_index_type.SWAP_MODE(); drawDesc.numInstances = vgt_dma_num_instances.NUM_INSTANCES(); } mCurrentDraw = &drawDesc; // Set up all the required state, ordering here is very important if (!checkCurrentVertexShader()) { gLog->debug("Skipped draw due to a vertex shader error"); return; } if (!checkCurrentGeometryShader()) { gLog->debug("Skipped draw due to a geometry shader error"); return; } if (!checkCurrentPixelShader()) { gLog->debug("Skipped draw due to a pixel shader error"); return; } if (!checkCurrentRectStubShader()) { gLog->debug("Skipped draw due to a rect stub shader error"); return; } if (!checkCurrentRenderPass()) { gLog->debug("Skipped draw due to a render pass error"); return; } if (!checkCurrentFramebuffer()) { gLog->debug("Skipped draw due to a framebuffer error"); return; } if (!checkCurrentPipeline()) { gLog->debug("Skipped draw due to a pipeline error"); return; } if (!checkCurrentSamplers()) { gLog->debug("Skipped draw due to a samplers error"); return; } if (!checkCurrentTextures()) { gLog->debug("Skipped draw due to a textures error"); return; } if (!checkCurrentAttribBuffers()) { gLog->debug("Skipped draw due to an attribute buffers error"); return; } if (!checkCurrentShaderBuffers()) { gLog->debug("Skipped draw due to a shader buffers error"); return; } if (!checkCurrentIndices()) { gLog->debug("Skipped draw due to an index buffer error"); return; } if (!checkCurrentViewportAndScissor()) { gLog->debug("Skipped draw due to a viewport or scissor area error"); return; } if (!checkCurrentStreamOut()) { gLog->debug("Skipped draw due to a stream out buffers error"); return; } // These things need to happen up here before we enter the render pass, otherwise // the pipeline barrier triggered by the memory cache transition will fail. uint32_t opaqueStride = 0; MemCacheObject *opaqueCounter = nullptr; if (useOpaque) { auto vgt_strmout_draw_opaque_vertex_stride = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE); opaqueStride = vgt_strmout_draw_opaque_vertex_stride << 2; auto soBufferSizeAddr = getRegisterAddr(latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE); opaqueCounter = getDataMemCache(soBufferSizeAddr, 4); transitionMemCache(opaqueCounter, ResourceUsage::StreamOutCounterRead); drawDesc.opaqueBuffer = opaqueCounter; drawDesc.opaqueStride = opaqueStride; } else { drawDesc.opaqueBuffer = nullptr; } // Finish this draw mCurrentDraw = nullptr; // Perform our pending draws if the renderpass has changed if (mActiveRenderPass != drawDesc.renderPass || mActiveFramebuffer != drawDesc.framebuffer) { flushPendingDraws(); mActiveRenderPass = drawDesc.renderPass; mActiveFramebuffer = drawDesc.framebuffer; } // If this is a stream-out draw, we have to flush it as a singular draw call. if (useStreamOut) { flushPendingDraws(); } // Now that we have potentially flushed our draws, lets prepare this draw. We need to do it in // this order, since this draw might use the last renderpasses framebuffer as a texture, and we // need to barrier AFTER the previous draws are flushed. mCurrentDraw = &drawDesc; prepareCurrentTextures(); prepareCurrentFramebuffer(); mCurrentDraw = nullptr; // Record this pending draw mPendingDraws.push_back(drawDesc); // Finish with this draw call mCurrentDraw = nullptr; // If this is a stream-out draw, we have to flush it as a singular draw call. if (useStreamOut) { flushPendingDraws(); } } void Driver::flushPendingDraws() { if (mPendingDraws.empty()) { return; } auto& fbRa = mActiveFramebuffer->renderArea; auto renderArea = vk::Rect2D { { 0, 0 }, fbRa }; // Bind and set up everything, and then do our draw auto passBeginDesc = vk::RenderPassBeginInfo {}; passBeginDesc.renderPass = mActiveRenderPass->renderPass; passBeginDesc.framebuffer = mActiveFramebuffer->framebuffer; passBeginDesc.renderArea = renderArea; passBeginDesc.clearValueCount = 0; passBeginDesc.pClearValues = nullptr; mActiveCommandBuffer.beginRenderPass(passBeginDesc, vk::SubpassContents::eInline); for (auto &drawDesc : mPendingDraws) { // Make sure that the draw descriptions match our expectations decaf_check(mActiveRenderPass == drawDesc.renderPass); decaf_check(mActiveFramebuffer == drawDesc.framebuffer); mCurrentDraw = &drawDesc; drawCurrentState(); mCurrentDraw = nullptr; } mPendingDraws.clear(); mActiveCommandBuffer.endRenderPass(); } void Driver::drawCurrentState() { auto &drawDesc = *mCurrentDraw; if (mActivePipeline != drawDesc.pipeline) { mActiveCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, drawDesc.pipeline->pipeline); mActivePipeline = drawDesc.pipeline; } bindAttribBuffers(); bindDescriptors(); bindShaderParams(); bindViewportAndScissor(); bindIndexBuffer(); bindStreamOutBuffers(); if (drawDesc.streamOutEnabled) { beginStreamOut(); } if (drawDesc.opaqueBuffer) { mActiveCommandBuffer.drawIndirectByteCountEXT(1, 0, drawDesc.opaqueBuffer->buffer, 0, 0, drawDesc.opaqueStride, mVkDynLoader); } else if (drawDesc.indexBuffer) { mActiveCommandBuffer.drawIndexed(drawDesc.numIndices, drawDesc.numInstances, 0, drawDesc.baseVertex, drawDesc.baseInstance); } else { mActiveCommandBuffer.draw(drawDesc.numIndices, drawDesc.numInstances, drawDesc.baseVertex, drawDesc.baseInstance); } if (drawDesc.streamOutEnabled) { endStreamOut(); } } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_driver.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "gpu_config.h" #include "gpu_configstorage.h" #include "gpu_event.h" #include "gpu_graphicsdriver.h" #include "gpu_ringbuffer.h" namespace vulkan { Driver::Driver() { } Driver::~Driver() { } void Driver::notifyCpuFlush(phys_addr address, uint32_t size) { } void Driver::notifyGpuFlush(phys_addr address, uint32_t size) { } void Driver::initialise(vk::Instance instance, vk::PhysicalDevice physDevice, vk::Device device, vk::Queue queue, uint32_t queueFamilyIndex) { if (mRunState != RunState::None) { return; } // Register config change handler static std::once_flag sRegisteredConfigChangeListener; std::call_once(sRegisteredConfigChangeListener, [this]() { gpu::registerConfigChangeListener( [this](const gpu::Settings &settings) { mDebug = settings.debug.debug_enabled; mDumpShaders = settings.debug.dump_shaders; mDumpShaderBinariesOnly = settings.debug.dump_shader_binaries_only; }); }); // Read config auto gpuConfig = gpu::config(); mDebug = gpuConfig->debug.debug_enabled; mDumpShaders = gpuConfig->debug.dump_shaders; mDumpShaderBinariesOnly = gpuConfig->debug.dump_shader_binaries_only; mPhysDevice = physDevice; mDevice = device; mQueue = queue; mRunState = RunState::Running; validateDevice(); // Initialize the dynamic loader we use for extensions mVkDynLoader.init(instance, ::vkGetInstanceProcAddr, mDevice, ::vkGetDeviceProcAddr); // Initialize our GPU retiler mGpuRetiler.initialise(mDevice); // Allocate a command pool to use auto commandPoolCreateInfo = vk::CommandPoolCreateInfo { }; commandPoolCreateInfo.flags = vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer; commandPoolCreateInfo.queueFamilyIndex = queueFamilyIndex; mCommandPool = mDevice.createCommandPool(commandPoolCreateInfo); // Start our fence thread mFenceThread = std::thread { std::bind(&Driver::fenceWaiterThread, this) }; // Set up the VMA auto allocatorCreateInfo = VmaAllocatorCreateInfo { }; allocatorCreateInfo.physicalDevice = mPhysDevice; allocatorCreateInfo.device = mDevice; CHECK_VK_RESULT(vmaCreateAllocator(&allocatorCreateInfo, &mAllocator)); // Set up the default pipeline layout and descriptor set auto basePlDesc = PipelineLayoutDesc { }; memset(&basePlDesc, 0xFF, sizeof(basePlDesc)); auto basePl = getPipelineLayout(basePlDesc); mBaseDescriptorSetLayout = basePl->descriptorLayout; mPipelineLayout = basePl->pipelineLayout; // Set up the pipeline cache auto pipelineCacheCreateInfo = vk::PipelineCacheCreateInfo { }; pipelineCacheCreateInfo.flags = vk::PipelineCacheCreateFlags { }; pipelineCacheCreateInfo.pInitialData = nullptr; pipelineCacheCreateInfo.initialDataSize = 0; mPipelineCache = mDevice.createPipelineCache(pipelineCacheCreateInfo); initialiseBlankSampler(); initialiseBlankImage(); initialiseBlankBuffer(); setupResources(); } void Driver::destroy() { mFenceSignal.notify_all(); mFenceThread.join(); destroyDisplayPipeline(); } void Driver::initialiseBlankSampler() { vk::SamplerCreateInfo samplerDesc; samplerDesc.magFilter = vk::Filter::eLinear; samplerDesc.minFilter = vk::Filter::eLinear; samplerDesc.mipmapMode = vk::SamplerMipmapMode::eLinear; samplerDesc.addressModeU = vk::SamplerAddressMode::eRepeat; samplerDesc.addressModeV = vk::SamplerAddressMode::eRepeat; samplerDesc.addressModeW = vk::SamplerAddressMode::eRepeat; samplerDesc.mipLodBias = 0.0f; samplerDesc.anisotropyEnable = false; samplerDesc.maxAnisotropy = 0.0f; samplerDesc.compareEnable = false; samplerDesc.compareOp = vk::CompareOp::eAlways; samplerDesc.minLod = 0.0f; samplerDesc.maxLod = 0.0f; samplerDesc.borderColor = vk::BorderColor::eFloatTransparentBlack; samplerDesc.unnormalizedCoordinates = VK_FALSE; auto emptySampler = mDevice.createSampler(samplerDesc); setVkObjectName(emptySampler, "PlaceholderSampler"); mBlankSampler = emptySampler; } void Driver::initialiseBlankImage() { // Create a random image to use for sampling vk::ImageCreateInfo createImageDesc; createImageDesc.imageType = vk::ImageType::e2D; createImageDesc.format = vk::Format::eR8G8B8A8Snorm; createImageDesc.extent = vk::Extent3D(1, 1, 1); createImageDesc.mipLevels = 1; createImageDesc.arrayLayers = 1; createImageDesc.samples = vk::SampleCountFlagBits::e1; createImageDesc.tiling = vk::ImageTiling::eOptimal; createImageDesc.usage = vk::ImageUsageFlagBits::eSampled; createImageDesc.sharingMode = vk::SharingMode::eExclusive; createImageDesc.initialLayout = vk::ImageLayout::eUndefined; auto emptyImage = mDevice.createImage(createImageDesc); setVkObjectName(emptyImage, "PlaceholderSurface"); auto imageMemReqs = mDevice.getImageMemoryRequirements(emptyImage); vk::MemoryAllocateInfo allocDesc; allocDesc.allocationSize = imageMemReqs.size; allocDesc.memoryTypeIndex = findMemoryType(imageMemReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal); auto imageMem = mDevice.allocateMemory(allocDesc); mDevice.bindImageMemory(emptyImage, imageMem, 0); vk::ImageViewCreateInfo imageViewDesc; imageViewDesc.image = emptyImage; imageViewDesc.viewType = vk::ImageViewType::e2D; imageViewDesc.format = vk::Format::eR8G8B8A8Snorm; imageViewDesc.components = vk::ComponentMapping(); imageViewDesc.subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }; auto emptyImageView = mDevice.createImageView(imageViewDesc); setVkObjectName(emptyImageView, "PlaceholderView"); mBlankImage = emptyImage; mBlankImageView = emptyImageView; } void Driver::initialiseBlankBuffer() { vk::BufferCreateInfo bufferDesc; bufferDesc.size = 1024; bufferDesc.usage = vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eTransferSrc; bufferDesc.sharingMode = vk::SharingMode::eExclusive; bufferDesc.queueFamilyIndexCount = 0; bufferDesc.pQueueFamilyIndices = nullptr; VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; VkBuffer emptyBuffer; VmaAllocation allocation; vmaCreateBuffer(mAllocator, reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc), &allocInfo, &emptyBuffer, &allocation, nullptr); setVkObjectName(emptyBuffer, "PlaceholderBuffer"); mBlankBuffer = emptyBuffer; } void Driver::setupResources() { vk::CommandBufferAllocateInfo cmdBufferAllocDesc(mCommandPool, vk::CommandBufferLevel::ePrimary, 1); auto cmdBuffer = mDevice.allocateCommandBuffers(cmdBufferAllocDesc)[0]; cmdBuffer.begin(vk::CommandBufferBeginInfo {}); { vk::ImageMemoryBarrier imageBarrier; imageBarrier.srcAccessMask = vk::AccessFlags(); imageBarrier.dstAccessMask = vk::AccessFlags(); imageBarrier.oldLayout = vk::ImageLayout::eUndefined; imageBarrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.image = mBlankImage; imageBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; imageBarrier.subresourceRange.baseMipLevel = 0; imageBarrier.subresourceRange.levelCount = 1; imageBarrier.subresourceRange.baseArrayLayer = 0; imageBarrier.subresourceRange.layerCount = 1; cmdBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags(), {}, {}, { imageBarrier }); } cmdBuffer.end(); vk::SubmitInfo submitInfo; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &cmdBuffer; mQueue.submit({ submitInfo }, vk::Fence()); } vk::DescriptorPool Driver::allocateDescriptorPool(uint32_t numDraws) { vk::DescriptorPool descriptorPool; if (!descriptorPool) { if (!mDescriptorPools.empty()) { descriptorPool = mDescriptorPools.back(); mDescriptorPools.pop_back(); } } if (!descriptorPool) { std::vector<vk::DescriptorPoolSize> descriptorPoolSizes = { vk::DescriptorPoolSize(vk::DescriptorType::eSampler, latte::MaxSamplers * 3 * numDraws), vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, latte::MaxTextures * 3 * numDraws), vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, latte::MaxUniformBlocks * 3 * numDraws), }; vk::DescriptorPoolCreateInfo descriptorPoolInfo; descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size()); descriptorPoolInfo.pPoolSizes = descriptorPoolSizes.data(); descriptorPoolInfo.maxSets = static_cast<uint32_t>(numDraws); descriptorPool = mDevice.createDescriptorPool(descriptorPoolInfo); } mActiveSyncWaiter->descriptorPools.push_back(descriptorPool); return descriptorPool; } vk::DescriptorSet Driver::allocateGenericDescriptorSet() { if (mAvailableDescriptorSets.empty()) { std::array<vk::DescriptorSetLayout, 32> setLayouts = { mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout }; auto numSetLayouts = static_cast<uint32_t>(setLayouts.size()); auto newPool = allocateDescriptorPool(numSetLayouts); vk::DescriptorSetAllocateInfo allocInfo; allocInfo.descriptorSetCount = numSetLayouts; allocInfo.pSetLayouts = setLayouts.data(); allocInfo.descriptorPool = newPool; mAvailableDescriptorSets = mDevice.allocateDescriptorSets(allocInfo); } auto descriptorSet = mAvailableDescriptorSets.back(); mAvailableDescriptorSets.pop_back(); return descriptorSet; } void Driver::retireDescriptorPool(vk::DescriptorPool descriptorPool) { mDevice.resetDescriptorPool(descriptorPool, vk::DescriptorPoolResetFlags()); mDescriptorPools.push_back(descriptorPool); } vk::QueryPool Driver::allocateOccQueryPool() { vk::QueryPool occPool; if (!occPool) { if (!mOccQueryPools.empty()) { occPool = mOccQueryPools.back(); mOccQueryPools.pop_back(); } } if (!occPool) { vk::QueryPoolCreateInfo queryDesc; queryDesc.queryType = vk::QueryType::eOcclusion; queryDesc.queryCount = 1; occPool = mDevice.createQueryPool(queryDesc); } mActiveCommandBuffer.resetQueryPool(occPool, 0, 1); mActiveSyncWaiter->occQueryPools.push_back(occPool); return occPool; } void Driver::retireOccQueryPool(vk::QueryPool pool) { mOccQueryPools.push_back(pool); } void Driver::run() { while (mRunState == RunState::Running) { // Grab the next buffer gpu::ringbuffer::wait(); auto buffer = gpu::ringbuffer::read(); // Check for any fences completing checkSyncFences(); // Process the buffer if there is anything new if (!buffer.empty()) { executeBuffer(buffer); } } destroy(); } void Driver::runUntilFlip() { auto startingSwap = mLastSwap; while (mRunState == RunState::Running) { // Grab the next item gpu::ringbuffer::wait(); // Check for any fences completing checkSyncFences(); // Process the buffer if there is anything new auto buffer = gpu::ringbuffer::read(); if (!buffer.empty()) { executeBuffer(buffer); } if (mLastSwap > startingSwap) { break; } } } void Driver::stop() { mRunState = RunState::Stopped; gpu::ringbuffer::wake(); } void Driver::beginCommandGroup() { mActiveBatchIndex++; mMemTracker.nextBatch(); mActiveSyncWaiter = allocateSyncWaiter(); mActiveCommandBuffer = mActiveSyncWaiter->cmdBuffer; } void Driver::endCommandGroup() { // Submit the active waiter to the queue submitSyncWaiter(mActiveSyncWaiter); // Clear our state in between command buffers for safety mActiveCommandBuffer = nullptr; mActiveSyncWaiter = nullptr; mAvailableDescriptorSets.clear(); } void Driver::beginCommandBuffer() { // Begin recording our host command buffer mActiveCommandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); } void Driver::endCommandBuffer() { // Flush our pending draws flushPendingDraws(); // We have to force our memcache objects to be downloaded at the // end of every PM4 buffer. downloadPendingMemCache(); // Clear our per-command-buffer state mActivePipeline = nullptr; mActiveRenderPass = nullptr; mActiveFramebuffer = nullptr; mActiveVsConstantsSet = false; mActivePsConstantsSet = false; mLastIndexBufferSet = false; mDrawCache = DrawDesc{}; // Stop recording this host command buffer mActiveCommandBuffer.end(); } int32_t Driver::findMemoryType(uint32_t memTypeBits, vk::MemoryPropertyFlags requestProps) { auto memoryProps = mPhysDevice.getMemoryProperties(); const uint32_t memoryCount = memoryProps.memoryTypeCount; for (uint32_t memoryIndex = 0; memoryIndex < memoryCount; ++memoryIndex) { const uint32_t memoryTypeBits = (1 << memoryIndex); const bool isRequiredMemoryType = memTypeBits & memoryTypeBits; const auto properties = memoryProps.memoryTypes[memoryIndex].propertyFlags; const bool hasRequiredProperties = (properties & requestProps) == requestProps; if (isRequiredMemoryType && hasRequiredProperties) return static_cast<int32_t>(memoryIndex); } throw std::logic_error("failed to find suitable memory type"); } ResourceUsageMeta Driver::getResourceUsageMeta(ResourceUsage usage) { switch (usage) { case ResourceUsage::Undefined: return { false, vk::AccessFlags(), vk::PipelineStageFlagBits::eBottomOfPipe, vk::ImageLayout::eUndefined }; case ResourceUsage::VertexTexture: return { false, vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eVertexShader, vk::ImageLayout::eShaderReadOnlyOptimal }; case ResourceUsage::GeometryTexture: return { false, vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eGeometryShader, vk::ImageLayout::eShaderReadOnlyOptimal }; case ResourceUsage::PixelTexture: return { false, vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eFragmentShader, vk::ImageLayout::eShaderReadOnlyOptimal }; case ResourceUsage::IndexBuffer: return { false, vk::AccessFlagBits::eIndexRead, vk::PipelineStageFlagBits::eVertexInput, vk::ImageLayout::eUndefined }; case ResourceUsage::VertexUniforms: return { false, vk::AccessFlagBits::eUniformRead, vk::PipelineStageFlagBits::eVertexShader, vk::ImageLayout::eUndefined }; case ResourceUsage::GeometryUniforms: return { false, vk::AccessFlagBits::eUniformRead, vk::PipelineStageFlagBits::eGeometryShader, vk::ImageLayout::eUndefined }; case ResourceUsage::PixelUniforms: return { false, vk::AccessFlagBits::eUniformRead, vk::PipelineStageFlagBits::eFragmentShader, vk::ImageLayout::eUndefined }; case ResourceUsage::AttributeBuffer: return { false, vk::AccessFlagBits::eVertexAttributeRead, vk::PipelineStageFlagBits::eVertexInput, vk::ImageLayout::eUndefined }; case ResourceUsage::StreamOutCounterRead: return { false, vk::AccessFlagBits::eTransformFeedbackCounterReadEXT, vk::PipelineStageFlagBits::eDrawIndirect, vk::ImageLayout::eUndefined }; case ResourceUsage::ComputeSsboRead: return { false, vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eComputeShader, vk::ImageLayout::eUndefined }; case ResourceUsage::TransferSrc: return { false, vk::AccessFlagBits::eTransferRead, vk::PipelineStageFlagBits::eTransfer, vk::ImageLayout::eTransferSrcOptimal }; case ResourceUsage::HostRead: return { false, vk::AccessFlagBits::eHostRead, vk::PipelineStageFlagBits::eHost, vk::ImageLayout::eUndefined }; case ResourceUsage::ColorAttachment: return { true, vk::AccessFlagBits::eColorAttachmentWrite, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::ImageLayout::eColorAttachmentOptimal }; case ResourceUsage::DepthStencilAttachment: return { true, vk::AccessFlagBits::eDepthStencilAttachmentWrite, vk::PipelineStageFlagBits::eLateFragmentTests, vk::ImageLayout::eDepthStencilAttachmentOptimal }; case ResourceUsage::StreamOutBuffer: return { true, vk::AccessFlagBits::eTransformFeedbackWriteEXT, vk::PipelineStageFlagBits::eTransformFeedbackEXT, vk::ImageLayout::eUndefined }; case ResourceUsage::StreamOutCounterWrite: return { true, vk::AccessFlagBits::eTransformFeedbackCounterWriteEXT, vk::PipelineStageFlagBits::eTransformFeedbackEXT, vk::ImageLayout::eUndefined }; case ResourceUsage::ComputeSsboWrite: return { true, vk::AccessFlagBits::eShaderWrite, vk::PipelineStageFlagBits::eComputeShader, vk::ImageLayout::eUndefined }; case ResourceUsage::TransferDst: return { true, vk::AccessFlagBits::eTransferWrite, vk::PipelineStageFlagBits::eTransfer, vk::ImageLayout::eTransferDstOptimal }; case ResourceUsage::HostWrite: return { true, vk::AccessFlagBits::eHostWrite, vk::PipelineStageFlagBits::eHost, vk::ImageLayout::eUndefined }; default: decaf_abort("Unexpected resource usage"); } } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_driver.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "gpu_graphicsdriver.h" #include "gpu_ringbuffer.h" #include "gpu_vulkandriver.h" #include "gpu7_tiling.h" #include "gpu7_tiling_vulkan.h" #include "latte/latte_formats.h" #include "latte/latte_constants.h" #include "spirv/spirv_translate.h" #include "spirv/spirv_pushconstants.h" #include "pm4_processor.h" #include "vk_mem_alloc_decaf.h" #include "vulkan_descs.h" #include "vulkan_memtracker.h" #include <atomic> #include <common/vulkan_hpp.h> #include <condition_variable> #include <functional> #include <gsl/gsl-lite.hpp> #include <list> #include <map> #include <mutex> #include <thread> #include <unordered_map> #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) // Evaluate f and if result is not a success throw proper vk exception. #define CHECK_VK_RESULT(x) do { \ vk::resultCheck(static_cast<vk::Result>(x), __FILE__ ":" TOSTRING(__LINE__)); \ } while (0) namespace vulkan { template<typename T> class HashedDesc { public: HashedDesc() { } HashedDesc(const T& desc) : mHash(desc.hash()), mDesc(desc) { } const T& operator*() const { return mDesc; } const T * operator->() const { return &mDesc; } DataHash hash() const { return mHash; } bool operator==(const HashedDesc<T>& other) const { return hash() == other.hash(); } private: DataHash mHash; T mDesc; }; enum class ResourceUsage : uint32_t { Undefined, ColorAttachment, DepthStencilAttachment, VertexTexture, GeometryTexture, PixelTexture, VertexUniforms, GeometryUniforms, PixelUniforms, IndexBuffer, AttributeBuffer, StreamOutBuffer, StreamOutCounterRead, StreamOutCounterWrite, ComputeSsboRead, ComputeSsboWrite, TransferSrc, TransferDst, HostWrite, HostRead }; struct ResourceUsageMeta { bool isWrite; vk::AccessFlags accessFlags; vk::PipelineStageFlags stageFlags; vk::ImageLayout imageLayout; }; typedef std::function<void()> DelayedMemWriteFunc; struct MemCacheObject; typedef MemoryTracker<MemCacheObject*> DriverMemoryTracker; typedef DriverMemoryTracker::SegmentRef MemSegmentRef; typedef DriverMemoryTracker::Segment MemSegment; struct SectionRange { uint32_t start = 0; uint32_t count = 0; inline bool covers(const SectionRange& other) const { auto end = start + count; auto other_end = other.start + other.count; return start <= other.start && end >= other_end; } inline bool intersects(const SectionRange& other) const { auto end = start + count; auto other_end = other.start + other.count; return !(start >= other_end || end <= other.start); } }; struct MemCacheSection { // Pointer into the segments map for this memory range MemSegmentRef firstSegment; // Records the last change index for this data uint64_t lastChangeIndex; // Records some information used to optimize the uploading and // copying of segments between different cache objects. uint64_t wantedChangeIndex; bool needsUpload; }; struct MemCacheObject { // Meta-data about what this cache object represents. phys_addr address; uint32_t size; uint32_t numSections; uint32_t sectionSize; ResourceUsage activeUsage; // The buffer data. VmaAllocation allocation; vk::Buffer buffer; // The various sections that make up this buffer std::vector<MemCacheSection> sections; // Stores a function used to update this buffer when the cost of // generating the new data is significant and we want to avoid it // unless the data ends up actually being needed. DelayedMemWriteFunc delayedWriteFunc; SectionRange delayedWriteRange; // Records the last PM4 context which refers to this data. uint64_t lastUsageIndex; // Records the number of external objects relying on this... uint64_t refCount; // Intrusive linked list to enable us to chain together multiple // objects with different section layouts for faster lookup. MemCacheObject *nextObject; }; struct MemChangeRecord { uint64_t changeIndex; MemCacheObject *cache; SectionRange sections; }; struct DataBufferObject : MemCacheObject { }; enum class ShaderStage { Vertex = 0, Geometry = 1, Pixel = 2 }; struct VertexShaderObject { HashedDesc<spirv::VertexShaderDesc> desc; spirv::VertexShader shader; vk::ShaderModule module; }; struct GeometryShaderObject { HashedDesc<spirv::GeometryShaderDesc> desc; spirv::GeometryShader shader; vk::ShaderModule module; }; struct PixelShaderObject { HashedDesc<spirv::PixelShaderDesc> desc; spirv::PixelShader shader; vk::ShaderModule module; }; struct RectStubShaderObject { HashedDesc<spirv::RectStubShaderDesc> desc; spirv::RectStubShader shader; vk::ShaderModule module; }; enum class StagingBufferType : uint32_t { CpuToGpu = 0, GpuToCpu = 1, GpuToGpu = 2 }; struct StagingBuffer { StagingBufferType type; uint32_t poolIndex; uint32_t maximumSize; uint32_t size; ResourceUsage activeUsage; vk::Buffer buffer; VmaAllocation memory; void *mappedPtr; }; struct SyncWaiter { bool isCompleted = false; vk::Fence fence; std::vector<vk::DescriptorPool> descriptorPools; std::vector<vk::QueryPool> occQueryPools; std::vector<gpu7::tiling::vulkan::RetileHandle> retileHandles; std::vector<StagingBuffer *> stagingBuffers; std::vector<std::function<void()>> callbacks; vk::CommandBuffer cmdBuffer; }; struct SurfaceSubRange { uint32_t firstSlice; uint32_t numSlices; }; struct SurfaceObject; struct SurfaceGroupObject { SurfaceDesc desc; std::list<SurfaceObject *> surfaces; std::vector<SurfaceObject *> sliceOwners; }; struct SurfaceSlice { uint64_t lastChangeIndex; }; struct SurfaceObject { SurfaceDesc desc; SurfaceGroupObject *group; uint64_t lastUsageIndex; uint32_t pitch; uint32_t width; uint32_t height; uint32_t depth; uint32_t arrayLayers; gpu7::tiling::SurfaceDescription tilingDesc; gpu7::tiling::SurfaceInfo tilingInfo; std::vector<SurfaceSlice> slices; MemCacheObject *memCache; ResourceUsage activeUsage; vk::Image image; vk::DeviceMemory imageMem; vk::BufferImageCopy bufferRegion; vk::ImageSubresourceRange subresRange; }; struct SurfaceViewObject { HashedDesc<SurfaceViewDesc> desc; SurfaceObject *surface; SurfaceSubRange surfaceRange; vk::Image boundImage; vk::ImageView imageView; vk::ImageSubresourceRange subresRange; }; struct FramebufferObject { HashedDesc<FramebufferDesc> desc; std::array<SurfaceViewObject*, latte::MaxRenderTargets> colorSurfaces; SurfaceViewObject *depthSurface; vk::Extent2D renderArea; std::array<vk::ImageView, 9> boundViews; vk::Framebuffer framebuffer; }; struct SamplerObject { HashedDesc<SamplerDesc> desc; vk::Sampler sampler; }; // SwapChainObjects are backed by a surface, but expose less things // as there are more specific guarentees provided, such as: // - layout is always TransferDstOptimal struct SwapChainObject { SwapChainDesc desc; bool presentable; vk::Image image; vk::ImageView imageView; vk::ImageSubresourceRange subresRange; SurfaceObject *_surface; }; struct RenderPassObject { HashedDesc<RenderPassDesc> desc; vk::RenderPass renderPass; std::array<int, latte::MaxRenderTargets> colorAttachmentIndexes; int depthAttachmentIndex; }; struct PipelineLayoutObject { HashedDesc<PipelineLayoutDesc> desc; vk::DescriptorSetLayout descriptorLayout; vk::PipelineLayout pipelineLayout; }; struct PipelineObject { HashedDesc<PipelineDesc> desc; PipelineLayoutObject *pipelineLayout; vk::Pipeline pipeline; bool needsPremultipliedTargets; std::array<bool, latte::MaxRenderTargets> targetIsPremultiplied; uint32_t shaderLopMode; uint32_t shaderAlphaFunc; float shaderAlphaRef; }; struct StreamContextObject { VmaAllocation allocation; vk::Buffer buffer; }; struct ShaderViewportData { float xAdd, xMul; float yAdd, yMul; float zAdd, zMul; }; struct IndexBufferCache { latte::VGT_DI_PRIMITIVE_TYPE primitiveType; latte::VGT_INDEX_TYPE indexType; latte::VGT_DMA_SWAP swapMode; uint32_t numIndices; void *indexData; latte::VGT_DI_PRIMITIVE_TYPE newPrimitiveType; uint32_t newNumIndices; StagingBuffer *indexBuffer; }; struct DrawDesc { void *indices; latte::VGT_INDEX_TYPE indexType; latte::VGT_DMA_SWAP indexSwapMode; latte::VGT_DI_PRIMITIVE_TYPE primitiveType; uint32_t pointSize; uint32_t numIndices; uint32_t baseVertex; uint32_t numInstances; uint32_t baseInstance; bool streamOutEnabled = false; MemCacheObject *opaqueBuffer = nullptr; uint32_t opaqueStride = 0; vk::Viewport viewport; ShaderViewportData shaderViewportData; vk::Rect2D scissor; StagingBuffer *indexBuffer = nullptr; VertexShaderObject *vertexShader = nullptr; GeometryShaderObject *geometryShader = nullptr; PixelShaderObject *pixelShader = nullptr; RectStubShaderObject *rectStubShader = nullptr; FramebufferObject *framebuffer = nullptr; RenderPassObject *renderPass = nullptr; PipelineObject *pipeline = nullptr; bool framebufferDirty = true; std::array<std::array<bool, latte::MaxTextures>, 3> textureDirty = { { true } }; std::array<DataBufferObject*, latte::MaxAttribBuffers> attribBuffers = { nullptr }; std::array<std::array<SamplerObject*, latte::MaxSamplers>, 3> samplers = { { nullptr } }; std::array<std::array<SurfaceViewObject*, latte::MaxTextures>, 3> textures = { { nullptr } }; std::array<StagingBuffer*, 3> gprBuffers = { nullptr }; std::array<std::array<DataBufferObject*, latte::MaxUniformBlocks>, 3> uniformBlocks = { { nullptr } }; std::array<StreamContextObject*, latte::MaxStreamOutBuffers> streamOutContext = { nullptr }; std::array<DataBufferObject*, latte::MaxStreamOutBuffers> streamOutBuffers = { nullptr }; }; struct VulkanDisplayPipeline { vk::SurfaceKHR windowSurface; vk::Format windowSurfaceFormat; uint32_t queueFamilyIndex; vk::RenderPass renderPass; // Swapchain vk::PresentModeKHR presentMode; vk::SwapchainKHR swapchain; vk::Extent2D swapchainExtents; std::vector<vk::ImageView> swapchainImageViews; std::vector<vk::Framebuffer> framebuffers; vk::Sampler trivialSampler; vk::DescriptorSetLayout descriptorSetLayout; vk::PipelineLayout pipelineLayout; vk::Pipeline graphicsPipeline; vk::ShaderModule vertexShader; vk::ShaderModule fragmentShader; vk::Buffer vertexBuffer; vk::DeviceMemory vertexBufferMemory; vk::DescriptorPool descriptorPool; std::vector<vk::DescriptorSet> descriptorSets; int frameIndex; std::vector<vk::Fence> renderFences; std::vector<vk::Semaphore> imageAvailableSemaphores; std::vector<vk::Semaphore> renderFinishedSemaphores; }; class Driver : public gpu::GraphicsDriver, public Pm4Processor { public: Driver(); virtual ~Driver(); virtual void setWindowSystemInfo(const gpu::WindowSystemInfo &wsi) override; virtual void windowHandleChanged(void *handle) override; virtual void windowSizeChanged(int width, int height) override; virtual void run() override; virtual void runUntilFlip() override; virtual void stop() override; virtual gpu::GraphicsDriverType type() override; virtual gpu::GraphicsDriverDebugInfo *getDebugInfo() override; virtual void notifyCpuFlush(phys_addr address, uint32_t size) override; virtual void notifyGpuFlush(phys_addr address, uint32_t size) override; protected: void initialise(vk::Instance instance, vk::PhysicalDevice physDevice, vk::Device device, vk::Queue queue, uint32_t queueFamilyIndex); void destroy(); void initialiseBlankSampler(); void initialiseBlankImage(); void initialiseBlankBuffer(); void setupResources(); void updateDebuggerInfo(); void validateDevice(); ResourceUsageMeta getResourceUsageMeta(ResourceUsage usage); void renderDisplay(); void destroyDisplayPipeline(); // Command Buffer Stuff void beginCommandGroup(); void endCommandGroup(); void beginCommandBuffer(); void endCommandBuffer(); // Descriptor Sets vk::DescriptorPool allocateDescriptorPool(uint32_t numDraws); vk::DescriptorSet allocateGenericDescriptorSet(); void retireDescriptorPool(vk::DescriptorPool descriptorPool); // Fences SyncWaiter * allocateSyncWaiter(); void releaseSyncWaiter(SyncWaiter *syncWaiter); void submitSyncWaiter(SyncWaiter *syncWaiter); void executeSyncWaiter(SyncWaiter *syncWaiter); void fenceWaiterThread(); void checkSyncFences(); void addRetireTask(std::function<void()> fn); // Retiling void dispatchGpuTile(const gpu7::tiling::RetileInfo& retileInfo, vk::CommandBuffer &commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices); void dispatchGpuUntile(const gpu7::tiling::RetileInfo& retileInfo, vk::CommandBuffer& commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices); // Query Pools vk::QueryPool allocateOccQueryPool(); void retireOccQueryPool(vk::QueryPool pool); // Driver void executeBuffer(const gpu::ringbuffer::Buffer &buffer); int32_t findMemoryType(uint32_t memoryTypeBits, vk::MemoryPropertyFlags props); // Viewports bool checkCurrentViewportAndScissor(); void bindViewportAndScissor(); // Samplers SamplerDesc getSamplerDesc(ShaderStage shaderStage, uint32_t samplerIdx); void updateDrawSampler(ShaderStage shaderStage, uint32_t samplerIdx); bool checkCurrentSamplers(); // Textures SurfaceViewDesc getTextureDesc(ShaderStage shaderStage, uint32_t textureIdx); void updateDrawTexture(ShaderStage shaderStage, uint32_t textureIdx); bool checkCurrentTextures(); void prepareCurrentTextures(); // CBuffers void updateDrawUniformBuffer(ShaderStage shaderStage, uint32_t cbufferIdx); void updateDrawGprBuffer(ShaderStage shaderStage); bool checkCurrentShaderBuffers(); MemCacheObject * _allocMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize); void _uploadMemCache(MemCacheObject *cache, SectionRange sections); void _downloadMemCache(MemCacheObject *cache, SectionRange sections); void _refreshMemCache_Check(MemCacheObject *cache, SectionRange sections); void _refreshMemCache_Update(MemCacheObject *cache, SectionRange sections); void _refreshMemCache(MemCacheObject *cache, SectionRange sections); void _invalidateMemCache(MemCacheObject *cache, SectionRange sections, const DelayedMemWriteFunc& delayedWriteHandler); void _barrierMemCache(MemCacheObject *cache, ResourceUsage usage, SectionRange sections); SectionRange _sectionsFromOffsets(MemCacheObject *cache, uint32_t begin, uint32_t end); MemCacheObject * getMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize); void invalidateMemCacheDelayed(MemCacheObject *cache, uint32_t offset, uint32_t size, const DelayedMemWriteFunc& delayedWriteHandler); void transitionMemCache(MemCacheObject *cache, ResourceUsage usage, uint32_t offset = 0, uint32_t size = 0); DataBufferObject * getDataMemCache(phys_addr baseAddress, uint32_t size); void downloadPendingMemCache(); // Staging StagingBuffer * _allocStagingBuffer(uint32_t size, StagingBufferType type); StagingBuffer * getStagingBuffer(uint32_t size, StagingBufferType type); void retireStagingBuffer(StagingBuffer *sbuffer); void transitionStagingBuffer(StagingBuffer *sbuffer, ResourceUsage usage); void copyToStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, const void *data, uint32_t size); void copyFromStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, void *data, uint32_t size); // Surfaces MemCacheObject * _getSurfaceMemCache(const SurfaceDesc &info, const gpu7::tiling::SurfaceInfo& tilingInfo); void _copySurface(SurfaceObject *dst, SurfaceObject *src, SurfaceSubRange range); SurfaceGroupObject * _allocateSurfaceGroup(const SurfaceDesc &info); void _releaseSurfaceGroup(SurfaceGroupObject *surfaceGroup); void _addSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface); void _removeSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface); void _updateSurfaceGroupSlice(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, SurfaceObject *surface); SurfaceObject * _getSurfaceGroupOwner(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, uint64_t minChangeIndex); SurfaceGroupObject * _getSurfaceGroup(const SurfaceDesc &info); SurfaceObject * _allocateSurface(const SurfaceDesc &info); void _releaseSurface(SurfaceObject *surface); void _upgradeSurface(SurfaceObject *surface, const SurfaceDesc &info); void _readSurfaceData(SurfaceObject *surface, SurfaceSubRange range); void _writeSurfaceData(SurfaceObject *surface, SurfaceSubRange range); void _refreshSurface(SurfaceObject *surface, SurfaceSubRange range); void _invalidateSurface(SurfaceObject *surface, SurfaceSubRange range); void _barrierSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range); SurfaceObject * getSurface(const SurfaceDesc& info); void transitionSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range, bool skipChangeCheck = false); SurfaceViewObject * _allocateSurfaceView(const SurfaceViewDesc& info); void _releaseSurfaceView(SurfaceViewObject *surfaceView); SurfaceViewObject * getSurfaceView(const SurfaceViewDesc& info); void transitionSurfaceView(SurfaceViewObject *surfaceView, ResourceUsage usage, vk::ImageLayout layout, bool skipChangeCheck = false); // Vertex Buffers VertexBufferDesc getAttribBufferDesc(uint32_t bufferIndex); bool checkCurrentAttribBuffers(); void bindAttribBuffers(); // Indices void maybeSwapIndices(); void maybeUnpackPrimitiveIndices(); bool checkCurrentIndices(); void bindIndexBuffer(); // Draws void bindDescriptors(); void bindShaderParams(); void drawGenericIndexed(latte::VGT_DRAW_INITIATOR drawInit, uint32_t numIndices, void *indices); void flushPendingDraws(); void drawCurrentState(); // Framebuffers FramebufferDesc getFramebufferDesc(); bool checkCurrentFramebuffer(); SurfaceViewObject * getColorBuffer(const ColorBufferDesc& info); SurfaceViewObject * getDepthStencilBuffer(const DepthStencilBufferDesc& info); void prepareCurrentFramebuffer(); // Swap Chains SwapChainObject * allocateSwapChain(const SwapChainDesc &desc); void releaseSwapChain(SwapChainObject *swapChain); // Shaders spirv::VertexShaderDesc getVertexShaderDesc(); spirv::GeometryShaderDesc getGeometryShaderDesc(); spirv::PixelShaderDesc getPixelShaderDesc(); bool checkCurrentVertexShader(); bool checkCurrentGeometryShader(); bool checkCurrentPixelShader(); bool checkCurrentRectStubShader(); // Render Passes RenderPassDesc getRenderPassDesc(); bool checkCurrentRenderPass(); // Pipeline Layouts PipelineLayoutDesc generatePipelineLayoutDesc(const PipelineDesc& pipelineDesc); PipelineLayoutObject * getPipelineLayout(const HashedDesc<PipelineLayoutDesc>& desc, bool forPush = false); // Pipelines PipelineDesc getPipelineDesc(); bool checkCurrentPipeline(); // Stream Out StreamContextObject * allocateStreamContext(uint32_t initialOffset); void releaseStreamContext(StreamContextObject* stream); void readbackStreamContext(StreamContextObject *stream, phys_addr writeAddr); StreamOutBufferDesc getStreamOutBufferDesc(uint32_t bufferIndex); bool checkCurrentStreamOut(); void bindStreamOutBuffers(); void beginStreamOut(); void endStreamOut(); // Debug void insertVkMarker(const std::string& text); void setVkObjectName(VkBuffer object, const char *name); void setVkObjectName(VkSampler object, const char *name); void setVkObjectName(VkImage object, const char *name); void setVkObjectName(VkImageView object, const char *name); void setVkObjectName(VkShaderModule object, const char *name); private: virtual void decafSetBuffer(const latte::pm4::DecafSetBuffer &data) override; virtual void decafCopyColorToScan(const latte::pm4::DecafCopyColorToScan &data) override; virtual void decafSwapBuffers(const latte::pm4::DecafSwapBuffers &data) override; virtual void decafClearColor(const latte::pm4::DecafClearColor &data) override; virtual void decafClearDepthStencil(const latte::pm4::DecafClearDepthStencil &data) override; virtual void decafOSScreenFlip(const latte::pm4::DecafOSScreenFlip &data) override; virtual void decafCopySurface(const latte::pm4::DecafCopySurface &data) override; virtual void decafExpandColorBuffer(const latte::pm4::DecafExpandColorBuffer &data) override; virtual void drawIndexAuto(const latte::pm4::DrawIndexAuto &data) override; virtual void drawIndex2(const latte::pm4::DrawIndex2 &data) override; virtual void drawIndexImmd(const latte::pm4::DrawIndexImmd &data) override; virtual void memWrite(const latte::pm4::MemWrite &data) override; virtual void eventWrite(const latte::pm4::EventWrite &data) override; virtual void eventWriteEOP(const latte::pm4::EventWriteEOP &data) override; virtual void pfpSyncMe(const latte::pm4::PfpSyncMe &data) override; virtual void setPredication(const latte::pm4::SetPredication &data) override; virtual void streamOutBaseUpdate(const latte::pm4::StreamOutBaseUpdate &data) override; virtual void streamOutBufferUpdate(const latte::pm4::StreamOutBufferUpdate &data) override; virtual void surfaceSync(const latte::pm4::SurfaceSync &data) override; virtual void waitMem(const latte::pm4::WaitMem &data) override; private: enum class RunState { None, Running, Stopped }; VulkanDisplayPipeline mDisplayPipeline = { }; vk::PhysicalDeviceTransformFeedbackFeaturesEXT mSupportedFeaturesTransformFeedback; vk::PhysicalDeviceFeatures2 mSupportedFeatures; std::atomic<RunState> mRunState = RunState::None; gpu::VulkanDriverDebugInfo mDebugInfo; std::thread mFenceThread; std::mutex mFenceMutex; std::list<SyncWaiter *> mFencesWaiting; std::list<SyncWaiter *> mFencesPending; std::condition_variable mFenceSignal; std::vector<SyncWaiter *> mWaiterPool; VmaAllocator mAllocator; uint64_t mMemChangeCounter = 0; uint64_t *mLastOccQueryAddr = nullptr; vk::QueryPool mLastOccQuery; vk::PipelineCache mPipelineCache; SyncWaiter *mActiveSyncWaiter = nullptr; vk::CommandBuffer mActiveCommandBuffer; std::vector<vk::DescriptorSet> mAvailableDescriptorSets; RenderPassObject *mActiveRenderPass = nullptr; FramebufferObject *mActiveFramebuffer = nullptr; PipelineObject *mActivePipeline = nullptr; uint64_t mActiveBatchIndex = 0; bool mActiveVsConstantsSet = false; spirv::VertexPushConstants mActiveVsConstants; bool mActivePsConstantsSet = false; spirv::FragmentPushConstants mActivePsConstants; bool mLastIndexBufferSet = false; IndexBufferCache mLastIndexBuffer; vk::DescriptorSetLayout mBaseDescriptorSetLayout; vk::PipelineLayout mPipelineLayout; std::array<StreamContextObject *, latte::MaxStreamOutBuffers> mStreamOutContext = { nullptr }; std::vector<DrawDesc> mPendingDraws; DrawDesc *mCurrentDraw = nullptr; DrawDesc mDrawCache; std::vector<MemChangeRecord> mDirtyMemCaches; std::vector<uint8_t> mScratchRetiling; std::vector<uint8_t> mScratchIdxSwap; std::vector<uint8_t> mScratchIdxPrim; std::vector<vk::WriteDescriptorSet> mScratchDescriptorWrites; using duration_system_clock = std::chrono::duration<double, std::chrono::system_clock::period>; using duration_ms = std::chrono::duration<double, std::chrono::milliseconds::period>; std::chrono::time_point<std::chrono::system_clock> mLastSwap; duration_system_clock mAverageFrameTime { 0.0 }; vk::PhysicalDevice mPhysDevice; vk::Device mDevice; vk::Queue mQueue; vk::DispatchLoaderDynamic mVkDynLoader; vk::CommandPool mCommandPool; vk::Sampler mBlankSampler; vk::Image mBlankImage; vk::ImageView mBlankImageView; vk::Buffer mBlankBuffer; SwapChainObject *mTvSwapChain = nullptr; SwapChainObject *mDrcSwapChain = nullptr; RenderPassObject *mRenderPass = nullptr; std::array<std::array<std::vector<StagingBuffer *>, 20>, 3> mStagingBuffers; std::vector<StreamContextObject *> mStreamOutContextPool; std::vector<vk::DescriptorPool> mDescriptorPools; std::vector<vk::QueryPool> mOccQueryPools; std::unordered_map<DataHash, SurfaceGroupObject*> mSurfaceGroups; std::unordered_map<DataHash, SurfaceObject*> mSurfaces; std::unordered_map<DataHash, SurfaceViewObject*> mSurfaceViews; std::unordered_map<DataHash, VertexShaderObject*> mVertexShaders; std::unordered_map<DataHash, GeometryShaderObject*> mGeometryShaders; std::unordered_map<DataHash, FramebufferObject*> mFramebuffers; std::unordered_map<DataHash, PixelShaderObject*> mPixelShaders; std::unordered_map<DataHash, RectStubShaderObject*> mRectStubShaders; std::unordered_map<DataHash, RenderPassObject*> mRenderPasses; std::unordered_map<DataHash, PipelineLayoutObject *> mPipelineLayouts; std::unordered_map<DataHash, PipelineObject*> mPipelines; std::unordered_map<DataHash, SamplerObject*> mSamplers; std::unordered_map<uint64_t, MemCacheObject *> mMemCaches; gpu7::tiling::vulkan::Retiler mGpuRetiler; DriverMemoryTracker mMemTracker; bool mDebug = false; bool mDumpShaders = false; bool mDumpShaderBinariesOnly = false; bool mDumpTextures = false; }; } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_fences.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { SyncWaiter * Driver::allocateSyncWaiter() { if (!mWaiterPool.empty()) { auto syncWaiter = mWaiterPool.back(); mWaiterPool.pop_back(); return syncWaiter; } auto syncWaiter = new SyncWaiter(); // Allocate a fence syncWaiter->fence = mDevice.createFence(vk::FenceCreateInfo()); // Allocate a command buffer vk::CommandBufferAllocateInfo cmdBufferAllocDesc(mCommandPool, vk::CommandBufferLevel::ePrimary, 1); syncWaiter->cmdBuffer = mDevice.allocateCommandBuffers(cmdBufferAllocDesc)[0]; return syncWaiter; } void Driver::releaseSyncWaiter(SyncWaiter *syncWaiter) { // Reset Vulkan state for this buffer resource thing mDevice.resetFences({ syncWaiter->fence }); syncWaiter->cmdBuffer.reset(vk::CommandBufferResetFlags()); // Reset our local state for this buffer resource thing syncWaiter->isCompleted = false; syncWaiter->callbacks.clear(); syncWaiter->stagingBuffers.clear(); syncWaiter->retileHandles.clear(); syncWaiter->descriptorPools.clear(); syncWaiter->occQueryPools.clear(); // Put this fence back in the pool mWaiterPool.push_back(syncWaiter); } void Driver::submitSyncWaiter(SyncWaiter *syncWaiter) { std::unique_lock lock(mFenceMutex); mFencesWaiting.push_back(syncWaiter); mFencesPending.push_back(syncWaiter); mFenceSignal.notify_all(); } void Driver::executeSyncWaiter(SyncWaiter *syncWaiter) { for (auto &callback : syncWaiter->callbacks) { callback(); } for (auto &buffer : syncWaiter->stagingBuffers) { retireStagingBuffer(buffer); } for (auto &handle : syncWaiter->retileHandles) { mGpuRetiler.releaseHandle(handle); } for (auto &pool : syncWaiter->descriptorPools) { retireDescriptorPool(pool); } for (auto &pool : syncWaiter->occQueryPools) { retireOccQueryPool(pool); } } void Driver::fenceWaiterThread() { std::unique_lock lock(mFenceMutex); while (mRunState == RunState::Running) { if (mFencesWaiting.size() == 0) { mFenceSignal.wait(lock); continue; } auto waiter = mFencesWaiting.front(); lock.unlock(); // Wake up every 100ms to check if we are no longer running auto waitTimeInNs = 10000000u; if (mDevice.waitForFences(1, &waiter->fence, false, waitTimeInNs) == vk::Result::eSuccess) { lock.lock(); waiter->isCompleted = true; mFencesWaiting.pop_front(); gpu::ringbuffer::wake(); } else { lock.lock(); } } } void Driver::checkSyncFences() { std::unique_lock lock(mFenceMutex); while (true) { // If there are no pending fences, return immediately if (mFencesPending.empty()) { break; } // Grab the oldest pending fence and see if its completed auto oldestPending = mFencesPending.front(); if (!oldestPending->isCompleted) { break; } mFencesPending.pop_front(); // Perform any actions this sync waiter has queued executeSyncWaiter(oldestPending); // Release the sync waiter back to our pool releaseSyncWaiter(oldestPending); } } void Driver::addRetireTask(std::function<void()> fn) { mActiveSyncWaiter->callbacks.push_back(fn); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_framebuffer.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "latte/latte_formats.h" namespace vulkan { FramebufferDesc Driver::getFramebufferDesc() { decaf_check(mCurrentDraw->renderPass); auto desc = FramebufferDesc {}; for (auto i = 0u; i < latte::MaxRenderTargets; ++i) { auto cb_color_base = getRegister<latte::CB_COLORN_BASE>(latte::Register::CB_COLOR0_BASE + i * 4); auto cb_color_size = getRegister<latte::CB_COLORN_SIZE>(latte::Register::CB_COLOR0_SIZE + i * 4); auto cb_color_info = getRegister<latte::CB_COLORN_INFO>(latte::Register::CB_COLOR0_INFO + i * 4); auto cb_color_view = getRegister<latte::CB_COLORN_VIEW>(latte::Register::CB_COLOR0_VIEW + i * 4); if (mCurrentDraw->renderPass->colorAttachmentIndexes[i] == -1) { // If the RenderPass doesn't want this attachment, skip it... desc.colorTargets[i] = ColorBufferDesc { 0, 0, 0, latte::CB_FORMAT::COLOR_INVALID, latte::CB_NUMBER_TYPE::UNORM, latte::BUFFER_ARRAY_MODE::LINEAR_GENERAL, 0, 0 }; continue; } decaf_check(cb_color_base.BASE_256B()); desc.colorTargets[i] = ColorBufferDesc { cb_color_base.BASE_256B(), cb_color_size.PITCH_TILE_MAX(), cb_color_size.SLICE_TILE_MAX(), cb_color_info.FORMAT(), cb_color_info.NUMBER_TYPE(), cb_color_info.ARRAY_MODE(), cb_color_view.SLICE_START(), cb_color_view.SLICE_MAX() + 1 }; } do { auto db_depth_base = getRegister<latte::DB_DEPTH_BASE>(latte::Register::DB_DEPTH_BASE); auto db_depth_size = getRegister<latte::DB_DEPTH_SIZE>(latte::Register::DB_DEPTH_SIZE); auto db_depth_info = getRegister<latte::DB_DEPTH_INFO>(latte::Register::DB_DEPTH_INFO); auto db_depth_view = getRegister<latte::DB_DEPTH_VIEW>(latte::Register::DB_DEPTH_VIEW); if (mCurrentDraw->renderPass->depthAttachmentIndex == -1) { // If the RenderPass doesn't want depth, skip it... desc.depthTarget = DepthStencilBufferDesc { 0, 0, 0, latte::DB_FORMAT::DEPTH_INVALID, latte::BUFFER_ARRAY_MODE::LINEAR_GENERAL, 0, 0 }; break; } decaf_check(db_depth_base.BASE_256B()); desc.depthTarget = DepthStencilBufferDesc { db_depth_base.BASE_256B(), db_depth_size.PITCH_TILE_MAX(), db_depth_size.SLICE_TILE_MAX(), db_depth_info.FORMAT(), db_depth_info.ARRAY_MODE(), db_depth_view.SLICE_START(), db_depth_view.SLICE_MAX() + 1 }; } while (false); return desc; } bool Driver::checkCurrentFramebuffer() { decaf_check(mCurrentDraw->renderPass); HashedDesc<FramebufferDesc> currentDesc = getFramebufferDesc(); if (mCurrentDraw->framebuffer && mCurrentDraw->framebuffer->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundFb = mFramebuffers[currentDesc.hash()]; if (foundFb) { mCurrentDraw->framebuffer = foundFb; mCurrentDraw->framebufferDirty = true; return true; } foundFb = new FramebufferObject(); foundFb->desc = currentDesc; vk::Extent2D overallSize; for (auto i = 0u; i < latte::MaxRenderTargets; ++i) { auto colorTarget = currentDesc->colorTargets[i]; if (!colorTarget.base256b) { // If the RenderPass doesn't want this attachment, skip it... foundFb->colorSurfaces[i] = nullptr; continue; } auto surfaceView = getColorBuffer(colorTarget); foundFb->colorSurfaces[i] = surfaceView; auto surface = surfaceView->surface; if (overallSize.width == 0 && overallSize.height == 0) { overallSize.width = surface->desc.width; overallSize.height = surface->desc.height; } else { overallSize.width = std::min(overallSize.width, surface->desc.width); overallSize.height = std::min(overallSize.height, surface->desc.height); } } do { auto depthTarget = currentDesc->depthTarget; if (!depthTarget.base256b) { // If the RenderPass doesn't want this attachment, skip it... foundFb->depthSurface = nullptr; continue; } auto surfaceView = getDepthStencilBuffer(depthTarget); foundFb->depthSurface = surfaceView; auto surface = surfaceView->surface; if (overallSize.width == 0 && overallSize.height == 0) { overallSize.width = surface->desc.width; overallSize.height = surface->desc.height; } else { overallSize.width = std::min(overallSize.width, surface->desc.width); overallSize.height = std::min(overallSize.height, surface->desc.height); } } while (false); // TODO: This currently sets up the framebuffers size to match the first // actual framebuffer surface we encounter. In reality I think we need // to make sure that the framebuffer is just the min of all surfaces. foundFb->renderArea = overallSize; mCurrentDraw->framebuffer = foundFb; mCurrentDraw->framebufferDirty = true; return true; } void Driver::prepareCurrentFramebuffer() { decaf_check(mCurrentDraw->renderPass); decaf_check(mCurrentDraw->framebuffer); auto& fb = mCurrentDraw->framebuffer; if (!mCurrentDraw->framebufferDirty) { // If the framebuffer is the same as the last frame, it is considered // clean, and we only need to perform barriers in order to make sure // that the image layout is appropriate. for (auto &surfaceView : fb->colorSurfaces) { if (surfaceView) { transitionSurfaceView(surfaceView, ResourceUsage::ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal, true); } } if (fb->depthSurface) { auto &surfaceView = fb->depthSurface; transitionSurfaceView(surfaceView, ResourceUsage::DepthStencilAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal, true); } return; } mCurrentDraw->framebufferDirty = false; // First we need to transition all the surfaces to their appropriate places. for (auto &surfaceView : fb->colorSurfaces) { if (surfaceView) { transitionSurfaceView(surfaceView, ResourceUsage::ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal); } } if (fb->depthSurface) { auto &surfaceView = fb->depthSurface; transitionSurfaceView(surfaceView, ResourceUsage::DepthStencilAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal); } // Next lets grab all the appropriate attachments we are using uint32_t numAttachments = 0; std::array<vk::ImageView, 9> attachments; bool needsRefresh = false; for (auto i = 0u; i < latte::MaxRenderTargets; ++i) { auto& surfaceView = fb->colorSurfaces[i]; if (!surfaceView) { // nothing bound here continue; } auto attachmentIndex = static_cast<uint32_t>(mCurrentDraw->renderPass->colorAttachmentIndexes[i]); numAttachments = std::max(numAttachments, attachmentIndex + 1); attachments[attachmentIndex] = surfaceView->imageView; if (fb->boundViews[attachmentIndex] != surfaceView->imageView) { needsRefresh = true; } } do { auto& surfaceView = fb->depthSurface; if (!surfaceView) { // nothing bound here continue; } auto attachmentIndex = static_cast<uint32_t>(mCurrentDraw->renderPass->depthAttachmentIndex); numAttachments = std::max(numAttachments, attachmentIndex + 1); attachments[attachmentIndex] = surfaceView->imageView; if (fb->boundViews[attachmentIndex] != surfaceView->imageView) { needsRefresh = true; } } while (false); if (!needsRefresh) { return; } // If we have an existing framebuffer, we can destroy it on the // next frame, once we are confident that nobody is using it if (fb->framebuffer) { auto oldFramebuffer = fb->framebuffer; addRetireTask([=](){ mDevice.destroyFramebuffer(oldFramebuffer); }); fb->framebuffer = nullptr; } vk::FramebufferCreateInfo framebufferDesc; framebufferDesc.renderPass = mCurrentDraw->renderPass->renderPass; framebufferDesc.attachmentCount = numAttachments; framebufferDesc.pAttachments = attachments.data(); framebufferDesc.width = fb->renderArea.width; framebufferDesc.height = fb->renderArea.height; framebufferDesc.layers = 1; auto framebuffer = mDevice.createFramebuffer(framebufferDesc); fb->framebuffer = framebuffer; fb->boundViews = attachments; } SurfaceViewObject * Driver::getColorBuffer(const ColorBufferDesc& info) { auto baseAddress = phys_addr(info.base256b << 8); auto pitch_tile_max = info.pitchTileMax; auto slice_tile_max = info.sliceTileMax; auto pitch = static_cast<uint32_t>((pitch_tile_max + 1) * latte::MicroTileWidth); auto height = static_cast<uint32_t>(((slice_tile_max + 1) * (latte::MicroTileWidth * latte::MicroTileHeight)) / pitch); auto surfaceFormat = latte::getColorBufferSurfaceFormat(info.format, info.numberType); auto tileMode = latte::getArrayModeTileMode(info.arrayMode); SurfaceDesc surfaceDesc; surfaceDesc.baseAddress = static_cast<uint32_t>(baseAddress); surfaceDesc.pitch = pitch; surfaceDesc.width = pitch; surfaceDesc.height = height; surfaceDesc.depth = 1; surfaceDesc.samples = 1u; surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D; surfaceDesc.format = surfaceFormat; surfaceDesc.tileType = latte::SQ_TILE_TYPE::DEFAULT; surfaceDesc.tileMode = tileMode; if (info.sliceEnd > 1) { surfaceDesc.depth = info.sliceEnd; surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D_ARRAY; } SurfaceViewDesc surfaceViewDesc; surfaceViewDesc.surfaceDesc = surfaceDesc; surfaceViewDesc.sliceStart = info.sliceStart; surfaceViewDesc.sliceEnd = info.sliceEnd; surfaceViewDesc.channels = { latte::SQ_SEL::SEL_X, latte::SQ_SEL::SEL_Y, latte::SQ_SEL::SEL_Z, latte::SQ_SEL::SEL_W }; return getSurfaceView(surfaceViewDesc); } SurfaceViewObject * Driver::getDepthStencilBuffer(const DepthStencilBufferDesc& info) { auto baseAddress = phys_addr(info.base256b << 8); auto pitch_tile_max = info.pitchTileMax; auto slice_tile_max = info.sliceTileMax; auto pitch = static_cast<uint32_t>((pitch_tile_max + 1) * latte::MicroTileWidth); auto height = static_cast<uint32_t>(((slice_tile_max + 1) * (latte::MicroTileWidth * latte::MicroTileHeight)) / pitch); auto surfaceFormat = latte::getDepthBufferSurfaceFormat(info.format); auto tileMode = latte::getArrayModeTileMode(info.arrayMode); SurfaceDesc surfaceDesc; surfaceDesc.baseAddress = static_cast<uint32_t>(baseAddress); surfaceDesc.pitch = pitch; surfaceDesc.width = pitch; surfaceDesc.height = height; surfaceDesc.depth = 1; surfaceDesc.samples = 1u; surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D; surfaceDesc.format = surfaceFormat; surfaceDesc.tileType = latte::SQ_TILE_TYPE::DEPTH; surfaceDesc.tileMode = tileMode; if (info.sliceEnd > 1) { surfaceDesc.depth = info.sliceEnd; surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D_ARRAY; } SurfaceViewDesc surfaceViewDesc; surfaceViewDesc.sliceStart = info.sliceStart; surfaceViewDesc.sliceEnd = info.sliceEnd; surfaceViewDesc.surfaceDesc = surfaceDesc; surfaceViewDesc.channels = { latte::SQ_SEL::SEL_X, latte::SQ_SEL::SEL_Y, latte::SQ_SEL::SEL_Z, latte::SQ_SEL::SEL_W }; return getSurfaceView(surfaceViewDesc); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_indices.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include <common/byte_swap_array.h> namespace vulkan { template<typename IndexType> static void unpackQuadList(uint32_t count, const IndexType *src, IndexType *dst) { // Unpack quad indices into triangle indices if (src) { for (IndexType i = 0u; i < count / 4; ++i) { IndexType index_0 = *src++; IndexType index_1 = *src++; IndexType index_2 = *src++; IndexType index_3 = *src++; *(dst++) = index_0; *(dst++) = index_1; *(dst++) = index_2; *(dst++) = index_0; *(dst++) = index_2; *(dst++) = index_3; } } else { IndexType index_0 = 0u; IndexType index_1 = 1u; IndexType index_2 = 2u; IndexType index_3 = 3u; for (IndexType i = 0u; i < count / 4; ++i) { IndexType index = i * 4; *(dst++) = index_0 + index; *(dst++) = index_1 + index; *(dst++) = index_2 + index; *(dst++) = index_0 + index; *(dst++) = index_2 + index; *(dst++) = index_3 + index; } } } static inline uint32_t calculateIndexBufferSize(latte::VGT_INDEX_TYPE indexType, uint32_t numIndices) { switch (indexType) { case latte::VGT_INDEX_TYPE::INDEX_16: return numIndices * 2; case latte::VGT_INDEX_TYPE::INDEX_32: return numIndices * 4; } decaf_abort("Unexpected index type"); } void Driver::maybeSwapIndices() { auto& drawDesc = *mCurrentDraw; auto& indices = mCurrentDraw->indices; if (indices) { if (mCurrentDraw->indexSwapMode == latte::VGT_DMA_SWAP::SWAP_16_BIT) { uint32_t indexBytes = calculateIndexBufferSize(mCurrentDraw->indexType, drawDesc.numIndices); indices = byte_swap_to_scratch<uint16_t>(indices, indexBytes, mScratchIdxSwap); } else if (drawDesc.indexSwapMode == latte::VGT_DMA_SWAP::SWAP_32_BIT) { uint32_t indexBytes = calculateIndexBufferSize(mCurrentDraw->indexType, drawDesc.numIndices); indices = byte_swap_to_scratch<uint32_t>(indices, indexBytes, mScratchIdxSwap); } else if (drawDesc.indexSwapMode == latte::VGT_DMA_SWAP::NONE) { // Nothing to do here! } else { decaf_abort(fmt::format("Unimplemented vgt_dma_index_type.SWAP_MODE {}", drawDesc.indexSwapMode)); } } } void Driver::maybeUnpackPrimitiveIndices() { auto &drawDesc = *mCurrentDraw; auto &indices = mCurrentDraw->indices; if (drawDesc.primitiveType == latte::VGT_DI_PRIMITIVE_TYPE::QUADLIST) { auto indexBytes = calculateIndexBufferSize(drawDesc.indexType, drawDesc.numIndices); mScratchIdxPrim.resize(indexBytes / 4 * 6); if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_16) { unpackQuadList(drawDesc.numIndices, reinterpret_cast<uint16_t*>(indices), reinterpret_cast<uint16_t*>(mScratchIdxPrim.data())); } else if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_32) { unpackQuadList(drawDesc.numIndices, reinterpret_cast<uint32_t*>(indices), reinterpret_cast<uint32_t*>(mScratchIdxPrim.data())); } else { decaf_abort("Unexpected index type"); } drawDesc.primitiveType = latte::VGT_DI_PRIMITIVE_TYPE::TRILIST; drawDesc.numIndices = drawDesc.numIndices / 4 * 6; indices = mScratchIdxPrim.data(); } else if (drawDesc.primitiveType == latte::VGT_DI_PRIMITIVE_TYPE::LINELOOP) { auto indexBytes = calculateIndexBufferSize(drawDesc.indexType, drawDesc.numIndices + 1); mScratchIdxPrim.resize(indexBytes); if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_16) { auto dst = reinterpret_cast<uint16_t *>(mScratchIdxPrim.data()); std::memcpy(dst, indices, indexBytes - 2); dst[drawDesc.numIndices] = dst[0]; } else if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_32) { auto dst = reinterpret_cast<uint32_t *>(mScratchIdxPrim.data()); std::memcpy(dst, indices, indexBytes - 4); dst[drawDesc.numIndices] = dst[0]; } else { decaf_abort("Unexpected index type"); } drawDesc.primitiveType = latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP; drawDesc.numIndices = drawDesc.numIndices + 1; indices = mScratchIdxPrim.data(); } } bool Driver::checkCurrentIndices() { auto& drawDesc = *mCurrentDraw; if (mLastIndexBufferSet) { if (drawDesc.indices == mLastIndexBuffer.indexData && drawDesc.indexType == mLastIndexBuffer.indexType && drawDesc.numIndices == mLastIndexBuffer.numIndices && drawDesc.indexSwapMode == mLastIndexBuffer.swapMode && drawDesc.primitiveType == mLastIndexBuffer.primitiveType) { drawDesc.primitiveType = mLastIndexBuffer.newPrimitiveType; drawDesc.numIndices = mLastIndexBuffer.newNumIndices; drawDesc.indexBuffer = mLastIndexBuffer.indexBuffer; return true; } } mLastIndexBuffer.indexData = drawDesc.indices; mLastIndexBuffer.indexType = drawDesc.indexType; mLastIndexBuffer.numIndices = drawDesc.numIndices; mLastIndexBuffer.swapMode = drawDesc.indexSwapMode; mLastIndexBuffer.primitiveType = drawDesc.primitiveType; maybeSwapIndices(); maybeUnpackPrimitiveIndices(); if (drawDesc.indices) { auto indexBytes = calculateIndexBufferSize(drawDesc.indexType, drawDesc.numIndices); auto indicesBuf = getStagingBuffer(indexBytes, StagingBufferType::CpuToGpu); copyToStagingBuffer(indicesBuf, 0, drawDesc.indices, indexBytes); transitionStagingBuffer(indicesBuf, ResourceUsage::IndexBuffer); drawDesc.indexBuffer = indicesBuf; } else { drawDesc.indexBuffer = nullptr; } mLastIndexBuffer.newPrimitiveType = drawDesc.primitiveType; mLastIndexBuffer.newNumIndices = drawDesc.numIndices; mLastIndexBuffer.indexBuffer = drawDesc.indexBuffer; mLastIndexBufferSet = true; return true; } void Driver::bindIndexBuffer() { if (!mCurrentDraw->indexBuffer) { return; } auto& drawDesc = *mCurrentDraw; vk::IndexType bindIndexType; if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_16) { bindIndexType = vk::IndexType::eUint16; } else if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_32) { bindIndexType = vk::IndexType::eUint32; } else { decaf_abort("Unexpected index type"); } mActiveCommandBuffer.bindIndexBuffer(mCurrentDraw->indexBuffer->buffer, 0, bindIndexType); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_memcache.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include <common/rangecombiner.h> namespace vulkan { // void(MemSegment&) template<typename FunctorType> static inline void forEachMemSegment(MemSegmentRef begin, uint32_t size, FunctorType functor) { auto segment = begin.get(); for (auto sizeLeft = size; sizeLeft > 0;) { functor(*segment); sizeLeft -= segment->size; segment = segment->nextSegment; } } // void(MemCacheSection&, MemSegment&) template<typename FunctorType> static inline void forEachSectionSegment(MemCacheObject *cache, SectionRange range, FunctorType functor) { auto rangeStart = range.start; auto rangeEnd = range.start + range.count; auto firstSection = cache->sections[rangeStart]; auto segment = firstSection.firstSegment.get(); for (auto i = rangeStart; i < rangeEnd; ++i) { auto& section = cache->sections[i]; for (auto sizeLeft = cache->sectionSize; sizeLeft > 0;) { functor(section, *segment); sizeLeft -= segment->size; segment = segment->nextSegment; } } } // void(MemSegment&) template<typename FunctorType> static inline void forEachMemSegment(MemCacheObject *cache, SectionRange range, FunctorType functor) { auto& firstSection = cache->sections[range.start]; auto begin = firstSection.firstSegment; auto size = range.count * cache->sectionSize; forEachMemSegment(begin, size, functor); } MemCacheObject * Driver::_allocMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize) { uint32_t totalSize = 0; std::vector<MemCacheSection> sections; for (auto i = 0u; i < numSections; ++i) { auto firstSegment = mMemTracker.get(address + totalSize, sectionSize); MemCacheSection section; section.lastChangeIndex = 0; section.firstSegment = firstSegment; section.needsUpload = false; section.wantedChangeIndex = 0; sections.push_back(section); totalSize += sectionSize; } // We add 32 bytes to all our buffers because in many cases, Vulkan will // error if we attempt to read past the edges of our buffers (such as can // happen with vertex buffers when the stride is 12 but the read is 16. // This will allow us to correctly execute these cases like hardware does // which sort of has free-range over memory. Note that this won't copy // hardware behaviour precisely, since hardware actually can access the // real contents after the end of the buffer whereas we just shove garbage // there. Better than not executing the draw at all though! vk::BufferCreateInfo bufferDesc; bufferDesc.size = totalSize + 32; bufferDesc.usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransformFeedbackBufferEXT | vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eTransferSrc; bufferDesc.sharingMode = vk::SharingMode::eExclusive; bufferDesc.queueFamilyIndexCount = 0; bufferDesc.pQueueFamilyIndices = nullptr; VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; VkBuffer buffer; VmaAllocation allocation; CHECK_VK_RESULT( vmaCreateBuffer(mAllocator, reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc), &allocInfo, &buffer, &allocation, nullptr)); static uint64_t memCacheIndex = 0; setVkObjectName(buffer, fmt::format("mcch_{}_{:08x}_{}", memCacheIndex++, address.getAddress(), totalSize).c_str()); auto cache = new MemCacheObject(); cache->address = address; cache->size = totalSize; cache->numSections = numSections; cache->sectionSize = sectionSize; cache->allocation = allocation; cache->buffer = buffer; cache->sections = std::move(sections); cache->delayedWriteFunc = nullptr; cache->delayedWriteRange = {}; cache->lastUsageIndex = mActiveBatchIndex; cache->refCount = 0; return cache; } void Driver::_uploadMemCache(MemCacheObject *cache, SectionRange range) { uint8_t *cacheBasePtr = phys_cast<uint8_t*>(cache->address).getRawPointer(); auto offsetStart = cache->sectionSize * range.start; auto rangeSize = cache->sectionSize * range.count; uint8_t *uploadData = cacheBasePtr + offsetStart; // Upload the data to the CPU auto stagingBuffer = getStagingBuffer(rangeSize, StagingBufferType::CpuToGpu); copyToStagingBuffer(stagingBuffer, 0, uploadData, rangeSize); // Transition the buffers appropriately transitionStagingBuffer(stagingBuffer, ResourceUsage::TransferSrc); _barrierMemCache(cache, ResourceUsage::TransferDst, range); // Copy the data out of the staging buffer into the memory cache. vk::BufferCopy copyDesc; copyDesc.srcOffset = 0; copyDesc.dstOffset = offsetStart; copyDesc.size = rangeSize; mActiveCommandBuffer.copyBuffer(stagingBuffer->buffer, cache->buffer, { copyDesc }); } void Driver::_downloadMemCache(MemCacheObject *cache, SectionRange range) { // If we have a pending delayed write that overlaps, we are going to need to // process it here before we can actually write to the CPU. if (cache->delayedWriteFunc && cache->delayedWriteRange.intersects(range)) { cache->delayedWriteFunc(); cache->delayedWriteFunc = nullptr; } // Lets start doing the actual download! auto offsetStart = cache->sectionSize * range.start; auto rangeSize = cache->sectionSize * range.count; // Create a staging buffer to use for the readback auto stagingBuffer = getStagingBuffer(rangeSize, StagingBufferType::GpuToCpu); // Transition the buffers appropriately transitionStagingBuffer(stagingBuffer, ResourceUsage::TransferDst); _barrierMemCache(cache, ResourceUsage::TransferSrc, range); // Copy the data into our staging buffer from the cache object vk::BufferCopy copyDesc; copyDesc.srcOffset = offsetStart; copyDesc.dstOffset = 0; copyDesc.size = rangeSize; mActiveCommandBuffer.copyBuffer(cache->buffer, stagingBuffer->buffer, { copyDesc }); // We have to pre-transition the buffer to being host-read, as it would be otherwise // illegal to be doing the transition during the retire function below. transitionStagingBuffer(stagingBuffer, ResourceUsage::HostRead); // TODO: This can be optimized... A lot... // Move the data onto the CPU on a per-section basis for (auto i = range.start; i < range.start + range.count; ++i) { auto& section = cache->sections[i]; auto changeIndex = section.lastChangeIndex; addRetireTask([=](){ void *data = phys_cast<void*>(cache->address + i * cache->sectionSize).getRawPointer(); auto stagingOffset = (i - range.start) * cache->sectionSize; // Copy the data out of the staging area into memory copyFromStagingBuffer(stagingBuffer, stagingOffset, data, cache->sectionSize); // We need to calculate new data hashes for the relevant segments that // are affected by this image and are not still being GPU written. forEachMemSegment(section.firstSegment, cache->sectionSize, [&](MemSegment& segment){ // For safety purposes, lets confirm that this write was intended. decaf_check(segment.lastChangeIndex >= section.lastChangeIndex); // Only bother recalculating the hashing if we are not already waiting // for more writes to this segment... if (segment.lastChangeIndex == changeIndex) { mMemTracker.markSegmentGpuDone(&segment); } }); }); } } void Driver::_refreshMemCache_Check(MemCacheObject *cache, SectionRange range) { forEachSectionSegment(cache, range, [&](MemCacheSection& section, MemSegment& segment){ // Refresh the segment to make sure we have up-to-date information mMemTracker.refreshSegment(&segment); // Update the wanted index if (segment.lastChangeIndex > section.wantedChangeIndex) { section.wantedChangeIndex = segment.lastChangeIndex; } // Check if we already have this data, if we do, there is nothing to do. if (section.lastChangeIndex >= segment.lastChangeIndex) { return; } // Check if we have no last-owner first, obviously an upload is needed in that // case. Additionally, we are required to perform an upload if the last owner // has rearranged the data such that it might not make sense for us anymore. if (!segment.lastChangeOwner) { section.needsUpload = true; } }); } void Driver::_refreshMemCache_Update(MemCacheObject *cache, SectionRange range) { // This is an optimization to enable us to do bigger copies and uploads in // the case of a fragmented set of underlying segments which point to the // same thing contiguously. auto uploadCombiner = makeRangeCombiner<void*, uint32_t, uint32_t>( [&](void*, uint32_t start, uint32_t count){ _uploadMemCache(cache, { start, count }); }); for (auto i = range.start; i < range.start + range.count; ++i) { auto& section = cache->sections[i]; if (section.needsUpload) { uploadCombiner.push(nullptr, i, 1); } } uploadCombiner.flush(); auto copyCombiner = makeRangeCombiner<MemCacheObject*, phys_addr, uint32_t>( [&](MemCacheObject *object, phys_addr address, uint32_t size){ vk::BufferCopy copyDesc; copyDesc.srcOffset = static_cast<uint32_t>(address - object->address); copyDesc.dstOffset = static_cast<uint32_t>(address - cache->address); copyDesc.size = size; mActiveCommandBuffer.copyBuffer(object->buffer, cache->buffer, { copyDesc }); }); // We have to do this independantly, as our section updates need to // happen only after all segments have been written... for (auto i = range.start; i < range.start + range.count; ++i) { auto& section = cache->sections[i]; forEachMemSegment(section.firstSegment, cache->sectionSize, [&](MemSegment& segment){ // Note that we have to do this delayed write check before we exit // early as we are not actually 'up to date' until the write occurs. auto& lastChangeOwner = segment.lastChangeOwner; if (lastChangeOwner && lastChangeOwner->delayedWriteFunc) { if (lastChangeOwner->delayedWriteRange.intersects(range)) { lastChangeOwner->delayedWriteFunc(); lastChangeOwner->delayedWriteFunc = nullptr; lastChangeOwner->delayedWriteRange = { 0, 0 }; } } // Check to see if we already have the latest data from this segment available // to us in our buffer already (to avoid needing to copy). if (section.lastChangeIndex >= segment.lastChangeIndex) { return; } // Lets make sure if there was no owner, that we uploaded this previously, // and that we take ownership and update the last change index. if (!segment.lastChangeOwner) { decaf_check(section.needsUpload); segment.lastChangeOwner = cache; return; } // Push this copy to our list of copies we want to do. copyCombiner.push(segment.lastChangeOwner, segment.address, segment.size); // If the segment was not GPU written, lets also take ownership since it will // increase the chances of condensing buffer copies for future transfers. if (!segment.gpuWritten) { segment.lastChangeOwner = cache; } }); // Mark the section as having been updated. section.lastChangeIndex = section.wantedChangeIndex; // Mark it as no longer needing to be uploaded section.needsUpload = false; } copyCombiner.flush(); } void Driver::_refreshMemCache(MemCacheObject *cache, SectionRange range) { _refreshMemCache_Check(cache, range); _refreshMemCache_Update(cache, range); } void Driver::_invalidateMemCache(MemCacheObject *cache, SectionRange range, const DelayedMemWriteFunc& delayedWriteFunc) { auto changeIndex = mMemTracker.newChangeIndex(); // If there is already a delayed write and its not covered by // this particular invalidation, we will have to execute it. if (cache->delayedWriteFunc) { if (!range.covers(cache->delayedWriteRange)) { cache->delayedWriteFunc(); } cache->delayedWriteFunc = nullptr; cache->delayedWriteRange = {}; } if (delayedWriteFunc) { cache->delayedWriteFunc = delayedWriteFunc; cache->delayedWriteRange = range; } for (auto i = range.start; i < range.start + range.count; ++i) { cache->sections[i].lastChangeIndex = changeIndex; } forEachMemSegment(cache, range, [&](MemSegment& segment){ segment.lastChangeIndex = changeIndex; segment.lastChangeOwner = cache; segment.gpuWritten = true; }); if (delayedWriteFunc) { // If there is a delayed write, we assume that this must have been a surface transition // that was happening, and we don't want to copy these all back, so lets not mark it // for download later... return; } mDirtyMemCaches.push_back({ changeIndex, cache, range }); } void Driver::_barrierMemCache(MemCacheObject *cache, ResourceUsage usage, SectionRange range) { auto offsetStart = range.start * cache->sectionSize;; auto memSize = range.count * cache->sectionSize; if (cache->activeUsage == usage) { return; } auto srcMeta = getResourceUsageMeta(cache->activeUsage); auto dstMeta = getResourceUsageMeta(usage); vk::BufferMemoryBarrier bufferBarrier; bufferBarrier.srcAccessMask = srcMeta.accessFlags; bufferBarrier.dstAccessMask = dstMeta.accessFlags; bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.buffer = cache->buffer; bufferBarrier.offset = offsetStart; bufferBarrier.size = memSize; mActiveCommandBuffer.pipelineBarrier( srcMeta.stageFlags, dstMeta.stageFlags, vk::DependencyFlags(), {}, { bufferBarrier }, {}); cache->activeUsage = usage; } SectionRange Driver::_sectionsFromOffsets(MemCacheObject *cache, uint32_t begin, uint32_t end) { decaf_check(begin % cache->sectionSize == 0); decaf_check(end % cache->sectionSize == 0); SectionRange range; range.start = begin / cache->sectionSize; range.count = (end - begin) / cache->sectionSize; return range; } MemCacheObject * Driver::getMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize) { // Note: We cast here first to make sure the types are in the appropriate // types, otherwise the bit operations may not behave as expected. uint64_t lookupAddr = address.getAddress(); uint64_t lookupSize = numSections * sectionSize; uint64_t lookupKey = (lookupSize << 32) | lookupAddr; // TODO: We should implement the ability to 'grow' a MemCacheObject to // contain more sections on top of the ones that already exist in the // object. This will improve memory usage, and help when new surfaces // appear which just add slices. auto& cacheRef = mMemCaches[lookupKey]; auto cache = cacheRef; while (cache) { if (cache->sectionSize == sectionSize) { break; } cache = cache->nextObject; } if (!cache) { // If there is not yet a cache object, we need to create it. cache = _allocMemCache(address, numSections, sectionSize); // Be warned about the fact that `cache` is a reference object, // and we are putting the new object at the head of the list. cache->nextObject = cacheRef; cacheRef = cache; } decaf_check(cache->address == address); decaf_check(cache->numSections == numSections); decaf_check(cache->sectionSize == sectionSize); return cache; } void Driver::invalidateMemCacheDelayed(MemCacheObject *cache, uint32_t offset, uint32_t size, const DelayedMemWriteFunc& delayedWriteHandler) { // Calculate which sections actually apply to this... auto range = _sectionsFromOffsets(cache, offset, offset + size); // Perform the invalidation _invalidateMemCache(cache, range, delayedWriteHandler); } void Driver::transitionMemCache(MemCacheObject *cache, ResourceUsage usage, uint32_t offset, uint32_t size) { // If no size was specified, it means the whole buffer if (size == 0) { size = cache->size - offset; } // Calculate which sections actually apply to this... auto range = _sectionsFromOffsets(cache, offset, offset + size); // Check if this is for reading or writing auto forWrite = getResourceUsageMeta(usage).isWrite; // Update the last usage here cache->lastUsageIndex = mActiveBatchIndex; // If this is a write-usage, we need to register this object to be // invalidated later when the batch is completed. Otherwise we // need to read the data for usage. Note that its safe to do the // invalidation before the actual write occurs since no transfers // occur until the end of the batch (or when it changes again). _refreshMemCache(cache, range); if (forWrite) { _invalidateMemCache(cache, range, nullptr); } _barrierMemCache(cache, usage, range); } void Driver::downloadPendingMemCache() { for (auto& dirtyRecord : mDirtyMemCaches) { auto& cache = dirtyRecord.cache; // If none of the sections we are targetting still have our change // index, there is no need to do any of the work. bool needsDownload = false; for (auto i = 0u; i < dirtyRecord.sections.count; ++i) { auto sectionIndex = dirtyRecord.sections.start + i; if (cache->sections[sectionIndex].lastChangeIndex <= dirtyRecord.changeIndex) { needsDownload = true; break; } } // Download the memory cache back to the CPU. Note that we have to // keep this object marked as being written by the GPU until its // actually downloaded and we can rehash it. if (needsDownload) { _downloadMemCache(cache, dirtyRecord.sections); } } mDirtyMemCaches.clear(); } DataBufferObject * Driver::getDataMemCache(phys_addr baseAddress, uint32_t size) { auto cache = getMemCache(baseAddress, 1, size); return static_cast<DataBufferObject *>(cache); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_memtracker.h ================================================ #pragma once #ifdef DECAF_VULKAN #include <cstdint> #include <libcpu/be2_struct.h> #include <libcpu/memtrack.h> #include <map> #include <forward_list> namespace vulkan { /* This memory tracker keeps track of a range of phys_addr memory for changes. It attempts to make iteration as fast as possible by trying to keep the segments linearly arranged in memory, while still allowing fast insertions. It does this by using a dynamic list to hold the segments initially, but then quickly optimizes them into a linear vector. Important Semantics: - SegmentRef's are stable - Pointers to segments ARE NOT stable. - Don't forget to call optimize periodically. */ template<typename _DataOwnerType> class MemoryTracker { public: struct Segment { // We keep a linked list of segments connected to eachother for // faster iteration through all the segments. It is intentionally // at the top to increase the chance of being on the cacheline of // the previous segment. Segment *nextSegment = nullptr; // Meta-data about what this segment represents phys_addr address = {}; uint32_t size = 0; // Stores the last memory tracking state for this segment. cpu::MemtrackState dataState; // Tracks the last CPU check of this segment, to avoid checking the // memory multiple times in a single batch. uint64_t lastCheckIndex = 0; // Records if there is a pending GPU write for this data. This is to ensure // that we do not overwrite a pending GPU write with random CPU data. bool gpuWritten = false; // The last change for this segment uint64_t lastChangeIndex = 0; // Represents the object owning the most up to date version of the data. _DataOwnerType lastChangeOwner = {}; }; protected: typedef std::map<phys_addr, Segment*> SegmentMap; typedef typename SegmentMap::iterator SegmentMapIter; public: class SegmentRef { friend MemoryTracker; public: SegmentRef() { } Segment * get() const { return mIterator->second; } Segment * operator->() const { return get(); } protected: SegmentRef(SegmentMapIter iterator) : mIterator(iterator) { } SegmentMapIter mIterator; }; void nextBatch() { mCurrentBatchIndex++; } uint64_t newChangeIndex() { return ++mChangeCounter; } SegmentRef get(phys_addr address, uint32_t size) { auto iter = _getSegment(address, size); _ensureSegments(iter, size); return iter; } void markSegmentGpuDone(Segment *segment) { segment->dataState = cpu::getMemoryState(segment->address, segment->size); segment->gpuWritten = false; } void refreshSegment(Segment *segment) { _refreshSegment(segment); } void optimize() { // TODO: Maybe avoid optimizing for low dynamic segment counts. if (mLookupMap.empty() || mDynamicSegments.empty()) { // If the map is empty, or there are no dynamic segments, // there is no need to optimize the map. return; } // Reserve the new linear segments std::vector<Segment> newLinearSegments; newLinearSegments.reserve(mLookupMap.size()); // Move all our segments auto iter = mLookupMap.begin(); newLinearSegments.push_back(std::move(*iter->second)); auto lastSegment = &newLinearSegments.back(); iter->second = lastSegment; ++iter; for ( ; iter != mLookupMap.end(); ++iter) { newLinearSegments.push_back(std::move(*iter->second)); auto newSegment = &newLinearSegments.back(); iter->second = newSegment; lastSegment->nextSegment = newSegment; lastSegment = newSegment; } lastSegment->nextSegment = nullptr; // Move the new linear map into place and clear the dynamic segments // that are no longer being used. mLinearSegments = std::move(newLinearSegments); mDynamicSegments.clear(); } protected: SegmentMapIter _insertSegment(SegmentMapIter position, phys_addr address, uint32_t size) { auto &segment = mDynamicSegments.emplace_front(); segment.address = address; segment.size = size; auto newIter = mLookupMap.insert(position, { segment.address, &segment }); if (newIter != mLookupMap.begin()) { auto prevIter = newIter; prevIter--; prevIter->second->nextSegment = &segment; } auto nextIter = newIter; nextIter++; if (nextIter != mLookupMap.end()) { segment.nextSegment = nextIter->second; } else { segment.nextSegment = nullptr; } return newIter; } SegmentMapIter _splitSegment(SegmentMapIter iter, uint32_t newSize) { auto& oldSegment = iter->second; decaf_check(oldSegment->size > newSize); // Save the old info so we can do the final hash check auto oldSize = oldSegment->size; auto oldState = oldSegment->dataState; // Create the new segment, save it to the map and resize the // old segment to not overlap the new one. auto newIter = _insertSegment(iter, oldSegment->address + newSize, oldSegment->size - newSize); auto &newSegment = newIter->second; oldSegment->size = newSize; // Copy over some state from the old Segment newSegment->lastCheckIndex = oldSegment->lastCheckIndex; newSegment->gpuWritten = oldSegment->gpuWritten; newSegment->lastChangeIndex = oldSegment->lastChangeIndex; newSegment->lastChangeOwner = oldSegment->lastChangeOwner; // If the old segment was written by the GPU, there is no need to // do any of the hashing work, it will be done during readback. if (oldSegment->gpuWritten) { newSegment->dataState = {}; return newIter; } // Lets calculate the new hashes for the segments after they have been // split to ensure we don't do unneeded uploading after a split. We check // that the new hashes reflect the same data that previous existed in the // segment following this. oldSegment->dataState = cpu::getMemoryState(oldSegment->address, oldSegment->size); newSegment->dataState = cpu::getMemoryState(newSegment->address, newSegment->size); // If the segment was last checked during this batch, there is no need to do // any additional work to figure out if the data changed. if (oldSegment->lastCheckIndex >= mCurrentBatchIndex) { return newIter; } // Now check that the data hasn't changed since we did the last hashing. auto newFullState = cpu::getMemoryState(oldSegment->address, oldSize); if (newFullState != oldState) { auto changeIndex = newChangeIndex(); oldSegment->lastCheckIndex = mCurrentBatchIndex; oldSegment->lastChangeIndex = changeIndex; oldSegment->lastChangeOwner = nullptr; newSegment->lastCheckIndex = mCurrentBatchIndex; newSegment->lastChangeIndex = changeIndex; newSegment->lastChangeOwner = nullptr; } return newIter; } SegmentMapIter _getSegment(phys_addr address, uint32_t maxSize) { auto iter = mLookupMap.lower_bound(address); if (iter != mLookupMap.end()) { // Check if we found an exact match. If so, return that. if (iter->second->address == address) { return iter; } // Otherwise we need to bound our maxSize not to tramble this. auto gapSize = static_cast<uint32_t>(iter->second->address - address); maxSize = std::min(maxSize, gapSize); } // Check that we are not at the beginning, if we are and it wasn't an // exact match, we know we need to make a new segment before this one. if (iter != mLookupMap.begin()) { --iter; auto& foundSegment = iter->second; // If the previous segment covers this new segments range, we // need to split it and return that. if (address < foundSegment->address + foundSegment->size) { auto newSize = static_cast<uint32_t>(address - foundSegment->address); return _splitSegment(iter, newSize); } } // maxSize being 0 indicates that we need to ensure there is a split // point in the map, but we don't need to generate anything for it. if (maxSize == 0) { return mLookupMap.end(); } // Allocate a new segment for this at the end return _insertSegment(mLookupMap.end(), address, maxSize); } void _ensureSegments(SegmentMapIter firstSegment, uint32_t size) { auto curAddress = firstSegment->second->address; auto sizeLeft = size; // We ensure there is a split point at the end for us to hit. // TODO: Do this below as it will be slightly faster _getSegment(curAddress + size, 0); auto iter = firstSegment; while (sizeLeft > 0) { if (iter == mLookupMap.end()) { _insertSegment(iter, curAddress, sizeLeft); break; } if (iter->second->address != curAddress) { auto gapSize = static_cast<uint32_t>(iter->second->address - curAddress); auto newSize = std::min(gapSize, sizeLeft); iter = _insertSegment(iter, curAddress, newSize); } auto& segment = iter->second; decaf_check(segment->address == curAddress); decaf_check(segment->size <= sizeLeft); curAddress += segment->size; sizeLeft -= segment->size; iter++; } } void _refreshSegment(Segment *segment) { // If this segment was last written by the GPU, then we are guarenteed // to already have the most up to date data in a GPU buffer somewhere. if (segment->gpuWritten) { return; } // If this segment was already checked during this batch, there is no // need to go check it again.. if (segment->lastCheckIndex >= mCurrentBatchIndex) { return; } // Rehash all our data auto dataState = cpu::getMemoryState(segment->address, segment->size); // If we already have a hash, and the hash already matches, we can // simply mark it as checked without any more downloads. if (segment->lastCheckIndex > 0 && segment->dataState == dataState) { segment->lastCheckIndex = mCurrentBatchIndex; return; } // The data has changed, lets update our internal hashes and create // a new memory change event to represent this. auto changeIndex = newChangeIndex(); segment->dataState = dataState; segment->lastCheckIndex = mCurrentBatchIndex; segment->lastChangeIndex = changeIndex; segment->lastChangeOwner = nullptr; } uint64_t mCurrentBatchIndex = 0; uint64_t mChangeCounter = 0; SegmentMap mLookupMap; std::vector<Segment> mLinearSegments; std::forward_list<Segment> mDynamicSegments; }; } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_pipelinelayouts.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_utils.h" #include "spirv/spirv_pushconstants.h" namespace vulkan { PipelineLayoutDesc Driver::generatePipelineLayoutDesc(const PipelineDesc& pipelineDesc) { PipelineLayoutDesc layoutDesc; if (pipelineDesc.vertexShader) { const auto& shaderMeta = pipelineDesc.vertexShader->shader.meta; if (shaderMeta.cfileUsed) { layoutDesc.vsBufferUsed[0] = true; for (auto i = 1; i < latte::MaxUniformBlocks; ++i) { layoutDesc.vsBufferUsed[i] = false; } } else { for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { layoutDesc.vsBufferUsed[i] = shaderMeta.cbufferUsed[i]; } } for (auto i = 0; i < latte::MaxSamplers; ++i) { layoutDesc.vsSamplerUsed[i] = shaderMeta.samplerUsed[i]; } for (auto i = 0; i < latte::MaxTextures; ++i) { layoutDesc.vsTextureUsed[i] = shaderMeta.textureUsed[i]; } } else { for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { layoutDesc.vsBufferUsed[i] = false; } for (auto i = 0; i < latte::MaxSamplers; ++i) { layoutDesc.vsSamplerUsed[i] = false; } for (auto i = 0; i < latte::MaxTextures; ++i) { layoutDesc.vsTextureUsed[i] = false; } } if (pipelineDesc.geometryShader) { const auto& shaderMeta = pipelineDesc.geometryShader->shader.meta; if (shaderMeta.cfileUsed) { layoutDesc.vsBufferUsed[0] = true; for (auto i = 1; i < latte::MaxUniformBlocks; ++i) { layoutDesc.gsBufferUsed[i] = false; } } else { for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { layoutDesc.gsBufferUsed[i] = shaderMeta.cbufferUsed[i]; } } for (auto i = 0; i < latte::MaxSamplers; ++i) { layoutDesc.gsSamplerUsed[i] = shaderMeta.samplerUsed[i]; } for (auto i = 0; i < latte::MaxTextures; ++i) { layoutDesc.gsTextureUsed[i] = shaderMeta.textureUsed[i]; } } else { for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { layoutDesc.gsBufferUsed[i] = false; } for (auto i = 0; i < latte::MaxSamplers; ++i) { layoutDesc.gsSamplerUsed[i] = false; } for (auto i = 0; i < latte::MaxTextures; ++i) { layoutDesc.gsTextureUsed[i] = false; } } if (pipelineDesc.pixelShader) { const auto& shaderMeta = pipelineDesc.pixelShader->shader.meta; if (shaderMeta.cfileUsed) { layoutDesc.psBufferUsed[0] = true; for (auto i = 1; i < latte::MaxUniformBlocks; ++i) { layoutDesc.psBufferUsed[i] = false; } } else { for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { layoutDesc.psBufferUsed[i] = shaderMeta.cbufferUsed[i]; } } for (auto i = 0; i < latte::MaxSamplers; ++i) { layoutDesc.psSamplerUsed[i] = shaderMeta.samplerUsed[i]; } for (auto i = 0; i < latte::MaxTextures; ++i) { layoutDesc.psTextureUsed[i] = shaderMeta.textureUsed[i]; } } else { for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { layoutDesc.psBufferUsed[i] = false; } for (auto i = 0; i < latte::MaxSamplers; ++i) { layoutDesc.psSamplerUsed[i] = false; } for (auto i = 0; i < latte::MaxTextures; ++i) { layoutDesc.psTextureUsed[i] = false; } } // Calculate the descriptor count for (auto i = 0; i < latte::MaxTextures; ++i) { if (layoutDesc.vsSamplerUsed[i] || layoutDesc.vsTextureUsed[i]) { layoutDesc.numDescriptors++; } if (layoutDesc.gsSamplerUsed[i] || layoutDesc.gsTextureUsed[i]) { layoutDesc.numDescriptors++; } if (layoutDesc.psSamplerUsed[i] || layoutDesc.psTextureUsed[i]) { layoutDesc.numDescriptors++; } } for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { if (layoutDesc.vsBufferUsed[i]) { layoutDesc.numDescriptors++; } if (layoutDesc.gsBufferUsed[i]) { layoutDesc.numDescriptors++; } if (layoutDesc.psBufferUsed[i]) { layoutDesc.numDescriptors++; } } return layoutDesc; } PipelineLayoutObject * Driver::getPipelineLayout(const HashedDesc<PipelineLayoutDesc>& desc, bool forPush) { const auto& currentDesc = desc; // We do not check if this pipeline layout matches the currently bound one // since that is pretty rare to happen, and this is only invoked whenever // a pipeline is being generated anyways... auto& foundPl = mPipelineLayouts[currentDesc.hash()]; if (foundPl) { return foundPl; } foundPl = new PipelineLayoutObject(); foundPl->desc = currentDesc; // -- Descriptor Layout std::vector<vk::DescriptorSetLayoutBinding> bindings; // We combine the samplers and textures into a single descriptor in the // pipeline layout, so we need to make sure this always matches! static_assert(latte::MaxTextures == latte::MaxSamplers); for (auto i = 0; i < latte::MaxTextures; ++i) { if (currentDesc->vsTextureUsed[i] || currentDesc->vsSamplerUsed[i]) { vk::DescriptorSetLayoutBinding texSampBindingDesc; texSampBindingDesc.binding = (0 * 32) + 0 + i; if (currentDesc->vsSamplerUsed[i] && currentDesc->vsTextureUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler; } else if (currentDesc->vsSamplerUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eSampler; } else if (currentDesc->vsTextureUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eSampledImage; } texSampBindingDesc.descriptorCount = 1; texSampBindingDesc.stageFlags = vk::ShaderStageFlagBits::eVertex; texSampBindingDesc.pImmutableSamplers = nullptr; bindings.push_back(texSampBindingDesc); } if (currentDesc->gsTextureUsed[i] || currentDesc->gsSamplerUsed[i]) { vk::DescriptorSetLayoutBinding texSampBindingDesc; texSampBindingDesc.binding = (1 * 32) + 0 + i; if (currentDesc->gsSamplerUsed[i] && currentDesc->gsTextureUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler; } else if (currentDesc->gsSamplerUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eSampler; } else if (currentDesc->gsTextureUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eSampledImage; } texSampBindingDesc.descriptorCount = 1; texSampBindingDesc.stageFlags = vk::ShaderStageFlagBits::eGeometry; texSampBindingDesc.pImmutableSamplers = nullptr; bindings.push_back(texSampBindingDesc); } if (currentDesc->psTextureUsed[i] || currentDesc->psSamplerUsed[i]) { vk::DescriptorSetLayoutBinding texSampBindingDesc; texSampBindingDesc.binding = (2 * 32) + 0 + i; if (currentDesc->psSamplerUsed[i] && currentDesc->psTextureUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler; } else if (currentDesc->psSamplerUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eSampler; } else if (currentDesc->psTextureUsed[i]) { texSampBindingDesc.descriptorType = vk::DescriptorType::eSampledImage; } texSampBindingDesc.descriptorCount = 1; texSampBindingDesc.stageFlags = vk::ShaderStageFlagBits::eFragment; texSampBindingDesc.pImmutableSamplers = nullptr; bindings.push_back(texSampBindingDesc); } } for (auto i = 0; i < latte::MaxUniformBlocks; ++i) { if (i >= 15) { // Vulkan does not support more than 15 uniform blocks unfortunately, // if we ever encounter a game needing all 15, we will need to do block // splitting or utilize SSBO's. break; } if (currentDesc->vsBufferUsed[i]) { vk::DescriptorSetLayoutBinding cbufferBindingDesc; cbufferBindingDesc.binding = (0 * 32) + 16 + i; cbufferBindingDesc.descriptorType = vk::DescriptorType::eStorageBuffer; cbufferBindingDesc.descriptorCount = 1; cbufferBindingDesc.stageFlags = vk::ShaderStageFlagBits::eVertex; cbufferBindingDesc.pImmutableSamplers = nullptr; bindings.push_back(cbufferBindingDesc); } if (currentDesc->gsBufferUsed[i]) { vk::DescriptorSetLayoutBinding cbufferBindingDesc; cbufferBindingDesc.binding = (1 * 32) + 16 + i; cbufferBindingDesc.descriptorType = vk::DescriptorType::eStorageBuffer; cbufferBindingDesc.descriptorCount = 1; cbufferBindingDesc.stageFlags = vk::ShaderStageFlagBits::eGeometry; cbufferBindingDesc.pImmutableSamplers = nullptr; bindings.push_back(cbufferBindingDesc); } if (currentDesc->psBufferUsed[i]) { vk::DescriptorSetLayoutBinding cbufferBindingDesc; cbufferBindingDesc.binding = (2 * 32) + 16 + i; cbufferBindingDesc.descriptorType = vk::DescriptorType::eStorageBuffer; cbufferBindingDesc.descriptorCount = 1; cbufferBindingDesc.stageFlags = vk::ShaderStageFlagBits::eFragment; cbufferBindingDesc.pImmutableSamplers = nullptr; bindings.push_back(cbufferBindingDesc); } } if (forPush) { decaf_check(bindings.size() == currentDesc->numDescriptors); } vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutDesc; if (forPush) { descriptorSetLayoutDesc.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR; } descriptorSetLayoutDesc.bindingCount = static_cast<uint32_t>(bindings.size()); descriptorSetLayoutDesc.pBindings = bindings.data(); auto descriptorLayout = mDevice.createDescriptorSetLayout(descriptorSetLayoutDesc); // -- Push Descriptors std::array<vk::PushConstantRange, 2> pushConstants; pushConstants[0].stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eGeometry; pushConstants[0].offset = spirv::VertexPushConstantsOffset; pushConstants[0].size = spirv::VertexPushConstantsSize; pushConstants[1].stageFlags = vk::ShaderStageFlagBits::eFragment; pushConstants[1].offset = spirv::FragmentPushConstantsOffset; pushConstants[1].size = spirv::FragmentPushConstantsSize; // -- Pipeline Layout vk::PipelineLayoutCreateInfo pipelineLayoutDesc; pipelineLayoutDesc.setLayoutCount = 1; pipelineLayoutDesc.pSetLayouts = &descriptorLayout; pipelineLayoutDesc.pushConstantRangeCount = static_cast<uint32_t>(pushConstants.size()); pipelineLayoutDesc.pPushConstantRanges = pushConstants.data(); auto pipelineLayout = mDevice.createPipelineLayout(pipelineLayoutDesc); foundPl->descriptorLayout = descriptorLayout; foundPl->pipelineLayout = pipelineLayout; return foundPl; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_pipelines.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_utils.h" #include <common/log.h> static constexpr bool ForceDescriptorSets = false; namespace vulkan { PipelineDesc Driver::getPipelineDesc() { PipelineDesc desc; desc.renderPass = mCurrentDraw->renderPass; desc.vertexShader = mCurrentDraw->vertexShader; desc.geometryShader = mCurrentDraw->geometryShader; desc.pixelShader = mCurrentDraw->pixelShader; desc.rectStubShader = mCurrentDraw->rectStubShader; // -- Vertex Strides for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) { // Skip unused input buffers if (!desc.vertexShader->shader.meta.attribBuffers[i].isUsed) { desc.attribBufferStride[i] = 0; continue; } auto resourceOffset = (latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + i) * 7; auto sq_vtx_constant_word2 = getRegister<latte::SQ_VTX_CONSTANT_WORD2_N>(latte::Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset); desc.attribBufferStride[i] = sq_vtx_constant_word2.STRIDE(); } // -- Vertex Buffer Divisors desc.attribBufferDivisor[0] = 1; desc.attribBufferDivisor[1] = 1; uint32_t instanceStepRate0 = getRegister<uint32_t>(latte::Register::VGT_INSTANCE_STEP_RATE_0); uint32_t instanceStepRate1 = getRegister<uint32_t>(latte::Register::VGT_INSTANCE_STEP_RATE_1); for (auto &attribBuffer : desc.vertexShader->shader.meta.attribBuffers) { if (attribBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_0) { desc.attribBufferDivisor[0] = instanceStepRate0; } else if (attribBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_1) { desc.attribBufferDivisor[1] = instanceStepRate1; } } // -- Primitive Type auto vgt_primitive_type = getRegister<latte::VGT_PRIMITIVE_TYPE>(latte::Register::VGT_PRIMITIVE_TYPE); desc.primitiveType = vgt_primitive_type.PRIM_TYPE(); // -- Primitive Reset Stuff switch (desc.primitiveType) { case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP: case latte::VGT_DI_PRIMITIVE_TYPE::TRIFAN: case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP: case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP_ADJ: case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP_ADJ: case latte::VGT_DI_PRIMITIVE_TYPE::POLYGON: case latte::VGT_DI_PRIMITIVE_TYPE::LINE_STRIP_2D: case latte::VGT_DI_PRIMITIVE_TYPE::TRI_STRIP_2D: case latte::VGT_DI_PRIMITIVE_TYPE::QUADSTRIP: { auto vgt_multi_prim_ib_reset_en = getRegister<latte::VGT_MULTI_PRIM_IB_RESET_EN>(latte::Register::VGT_MULTI_PRIM_IB_RESET_EN); auto vgt_multi_prim_ib_reset_idx = getRegister<latte::VGT_MULTI_PRIM_IB_RESET_INDX>(latte::Register::VGT_MULTI_PRIM_IB_RESET_INDX); desc.primitiveResetEnabled = vgt_multi_prim_ib_reset_en.RESET_EN(); if (desc.primitiveResetEnabled) { desc.primitiveResetIndex = vgt_multi_prim_ib_reset_idx.RESET_INDX(); } break; } case latte::VGT_DI_PRIMITIVE_TYPE::POINTLIST: case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST: case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST_ADJ: case latte::VGT_DI_PRIMITIVE_TYPE::LINELOOP: case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST: case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST_ADJ: case latte::VGT_DI_PRIMITIVE_TYPE::RECTLIST: case latte::VGT_DI_PRIMITIVE_TYPE::QUADLIST: desc.primitiveResetEnabled = false; desc.primitiveResetIndex = 0; break; default: decaf_abort("Unexpected VGT primitive type"); } // -- Constants mode auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG); desc.dx9Consts = sq_config.DX9_CONSTS(); // -- Rasterization stuff auto pa_cl_clip_cntl = getRegister<latte::PA_CL_CLIP_CNTL>(latte::Register::PA_CL_CLIP_CNTL); auto pa_su_line_cntl = getRegister<latte::PA_SU_LINE_CNTL>(latte::Register::PA_SU_LINE_CNTL); auto pa_su_sc_mode_cntl = getRegister<latte::PA_SU_SC_MODE_CNTL>(latte::Register::PA_SU_SC_MODE_CNTL); auto pa_su_poly_offset_front_offset = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_OFFSET>(latte::Register::PA_SU_POLY_OFFSET_FRONT_OFFSET); auto pa_su_poly_offset_front_scale = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_SCALE>(latte::Register::PA_SU_POLY_OFFSET_FRONT_SCALE); auto pa_su_poly_offset_back_offset = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_OFFSET>(latte::Register::PA_SU_POLY_OFFSET_BACK_OFFSET); auto pa_su_poly_offset_back_scale = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_SCALE>(latte::Register::PA_SU_POLY_OFFSET_BACK_SCALE); auto pa_su_poly_offset_clamp = getRegister<latte::PA_SU_POLY_OFFSET_CLAMP>(latte::Register::PA_SU_POLY_OFFSET_CLAMP); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_0()); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_1()); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_2()); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_3()); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_4()); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_5()); decaf_check_warn_once(!pa_cl_clip_cntl.PS_UCP_Y_SCALE_NEG()); decaf_check_warn_once(pa_cl_clip_cntl.PS_UCP_MODE() == latte::PA_PS_UCP_MODE::CULL_DISTANCE); decaf_check_warn_once(!pa_cl_clip_cntl.UCP_CULL_ONLY_ENA()); decaf_check_warn_once(!pa_cl_clip_cntl.BOUNDARY_EDGE_FLAG_ENA()); decaf_check_warn_once(!pa_cl_clip_cntl.DIS_CLIP_ERR_DETECT()); decaf_check_warn_once(!pa_cl_clip_cntl.VTX_KILL_OR()); decaf_check_warn_once(!pa_cl_clip_cntl.DX_LINEAR_ATTR_CLIP_ENA()); decaf_check_warn_once(!pa_cl_clip_cntl.VTE_VPORT_PROVOKE_DISABLE()); // pa_cl_clip_cntl.CLIP_DISABLE() is really an optimization which // indicates that there will be no draws outside the boundary of // the framebuffer. We don't need to handle this. // pa_cl_clip_cntl.DX_CLIP_SPACE_DEF() is handled by the shaders, // so it is uploaded in the push constants buffer in the shader // resources binding desc.rasteriserDisable = pa_cl_clip_cntl.RASTERISER_DISABLE(); // Line widths desc.lineWidth = pa_su_line_cntl.WIDTH(); desc.cullFront = pa_su_sc_mode_cntl.CULL_FRONT(); desc.cullBack = pa_su_sc_mode_cntl.CULL_BACK(); desc.paFace = pa_su_sc_mode_cntl.FACE(); // We do not support split front/back mode, but we make a best-effort // to avoid needing to configure the two differently. In most cases // that there is divergence between front/back configuration, its due // to one of them being culled away anyways. I'm not 100% confident // that this is the correct behaviour though, it would be better if // we supported splitting the polygons as needed. // TODO: Use decaf_check_warn_once here instead... desc.polyPType = latte::PA_PTYPE::TRIANGLES; auto polyMode = pa_su_sc_mode_cntl.POLY_MODE(); if (polyMode == 0) { // POLY_MODE is disabled } else if (polyMode == 1) { // POLY_MODE is dual-triangle if (desc.cullBack) { desc.polyPType = pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE(); } else if (desc.cullFront) { desc.polyPType = pa_su_sc_mode_cntl.POLYMODE_BACK_PTYPE(); } else { decaf_check_warn_once(pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE() == pa_su_sc_mode_cntl.POLYMODE_BACK_PTYPE()); desc.polyPType = pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE(); } } else { gLog->warn("Unexpected POLY_MODE value {}.", polyMode); } desc.polyBiasEnabled = false; if (desc.polyPType == latte::PA_PTYPE::TRIANGLES) { if (desc.cullBack) { desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE(); } else if (desc.cullFront) { desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_BACK_ENABLE(); } else { decaf_check_warn_once(pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE() == pa_su_sc_mode_cntl.POLY_OFFSET_BACK_ENABLE()); desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE(); } } else { desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_PARA_ENABLE(); } if (desc.polyBiasEnabled) { desc.polyBiasClamp = pa_su_poly_offset_clamp.CLAMP(); if (desc.cullBack) { desc.polyBiasOffset = pa_su_poly_offset_front_offset.OFFSET(); desc.polyBiasScale = pa_su_poly_offset_front_scale.SCALE(); } else if (desc.cullFront) { desc.polyBiasOffset = pa_su_poly_offset_back_offset.OFFSET(); desc.polyBiasScale = pa_su_poly_offset_back_scale.SCALE(); } else { decaf_check_warn_once(pa_su_poly_offset_front_offset.value == pa_su_poly_offset_back_offset.value); decaf_check_warn_once(pa_su_poly_offset_front_scale.value == pa_su_poly_offset_back_scale.value); desc.polyBiasOffset = pa_su_poly_offset_front_offset.OFFSET(); desc.polyBiasScale = pa_su_poly_offset_front_scale.SCALE(); } } else { desc.polyBiasClamp = 0.0f; desc.polyBiasOffset = 0.0f; desc.polyBiasScale = 0.0f; } // We only support zclip being on or off, not individually for near/far. decaf_check_warn_once(pa_cl_clip_cntl.ZCLIP_NEAR_DISABLE() == pa_cl_clip_cntl.ZCLIP_FAR_DISABLE()); desc.zclipDisabled = pa_cl_clip_cntl.ZCLIP_NEAR_DISABLE(); // -- Depth/Stencil control stuff auto db_depth_control = getRegister<latte::DB_DEPTH_CONTROL>(latte::Register::DB_DEPTH_CONTROL); auto db_stencilrefmask = getRegister<latte::DB_STENCILREFMASK>(latte::Register::DB_STENCILREFMASK); auto db_stencilrefmask_bf = getRegister<latte::DB_STENCILREFMASK_BF>(latte::Register::DB_STENCILREFMASK_BF); desc.stencilEnable = db_depth_control.STENCIL_ENABLE(); if (desc.stencilEnable) { desc.stencilFront.compareFunc = db_depth_control.STENCILFUNC(); desc.stencilFront.failOp = db_depth_control.STENCILFAIL(); desc.stencilFront.zPassOp = db_depth_control.STENCILZPASS(); desc.stencilFront.zFailOp = db_depth_control.STENCILZFAIL(); desc.stencilFront.ref = db_stencilrefmask.STENCILREF(); desc.stencilFront.mask = db_stencilrefmask.STENCILMASK(); desc.stencilFront.writeMask = db_stencilrefmask.STENCILWRITEMASK(); if (db_depth_control.BACKFACE_ENABLE()) { desc.stencilBack.compareFunc = db_depth_control.STENCILFUNC_BF(); desc.stencilBack.failOp = db_depth_control.STENCILFAIL_BF(); desc.stencilBack.zPassOp = db_depth_control.STENCILZPASS_BF(); desc.stencilBack.zFailOp = db_depth_control.STENCILZFAIL_BF(); desc.stencilBack.ref = db_stencilrefmask_bf.STENCILREF_BF(); desc.stencilBack.mask = db_stencilrefmask_bf.STENCILMASK_BF(); desc.stencilBack.writeMask = db_stencilrefmask_bf.STENCILWRITEMASK_BF(); } else { desc.stencilBack = desc.stencilFront; } } else { desc.stencilFront.compareFunc = latte::REF_FUNC::NEVER; desc.stencilFront.failOp = latte::DB_STENCIL_FUNC::KEEP; desc.stencilFront.zPassOp = latte::DB_STENCIL_FUNC::KEEP; desc.stencilFront.zFailOp = latte::DB_STENCIL_FUNC::KEEP; desc.stencilFront.ref = 0; desc.stencilFront.mask = 0; desc.stencilFront.writeMask = 0; desc.stencilBack = desc.stencilFront; } desc.zEnable = db_depth_control.Z_ENABLE(); desc.zWriteEnable = db_depth_control.Z_WRITE_ENABLE(); if (desc.zEnable) { desc.zFunc = db_depth_control.ZFUNC(); } else { desc.zFunc = latte::REF_FUNC::ALWAYS; } // -- Color control stuff auto makeBlendControl = [&](PipelineDesc::BlendControl& blend, const latte::CB_BLENDN_CONTROL& cb_blend_control) { if (blend.blendingEnabled) { blend.opacityWeight = cb_blend_control.OPACITY_WEIGHT(); blend.colorCombFcn = cb_blend_control.COLOR_COMB_FCN(); blend.colorSrcBlend = cb_blend_control.COLOR_SRCBLEND(); blend.colorDstBlend = cb_blend_control.COLOR_DESTBLEND(); if (cb_blend_control.SEPARATE_ALPHA_BLEND()) { blend.alphaCombFcn = cb_blend_control.ALPHA_COMB_FCN(); blend.alphaSrcBlend = cb_blend_control.ALPHA_SRCBLEND(); blend.alphaDstBlend = cb_blend_control.ALPHA_DESTBLEND(); } else { blend.alphaCombFcn = blend.colorCombFcn; blend.alphaSrcBlend = blend.colorSrcBlend; blend.alphaDstBlend = blend.colorDstBlend; } } else { blend.colorCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC; blend.colorSrcBlend = latte::CB_BLEND_FUNC::ZERO; blend.colorDstBlend = latte::CB_BLEND_FUNC::ZERO; blend.alphaCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC; blend.alphaSrcBlend = latte::CB_BLEND_FUNC::ZERO; blend.alphaDstBlend = latte::CB_BLEND_FUNC::ZERO; blend.opacityWeight = false; } return blend; }; auto cb_color_control = getRegister<latte::CB_COLOR_CONTROL>(latte::Register::CB_COLOR_CONTROL); // TODO: Implement cb_color_control.FOG_ENABLE() // TODO: Implement cb_color_control.MULTIWRITE_ENABLE() // TODO: Implement cb_color_control.DITHER_ENABLE() // TODO: Implement cb_color_control.DEGAMMA_ENABLE() desc.rop3 = cb_color_control.ROP3(); bool shouldUseBlendEnable = false; bool shouldUseBlendControls = false; bool shouldUseBlendMasks = false; if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::NORMAL) { shouldUseBlendEnable = true; shouldUseBlendControls = true; shouldUseBlendMasks = true; } else if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::DISABLE) { // We disable all backend state with this op... } else if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::FAST_CLEAR) { shouldUseBlendMasks = true; } else if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::FORCE_CLEAR) { shouldUseBlendMasks = true; } else { decaf_abort("Encountered unexpected CB_SPECIAL_OP"); } if (shouldUseBlendEnable) { for (auto id = 0u; id < latte::MaxRenderTargets; ++id) { desc.cbBlendControls[id].blendingEnabled = cb_color_control.TARGET_BLEND_ENABLE() & (1 << id); } } else { for (auto id = 0u; id < latte::MaxRenderTargets; ++id) { desc.cbBlendControls[id].blendingEnabled = false; } } if (shouldUseBlendControls) { if (!cb_color_control.PER_MRT_BLEND()) { // For some reason, even though there is a CB_BLEND_CONTROL register, it is // not used here like the docs indicate, and BLEND0 is used instead... auto cb_blend_control = getRegister<latte::CB_BLENDN_CONTROL>(latte::Register::CB_BLEND0_CONTROL); // We need to iterate still, since blending could be off on specific targets for (auto id = 0u; id < latte::MaxRenderTargets; ++id) { makeBlendControl(desc.cbBlendControls[id], cb_blend_control); } } else { for (auto id = 0u; id < latte::MaxRenderTargets; ++id) { auto cb_blend_control = getRegister<latte::CB_BLENDN_CONTROL>(latte::Register::CB_BLEND0_CONTROL + 4 * id); makeBlendControl(desc.cbBlendControls[id], cb_blend_control); } } } else { for (auto id = 0u; id < latte::MaxRenderTargets; ++id) { auto& blend = desc.cbBlendControls[id]; blend.colorCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC; blend.colorSrcBlend = latte::CB_BLEND_FUNC::ZERO; blend.colorDstBlend = latte::CB_BLEND_FUNC::ZERO; blend.alphaCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC; blend.alphaSrcBlend = latte::CB_BLEND_FUNC::ZERO; blend.alphaDstBlend = latte::CB_BLEND_FUNC::ZERO; blend.opacityWeight = false; } } if (shouldUseBlendMasks) { auto cb_target_mask = getRegister<latte::CB_TARGET_MASK>(latte::Register::CB_TARGET_MASK); decaf_check(latte::MaxRenderTargets == 8); desc.cbBlendControls[0].targetMask = cb_target_mask.TARGET0_ENABLE(); desc.cbBlendControls[1].targetMask = cb_target_mask.TARGET1_ENABLE(); desc.cbBlendControls[2].targetMask = cb_target_mask.TARGET2_ENABLE(); desc.cbBlendControls[3].targetMask = cb_target_mask.TARGET3_ENABLE(); desc.cbBlendControls[4].targetMask = cb_target_mask.TARGET4_ENABLE(); desc.cbBlendControls[5].targetMask = cb_target_mask.TARGET5_ENABLE(); desc.cbBlendControls[6].targetMask = cb_target_mask.TARGET6_ENABLE(); desc.cbBlendControls[7].targetMask = cb_target_mask.TARGET7_ENABLE(); } else { for (auto id = 0u; id < latte::MaxRenderTargets; ++id) { desc.cbBlendControls[id].targetMask = 1 | 2 | 4 | 8; } } auto cb_blend_red = getRegister<latte::CB_BLEND_RED>(latte::Register::CB_BLEND_RED); auto cb_blend_green = getRegister<latte::CB_BLEND_GREEN>(latte::Register::CB_BLEND_GREEN); auto cb_blend_blue = getRegister<latte::CB_BLEND_BLUE>(latte::Register::CB_BLEND_BLUE); auto cb_blend_alpha = getRegister<latte::CB_BLEND_ALPHA>(latte::Register::CB_BLEND_ALPHA); desc.cbBlendConstants = { cb_blend_red.BLEND_RED(), cb_blend_green.BLEND_GREEN(), cb_blend_blue.BLEND_BLUE(), cb_blend_alpha.BLEND_ALPHA() }; auto sx_alpha_test_control = getRegister<latte::SX_ALPHA_TEST_CONTROL>(latte::Register::SX_ALPHA_TEST_CONTROL); auto sx_alpha_ref = getRegister<latte::SX_ALPHA_REF>(latte::Register::SX_ALPHA_REF); desc.alphaFunc = sx_alpha_test_control.ALPHA_FUNC(); if (!sx_alpha_test_control.ALPHA_TEST_ENABLE()) { desc.alphaFunc = latte::REF_FUNC::ALWAYS; } if (sx_alpha_test_control.ALPHA_TEST_BYPASS()) { desc.alphaFunc = latte::REF_FUNC::ALWAYS; } desc.alphaRef = sx_alpha_ref.ALPHA_REF(); return desc; } bool Driver::checkCurrentPipeline() { decaf_check(mCurrentDraw->vertexShader); decaf_check(mCurrentDraw->renderPass); HashedDesc<PipelineDesc> currentDesc = getPipelineDesc(); if (mCurrentDraw->pipeline && mCurrentDraw->pipeline->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundPipeline = mPipelines[currentDesc.hash()]; if (foundPipeline) { mCurrentDraw->pipeline = foundPipeline; return true; } foundPipeline = new PipelineObject(); foundPipeline->desc = currentDesc; // ------------------------------------------------------------ // Pipeline Layout // ------------------------------------------------------------ vk::PipelineLayout pipelineLayout; HashedDesc<PipelineLayoutDesc> pipelineLayoutDesc = generatePipelineLayoutDesc(*currentDesc); if (!ForceDescriptorSets && pipelineLayoutDesc->numDescriptors < 32) { auto pipelineLayoutObj = getPipelineLayout(pipelineLayoutDesc, true); foundPipeline->pipelineLayout = pipelineLayoutObj; pipelineLayout = pipelineLayoutObj->pipelineLayout; } else { // Too many descriptors to take advantage of using push descriptors, we have to // fall back to using dynamically generated descriptor sets. foundPipeline->pipelineLayout = nullptr; pipelineLayout = mPipelineLayout; } // ------------------------------------------------------------ // Shader Stages // ------------------------------------------------------------ std::vector<vk::PipelineShaderStageCreateInfo> shaderStages; if (currentDesc->vertexShader) { vk::PipelineShaderStageCreateInfo shaderStageDesc; shaderStageDesc.stage = vk::ShaderStageFlagBits::eVertex; shaderStageDesc.module = currentDesc->vertexShader->module; shaderStageDesc.pName = "main"; shaderStageDesc.pSpecializationInfo = nullptr; shaderStages.push_back(shaderStageDesc); } if (currentDesc->geometryShader) { vk::PipelineShaderStageCreateInfo shaderStageDesc; shaderStageDesc.stage = vk::ShaderStageFlagBits::eGeometry; shaderStageDesc.module = currentDesc->geometryShader->module; shaderStageDesc.pName = "main"; shaderStageDesc.pSpecializationInfo = nullptr; shaderStages.push_back(shaderStageDesc); } if (currentDesc->pixelShader) { vk::PipelineShaderStageCreateInfo shaderStageDesc; shaderStageDesc.stage = vk::ShaderStageFlagBits::eFragment; shaderStageDesc.module = currentDesc->pixelShader->module; shaderStageDesc.pName = "main"; shaderStageDesc.pSpecializationInfo = nullptr; shaderStages.push_back(shaderStageDesc); } if (currentDesc->rectStubShader) { decaf_check(!currentDesc->geometryShader); vk::PipelineShaderStageCreateInfo rectStageDesc; rectStageDesc.stage = vk::ShaderStageFlagBits::eGeometry; rectStageDesc.module = currentDesc->rectStubShader->module; rectStageDesc.pName = "main"; rectStageDesc.pSpecializationInfo = nullptr; shaderStages.push_back(rectStageDesc); } // ------------------------------------------------------------ // Attribute buffers and shader attributes // ------------------------------------------------------------ std::vector<vk::VertexInputBindingDescription> bindingDescs; std::vector<vk::VertexInputBindingDivisorDescriptionEXT> divisorDescs; const auto& inputBuffers = currentDesc->vertexShader->shader.meta.attribBuffers; for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) { const auto &inputBuffer = inputBuffers[i]; if (!inputBuffer.isUsed) { continue; } vk::VertexInputBindingDescription bindingDesc; bindingDesc.binding = i; bindingDesc.stride = currentDesc->attribBufferStride[i]; if (inputBuffer.indexMode == spirv::AttribBuffer::IndexMode::PerVertex) { decaf_check(inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::CONST_1); bindingDesc.inputRate = vk::VertexInputRate::eVertex; } else if (inputBuffer.indexMode == spirv::AttribBuffer::IndexMode::PerInstance) { uint32_t divisorValue; if (inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::CONST_1) { divisorValue = 1; } else if (inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_0) { divisorValue = currentDesc->attribBufferDivisor[0]; } else if (inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_1) { divisorValue = currentDesc->attribBufferDivisor[1]; } else { decaf_abort("Unexpected divisor mode in vertex shader attributes"); } bindingDesc.inputRate = vk::VertexInputRate::eInstance; if (divisorValue != 1) { vk::VertexInputBindingDivisorDescriptionEXT divisorDesc; divisorDesc.binding = bindingDesc.binding; divisorDesc.divisor = divisorValue; divisorDescs.push_back(divisorDesc); } } else { decaf_abort("Unexpected indexing mode for buffer"); } bindingDescs.push_back(bindingDesc); } std::vector<vk::VertexInputAttributeDescription> attribDescs; const auto& inputAttribs = currentDesc->vertexShader->shader.meta.attribElems; for (auto i = 0u; i < inputAttribs.size(); ++i) { const auto &inputAttrib = inputAttribs[i]; vk::VertexInputAttributeDescription attribDesc; attribDesc.location = i; attribDesc.binding = inputAttrib.bufferIndex; if (inputAttrib.elemWidth == 8) { if (inputAttrib.elemCount == 1) { attribDesc.format = vk::Format::eR8Uint; } else if (inputAttrib.elemCount == 2) { attribDesc.format = vk::Format::eR8G8Uint; } else if (inputAttrib.elemCount == 3) { attribDesc.format = vk::Format::eR8G8B8Uint; } else if (inputAttrib.elemCount == 4) { attribDesc.format = vk::Format::eR8G8B8A8Uint; } else { decaf_abort("Unexpected vertex attribute element count"); } } else if (inputAttrib.elemWidth == 16) { if (inputAttrib.elemCount == 1) { attribDesc.format = vk::Format::eR16Uint; } else if (inputAttrib.elemCount == 2) { attribDesc.format = vk::Format::eR16G16Uint; } else if (inputAttrib.elemCount == 3) { attribDesc.format = vk::Format::eR16G16B16Uint; } else if (inputAttrib.elemCount == 4) { attribDesc.format = vk::Format::eR16G16B16A16Uint; } else { decaf_abort("Unexpected vertex attribute element count"); } } else if (inputAttrib.elemWidth == 32) { if (inputAttrib.elemCount == 1) { attribDesc.format = vk::Format::eR32Uint; } else if (inputAttrib.elemCount == 2) { attribDesc.format = vk::Format::eR32G32Uint; } else if (inputAttrib.elemCount == 3) { attribDesc.format = vk::Format::eR32G32B32Uint; } else if (inputAttrib.elemCount == 4) { attribDesc.format = vk::Format::eR32G32B32A32Uint; } else { decaf_abort("Unexpected vertex attribute element count"); } } else { decaf_abort("Unexpected vertex attribute element width"); } attribDesc.offset = inputAttrib.offset; attribDescs.push_back(attribDesc); } vk::PipelineVertexInputStateCreateInfo vertexInputInfo; vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescs.size()); vertexInputInfo.pVertexBindingDescriptions = bindingDescs.data(); vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribDescs.size()); vertexInputInfo.pVertexAttributeDescriptions = attribDescs.data(); if (divisorDescs.size() > 0) { vk::PipelineVertexInputDivisorStateCreateInfoEXT divisorBindingDesc; divisorBindingDesc.vertexBindingDivisorCount = static_cast<uint32_t>(divisorDescs.size()); divisorBindingDesc.pVertexBindingDivisors = divisorDescs.data(); vertexInputInfo.pNext = &divisorBindingDesc; } // ------------------------------------------------------------ // Input assembly // ------------------------------------------------------------ vk::PipelineInputAssemblyStateCreateInfo inputAssembly; switch (currentDesc->primitiveType) { case latte::VGT_DI_PRIMITIVE_TYPE::POINTLIST: inputAssembly.topology = vk::PrimitiveTopology::ePointList; break; case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST: inputAssembly.topology = vk::PrimitiveTopology::eLineList; break; case latte::VGT_DI_PRIMITIVE_TYPE::LINELOOP: // We handle translation of this during draw inputAssembly.topology = vk::PrimitiveTopology::eLineStrip; break; case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP: inputAssembly.topology = vk::PrimitiveTopology::eLineStrip; break; case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST_ADJ: inputAssembly.topology = vk::PrimitiveTopology::eLineListWithAdjacency; break; case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP_ADJ: inputAssembly.topology = vk::PrimitiveTopology::eLineStripWithAdjacency; break; case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST: inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; break; case latte::VGT_DI_PRIMITIVE_TYPE::TRIFAN: inputAssembly.topology = vk::PrimitiveTopology::eTriangleFan; break; case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP: inputAssembly.topology = vk::PrimitiveTopology::eTriangleStrip; break; case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST_ADJ: inputAssembly.topology = vk::PrimitiveTopology::eTriangleListWithAdjacency; break; case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP_ADJ: inputAssembly.topology = vk::PrimitiveTopology::eTriangleStripWithAdjacency; break; case latte::VGT_DI_PRIMITIVE_TYPE::RECTLIST: // We handle translation of this during draw inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; break; case latte::VGT_DI_PRIMITIVE_TYPE::QUADLIST: // We handle translation of this during draw inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; break; case latte::VGT_DI_PRIMITIVE_TYPE::QUADSTRIP: // We handle translation of this during draw inputAssembly.topology = vk::PrimitiveTopology::eTriangleStrip; break; //case latte::VGT_DI_PRIMITIVE_TYPE::NONE: //case latte::VGT_DI_PRIMITIVE_TYPE::TRI_WITH_WFLAGS: //case latte::VGT_DI_PRIMITIVE_TYPE::POLYGON: //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V0: //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V1: //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V2: //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V3: //case latte::VGT_DI_PRIMITIVE_TYPE::FILL_RECT_LIST_2D: //case latte::VGT_DI_PRIMITIVE_TYPE::LINE_STRIP_2D: //case latte::VGT_DI_PRIMITIVE_TYPE::TRI_STRIP_2D: default: decaf_abort("Unexpected VGT primitive type"); } // Technically there is another valid configuration using 32-bit indices and a reset of 0xFFFF, // but checking for this requires more state input to the pipeline... inputAssembly.primitiveRestartEnable = currentDesc->primitiveResetEnabled; decaf_check(!currentDesc->primitiveResetEnabled || (currentDesc->primitiveResetIndex == 0xFFFF || currentDesc->primitiveResetIndex == 0xFFFFFFFF)); // ------------------------------------------------------------ // Viewports and Scissors // ------------------------------------------------------------ vk::PipelineViewportStateCreateInfo viewportState; viewportState.viewportCount = 1; viewportState.pViewports = nullptr; viewportState.scissorCount = 1; viewportState.pScissors = nullptr; // ------------------------------------------------------------ // Rasterizer controls // ------------------------------------------------------------ // TODO: Implement support for doing multi-sampled rendering. vk::PipelineRasterizationStateCreateInfo rasterizer; rasterizer.depthClampEnable = currentDesc->zclipDisabled; rasterizer.rasterizerDiscardEnable = currentDesc->rasteriserDisable; if (currentDesc->polyPType == latte::PA_PTYPE::LINES) { rasterizer.polygonMode = vk::PolygonMode::eLine; } else if (currentDesc->polyPType == latte::PA_PTYPE::POINTS) { rasterizer.polygonMode = vk::PolygonMode::ePoint; } else if (currentDesc->polyPType == latte::PA_PTYPE::TRIANGLES) { rasterizer.polygonMode = vk::PolygonMode::eFill; } else { decaf_abort("Unexpected rasterization ptype"); } if (currentDesc->cullFront && currentDesc->cullBack) { rasterizer.cullMode = vk::CullModeFlagBits::eFrontAndBack; } else if (currentDesc->cullFront) { rasterizer.cullMode = vk::CullModeFlagBits::eFront; } else if (currentDesc->cullBack) { rasterizer.cullMode = vk::CullModeFlagBits::eBack; } else { rasterizer.cullMode = vk::CullModeFlagBits::eNone; } if (currentDesc->paFace == latte::PA_FACE::CW) { rasterizer.frontFace = vk::FrontFace::eClockwise; } else if (currentDesc->paFace == latte::PA_FACE::CCW) { rasterizer.frontFace = vk::FrontFace::eCounterClockwise; } else { decaf_abort("Unexpected pipeline cull mode"); } rasterizer.depthBiasEnable = currentDesc->polyBiasEnabled; if (rasterizer.depthBiasEnable) { rasterizer.depthBiasConstantFactor = currentDesc->polyBiasOffset; rasterizer.depthBiasClamp = currentDesc->polyBiasClamp; rasterizer.depthBiasSlopeFactor = currentDesc->polyBiasScale; } rasterizer.lineWidth = static_cast<float>(currentDesc->lineWidth) / 8.0f; vk::PipelineMultisampleStateCreateInfo multisampling; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; multisampling.sampleShadingEnable = false; multisampling.minSampleShading = 1.0f; multisampling.pSampleMask = nullptr; multisampling.alphaToCoverageEnable = false; multisampling.alphaToOneEnable = false; // ------------------------------------------------------------ // Color Blending // ------------------------------------------------------------ std::array<vk::PipelineColorBlendAttachmentState, latte::MaxRenderTargets> colorBlendAttachments; std::array<bool, latte::MaxRenderTargets> targetIsPremultiplied = { false }; auto needsPremultipliedTargets = false; for (auto i = 0u; i < latte::MaxRenderTargets; ++i) { const auto& blendData = currentDesc->cbBlendControls[i]; vk::PipelineColorBlendAttachmentState colorBlendAttachment; colorBlendAttachment.blendEnable = blendData.blendingEnabled; if (colorBlendAttachment.blendEnable) { colorBlendAttachment.srcColorBlendFactor = getVkBlendFactor(blendData.colorSrcBlend); colorBlendAttachment.dstColorBlendFactor = getVkBlendFactor(blendData.colorDstBlend); colorBlendAttachment.colorBlendOp = getVkBlendOp(blendData.colorCombFcn); colorBlendAttachment.srcAlphaBlendFactor = getVkBlendFactor(blendData.alphaSrcBlend); colorBlendAttachment.dstAlphaBlendFactor = getVkBlendFactor(blendData.alphaDstBlend); colorBlendAttachment.alphaBlendOp = getVkBlendOp(blendData.alphaCombFcn); if (blendData.opacityWeight) { needsPremultipliedTargets = true; targetIsPremultiplied[i] = true; } } if (blendData.targetMask & 1) { colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eR; } if (blendData.targetMask & 2) { colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eG; } if (blendData.targetMask & 4) { colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eB; } if (blendData.targetMask & 8) { colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eA; } colorBlendAttachments[i] = colorBlendAttachment; } vk::PipelineColorBlendStateCreateInfo colorBlendState; if (currentDesc->rop3 == 0xCC) { // COPY colorBlendState.logicOpEnable = false; colorBlendState.logicOp = vk::LogicOp::eCopy; } else { colorBlendState.logicOpEnable = true; switch (currentDesc->rop3) { case 0x00: // BLACKNESS colorBlendState.logicOp = vk::LogicOp::eClear; break; case 0x11: // NOTSRCERASE colorBlendState.logicOp = vk::LogicOp::eNor; break; case 0x22: colorBlendState.logicOp = vk::LogicOp::eAndInverted; break; case 0x33: // NOTSRCCOPY colorBlendState.logicOp = vk::LogicOp::eCopyInverted; break; case 0x44: // SRCERASE colorBlendState.logicOp = vk::LogicOp::eAndReverse; break; case 0x55: // DSTINVERT colorBlendState.logicOp = vk::LogicOp::eInvert; break; case 0x66: // SRCINVERT colorBlendState.logicOp = vk::LogicOp::eXor; break; case 0x77: colorBlendState.logicOp = vk::LogicOp::eNand; break; case 0x88: // SRCAND colorBlendState.logicOp = vk::LogicOp::eAnd; break; case 0x99: colorBlendState.logicOp = vk::LogicOp::eEquivalent; break; case 0xAA: colorBlendState.logicOp = vk::LogicOp::eNoOp; break; case 0xBB: // MERGEPAINT colorBlendState.logicOp = vk::LogicOp::eOrInverted; break; case 0xDD: colorBlendState.logicOp = vk::LogicOp::eOrReverse; break; case 0xEE: // SRCPAINT colorBlendState.logicOp = vk::LogicOp::eOr; break; case 0xFF: // WHITENESS colorBlendState.logicOp = vk::LogicOp::eSet; break; case 0x5A: // PATINVERT case 0xF0: // PATCOPY default: decaf_abort("Unexpected ROP3 operation"); } } colorBlendState.attachmentCount = static_cast<uint32_t>(colorBlendAttachments.size()); colorBlendState.pAttachments = colorBlendAttachments.data(); colorBlendState.blendConstants[0] = currentDesc->cbBlendConstants[0]; colorBlendState.blendConstants[1] = currentDesc->cbBlendConstants[1]; colorBlendState.blendConstants[2] = currentDesc->cbBlendConstants[2]; colorBlendState.blendConstants[3] = currentDesc->cbBlendConstants[3]; vk::PipelineColorBlendAdvancedStateCreateInfoEXT advancedColorBlendState; if (needsPremultipliedTargets) { advancedColorBlendState.dstPremultiplied = true; advancedColorBlendState.srcPremultiplied = false; advancedColorBlendState.blendOverlap = vk::BlendOverlapEXT::eUncorrelated; colorBlendState.pNext = &advancedColorBlendState; } // ------------------------------------------------------------ // Depth/Stencil State // ------------------------------------------------------------ vk::PipelineDepthStencilStateCreateInfo depthStencil; depthStencil.depthBoundsTestEnable = false; depthStencil.depthTestEnable = currentDesc->zEnable; depthStencil.depthWriteEnable = currentDesc->zWriteEnable; depthStencil.depthCompareOp = getVkCompareOp(currentDesc->zFunc); depthStencil.stencilTestEnable = currentDesc->stencilEnable; depthStencil.front.compareOp = getVkCompareOp(currentDesc->stencilFront.compareFunc); depthStencil.front.failOp = getVkStencilOp(currentDesc->stencilFront.failOp); depthStencil.front.passOp = getVkStencilOp(currentDesc->stencilFront.zPassOp); depthStencil.front.depthFailOp = getVkStencilOp(currentDesc->stencilFront.zFailOp); depthStencil.front.reference = static_cast<uint32_t>(currentDesc->stencilFront.ref); depthStencil.front.compareMask = static_cast<uint32_t>(currentDesc->stencilFront.mask); depthStencil.front.writeMask = static_cast<uint32_t>(currentDesc->stencilFront.writeMask); depthStencil.back.compareOp = getVkCompareOp(currentDesc->stencilBack.compareFunc); depthStencil.back.failOp = getVkStencilOp(currentDesc->stencilBack.failOp); depthStencil.back.passOp = getVkStencilOp(currentDesc->stencilBack.zPassOp); depthStencil.back.depthFailOp = getVkStencilOp(currentDesc->stencilBack.zFailOp); depthStencil.back.reference = static_cast<uint32_t>(currentDesc->stencilBack.ref); depthStencil.back.compareMask = static_cast<uint32_t>(currentDesc->stencilBack.mask); depthStencil.back.writeMask = static_cast<uint32_t>(currentDesc->stencilBack.writeMask); // ------------------------------------------------------------ // Dynamic states // ------------------------------------------------------------ auto dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor, }; vk::PipelineDynamicStateCreateInfo dynamicDesc; dynamicDesc.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size()); dynamicDesc.pDynamicStates = dynamicStates.begin(); // ------------------------------------------------------------ // Shader stuff // ------------------------------------------------------------ // TODO: I don't understand why the vulkan drivers use the color from the // shader when rendering this stuff... We have to do this for now. auto shaderLopMode = 0; if (currentDesc->rop3 == 0xFF) { shaderLopMode = 1; } else if (currentDesc->rop3 == 0x00) { shaderLopMode = 2; } // ------------------------------------------------------------ // Pipeline // ------------------------------------------------------------ vk::GraphicsPipelineCreateInfo pipelineInfo; pipelineInfo.pStages = shaderStages.data(); pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size()); pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pTessellationState = nullptr; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlendState; pipelineInfo.pDynamicState = &dynamicDesc; pipelineInfo.layout = pipelineLayout; pipelineInfo.renderPass = mCurrentDraw->renderPass->renderPass; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = vk::Pipeline(); pipelineInfo.basePipelineIndex = -1; auto pipeline = mDevice.createGraphicsPipeline(mPipelineCache, pipelineInfo); foundPipeline->pipeline = pipeline.value; foundPipeline->needsPremultipliedTargets = needsPremultipliedTargets; foundPipeline->targetIsPremultiplied = targetIsPremultiplied; foundPipeline->shaderLopMode = shaderLopMode; foundPipeline->shaderAlphaFunc = currentDesc->alphaFunc; foundPipeline->shaderAlphaRef = currentDesc->alphaRef; mCurrentDraw->pipeline = foundPipeline; return true; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_pm4.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "gpu_clock.h" #include "gpu_event.h" #include "gpu_ih.h" #include "gpu_memory.h" #include "latte/latte_endian.h" #include "latte/latte_enum_as_string.h" #include <common/decaf_assert.h> #include <common/log.h> namespace vulkan { void Driver::decafSetBuffer(const latte::pm4::DecafSetBuffer &data) { SwapChainObject **swapChain; if (data.scanTarget == latte::pm4::ScanTarget::TV) { swapChain = &mTvSwapChain; } else if (data.scanTarget == latte::pm4::ScanTarget::DRC) { swapChain = &mDrcSwapChain; } else { decaf_abort("Unexpected decafSetBuffer target"); } // Release the existing swap chain if we had it... if (*swapChain) { releaseSwapChain(*swapChain); *swapChain = nullptr; } // Allocate a new swap chain auto swapChainDesc = SwapChainDesc { data.buffer, data.width, data.height }; auto newSwapChain = allocateSwapChain(swapChainDesc); // Give the swapchain a name so its easy to see. if (data.scanTarget == latte::pm4::ScanTarget::TV) { setVkObjectName(newSwapChain->image, fmt::format("swapchain_tv").c_str()); } else if (data.scanTarget == latte::pm4::ScanTarget::DRC) { setVkObjectName(newSwapChain->image, fmt::format("swapchain_drc").c_str()); } else { decaf_abort("Unexpected decafSetBuffer target"); } // Assign the new swap chain *swapChain = newSwapChain; // Only make this swapchain presentable after this frame has completed // (we need to run the frame at least once for setup to complete before use). addRetireTask([=](){ newSwapChain->presentable = true; }); } void Driver::decafCopyColorToScan(const latte::pm4::DecafCopyColorToScan &data) { flushPendingDraws(); ColorBufferDesc colorBuffer; colorBuffer.base256b = data.cb_color_base.BASE_256B(); colorBuffer.pitchTileMax = data.cb_color_size.PITCH_TILE_MAX(); colorBuffer.sliceTileMax = data.cb_color_size.SLICE_TILE_MAX(); colorBuffer.format = data.cb_color_info.FORMAT(); colorBuffer.numberType = data.cb_color_info.NUMBER_TYPE(); colorBuffer.arrayMode = data.cb_color_info.ARRAY_MODE(); colorBuffer.sliceStart = 0; colorBuffer.sliceEnd = 1; auto surfaceView = getColorBuffer(colorBuffer); auto surface = surfaceView->surface; SwapChainObject *target = nullptr; if (data.scanTarget == latte::pm4::ScanTarget::TV) { target = mTvSwapChain; } else if (data.scanTarget == latte::pm4::ScanTarget::DRC) { target = mDrcSwapChain; } else { decaf_abort("decafCopyColorToScan called for unknown scanTarget"); } transitionSurface(surface, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, { 0, 1 }); // TODO: We actually need to call AVMSetTVScale inside of the SetBuffer functions // and then pass that data all the way down to here so we can scale correctly. auto copyWidth = target->desc.width; auto copyHeight = target->desc.height; if (surface->desc.width < target->desc.width) { copyWidth = surface->desc.width; } if (surface->desc.height < target->desc.height) { copyHeight = surface->desc.height; } vk::ImageBlit blitRegion( vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), { vk::Offset3D(0, 0, 0), vk::Offset3D(copyWidth, copyHeight, 1) }, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), { vk::Offset3D(0, 0, 0), vk::Offset3D(target->desc.width, target->desc.height, 1) }); mActiveCommandBuffer.blitImage( surface->image, vk::ImageLayout::eTransferSrcOptimal, target->image, vk::ImageLayout::eTransferDstOptimal, { blitRegion }, vk::Filter::eNearest); } void Driver::decafSwapBuffers(const latte::pm4::DecafSwapBuffers &data) { static const auto weight = 0.9; addRetireTask([=](){ // Send out the flip event gpu::onFlip(); // Update our frametime and last swap times auto now = std::chrono::system_clock::now(); if (mLastSwap.time_since_epoch().count()) { mAverageFrameTime = weight * mAverageFrameTime + (1.0 - weight) * (now - mLastSwap); } mLastSwap = now; // Update our debugging info every flip updateDebuggerInfo(); // Render the display! renderDisplay(); }); } void Driver::decafClearColor(const latte::pm4::DecafClearColor &data) { flushPendingDraws(); // Find our colorbuffer to clear ColorBufferDesc colorBuffer; colorBuffer.base256b = data.cb_color_base.BASE_256B(); colorBuffer.pitchTileMax = data.cb_color_size.PITCH_TILE_MAX(); colorBuffer.sliceTileMax = data.cb_color_size.SLICE_TILE_MAX(); colorBuffer.format = data.cb_color_info.FORMAT(); colorBuffer.numberType = data.cb_color_info.NUMBER_TYPE(); colorBuffer.arrayMode = data.cb_color_info.ARRAY_MODE(); colorBuffer.sliceStart = data.cb_color_view.SLICE_START(); colorBuffer.sliceEnd = data.cb_color_view.SLICE_MAX() + 1; auto surfaceView = getColorBuffer(colorBuffer); transitionSurfaceView(surfaceView, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal); std::array<float, 4> clearColor = { data.red, data.green, data.blue, data.alpha }; mActiveCommandBuffer.clearColorImage(surfaceView->surface->image, vk::ImageLayout::eTransferDstOptimal, clearColor, { surfaceView->subresRange }); } void Driver::decafClearDepthStencil(const latte::pm4::DecafClearDepthStencil &data) { flushPendingDraws(); // Find our depthbuffer to clear DepthStencilBufferDesc depthBuffer; depthBuffer.base256b = data.db_depth_base.BASE_256B(); depthBuffer.pitchTileMax = data.db_depth_size.PITCH_TILE_MAX(); depthBuffer.sliceTileMax = data.db_depth_size.SLICE_TILE_MAX(); depthBuffer.format = data.db_depth_info.FORMAT(); depthBuffer.arrayMode = data.db_depth_info.ARRAY_MODE(); depthBuffer.sliceStart = data.db_depth_view.SLICE_START(); depthBuffer.sliceEnd = data.db_depth_view.SLICE_MAX() + 1; auto surfaceView = getDepthStencilBuffer(depthBuffer); transitionSurfaceView(surfaceView, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal); auto db_depth_clear = getRegister<latte::DB_DEPTH_CLEAR>(latte::Register::DB_DEPTH_CLEAR); auto db_stencil_clear = getRegister<latte::DB_STENCIL_CLEAR>(latte::Register::DB_STENCIL_CLEAR); vk::ClearDepthStencilValue clearDepthStencil; clearDepthStencil.depth = db_depth_clear.DEPTH_CLEAR(); clearDepthStencil.stencil = db_stencil_clear.CLEAR(); mActiveCommandBuffer.clearDepthStencilImage( surfaceView->surface->image, vk::ImageLayout::eTransferDstOptimal, clearDepthStencil, { surfaceView->subresRange }); } void Driver::decafOSScreenFlip(const latte::pm4::DecafOSScreenFlip &data) { decaf_abort("Unsupported pm4 decafOSScreenFlip"); } void Driver::decafCopySurface(const latte::pm4::DecafCopySurface &data) { flushPendingDraws(); if (data.dstImage.getAddress() == 0 || data.srcImage.getAddress() == 0) { return; } //decaf_check(data.dstPitch <= data.srcPitch); //decaf_check(data.dstWidth == data.srcWidth); //decaf_check(data.dstHeight == data.srcHeight); //decaf_check(data.dstDepth == data.srcDepth); //decaf_check(data.dstDim == data.srcDim); // Commented the above because slice-wise accross different things is fine... if (data.dstLevel > 0) { // We do not currently support mip mapping levels. return; } // Fetch the source surface SurfaceDesc sourceDataDesc; sourceDataDesc.baseAddress = static_cast<uint32_t>(data.srcImage); sourceDataDesc.pitch = data.srcPitch; sourceDataDesc.width = data.srcWidth; sourceDataDesc.height = data.srcHeight; sourceDataDesc.depth = data.srcDepth; sourceDataDesc.samples = 1; sourceDataDesc.dim = data.srcDim; sourceDataDesc.format = latte::getSurfaceFormat( data.srcFormat, data.srcNumFormat, data.srcFormatComp, data.srcForceDegamma); sourceDataDesc.tileType = data.srcTileType; sourceDataDesc.tileMode = data.srcTileMode; auto sourceImage = getSurface(sourceDataDesc); // Fetch the destination surface SurfaceDesc destDataDesc; destDataDesc.baseAddress = static_cast<uint32_t>(data.dstImage); destDataDesc.pitch = data.dstPitch; destDataDesc.width = data.dstWidth; destDataDesc.height = data.dstHeight; destDataDesc.depth = data.dstDepth; destDataDesc.samples = 1; destDataDesc.dim = data.dstDim; destDataDesc.format = latte::getSurfaceFormat( data.dstFormat, data.dstNumFormat, data.dstFormatComp, data.dstForceDegamma); destDataDesc.tileType = data.dstTileType; destDataDesc.tileMode = data.dstTileMode; auto destImage = getSurface(destDataDesc); // Transition the surfaces to the appropriate layouts transitionSurface(sourceImage, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, { data.srcSlice, 1 }); transitionSurface(destImage, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal, { data.dstSlice, 1 }); // Calculate the bounds of the copy auto copyWidth = data.srcWidth; auto copyHeight = data.srcHeight; auto copyDepth = data.srcDepth; if (data.srcDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) { copyDepth *= 6; } // Perform the copy, note that CopySurface only supports 1:1 copies vk::ImageBlit blitRegion( vk::ImageSubresourceLayers(sourceImage->subresRange.aspectMask, 0, data.srcSlice, 1), { vk::Offset3D(0, 0, 0), vk::Offset3D(copyWidth, copyHeight, copyDepth) }, vk::ImageSubresourceLayers(sourceImage->subresRange.aspectMask, 0, data.dstSlice, 1), { vk::Offset3D(0, 0, 0), vk::Offset3D(copyWidth, copyHeight, copyDepth) }); mActiveCommandBuffer.blitImage( sourceImage->image, vk::ImageLayout::eTransferSrcOptimal, destImage->image, vk::ImageLayout::eTransferDstOptimal, { blitRegion }, vk::Filter::eNearest); } void Driver::decafExpandColorBuffer(const latte::pm4::DecafExpandColorBuffer &data) { flushPendingDraws(); // We do not actually support MSAA in our Vulkan backend, so we simply // need to translate this to a series of surface copies. for (auto i = 0u; i < data.numSlices; ++i) { decafCopySurface({ data.dstImage, data.dstMipmaps, data.dstLevel, data.dstSlice + i, data.dstPitch, data.dstWidth, data.dstHeight, data.dstDepth, data.dstSamples, data.dstDim, data.dstFormat, data.dstNumFormat, data.dstFormatComp, data.dstForceDegamma, data.dstTileType, data.dstTileMode, data.srcImage, data.srcMipmaps, data.srcLevel, data.srcSlice + i, data.srcPitch, data.srcWidth, data.srcHeight, data.srcDepth, data.srcSamples, data.srcDim, data.srcFormat, data.srcNumFormat, data.srcFormatComp, data.srcForceDegamma, data.srcTileType, data.srcTileMode }); } } void Driver::drawIndexAuto(const latte::pm4::DrawIndexAuto &data) { drawGenericIndexed(data.drawInitiator, data.count, nullptr); } void Driver::drawIndex2(const latte::pm4::DrawIndex2 &data) { drawGenericIndexed(data.drawInitiator, data.count, phys_cast<void*>(data.addr).getRawPointer()); } void Driver::drawIndexImmd(const latte::pm4::DrawIndexImmd &data) { drawGenericIndexed(data.drawInitiator, data.count, data.indices.data()); } void Driver::memWrite(const latte::pm4::MemWrite &data) { auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 }; auto ptr = gpu::internal::translateAddress(addr); auto value = uint64_t { 0 }; // Read value if (data.addrHi.CNTR_SEL() == latte::pm4::MW_WRITE_CLOCK) { value = gpu::clock::now(); } else if (data.addrHi.DATA32()) { value = static_cast<uint64_t>(data.dataLo); } else { value = static_cast<uint64_t>(data.dataLo) | (static_cast<uint64_t>(data.dataHi) << 32); } // Swap value value = latte::applyEndianSwap(value, data.addrLo.ENDIAN_SWAP()); // Write value addRetireTask([=](){ if (data.addrHi.DATA32()) { *reinterpret_cast<uint32_t *>(ptr) = static_cast<uint32_t>(value); } else { *reinterpret_cast<uint64_t *>(ptr) = value; } }); } void Driver::eventWrite(const latte::pm4::EventWrite &data) { auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 }; auto dataPtr = phys_cast<uint64_t*>(addr).getRawPointer(); if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::ZPASS_DONE) { // Check if this is the first query, or a second query (as part // of a single occlusion query grouping). if (mLastOccQueryAddr == 0) { auto dstBuffer = getDataMemCache(addr, 8); transitionMemCache(dstBuffer, ResourceUsage::TransferDst); // Write `0` into the beginning zpass count asynchronously. mActiveCommandBuffer.fillBuffer(dstBuffer->buffer, 0, 8, 0); // Start our occlusion query auto queryPool = allocateOccQueryPool(); mActiveCommandBuffer.beginQuery(queryPool, 0, vk::QueryControlFlagBits::ePrecise); mLastOccQuery = queryPool; mLastOccQueryAddr = dataPtr; } else { // Make sure this is the result value of the same query decaf_check(dataPtr == mLastOccQueryAddr + 1); auto dstBuffer = getDataMemCache(addr, 8); transitionMemCache(dstBuffer, ResourceUsage::TransferDst); mActiveCommandBuffer.endQuery(mLastOccQuery, 0); mActiveCommandBuffer.copyQueryPoolResults(mLastOccQuery, 0, 1, dstBuffer->buffer, 0, 8, vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait); mLastOccQuery = vk::QueryPool(); mLastOccQueryAddr = nullptr; } } else if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::CACHE_FLUSH) { // This should flush all GPU-written data back to the CPU immediately. decaf_check_warn_once(!"Use of VGT_EVENT_TYPE::CACHE_FLUSH"); } else if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_EVENT) { // This should flush all GPU-written data back to the CPU immediately // and then invalidate all GPU caches of textures so they are re-read. decaf_check_warn_once(!"Use of VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_EVENT"); } else if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::VS_PARTIAL_FLUSH) { // This should flush all data out of VS-specific pipelines, such as stream-out. decaf_check_warn_once(!"Use of VGT_EVENT_TYPE::VS_PARTIAL_FLUSH"); } else { gLog->warn("Unexpected eventWrite event type {}", latte::to_string(data.eventInitiator.EVENT_TYPE())); } } void Driver::eventWriteEOP(const latte::pm4::EventWriteEOP &data) { // Write event data to memory if required if (data.addrHi.DATA_SEL() != latte::pm4::EWP_DATA_DISCARD) { auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 }; auto ptr = gpu::internal::translateAddress(addr); decaf_assert(data.addrHi.ADDR_HI() == 0, "Invalid event write address (high word not zero)"); // Read value auto value = uint64_t { 0u }; switch (data.addrHi.DATA_SEL()) { case latte::pm4::EWP_DATA_32: value = data.dataLo; break; case latte::pm4::EWP_DATA_64: value = static_cast<uint64_t>(data.dataLo) | (static_cast<uint64_t>(data.dataHi) << 32); break; case latte::pm4::EWP_DATA_CLOCK: value = gpu::clock::now(); break; } // Swap value value = latte::applyEndianSwap(value, data.addrLo.ENDIAN_SWAP()); addRetireTask([=](){ // Write value switch (data.addrHi.DATA_SEL()) { case latte::pm4::EWP_DATA_32: *reinterpret_cast<uint32_t *>(ptr) = static_cast<uint32_t>(value); break; case latte::pm4::EWP_DATA_64: case latte::pm4::EWP_DATA_CLOCK: *reinterpret_cast<uint64_t *>(ptr) = value; break; } }); } // Generate interrupt if required if (data.addrHi.INT_SEL() != latte::pm4::EWP_INT_NONE) { addRetireTask([=](){ auto interrupt = gpu::ih::Entry { }; interrupt.word0 = latte::CP_INT_SRC_ID::CP_EOP_EVENT; gpu::ih::write(interrupt); }); } } void Driver::pfpSyncMe(const latte::pm4::PfpSyncMe &data) { // Due to the specialized implementation of queries, there is // no need to implement any special behaviours here. } void Driver::setPredication(const latte::pm4::SetPredication &data) { // We do not currently implement GPU-side predication of drawing. Instead // we simply allow all draws to proceed as if they were successful. } void Driver::streamOutBaseUpdate(const latte::pm4::StreamOutBaseUpdate &data) { // This is ignored as we don't need to do anything special when the base is // updated. Instead we detect that the buffer registers have changed and // use that as the indication to switch. } void Driver::streamOutBufferUpdate(const latte::pm4::StreamOutBufferUpdate &data) { auto bufferIdx = data.control.SELECT_BUFFER(); if (data.control.STORE_BUFFER_FILLED_SIZE()) { auto stream = mStreamOutContext[bufferIdx]; decaf_check(data.dstLo); decaf_check(stream); readbackStreamContext(stream, data.dstLo); } StreamContextObject *newStreamOut = nullptr; if (data.control.OFFSET_SOURCE() == STRMOUT_OFFSET_SOURCE::STRMOUT_OFFSET_FROM_MEM) { auto srcPtr = phys_cast<uint32_t*>(data.srcLo); decaf_check(srcPtr); newStreamOut = allocateStreamContext(*srcPtr); } else if (data.control.OFFSET_SOURCE() == STRMOUT_OFFSET_SOURCE::STRMOUT_OFFSET_FROM_PACKET) { auto offset = static_cast<uint32_t>(data.srcLo); newStreamOut = allocateStreamContext(offset); } else if (data.control.OFFSET_SOURCE() == STRMOUT_OFFSET_SOURCE::STRMOUT_OFFSET_NONE) { // Nothing to do here, as they didn't want to load the offset from anywhere... } else { decaf_abort("Unexpected offset source during stream out buffer update"); } if (newStreamOut) { auto oldStreamOut = mStreamOutContext[bufferIdx]; mStreamOutContext[bufferIdx] = newStreamOut; if (oldStreamOut) { // We have to defer the destruction of this buffer until the end of // the current context or we may destroy it while a pending read is // in the contexts callback list. addRetireTask([=](){ releaseStreamContext(oldStreamOut); }); } } } void Driver::surfaceSync(const latte::pm4::SurfaceSync &data) { // TODO: Handle surface syncs when using non-coherent surface optimizations. } void Driver::waitMem(const latte::pm4::WaitMem &data) { auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 }; auto ptr = gpu::internal::translateAddress(addr); while (true) { auto value = *reinterpret_cast<volatile uint32_t *>(ptr); value = static_cast<uint32_t>( latte::applyEndianSwap(value, data.addrLo.ENDIAN_SWAP())); value &= data.mask; bool result; switch (data.memSpaceFunction.FUNCTION()) { case WRM_FUNCTION::FUNCTION_ALWAYS: result = true; break; case WRM_FUNCTION::FUNCTION_LESS_THAN: result = value < data.reference; break; case WRM_FUNCTION::FUNCTION_LESS_THAN_EQUAL: result = value <= data.reference; break; case WRM_FUNCTION::FUNCTION_EQUAL: result = value == data.reference; break; case WRM_FUNCTION::FUNCTION_NOT_EQUAL: result = value != data.reference; break; case WRM_FUNCTION::FUNCTION_GREATER_THAN_EQUAL: result = value >= data.reference; break; case WRM_FUNCTION::FUNCTION_GREATER_THAN: result = value > data.reference; break; default: result = true; } if (result) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void Driver::executeBuffer(const gpu::ringbuffer::Buffer &buffer) { decaf_check(!mActiveSyncWaiter); // Begin our command group (sync waiter) beginCommandGroup(); // Begin preparing our command buffer beginCommandBuffer(); // Execute guest PM4 command buffer runCommandBuffer(buffer); // End preparing our command buffer endCommandBuffer(); // Submit the generated command buffer to the host GPU queue vk::SubmitInfo submitInfo; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mActiveCommandBuffer; mQueue.submit({ submitInfo }, mActiveSyncWaiter->fence); // End our command group endCommandGroup(); // Optimize the memory layout of our segments every 10 frames. if (mActiveBatchIndex % 10 == 0) { mMemTracker.optimize(); } } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_renderpass.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_utils.h" namespace vulkan { RenderPassDesc Driver::getRenderPassDesc() { auto desc = RenderPassDesc {}; // TODO: Maybe move the cb_shader_masking into the framebuffer creation to // reduce the number of renderpasses that we need to create. SM3DW is a good // game to check the shader masking behaviours. auto cb_target_mask = getRegister<latte::CB_TARGET_MASK>(latte::Register::CB_TARGET_MASK); auto cb_color_control = getRegister<latte::CB_COLOR_CONTROL>(latte::Register::CB_COLOR_CONTROL); auto cb_shader_mask = getRegister<latte::CB_SHADER_MASK>(latte::Register::CB_SHADER_MASK); auto colorWritesEnabled = true; if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::DISABLE) { colorWritesEnabled = false; } for (auto i = 0u; i < latte::MaxRenderTargets; ++i) { auto targetMask = (cb_target_mask.value >> (i * 4)) & 0xF; auto shaderMask = (cb_shader_mask.value >> (i * 4)) & 0xF; auto cb_color_base = getRegister<latte::CB_COLORN_BASE>(latte::Register::CB_COLOR0_BASE + i * 4); auto cb_color_info = getRegister<latte::CB_COLORN_INFO>(latte::Register::CB_COLOR0_INFO + i * 4); auto isValidBuffer = true; isValidBuffer &= !!cb_color_base.BASE_256B(); isValidBuffer &= (cb_color_info.FORMAT() != latte::CB_FORMAT::COLOR_INVALID); if (!!targetMask && !!shaderMask && colorWritesEnabled) { if (isValidBuffer) { desc.colorTargets[i] = RenderPassDesc::ColorTarget { true, cb_color_info.FORMAT(), cb_color_info.NUMBER_TYPE(), 1 }; } else { // This export is valid, but the color write is ignored desc.colorTargets[i] = RenderPassDesc::ColorTarget { true, latte::CB_FORMAT::COLOR_INVALID, latte::CB_NUMBER_TYPE::UNORM, 0 }; } } else { desc.colorTargets[i] = RenderPassDesc::ColorTarget { false, latte::CB_FORMAT::COLOR_INVALID, latte::CB_NUMBER_TYPE::UNORM, 0 }; } } { auto db_depth_control = getRegister<latte::DB_DEPTH_CONTROL>(latte::Register::DB_DEPTH_CONTROL); auto zEnable = db_depth_control.Z_ENABLE(); auto stencilEnable = db_depth_control.STENCIL_ENABLE(); auto depthEnabled = zEnable || stencilEnable; auto db_depth_base = getRegister<latte::DB_DEPTH_BASE>(latte::Register::DB_DEPTH_BASE); auto db_depth_info = getRegister<latte::DB_DEPTH_INFO>(latte::Register::DB_DEPTH_INFO); auto isValidBuffer = true; isValidBuffer &= !!db_depth_base.BASE_256B(); isValidBuffer &= (db_depth_info.FORMAT() != latte::DB_FORMAT::DEPTH_INVALID); if (depthEnabled) { if (isValidBuffer) { desc.depthTarget = RenderPassDesc::DepthStencilTarget { true, db_depth_info.FORMAT() }; } else { desc.depthTarget = { true, latte::DB_FORMAT::DEPTH_INVALID }; } } else { desc.depthTarget = { false, latte::DB_FORMAT::DEPTH_INVALID }; } } return desc; } bool Driver::checkCurrentRenderPass() { HashedDesc<RenderPassDesc> currentDesc = getRenderPassDesc(); if (!currentDesc->colorTargets[0].isEnabled && !currentDesc->colorTargets[1].isEnabled && !currentDesc->colorTargets[2].isEnabled && !currentDesc->colorTargets[3].isEnabled && !currentDesc->colorTargets[4].isEnabled && !currentDesc->colorTargets[5].isEnabled && !currentDesc->colorTargets[6].isEnabled && !currentDesc->colorTargets[7].isEnabled && !currentDesc->depthTarget.isEnabled) { decaf_check_warn_once(!"Draw executed with no render targets"); return false; } if (mCurrentDraw->renderPass && mCurrentDraw->renderPass->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundRp = mRenderPasses[currentDesc.hash()]; if (foundRp) { mCurrentDraw->renderPass = foundRp; return true; } foundRp = new RenderPassObject(); foundRp->desc = currentDesc; std::vector<vk::AttachmentDescription> attachmentDescs; std::array<vk::AttachmentReference, latte::MaxRenderTargets> colorAttachmentRefs; vk::AttachmentReference depthAttachmentRef; for (auto i = 0u; i < latte::MaxRenderTargets; ++i) { auto &colorTarget = currentDesc->colorTargets[i]; if (!colorTarget.isEnabled || colorTarget.format == latte::CB_FORMAT::COLOR_INVALID) { colorAttachmentRefs[i].attachment = VK_ATTACHMENT_UNUSED; colorAttachmentRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal; foundRp->colorAttachmentIndexes[i] = -1; continue; } auto surfaceFormat = latte::getColorBufferSurfaceFormat(colorTarget.format, colorTarget.numberType); auto hostFormat = getVkSurfaceFormat(surfaceFormat, latte::SQ_TILE_TYPE::DEFAULT); vk::AttachmentDescription colorAttachmentDesc; colorAttachmentDesc.flags = vk::AttachmentDescriptionFlagBits::eMayAlias; colorAttachmentDesc.format = hostFormat; colorAttachmentDesc.samples = vk::SampleCountFlagBits::e1; colorAttachmentDesc.loadOp = vk::AttachmentLoadOp::eLoad; colorAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore; colorAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; colorAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; colorAttachmentDesc.initialLayout = vk::ImageLayout::eColorAttachmentOptimal; colorAttachmentDesc.finalLayout = vk::ImageLayout::eColorAttachmentOptimal; attachmentDescs.push_back(colorAttachmentDesc); auto attachmentIndex = static_cast<uint32_t>(attachmentDescs.size() - 1); colorAttachmentRefs[i].attachment = attachmentIndex; colorAttachmentRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal; foundRp->colorAttachmentIndexes[i] = attachmentIndex; } do { auto depthTarget = currentDesc->depthTarget; if (!depthTarget.isEnabled || depthTarget.format == latte::DB_FORMAT::DEPTH_INVALID) { depthAttachmentRef.attachment = VK_ATTACHMENT_UNUSED; depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; foundRp->depthAttachmentIndex = -1; continue; } auto surfaceFormat = latte::getDepthBufferSurfaceFormat(depthTarget.format); auto hostFormat = getVkSurfaceFormat(surfaceFormat, latte::SQ_TILE_TYPE::DEPTH); vk::AttachmentDescription depthAttachmentDesc; depthAttachmentDesc.format = hostFormat; depthAttachmentDesc.samples = vk::SampleCountFlagBits::e1; depthAttachmentDesc.loadOp = vk::AttachmentLoadOp::eLoad; depthAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore; depthAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eLoad; depthAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eStore; depthAttachmentDesc.initialLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; depthAttachmentDesc.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; attachmentDescs.push_back(depthAttachmentDesc); auto attachmentIndex = static_cast<uint32_t>(attachmentDescs.size() - 1); depthAttachmentRef.attachment = attachmentIndex; depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; foundRp->depthAttachmentIndex = attachmentIndex; } while (false); vk::SubpassDescription genericSubpass; genericSubpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; genericSubpass.inputAttachmentCount = 0; genericSubpass.pInputAttachments = nullptr; genericSubpass.colorAttachmentCount = static_cast<uint32_t>(colorAttachmentRefs.size()); genericSubpass.pColorAttachments = colorAttachmentRefs.data(); genericSubpass.pResolveAttachments = 0; genericSubpass.pDepthStencilAttachment = &depthAttachmentRef; genericSubpass.preserveAttachmentCount = 0; genericSubpass.pPreserveAttachments = nullptr; vk::RenderPassCreateInfo renderPassDesc; renderPassDesc.attachmentCount = static_cast<uint32_t>(attachmentDescs.size()); renderPassDesc.pAttachments = attachmentDescs.data(); renderPassDesc.subpassCount = 1; renderPassDesc.pSubpasses = &genericSubpass; renderPassDesc.dependencyCount = 0; renderPassDesc.pDependencies = nullptr; auto renderPass = mDevice.createRenderPass(renderPassDesc); foundRp->renderPass = renderPass; mCurrentDraw->renderPass = foundRp; return true; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_samplers.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_utils.h" namespace vulkan { SamplerDesc Driver::getSamplerDesc(ShaderStage shaderStage, uint32_t samplerIdx) { uint32_t samplerBaseIdx; if (shaderStage == ShaderStage::Vertex) { samplerBaseIdx = 18; } else if (shaderStage == ShaderStage::Geometry) { samplerBaseIdx = 36; } else if (shaderStage == ShaderStage::Pixel) { samplerBaseIdx = 0; } else { decaf_abort("Unknown shader stage"); } SamplerDesc desc; auto i = samplerBaseIdx + samplerIdx; desc.texSamplerWord0 = getRegister<latte::SQ_TEX_SAMPLER_WORD0_N>(latte::Register::SQ_TEX_SAMPLER_WORD0_0 + 4 * (i * 3)); desc.texSamplerWord1 = getRegister<latte::SQ_TEX_SAMPLER_WORD1_N>(latte::Register::SQ_TEX_SAMPLER_WORD1_0 + 4 * (i * 3)); desc.texSamplerWord2 = getRegister<latte::SQ_TEX_SAMPLER_WORD2_N>(latte::Register::SQ_TEX_SAMPLER_WORD2_0 + 4 * (i * 3)); return desc; } void Driver::updateDrawSampler(ShaderStage shaderStage, uint32_t samplerIdx) { uint32_t shaderStageIdx = static_cast<uint32_t>(shaderStage); auto &drawSampler = mCurrentDraw->samplers[shaderStageIdx][samplerIdx]; HashedDesc<SamplerDesc> currentDesc = getSamplerDesc(shaderStage, samplerIdx); if (drawSampler && drawSampler->desc == currentDesc) { // We already have the correct sampler set return; } auto &foundSamp = mSamplers[currentDesc.hash()]; if (foundSamp) { drawSampler = foundSamp; return; } vk::SamplerCreateInfo samplerDesc; samplerDesc.magFilter = getVkXyTextureFilter(currentDesc->texSamplerWord0.XY_MAG_FILTER()); samplerDesc.minFilter = getVkXyTextureFilter(currentDesc->texSamplerWord0.XY_MIN_FILTER()); samplerDesc.mipmapMode = getVkZTextureFilter(currentDesc->texSamplerWord0.MIP_FILTER()); samplerDesc.addressModeU = getVkTextureAddressMode(currentDesc->texSamplerWord0.CLAMP_X()); samplerDesc.addressModeV = getVkTextureAddressMode(currentDesc->texSamplerWord0.CLAMP_Y()); samplerDesc.addressModeW = getVkTextureAddressMode(currentDesc->texSamplerWord0.CLAMP_Z()); samplerDesc.mipLodBias = static_cast<float>(currentDesc->texSamplerWord1.LOD_BIAS()); samplerDesc.anisotropyEnable = getVkAnisotropyEnabled(currentDesc->texSamplerWord0.MAX_ANISO_RATIO()); samplerDesc.maxAnisotropy = getVkMaxAnisotropy(currentDesc->texSamplerWord0.MAX_ANISO_RATIO()); samplerDesc.compareEnable = getVkCompareOpEnabled(currentDesc->texSamplerWord0.DEPTH_COMPARE_FUNCTION()); samplerDesc.compareOp = getVkCompareOp(currentDesc->texSamplerWord0.DEPTH_COMPARE_FUNCTION()); samplerDesc.minLod = static_cast<float>(currentDesc->texSamplerWord1.MIN_LOD()); samplerDesc.maxLod = static_cast<float>(currentDesc->texSamplerWord1.MAX_LOD()); samplerDesc.borderColor = getVkBorderColor(currentDesc->texSamplerWord0.BORDER_COLOR_TYPE()); samplerDesc.unnormalizedCoordinates = VK_FALSE; auto sampler = mDevice.createSampler(samplerDesc); foundSamp = new SamplerObject(); foundSamp->desc = currentDesc; foundSamp->sampler = sampler; drawSampler = foundSamp; } bool Driver::checkCurrentSamplers() { if (mCurrentDraw->vertexShader) { for (auto samplerIdx = 0u; samplerIdx < latte::MaxSamplers; ++samplerIdx) { if (mCurrentDraw->vertexShader->shader.meta.samplerUsed[samplerIdx]) { updateDrawSampler(ShaderStage::Vertex, samplerIdx); } else { mCurrentDraw->samplers[0][samplerIdx] = nullptr; } } } else { for (auto samplerIdx = 0; samplerIdx < latte::MaxSamplers; ++samplerIdx) { mCurrentDraw->samplers[0][samplerIdx] = nullptr; } } if (mCurrentDraw->geometryShader) { for (auto samplerIdx = 0u; samplerIdx < latte::MaxSamplers; ++samplerIdx) { if (mCurrentDraw->geometryShader->shader.meta.samplerUsed[samplerIdx]) { updateDrawSampler(ShaderStage::Geometry, samplerIdx); } else { mCurrentDraw->samplers[1][samplerIdx] = nullptr; } } } else { for (auto samplerIdx = 0; samplerIdx < latte::MaxSamplers; ++samplerIdx) { mCurrentDraw->samplers[1][samplerIdx] = nullptr; } } if (mCurrentDraw->pixelShader) { for (auto samplerIdx = 0u; samplerIdx < latte::MaxSamplers; ++samplerIdx) { if (mCurrentDraw->pixelShader->shader.meta.samplerUsed[samplerIdx]) { updateDrawSampler(ShaderStage::Pixel, samplerIdx); } else { mCurrentDraw->samplers[2][samplerIdx] = nullptr; } } } else { for (auto samplerIdx = 0; samplerIdx < latte::MaxSamplers; ++samplerIdx) { mCurrentDraw->samplers[2][samplerIdx] = nullptr; } } return true; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_shaderbuffers.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { void Driver::updateDrawGprBuffer(ShaderStage shaderStage) { auto shaderStageInt = static_cast<uint32_t>(shaderStage); const void *registerVals = nullptr; if (shaderStage == ShaderStage::Vertex) { registerVals = &mRegisters[latte::Register::SQ_ALU_CONSTANT0_256 / 4]; } else if (shaderStage == ShaderStage::Geometry) { // No registers are reserved for GS return; } else if (shaderStage == ShaderStage::Pixel) { registerVals = &mRegisters[latte::Register::SQ_ALU_CONSTANT0_0 / 4]; } else { decaf_abort("Unknown shader stage"); } auto gprsBuffer = getStagingBuffer(256 * 4 * 4, StagingBufferType::CpuToGpu); copyToStagingBuffer(gprsBuffer, 0, registerVals, 256 * 4 * 4); if (shaderStage == ShaderStage::Vertex) { transitionStagingBuffer(gprsBuffer, ResourceUsage::VertexUniforms); } else if (shaderStage == ShaderStage::Geometry) { transitionStagingBuffer(gprsBuffer, ResourceUsage::GeometryUniforms); } else if (shaderStage == ShaderStage::Pixel) { transitionStagingBuffer(gprsBuffer, ResourceUsage::PixelUniforms); } else { decaf_abort("Unexpected shader stage in GPR buffer setup"); } mCurrentDraw->gprBuffers[shaderStageInt] = gprsBuffer; } void Driver::updateDrawUniformBuffer(ShaderStage shaderStage, uint32_t cbufferIdx) { auto shaderStageInt = static_cast<uint32_t>(shaderStage); uint32_t cacheBase; uint32_t sizeBase; if (shaderStage == ShaderStage::Vertex) { cacheBase = latte::Register::SQ_ALU_CONST_CACHE_VS_0; sizeBase = latte::Register::SQ_ALU_CONST_BUFFER_SIZE_VS_0; } else if (shaderStage == ShaderStage::Geometry) { cacheBase = latte::Register::SQ_ALU_CONST_CACHE_GS_0; sizeBase = latte::Register::SQ_ALU_CONST_BUFFER_SIZE_GS_0; } else if (shaderStage == ShaderStage::Pixel) { cacheBase = latte::Register::SQ_ALU_CONST_CACHE_PS_0; sizeBase = latte::Register::SQ_ALU_CONST_BUFFER_SIZE_PS_0; } else { decaf_abort("Unexpected shader stage"); } auto sq_alu_const_cache_vs = getRegister<uint32_t>(cacheBase + 4 * cbufferIdx); auto sq_alu_const_buffer_size_vs = getRegister<uint32_t>(sizeBase + 4 * cbufferIdx); auto bufferPtr = phys_addr(sq_alu_const_cache_vs << 8); auto bufferSize = sq_alu_const_buffer_size_vs << 8; if (!bufferPtr || bufferSize == 0) { mCurrentDraw->uniformBlocks[shaderStageInt][cbufferIdx] = nullptr; return; } auto ¤tUniformBlock = mCurrentDraw->uniformBlocks[shaderStageInt][cbufferIdx]; if (currentUniformBlock && currentUniformBlock->address == bufferPtr && currentUniformBlock->size == bufferSize) { // We already have the correct buffer, no need to look it up again. return; } auto memCache = getDataMemCache(bufferPtr, bufferSize); if (shaderStage == ShaderStage::Vertex) { transitionMemCache(memCache, ResourceUsage::VertexUniforms); } else if (shaderStage == ShaderStage::Geometry) { transitionMemCache(memCache, ResourceUsage::GeometryUniforms); } else if (shaderStage == ShaderStage::Pixel) { transitionMemCache(memCache, ResourceUsage::PixelUniforms); } else { decaf_abort("Unexpected shader stage"); } currentUniformBlock = memCache; } bool Driver::checkCurrentShaderBuffers() { auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG); bool isDx9Consts = sq_config.DX9_CONSTS(); if (!isDx9Consts) { for (auto shaderStage = 0; shaderStage < 3; ++shaderStage) { mCurrentDraw->gprBuffers[shaderStage] = nullptr; } if (mCurrentDraw->vertexShader) { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { if (mCurrentDraw->vertexShader->shader.meta.cbufferUsed[blockIdx]) { updateDrawUniformBuffer(ShaderStage::Vertex, blockIdx); } else { mCurrentDraw->uniformBlocks[0][blockIdx] = nullptr; } } } else { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { mCurrentDraw->uniformBlocks[0][blockIdx] = nullptr; } } if (mCurrentDraw->geometryShader) { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { if (mCurrentDraw->geometryShader->shader.meta.cbufferUsed[blockIdx]) { updateDrawUniformBuffer(ShaderStage::Geometry, blockIdx); } else { mCurrentDraw->uniformBlocks[1][blockIdx] = nullptr; } } } else { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { mCurrentDraw->uniformBlocks[1][blockIdx] = nullptr; } } if (mCurrentDraw->pixelShader) { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { if (mCurrentDraw->pixelShader->shader.meta.cbufferUsed[blockIdx]) { updateDrawUniformBuffer(ShaderStage::Pixel, blockIdx); } else { mCurrentDraw->uniformBlocks[2][blockIdx] = nullptr; } } } else { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { mCurrentDraw->uniformBlocks[2][blockIdx] = nullptr; } } } else { for (auto shaderStage = 0; shaderStage < 3; ++shaderStage) { for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) { mCurrentDraw->uniformBlocks[shaderStage][blockIdx] = nullptr; } } if (mCurrentDraw->vertexShader) { if (mCurrentDraw->vertexShader->shader.meta.cfileUsed) { updateDrawGprBuffer(ShaderStage::Vertex); } else { mCurrentDraw->gprBuffers[0] = nullptr; } } else { mCurrentDraw->gprBuffers[0] = nullptr; } if (mCurrentDraw->geometryShader) { if (mCurrentDraw->geometryShader->shader.meta.cfileUsed) { updateDrawGprBuffer(ShaderStage::Geometry); } else { mCurrentDraw->gprBuffers[1] = nullptr; } } else { mCurrentDraw->gprBuffers[1] = nullptr; } if (mCurrentDraw->pixelShader) { if (mCurrentDraw->pixelShader->shader.meta.cfileUsed) { updateDrawGprBuffer(ShaderStage::Pixel); } else { mCurrentDraw->gprBuffers[2] = nullptr; } } else { mCurrentDraw->gprBuffers[2] = nullptr; } } return true; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_shaders.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "gpu_config.h" #include "latte/latte_disassembler.h" #include <common/log.h> #include <common/platform_dir.h> #include <fstream> #include <vector> namespace vulkan { spirv::TextureInputType spirvTextureTypeFromLatte(latte::SQ_NUM_FORMAT format) { if (format == latte::SQ_NUM_FORMAT::INT) { return spirv::TextureInputType::INT; } return spirv::TextureInputType::FLOAT; } spirv::PixelOutputType spirvPixelTypeFromLatte(latte::CB_NUMBER_TYPE format) { switch (format) { case latte::CB_NUMBER_TYPE::SINT: return spirv::PixelOutputType::SINT; case latte::CB_NUMBER_TYPE::UINT: return spirv::PixelOutputType::UINT; default: return spirv::PixelOutputType::FLOAT; } } spirv::VertexShaderDesc Driver::getVertexShaderDesc() { gsl::span<uint8_t> fsShaderBinary; gsl::span<uint8_t> vsShaderBinary; auto pgm_start_fs = getRegister<latte::SQ_PGM_START_FS>(latte::Register::SQ_PGM_START_FS); auto pgm_offset_fs = getRegister<latte::SQ_PGM_CF_OFFSET_FS>(latte::Register::SQ_PGM_CF_OFFSET_FS); auto pgm_size_fs = getRegister<latte::SQ_PGM_SIZE_FS>(latte::Register::SQ_PGM_SIZE_FS); fsShaderBinary = gsl::make_span( phys_cast<uint8_t*>(phys_addr(pgm_start_fs.PGM_START() << 8)).getRawPointer(), pgm_size_fs.PGM_SIZE() << 3); decaf_check(pgm_offset_fs.PGM_OFFSET() == 0); auto vgt_gs_mode = getRegister<latte::VGT_GS_MODE>(latte::Register::VGT_GS_MODE); if (vgt_gs_mode.MODE() == latte::VGT_GS_ENABLE_MODE::OFF) { // When GS is disabled, vertex shader comes from vertex shader register auto pgm_start_vs = getRegister<latte::SQ_PGM_START_VS>(latte::Register::SQ_PGM_START_VS); auto pgm_offset_vs = getRegister<latte::SQ_PGM_CF_OFFSET_VS>(latte::Register::SQ_PGM_CF_OFFSET_VS); auto pgm_size_vs = getRegister<latte::SQ_PGM_SIZE_VS>(latte::Register::SQ_PGM_SIZE_VS); vsShaderBinary = gsl::make_span( phys_cast<uint8_t*>(phys_addr(pgm_start_vs.PGM_START() << 8)).getRawPointer(), pgm_size_vs.PGM_SIZE() << 3); decaf_check(pgm_offset_vs.PGM_OFFSET() == 0); } else { // When GS is enabled, vertex shader comes from export shader register auto pgm_start_es = getRegister<latte::SQ_PGM_START_ES>(latte::Register::SQ_PGM_START_ES); auto pgm_offset_es = getRegister<latte::SQ_PGM_CF_OFFSET_ES>(latte::Register::SQ_PGM_CF_OFFSET_ES); auto pgm_size_es = getRegister<latte::SQ_PGM_SIZE_ES>(latte::Register::SQ_PGM_SIZE_ES); vsShaderBinary = gsl::make_span( phys_cast<uint8_t*>(phys_addr(pgm_start_es.PGM_START() << 8)).getRawPointer(), pgm_size_es.PGM_SIZE() << 3); decaf_check(pgm_offset_es.PGM_OFFSET() == 0); } auto shaderDesc = spirv::VertexShaderDesc { }; shaderDesc.type = spirv::ShaderType::Vertex; shaderDesc.binary = vsShaderBinary; shaderDesc.fsBinary = fsShaderBinary; auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG); shaderDesc.aluInstPreferVector = sq_config.ALU_INST_PREFER_VECTOR(); for (auto i = 0; i < latte::MaxTextures; ++i) { auto resourceOffset = (latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0 + i) * 7; auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset); shaderDesc.texDims[i] = sq_tex_resource_word0.DIM(); shaderDesc.texFormat[i] = spirvTextureTypeFromLatte(sq_tex_resource_word4.NUM_FORMAT_ALL()); } shaderDesc.regs.sq_pgm_resources_vs = getRegister<latte::SQ_PGM_RESOURCES_VS>(latte::Register::SQ_PGM_RESOURCES_VS); shaderDesc.regs.pa_cl_vs_out_cntl = getRegister<latte::PA_CL_VS_OUT_CNTL>(latte::Register::PA_CL_VS_OUT_CNTL); for (auto i = 0u; i < 32; ++i) { shaderDesc.regs.sq_vtx_semantics[i] = getRegister<latte::SQ_VTX_SEMANTIC_N>(latte::Register::SQ_VTX_SEMANTIC_0 + i * 4); } for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { // Note that these registers are not contiguous! shaderDesc.streamOutStride[i] = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_VTX_STRIDE_0 + i * 16) << 2; } return shaderDesc; } spirv::GeometryShaderDesc Driver::getGeometryShaderDesc() { // Do not generate geometry shaders if they are disabled auto vgt_gs_mode = getRegister<latte::VGT_GS_MODE>(latte::Register::VGT_GS_MODE); if (vgt_gs_mode.MODE() == latte::VGT_GS_ENABLE_MODE::OFF) { return spirv::GeometryShaderDesc(); } decaf_check(mCurrentDraw->vertexShader); // Geometry shader comes from geometry shader register auto pgm_start_gs = getRegister<latte::SQ_PGM_START_VS>(latte::Register::SQ_PGM_START_GS); auto pgm_offset_gs = getRegister<latte::SQ_PGM_CF_OFFSET_GS>(latte::Register::SQ_PGM_CF_OFFSET_GS); auto pgm_size_gs = getRegister<latte::SQ_PGM_SIZE_GS>(latte::Register::SQ_PGM_SIZE_GS); auto gsShaderBinary = gsl::make_span( phys_cast<uint8_t*>(phys_addr(pgm_start_gs.PGM_START() << 8)).getRawPointer(), pgm_size_gs.PGM_SIZE() << 3); decaf_check(pgm_offset_gs.PGM_OFFSET() == 0); // Data cache shader comes from vertex shader register auto pgm_start_vs = getRegister<latte::SQ_PGM_START_VS>(latte::Register::SQ_PGM_START_VS); auto pgm_offset_vs = getRegister<latte::SQ_PGM_CF_OFFSET_VS>(latte::Register::SQ_PGM_CF_OFFSET_VS); auto pgm_size_vs = getRegister<latte::SQ_PGM_SIZE_VS>(latte::Register::SQ_PGM_SIZE_VS); auto dcShaderBinary = gsl::make_span( phys_cast<uint8_t*>(phys_addr(pgm_start_vs.PGM_START() << 8)).getRawPointer(), pgm_size_vs.PGM_SIZE() << 3); decaf_check(pgm_offset_vs.PGM_OFFSET() == 0); // If Geometry shading is enabled, we need to have a geometry shader, and data-cache // shaders must always be set if a geometry shader is used. decaf_check(!gsShaderBinary.empty()); decaf_check(!dcShaderBinary.empty()); // Need to generate the shader here... auto shaderDesc = spirv::GeometryShaderDesc { }; shaderDesc.type = spirv::ShaderType::Geometry; shaderDesc.binary = gsShaderBinary; shaderDesc.dcBinary = dcShaderBinary; auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG); shaderDesc.aluInstPreferVector = sq_config.ALU_INST_PREFER_VECTOR(); for (auto i = 0; i < latte::MaxTextures; ++i) { auto resourceOffset = (latte::SQ_RES_OFFSET::GS_TEX_RESOURCE_0 + i) * 7; auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset); shaderDesc.texDims[i] = sq_tex_resource_word0.DIM(); shaderDesc.texFormat[i] = spirvTextureTypeFromLatte(sq_tex_resource_word4.NUM_FORMAT_ALL()); } shaderDesc.regs.sq_gs_vert_itemsize = getRegister<latte::SQ_GS_VERT_ITEMSIZE>(latte::Register::SQ_GS_VERT_ITEMSIZE); shaderDesc.regs.vgt_gs_out_prim_type = getRegister<latte::VGT_GS_OUT_PRIMITIVE_TYPE>(latte::Register::VGT_GS_OUT_PRIM_TYPE); shaderDesc.regs.vgt_gs_mode = getRegister<latte::VGT_GS_MODE>(latte::Register::VGT_GS_MODE); shaderDesc.regs.sq_gsvs_ring_itemsize = getRegister<uint32_t>(latte::Register::SQ_GSVS_RING_ITEMSIZE); shaderDesc.regs.pa_cl_vs_out_cntl = getRegister<latte::PA_CL_VS_OUT_CNTL>(latte::Register::PA_CL_VS_OUT_CNTL); for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { // Note that these registers are not contiguous! shaderDesc.streamOutStride[i] = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_VTX_STRIDE_0 + i * 16) << 2; } return shaderDesc; } spirv::PixelShaderDesc Driver::getPixelShaderDesc() { // Do not generate pixel shaders if rasterization is disabled auto pa_cl_clip_cntl = getRegister<latte::PA_CL_CLIP_CNTL>(latte::Register::PA_CL_CLIP_CNTL); if (pa_cl_clip_cntl.RASTERISER_DISABLE()) { return spirv::PixelShaderDesc(); } decaf_check(mCurrentDraw->vertexShader); auto pgm_start_ps = getRegister<latte::SQ_PGM_START_PS>(latte::Register::SQ_PGM_START_PS); auto pgm_offset_ps = getRegister<latte::SQ_PGM_CF_OFFSET_PS>(latte::Register::SQ_PGM_CF_OFFSET_PS); auto pgm_size_ps = getRegister<latte::SQ_PGM_SIZE_PS>(latte::Register::SQ_PGM_SIZE_PS); auto psShaderBinary = gsl::make_span( phys_cast<uint8_t*>(phys_addr(pgm_start_ps.PGM_START() << 8)).getRawPointer(), pgm_size_ps.PGM_SIZE() << 3); decaf_check(pgm_offset_ps.PGM_OFFSET() == 0); auto shaderDesc = spirv::PixelShaderDesc { }; shaderDesc.type = spirv::ShaderType::Pixel; shaderDesc.binary = psShaderBinary; auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG); shaderDesc.aluInstPreferVector = sq_config.ALU_INST_PREFER_VECTOR(); for (auto i = 0; i < latte::MaxRenderTargets; ++i) { auto cb_color_info = getRegister<latte::CB_COLORN_INFO>(latte::Register::CB_COLOR0_INFO + i * 4); shaderDesc.pixelOutType[i] = spirvPixelTypeFromLatte(cb_color_info.NUMBER_TYPE()); } for (auto i = 0; i < latte::MaxTextures; ++i) { auto resourceOffset = (latte::SQ_RES_OFFSET::PS_TEX_RESOURCE_0 + i) * 7; auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset); shaderDesc.texDims[i] = sq_tex_resource_word0.DIM(); shaderDesc.texFormat[i] = spirvTextureTypeFromLatte(sq_tex_resource_word4.NUM_FORMAT_ALL()); } shaderDesc.regs.sq_pgm_resources_ps = getRegister<latte::SQ_PGM_RESOURCES_PS>(latte::Register::SQ_PGM_RESOURCES_PS); shaderDesc.regs.sq_pgm_exports_ps = getRegister<latte::SQ_PGM_EXPORTS_PS>(latte::Register::SQ_PGM_EXPORTS_PS); shaderDesc.regs.spi_ps_in_control_0 = getRegister<latte::SPI_PS_IN_CONTROL_0>(latte::Register::SPI_PS_IN_CONTROL_0); shaderDesc.regs.spi_ps_in_control_1 = getRegister<latte::SPI_PS_IN_CONTROL_1>(latte::Register::SPI_PS_IN_CONTROL_1); shaderDesc.regs.spi_vs_out_config = getRegister<latte::SPI_VS_OUT_CONFIG>(latte::Register::SPI_VS_OUT_CONFIG); shaderDesc.regs.cb_shader_control = getRegister<latte::CB_SHADER_CONTROL>(latte::Register::CB_SHADER_CONTROL); shaderDesc.regs.cb_shader_mask = getRegister<latte::CB_SHADER_MASK>(latte::Register::CB_SHADER_MASK); shaderDesc.regs.db_shader_control = getRegister<latte::DB_SHADER_CONTROL>(latte::Register::DB_SHADER_CONTROL); for (auto i = 0; i < 32; ++i) { shaderDesc.regs.spi_ps_input_cntls[i] = getRegister<latte::SPI_PS_INPUT_CNTL_N>(latte::Register::SPI_PS_INPUT_CNTL_0 + i * 4); } for (auto i = 0; i < 10; ++i) { shaderDesc.regs.spi_vs_out_ids[i] = getRegister<latte::SPI_VS_OUT_ID_N>(latte::Register::SPI_VS_OUT_ID_0 + i * 4); } return shaderDesc; } struct ShaderBinaryEntry { ShaderBinaryEntry(std::string name, gsl::span<const uint8_t> binary) : name(name), binary(binary) { } std::string name; gsl::span<const uint8_t> binary; }; using ShaderBinaries = std::vector<ShaderBinaryEntry>; static void dumpRawShaderBinaries(const ShaderBinaries &shaderBinaries, std::string shaderName) { // Write binary shaders to dump file for (auto shaderBinary : shaderBinaries) { auto filePathBinarySuffix = shaderBinary.name.empty() ? "" : fmt::format("_{}", shaderBinary.name); auto filePathBinary = fmt::format("dump/{}{}.bin", shaderName, filePathBinarySuffix); if (!platform::fileExists(filePathBinary)) { platform::createDirectory("dump"); // Write Binary Output auto file = std::ofstream{ filePathBinary, std::ofstream::out | std::ofstream::binary }; file.write(reinterpret_cast<const char *>(shaderBinary.binary.data()), shaderBinary.binary.size()); } } } static void dumpRawShader(const spirv::ShaderDesc *desc, bool onlyDumpBinaries) { auto shaderBinaries = ShaderBinaries { }; auto shaderName = std::string { }; if (desc->type == spirv::ShaderType::Vertex) { auto vsDesc = reinterpret_cast<const spirv::VertexShaderDesc*>(desc); auto vsAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(vsDesc->binary.data())); auto fsAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(vsDesc->fsBinary.data())); shaderName = fmt::format("vs_{:08x}_{:08x}", vsAddr, fsAddr); if ((reinterpret_cast<uintptr_t>(vsDesc->fsBinary.data()) & 0xFFFFFFFF) != 0) { shaderBinaries.emplace_back("fs", vsDesc->fsBinary); } if ((reinterpret_cast<uintptr_t>(vsDesc->binary.data()) & 0xFFFFFFFF) != 0) { shaderBinaries.emplace_back("", vsDesc->binary); } } else if (desc->type == spirv::ShaderType::Geometry) { auto gsDesc = reinterpret_cast<const spirv::GeometryShaderDesc*>(desc); auto gsAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(gsDesc->binary.data())); auto dcAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(gsDesc->dcBinary.data())); shaderName = fmt::format("gs_{:08x}_{:08x}", gsAddr, dcAddr); if ((reinterpret_cast<uintptr_t>(gsDesc->dcBinary.data()) & 0xFFFFFFFF) != 0) { shaderBinaries.emplace_back("dc", gsDesc->dcBinary); } if ((reinterpret_cast<uintptr_t>(gsDesc->binary.data()) & 0xFFFFFFFF) != 0) { shaderBinaries.emplace_back("", gsDesc->binary); } } else if (desc->type == spirv::ShaderType::Pixel) { auto psDesc = reinterpret_cast<const spirv::PixelShaderDesc*>(desc); auto psAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(psDesc->binary.data())); shaderName = fmt::format("ps_{:08x}", psAddr); if ((reinterpret_cast<uintptr_t>(psDesc->binary.data()) & 0xFFFFFFFF) != 0) { shaderBinaries.emplace_back("", psDesc->binary); } } else { decaf_abort("Unexpected shader type"); } // Dump binaries dumpRawShaderBinaries(shaderBinaries, shaderName); if (onlyDumpBinaries) { return; } // Dump disassembly auto outputStr = std::string { }; if (desc->type == spirv::ShaderType::Vertex) { auto vsDesc = reinterpret_cast<const spirv::VertexShaderDesc*>(desc); auto fsDisasm = std::string { }; auto vsDisasm = std::string { }; if (!vsDesc->fsBinary.empty()) { fsDisasm = latte::disassemble(vsDesc->fsBinary, true); } if (!vsDesc->binary.empty()) { vsDisasm = latte::disassemble(vsDesc->binary, false); } outputStr += "Fetch Shader:\n"; outputStr += fsDisasm + "\n\n"; outputStr += "Vertex Shader:\n"; outputStr += vsDisasm + "\n\n"; } else if (desc->type == spirv::ShaderType::Geometry) { auto gsDesc = reinterpret_cast<const spirv::GeometryShaderDesc*>(desc); auto gsDisasm = std::string { }; auto dcDisasm = std::string { }; if (!gsDesc->dcBinary.empty()) { dcDisasm = latte::disassemble(gsDesc->dcBinary, false); } if (!gsDesc->binary.empty()) { gsDisasm = latte::disassemble(gsDesc->binary, false); } outputStr += "Geometry Shader:\n"; outputStr += gsDisasm + "\n\n"; outputStr += "DMA Copy Shader:\n"; outputStr += dcDisasm + "\n\n"; } else if (desc->type == spirv::ShaderType::Pixel) { auto psDesc = reinterpret_cast<const spirv::PixelShaderDesc*>(desc); auto psDisasm = std::string { }; if (!psDesc->binary.empty()) { psDisasm = latte::disassemble(psDesc->binary, false); } outputStr += "Pixel Shader:\n"; outputStr += psDisasm + "\n\n"; } else { decaf_abort("Unexpected shader type"); } // Write shader disassembly to dump file auto filePath = fmt::format("dump/{}.txt", shaderName); if (!platform::fileExists(filePath)) { platform::createDirectory("dump"); // Write Text Output auto file = std::ofstream { filePath, std::ofstream::out }; file << outputStr << std::endl; } } static void dumpTranslatedShader(const spirv::ShaderDesc *desc, const spirv::Shader *shader) { auto shaderText = spirv::shaderToString(shader); auto shaderName = std::string { }; auto outputStr = std::string { }; if (desc->type == spirv::ShaderType::Vertex) { auto vsDesc = reinterpret_cast<const spirv::VertexShaderDesc*>(desc); auto vsAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(vsDesc->binary.data())); auto fsAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(vsDesc->fsBinary.data())); shaderName = fmt::format("vs_{:08x}_{:08x}", vsAddr, fsAddr); } else if (desc->type == spirv::ShaderType::Geometry) { auto gsDesc = reinterpret_cast<const spirv::GeometryShaderDesc*>(desc); auto gsAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(gsDesc->binary.data())); auto dcAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(gsDesc->dcBinary.data())); shaderName = fmt::format("gs_{:08x}_{:08x}", gsAddr, dcAddr); } else if (desc->type == spirv::ShaderType::Pixel) { auto psDesc = reinterpret_cast<const spirv::PixelShaderDesc*>(desc); auto psAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(psDesc->binary.data())); shaderName = fmt::format("ps_{:08x}", psAddr); } else { decaf_abort("Unexpected shader type"); } if (desc->type == spirv::ShaderType::Vertex) { outputStr += "Compiled Vertex Shader:\n"; outputStr += shaderText + "\n\n"; } else if (desc->type == spirv::ShaderType::Geometry) { outputStr += "Compiled Geometry Shader:\n"; outputStr += shaderText + "\n\n"; } else if (desc->type == spirv::ShaderType::Pixel) { outputStr += "Compiled Pixel Shader:\n"; outputStr += shaderText + "\n\n"; } else { decaf_abort("Unexpected shader type"); } // Write to dump file auto filePath = fmt::format("dump/{}.spv.txt", shaderName); if (!platform::fileExists(filePath)) { platform::createDirectory("dump"); // Write Text Output auto file = std::ofstream { filePath, std::ofstream::out }; file << outputStr << std::endl; // SPIRV Binary Output auto binFilePath = fmt::format("dump/{}.spv", shaderName); auto binFile = std::ofstream { binFilePath, std::ofstream::out | std::ofstream::binary }; binFile.write(reinterpret_cast<const char*>(shader->binary.data()), shader->binary.size() * sizeof(shader->binary[0])); } } bool Driver::checkCurrentVertexShader() { // We defer the hashing until after we check if this shader is even // actually enabled or not... Performance ! auto currentDescPrehash = getVertexShaderDesc(); // Check if the shader stage is disabled if (currentDescPrehash.type == spirv::ShaderType::Unknown) { mCurrentDraw->vertexShader = nullptr; return true; } auto currentDesc = HashedDesc<spirv::VertexShaderDesc> { currentDescPrehash }; if (mCurrentDraw->vertexShader && mCurrentDraw->vertexShader->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundShader = mVertexShaders[currentDesc.hash()]; if (foundShader) { mCurrentDraw->vertexShader = foundShader; return true; } foundShader = new VertexShaderObject(); foundShader->desc = currentDesc; if (mDumpShaders) { dumpRawShader(&*currentDesc, mDumpShaderBinariesOnly); } if (!spirv::translate(*currentDesc, &foundShader->shader)) { decaf_abort("Failed to translate vertex shader"); } if (mDumpShaders) { dumpTranslatedShader(&*currentDesc, &foundShader->shader); } auto module = mDevice.createShaderModule( vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4, foundShader->shader.binary.data())); foundShader->module = module; auto shaderAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(currentDesc->binary.data())); setVkObjectName(module, fmt::format("vs_{:08x}", shaderAddr).c_str()); mCurrentDraw->vertexShader = foundShader; return true; } bool Driver::checkCurrentGeometryShader() { // We defer the hashing until after we check if this shader is even // actually enabled or not... Performance ! auto currentDescPrehash = getGeometryShaderDesc(); // Check if the shader stage is disabled if (currentDescPrehash.type == spirv::ShaderType::Unknown) { mCurrentDraw->geometryShader = nullptr; return true; } auto currentDesc = HashedDesc<spirv::GeometryShaderDesc> { currentDescPrehash }; if (mCurrentDraw->geometryShader && mCurrentDraw->geometryShader->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundShader = mGeometryShaders[currentDesc.hash()]; if (foundShader) { mCurrentDraw->geometryShader = foundShader; return true; } foundShader = new GeometryShaderObject(); foundShader->desc = currentDesc; if (mDumpShaders) { dumpRawShader(&*currentDesc, mDumpShaderBinariesOnly); } if (!spirv::translate(*currentDesc, &foundShader->shader)) { decaf_abort("Failed to translate geometry shader"); } if (mDumpShaders) { dumpTranslatedShader(&*currentDesc, &foundShader->shader); } auto module = mDevice.createShaderModule( vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4, foundShader->shader.binary.data())); foundShader->module = module; auto shaderAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(currentDesc->binary.data())); setVkObjectName(module, fmt::format("gs_{:08x}", shaderAddr).c_str()); mCurrentDraw->geometryShader = foundShader; return true; } bool Driver::checkCurrentPixelShader() { // We defer the hashing until after we check if this shader is even // actually enabled or not... Performance ! auto currentDescPrehash = getPixelShaderDesc(); // Check if the shader stage is disabled if (currentDescPrehash.type == spirv::ShaderType::Unknown) { mCurrentDraw->pixelShader = nullptr; return true; } auto currentDesc = HashedDesc<spirv::PixelShaderDesc> { currentDescPrehash }; if (mCurrentDraw->pixelShader && mCurrentDraw->pixelShader->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundShader = mPixelShaders[currentDesc.hash()]; if (foundShader) { mCurrentDraw->pixelShader = foundShader; return true; } foundShader = new PixelShaderObject(); foundShader->desc = currentDesc; if (mDumpShaders) { dumpRawShader(&*currentDesc, mDumpShaderBinariesOnly); } if (!spirv::translate(*currentDesc, &foundShader->shader)) { decaf_abort("Failed to translate pixel shader"); } if (mDumpShaders) { dumpTranslatedShader(&*currentDesc, &foundShader->shader); } auto module = mDevice.createShaderModule( vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4, foundShader->shader.binary.data())); foundShader->module = module; auto shaderAddr = static_cast<uint32_t>( reinterpret_cast<uintptr_t>(currentDesc->binary.data())); setVkObjectName(module, fmt::format("ps_{:08x}", shaderAddr).c_str()); mCurrentDraw->pixelShader = foundShader; return true; } bool Driver::checkCurrentRectStubShader() { if (mCurrentDraw->primitiveType != latte::VGT_DI_PRIMITIVE_TYPE::RECTLIST) { mCurrentDraw->rectStubShader = nullptr; return true; } decaf_check(mCurrentDraw->vertexShader); auto currentDesc = HashedDesc<spirv::RectStubShaderDesc> { spirv::generateRectSubShaderDesc(&mCurrentDraw->vertexShader->shader) }; if (mCurrentDraw->rectStubShader && mCurrentDraw->rectStubShader->desc == currentDesc) { // Already active, nothing to do. return true; } auto& foundShader = mRectStubShaders[currentDesc.hash()]; if (foundShader) { mCurrentDraw->rectStubShader = foundShader; return true; } foundShader = new RectStubShaderObject(); foundShader->desc = currentDesc; if (!spirv::generateRectStub(*currentDesc, &foundShader->shader)) { decaf_abort("Failed to generate rect stub shader"); } auto module = mDevice.createShaderModule( vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4, foundShader->shader.binary.data())); foundShader->module = module; setVkObjectName(module, fmt::format("rstub_{}", currentDesc->numVsExports).c_str()); mCurrentDraw->rectStubShader = foundShader; return true; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_staging.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { /* Staging buffers are used for performing uploads/downloads from the host GPU. These buffers will only last as long as a single host command buffer does, and thus all uploading must be done in the context where the buffer is created, or within a retire task of that particular command buffer. */ StagingBuffer * Driver::_allocStagingBuffer(uint32_t size, StagingBufferType type) { // Lets at least align our staging buffers to 1kb... size = align_up(size, 1024); vk::BufferUsageFlags bufferUsage; VmaMemoryUsage allocUsage; if (type == StagingBufferType::CpuToGpu) { allocUsage = VMA_MEMORY_USAGE_CPU_TO_GPU; bufferUsage = vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eStorageBuffer; } else if (type == StagingBufferType::GpuToCpu) { allocUsage = VMA_MEMORY_USAGE_GPU_TO_CPU; bufferUsage = vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst; } else if (type == StagingBufferType::GpuToGpu) { allocUsage = VMA_MEMORY_USAGE_GPU_ONLY; bufferUsage = vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer; } else { decaf_abort("Unexpected staging buffer type"); } vk::BufferCreateInfo bufferDesc; bufferDesc.size = size; bufferDesc.usage = bufferUsage; bufferDesc.sharingMode = vk::SharingMode::eExclusive; bufferDesc.queueFamilyIndexCount = 0; bufferDesc.pQueueFamilyIndices = nullptr; VmaAllocationCreateInfo allocDesc = {}; allocDesc.usage = allocUsage; VkBuffer buffer; VmaAllocation allocation; CHECK_VK_RESULT( vmaCreateBuffer(mAllocator, reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc), &allocDesc, &buffer, &allocation, nullptr)); static uint64_t stagingBufferIdx = 0; setVkObjectName(buffer, fmt::format("stg_{}_{}_{}", stagingBufferIdx++, static_cast<uint32_t>(type), size).c_str()); auto sbuffer = new StagingBuffer(); sbuffer->type = type; sbuffer->maximumSize = size; sbuffer->size = 0; sbuffer->activeUsage = ResourceUsage::Undefined; sbuffer->buffer = buffer; sbuffer->memory = allocation; sbuffer->mappedPtr = nullptr; if (type == StagingBufferType::GpuToCpu || type == StagingBufferType::CpuToGpu) { CHECK_VK_RESULT(vmaMapMemory(mAllocator, sbuffer->memory, &sbuffer->mappedPtr)); } return sbuffer; } StagingBuffer * Driver::getStagingBuffer(uint32_t size, StagingBufferType type) { StagingBuffer *sbuffer = nullptr; auto alignedSizeBit = 0u; for (auto i = size >> 12; i > 0; i >>= 1, alignedSizeBit++); auto alignedSize = 1 << (alignedSizeBit + 12); if (!sbuffer) { auto typeIndex = static_cast<uint32_t>(type); auto &stagingBuffers = mStagingBuffers[typeIndex][alignedSizeBit]; if (!stagingBuffers.empty()) { sbuffer = stagingBuffers.back(); stagingBuffers.pop_back(); // This is just to double-check that our algorithm is working // as it is intended to be working... decaf_check(sbuffer->maximumSize >= size); } } if (!sbuffer) { sbuffer = _allocStagingBuffer(alignedSize, type); sbuffer->poolIndex = alignedSizeBit; } sbuffer->size = size; mActiveSyncWaiter->stagingBuffers.push_back(sbuffer); return sbuffer; } void Driver::retireStagingBuffer(StagingBuffer *sbuffer) { auto typeIndex = static_cast<uint32_t>(sbuffer->type); auto poolIndex = sbuffer->poolIndex; mStagingBuffers[typeIndex][poolIndex].push_back(sbuffer); } void Driver::transitionStagingBuffer(StagingBuffer *sbuffer, ResourceUsage usage) { // If we are already set to the correct usage, no need to do any additional // work. It is implied that a transition would need to happen for a change // to have occurred to the staging buffer. if (sbuffer->activeUsage == usage) { return; } auto srcMeta = getResourceUsageMeta(sbuffer->activeUsage); auto dstMeta = getResourceUsageMeta(usage); vk::BufferMemoryBarrier bufferBarrier; bufferBarrier.srcAccessMask = srcMeta.accessFlags; bufferBarrier.dstAccessMask = dstMeta.accessFlags; bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.buffer = sbuffer->buffer; bufferBarrier.offset = 0; bufferBarrier.size = VK_WHOLE_SIZE; mActiveCommandBuffer.pipelineBarrier( srcMeta.stageFlags, dstMeta.stageFlags, vk::DependencyFlags(), {}, { bufferBarrier }, {}); sbuffer->activeUsage = usage; } void Driver::copyToStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, const void *data, uint32_t size) { decaf_check(sbuffer->type == StagingBufferType::CpuToGpu); // Transition the buffer to allow safe writing transitionStagingBuffer(sbuffer, ResourceUsage::HostWrite); // Copy the data into the staging buffer. memcpy(static_cast<uint8_t*>(sbuffer->mappedPtr) + offset, data, size); // Flush the allocation to make the CPU write visible to the GPU. vmaFlushAllocation(mAllocator, sbuffer->memory, 0, VK_WHOLE_SIZE); } void Driver::copyFromStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, void *data, uint32_t size) { decaf_check(sbuffer->type == StagingBufferType::GpuToCpu); // In the case of copying FROM the staging buffer, we actually only check that the // correct usage is configured. This is because the read occurs later after the // transition, when we don't have a command buffer or a sync path to transition. decaf_check(sbuffer->activeUsage == ResourceUsage::HostRead); // Invalidate the allocation to make the GPU writes visible to the CPU vmaInvalidateAllocation(mAllocator, sbuffer->memory, 0, VK_WHOLE_SIZE); // Copy the data from the staging buffer memcpy(data, static_cast<uint8_t*>(sbuffer->mappedPtr) + offset, size); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_streamout.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { // TODO: We should consolidate the management of all 3 types of // buffers that we create (memory cache, staging and stream out // counters). This will allow us to share the logic for the // management of those buffers. static inline void _barrierStreamContextBuffer(vk::CommandBuffer cmdBuffer, vk::Buffer buffer, vk::PipelineStageFlags srcStage, vk::AccessFlags srcMask, vk::PipelineStageFlags dstStage, vk::AccessFlags dstMask) { vk::BufferMemoryBarrier bufferBarrier; bufferBarrier.srcAccessMask = srcMask; bufferBarrier.dstAccessMask = dstMask; bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.buffer = buffer; bufferBarrier.offset = 0; bufferBarrier.size = VK_WHOLE_SIZE; cmdBuffer.pipelineBarrier( srcStage, dstStage, vk::DependencyFlags(), { }, { bufferBarrier }, { }); } StreamContextObject * Driver::allocateStreamContext(uint32_t initialOffset) { StreamContextObject *context = nullptr; if (!mStreamOutContextPool.empty()) { context = mStreamOutContextPool.back(); mStreamOutContextPool.pop_back(); } if (!context) { vk::BufferCreateInfo bufferDesc; bufferDesc.size = 4; bufferDesc.usage = vk::BufferUsageFlagBits::eTransformFeedbackCounterBufferEXT | vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst; bufferDesc.sharingMode = vk::SharingMode::eExclusive; bufferDesc.queueFamilyIndexCount = 0; bufferDesc.pQueueFamilyIndices = nullptr; VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; VkBuffer buffer; VmaAllocation allocation = nullptr; vmaCreateBuffer(mAllocator, reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc), &allocInfo, &buffer, &allocation, nullptr); static uint64_t streamOutCounterIdx = 0; setVkObjectName(buffer, fmt::format("soctr_{}", streamOutCounterIdx++).c_str()); context = new StreamContextObject(); context->allocation = allocation; context->buffer = buffer; } // Transition this buffer to being filled. _barrierStreamContextBuffer(mActiveCommandBuffer, context->buffer, vk::PipelineStageFlagBits::eDrawIndirect, vk::AccessFlagBits::eTransformFeedbackCounterReadEXT, vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferWrite); // Fill the buffer with the initial value mActiveCommandBuffer.fillBuffer(context->buffer, 0, 4, initialOffset); // Transition the stream out context buffer to the correct state for having counter // data written into it. It is expected that all contexts will always be in a state // ready to receive transform feedback, and readers will switch it in and then back // out of this state. _barrierStreamContextBuffer(mActiveCommandBuffer, context->buffer, vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferWrite, vk::PipelineStageFlagBits::eDrawIndirect, vk::AccessFlagBits::eTransformFeedbackCounterReadEXT); return context; } void Driver::releaseStreamContext(StreamContextObject* stream) { mStreamOutContextPool.push_back(stream); } void Driver::readbackStreamContext(StreamContextObject *stream, phys_addr writeAddr) { // Transition the stream-out context to being read for a read by the transfer sytem. _barrierStreamContextBuffer(mActiveCommandBuffer, stream->buffer, vk::PipelineStageFlagBits::eTransformFeedbackEXT, vk::AccessFlagBits::eTransformFeedbackCounterWriteEXT, vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferRead); // Grab the memory cache object for the destination auto memCache = getDataMemCache(writeAddr, 4); // Transition the cache to being a write target, this will cause it to write-back // to the GPU whenever this Pm4 Context is completed (or if its needed). transitionMemCache(memCache, ResourceUsage::StreamOutCounterWrite); // Copy the pointer from our local stream-out buffers into the user supplied region vk::BufferCopy copyRegion(0, 0, 4); mActiveCommandBuffer.copyBuffer(stream->buffer, memCache->buffer, { copyRegion }); // Return the stream out context to its normal feedback counter write state to // enable it to continue to be used by future stream-out operations. _barrierStreamContextBuffer(mActiveCommandBuffer, stream->buffer, vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferRead, vk::PipelineStageFlagBits::eTransformFeedbackEXT, vk::AccessFlagBits::eTransformFeedbackCounterWriteEXT); } StreamOutBufferDesc Driver::getStreamOutBufferDesc(uint32_t bufferIndex) { // If streamout is disabled, then there is no buffer here. auto vgt_strmout_en = getRegister<latte::VGT_STRMOUT_EN>(latte::Register::VGT_STRMOUT_EN); if (!vgt_strmout_en.STREAMOUT()) { return StreamOutBufferDesc(); } StreamOutBufferDesc desc; auto vgt_strmout_buffer_base = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_BUFFER_BASE_0 + 16 * bufferIndex); auto vgt_strmout_buffer_offset = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_BUFFER_OFFSET_0 + 16 * bufferIndex); auto vgt_strmout_buffer_size = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_BUFFER_SIZE_0 + 16 * bufferIndex); auto vgt_strmout_vtx_stride = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_VTX_STRIDE_0 + 16 * bufferIndex); decaf_check(vgt_strmout_buffer_offset == 0); desc.baseAddress = phys_addr(vgt_strmout_buffer_base << 8); desc.size = vgt_strmout_buffer_size << 2; desc.stride = vgt_strmout_vtx_stride << 2; return desc; } bool Driver::checkCurrentStreamOut() { if (!mCurrentDraw->streamOutEnabled) { return true; } for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { auto currentDesc = getStreamOutBufferDesc(i); if (!currentDesc.baseAddress) { mCurrentDraw->streamOutBuffers[i] = nullptr; continue; } // Fetch the memory cache for this buffer auto memCache = getDataMemCache(currentDesc.baseAddress, currentDesc.size); // Transition the buffer to being a stream out buffer. This will cause it to be // automatically invalidated later on. transitionMemCache(memCache, ResourceUsage::StreamOutBuffer); mCurrentDraw->streamOutBuffers[i] = memCache; } return true; } void Driver::bindStreamOutBuffers() { for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { auto& buffer = mCurrentDraw->streamOutBuffers[i]; if (!buffer) { continue; } mActiveCommandBuffer.bindTransformFeedbackBuffersEXT(i, { buffer->buffer }, { 0 }, { buffer->size }, mVkDynLoader); } } void Driver::beginStreamOut() { std::array<vk::Buffer, latte::MaxStreamOutBuffers> buffers = { vk::Buffer{} }; std::array<vk::DeviceSize, latte::MaxStreamOutBuffers> offsets = { 0 }; for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { auto ctxData = mCurrentDraw->streamOutContext[i]; if (ctxData) { buffers[i] = ctxData->buffer; } } mActiveCommandBuffer.beginTransformFeedbackEXT(0, buffers, offsets, mVkDynLoader); } void Driver::endStreamOut() { std::array<vk::Buffer, latte::MaxStreamOutBuffers> buffers = { vk::Buffer{} }; std::array<vk::DeviceSize, latte::MaxStreamOutBuffers> offsets = { 0 }; for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) { auto ctxData = mCurrentDraw->streamOutContext[i]; if (ctxData) { buffers[i] = ctxData->buffer; } } mActiveCommandBuffer.endTransformFeedbackEXT(0, buffers, offsets, mVkDynLoader); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_surface.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_utils.h" #include "latte/latte_formats.h" #include <common/rangecombiner.h> namespace vulkan { static inline std::string _makeSurfaceDescStr(const SurfaceDesc& info) { static const char *DIM_NAMES[] = { "1d", "2d", "3d", "cube", "1darr", "2darr", "2daa", "2daaarr" }; return fmt::format("{:08x}_{:x}_{}_{:x}_{:x}_{:x}_{}x{}x{}x{}#{}", info.calcAlignedBaseAddress(), info.calcSwizzle(), DIM_NAMES[info.dim], info.format, info.tileType, info.tileMode, info.pitch, info.width, info.height, info.depth, info.samples); } static inline std::string _makeSurfaceName(const SurfaceDesc& info) { static uint64_t surfImgIndex = 0; return fmt::format("surf_{}:{}", surfImgIndex++, _makeSurfaceDescStr(info)); } static inline std::string _makeSurfaceViewName(const SurfaceViewDesc& info) { static const char *COMP_NAMES[] = { "x", "y", "z", "w", "0", "1", "_" }; static uint64_t imageViewIndex = 0; return fmt::format("sview_{}:{}:{}{}{}{}:{}-{}", imageViewIndex++, _makeSurfaceDescStr(info.surfaceDesc), COMP_NAMES[info.channels[0]], COMP_NAMES[info.channels[1]], COMP_NAMES[info.channels[2]], COMP_NAMES[info.channels[3]], info.sliceStart, info.sliceEnd); } static inline SectionRange _sliceRangeToSectionRange(const SurfaceSubRange& range) { // Because of the way our memory cache retiler works, we can guarentee // that our slices are mapped 1:1 into sections. return { range.firstSlice, range.numSlices }; } static inline uint32_t _unthickenedSliceSize(const gpu7::tiling::SurfaceInfo &info) { return info.pitch * info.height * info.bpp / 8; } static inline std::pair<uint32_t, uint32_t> _sliceRangeToMemRange(SurfaceObject *surface, const SurfaceSubRange& range) { auto sliceSize = _unthickenedSliceSize(surface->tilingInfo); auto slicesOffset = range.firstSlice * sliceSize; auto slicesSize = range.numSlices * sliceSize; return { slicesOffset, slicesSize }; } static inline gpu7::tiling::SurfaceDescription _getTilingSurfaceDesc(const SurfaceDesc &info) { auto swizzle = info.calcSwizzle(); auto dataFormat = getSurfaceFormatDataFormat(info.format); auto bpp = latte::getDataFormatBitsPerElement(dataFormat); /* AddrTileMode tileMode; AddrFormat format; uint32_t bpp; uint32_t numSamples; uint32_t width; uint32_t height; uint32_t numSlices; ADDR_SURFACE_FLAGS flags; uint32_t numFrags; uint32_t numLevels; uint32_t bankSwizzle; uint32_t pipeSwizzle; */ gpu7::tiling::SurfaceDescription tilingDesc; tilingDesc.tileMode = static_cast<gpu7::tiling::TileMode>(info.tileMode); tilingDesc.format = static_cast<gpu7::tiling::DataFormat>(dataFormat); tilingDesc.bpp = bpp; tilingDesc.numSamples = 1; tilingDesc.width = info.pitch; tilingDesc.height = info.height; tilingDesc.numSlices = info.depth; tilingDesc.numFrags = 0; tilingDesc.numLevels = 1; tilingDesc.pipeSwizzle = (swizzle >> 8) & 1; tilingDesc.bankSwizzle = (swizzle >> 9) & 3; tilingDesc.dim = static_cast<gpu7::tiling::SurfaceDim>(info.dim); if (info.tileType == latte::SQ_TILE_TYPE::DEPTH) { tilingDesc.use |= gpu7::tiling::SurfaceUse::DepthBuffer; } /* if (dataFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dataFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { tilingDesc.width = (tilingDesc.width + 3) / 4; tilingDesc.height = (tilingDesc.height + 3) / 4; } */ return tilingDesc; } MemCacheObject * Driver::_getSurfaceMemCache(const SurfaceDesc &info, const gpu7::tiling::SurfaceInfo& tilingInfo) { auto realAddr = info.calcAlignedBaseAddress(); auto sliceSize = _unthickenedSliceSize(tilingInfo); auto alignedDepth = tilingInfo.depth; // Grab a memory cache object for this image return getMemCache(phys_addr(realAddr), alignedDepth, sliceSize); } void Driver::_copySurface(SurfaceObject *dst, SurfaceObject *src, SurfaceSubRange range) { // TODO: Add support for copying 1D Array's here... decaf_check(src->desc.dim != latte::SQ_TEX_DIM::DIM_1D_ARRAY); decaf_check(dst->desc.dim != latte::SQ_TEX_DIM::DIM_1D_ARRAY); // TODO: Add support for copying depth buffers here... decaf_check(dst->desc.tileType == src->desc.tileType); //decaf_check(dst->desc.tileType == latte::SQ_TILE_TYPE::DEFAULT); auto copyWidth = std::min(dst->width, src->width); auto copyHeight = std::min(dst->height, src->height); auto srcSlices = src->arrayLayers; if (src->desc.dim == latte::SQ_TEX_DIM::DIM_3D) { decaf_check(src->arrayLayers == 1); srcSlices = src->depth; } auto dstSlices = dst->arrayLayers; if (dst->desc.dim == latte::SQ_TEX_DIM::DIM_3D) { decaf_check(dst->arrayLayers == 1); dstSlices = dst->depth; } auto copySlices = std::min(srcSlices, dstSlices); if (range.firstSlice >= copySlices) { // We cannot perform any work, since the requested slice start // is beyond the end of one of the surfaces! return; } if (range.firstSlice + range.numSlices > copySlices) { // If the requested end is beyond the size of one of the surfaces, // lets shrink the range to only cover the available layers. range.numSlices = copySlices - range.firstSlice; } auto copyAspect = vk::ImageAspectFlags(); auto formatUsage = getVkSurfaceFormatUsage(src->desc.format); if (src->desc.tileType != latte::SQ_TILE_TYPE::DEPTH) { if (formatUsage & (SurfaceFormatUsage::TEXTURE | SurfaceFormatUsage::COLOR)) { copyAspect |= vk::ImageAspectFlagBits::eColor; } } else { if (formatUsage & SurfaceFormatUsage::DEPTH) { copyAspect |= vk::ImageAspectFlagBits::eDepth; } if (formatUsage & SurfaceFormatUsage::STENCIL) { copyAspect |= vk::ImageAspectFlagBits::eStencil; } } vk::ImageCopy copyRegion; if (src->desc.dim == latte::SQ_TEX_DIM::DIM_3D) { copyRegion.srcOffset = vk::Offset3D { 0, 0, static_cast<int32_t>(range.firstSlice) }; copyRegion.srcSubresource = { copyAspect, 0, 0, 1 }; // range.numSlices is expressed by the extent here... } else { copyRegion.srcOffset = vk::Offset3D { 0, 0, 0 }; copyRegion.srcSubresource = { copyAspect, 0, range.firstSlice, range.numSlices }; } if (dst->desc.dim == latte::SQ_TEX_DIM::DIM_3D) { copyRegion.dstOffset = vk::Offset3D { 0, 0, static_cast<int32_t>(range.firstSlice) }; copyRegion.dstSubresource = { copyAspect, 0, 0, 1 }; // range.numSlices is expressed by the extent here... } else { copyRegion.dstOffset = vk::Offset3D { 0, 0, 0 }; copyRegion.dstSubresource = { copyAspect, 0, range.firstSlice, range.numSlices }; // range.numSlices is expressed by the extent here... } copyRegion.extent = vk::Extent3D { copyWidth, copyHeight, copySlices }; auto originalSrcUsage = src->activeUsage; _barrierSurface(src, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, range); _barrierSurface(dst, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal, range); mActiveCommandBuffer.copyImage( src->image, vk::ImageLayout::eTransferSrcOptimal, dst->image, vk::ImageLayout::eTransferDstOptimal, { copyRegion }); // We don't know what the source was doing before, so we need to return // it back to it's original usage/layout in case its in use. if (originalSrcUsage != ResourceUsage::Undefined) { auto originalSrcLayout = getResourceUsageMeta(originalSrcUsage).imageLayout; _barrierSurface(src, originalSrcUsage, originalSrcLayout, range); } } SurfaceGroupObject * Driver::_allocateSurfaceGroup(const SurfaceDesc& info) { auto surfaceGroup = new SurfaceGroupObject(); surfaceGroup->desc = info; return surfaceGroup; } void Driver::_releaseSurfaceGroup(SurfaceGroupObject *surfaceGroup) { decaf_check(surfaceGroup->surfaces.empty()); delete surfaceGroup; } void Driver::_addSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface) { surfaceGroup->surfaces.push_back(surface); auto sliceCount = surface->slices.size(); while (surfaceGroup->sliceOwners.size() < sliceCount) { surfaceGroup->sliceOwners.push_back(nullptr); } } void Driver::_removeSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface) { for (auto& sliceOwner : surfaceGroup->sliceOwners) { if (sliceOwner == surface) { sliceOwner = nullptr; } } surfaceGroup->surfaces.remove(surface); } void Driver::_updateSurfaceGroupSlice(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, SurfaceObject *surface) { decaf_check(sliceId < surfaceGroup->sliceOwners.size()); surfaceGroup->sliceOwners[sliceId] = surface; } SurfaceObject * Driver::_getSurfaceGroupOwner(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, uint64_t minChangeIndex) { decaf_check(sliceId < surfaceGroup->sliceOwners.size()); auto& sliceOwner = surfaceGroup->sliceOwners[sliceId]; if (!sliceOwner) { return nullptr; } if (sliceOwner->slices[sliceId].lastChangeIndex < minChangeIndex) { return nullptr; } return sliceOwner; } SurfaceGroupObject * Driver::_getSurfaceGroup(const SurfaceDesc& info) { auto &surface = mSurfaceGroups[info.hash(true)]; if (!surface) { surface = _allocateSurfaceGroup(info); } return surface; } SurfaceObject * Driver::_allocateSurface(const SurfaceDesc& info) { decaf_check(info.baseAddress); decaf_check(info.width); decaf_check(info.height); decaf_check(info.depth); decaf_check(info.width <= 8192); decaf_check(info.height <= 8192); auto hostFormat = getVkSurfaceFormat(info.format, info.tileType); auto formatUsage = getVkSurfaceFormatUsage(info.format); vk::ImageAspectFlags aspectFlags; if (info.tileType != latte::SQ_TILE_TYPE::DEPTH) { if (formatUsage & (SurfaceFormatUsage::TEXTURE | SurfaceFormatUsage::COLOR)) { aspectFlags |= vk::ImageAspectFlagBits::eColor; } } else { if (formatUsage & SurfaceFormatUsage::DEPTH) { aspectFlags |= vk::ImageAspectFlagBits::eDepth; } if (formatUsage & SurfaceFormatUsage::STENCIL) { aspectFlags |= vk::ImageAspectFlagBits::eStencil; } } vk::ImageUsageFlags usageFlags; usageFlags |= vk::ImageUsageFlagBits::eTransferDst; usageFlags |= vk::ImageUsageFlagBits::eTransferSrc; if (formatUsage & SurfaceFormatUsage::TEXTURE) { usageFlags |= vk::ImageUsageFlagBits::eSampled; } if (info.tileType != latte::SQ_TILE_TYPE::DEPTH) { if (formatUsage & SurfaceFormatUsage::COLOR) { usageFlags |= vk::ImageUsageFlagBits::eColorAttachment; } } else { if (formatUsage & (SurfaceFormatUsage::DEPTH | SurfaceFormatUsage::STENCIL)) { usageFlags |= vk::ImageUsageFlagBits::eDepthStencilAttachment; } } vk::ImageType imageType; auto realPitch = 1u; auto realWidth = 1u; auto realHeight = 1u; auto realDepth = 1u; auto realArrayLayers = 1u; switch (info.dim) { case latte::SQ_TEX_DIM::DIM_1D: imageType = vk::ImageType::e1D; realPitch = info.pitch; realWidth = info.width; realHeight = 1; realDepth = 1; realArrayLayers = 1; break; case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: imageType = vk::ImageType::e2D; realPitch = info.pitch; realWidth = info.width; realHeight = info.height; realDepth = 1; realArrayLayers = 1; break; case latte::SQ_TEX_DIM::DIM_3D: imageType = vk::ImageType::e3D; realPitch = info.pitch; realWidth = info.width; realHeight = info.height; realDepth = info.depth; realArrayLayers = 1; break; case latte::SQ_TEX_DIM::DIM_CUBEMAP: imageType = vk::ImageType::e2D; realPitch = info.pitch; realWidth = info.width; realHeight = info.height; realDepth = 1; realArrayLayers = info.depth; break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: imageType = vk::ImageType::e1D; realPitch = info.pitch; realWidth = info.width; realHeight = 1; realDepth = 1; realArrayLayers = info.height; break; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: imageType = vk::ImageType::e2D; realPitch = info.pitch; realWidth = info.width; realHeight = info.height; realDepth = 1; realArrayLayers = info.depth; break; default: decaf_abort(fmt::format("Failed to pick vulkan dim for latte dim {}", info.dim)); } auto dataFormat = getSurfaceFormatDataFormat(info.format); auto texelPitch = info.pitch; auto texelWidth = info.width; auto texelHeight = info.height; if (dataFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dataFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { // Block compressed textures are tiled/untiled in terms of blocks texelPitch = texelPitch / 4; texelWidth = (texelWidth + 3) / 4; texelHeight = (texelHeight + 3) / 4; // We need to make sure to round the sizes up appropriately realWidth = align_up(realWidth, 4); realHeight = align_up(realHeight, 4); } vk::ImageCreateInfo createImageDesc; createImageDesc.imageType = imageType; createImageDesc.format = hostFormat; createImageDesc.extent = vk::Extent3D(realWidth, realHeight, realDepth); createImageDesc.mipLevels = 1; createImageDesc.arrayLayers = realArrayLayers; createImageDesc.samples = vk::SampleCountFlagBits::e1; createImageDesc.tiling = vk::ImageTiling::eOptimal; createImageDesc.usage = usageFlags; createImageDesc.sharingMode = vk::SharingMode::eExclusive; createImageDesc.initialLayout = vk::ImageLayout::eUndefined; auto image = mDevice.createImage(createImageDesc); setVkObjectName(image, _makeSurfaceName(info).c_str()); auto imageMemReqs = mDevice.getImageMemoryRequirements(image); vk::MemoryAllocateInfo allocDesc; allocDesc.allocationSize = imageMemReqs.size; allocDesc.memoryTypeIndex = findMemoryType(imageMemReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal); auto imageMem = mDevice.allocateMemory(allocDesc); mDevice.bindImageMemory(image, imageMem, 0); vk::ImageSubresourceRange subresRange; subresRange.aspectMask = aspectFlags; subresRange.baseMipLevel = 0; subresRange.levelCount = 1; subresRange.baseArrayLayer = 0; subresRange.layerCount = realArrayLayers; auto tilingDesc = _getTilingSurfaceDesc(info); auto tilingInfo = gpu7::tiling::computeSurfaceInfo(tilingDesc, 0); // Grab a reference to the memory cache that backs this surface auto memCache = _getSurfaceMemCache(info, tilingInfo); // TODO: Maybe join together the getSurfaceMemCache code and this? // Generate some meta-data about how we copy in/out auto alignedPitch = tilingInfo.pitch; auto alignedHeight = tilingInfo.height; if (dataFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dataFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) { alignedPitch *= 4; alignedHeight *= 4; } vk::BufferImageCopy bufferRegion = {}; bufferRegion.bufferOffset = 0; bufferRegion.bufferRowLength = alignedPitch; bufferRegion.bufferImageHeight = alignedHeight; bufferRegion.imageSubresource.aspectMask = subresRange.aspectMask; bufferRegion.imageSubresource.mipLevel = 0; bufferRegion.imageSubresource.baseArrayLayer = 0; bufferRegion.imageSubresource.layerCount = realArrayLayers; bufferRegion.imageOffset = vk::Offset3D { 0, 0, 0 }; bufferRegion.imageExtent = vk::Extent3D { static_cast<uint32_t>(realWidth), static_cast<uint32_t>(realHeight), static_cast<uint32_t>(realDepth) }; std::vector<SurfaceSlice> slices; if (info.dim == latte::SQ_TEX_DIM::DIM_3D) { // In the case of a 3D texture, we have to have a slice per DIM. This // is the only way to enable us to render to the surface appropriately. decaf_check(realArrayLayers == 1); for (auto i = 0u; i < info.depth; ++i) { SurfaceSlice slice; slice.lastChangeIndex = 0; slices.push_back(slice); } } else { for (auto i = 0u; i < realArrayLayers; ++i) { SurfaceSlice slice; slice.lastChangeIndex = 0; slices.push_back(slice); } } auto surfaceGroup = _getSurfaceGroup(info); // Return our freshly minted surface data object auto surface = new SurfaceObject(); surface->desc = info; surface->image = image; surface->imageMem = imageMem; surface->pitch = realPitch; surface->width = realWidth; surface->height = realHeight; surface->depth = realDepth; surface->arrayLayers = realArrayLayers; surface->tilingDesc = tilingDesc; surface->tilingInfo = tilingInfo; surface->slices = std::move(slices); surface->memCache = memCache; surface->subresRange = subresRange; surface->bufferRegion = bufferRegion; surface->activeUsage = ResourceUsage::Undefined; surface->lastUsageIndex = mActiveBatchIndex; surface->group = surfaceGroup; _addSurfaceGroupSurface(surfaceGroup, surface); return surface; } void Driver::_releaseSurface(SurfaceObject *surface) { // TODO: Add support for releasing surface data... delete surface; } void Driver::_upgradeSurface(SurfaceObject *surface, const SurfaceDesc &info) { // Allocate the new surface auto newSurface = _allocateSurface(info); // Verify that we are not making any errors // TODO: Reenable this check with support for array upgrades. //decaf_check(newSurface->desc.dim == surface->desc.dim); decaf_check(newSurface->desc.tileType == surface->desc.tileType); decaf_check(newSurface->desc.tileMode == surface->desc.tileMode); decaf_check(newSurface->desc.format == surface->desc.format); decaf_check(newSurface->width == surface->width); decaf_check(newSurface->height == surface->height); decaf_check(newSurface->depth == surface->depth); decaf_check(newSurface->arrayLayers > surface->arrayLayers); _copySurface(newSurface, surface, { 0, surface->arrayLayers }); newSurface->lastUsageIndex = surface->lastUsageIndex; for (auto i = 0u; i < surface->slices.size(); ++i) { newSurface->slices[i] = surface->slices[i]; } // Switch out the surfaces from the map auto switchIter = mSurfaces.find(info.hash()); decaf_check(switchIter->second == surface); std::swap(*switchIter->second, *newSurface); // Remove the surface from the group (it was automatically added) _removeSurfaceGroupSurface(surface->group, newSurface); // Release the surface on the next frame (an earlier reference to this surface // might have bound it to Vulkan, so we need to wait). addRetireTask([=](){ _releaseSurface(newSurface); }); } void Driver::_readSurfaceData(SurfaceObject *surface, SurfaceSubRange range) { auto& memCache = surface->memCache; auto memRange = _sliceRangeToMemRange(surface, range); auto sectionRange = _sliceRangeToSectionRange(range); auto untiledOffset = memRange.first; auto untiledBuffer = memCache->buffer; auto retileInfo = gpu7::tiling::computeRetileInfo(surface->tilingInfo); if (retileInfo.isTiled) { // Lets just double-check everyone is in agreement... // TODO: These wont match due to tile thickness needing to be aligned! //decaf_check(memRange.first == retileInfo.sliceOffset); // Calculate our retiling buffer size. auto retileSize = retileInfo.thinSliceBytes * range.numSlices; // Check that we are aligned based on our thickness. This is critical to // ensure that we correctly invalidate the regions touched by the retiler. decaf_check(range.firstSlice % retileInfo.microTileThickness == 0); decaf_check(range.numSlices % retileInfo.microTileThickness == 0); //retileSize /= retileInfo.microTileThickness; // Grab a staging buffer to write into before the image read auto retileStaging = getStagingBuffer(retileSize, StagingBufferType::GpuToGpu); // Calculate the real offset into our tiled data, the GPU retiler needs a buffer // offset that points directly to the slice. auto tiledOffset = range.firstSlice * retileInfo.thinSliceBytes; auto tiledBuffer = untiledBuffer; // Remap the untiled surface to our staging buffer untiledOffset = 0; untiledBuffer = retileStaging->buffer; _barrierMemCache(surface->memCache, ResourceUsage::ComputeSsboRead, sectionRange); dispatchGpuUntile(retileInfo, mActiveCommandBuffer, untiledBuffer, untiledOffset, tiledBuffer, tiledOffset, range.firstSlice, range.numSlices); _barrierMemCache(surface->memCache, ResourceUsage::TransferSrc, sectionRange); } else { // We will directly read from the memory cache, since this is a linear surface _barrierMemCache(surface->memCache, ResourceUsage::TransferSrc, sectionRange); } // Actually load the surface auto region = surface->bufferRegion; region.bufferOffset = untiledOffset; if (surface->desc.dim == latte::SQ_TEX_DIM::DIM_3D) { region.imageOffset.z = range.firstSlice; region.imageExtent.depth = range.numSlices; } else { region.imageSubresource.baseArrayLayer = range.firstSlice; region.imageSubresource.layerCount = range.numSlices; } _barrierSurface(surface, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal, range); // TODO: Improve how we handle subresources everywhere. bool hasDepth = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eDepth); bool hasStencil = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eStencil); if (!(hasDepth & hasStencil)) { mActiveCommandBuffer.copyBufferToImage( untiledBuffer, surface->image, vk::ImageLayout::eTransferDstOptimal, { region }); } else { auto region2 = region; region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth; region2.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil; mActiveCommandBuffer.copyBufferToImage( untiledBuffer, surface->image, vk::ImageLayout::eTransferDstOptimal, { region, region2 }); } } void Driver::_writeSurfaceData(SurfaceObject *surface, SurfaceSubRange range) { auto& memCache = surface->memCache; auto memRange = _sliceRangeToMemRange(surface, range); auto sectionRange = _sliceRangeToSectionRange(range); auto untiledOffset = memRange.first; auto untiledBuffer = memCache->buffer; auto retileInfo = gpu7::tiling::computeRetileInfo(surface->tilingInfo); if (retileInfo.isTiled) { // Calculate the retiling buffer size auto retileSize = retileInfo.thinSliceBytes * range.numSlices; // Check that we are aligned based on our thickness. This is critical to // ensure that we correctly invalidate the regions touched by the retiler. decaf_check(range.firstSlice % retileInfo.microTileThickness == 0); decaf_check(range.numSlices % retileInfo.microTileThickness == 0); //retileSize /= retileInfo.microTileThickness; // Grab our buffer used for retiling auto retileStaging = getStagingBuffer(retileSize, StagingBufferType::GpuToGpu); untiledOffset = 0; untiledBuffer = retileStaging->buffer; } else { // Write directly to the surface. _barrierMemCache(surface->memCache, ResourceUsage::TransferDst, sectionRange); } auto region = surface->bufferRegion; region.bufferOffset = untiledOffset; if (surface->desc.dim == latte::SQ_TEX_DIM::DIM_3D) { region.imageOffset.z = range.firstSlice; region.imageExtent.depth = range.numSlices; } else { region.imageSubresource.baseArrayLayer = range.firstSlice; region.imageSubresource.layerCount = range.numSlices; } _barrierSurface(surface, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, range); // TODO: Improve how we handle subresources everywhere. bool hasDepth = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eDepth); bool hasStencil = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eStencil); if (!(hasDepth & hasStencil)) { mActiveCommandBuffer.copyImageToBuffer( surface->image, vk::ImageLayout::eTransferSrcOptimal, untiledBuffer, { region }); } else { auto region2 = region; region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth; region2.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil; mActiveCommandBuffer.copyImageToBuffer( surface->image, vk::ImageLayout::eTransferSrcOptimal, untiledBuffer, { region, region2 }); } if (retileInfo.isTiled) { _barrierMemCache(surface->memCache, ResourceUsage::ComputeSsboWrite, sectionRange); auto tiledBuffer = memCache->buffer; auto tiledOffset = range.firstSlice * retileInfo.thinSliceBytes; dispatchGpuTile(retileInfo, mActiveCommandBuffer, tiledBuffer, tiledOffset, untiledBuffer, untiledOffset, range.firstSlice, range.numSlices); } } void Driver::_refreshSurface(SurfaceObject *surface, SurfaceSubRange range) { auto sectionRange = _sliceRangeToSectionRange(range); auto& memCache = surface->memCache; // We manually call refresh here, as in most cases a barrier on the memory // and a full data load will be unneccessary. surface->memCache->lastUsageIndex = surface->lastUsageIndex; _refreshMemCache_Check(memCache, sectionRange); auto readCombiner = makeRangeCombiner<SurfaceObject*, uint32_t, uint32_t>( [&](SurfaceObject* object, uint32_t start, uint32_t count){ _refreshMemCache_Update(memCache, { start, count }); _readSurfaceData(surface, { start, count }); }); auto blitCombiner = makeRangeCombiner<SurfaceObject*, uint32_t, uint32_t>( [&](SurfaceObject* object, uint32_t start, uint32_t count){ _copySurface(surface, object, { start, count }); }); for (auto i = sectionRange.start; i < sectionRange.start + sectionRange.count; ++i) { auto latestChangeIndex = memCache->sections[i].wantedChangeIndex; if (surface->slices[i].lastChangeIndex >= latestChangeIndex) { continue; } auto localOwner = _getSurfaceGroupOwner(surface->group, i, latestChangeIndex); if (localOwner) { decaf_check(!memCache->sections[i].needsUpload); blitCombiner.push(localOwner, i, 1); } else { readCombiner.push(nullptr, i, 1); } surface->slices[i].lastChangeIndex = latestChangeIndex; } readCombiner.flush(); blitCombiner.flush(); } void Driver::_invalidateSurface(SurfaceObject *surface, SurfaceSubRange range) { auto& memCache = surface->memCache; auto memRange = _sliceRangeToMemRange(surface, range); // Mark the memory cache as delayed invalidated, and perform the write // if the memory cache ends up requesting it (sometimes its cancelled). invalidateMemCacheDelayed(memCache, memRange.first, memRange.second, [=](){ // We need to be careful to restore the surface layout after we are // done, as this async download could happen mid-draw. auto oldUsage = surface->activeUsage; _writeSurfaceData(surface, range); auto oldLayout = getResourceUsageMeta(oldUsage).imageLayout; _barrierSurface(surface, oldUsage, oldLayout, range); }); // Update our last change index to match the data we wrote auto sectionRange = _sliceRangeToSectionRange(range); for (auto i = sectionRange.start; i < sectionRange.start + sectionRange.count; ++i) { surface->slices[i].lastChangeIndex = memCache->sections[i].lastChangeIndex; // Mark the surface as the owner in this surface group _updateSurfaceGroupSlice(surface->group, i, surface); } } void Driver::_barrierSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range) { auto srcMeta = getResourceUsageMeta(surface->activeUsage); auto dstMeta = getResourceUsageMeta(usage); // Lets make sure everyone agrees on what the layout // actually needs to be for this transition. decaf_check(dstMeta.imageLayout == layout); if (surface->activeUsage == usage) { return; } vk::ImageMemoryBarrier imageBarrier; imageBarrier.srcAccessMask = srcMeta.accessFlags; imageBarrier.dstAccessMask = dstMeta.accessFlags; imageBarrier.oldLayout = srcMeta.imageLayout; imageBarrier.newLayout = dstMeta.imageLayout; imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.image = surface->image; imageBarrier.subresourceRange = surface->subresRange; mActiveCommandBuffer.pipelineBarrier( srcMeta.stageFlags, dstMeta.stageFlags, vk::DependencyFlags(), {}, {}, { imageBarrier }); surface->activeUsage = usage; } SurfaceObject * Driver::getSurface(const SurfaceDesc& info) { auto& surface = mSurfaces[info.hash()]; if (!surface) { surface = _allocateSurface(info); } if (info.pitch > surface->desc.pitch || info.width > surface->desc.width || info.height > surface->desc.height || info.depth > surface->desc.depth) { _upgradeSurface(surface, info); } // Check we got the surface we wanted, not that we check height,depth // as a greater-equal to easily handle the array cases. We also skip // DIM check, since it can go from 2D to 2D_ARRAY. decaf_check(surface->desc.calcAlignedBaseAddress() == info.calcAlignedBaseAddress()); decaf_check(surface->desc.pitch == info.pitch); decaf_check(surface->desc.width == info.width); decaf_check(surface->desc.height >= info.height); decaf_check(surface->desc.depth >= info.depth); decaf_check(surface->desc.samples == info.samples); decaf_check(surface->desc.tileType == info.tileType); decaf_check(surface->desc.tileMode == info.tileMode); decaf_check(surface->desc.format == info.format); return surface; } void Driver::transitionSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range, bool skipChangeCheck) { // We need to align our invalidation groups along a tickness boundary! auto alignedRange = range; auto tileThickness = gpu7::tiling::getMicroTileThickness(surface->tilingInfo.tileMode); if (tileThickness > 1) { auto endSlice = range.firstSlice + range.numSlices; alignedRange.firstSlice = align_down(range.firstSlice, tileThickness); endSlice = align_up(endSlice, tileThickness); alignedRange.numSlices = endSlice - alignedRange.firstSlice; } surface->lastUsageIndex = mActiveBatchIndex; bool forWrite = getResourceUsageMeta(usage).isWrite; if (!skipChangeCheck) { _refreshSurface(surface, alignedRange); if (forWrite) { _invalidateSurface(surface, alignedRange); } } _barrierSurface(surface, usage, layout, alignedRange); } SurfaceViewObject * Driver::_allocateSurfaceView(const SurfaceViewDesc& info) { auto adjInfo = info; if (adjInfo.surfaceDesc.dim == latte::SQ_TEX_DIM::DIM_3D) { // The source GPU allows slice selection on 3D textures. In order // to support this, we will actually have to adjust the sizing of // the underlying 3D textures so that the image view can correctly // see it (since we cannot view particular slices with this). decaf_check(adjInfo.sliceStart == 0); decaf_check(adjInfo.sliceEnd == adjInfo.surfaceDesc.depth); adjInfo.sliceEnd = 1; } auto surface = getSurface(adjInfo.surfaceDesc); auto subresRange = surface->subresRange; subresRange.baseArrayLayer = adjInfo.sliceStart; subresRange.layerCount = adjInfo.sliceEnd - adjInfo.sliceStart; // We still need to support invalidating specific slices in the underlying // image, in case someone renders to a specific slice of the image. SurfaceSubRange range; range.firstSlice = info.sliceStart; range.numSlices = info.sliceEnd - info.sliceStart; auto surfaceView = new SurfaceViewObject(); surfaceView->desc = info; surfaceView->surfaceRange = range; surfaceView->surface = surface; //surfaceView->imageView = nullptr; //surfaceView->boundImage = nullptr; surfaceView->subresRange = subresRange; return surfaceView; } void Driver::_releaseSurfaceView(SurfaceViewObject *surfaceView) { // TODO: Add support for releasing surfaces... } SurfaceViewObject * Driver::getSurfaceView(const SurfaceViewDesc& info) { auto &surfaceView = mSurfaceViews[info.hash()]; if (!surfaceView) { surfaceView = _allocateSurfaceView(info); } decaf_check(surfaceView->desc->sliceStart == info.sliceStart); decaf_check(surfaceView->desc->sliceEnd == info.sliceEnd); return surfaceView; } void Driver::transitionSurfaceView(SurfaceViewObject *surfaceView, ResourceUsage usage, vk::ImageLayout layout, bool skipChangeCheck) { transitionSurface(surfaceView->surface, usage, layout, surfaceView->surfaceRange, skipChangeCheck); if (surfaceView->boundImage == surfaceView->surface->image) { return; } auto& info = surfaceView->desc; auto& surface = surfaceView->surface; auto hostFormat = getVkSurfaceFormat(info->surfaceDesc.format, info->surfaceDesc.tileType); vk::ImageViewType imageViewType; switch (info->surfaceDesc.dim) { case latte::SQ_TEX_DIM::DIM_1D: imageViewType = vk::ImageViewType::e1D; break; case latte::SQ_TEX_DIM::DIM_2D: case latte::SQ_TEX_DIM::DIM_2D_MSAA: imageViewType = vk::ImageViewType::e2D; break; case latte::SQ_TEX_DIM::DIM_CUBEMAP: imageViewType = vk::ImageViewType::e2DArray; break; case latte::SQ_TEX_DIM::DIM_1D_ARRAY: imageViewType = vk::ImageViewType::e1DArray; break; case latte::SQ_TEX_DIM::DIM_2D_ARRAY: case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA: imageViewType = vk::ImageViewType::e2DArray; break; case latte::SQ_TEX_DIM::DIM_3D: imageViewType = vk::ImageViewType::e3D; break; default: decaf_abort(fmt::format("Failed to pick vulkan image view type for dim {}", info->surfaceDesc.dim)); } auto hostComponentMap = vk::ComponentMapping(); hostComponentMap.r = getVkComponentSwizzle(info->channels[0]); hostComponentMap.g = getVkComponentSwizzle(info->channels[1]); hostComponentMap.b = getVkComponentSwizzle(info->channels[2]); hostComponentMap.a = getVkComponentSwizzle(info->channels[3]); vk::ImageViewCreateInfo imageViewDesc; imageViewDesc.image = surface->image; imageViewDesc.viewType = imageViewType; imageViewDesc.format = hostFormat; imageViewDesc.components = hostComponentMap; imageViewDesc.subresourceRange = surfaceView->subresRange; auto imageView = mDevice.createImageView(imageViewDesc); setVkObjectName(imageView, _makeSurfaceViewName(*info).c_str()); if (surfaceView->imageView) { auto oldImageView = surfaceView->imageView; addRetireTask([=](){ mDevice.destroyImageView(oldImageView); }); surfaceView->imageView = vk::ImageView(); } surfaceView->imageView = imageView; surfaceView->boundImage = surface->image; } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_swapchain.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { SwapChainObject * Driver::allocateSwapChain(const SwapChainDesc &desc) { SurfaceDesc surfaceDesc; surfaceDesc.baseAddress = desc.baseAddress.getAddress(); surfaceDesc.pitch = desc.width; surfaceDesc.width = desc.width; surfaceDesc.height = desc.height; surfaceDesc.depth = 1; surfaceDesc.samples = 1u; surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D; surfaceDesc.format = latte::SurfaceFormat::R8G8B8A8Srgb; surfaceDesc.tileType = latte::SQ_TILE_TYPE::DEFAULT; surfaceDesc.tileMode = latte::SQ_TILE_MODE::LINEAR_ALIGNED; SurfaceViewDesc surfaceViewDesc; surfaceViewDesc.sliceStart = 0; surfaceViewDesc.sliceEnd = 1; surfaceViewDesc.surfaceDesc = surfaceDesc; surfaceViewDesc.channels = { latte::SQ_SEL::SEL_X, latte::SQ_SEL::SEL_Y, latte::SQ_SEL::SEL_Z, latte::SQ_SEL::SEL_W }; // TODO: The swap buffer manager inside libgpu should not be managing the ImageView // that is being used by the host application... auto surfaceView = getSurfaceView(surfaceViewDesc); auto surface = surfaceView->surface; // We have to transition the view not the surface to ensure the imageView is created. transitionSurfaceView(surfaceView, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal); std::array<float, 4> clearColor = { 0.1f, 0.1f, 0.1f, 1.0f }; mActiveCommandBuffer.clearColorImage(surface->image, vk::ImageLayout::eTransferDstOptimal, clearColor, { surface->subresRange }); auto swapChain = new SwapChainObject(); swapChain->_surface = surface; swapChain->desc = desc; swapChain->presentable = false; swapChain->imageView = surfaceView->imageView; swapChain->image = surface->image; swapChain->subresRange = surface->subresRange; return swapChain; } void Driver::releaseSwapChain(SwapChainObject *swapChain) { // TODO: Implement releasing of vulkan swap chains. } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_textures.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { SurfaceViewDesc Driver::getTextureDesc(ShaderStage shaderStage, uint32_t textureIdx) { uint32_t samplerBaseIdx; if (shaderStage == ShaderStage::Vertex) { samplerBaseIdx = latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0; } else if (shaderStage == ShaderStage::Geometry) { samplerBaseIdx = latte::SQ_RES_OFFSET::GS_TEX_RESOURCE_0; } else if (shaderStage == ShaderStage::Pixel) { samplerBaseIdx = latte::SQ_RES_OFFSET::PS_TEX_RESOURCE_0; } else { decaf_abort("Unknown shader stage"); } auto resourceOffset = (samplerBaseIdx + textureIdx) * 7; auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset); auto sq_tex_resource_word1 = getRegister<latte::SQ_TEX_RESOURCE_WORD1_N>(latte::Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset); auto sq_tex_resource_word2 = getRegister<latte::SQ_TEX_RESOURCE_WORD2_N>(latte::Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset); auto sq_tex_resource_word3 = getRegister<latte::SQ_TEX_RESOURCE_WORD3_N>(latte::Register::SQ_RESOURCE_WORD3_0 + 4 * resourceOffset); auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset); auto sq_tex_resource_word5 = getRegister<latte::SQ_TEX_RESOURCE_WORD5_N>(latte::Register::SQ_RESOURCE_WORD5_0 + 4 * resourceOffset); auto sq_tex_resource_word6 = getRegister<latte::SQ_TEX_RESOURCE_WORD6_N>(latte::Register::SQ_RESOURCE_WORD6_0 + 4 * resourceOffset); SurfaceDesc surfaceDataDesc; surfaceDataDesc.baseAddress = sq_tex_resource_word2.BASE_ADDRESS() << 8; surfaceDataDesc.pitch = (sq_tex_resource_word0.PITCH() + 1) * 8; surfaceDataDesc.width = sq_tex_resource_word0.TEX_WIDTH() + 1; surfaceDataDesc.height = sq_tex_resource_word1.TEX_HEIGHT() + 1; surfaceDataDesc.depth = sq_tex_resource_word1.TEX_DEPTH() + 1; surfaceDataDesc.samples = 1u; surfaceDataDesc.dim = sq_tex_resource_word0.DIM(); surfaceDataDesc.format = latte::getSurfaceFormat( sq_tex_resource_word1.DATA_FORMAT(), sq_tex_resource_word4.NUM_FORMAT_ALL(), sq_tex_resource_word4.FORMAT_COMP_X(), sq_tex_resource_word4.FORCE_DEGAMMA()); surfaceDataDesc.tileType = sq_tex_resource_word0.TILE_TYPE(); surfaceDataDesc.tileMode = sq_tex_resource_word0.TILE_MODE(); if (surfaceDataDesc.dim == latte::SQ_TEX_DIM::DIM_CUBEMAP) { surfaceDataDesc.depth *= 6; } SurfaceViewDesc surfaceDesc; surfaceDesc.surfaceDesc = surfaceDataDesc; surfaceDesc.sliceStart = sq_tex_resource_word5.BASE_ARRAY(); surfaceDesc.sliceEnd = sq_tex_resource_word5.LAST_ARRAY() + 1; surfaceDesc.channels[0] = sq_tex_resource_word4.DST_SEL_X(); surfaceDesc.channels[1] = sq_tex_resource_word4.DST_SEL_Y(); surfaceDesc.channels[2] = sq_tex_resource_word4.DST_SEL_Z(); surfaceDesc.channels[3] = sq_tex_resource_word4.DST_SEL_W(); return surfaceDesc; } void Driver::updateDrawTexture(ShaderStage shaderStage, uint32_t textureIdx) { uint32_t shaderStageIdx = static_cast<uint32_t>(shaderStage); auto &drawTexture = mCurrentDraw->textures[shaderStageIdx][textureIdx]; HashedDesc<SurfaceViewDesc> currentDesc = getTextureDesc(shaderStage, textureIdx); if (!currentDesc->surfaceDesc.baseAddress) { drawTexture = nullptr; return; } if (drawTexture && drawTexture->desc == currentDesc) { // We already have the correct texture set return; } drawTexture = getSurfaceView(*currentDesc); mCurrentDraw->textureDirty[shaderStageIdx][textureIdx] = true; } bool Driver::checkCurrentTextures() { if (mCurrentDraw->vertexShader) { for (auto textureIdx = 0u; textureIdx < latte::MaxTextures; ++textureIdx) { if (mCurrentDraw->vertexShader->shader.meta.textureUsed[textureIdx]) { updateDrawTexture(ShaderStage::Vertex, textureIdx); } else { mCurrentDraw->textures[0][textureIdx] = nullptr; } } } else { for (auto textureIdx = 0; textureIdx < latte::MaxTextures; ++textureIdx) { mCurrentDraw->textures[0][textureIdx] = nullptr; } } if (mCurrentDraw->geometryShader) { for (auto textureIdx = 0u; textureIdx < latte::MaxTextures; ++textureIdx) { if (mCurrentDraw->geometryShader->shader.meta.textureUsed[textureIdx]) { updateDrawTexture(ShaderStage::Geometry, textureIdx); } else { mCurrentDraw->textures[1][textureIdx] = nullptr; } } } else { for (auto textureIdx = 0; textureIdx < latte::MaxTextures; ++textureIdx) { mCurrentDraw->textures[1][textureIdx] = nullptr; } } if (mCurrentDraw->pixelShader) { for (auto textureIdx = 0u; textureIdx < latte::MaxTextures; ++textureIdx) { if (mCurrentDraw->pixelShader->shader.meta.textureUsed[textureIdx]) { updateDrawTexture(ShaderStage::Pixel, textureIdx); } else { mCurrentDraw->textures[2][textureIdx] = nullptr; } } } else { for (auto textureIdx = 0; textureIdx < latte::MaxTextures; ++textureIdx) { mCurrentDraw->textures[2][textureIdx] = nullptr; } } return true; } void Driver::prepareCurrentTextures() { for (auto i = 0u; i < latte::MaxTextures; ++i) { auto& vsSurface = mCurrentDraw->textures[0][i]; if (vsSurface) { if (!mCurrentDraw->textureDirty[0][i]) { transitionSurfaceView(vsSurface, ResourceUsage::VertexTexture, vk::ImageLayout::eShaderReadOnlyOptimal, true); } else { transitionSurfaceView(vsSurface, ResourceUsage::VertexTexture, vk::ImageLayout::eShaderReadOnlyOptimal); mCurrentDraw->textureDirty[0][i] = false; } } auto& gsSurface = mCurrentDraw->textures[1][i]; if (gsSurface) { if (!mCurrentDraw->textureDirty[1][i]) { transitionSurfaceView(gsSurface, ResourceUsage::GeometryTexture, vk::ImageLayout::eShaderReadOnlyOptimal, true); } else { transitionSurfaceView(gsSurface, ResourceUsage::GeometryTexture, vk::ImageLayout::eShaderReadOnlyOptimal); mCurrentDraw->textureDirty[1][i] = false; } } auto& psSurface = mCurrentDraw->textures[2][i]; if (psSurface) { if (!mCurrentDraw->textureDirty[2][i]) { transitionSurfaceView(psSurface, ResourceUsage::PixelTexture, vk::ImageLayout::eShaderReadOnlyOptimal, true); } else { transitionSurfaceView(psSurface, ResourceUsage::PixelTexture, vk::ImageLayout::eShaderReadOnlyOptimal); mCurrentDraw->textureDirty[2][i] = false; } } } } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_tiling.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { void Driver::dispatchGpuTile(const gpu7::tiling::RetileInfo& retileInfo, vk::CommandBuffer &commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices) { auto handle = mGpuRetiler.tile(retileInfo, commandBuffer, dstBuffer, dstOffset, srcBuffer, srcOffset, firstSlice, numSlices); mActiveSyncWaiter->retileHandles.push_back(handle); } void Driver::dispatchGpuUntile(const gpu7::tiling::RetileInfo& retileInfo, vk::CommandBuffer &commandBuffer, vk::Buffer dstBuffer, uint32_t dstOffset, vk::Buffer srcBuffer, uint32_t srcOffset, uint32_t firstSlice, uint32_t numSlices) { auto handle = mGpuRetiler.untile(retileInfo, commandBuffer, dstBuffer, dstOffset, srcBuffer, srcOffset, firstSlice, numSlices); mActiveSyncWaiter->retileHandles.push_back(handle); } } #endif // DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_utils.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_utils.h" #include <common/decaf_assert.h> #include <common/log.h> namespace vulkan { vk::Format getVkSurfaceFormat(latte::SurfaceFormat format, latte::SQ_TILE_TYPE tileType) { if (tileType == latte::SQ_TILE_TYPE::DEPTH) { switch (format) { case latte::SurfaceFormat::R16Unorm: return vk::Format::eD16Unorm; case latte::SurfaceFormat::R32Float: return vk::Format::eD32Sfloat; case latte::SurfaceFormat::D24UnormS8Uint: return vk::Format::eD24UnormS8Uint; case latte::SurfaceFormat::X24G8Uint: return vk::Format::eD24UnormS8Uint; // Remapped? case latte::SurfaceFormat::D32FloatS8UintX24: return vk::Format::eD32SfloatS8Uint; // Wrong Size? case latte::SurfaceFormat::D32G8UintX24: return vk::Format::eD32SfloatS8Uint; // Remapped? } decaf_abort(fmt::format("Unexpected depth surface format {}", format)); } switch (format) { case latte::SurfaceFormat::R8Unorm: return vk::Format::eR8Unorm; case latte::SurfaceFormat::R8Uint: return vk::Format::eR8Uint; case latte::SurfaceFormat::R8Snorm: return vk::Format::eR8Snorm; case latte::SurfaceFormat::R8Sint: return vk::Format::eR8Sint; case latte::SurfaceFormat::R4G4Unorm: return vk::Format::eR4G4UnormPack8; case latte::SurfaceFormat::R16Unorm: return vk::Format::eR16Unorm; case latte::SurfaceFormat::R16Uint: return vk::Format::eR16Uint; case latte::SurfaceFormat::R16Snorm: return vk::Format::eR16Snorm; case latte::SurfaceFormat::R16Sint: return vk::Format::eR16Sint; case latte::SurfaceFormat::R16Float: return vk::Format::eR16Sfloat; case latte::SurfaceFormat::R8G8Unorm: return vk::Format::eR8G8Unorm; case latte::SurfaceFormat::R8G8Uint: return vk::Format::eR8G8Uint; case latte::SurfaceFormat::R8G8Snorm: return vk::Format::eR8G8Snorm; case latte::SurfaceFormat::R8G8Sint: return vk::Format::eR8G8Sint; case latte::SurfaceFormat::R5G6B5Unorm: return vk::Format::eR5G6B5UnormPack16; case latte::SurfaceFormat::R5G5B5A1Unorm: return vk::Format::eR5G5B5A1UnormPack16; case latte::SurfaceFormat::R4G4B4A4Unorm: return vk::Format::eR4G4B4A4UnormPack16; case latte::SurfaceFormat::A1B5G5R5Unorm: return vk::Format::eR5G5B5A1UnormPack16; // Reversed? case latte::SurfaceFormat::R32Uint: return vk::Format::eR32Uint; case latte::SurfaceFormat::R32Sint: return vk::Format::eR32Sint; case latte::SurfaceFormat::R32Float: return vk::Format::eR32Sfloat; case latte::SurfaceFormat::R16G16Unorm: return vk::Format::eR16G16Unorm; case latte::SurfaceFormat::R16G16Uint: return vk::Format::eR16G16Uint; case latte::SurfaceFormat::R16G16Snorm: return vk::Format::eR16G16Snorm; case latte::SurfaceFormat::R16G16Sint: return vk::Format::eR16G16Sint; case latte::SurfaceFormat::R16G16Float: return vk::Format::eR16G16Sfloat; case latte::SurfaceFormat::D24UnormS8Uint: return vk::Format::eD24UnormS8Uint; case latte::SurfaceFormat::X24G8Uint: return vk::Format::eD24UnormS8Uint; // Not sure if this is actually right... case latte::SurfaceFormat::R11G11B10Float: return vk::Format::eB10G11R11UfloatPack32; // This is the incorrect format... //return vk::Format::eB10G11R11UfloatPack32; // Remapped? case latte::SurfaceFormat::R10G10B10A2Unorm: return vk::Format::eA2B10G10R10UnormPack32; // Remapped? case latte::SurfaceFormat::R10G10B10A2Uint: return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format... //return vk::Format::eA2B10G10R10UintPack32; // Remapped? case latte::SurfaceFormat::R10G10B10A2Snorm: return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format... //return vk::Format::eA2B10G10R10SnormPack32; // Remapped? case latte::SurfaceFormat::R10G10B10A2Sint: return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format... //return vk::Format::eA2B10G10R10SintPack32; // Remapped? case latte::SurfaceFormat::R8G8B8A8Unorm: return vk::Format::eR8G8B8A8Unorm; case latte::SurfaceFormat::R8G8B8A8Uint: return vk::Format::eR8G8B8A8Uint; case latte::SurfaceFormat::R8G8B8A8Snorm: return vk::Format::eR8G8B8A8Snorm; case latte::SurfaceFormat::R8G8B8A8Sint: return vk::Format::eR8G8B8A8Sint; case latte::SurfaceFormat::R8G8B8A8Srgb: return vk::Format::eR8G8B8A8Srgb; case latte::SurfaceFormat::A2B10G10R10Unorm: return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format... //return vk::Format::eA2B10G10R10UnormPack32; case latte::SurfaceFormat::A2B10G10R10Uint: return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format... //return vk::Format::eA2B10G10R10UintPack32; case latte::SurfaceFormat::D32FloatS8UintX24: return vk::Format::eD32SfloatS8Uint; case latte::SurfaceFormat::D32G8UintX24: return vk::Format::eD32SfloatS8Uint; case latte::SurfaceFormat::R32G32Uint: return vk::Format::eR32G32Uint; case latte::SurfaceFormat::R32G32Sint: return vk::Format::eR32G32Sint; case latte::SurfaceFormat::R32G32Float: return vk::Format::eR32G32Sfloat; case latte::SurfaceFormat::R16G16B16A16Unorm: return vk::Format::eR16G16B16A16Unorm; case latte::SurfaceFormat::R16G16B16A16Uint: return vk::Format::eR16G16B16A16Uint; case latte::SurfaceFormat::R16G16B16A16Snorm: return vk::Format::eR16G16B16A16Snorm; case latte::SurfaceFormat::R16G16B16A16Sint: return vk::Format::eR16G16B16A16Uint; case latte::SurfaceFormat::R16G16B16A16Float: return vk::Format::eR16G16B16A16Sfloat; case latte::SurfaceFormat::R32G32B32A32Uint: return vk::Format::eR32G32B32A32Uint; case latte::SurfaceFormat::R32G32B32A32Sint: return vk::Format::eR32G32B32A32Sint; case latte::SurfaceFormat::R32G32B32A32Float: return vk::Format::eR32G32B32A32Sfloat; case latte::SurfaceFormat::BC1Unorm: return vk::Format::eBc1RgbaUnormBlock; case latte::SurfaceFormat::BC1Srgb: return vk::Format::eBc1RgbaSrgbBlock; case latte::SurfaceFormat::BC2Unorm: return vk::Format::eBc2UnormBlock; case latte::SurfaceFormat::BC2Srgb: return vk::Format::eBc2SrgbBlock; case latte::SurfaceFormat::BC3Unorm: return vk::Format::eBc3UnormBlock; case latte::SurfaceFormat::BC3Srgb: return vk::Format::eBc3SrgbBlock; case latte::SurfaceFormat::BC4Unorm: return vk::Format::eBc4UnormBlock; case latte::SurfaceFormat::BC4Snorm: return vk::Format::eBc4SnormBlock; case latte::SurfaceFormat::BC5Unorm: return vk::Format::eBc5UnormBlock; case latte::SurfaceFormat::BC5Snorm: return vk::Format::eBc5SnormBlock; //case latte::SurfaceFormat::NV12: // Honestly have no clue how to support this format... } decaf_abort(fmt::format("Unexpected surface format {}", format)); } SurfaceFormatUsage getVkSurfaceFormatUsage(latte::SurfaceFormat format) { switch (format) { case latte::SurfaceFormat::R8Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R4G4Unorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::R16Unorm: return SurfaceFormatUsage::TD; // TODO: Should support TCD case latte::SurfaceFormat::R16Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16Float: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R5G6B5Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R5G5B5A1Unorm: return SurfaceFormatUsage::T; // TODO: Should support TC case latte::SurfaceFormat::R4G4B4A4Unorm: return SurfaceFormatUsage::T; // TODO: Should support TC case latte::SurfaceFormat::A1B5G5R5Unorm: return SurfaceFormatUsage::T; // TODO: Should support TC case latte::SurfaceFormat::R32Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32Float: return SurfaceFormatUsage::TCD; case latte::SurfaceFormat::R16G16Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16Float: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::D24UnormS8Uint: return SurfaceFormatUsage::DS; case latte::SurfaceFormat::X24G8Uint: return SurfaceFormatUsage::T; // TODO: Should support TC case latte::SurfaceFormat::R11G11B10Float: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R10G10B10A2Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R10G10B10A2Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R10G10B10A2Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R10G10B10A2Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8B8A8Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8B8A8Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8B8A8Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8B8A8Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R8G8B8A8Srgb: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::A2B10G10R10Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::A2B10G10R10Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::D32FloatS8UintX24: return SurfaceFormatUsage::TDS; case latte::SurfaceFormat::D32G8UintX24: return SurfaceFormatUsage::T; case latte::SurfaceFormat::R32G32Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32G32Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32G32Float: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16B16A16Unorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16B16A16Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16B16A16Snorm: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16B16A16Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R16G16B16A16Float: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32G32B32A32Uint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32G32B32A32Sint: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::R32G32B32A32Float: return SurfaceFormatUsage::TC; case latte::SurfaceFormat::BC1Unorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC1Srgb: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC2Unorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC2Srgb: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC3Unorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC3Srgb: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC4Unorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC4Snorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC5Unorm: return SurfaceFormatUsage::T; case latte::SurfaceFormat::BC5Snorm: return SurfaceFormatUsage::T; //case latte::SurfaceFormat::NV12: // Honestly have no clue how to support this format... } decaf_abort(fmt::format("Unexpected surface format {}", format)); } vk::ComponentSwizzle getVkComponentSwizzle(latte::SQ_SEL sel) { switch (sel) { case latte::SQ_SEL::SEL_X: return vk::ComponentSwizzle::eR; case latte::SQ_SEL::SEL_Y: return vk::ComponentSwizzle::eG; case latte::SQ_SEL::SEL_Z: return vk::ComponentSwizzle::eB; case latte::SQ_SEL::SEL_W: return vk::ComponentSwizzle::eA; case latte::SQ_SEL::SEL_0: return vk::ComponentSwizzle::eZero; case latte::SQ_SEL::SEL_1: return vk::ComponentSwizzle::eOne; case latte::SQ_SEL::SEL_MASK: return vk::ComponentSwizzle::eIdentity; } decaf_abort(fmt::format("Unexpected component swizzle {}", sel)); } vk::BorderColor getVkBorderColor(latte::SQ_TEX_BORDER_COLOR color) { switch (color) { case latte::SQ_TEX_BORDER_COLOR::TRANS_BLACK: return vk::BorderColor::eFloatTransparentBlack; case latte::SQ_TEX_BORDER_COLOR::OPAQUE_BLACK: return vk::BorderColor::eFloatOpaqueBlack; case latte::SQ_TEX_BORDER_COLOR::OPAQUE_WHITE: return vk::BorderColor::eFloatOpaqueWhite; case latte::SQ_TEX_BORDER_COLOR::REGISTER: decaf_abort("Unsupported register-based texture border color"); default: decaf_abort("Unexpected texture border color type"); } } vk::Filter getVkXyTextureFilter(latte::SQ_TEX_XY_FILTER filter) { switch (filter) { case latte::SQ_TEX_XY_FILTER::POINT: return vk::Filter::eNearest; case latte::SQ_TEX_XY_FILTER::BILINEAR: return vk::Filter::eLinear; case latte::SQ_TEX_XY_FILTER::BICUBIC: return vk::Filter::eCubicIMG; default: gLog->warn("Unexpected texture xy filter mode"); return vk::Filter::eNearest; //decaf_abort("Unexpected texture xy filter mode"); } } vk::SamplerMipmapMode getVkZTextureFilter(latte::SQ_TEX_Z_FILTER filter) { switch (filter) { case latte::SQ_TEX_Z_FILTER::NONE: return vk::SamplerMipmapMode::eNearest; case latte::SQ_TEX_Z_FILTER::POINT: return vk::SamplerMipmapMode::eNearest; case latte::SQ_TEX_Z_FILTER::LINEAR: return vk::SamplerMipmapMode::eLinear; default: decaf_abort("Unexpected texture xy filter mode"); } } vk::SamplerAddressMode getVkTextureAddressMode(latte::SQ_TEX_CLAMP clamp) { switch (clamp) { case latte::SQ_TEX_CLAMP::WRAP: return vk::SamplerAddressMode::eRepeat; case latte::SQ_TEX_CLAMP::MIRROR: return vk::SamplerAddressMode::eMirroredRepeat; case latte::SQ_TEX_CLAMP::CLAMP_LAST_TEXEL: return vk::SamplerAddressMode::eClampToEdge; case latte::SQ_TEX_CLAMP::MIRROR_ONCE_LAST_TEXEL: return vk::SamplerAddressMode::eMirrorClampToEdge; case latte::SQ_TEX_CLAMP::CLAMP_HALF_BORDER: return vk::SamplerAddressMode::eClampToBorder; case latte::SQ_TEX_CLAMP::MIRROR_ONCE_HALF_BORDER: return vk::SamplerAddressMode::eMirrorClampToEdge; case latte::SQ_TEX_CLAMP::CLAMP_BORDER: return vk::SamplerAddressMode::eClampToBorder; case latte::SQ_TEX_CLAMP::MIRROR_ONCE_BORDER: return vk::SamplerAddressMode::eMirrorClampToEdge; default: decaf_abort("Unexpected texture clamp mode"); } } bool getVkAnisotropyEnabled(latte::SQ_TEX_ANISO aniso) { return aniso != latte::SQ_TEX_ANISO::ANISO_1_TO_1; } float getVkMaxAnisotropy(latte::SQ_TEX_ANISO aniso) { switch (aniso) { case latte::SQ_TEX_ANISO::ANISO_1_TO_1: return 1.0f; case latte::SQ_TEX_ANISO::ANISO_2_TO_1: return 2.0f; case latte::SQ_TEX_ANISO::ANISO_4_TO_1: return 4.0f; case latte::SQ_TEX_ANISO::ANISO_8_TO_1: return 8.0f; case latte::SQ_TEX_ANISO::ANISO_16_TO_1: return 16.0f; default: decaf_abort("Unexpected texture anisotropy mode"); } } bool getVkCompareOpEnabled(latte::REF_FUNC refFunc) { return refFunc != latte::REF_FUNC::NEVER; } vk::CompareOp getVkCompareOp(latte::REF_FUNC func) { switch (func) { case latte::REF_FUNC::NEVER: return vk::CompareOp::eNever; case latte::REF_FUNC::LESS: return vk::CompareOp::eLess; case latte::REF_FUNC::EQUAL: return vk::CompareOp::eEqual; case latte::REF_FUNC::LESS_EQUAL: return vk::CompareOp::eLessOrEqual; case latte::REF_FUNC::GREATER: return vk::CompareOp::eGreater; case latte::REF_FUNC::NOT_EQUAL: return vk::CompareOp::eNotEqual; case latte::REF_FUNC::GREATER_EQUAL: return vk::CompareOp::eGreaterOrEqual; case latte::REF_FUNC::ALWAYS: return vk::CompareOp::eAlways; } decaf_abort(fmt::format("Unexpected compare op {}", func)); } vk::StencilOp getVkStencilOp(latte::DB_STENCIL_FUNC func) { switch (func) { case latte::DB_STENCIL_FUNC::KEEP: return vk::StencilOp::eKeep; case latte::DB_STENCIL_FUNC::ZERO: return vk::StencilOp::eZero; case latte::DB_STENCIL_FUNC::REPLACE: return vk::StencilOp::eReplace; case latte::DB_STENCIL_FUNC::INCR_CLAMP: return vk::StencilOp::eIncrementAndClamp; case latte::DB_STENCIL_FUNC::DECR_CLAMP: return vk::StencilOp::eDecrementAndClamp; case latte::DB_STENCIL_FUNC::INVERT: return vk::StencilOp::eInvert; case latte::DB_STENCIL_FUNC::INCR_WRAP: return vk::StencilOp::eIncrementAndWrap; case latte::DB_STENCIL_FUNC::DECR_WRAP: return vk::StencilOp::eDecrementAndWrap; } decaf_abort(fmt::format("Unexpected stencil op {}", func)); } vk::BlendFactor getVkBlendFactor(latte::CB_BLEND_FUNC func) { switch (func) { case latte::CB_BLEND_FUNC::ZERO: return vk::BlendFactor::eZero; case latte::CB_BLEND_FUNC::ONE: return vk::BlendFactor::eOne; case latte::CB_BLEND_FUNC::SRC_COLOR: return vk::BlendFactor::eSrcColor; case latte::CB_BLEND_FUNC::ONE_MINUS_SRC_COLOR: return vk::BlendFactor::eOneMinusSrcColor; case latte::CB_BLEND_FUNC::SRC_ALPHA: return vk::BlendFactor::eSrcAlpha; case latte::CB_BLEND_FUNC::ONE_MINUS_SRC_ALPHA: return vk::BlendFactor::eOneMinusSrcAlpha; case latte::CB_BLEND_FUNC::DST_ALPHA: return vk::BlendFactor::eDstAlpha; case latte::CB_BLEND_FUNC::ONE_MINUS_DST_ALPHA: return vk::BlendFactor::eOneMinusDstAlpha; case latte::CB_BLEND_FUNC::DST_COLOR: return vk::BlendFactor::eDstColor; case latte::CB_BLEND_FUNC::ONE_MINUS_DST_COLOR: return vk::BlendFactor::eOneMinusDstColor; case latte::CB_BLEND_FUNC::SRC_ALPHA_SATURATE: return vk::BlendFactor::eSrcAlphaSaturate; case latte::CB_BLEND_FUNC::BOTH_SRC_ALPHA: decaf_abort("Unsupported BOTH_SRC_ALPHA blend function"); case latte::CB_BLEND_FUNC::BOTH_INV_SRC_ALPHA: decaf_abort("Unsupported BOTH_INV_SRC_ALPHA blend function"); case latte::CB_BLEND_FUNC::CONSTANT_COLOR: return vk::BlendFactor::eConstantColor; case latte::CB_BLEND_FUNC::ONE_MINUS_CONSTANT_COLOR: return vk::BlendFactor::eOneMinusConstantColor; case latte::CB_BLEND_FUNC::SRC1_COLOR: return vk::BlendFactor::eSrc1Color; case latte::CB_BLEND_FUNC::ONE_MINUS_SRC1_COLOR: return vk::BlendFactor::eOneMinusSrc1Color; case latte::CB_BLEND_FUNC::SRC1_ALPHA: return vk::BlendFactor::eSrc1Alpha; case latte::CB_BLEND_FUNC::ONE_MINUS_SRC1_ALPHA: return vk::BlendFactor::eOneMinusSrc1Alpha; case latte::CB_BLEND_FUNC::CONSTANT_ALPHA: return vk::BlendFactor::eConstantAlpha; case latte::CB_BLEND_FUNC::ONE_MINUS_CONSTANT_ALPHA: return vk::BlendFactor::eOneMinusConstantAlpha; } decaf_abort(fmt::format("Unexpected blend factor {}", func)); } vk::BlendOp getVkBlendOp(latte::CB_COMB_FUNC func) { switch (func) { case latte::CB_COMB_FUNC::DST_PLUS_SRC: return vk::BlendOp::eAdd; case latte::CB_COMB_FUNC::SRC_MINUS_DST: return vk::BlendOp::eSubtract; case latte::CB_COMB_FUNC::MIN_DST_SRC: return vk::BlendOp::eMin; case latte::CB_COMB_FUNC::MAX_DST_SRC: return vk::BlendOp::eMax; case latte::CB_COMB_FUNC::DST_MINUS_SRC: return vk::BlendOp::eReverseSubtract; } decaf_abort(fmt::format("Unexpected blend op {}", func)); } vk::SampleCountFlags getVkSampleCount(uint32_t samples) { switch (samples) { case 1: return vk::SampleCountFlagBits::e1; case 2: return vk::SampleCountFlagBits::e2; case 4: return vk::SampleCountFlagBits::e4; case 8: return vk::SampleCountFlagBits::e8; case 16: return vk::SampleCountFlagBits::e16; case 32: return vk::SampleCountFlagBits::e32; case 64: return vk::SampleCountFlagBits::e64; } decaf_abort("Unexpected surface samples value"); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_utils.h ================================================ #pragma once #ifdef DECAF_VULKAN #include "latte/latte_formats.h" #include <common/vulkan_hpp.h> namespace vulkan { enum SurfaceFormatUsage : uint32_t { TEXTURE = (1 << 0), COLOR = (1 << 1), DEPTH = (1 << 2), STENCIL = (1 << 3), T = TEXTURE, TC = TEXTURE | COLOR, TD = TEXTURE | DEPTH, TCD = TEXTURE | COLOR | DEPTH, TDS = TEXTURE | DEPTH | STENCIL, D = DEPTH, DS = DEPTH | STENCIL }; vk::Format getVkSurfaceFormat(latte::SurfaceFormat format, latte::SQ_TILE_TYPE tileType); vk::ComponentSwizzle getVkComponentSwizzle(latte::SQ_SEL sel); vk::BorderColor getVkBorderColor(latte::SQ_TEX_BORDER_COLOR color); vk::Filter getVkXyTextureFilter(latte::SQ_TEX_XY_FILTER filter); vk::SamplerMipmapMode getVkZTextureFilter(latte::SQ_TEX_Z_FILTER filter); vk::SamplerAddressMode getVkTextureAddressMode(latte::SQ_TEX_CLAMP clamp); bool getVkAnisotropyEnabled(latte::SQ_TEX_ANISO aniso); float getVkMaxAnisotropy(latte::SQ_TEX_ANISO aniso); bool getVkCompareOpEnabled(latte::REF_FUNC func); vk::CompareOp getVkCompareOp(latte::REF_FUNC func); vk::StencilOp getVkStencilOp(latte::DB_STENCIL_FUNC func); vk::BlendFactor getVkBlendFactor(latte::CB_BLEND_FUNC func); vk::BlendOp getVkBlendOp(latte::CB_COMB_FUNC func); SurfaceFormatUsage getVkSurfaceFormatUsage(latte::SurfaceFormat format); vk::SampleCountFlags getVkSampleCount(uint32_t samples); } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_validate.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" #include "vulkan_utils.h" #include <common/log.h> namespace vulkan { auto supportedColorFormats = { latte::SurfaceFormat::R8Unorm, latte::SurfaceFormat::R8Uint, latte::SurfaceFormat::R8Snorm, latte::SurfaceFormat::R8Sint, latte::SurfaceFormat::R4G4Unorm, latte::SurfaceFormat::R16Unorm, latte::SurfaceFormat::R16Uint, latte::SurfaceFormat::R16Snorm, latte::SurfaceFormat::R16Sint, latte::SurfaceFormat::R16Float, latte::SurfaceFormat::R8G8Unorm, latte::SurfaceFormat::R8G8Uint, latte::SurfaceFormat::R8G8Snorm, latte::SurfaceFormat::R8G8Sint, latte::SurfaceFormat::R5G6B5Unorm, latte::SurfaceFormat::R5G5B5A1Unorm, latte::SurfaceFormat::R4G4B4A4Unorm, latte::SurfaceFormat::A1B5G5R5Unorm, latte::SurfaceFormat::R32Uint, latte::SurfaceFormat::R32Sint, latte::SurfaceFormat::R32Float, latte::SurfaceFormat::R16G16Unorm, latte::SurfaceFormat::R16G16Uint, latte::SurfaceFormat::R16G16Snorm, latte::SurfaceFormat::R16G16Sint, latte::SurfaceFormat::R16G16Float, latte::SurfaceFormat::D24UnormS8Uint, latte::SurfaceFormat::X24G8Uint, latte::SurfaceFormat::R11G11B10Float, latte::SurfaceFormat::R10G10B10A2Unorm, latte::SurfaceFormat::R10G10B10A2Uint, latte::SurfaceFormat::R10G10B10A2Snorm, latte::SurfaceFormat::R10G10B10A2Sint, latte::SurfaceFormat::R8G8B8A8Unorm, latte::SurfaceFormat::R8G8B8A8Uint, latte::SurfaceFormat::R8G8B8A8Snorm, latte::SurfaceFormat::R8G8B8A8Sint, latte::SurfaceFormat::R8G8B8A8Srgb, latte::SurfaceFormat::A2B10G10R10Unorm, latte::SurfaceFormat::A2B10G10R10Uint, latte::SurfaceFormat::D32FloatS8UintX24, latte::SurfaceFormat::D32G8UintX24, latte::SurfaceFormat::R32G32Uint, latte::SurfaceFormat::R32G32Sint, latte::SurfaceFormat::R32G32Float, latte::SurfaceFormat::R16G16B16A16Unorm, latte::SurfaceFormat::R16G16B16A16Uint, latte::SurfaceFormat::R16G16B16A16Snorm, latte::SurfaceFormat::R16G16B16A16Sint, latte::SurfaceFormat::R16G16B16A16Float, latte::SurfaceFormat::R32G32B32A32Uint, latte::SurfaceFormat::R32G32B32A32Sint, latte::SurfaceFormat::R32G32B32A32Float, latte::SurfaceFormat::BC1Unorm, latte::SurfaceFormat::BC1Srgb, latte::SurfaceFormat::BC2Unorm, latte::SurfaceFormat::BC2Srgb, latte::SurfaceFormat::BC3Unorm, latte::SurfaceFormat::BC3Srgb, latte::SurfaceFormat::BC4Unorm, latte::SurfaceFormat::BC4Snorm, latte::SurfaceFormat::BC5Unorm, latte::SurfaceFormat::BC5Snorm, //latte::SurfaceFormat::NV12, }; auto supportedDepthFormats = { latte::SurfaceFormat::R16Unorm, latte::SurfaceFormat::R32Float, latte::SurfaceFormat::D24UnormS8Uint, latte::SurfaceFormat::X24G8Uint, latte::SurfaceFormat::D32FloatS8UintX24, latte::SurfaceFormat::D32G8UintX24, }; void Driver::validateDevice() { // TODO: Decide what is a hard requirement and what is optional auto checkFormat = [&](latte::SurfaceFormat format, latte::SQ_TILE_TYPE tileType) { auto hostFormat = getVkSurfaceFormat(format, tileType); auto formatUsages = getVkSurfaceFormatUsage(format); auto formatProps = mPhysDevice.getFormatProperties(hostFormat); if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eTransferDst)) { gLog->warn("Surface format {:03x}[{}]({}) does not support TransferDst feature", format, tileType, vk::to_string(hostFormat)); } if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eTransferSrc)) { gLog->warn("Surface format {:03x}[{}]({}) does not support TransferSrc feature", format, tileType, vk::to_string(hostFormat)); } if (tileType != latte::SQ_TILE_TYPE::DEPTH) { if (formatUsages & SurfaceFormatUsage::TEXTURE) { if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage)) { gLog->warn("Surface format {:03x}[{}]({}) does not support SampledImage feature", format, tileType, vk::to_string(hostFormat)); } } if (formatUsages & SurfaceFormatUsage::COLOR) { if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachment)) { gLog->warn("Surface format {:03x}[{}]({}) does not support ColorAttachment feature", format, tileType, vk::to_string(hostFormat)); } } } else { if (formatUsages & (SurfaceFormatUsage::DEPTH | SurfaceFormatUsage::STENCIL)) { if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment)) { gLog->warn("Surface format {:03x}[{}]({}) does not support DepthStencilAttachement feature", format, tileType, vk::to_string(hostFormat)); } } } }; for (auto format : supportedColorFormats) { checkFormat(format, latte::SQ_TILE_TYPE::DEFAULT); } for (auto format : supportedDepthFormats) { checkFormat(format, latte::SQ_TILE_TYPE::DEPTH); } } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/src/vulkan/vulkan_viewscissor.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_driver.h" namespace vulkan { bool Driver::checkCurrentViewportAndScissor() { // GPU7 actually supports many viewports and many scissors, but it // seems that CafeOS itself only supports a single one. // ------------------------------------------------------------ // Viewport // ------------------------------------------------------------ auto pa_cl_vport_xscale = getRegister<latte::PA_CL_VPORT_XSCALE_N>(latte::Register::PA_CL_VPORT_XSCALE_0); auto pa_cl_vport_yscale = getRegister<latte::PA_CL_VPORT_YSCALE_N>(latte::Register::PA_CL_VPORT_YSCALE_0); auto pa_cl_vport_zscale = getRegister<latte::PA_CL_VPORT_ZSCALE_N>(latte::Register::PA_CL_VPORT_ZSCALE_0); auto pa_cl_vport_xoffset = getRegister<latte::PA_CL_VPORT_XOFFSET_N>(latte::Register::PA_CL_VPORT_XOFFSET_0); auto pa_cl_vport_yoffset = getRegister<latte::PA_CL_VPORT_YOFFSET_N>(latte::Register::PA_CL_VPORT_YOFFSET_0); auto pa_cl_vport_zoffset = getRegister<latte::PA_CL_VPORT_ZOFFSET_N>(latte::Register::PA_CL_VPORT_ZOFFSET_0); auto pa_sc_vport_zmin = getRegister<latte::PA_SC_VPORT_ZMIN_N>(latte::Register::PA_SC_VPORT_ZMIN_0); auto pa_sc_vport_zmax = getRegister<latte::PA_SC_VPORT_ZMAX_N>(latte::Register::PA_SC_VPORT_ZMAX_0); auto pa_cl_vte_cntl = getRegister<latte::PA_CL_VTE_CNTL>(latte::Register::PA_CL_VTE_CNTL); auto pa_cl_clip_cntl = getRegister<latte::PA_CL_CLIP_CNTL>(latte::Register::PA_CL_CLIP_CNTL); auto raWidth = static_cast<float>(mCurrentDraw->framebuffer->renderArea.width); auto raHeight = static_cast<float>(mCurrentDraw->framebuffer->renderArea.height); // NOTE: Our shaders which output positions understand that if the // xoffset/yoffset/xscale/yscale are disabled, that we need to // pre-transform the render-area down to -1to1 on X and Y. This // means rather than using a disabled viewport here, we need to // expand this back out. The reason to do this is that there is // NDC-space clipping occuring in Vulkan which would prevent this // from working as it does on GPU7. float vportOX, vportOY, vportSX, vportSY; if (pa_cl_vte_cntl.VPORT_X_OFFSET_ENA()) { vportOX = pa_cl_vport_xoffset.VPORT_XOFFSET(); } else { vportOX = raWidth / 2; } if (pa_cl_vte_cntl.VPORT_Y_OFFSET_ENA()) { vportOY = pa_cl_vport_yoffset.VPORT_YOFFSET(); } else { vportOY = raHeight / 2; } if (pa_cl_vte_cntl.VPORT_X_SCALE_ENA()) { vportSX = pa_cl_vport_xscale.VPORT_XSCALE(); } else { vportSX = raWidth / 2; } if (pa_cl_vte_cntl.VPORT_Y_SCALE_ENA()) { vportSY = pa_cl_vport_yscale.VPORT_YSCALE(); } else { vportSY = raHeight / 2; } vk::Viewport viewport; viewport.x = vportOX - vportSX; viewport.y = vportOY - vportSY; viewport.width = vportSX * 2; viewport.height = vportSY * 2; // the view port is 'flipped by default' viewport.y = viewport.height + viewport.y; viewport.height = -viewport.height; // TODO: Investigate whether we should be using ZOFFSET/ZSCALE to calculate these? viewport.minDepth = pa_sc_vport_zmin.VPORT_ZMIN(); viewport.maxDepth = pa_sc_vport_zmax.VPORT_ZMAX(); mCurrentDraw->viewport = viewport; // Set up some stuff used by the shaders ShaderViewportData shaderViewport; // These are not handled as I don't believe we actually scale/offset by the viewport... //pa_cl_vte_cntl.VPORT_Z_OFFSET_ENA(); //pa_cl_vte_cntl.VPORT_Z_SCALE_ENA(); // TODO: Implement these //pa_cl_vte_cntl.VTX_XY_FMT(); //pa_cl_vte_cntl.VTX_Z_FMT(); //pa_cl_vte_cntl.VTX_W0_FMT(); auto screenSizeX = viewport.width; auto screenSizeY = -viewport.height; if (pa_cl_vte_cntl.VPORT_X_OFFSET_ENA()) { shaderViewport.xAdd = 0.0f; } else { shaderViewport.xAdd = -1.0f; } if (pa_cl_vte_cntl.VPORT_X_SCALE_ENA()) { shaderViewport.xMul = 1.0f; } else { shaderViewport.xMul = 2.0f / screenSizeX; } if (pa_cl_vte_cntl.VPORT_Y_OFFSET_ENA()) { shaderViewport.yAdd = 0.0f; } else { shaderViewport.yAdd = -1.0f; } if (pa_cl_vte_cntl.VPORT_Y_SCALE_ENA()) { shaderViewport.yMul = 1.0f; } else { shaderViewport.yMul = 2.0f / screenSizeY; } if (!pa_cl_clip_cntl.DX_CLIP_SPACE_DEF()) { // map gl(-1 to 1) onto vk(0 to 1) shaderViewport.zAdd = 1.0f; // Add W shaderViewport.zMul = 0.5f; // * 0.5 } else { // maintain 0 to 1 shaderViewport.zAdd = 0.0f; // Add 0 shaderViewport.zMul = 1.0f; // * 1.0 } mCurrentDraw->shaderViewportData = shaderViewport; // ------------------------------------------------------------ // Scissoring // ------------------------------------------------------------ auto pa_sc_generic_scissor_tl = getRegister<latte::PA_SC_GENERIC_SCISSOR_TL>(latte::Register::PA_SC_GENERIC_SCISSOR_TL); auto pa_sc_generic_scissor_br = getRegister<latte::PA_SC_GENERIC_SCISSOR_BR>(latte::Register::PA_SC_GENERIC_SCISSOR_BR); vk::Rect2D scissor; scissor.offset.x = pa_sc_generic_scissor_tl.TL_X(); scissor.offset.y = pa_sc_generic_scissor_tl.TL_Y(); scissor.extent.width = pa_sc_generic_scissor_br.BR_X() - scissor.offset.x; scissor.extent.height = pa_sc_generic_scissor_br.BR_Y() - scissor.offset.y; mCurrentDraw->scissor = scissor; return true; } void Driver::bindViewportAndScissor() { mActiveCommandBuffer.setViewport(0, { mCurrentDraw->viewport }); mActiveCommandBuffer.setScissor(0, { mCurrentDraw->scissor }); } } // namespace vulkan #endif // ifdef DECAF_VULKAN ================================================ FILE: src/libgpu/vulkan_shaders/gpu7_tiling.comp.glsl ================================================ #version 450 layout(constant_id = 0) const bool IsUntiling = true; layout(constant_id = 1) const uint MicroTileThickness = 1; layout(constant_id = 2) const uint MacroTileWidth = 1; layout(constant_id = 3) const uint MacroTileHeight = 1; layout(constant_id = 4) const bool IsMacro3X = false; layout(constant_id = 5) const bool IsBankSwapped = false; layout(constant_id = 6) const uint BitsPerElement = 8; layout(constant_id = 7) const bool IsDepth = false; #ifndef DECAF_MVK_COMPAT layout(constant_id = 8) const uint SubGroupSize = 32; #endif // Specify our grouping setup layout(local_size_x_id = 8, local_size_y = 1, local_size_z = 1) in; // Information about the GPU tiling itself. const uint MicroTileWidth = 8; const uint MicroTileHeight = 8; const uint NumPipes = 2; const uint NumBanks = 4; const uint PipeInterleaveBytes = 256; const uint NumGroupBits = 8; const uint NumPipeBits = 1; const uint NumBankBits = 2; const uint GroupMask = ((1 << NumGroupBits) - 1); // Setup some convenience information based on our constants const bool IsMacroTiling = (MacroTileWidth > 1 || MacroTileHeight > 1); const uint BytesPerElement = BitsPerElement / 8; const uint MicroTileBytes = MicroTileWidth * MicroTileHeight * MicroTileThickness * BytesPerElement; const uint MacroTileBytes = MacroTileWidth * MacroTileHeight * MicroTileBytes; layout(push_constant) uniform Parameters { uint firstSliceIndex; uint maxTiles; // Micro tiling parameters uint numTilesPerRow; uint numTilesPerSlice; uint thinMicroTileBytes; uint thickSliceBytes; // Macro tiling parameters uint bankSwizzle; uint pipeSwizzle; uint bankSwapWidth; } params; // Set up the shader inputs layout(std430, binding = 0) buffer tiledBuffer4 { uint tiled4[]; }; layout(std430, binding = 1) buffer untiledBuffer4 { uint untiled4[]; }; void copyElems4(uint untiledOffset, uint tiledOffset, uint numElems) { if (IsUntiling) { for (uint i = 0; i < numElems; ++i) { untiled4[(untiledOffset / 4) + i] = tiled4[(tiledOffset / 4) + i]; } } else { for (uint i = 0; i < numElems; ++i) { tiled4[(tiledOffset / 4) + i] = untiled4[(untiledOffset / 4) + i]; } } } #ifndef DECAF_MVK_COMPAT layout(std430, binding = 0) buffer tiledBuffer8 { uvec2 tiled8[]; }; layout(std430, binding = 1) buffer untiledBuffer8 { uvec2 untiled8[]; }; layout(std430, binding = 0) buffer tiledBuffer16 { uvec4 tiled16[]; }; layout(std430, binding = 1) buffer untiledBuffer16 { uvec4 untiled16[]; }; void copyElems8(uint untiledOffset, uint tiledOffset, uint numElems) { if (IsUntiling) { for (uint i = 0; i < numElems; ++i) { untiled8[(untiledOffset / 8) + i] = tiled8[(tiledOffset / 8) + i]; } } else { for (uint i = 0; i < numElems; ++i) { tiled8[(tiledOffset / 8) + i] = untiled8[(untiledOffset / 8) + i]; } } } void copyElems16(uint untiledOffset, uint tiledOffset, uint numElems) { if (IsUntiling) { for (uint i = 0; i < numElems; ++i) { untiled16[(untiledOffset / 16)] = tiled16[(tiledOffset / 16)]; } } else { for (uint i = 0; i < numElems; ++i) { tiled16[(tiledOffset / 16)] = untiled16[(untiledOffset / 16)]; } } } #else void copyElems8(uint untiledOffset, uint tiledOffset, uint numElems) { copyElems4(untiledOffset, tiledOffset, numElems * 2); } void copyElems16(uint untiledOffset, uint tiledOffset, uint numElems) { copyElems4(untiledOffset, tiledOffset, numElems * 4); } #endif void retileMicro8(uint tiledOffset, uint untiledOffset, uint untiledStride) { const uint tiledStride = MicroTileWidth; const uint rowElems = MicroTileWidth / 8; for (uint y = 0; y < MicroTileHeight; y += 4) { const uint untiledRow0 = untiledOffset + 0 * untiledStride; const uint untiledRow1 = untiledOffset + 1 * untiledStride; const uint untiledRow2 = untiledOffset + 2 * untiledStride; const uint untiledRow3 = untiledOffset + 3 * untiledStride; const uint tiledRow0 = tiledOffset + 0 * tiledStride; const uint tiledRow1 = tiledOffset + 1 * tiledStride; const uint tiledRow2 = tiledOffset + 2 * tiledStride; const uint tiledRow3 = tiledOffset + 3 * tiledStride; copyElems8(untiledRow0, tiledRow0, rowElems); copyElems8(untiledRow1, tiledRow2, rowElems); copyElems8(untiledRow2, tiledRow1, rowElems); copyElems8(untiledRow3, tiledRow3, rowElems); untiledOffset += 4 * untiledStride; tiledOffset += 4 * tiledStride; } } void retileMicro16(uint tiledOffset, uint untiledOffset, uint untiledStride) { const uint tiledStride = MicroTileWidth * 2; const uint rowElems = MicroTileWidth * 2 / 16; for (uint y = 0; y < MicroTileHeight; ++y) { copyElems16(untiledOffset, tiledOffset, rowElems); untiledOffset += untiledStride; tiledOffset += tiledStride; } } void retileMicro32(uint tiledOffset, uint untiledOffset, uint untiledStride) { const uint tiledStride = MicroTileWidth * 4; const uint groupElems = 4 * 4 / 16; for (uint y = 0; y < MicroTileHeight; y += 2) { const uint untiledRow1 = untiledOffset + 0 * untiledStride; const uint untiledRow2 = untiledOffset + 1 * untiledStride; const uint tiledRow1 = tiledOffset + 0 * tiledStride; const uint tiledRow2 = tiledOffset + 1 * tiledStride; copyElems16(untiledRow1 + 0, tiledRow1 + 0, groupElems); copyElems16(untiledRow1 + 16, tiledRow2 + 0, groupElems); copyElems16(untiledRow2 + 0, tiledRow1 + 16, groupElems); copyElems16(untiledRow2 + 16, tiledRow2 + 16, groupElems); tiledOffset += tiledStride * 2; untiledOffset += untiledStride * 2; } } void retileMicro64(uint tiledOffset, uint untiledOffset, uint untiledStride) { const uint tiledStride = MicroTileWidth * 8; const uint groupElems = 2 * (64 / 8) / 16; // This will automatically DCE'd by compiler if the below ifdef is not used. const uint nextGroupOffset = tiledOffset + (0x100 << (NumBankBits + NumPipeBits)); for (uint y = 0; y < MicroTileHeight; y += 2) { if (IsMacroTiling && y == 4) { tiledOffset = nextGroupOffset; } const uint untiledRow1 = untiledOffset + 0 * untiledStride; const uint untiledRow2 = untiledOffset + 1 * untiledStride; const uint tiledRow1 = tiledOffset + 0 * tiledStride; const uint tiledRow2 = tiledOffset + 1 * tiledStride; copyElems16(untiledRow1 + 0, tiledRow1 + 0, groupElems); copyElems16(untiledRow2 + 0, tiledRow1 + 16, groupElems); copyElems16(untiledRow1 + 16, tiledRow1 + 32, groupElems); copyElems16(untiledRow2 + 16, tiledRow1 + 48, groupElems); copyElems16(untiledRow1 + 32, tiledRow2 + 0, groupElems); copyElems16(untiledRow2 + 32, tiledRow2 + 16, groupElems); copyElems16(untiledRow1 + 48, tiledRow2 + 32, groupElems); copyElems16(untiledRow2 + 48, tiledRow2 + 48, groupElems); tiledOffset += tiledStride * 2; untiledOffset += untiledStride * 2; } } void retileMicro128(uint tiledOffset, uint untiledOffset, uint untiledStride) { const uint tiledStride = MicroTileWidth * 16; const uint groupBytes = 16; const uint groupElems = 16 / 16; for (uint y = 0; y < MicroTileHeight; y += 2) { const uint untiledRow1 = untiledOffset + 0 * untiledStride; const uint untiledRow2 = untiledOffset + 1 * untiledStride; const uint tiledRow1 = tiledOffset + 0 * tiledStride; const uint tiledRow2 = tiledOffset + 1 * tiledStride; copyElems16(untiledRow1 + 0 * groupBytes, tiledRow1 + 0 * groupBytes, groupElems); copyElems16(untiledRow1 + 1 * groupBytes, tiledRow1 + 2 * groupBytes, groupElems); copyElems16(untiledRow2 + 0 * groupBytes, tiledRow1 + 1 * groupBytes, groupElems); copyElems16(untiledRow2 + 1 * groupBytes, tiledRow1 + 3 * groupBytes, groupElems); copyElems16(untiledRow1 + 2 * groupBytes, tiledRow1 + 4 * groupBytes, groupElems); copyElems16(untiledRow1 + 3 * groupBytes, tiledRow1 + 6 * groupBytes, groupElems); copyElems16(untiledRow2 + 2 * groupBytes, tiledRow1 + 5 * groupBytes, groupElems); copyElems16(untiledRow2 + 3 * groupBytes, tiledRow1 + 7 * groupBytes, groupElems); copyElems16(untiledRow1 + 4 * groupBytes, tiledRow2 + 0 * groupBytes, groupElems); copyElems16(untiledRow1 + 5 * groupBytes, tiledRow2 + 2 * groupBytes, groupElems); copyElems16(untiledRow2 + 4 * groupBytes, tiledRow2 + 1 * groupBytes, groupElems); copyElems16(untiledRow2 + 5 * groupBytes, tiledRow2 + 3 * groupBytes, groupElems); copyElems16(untiledRow1 + 6 * groupBytes, tiledRow2 + 4 * groupBytes, groupElems); copyElems16(untiledRow1 + 7 * groupBytes, tiledRow2 + 6 * groupBytes, groupElems); copyElems16(untiledRow2 + 6 * groupBytes, tiledRow2 + 5 * groupBytes, groupElems); copyElems16(untiledRow2 + 7 * groupBytes, tiledRow2 + 7 * groupBytes, groupElems); if (IsMacroTiling) { tiledOffset += 0x100 << (NumBankBits + NumPipeBits); } else { tiledOffset += tiledStride * 2; } untiledOffset += untiledStride * 2; } } void copyDepthXYGroup(uint tiledOffset, uint untiledOffset, uint untiledStride, uint tX, uint tY, uint uX, uint uY) { const uint groupBytes = 2 * BytesPerElement; const uint tiledStride = MicroTileWidth * BytesPerElement; copyElems4(untiledOffset + uY * untiledStride + uX * groupBytes, tiledOffset + tY * tiledStride + tX * groupBytes, groupBytes); } void retileMicroDepth(uint tiledOffset, uint untiledOffset, uint untiledStride) { for (uint y = 0; y < MicroTileHeight; y += 4) { copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 0, 0, y + 0); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 0, 0, y + 1); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 0, 1, y + 0); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 0, 1, y + 1); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 1, 0, y + 2); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 1, 0, y + 3); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 1, 1, y + 2); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 1, 1, y + 3); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 2, 2, y + 0); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 2, 2, y + 1); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 2, 3, y + 0); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 2, 3, y + 1); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 3, 2, y + 2); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 3, 2, y + 3); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 3, 3, y + 2); copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 3, 3, y + 3); } } void mainMicroTiling(uint tileIndex) { const uint thinSliceBytes = params.thickSliceBytes / MicroTileThickness; const uint untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement; const uint thickMicroTileBytes = params.thinMicroTileBytes * MicroTileThickness; const uint dispatchSliceIndex = tileIndex / params.numTilesPerSlice; const uint sliceTileIndex = tileIndex % params.numTilesPerSlice; // Find the global slice index we are currently at. const uint srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex; // We need to identify where inside the current thick slice we are. const uint localSliceIndex = srcSliceIndex % MicroTileThickness; // Calculate the offset to our untiled data starting from the thick slice const uint srcTileY = sliceTileIndex / params.numTilesPerRow; const uint srcTileX = sliceTileIndex % params.numTilesPerRow; uint untiledOffset = (localSliceIndex * thinSliceBytes) + (srcTileX * MicroTileWidth * BytesPerElement) + (srcTileY * params.numTilesPerRow * params.thinMicroTileBytes); // Calculate the offset to our tiled data starting from the thick slice uint tiledOffset = (localSliceIndex * params.thinMicroTileBytes) + sliceTileIndex * thickMicroTileBytes; // In the case that we are using thick micro tiles, we need to advance our // offsets to the current thick slice boundary that we are at. const uint firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness; const uint thickSliceIndex = srcSliceIndex / MicroTileThickness; const uint thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes; tiledOffset += thickSliceOffset; untiledOffset += thickSliceOffset; // The untiled pointers are offset forward by the local slice index already, // we need to back it up since our calculations above consider it. const uint firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness; untiledOffset -= firstThinSliceIndex * thinSliceBytes; if (IsDepth) { retileMicroDepth(tiledOffset, untiledOffset, untiledStride); } else { if (BitsPerElement == 8) { retileMicro8(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 16) { retileMicro16(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 32) { retileMicro32(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 64) { retileMicro64(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 128) { retileMicro128(tiledOffset, untiledOffset, untiledStride); } } } void mainMacroTiling(uint tileIndex) { const uint thinSliceBytes = params.thickSliceBytes / MicroTileThickness; const uint untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement; const uint dispatchSliceIndex = tileIndex / params.numTilesPerSlice; const uint sliceTileIndex = tileIndex % params.numTilesPerSlice; // Find the global slice index we are currently at. const uint srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex; // We need to identify where inside the current thick slice we are. const uint localSliceIndex = srcSliceIndex % MicroTileThickness; // Calculate the thickSliceIndex const uint thickSliceIndex = srcSliceIndex / MicroTileThickness; // Calculate our tile positions const uint microTilesPerMacro = MacroTileWidth * MacroTileHeight; const uint macroTilesPerRow = params.numTilesPerRow / MacroTileWidth; const uint microTilesPerMacroRow = microTilesPerMacro * macroTilesPerRow; const uint srcMacroTileY = sliceTileIndex / microTilesPerMacroRow; const uint macroRowTileIndex = sliceTileIndex % microTilesPerMacroRow; const uint srcMacroTileX = macroRowTileIndex / microTilesPerMacro; const uint microTileIndex = macroRowTileIndex % microTilesPerMacro; const uint srcMicroTileY = microTileIndex / MacroTileWidth; const uint srcMicroTileX = microTileIndex % MacroTileWidth; const uint srcTileX = srcMacroTileX * MacroTileWidth + srcMicroTileX; const uint srcTileY = srcMacroTileY * MacroTileHeight + srcMicroTileY; // Figure out what our untiled offset shall be uint untiledOffset = (localSliceIndex * thinSliceBytes) + (srcTileX * MicroTileWidth * BytesPerElement) + (srcTileY * MicroTileHeight * untiledStride); // Calculate the offset to our untiled data starting from the thick slice const uint macroTileIndex = (srcMacroTileY * macroTilesPerRow) + srcMacroTileX; const uint macroTileOffset = macroTileIndex * MacroTileBytes; const uint tiledBaseOffset = (macroTileOffset >> (NumBankBits + NumPipeBits)) + (localSliceIndex * params.thinMicroTileBytes); const uint offsetHigh = (tiledBaseOffset & ~GroupMask) << (NumBankBits + NumPipeBits); const uint offsetLow = tiledBaseOffset & GroupMask; // Calculate our bank/pipe/sample rotations and swaps uint bankSliceRotation = 0; uint pipeSliceRotation = 0; if (!IsMacro3X) { // 2_ format bankSliceRotation = ((NumBanks >> 1) - 1)* thickSliceIndex; } else { // 3_ format bankSliceRotation = thickSliceIndex / NumPipes; pipeSliceRotation = thickSliceIndex; } uint bankSwapRotation = 0; if (IsBankSwapped) { const uint bankSwapOrder[] = { 0, 1, 3, 2 }; const uint swapIndex = ((srcMacroTileX * MicroTileWidth * MacroTileWidth) / params.bankSwapWidth); bankSwapRotation = bankSwapOrder[swapIndex % NumBanks]; } uint bank = 0; bank |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 2) & 1); bank |= (((srcTileX >> 1) & 1) ^ ((srcTileY >> 1) & 1)) << 1; bank ^= (params.bankSwizzle + bankSliceRotation) & (NumBanks - 1); bank ^= bankSwapRotation; uint pipe = 0; pipe |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 0) & 1); pipe ^= (params.pipeSwizzle + pipeSliceRotation) & (NumPipes - 1); uint tiledOffset = (bank << (NumGroupBits + NumPipeBits)) | (pipe << NumGroupBits) | offsetLow | offsetHigh; // In the case that we are using thick micro tiles, we need to advance our // offsets to the current thick slice boundary that we are at. const uint firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness; const uint thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes; tiledOffset += thickSliceOffset; untiledOffset += thickSliceOffset; // The untiled pointers are offset forward by the local slice index already, // we need to back it up since our calculations above consider it. const uint firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness; untiledOffset -= firstThinSliceIndex * thinSliceBytes; if (IsDepth) { retileMicroDepth(tiledOffset, untiledOffset, untiledStride); } else { if (BitsPerElement == 8) { retileMicro8(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 16) { retileMicro16(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 32) { retileMicro32(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 64) { retileMicro64(tiledOffset, untiledOffset, untiledStride); } else if (BitsPerElement == 128) { retileMicro128(tiledOffset, untiledOffset, untiledStride); } } } void main() { const uint tileIndex = gl_GlobalInvocationID.x; if (tileIndex >= params.maxTiles) { return; } if (IsMacroTiling) { mainMacroTiling(tileIndex); } else { mainMicroTiling(tileIndex); } } ================================================ FILE: tests/CMakeLists.txt ================================================ project(tests) include_directories("../src") add_subdirectory("cpu") add_subdirectory("gpu") ================================================ FILE: tests/cpu/CMakeLists.txt ================================================ project(tests-cpu) add_subdirectory("libcpu") add_subdirectory("runner-achurch") add_subdirectory("runner-generated") ================================================ FILE: tests/cpu/fuzz-compare/fuzztests.cpp ================================================ #include <algorithm> #include <random> #include <string> #include "fuzztests.h" #include "common/bitutils.h" #include "common/log.h" #include "libcpu/src/interpreter/interpreter.h" #include "libcpu/src/interpreter/interpreter_insreg.h" #include "libcpu/src/jit/jit.h" #include "libcpu/state.h" #include "libcpu/src/utils.h" #include "libcpu/mem.h" #include "libcpu/trace.h" #include "libcpu/espresso/espresso_instructionset.h" #include "libcpu/espresso/espresso_spr.h" using namespace espresso; template<size_t SIZE, class T> inline size_t array_size(T (&arr)[SIZE]) { return SIZE; } struct InstructionFuzzData { uint32_t baseInstr; std::vector<InstructionField> allFields; }; static const uint32_t instructionBase = mem::MEM2Base; static const uint32_t dataBase = instructionBase + 0x01000000; std::vector<InstructionFuzzData> instructionFuzzData; bool buildFuzzData(InstructionID instrId, InstructionFuzzData &fuzzData) { if (instrId == InstructionID::Invalid) { return true; } auto data = findInstructionInfo(instrId); // Verify JIT and Interpreter have everything registered... bool hasInterpHandler = cpu::interpreter::hasInstruction(static_cast<InstructionID>(instrId)); bool hasJitHandler = cpu::jit::hasInstruction(static_cast<InstructionID>(instrId)); if ((hasInterpHandler ^ hasJitHandler) != 0) { if (!hasInterpHandler) { gLog->error("Instruction {} has a JIT handler but no Interpreter handler", data->name); } if (!hasJitHandler) { gLog->error("Instruction {} has a Interpreter handler but no JIT handler", data->name); } return false; } uint32_t instr = 0x00000000; uint32_t instrBits = 0x00000000; for (auto &op : data->opcode) { auto field = op.field; auto value = op.value; auto start = getInstructionFieldStart(field); if (start >= 32) continue; auto fieldBits = getInstructionFieldBitmask(field); if (instrBits & fieldBits) { gLog->error("Instruction {} opcode {} overwrites bits", data->name, (uint32_t)field); gLog->error(" {:032b} on {:032b}", fieldBits, instrBits); return false; } instrBits |= fieldBits; instr |= value << start; } std::vector<InstructionField> allFields; for (auto i : data->read) { if (std::find(allFields.begin(), allFields.end(), i) == allFields.end()) { allFields.push_back(i); } } for (auto i : data->write) { if (std::find(allFields.begin(), allFields.end(), i) == allFields.end()) { allFields.push_back(i); } } for (auto i : data->flags) { if (std::find(allFields.begin(), allFields.end(), i) == allFields.end()) { allFields.push_back(i); } } for (auto i : allFields) { if (isInstructionFieldMarker(i)) continue; auto fieldBits = getInstructionFieldBitmask(i); if (instrBits & fieldBits) { gLog->error("Instruction {} field {} overwrites bits", data->name, (uint32_t)i); gLog->error(" {:032b} on {:032b}", fieldBits, instrBits); return false; } instrBits |= fieldBits; } if (instrBits != 0xFFFFFFFF) { gLog->error("Instruction {} does not describe all its bits", data->name); gLog->error(" {:032b}", instrBits); return false; } fuzzData.baseInstr = instr; fuzzData.allFields = std::move(allFields); return true; } bool setupFuzzData() { instructionFuzzData.resize((size_t)InstructionID::InstructionCount); bool res = true; for (int i = 0; i < (int)InstructionID::InstructionCount; ++i) { res &= buildFuzzData((InstructionID)i, instructionFuzzData[i]); } return res; } void setFieldValue(Instruction &instr, InstructionField field, uint32_t value) { instr.value |= (value << getInstructionFieldStart(field)) & getInstructionFieldBitmask(field); } bool compareStateField(int field, const TraceFieldValue &x, const TraceFieldValue &y) { if (field >= StateField::FPR0 && field <= StateField::FPR31) { return fabs(x.f64v0 - y.f64v0) < 0.0001 && fabs(x.f64v1 - y.f64v1) < 0.0001; } return x.u64v0 == y.u64v0 && x.u64v1 == y.u64v1; } bool compareStateField(int field, const TraceFieldValue &x, const TraceFieldValue &y, const TraceFieldValue &m, bool neg = false) { if (field >= StateField::FPR0 && field <= StateField::FPR31) { uint64_t xa = x.u64v0 & m.u64v0; uint64_t xb = x.u64v1 & m.u64v1; uint64_t ya = y.u64v0 & m.u64v0; uint64_t yb = y.u64v1 & m.u64v1; return (*(double*)&xa) == (*(double*)&ya) && (*(double*)&xb) == (*(double*)&yb); } return (x.u32v0 & m.u32v0) == (y.u32v0 & m.u32v0) && (x.u32v0 & m.u32v1) == (y.u32v0 & m.u32v1) && (x.u32v0 & m.u32v2) == (y.u32v0 & m.u32v2) && (x.u32v0 & m.u32v3) == (y.u32v0 & m.u32v3); } bool executeInstrTest(uint32_t test_seed) { std::mt19937 test_rand(test_seed); InstructionID instrId = (InstructionID)(test_rand() % (int)InstructionID::InstructionCount); // Special cases that we can't test easily switch (instrId) { case InstructionID::Invalid: return true; case InstructionID::lmw: case InstructionID::lswi: case InstructionID::lswx: case InstructionID::stmw: case InstructionID::stswi: case InstructionID::stswx: // Multi-word logic, disabled for now return true; case InstructionID::psq_l: case InstructionID::psq_lu: case InstructionID::psq_lux: case InstructionID::psq_lx: case InstructionID::psq_st: case InstructionID::psq_stu: case InstructionID::psq_stux: case InstructionID::psq_stx: // Quantization Registers need to be properly configured for these, disabled for now return true; case InstructionID::b: case InstructionID::bc: case InstructionID::bcctr: case InstructionID::bclr: // Branching cannot be fuzzed return true; case InstructionID::kc: // Emulator Instruction return true; case InstructionID::sc: case InstructionID::tw: case InstructionID::twi: case InstructionID::mfsr: case InstructionID::mfsrin: case InstructionID::mtsr: case InstructionID::mtsrin: // Supervisory Instructions return true; default: break; } const auto data = findInstructionInfo(instrId); const InstructionFuzzData *fuzzData = &instructionFuzzData[(int)instrId]; if (!data || !fuzzData) { return false; } if (!cpu::interpreter::hasInstruction(instrId)) { // No handler, skip it... return true; } Instruction instr(fuzzData->baseInstr); { // TODO: Add handling for rA==0 being 0 :S uint32_t gprAlloc = 0; uint32_t fprAlloc = 0; uint32_t gqrAlloc = 0; static const uint32_t gprAllocatable[] = { 5, 6, 7, 8, 9 }; static const uint32_t fprAllocatable[] = { 0, 1, 2, 3 }; static const uint32_t gqrAllocatable[] = { 0, 1, 2, 3 }; auto nextGpr = [&]() { assert(gprAlloc < array_size(gprAllocatable)); return gprAllocatable[gprAlloc++]; }; auto nextFpr = [&]() { assert(fprAlloc < array_size(fprAllocatable)); return fprAllocatable[fprAlloc++]; }; auto nextGqr = [&]() { assert(gqrAlloc < array_size(gqrAllocatable)); return gqrAllocatable[gqrAlloc++]; }; for (auto i : fuzzData->allFields) { if (isInstructionFieldMarker(i)) { continue; } switch (i) { case InstructionField::rA: // gpr Targets case InstructionField::rB: case InstructionField::rD: case InstructionField::rS: setFieldValue(instr, i, nextGpr()); break; case InstructionField::frA: // fpr Targets case InstructionField::frB: case InstructionField::frC: case InstructionField::frD: case InstructionField::frS: setFieldValue(instr, i, nextFpr()); break; case InstructionField::i: // gqr Targets case InstructionField::qi: setFieldValue(instr, i, test_rand() & 4); case InstructionField::crbA: // crb Targets case InstructionField::crbB: case InstructionField::crbD: setFieldValue(instr, i, test_rand()); break; case InstructionField::crfD: // crf Targets case InstructionField::crfS: setFieldValue(instr, i, test_rand()); break; case InstructionField::imm: // Random Values case InstructionField::simm: case InstructionField::uimm: case InstructionField::rc: // Record Condition case InstructionField::frc: case InstructionField::oe: case InstructionField::crm: case InstructionField::fm: case InstructionField::w: case InstructionField::qw: case InstructionField::sh: // Shift Registers case InstructionField::mb: case InstructionField::me: setFieldValue(instr, i, test_rand()); break; case InstructionField::d: // Memory Delta... case InstructionField::qd: setFieldValue(instr, i, test_rand()); break; case InstructionField::spr: // Special Purpose Registers { SPR validSprs[] = { SPR::XER, SPR::CTR, SPR::GQR0, SPR::GQR1, SPR::GQR2, SPR::GQR3, SPR::GQR4, SPR::GQR5, SPR::GQR6, SPR::GQR7 }; encodeSPR(instr, validSprs[test_rand() % array_size(validSprs)]); break; } case InstructionField::tbr: // Time Base Registers { SPR validTbrs[] = { SPR::TBL, SPR::TBU }; encodeSPR(instr, validTbrs[test_rand() % array_size(validTbrs)]); break; } case InstructionField::l: // l always must be 0 instr.l = 0; break; default: gLog->error("Instruction {} field {} is unsupported by fuzzer", data->name, (uint32_t)i); return false; } } } // Write an instruction mem::write(instructionBase + 0, instr.value); // Write a return for the Interpreter auto bclr = encodeInstruction(InstructionID::bclr); bclr.bo = 0x1f; mem::write(instructionBase + 4, bclr.value); #define STATEFIELDO(x, y) (StateField::Field)((int)x + y) StateField::Field randFields[] = { STATEFIELDO(StateField::GPR, 0), STATEFIELDO(StateField::GPR, 5), STATEFIELDO(StateField::GPR, 6), STATEFIELDO(StateField::GPR, 7), STATEFIELDO(StateField::GPR, 8), STATEFIELDO(StateField::GPR, 9), STATEFIELDO(StateField::FPR, 0), STATEFIELDO(StateField::FPR, 1), STATEFIELDO(StateField::FPR, 2), STATEFIELDO(StateField::FPR, 3), STATEFIELDO(StateField::GQR, 0), STATEFIELDO(StateField::GQR, 1), STATEFIELDO(StateField::GQR, 2), STATEFIELDO(StateField::GQR, 3), StateField::CR, StateField::FPSCR, StateField::XER, StateField::CTR }; #undef STATEFIELDO size_t numRandFields = array_size(randFields); // Build some randomized state data cpu::Core iState, jState; for (auto i = 0u; i < numRandFields; ++i) { auto field = randFields[i]; TraceFieldValue v; v.u32v0 = test_rand(); v.u32v1 = test_rand(); v.u32v2 = test_rand(); v.u32v3 = test_rand(); restoreStateField(&iState, field, v); restoreStateField(&jState, field, v); } // Build some randomized memory data const uint32_t memSize = 64; uint8_t iMem[memSize], jMem[memSize]; for (auto i = 0u; i < memSize; ++i) { auto randVal = (uint8_t)test_rand(); iMem[i] = randVal; jMem[i] = randVal; } // Some instructions need to be forced to a certain address #define SETGPR(i, v) \ iState.gpr[i]=v; \ jState.gpr[i]=v; #define CONFIG_rA_rB() { \ auto d = static_cast<int32_t>(test_rand()); \ SETGPR(instr.rA, d); \ SETGPR(instr.rB, dataBase - d); \ break; } #define CONFIG_rA_D() { \ auto d = sign_extend<16, int32_t>(instr.d); \ SETGPR(instr.rA, dataBase - d); \ break; } #define CONFIG_rA_QD() { \ auto d = sign_extend<12, int32_t>(instr.qd); \ SETGPR(instr.rA, dataBase - d); \ break; } switch (instrId) { case InstructionID::lbz: CONFIG_rA_D(); case InstructionID::lbzu: CONFIG_rA_D(); case InstructionID::lha: CONFIG_rA_D(); case InstructionID::lhau: CONFIG_rA_D(); case InstructionID::lhz: CONFIG_rA_D(); case InstructionID::lhzu: CONFIG_rA_D(); case InstructionID::lwz: CONFIG_rA_D(); case InstructionID::lwzu: CONFIG_rA_D(); case InstructionID::lfs: CONFIG_rA_D(); case InstructionID::lfsu: CONFIG_rA_D(); case InstructionID::lfd: CONFIG_rA_D(); case InstructionID::lfdu: CONFIG_rA_D(); case InstructionID::lbzx: CONFIG_rA_rB(); case InstructionID::lbzux: CONFIG_rA_rB(); case InstructionID::lhax: CONFIG_rA_rB(); case InstructionID::lhaux: CONFIG_rA_rB(); case InstructionID::lhbrx: CONFIG_rA_rB(); case InstructionID::lhzx: CONFIG_rA_rB(); case InstructionID::lhzux: CONFIG_rA_rB(); case InstructionID::lwbrx: CONFIG_rA_rB(); case InstructionID::lwarx: CONFIG_rA_rB(); case InstructionID::lwzx: CONFIG_rA_rB(); case InstructionID::lwzux: CONFIG_rA_rB(); case InstructionID::lfsx: CONFIG_rA_rB(); case InstructionID::lfsux: CONFIG_rA_rB(); case InstructionID::lfdx: CONFIG_rA_rB(); case InstructionID::lfdux: CONFIG_rA_rB(); case InstructionID::stb: CONFIG_rA_D(); case InstructionID::stbu: CONFIG_rA_D(); case InstructionID::sth: CONFIG_rA_D(); case InstructionID::sthu: CONFIG_rA_D(); case InstructionID::stw: CONFIG_rA_D(); case InstructionID::stwu: CONFIG_rA_D(); case InstructionID::stfs: CONFIG_rA_D(); case InstructionID::stfsu: CONFIG_rA_D(); case InstructionID::stfd: CONFIG_rA_D(); case InstructionID::stfdu: CONFIG_rA_D(); case InstructionID::stbx: CONFIG_rA_rB(); case InstructionID::stbux: CONFIG_rA_rB(); case InstructionID::sthx: CONFIG_rA_rB(); case InstructionID::sthux: CONFIG_rA_rB(); case InstructionID::stwx: CONFIG_rA_rB(); case InstructionID::stwux: CONFIG_rA_rB(); case InstructionID::sthbrx: CONFIG_rA_rB(); case InstructionID::stwbrx: CONFIG_rA_rB(); case InstructionID::stwcx: CONFIG_rA_rB(); case InstructionID::stfsx: CONFIG_rA_rB(); case InstructionID::stfsux: CONFIG_rA_rB(); case InstructionID::stfdx: CONFIG_rA_rB(); case InstructionID::stfdux: CONFIG_rA_rB(); case InstructionID::stfiwx: CONFIG_rA_rB(); case InstructionID::psq_l: CONFIG_rA_QD(); case InstructionID::psq_lx: CONFIG_rA_rB(); case InstructionID::psq_lu: CONFIG_rA_QD(); case InstructionID::psq_lux: CONFIG_rA_rB(); case InstructionID::dcbz: CONFIG_rA_rB(); case InstructionID::dcbz_l: CONFIG_rA_rB(); default: break; } #undef SETGPR #undef CONFIG_rA_rB #undef CONFIG_rA_D #undef CONFIG_rA_QD // Disable Reserveds for now iState.reserve = false; jState.reserve = false; // Required to be set to this iState.tracer = nullptr; iState.cia = 0; iState.nia = instructionBase; jState.tracer = nullptr; jState.cia = 0; jState.nia = instructionBase; { memcpy(mem::translate(dataBase), iMem, memSize); cpu::interpreter::executeSub(&iState); memcpy(iMem, mem::translate(dataBase), memSize); } { cpu::jit::clearCache(); memcpy(mem::translate(dataBase), jMem, memSize); cpu::jit::executeSub(&jState); memcpy(jMem, mem::translate(dataBase), memSize); } for (auto i = 0u; i < numRandFields; ++i) { auto field = randFields[i]; TraceFieldValue iVal, jVal; saveStateField(&iState, field, iVal); saveStateField(&jState, field, jVal); if (!compareStateField(field, iVal, jVal)) { gLog->warn("{}({:08x}) :: JIT does not match Interp on {}", data->name, test_seed, getStateFieldName(field)); } } return true; } bool executeFuzzTests(uint32_t suite_seed) { if (!setupFuzzData()) { return false; } std::mt19937 suite_rand(suite_seed); for (auto i = 0; i < 10000; ++i) { if (false) { gLog->info("Executing test {}", i); } executeInstrTest(suite_rand()); } return true; } ================================================ FILE: tests/cpu/fuzz-compare/fuzztests.h ================================================ #pragma once #include <cstdint> bool executeFuzzTests(uint32_t suite_seed = 0x12345678); ================================================ FILE: tests/cpu/fuzz-compare/main.cpp ================================================ #include <memory> #include <spdlog/spdlog.h> #include "fuzztests.h" #include "libcpu/mem.h" std::shared_ptr<spdlog::logger> gLog; int main(int argc, char *argv[]) { gLog = std::make_shared<spdlog::logger>("logger", std::make_shared<spdlog::sinks::stdout_sink_st>()); gLog->set_level(spdlog::level::debug); cpu::initialise(); auto result = executeFuzzTests() ? 0 : 1; return result; } ================================================ FILE: tests/cpu/generator/client/code_test.s ================================================ # rD, rA, rB, oe, rc, simm, XER # # rA, rB, rD -> affects gpr # rc -> affects cr0 (cr) # oe -> affects overflow flag (xer) # # r3...r6 # f1...f4 # # struct TestState # { # uint32_t xer; // 0x00 # uint32_t cr; // 0x04 # uint32_t ctr // 0x08 # uint32_t _ // 0x0c # uint32_t r3; // 0x10 # uint32_t r4; // 0x14 # uint32_t r5; // 0x18 # uint32_t r6; // 0x1C # uint64_t fpscr // 0x20 # double f1 // 0x28 # double f2 // 0x30 # double f3 // 0x38 # double f4 // 0x40 # } // 0x48 # # struct StackSavedState # { # uint32_t xer; // 0x00 # uint32_t cr; // 0x04 # double/u64 fpscr; // 0x08 # uint32_t lr; // 0x10 # } // 0x14 .global funcTest funcTest: li r3, 1337 blr # void executeCodeTest(TestState *state, void *func) .global executeCodeTest executeCodeTest: # Create space on stack for StackSavedState stwu r1, -0x20(r1) # r10 = state mr r10, r3 # Save System registers mfxer r3 stw r3, 0x00(r1) mfcr r3 stw r3, 0x04(r1) mffs f3 stfd f3, 0x08(r1) mflr r3 stw r3, 0x10(r1) # Put func to call into lr mtlr r4 # Load Test State lwz r3, 0x00(r10) mtxer r3 lwz r3, 0x04(r10) mtcr r3 lwz r3, 0x08(r10) mtctr r3 lwz r3, 0x10(r10) lwz r4, 0x14(r10) lwz r5, 0x18(r10) lwz r6, 0x1C(r10) li r0, 0 lfd f3, 0x20(r10) mtfsf 0xFF, r3 lfd f1, 0x28(r10) lfd f2, 0x30(r10) lfd f3, 0x38(r10) lfd f4, 0x40(r10) # Execute test blrl # Save Test State stw r3, 0x10(r10) stw r4, 0x14(r10) stw r5, 0x18(r10) stw r6, 0x1C(r10) mfxer r3 stw r3, 0x00(r10) mfcr r3 stw r3, 0x04(r10) mfctr r3 stw r3, 0x08(r10) mffs f5 stfd f5, 0x20(r10) stfd f1, 0x28(r10) stfd f2, 0x30(r10) stfd f3, 0x38(r10) stfd f4, 0x40(r10) # Restore System registers lwz r3, 0x00(r1) mtxer r3 lwz r3, 0x04(r1) mtcr r3 lfd f3, 0x08(r1) mtfsf 0xFF, f3 lwz r3, 0x10(r1) mtlr r3 # Restore stack pointer and return addi r1, r1, 0x20 blr ================================================ FILE: tests/cpu/generator/client/console.c ================================================ #include "console.h" void allocConsole(struct SystemFunctions *funcs, struct ConsoleData *console) { unsigned i; console->activeLine = 0; for (i = 0; i < MAX_CONSOLE_LINES; ++i) { console->line[i] = funcs->OSAllocFromSystem(MAX_CONSOLE_LINE_LENGTH, 4); console->line[i][0] = 0; } } void freeConsole(struct SystemFunctions *funcs, struct ConsoleData *console) { unsigned i; for (i = 0; i < MAX_CONSOLE_LINES; ++i) { funcs->OSFreeToSystem(console->line[i]); } } void renderConsole(struct ConsoleData *console) { unsigned i, j; for(i = 0; i < 2; i++) { fillScreen(0, 0, 0, 0); for (j = 0; j < MAX_CONSOLE_LINES; ++j) { drawString(0, j, console->line[j]); } flipBuffers(); } } char *nextConsoleLine(struct ConsoleData *console) { if (console->activeLine < MAX_CONSOLE_LINES) { return console->line[console->activeLine++]; } else { char *nextLine = console->line[0]; unsigned i; // Shift all lines up screen 1 for (i = 0; i < MAX_CONSOLE_LINES - 1; ++i) { console->line[i] = console->line[i + 1]; } // Move 0th line to end of console, reuse for nextLine console->line[MAX_CONSOLE_LINES - 1] = nextLine; // Clear line and return it nextLine[0] = 0; return nextLine; } } ================================================ FILE: tests/cpu/generator/client/console.h ================================================ #ifndef CONSOLE_H #define CONSOLE_H #include "sysfuncs.h" #include "../../../libwiiu/src/coreinit.h" #include "../../../libwiiu/src/types.h" #define MAX_CONSOLE_LINE_LENGTH 64 #define MAX_CONSOLE_LINES 16 #define LOG(c, ...) __os_snprintf(nextConsoleLine(c), MAX_CONSOLE_LINE_LENGTH, __VA_ARGS__); renderConsole(&consoleData); struct ConsoleData { unsigned activeLine; char *line[MAX_CONSOLE_LINES]; }; void allocConsole(struct SystemFunctions *funcs, struct ConsoleData *console); void freeConsole(struct SystemFunctions *funcs, struct ConsoleData *console); void renderConsole(struct ConsoleData *console); char *nextConsoleLine(struct ConsoleData *console); #endif ================================================ FILE: tests/cpu/generator/client/loader.c ================================================ #include "loader.h" void _start() { /****************************> Fix Stack <****************************/ //Load a good stack asm( "lis %r1, 0x1ab5 ;" "ori %r1, %r1, 0xd138 ;" ); /****************************> Get Handles <****************************/ //Get a handle to coreinit.rpl unsigned int coreinit_handle; OSDynLoad_Acquire("coreinit.rpl", &coreinit_handle); /****************************> External Prototypes <****************************/ //OSScreen functions void(*OSScreenInit)(); unsigned int(*OSScreenGetBufferSizeEx)(unsigned int bufferNum); unsigned int(*OSScreenSetBufferEx)(unsigned int bufferNum, void * addr); //OS Memory functions void*(*memset)(void * dest, uint32_t value, uint32_t bytes); void*(*OSAllocFromSystem)(uint32_t size, int align); void(*OSFreeToSystem)(void *ptr); //IM functions int(*IM_Open)(); int(*IM_Close)(int fd); int(*IM_SetDeviceState)(int fd, void *mem, int state, int a, int b); /****************************> Exports <****************************/ //OSScreen functions OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenInit", &OSScreenInit); OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenGetBufferSizeEx", &OSScreenGetBufferSizeEx); OSDynLoad_FindExport(coreinit_handle, 0, "OSScreenSetBufferEx", &OSScreenSetBufferEx); //OS Memory functions OSDynLoad_FindExport(coreinit_handle, 0, "memset", &memset); OSDynLoad_FindExport(coreinit_handle, 0, "OSAllocFromSystem", &OSAllocFromSystem); OSDynLoad_FindExport(coreinit_handle, 0, "OSFreeToSystem", &OSFreeToSystem); //IM functions OSDynLoad_FindExport(coreinit_handle, 0, "IM_Open", &IM_Open); OSDynLoad_FindExport(coreinit_handle, 0, "IM_Close", &IM_Close); OSDynLoad_FindExport(coreinit_handle, 0, "IM_SetDeviceState", &IM_SetDeviceState); /****************************> Initial Setup <****************************/ //Restart system to get lib access int fd = IM_Open(); void *mem = OSAllocFromSystem(0x100, 64); memset(mem, 0, 0x100); //set restart flag to force quit browser IM_SetDeviceState(fd, mem, 3, 0, 0); IM_Close(fd); OSFreeToSystem(mem); //wait a bit for browser end unsigned int t1 = 0x1FFFFFFF; while(t1--) ; //Call the Screen initilzation function. OSScreenInit(); //Grab the buffer size for each screen (TV and gamepad) int buf0_size = OSScreenGetBufferSizeEx(0); int buf1_size = OSScreenGetBufferSizeEx(1); //Set the buffer area. OSScreenSetBufferEx(0, (void *)0xF4000000); OSScreenSetBufferEx(1, (void *)0xF4000000 + buf0_size); //Clear both framebuffers. int ii = 0; for (ii; ii < 2; ii++) { fillScreen(0,0,0,0); flipBuffers(); } //Jump to entry point. _entryPoint(); } ================================================ FILE: tests/cpu/generator/client/loader.h ================================================ #ifndef LOADER_H #define LOADER_H #include "../../../libwiiu/src/coreinit.h" #include "../../../libwiiu/src/vpad.h" #include "../../../libwiiu/src/types.h" #include "../../../libwiiu/src/draw.h" #include "program.h" void _start(); void _entryPoint(); #endif /* LOADER_H */ ================================================ FILE: tests/cpu/generator/client/program.c ================================================ #include "program.h" #include "console.h" #include "sysfuncs.h" #define CLIENT_VERSION 1 #define ALIGN_BACKWARD(x,align) \ ((typeof(x))(((unsigned int)(x)) & (~(align-1)))) int sendwait(struct SystemFunctions *sysFuncs, int sock, const void *buffer, int len); int recvwait(struct SystemFunctions *sysFuncs, int sock, void *buffer, int len); struct PacketHeader { uint16_t size; uint16_t command; }; struct VersionPacket { struct PacketHeader header; uint32_t version; }; struct ExecuteCodeTestPacket { struct PacketHeader header; uint32_t instr; struct TestState state; }; void writeInstruction(struct SystemFunctions *sysFuncs, void *func, uint32_t instr) { // Write code uint32_t *topatch = (uint32_t*)(0xA0000000 + (uint32_t)func); topatch[0] = instr; // instr topatch[1] = 0x4E800020; // blr // Flush caches unsigned int *faddr = ALIGN_BACKWARD(topatch, 32); sysFuncs->DCFlushRange(faddr, 0x40); sysFuncs->ICInvalidateRange(faddr, 0x40); } void _entryPoint() { struct SystemFunctions sysFuncs; struct ConsoleData consoleData; loadSysFuncs(&sysFuncs); allocConsole(&sysFuncs, &consoleData); char *packetBuffer = (char *)sysFuncs.OSAllocFromSystem(4096, 16); sysFuncs.socket_lib_init(); int error; VPADData vpad_data; sysFuncs.VPADRead(0, &vpad_data, 1, &error); struct sockaddr sin; sysFuncs.memset(&sin, 0, sizeof(struct sockaddr)); sin.sin_family = AF_INET; sin.sin_port = 8008; sin.sin_addr.s_addr = ((192<<24) | (168<<16) | (1<<8) | (67<<0)); LOG(&consoleData, "Create socket"); int sockfd = sysFuncs.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); LOG(&consoleData, "Connecting..."); int status = sysFuncs.connect(sockfd, &sin, 0x10); LOG(&consoleData, "Connected."); bool hasKernelPermissions = false; bool running = true; if (sysFuncs.OSEffectiveToPhysical((void *)0xA0000000) != (void *)0x31000000) { LOG(&consoleData, "Running without kernel permissions"); hasKernelPermissions = false; } else { hasKernelPermissions = true; } if (status) { sockfd = 0; LOG(&consoleData, "Error connecting to server, status = %d", status); } else { while (running) { // Read packet header if (recvwait(&sysFuncs, sockfd, packetBuffer, 4) <= 0) { LOG(&consoleData, "Error reading packet header"); break; } // Read packet data uint16_t packetSize = ((uint16_t*)packetBuffer)[0]; uint16_t packetCmd = ((uint16_t*)packetBuffer)[1]; uint16_t remaining = packetSize - 4; if (remaining) { if (recvwait(&sysFuncs, sockfd, packetBuffer + 4, remaining) <= 0) { LOG(&consoleData, "Error reading packet data"); break; } } // Process packet switch(packetCmd) { case 1: { // Version struct VersionPacket *packet = (struct VersionPacket*)packetBuffer; LOG(&consoleData, "Received server version %d", packet->version); // Reply with client version packet->version = CLIENT_VERSION; if (sendwait(&sysFuncs, sockfd, packet, packet->header.size) <= 0) { LOG(&consoleData, "Error sending version packet"); running = false; } } break; case 10: { struct ExecuteCodeTestPacket *packet = (struct ExecuteCodeTestPacket*)packetBuffer; if (hasKernelPermissions) { writeInstruction(&sysFuncs, sysFuncs.PPCMtpmc4, packet->instr); executeCodeTest(&packet->state, sysFuncs.PPCMtpmc4); } else { LOG(&consoleData, "Skipping code test %08X (requires kernel permissions)", packet->instr); } // Reply with test results if (sendwait(&sysFuncs, sockfd, packet, packet->header.size) <= 0) { LOG(&consoleData, "Error sending test results."); running = false; } } break; case 11: { LOG(&consoleData, "Tests finished"); running = false; } break; } } } renderLoop: while(1) { sysFuncs.VPADRead(0, &vpad_data, 1, &error); // Exit when HOME button pressed if(vpad_data.btn_hold & BUTTON_HOME) { break; } renderConsole(&consoleData); } // Safely exit { unsigned i; sysFuncs.socket_lib_finish(); freeConsole(&sysFuncs, &consoleData); sysFuncs.OSFreeToSystem(packetBuffer); for(i = 0; i < 2; i++) { fillScreen(0,0,0,0); flipBuffers(); } sysFuncs._Exit(); } } int recvwait(struct SystemFunctions *sysFuncs, int sock, void *buffer, int len) { int ret; int recvd = 0; while (len > 0) { ret = sysFuncs->recv(sock, buffer, len, 0); if (ret <= 0) { return ret; } recvd += ret; len -= ret; buffer += ret; } return recvd; } int sendwait(struct SystemFunctions *sysFuncs, int sock, const void *buffer, int len) { int ret; int recvd = 0; while (len > 0) { ret = sysFuncs->send(sock, buffer, len, 0); if (ret <= 0) { return ret; } len -= ret; buffer += ret; } return recvd; } ================================================ FILE: tests/cpu/generator/client/program.h ================================================ #ifndef PROGRAM_H #define PROGRAM_H #include "../../../libwiiu/src/coreinit.h" #include "../../../libwiiu/src/vpad.h" #include "../../../libwiiu/src/types.h" #include "../../../libwiiu/src/draw.h" #include "../../../libwiiu/src/socket.h" struct TestState { uint32_t xer; // 0x00 uint32_t cr; // 0x04 uint32_t ctr; // 0x0c uint32_t _; // 0x08 uint32_t r3; // 0x10 uint32_t r4; // 0x14 uint32_t r5; // 0x18 uint32_t r6; // 0x1C uint64_t fpscr; // 0x20 double f1; // 0x28 double f2; // 0x30 double f3; // 0x38 double f4; // 0x40 }; // 0x48 extern void executeCodeTest(struct TestState *state, void *func); void _entryPoint(); #endif /* PROGRAM_H */ ================================================ FILE: tests/cpu/generator/client/sysfuncs.c ================================================ #include "sysfuncs.h" #include "../../../libwiiu/src/coreinit.h" void loadSysFuncs(struct SystemFunctions *sysFuncs) { unsigned coreinit, vpad, nsysnet; OSDynLoad_Acquire("coreinit.rpl", &coreinit); OSDynLoad_FindExport(coreinit, 0, "memset", &sysFuncs->memset); OSDynLoad_FindExport(coreinit, 0, "_Exit", &sysFuncs->_Exit); OSDynLoad_FindExport(coreinit, 0, "ICInvalidateRange", &sysFuncs->ICInvalidateRange); OSDynLoad_FindExport(coreinit, 0, "DCFlushRange", &sysFuncs->DCFlushRange); OSDynLoad_FindExport(coreinit, 0, "OSAllocFromSystem", &sysFuncs->OSAllocFromSystem); OSDynLoad_FindExport(coreinit, 0, "OSFreeToSystem", &sysFuncs->OSFreeToSystem); OSDynLoad_FindExport(coreinit, 0, "OSEffectiveToPhysical", &sysFuncs->OSEffectiveToPhysical); OSDynLoad_FindExport(coreinit, 0, "PPCMtpmc4", &sysFuncs->PPCMtpmc4); sysFuncs->coreinit_handle = coreinit; OSDynLoad_Acquire("vpad.rpl", &vpad); OSDynLoad_FindExport(vpad, 0, "VPADRead", &sysFuncs->VPADRead); sysFuncs->vpad_handle = vpad; OSDynLoad_Acquire("nsysnet.rpl", &nsysnet); OSDynLoad_FindExport(nsysnet, 0, "socket_lib_init", &sysFuncs->socket_lib_init); OSDynLoad_FindExport(nsysnet, 0, "socket_lib_finish", &sysFuncs->socket_lib_finish); OSDynLoad_FindExport(nsysnet, 0, "socket", &sysFuncs->socket); OSDynLoad_FindExport(nsysnet, 0, "connect", &sysFuncs->connect); OSDynLoad_FindExport(nsysnet, 0, "recv", &sysFuncs->recv); OSDynLoad_FindExport(nsysnet, 0, "send", &sysFuncs->send); sysFuncs->nsysnet_handle = nsysnet; } ================================================ FILE: tests/cpu/generator/client/sysfuncs.h ================================================ #ifndef SYSFUNCS_H #define SYSFUNCS_H #include "../../../libwiiu/src/types.h" #include "../../../libwiiu/src/vpad.h" #include "../../../libwiiu/src/socket.h" struct SystemFunctions { unsigned int coreinit_handle; void *(*OSAllocFromSystem)(uint32_t size, int align); void (*OSFreeToSystem)(void *ptr); void *(*OSEffectiveToPhysical)(const void *); void (*ICInvalidateRange)(const void *, int); void (*DCFlushRange)(const void *, int); void (*PPCMtpmc4)(); void (*memset)(void *dst, char val, int bytes); void (*_Exit)(); unsigned int vpad_handle; int(*VPADRead)(int controller, VPADData *buffer, unsigned int num, int *error); unsigned int nsysnet_handle; void (*socket_lib_init)(); void (*socket_lib_finish)(); int (*socket)(int family, int type, int proto); int (*connect)(int fd, struct sockaddr *addr, int addrlen); int (*recv)(int fd, void *buffer, int len, int flags); int (*send)(int fd, const void *buffer, int len, int flags); }; void loadSysFuncs(struct SystemFunctions *sysFuncs); #endif ================================================ FILE: tests/cpu/generator/dataset/generator.cpp ================================================ #include <cassert> #include <cstdint> #include <fstream> #include <iostream> #include <memory> #include <vector> #include <spdlog/spdlog.h> #include <common/be_val.h> #include <common/bitutils.h> #include "libcpu/state.h" #include "libcpu/espresso/espresso_instructionset.h" #include "hardware-test/hardwaretests.h" #include "generator_testlist.h" #include "generator_valuelist.h" using namespace espresso; std::shared_ptr<spdlog::logger> gLog; static void setCRB(hwtest::RegisterState &state, uint32_t bit, uint32_t value) { state.cr.value = set_bit_value(state.cr.value, 31 - bit, value); } static void generateTests(InstructionInfo *data) { std::vector<size_t> indexCur, indexMax; std::vector<bool> flagSet; hwtest::TestFile testFile; auto complete = false; auto completeIndices = false; for (auto i = 0; i < data->read.size(); ++i) { auto &field = data->read[i]; indexCur.push_back(0); switch (field) { case InstructionField::rA: case InstructionField::rB: case InstructionField::rS: indexMax.push_back(gValuesGPR.size()); break; case InstructionField::frA: case InstructionField::frB: case InstructionField::frC: case InstructionField::frS: indexMax.push_back(gValuesFPR.size()); break; case InstructionField::crbA: case InstructionField::crbB: indexMax.push_back(gValuesCRB.size()); break; case InstructionField::simm: indexMax.push_back(gValuesSIMM.size()); break; case InstructionField::sh: indexMax.push_back(gValuesSH.size()); break; case InstructionField::mb: indexMax.push_back(gValuesMB.size()); break; case InstructionField::me: indexMax.push_back(gValuesME.size()); break; case InstructionField::uimm: indexMax.push_back(gValuesUIMM.size()); break; case InstructionField::XERC: indexMax.push_back(gValuesXERC.size()); break; case InstructionField::XERSO: indexMax.push_back(gValuesXERSO.size()); break; default: assert(false); } } for (auto i = 0; i < data->flags.size(); ++i) { flagSet.push_back(false); } while (!complete) { uint32_t gpr = 0, fpr = 0, crf = 0, crb = 0; hwtest::TestData test; memset(&test, 0, sizeof(hwtest::TestData)); test.instr = encodeInstruction(data->id); for (auto i = 0; i < data->read.size(); ++i) { auto index = indexCur[i]; // Generate read field values switch (data->read[i]) { case InstructionField::rA: test.instr.rA = gpr + hwtest::GPR_BASE; test.input.gpr[gpr++] = gValuesGPR[index]; break; case InstructionField::rB: test.instr.rB = gpr + hwtest::GPR_BASE; test.input.gpr[gpr++] = gValuesGPR[index]; break; case InstructionField::rS: test.instr.rS = gpr + hwtest::GPR_BASE; test.input.gpr[gpr++] = gValuesGPR[index]; break; case InstructionField::frA: test.instr.frA = fpr + hwtest::FPR_BASE; test.input.fr[fpr++] = gValuesFPR[index]; break; case InstructionField::frB: test.instr.frB = fpr + hwtest::FPR_BASE; test.input.fr[fpr++] = gValuesFPR[index]; break; case InstructionField::frC: test.instr.frC = fpr + hwtest::FPR_BASE; test.input.fr[fpr++] = gValuesFPR[index]; break; case InstructionField::frS: test.instr.frS = fpr + hwtest::FPR_BASE; test.input.fr[fpr++] = gValuesFPR[index]; break; case InstructionField::crbA: test.instr.crbA = (crb++) + hwtest::CRB_BASE; setCRB(test.input, test.instr.crbA, gValuesCRB[index]); break; case InstructionField::crbB: test.instr.crbB = (crb++) + hwtest::CRB_BASE; setCRB(test.input, test.instr.crbB, gValuesCRB[index]); break; case InstructionField::simm: test.instr.simm = gValuesSIMM[index]; break; case InstructionField::sh: test.instr.sh = gValuesSH[index]; break; case InstructionField::mb: test.instr.mb = gValuesMB[index]; break; case InstructionField::me: test.instr.me = gValuesME[index]; break; case InstructionField::uimm: test.instr.uimm = gValuesUIMM[index]; break; case InstructionField::XERC: test.input.xer.ca = gValuesXERC[index]; break; case InstructionField::XERSO: test.input.xer.so = gValuesXERSO[index]; break; default: assert(false); } } // Generate write field values for (auto field : data->write) { switch (field) { case InstructionField::rA: test.instr.rA = gpr + hwtest::GPR_BASE; gpr++; break; case InstructionField::rD: test.instr.rD = gpr + hwtest::GPR_BASE; gpr++; break; case InstructionField::frD: test.instr.frD = fpr + hwtest::FPR_BASE; fpr++; break; case InstructionField::crfD: test.instr.crfD = crf + hwtest::CRF_BASE; crf++; break; case InstructionField::crbD: test.instr.crbD = crb + hwtest::CRB_BASE; crb++; break; case InstructionField::XERC: case InstructionField::XERSO: case InstructionField::FCRISI: case InstructionField::FCRZDZ: case InstructionField::FCRIDI: case InstructionField::FCRSNAN: break; default: assert(false); } } testFile.tests.emplace_back(test); // Increase indices for (auto i = 0; i < indexCur.size(); ++i) { indexCur[i]++; if (indexCur[i] < indexMax[i]) { break; } else if (indexCur[i] == indexMax[i]) { indexCur[i] = 0; if (i == indexCur.size() - 1) { completeIndices = true; } } } if (completeIndices) { if (flagSet.size() == 0) { complete = true; break; } completeIndices = false; // Do next flag! for (auto i = 0; i < flagSet.size(); ++i) { if (!flagSet[i]) { flagSet[i] = true; break; } else { flagSet[i] = false; if (i == flagSet.size() - 1) { complete = true; break; } } } } } // Save tests to file auto filename = std::string("tests/cpu/input/") + data->name; std::ofstream out { filename, std::ofstream::out | std::ofstream::binary }; cereal::BinaryOutputArchive archive(out); archive(testFile); } int main(int argc, char **argv) { std::vector<spdlog::sink_ptr> sinks; sinks.push_back(spdlog::sinks::stdout_sink_st::instance()); gLog = std::make_shared<spdlog::logger>("decaf", begin(sinks), end(sinks)); initialiseInstructionSet(); for (auto &group : gTestInstructions) { for (auto id : group) { auto data = findInstructionInfo(id); generateTests(data); } } return 0; } /* Unimplemented instructions: // Floating-Point Status and Control Register INS(mcrfs, (crfD), (crfS), (), (opcd == 63, xo1 == 64, !_9_10, !_14_15, !_16_20, !_31), "") INS(mffs, (frD), (), (rc), (opcd == 63, xo1 == 583, !_11_15, !_16_20), "") INS(mtfsf, (), (fm, frB), (rc), (opcd == 63, xo1 == 711, !_6, !_15), "") INS(mtfsfi, (crfD), (), (rc, imm), (opcd == 63, xo1 == 134, !_9_10, !_11_15, !_20), "") // Integer Load INS(lbz, (rD), (rA, d), (), (opcd == 34), "Load Byte and Zero") INS(lbzu, (rD, rA), (rA, d), (), (opcd == 35), "Load Byte and Zero with Update") INS(lbzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 87, !_31), "Load Byte and Zero Indexed") INS(lbzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 119, !_31), "Load Byte and Zero with Update Indexed") INS(lha, (rD), (rA, d), (), (opcd == 42), "Load Half Word Algebraic") INS(lhau, (rD, rA), (rA, d), (), (opcd == 43), "Load Half Word Algebraic with Update") INS(lhax, (rD), (rA, rB), (), (opcd == 31, xo1 == 343, !_31), "Load Half Word Algebraic Indexed") INS(lhaux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 375, !_31), "Load Half Word Algebraic with Update Indexed") INS(lhz, (rD), (rA, d), (), (opcd == 40), "Load Half Word and Zero") INS(lhzu, (rD, rA), (rA, d), (), (opcd == 41), "Load Half Word and Zero with Update") INS(lhzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 279, !_31), "Load Half Word and Zero Indexed") INS(lhzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 311, !_31), "Load Half Word and Zero with Update Indexed") INS(lwz, (rD), (rA, d), (), (opcd == 32), "Load Word and Zero") INS(lwzu, (rD, rA), (rA, d), (), (opcd == 33), "Load Word and Zero with Update") INS(lwzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 23, !_31), "Load Word and Zero Indexed") INS(lwzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 55, !_31), "Load Word and Zero with Update Indexed") // Integer Store INS(stb, (), (rS, rA, d), (), (opcd == 38), "Store Byte") INS(stbu, (rA), (rS, rA, d), (), (opcd == 39), "Store Byte with Update") INS(stbx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 215, !_31), "Store Byte Indexed") INS(stbux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 247, !_31), "Store Byte with Update Indexed") INS(sth, (), (rS, rA, d), (), (opcd == 44), "Store Half Word") INS(sthu, (rA), (rS, rA, d), (), (opcd == 45), "Store Half Word with Update") INS(sthx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 407, !_31), "Store Half Word Indexed") INS(sthux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 439, !_31), "Store Half Word with Update Indexed") INS(stw, (), (rS, rA, d), (), (opcd == 36), "Store Word") INS(stwu, (rA), (rS, rA, d), (), (opcd == 37), "Store Word with Update") INS(stwx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 151, !_31), "Store Word Indexed") INS(stwux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 183, !_31), "Store Word with Update Indexed") // Integer Load and Store with Byte Reverse INS(lhbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 790, !_31), "Load Half Word Byte-Reverse Indexed") INS(lwbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 534, !_31), "Load Word Byte-Reverse Indexed") INS(sthbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 918, !_31), "Store Half Word Byte-Reverse Indexed") INS(stwbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 662, !_31), "Store Word Byte-Reverse Indexed") // Integer Load and Store Multiple INS(lmw, (rD), (rA, d), (), (opcd == 46), "Load Multiple Words") INS(stmw, (), (rS, rA, d), (), (opcd == 47), "Store Multiple Words") // Integer Load and Store String INS(lswi, (rD), (rA, nb), (), (opcd == 31, xo1 == 597, !_31), "Load String Word Immediate") INS(lswx, (rD), (rA, rB), (), (opcd == 31, xo1 == 533, !_31), "Load String Word Indexed") INS(stswi, (), (rS, rA, nb), (), (opcd == 31, xo1 == 725, !_31), "Store String Word Immediate") INS(stswx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 661, !_31), "Store String Word Indexed") // Memory Synchronisation INS(eieio, (), (), (), (opcd == 31, xo1 == 854, !_6_10, !_11_15, !_16_20, !_31), "Enforce In-Order Execution of I/O") INS(isync, (), (), (), (opcd == 19, xo1 == 150, !_6_10, !_11_15, !_16_20, !_31), "Instruction Synchronise") INS(lwarx, (rD, RSRV), (rA, rB), (), (opcd == 31, xo1 == 20, !_31), "Load Word and Reserve Indexed") INS(stwcx, (RSRV), (rS, rA, rB), (), (opcd == 31, xo1 == 150, _31 == 1), "Store Word Conditional Indexed") INS(sync, (), (), (), (opcd == 31, xo1 == 598, !_6_10, !_11_15, !_16_20, !_31), "Synchronise") // Floating-Point Load INS(lfd, (frD), (rA, d), (), (opcd == 50), "Load Floating-Point Double") INS(lfdu, (frD, rA), (rA, d), (), (opcd == 51), "Load Floating-Point Double with Update") INS(lfdx, (frD), (rA, rB), (), (opcd == 31, xo1 == 599, !_31), "Load Floating-Point Double Indexed") INS(lfdux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 631, !_31), "Load Floating-Point Double with Update Indexed") INS(lfs, (frD), (rA, d), (), (opcd == 48), "Load Floating-Point Single") INS(lfsu, (frD, rA), (rA, d), (), (opcd == 49), "Load Floating-Point Single with Update") INS(lfsx, (frD), (rA, rB), (), (opcd == 31, xo1 == 535, !_31), "Load Floating-Point Single Indexed") INS(lfsux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 567, !_31), "Load Floating-Point Single with Update Indexed") // Floating-Point Store INS(stfd, (), (frS, rA, d), (), (opcd == 54), "Store Floating-Point Double") INS(stfdu, (rA), (frS, rA, d), (), (opcd == 55), "Store Floating-Point Double with Update") INS(stfdx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 727, !_31), "Store Floating-Point Double Indexed") INS(stfdux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 759, !_31), "Store Floating-Point Double with Update Indexed") INS(stfiwx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 983, !_31), "Store Floating-Point as Integer Word Indexed") INS(stfs, (), (frS, rA, d), (), (opcd == 52), "Store Floating-Point Single") INS(stfsu, (rA), (frS, rA, d), (), (opcd == 53), "Store Floating-Point Single with Update") INS(stfsx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 663, !_31), "Store Floating-Point Single Indexed") INS(stfsux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 695, !_31), "Store Floating-Point Single with Update Indexed") // Branch INS(b, (), (li), (aa, lk), (opcd == 18), "Branch") INS(bc, (bo), (bi, bd), (aa, lk), (opcd == 16), "Branch Conditional") INS(bcctr, (bo), (bi, CTR), (lk), (opcd == 19, xo1 == 528, !_16_20), "Branch Conditional to CTR") INS(bclr, (bo), (bi, LR), (lk), (opcd == 19, xo1 == 16, !_16_20), "Branch Conditional to LR") // System Linkage INS(rfi, (), (), (), (opcd == 19, xo1 == 50, !_6_10, !_11_15, !_16_20, !_31), "") INS(sc, (), (), (), (opcd == 17, !_6_10, !_11_15, !_16_29, _30 == 1, !_31), "Syscall") INS(kc, (), (kcn), (), (opcd == 1), "krncall") // Trap INS(tw, (), (to, rA, rB), (), (opcd == 31, xo1 == 4, !_31), "") INS(twi, (), (to, rA, simm), (), (opcd == 3), "") // Processor Control INS(mcrxr, (crfD), (XERO), (), (opcd == 31, xo1 == 512, !_9_10, !_11_15, !_16_20, !_31), "Move to Condition Register from XERO") INS(mfcr, (rD), (), (), (opcd == 31, xo1 == 19, !_11_15, !_16_20, !_31), "Move from Condition Register") INS(mfmsr, (rD), (), (), (opcd == 31, xo1 == 83, !_11_15, !_16_20, !_31), "Move from Machine State Register") INS(mfspr, (rD), (spr), (), (opcd == 31, xo1 == 339, !_31), "Move from Special Purpose Register") INS(mftb, (rD), (tbr), (), (opcd == 31, xo1 == 371, !_31), "Move from Time Base Register") INS(mtcrf, (crm), (rS), (), (opcd == 31, xo1 == 144, !_11, !_20, !_31), "Move to Condition Register Fields") INS(mtmsr, (), (rS), (), (opcd == 31, xo1 == 146, !_11_15, !_16_20, !_31), "Move to Machine State Register") INS(mtspr, (spr), (rS), (), (opcd == 31, xo1 == 467, !_31), "Move to Special Purpose Register") // Cache Management INS(dcbf, (), (rA, rB), (), (opcd == 31, xo1 == 86, !_6_10, !_31), "") INS(dcbi, (), (rA, rB), (), (opcd == 31, xo1 == 470, !_6_10, !_31), "") INS(dcbst, (), (rA, rB), (), (opcd == 31, xo1 == 54, !_6_10, !_31), "") INS(dcbt, (), (rA, rB), (), (opcd == 31, xo1 == 278, !_6_10, !_31), "") INS(dcbtst, (), (rA, rB), (), (opcd == 31, xo1 == 246, !_6_10, !_31), "") INS(dcbz, (), (rA, rB), (), (opcd == 31, xo1 == 1014, !_6_10, !_31), "") INS(icbi, (), (rA, rB), (), (opcd == 31, xo1 == 982, !_6_10, !_31), "") INS(dcbz_l, (), (rA, rB), (), (opcd == 4, xo1 == 1014, !_6_10, !_31), "") // Segment Register Manipulation INS(mfsr, (rD), (sr), (), (opcd == 31, xo1 == 595, !_11, !_16_20, !_31), "Move from Segment Register") INS(mfsrin, (rD), (rB), (), (opcd == 31, xo1 == 659, !_11_15, !_31), "Move from Segment Register Indirect") INS(mtsr, (), (rD, sr), (), (opcd == 31, xo1 == 210, !_11, !_16_20, !_31), "Move to Segment Register") INS(mtsrin, (), (rD, rB), (), (opcd == 31, xo1 == 242, !_11_15, !_31), "Move to Segment Register Indirect") // Lookaside Buffer Management INS(tlbie, (), (rB), (), (opcd == 31, xo1 == 306, !_6_10, !_11_15, !_31), "") INS(tlbsync, (), (), (), (opcd == 31, xo1 == 566, !_6_10, !_11_15, !_16_20, !_31), "") // External Control INS(eciwx, (rD), (rA, rB), (), (opcd == 31, xo1 == 310, !_31), "") INS(ecowx, (rD), (rA, rB), (), (opcd == 31, xo1 == 438, !_31), "") // Paired-Single Load and Store INS(psq_l, (frD), (rA, qd), (w, i), (opcd == 56), "Paired Single Load") INS(psq_lu, (frD), (rA, qd), (w, i), (opcd == 57), "Paired Single Load with Update") INS(psq_lx, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 6, !_31), "Paired Single Load Indexed") INS(psq_lux, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 38, !_31), "Paired Single Load with Update Indexed") INS(psq_st, (frD), (rA, qd), (w, i), (opcd == 60), "Paired Single Store") INS(psq_stu, (frD), (rA, qd), (w, i), (opcd == 61), "Paired Single Store with Update") INS(psq_stx, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 7, !_31), "Paired Single Store Indexed") INS(psq_stux, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 39, !_31), "Paired Single Store with Update Indexed") // Paired-Single Floating Point Arithmetic INS(ps_add, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 21, !_21_25), "Paired Single Add") INS(ps_div, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 18, !_21_25), "Paired Single Divide") INS(ps_mul, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 25, !_16_20), "Paired Single Multiply") INS(ps_sub, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 20, !_21_25), "Paired Single Subtract") INS(ps_abs, (frD), (frB), (rc), (opcd == 4, xo1 == 264, !_11_15), "Paired Single Absolute") INS(ps_nabs, (frD), (frB), (rc), (opcd == 4, xo1 == 136, !_11_15), "Paired Single Negate Absolute") INS(ps_neg, (frD), (frB), (rc), (opcd == 4, xo1 == 40, !_11_15), "Paired Single Negate") INS(ps_sel, (frD), (frA, frB, frC), (rc), (opcd == 4, xo4 == 23), "Paired Single Select") INS(ps_res, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 24, !_11_15, !_21_25), "Paired Single Reciprocal") INS(ps_rsqrte, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 26, !_11_15, !_21_25), "Paired Single Reciprocal Square Root Estimate") INS(ps_msub, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 28), "Paired Single Multiply and Subtract") INS(ps_madd, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 29), "Paired Single Multiply and Add") INS(ps_nmsub, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 30), "Paired Single Negate Multiply and Subtract") INS(ps_nmadd, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 31), "Paired Single Negate Multiply and Add") INS(ps_mr, (frD), (frB), (rc), (opcd == 4, xo1 == 72, !_11_15), "Paired Single Move Register") INS(ps_sum0, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 10), "Paired Single Sum High") INS(ps_sum1, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 11), "Paired Single Sum Low") INS(ps_muls0, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 12, !_16_20), "Paired Single Multiply Scalar High") INS(ps_muls1, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 13, !_16_20), "Paired Single Multiply Scalar Low") INS(ps_madds0, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 14), "Paired Single Multiply and Add Scalar High") INS(ps_madds1, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 15), "Paired Single Multiply and Add Scalar Low") INS(ps_cmpu0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 0, !_9_10, !_31), "Paired Single Compare Unordered High") INS(ps_cmpo0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 32, !_9_10, !_31), "Paired Single Compare Ordered High") INS(ps_cmpu1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 64, !_9_10, !_31), "Paired Single Compare Unordered Low") INS(ps_cmpo1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 96, !_9_10, !_31), "Paired Single Compare Ordered Low") INS(ps_merge00, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 528), "Paired Single Merge High") INS(ps_merge01, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 560), "Paired Single Merge Direct") INS(ps_merge10, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 592), "Paired Single Merge Swapped") INS(ps_merge11, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 624), "Paired Single Merge Low") */ ================================================ FILE: tests/cpu/generator/dataset/generator_testlist.h ================================================ #pragma once #include <vector> #include "libcpu/espresso/espresso_instructionid.h" using espresso::InstructionID; static const auto gIntegerArithmetic = { InstructionID::add, InstructionID::addc, InstructionID::adde, InstructionID::addi, InstructionID::addic, InstructionID::addicx, InstructionID::addis, InstructionID::addme, InstructionID::addze, InstructionID::divw, InstructionID::divwu, InstructionID::mulhw, InstructionID::mulhwu, InstructionID::mulli, InstructionID::mullw, InstructionID::neg, InstructionID::subf, InstructionID::subfc, InstructionID::subfe, InstructionID::subfic, InstructionID::subfme, InstructionID::subfze, }; static const auto gIntegerLogical = { InstructionID::and_, InstructionID::andc, InstructionID::andi, InstructionID::andis, InstructionID::cntlzw, InstructionID::eqv, InstructionID::extsb, InstructionID::extsh, InstructionID::nand, InstructionID::nor, InstructionID::or_, InstructionID::orc, InstructionID::ori, InstructionID::oris, InstructionID::xor_, InstructionID::xori, InstructionID::xoris, }; static const auto gIntegerCompare = { InstructionID::cmp, InstructionID::cmpi, InstructionID::cmpl, InstructionID::cmpli, }; static const auto gIntegerShift = { InstructionID::slw, InstructionID::sraw, InstructionID::srawi, InstructionID::srw, }; static const auto gIntegerRotate = { InstructionID::rlwimi, InstructionID::rlwinm, InstructionID::rlwnm, }; static const auto gConditionRegisterLogical = { InstructionID::crand, InstructionID::crandc, InstructionID::creqv, InstructionID::crnand, InstructionID::crnor, InstructionID::cror, InstructionID::crorc, InstructionID::crxor, //InstructionID::mcrf, }; static const auto gFloatArithmetic = { InstructionID::fadd, InstructionID::fadds, InstructionID::fdiv, InstructionID::fdivs, InstructionID::fmul, InstructionID::fmuls, InstructionID::fres, InstructionID::fsub, InstructionID::fsubs, InstructionID::fsel, }; static const auto gFloatArithmeticMuladd = { InstructionID::fmadd, InstructionID::fmadds, InstructionID::fmsub, InstructionID::fmsubs, InstructionID::fnmadd, InstructionID::fnmadds, InstructionID::fnmsub, InstructionID::fnmsubs, }; static const auto gFloatRound = { InstructionID::fctiw, InstructionID::fctiwz, InstructionID::frsp, }; static const auto gFloatMove = { InstructionID::fabs, InstructionID::fmr, InstructionID::fnabs, InstructionID::fneg, }; static const auto gFloatCompare = { InstructionID::fcmpo, InstructionID::fcmpu, }; static const auto gTestInstructions = { gIntegerArithmetic, gIntegerLogical, gIntegerCompare, gIntegerShift, gIntegerRotate, gConditionRegisterLogical, gFloatArithmetic, gFloatArithmeticMuladd, gFloatRound, gFloatMove, }; ================================================ FILE: tests/cpu/generator/dataset/generator_valuelist.h ================================================ #pragma once #include <cstdint> #include <vector> static const std::vector<uint32_t> gValuesCRB = { 0, 1, }; static const std::vector<uint32_t> gValuesGPR = { 0, 1, static_cast<uint32_t>(-1), static_cast<uint32_t>(std::numeric_limits<int32_t>::min()), static_cast<uint32_t>(std::numeric_limits<int32_t>::max()), 53, 0x12345678, 0x87654321 }; static const std::vector<int16_t> gValuesSIMM = { 0, 1, -1, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max(), 53, 0x1234, static_cast<int16_t>(0x8765u) }; static const std::vector<uint16_t> gValuesUIMM = { 0, 1, static_cast<uint16_t>(-1), static_cast<uint16_t>(std::numeric_limits<int16_t>::min()), static_cast<uint16_t>(std::numeric_limits<int16_t>::max()), 53, 0x1234, 0x8765 }; static const std::vector<double> gValuesFPR = { 0.0, -0.0, 1.0, -1.0, std::numeric_limits<double>::min(), std::numeric_limits<double>::max(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::signaling_NaN(), std::numeric_limits<double>::denorm_min(), std::numeric_limits<double>::epsilon(), 33525.78 }; static const std::vector<uint32_t> gValuesXERC = { 0, 1 }; static const std::vector<uint32_t> gValuesXERSO = { 0, 1 }; static const std::vector<uint32_t> gValuesSH = { 0, 15, 23, 31 }; static const std::vector<uint32_t> gValuesMB = { 0, 15, 23, 31 }; static const std::vector<uint32_t> gValuesME = { 0, 15, 23, 31 }; ================================================ FILE: tests/cpu/generator/server/server.cpp ================================================ #include <cassert> #include <cstdint> #include <iostream> #include <experimental/filesystem> #include <fstream> #include <memory> #include <vector> #include <ovsocket/socket.h> #include <ovsocket/networkthread.h> #include "hardware-test/hardwaretests.h" #include "common/be_val.h" #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "ovsocket.lib") using namespace ovs; using namespace std::placeholders; namespace fs = std::experimental::filesystem; // Deal with it. static std::vector<hwtest::TestFile> gTestSet; #pragma pack(push, 1) struct HWRegisterState { be_val<uint32_t> xer; be_val<uint32_t> cr; be_val<uint32_t> ctr; be_val<uint32_t> _; be_val<uint32_t> r3; be_val<uint32_t> r4; be_val<uint32_t> r5; be_val<uint32_t> r6; be_val<uint64_t> fpscr; be_val<double> f1; be_val<double> f2; be_val<double> f3; be_val<double> f4; }; struct PacketHeader { enum Commands { Version = 1, ExecuteGeneralTest = 10, ExecutePairedTest = 20, TestsFinished = 50, }; be_val<uint16_t> size; be_val<uint16_t> command; }; struct VersionPacket : PacketHeader { VersionPacket(uint32_t value) { size = sizeof(VersionPacket); command = PacketHeader::Version; version = value; } be_val<uint32_t> version; }; struct ExecuteGeneralTestPacket : PacketHeader { ExecuteGeneralTestPacket() { size = sizeof(ExecuteGeneralTestPacket); command = PacketHeader::ExecuteGeneralTest; memset(&state, 0, sizeof(HWRegisterState)); } be_val<uint32_t> instr; HWRegisterState state; }; struct TestFinishedPacket : PacketHeader { TestFinishedPacket() { size = sizeof(VersionPacket); command = PacketHeader::TestsFinished; } }; #pragma pack(pop) class TestServer { static const uint32_t Version = 1; public: TestServer(Socket *socket) : mSocket(socket) { mSocket->addErrorListener(std::bind(&TestServer::onSocketError, this, _1, _2)); mSocket->addDisconnectListener(std::bind(&TestServer::onSocketDisconnect, this, _1)); mSocket->addReceiveListener(std::bind(&TestServer::onSocketReceive, this, _1, _2, _3)); // Send version VersionPacket version { Version }; mSocket->send(reinterpret_cast<const char *>(&version), sizeof(VersionPacket)); // Read first packet mSocket->recvFill(sizeof(PacketHeader)); // Initialise test iterators mTestFile = gTestSet.begin(); mTestData = mTestFile->tests.begin(); } private: void saveTestFile() { // Save test result std::ofstream out("tests/cpu/wiiu/" + mTestFile->name, std::ofstream::out | std::ofstream::binary); cereal::BinaryOutputArchive ar(out); ar(*mTestFile); std::cout << "Wrote file tests/cpu/wiiu/" << mTestFile->name << std::endl; } void sendNextTest() { if (mTestData == mTestFile->tests.end()) { // Save current test file saveTestFile(); // To the next test! ++mTestFile; if (mTestFile == gTestSet.end()) { std::cout << "Tests finished." << std::endl; // Notify client tests have finished TestFinishedPacket pak; mSocket->send(reinterpret_cast<const char *>(&pak), sizeof(TestFinishedPacket)); } else { // Start next test file mTestData = mTestFile->tests.begin(); } } // Copy test input ExecuteGeneralTestPacket packet; packet.instr = mTestData->instr.value; packet.state.xer = mTestData->input.xer.value; packet.state.cr = mTestData->input.cr.value; packet.state.ctr = mTestData->input.ctr; packet.state.r3 = mTestData->input.gpr[0]; packet.state.r4 = mTestData->input.gpr[1]; packet.state.r5 = mTestData->input.gpr[2]; packet.state.r6 = mTestData->input.gpr[3]; packet.state.fpscr = mTestData->input.fpscr.value; packet.state.f1 = mTestData->input.fr[0]; packet.state.f2 = mTestData->input.fr[1]; packet.state.f3 = mTestData->input.fr[2]; packet.state.f4 = mTestData->input.fr[3]; mSocket->send(reinterpret_cast<const char *>(&packet), sizeof(ExecuteGeneralTestPacket)); } void handleTestResult(ExecuteGeneralTestPacket *result) { // Sanity check assert(mTestData->instr.value == result->instr.value()); // Copy the output mTestData->output.xer.value = result->state.xer; mTestData->output.cr.value = result->state.cr; mTestData->output.ctr = result->state.ctr; mTestData->output.gpr[0] = result->state.r3; mTestData->output.gpr[1] = result->state.r4; mTestData->output.gpr[2] = result->state.r5; mTestData->output.gpr[3] = result->state.r6; mTestData->output.fpscr.value = static_cast<uint32_t>(result->state.fpscr); mTestData->output.fr[0] = result->state.f1; mTestData->output.fr[1] = result->state.f2; mTestData->output.fr[2] = result->state.f3; mTestData->output.fr[3] = result->state.f4; // Start next test mTestData++; sendNextTest(); } void onReceivePacket(PacketHeader *packet) { VersionPacket *version; ExecuteGeneralTestPacket *result; switch (packet->command) { case PacketHeader::Version: // Receive version, begin tests version = reinterpret_cast<VersionPacket *>(packet); std::cout << "Server Version: " << Version << ", Client Version: " << version->version << std::endl; std::cout << "Running tests..." << std::endl; sendNextTest(); break; case PacketHeader::ExecuteGeneralTest: // Receive test result result = reinterpret_cast<ExecuteGeneralTestPacket *>(packet); handleTestResult(result); break; } } void onSocketError(Socket *socket, int code) { assert(mSocket == socket); std::cout << "Socket error: " << code << std::endl; } void onSocketDisconnect(Socket *socket) { assert(mSocket == socket); std::cout << "Socket Disconnected" << std::endl; } void onSocketReceive(Socket *socket, const char *buffer, size_t size) { PacketHeader *header; assert(mSocket == socket); if (mCurrentPacket.size() == 0) { assert(size == sizeof(PacketHeader)); // Copy packet to buffer mCurrentPacket.resize(size); header = reinterpret_cast<PacketHeader *>(mCurrentPacket.data()); std::memcpy(header, buffer, size); // Read rest of packet auto read = header->size - size; socket->recvFill(read); } else { // Check we have read rest of packet header = reinterpret_cast<PacketHeader *>(mCurrentPacket.data()); auto totalSize = size + sizeof(PacketHeader); assert(totalSize == header->size); // Read rest of packet mCurrentPacket.resize(totalSize); header = reinterpret_cast<PacketHeader *>(mCurrentPacket.data()); std::memcpy(mCurrentPacket.data() + sizeof(PacketHeader), buffer, size); onReceivePacket(header); // Read next packet mCurrentPacket.clear(); socket->recvFill(sizeof(PacketHeader)); } } private: Socket *mSocket; std::vector<char> mCurrentPacket; std::vector<hwtest::TestFile>::iterator mTestFile; std::vector<hwtest::TestData>::iterator mTestData; }; static std::vector<std::unique_ptr<TestServer>> gTestServers; static void loadTests() { fs::create_directories("tests/cpu/wiiu"); // Read all tests for (auto &entry : fs::directory_iterator("tests/cpu/input")) { std::ifstream file { entry.path().string(), std::ifstream::in | std::ifstream::binary }; gTestSet.emplace_back(); auto &test = gTestSet.back(); test.name = entry.path().filename().string(); // Parse cereal data cereal::BinaryInputArchive input(file); input(test); } } int main(int argc, char **argv) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); loadTests(); NetworkThread thread; auto socket = new Socket(); auto ip = "0.0.0.0"; auto port = "8008"; // On socket error socket->addErrorListener([](Socket *socket, int code) { std::cout << "Listen Socket Error: " << code << std::endl; }); socket->addDisconnectListener([](Socket *socket) { std::cout << "Listen Socket Disconnected" << std::endl; }); // On socket connected, accept pls socket->addAcceptListener([&thread](Socket *socket) { auto newSock = socket->accept(); if (!newSock) { std::cout << "Failed to accept new connection" << std::endl; return; } else { std::cout << "New Connection Accepted" << std::endl; } gTestServers.emplace_back(new TestServer(socket)); thread.addSocket(newSock); }); // Start server if (!socket->listen(ip, port)) { std::cout << "Error starting connect!" << std::endl; return 0; } // Run network thread in main thread std::cout << "Listening on " << ip << ":" << port << std::endl; thread.addSocket(socket); thread.start(); WSACleanup(); return 0; } ================================================ FILE: tests/cpu/libcpu/CMakeLists.txt ================================================ include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(test-libcpu ${SOURCE_FILES} ${HEADER_FILES}) set_target_properties(test-libcpu PROPERTIES FOLDER tests) target_link_libraries(test-libcpu catch2 common libcpu) add_test(NAME tests_libcpu WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMAND test-libcpu) ================================================ FILE: tests/cpu/libcpu/main.cpp ================================================ #define CATCH_CONFIG_MAIN #include <catch.hpp> #include <libcpu/be2_struct.h> TEST_CASE("be_* address of returns virt_ptr") { // "virt_addrof be_val<uint32_t>" returns "virt_ptr<uint32_t>" REQUIRE((std::is_same<virt_ptr<uint32_t>, decltype(virt_addrof(std::declval<be2_val<uint32_t>>()))>::value)); // "virt_addrof be_struct<SomeStructure>" returns "virt_ptr<SomeStructure>" struct SomeStructure { }; REQUIRE((std::is_same<virt_ptr<SomeStructure>, decltype(virt_addrof(std::declval<be2_struct<SomeStructure>>()))>::value)); // "virt_addrof be_array<char, 100>" returns "vitr_ptr<char>" REQUIRE((std::is_same<virt_ptr<char>, decltype(virt_addrof(std::declval<be2_array<char, 100>>()))>::value)); } TEST_CASE("virt_ptr dereference") { // Dereferencing "virt_ptr<uint32_t>" returns "be2_val<uint32_t> &" REQUIRE((std::is_same<be2_val<uint32_t> &, decltype(*std::declval<virt_ptr<uint32_t>>())>::value)); // Dereferencing "virt_ptr<const uint32_t>" returns "const be_val<const uint32_t> &" REQUIRE((std::is_same<const be2_val<const uint32_t> &, decltype(*std::declval<virt_ptr<const uint32_t>>())>::value)); } TEST_CASE("virt_ptr cast") { // virt_ptr<T> can be cast to virt_ptr<void> REQUIRE((std::is_constructible<virt_ptr<void>, virt_ptr<uint32_t>>::value)); // virt_ptr<X> can not be cast to virt_ptr<Y> REQUIRE((!std::is_constructible<virt_ptr<uint32_t>, virt_ptr<void>>::value)); REQUIRE((!std::is_constructible<virt_ptr<uint64_t>, virt_ptr<uint32_t>>::value)); // Assign virt_ptr<uint32_t> to be2_ptr<uint32_t> REQUIRE((std::is_assignable<be2_ptr<uint32_t>, virt_ptr<uint32_t>>::value)); // Can construct virt_ptr<const void> from virt_ptr<void> REQUIRE((std::is_constructible<virt_ptr<const void>, virt_ptr<void>>::value)); } TEST_CASE("be_val values") { // Assign uint32_t to be2_val<uint32_t> REQUIRE((std::is_assignable<be2_val<uint32_t>, uint32_t>::value)); // Array access of be_array<uint32_t, N> returns a be_val<uint32_t> & REQUIRE((std::is_same<be2_val<uint32_t> &, decltype(std::declval<be2_array<uint32_t, 100>>()[1])>::value)); } ================================================ FILE: tests/cpu/runner-achurch/CMakeLists.txt ================================================ include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(runner-achurch ${SOURCE_FILES} ${HEADER_FILES}) set_target_properties(runner-achurch PROPERTIES FOLDER tests) target_link_libraries(runner-achurch common libcpu) add_test(NAME tests_cpu_achurch WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMAND runner-achurch) ================================================ FILE: tests/cpu/runner-achurch/main.cpp ================================================ #include <common/decaf_assert.h> #include <common/log.h> #include <fstream> #include <libcpu/cpu.h> #include <libcpu/cpu_config.h> #include <libcpu/espresso/espresso_disassembler.h> #include <libcpu/mem.h> #include <memory> #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_sinks.h> int runTests() { // Built From http://achurch.org/cpu-tests/ppc750cl.s // powerpc-eabi-as ppc750cl.S -o ppc750cl.o --defsym TEST_SC=0 --defsym HAVE_UGQR=1 --defsym TEST_TRAP=0 // powerpc-eabi-ld ppc750cl.o -o ppc750cl --oformat=binary std::ifstream file { "data/achurch.bin", std::ifstream::in | std::ifstream::binary }; if (!file.is_open()) { gLog->error("Could not open data/achurch.bin"); return -1; } // Calculate the total size of the file file.seekg(0, std::ios::end); auto file_size = file.tellg(); file.seekg(0, std::ios::beg); // Allocate code memory auto baseCodeAddress = cpu::VirtualAddress { 0x01000000u }; auto baseCodePhysicalAddress = cpu::PhysicalAddress { 0x50000000u }; auto codeSize = align_up(static_cast<uint32_t>(file_size), cpu::PageSize); cpu::allocateVirtualAddress(baseCodeAddress, codeSize); cpu::mapMemory(baseCodeAddress, baseCodePhysicalAddress, codeSize, cpu::MapPermission::ReadWrite); // Allocate data memory auto baseDataAddress = cpu::VirtualAddress { 0x03000000u }; auto baseDataPhysicalAddress = cpu::PhysicalAddress { 0x52000000u }; auto dataSize = 0x02000000u; cpu::allocateVirtualAddress(baseDataAddress, dataSize); cpu::mapMemory(baseDataAddress, baseDataPhysicalAddress, dataSize, cpu::MapPermission::ReadWrite); // Read the file directly into PPC memory file.read(mem::translate<char>(baseCodeAddress.getAddress()), file_size); auto core = cpu::this_core::state(); auto scratchMemAddr = baseDataAddress.getAddress() + (dataSize / 2); auto failResultsAddr = baseDataAddress.getAddress(); std::memset(mem::translate<void>(scratchMemAddr), 0, 32 * 1024u); core->nia = baseCodeAddress.getAddress(); core->gpr[3] = 0; core->gpr[4] = scratchMemAddr; core->gpr[5] = failResultsAddr; core->fpr[1].paired0 = 1.0; cpu::this_core::executeSub(); auto failedTests = core->gpr[3]; if (failedTests) { for (uint32_t i = 0; i < failedTests; ++i) { uint32_t failedInstr = mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 0); uint32_t failedAddr = mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 4); uint32_t failedAux[] = { mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 8), mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 12), mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 16), mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 20) }; espresso::Disassembly disassembly; espresso::disassemble(failedInstr, disassembly, failedAddr); gLog->warn(" {:08x} = {:08x} failed - {}", failedAddr, failedInstr, espresso::disassemblyToText(disassembly)); gLog->warn(" {:08x} {:08x} {:08x} {:08x}", failedAux[0], failedAux[1], failedAux[2], failedAux[3]); } gLog->error("Failed {} tests.", failedTests); return static_cast<int>(failedTests); } return 0; } int main(int argc, char *argv[]) { int runResult; auto logger = std::make_shared<spdlog::logger>("logger", std::make_shared<spdlog::sinks::stdout_sink_st>()); logger->set_level(spdlog::level::debug); gLog = logger; auto cpuConfig = cpu::Settings { }; cpuConfig.jit.enabled = true; cpu::setConfig(cpuConfig); cpu::initialise(); // We need to run the tests on a core. cpu::setCoreEntrypointHandler( [&runResult](cpu::Core *core) { if (cpu::this_core::id() == 1) { // Run the tests on only a single core. runResult = runTests(); } }); cpu::start(); cpu::join(); return runResult; } ================================================ FILE: tests/cpu/runner-generated/CMakeLists.txt ================================================ include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(runner-generated ${SOURCE_FILES} ${HEADER_FILES}) set_target_properties(runner-generated PROPERTIES FOLDER tests) target_link_libraries(runner-generated common libcpu libdecaf cereal) add_test(NAME tests_cpu_generated WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMAND runner-generated) ================================================ FILE: tests/cpu/runner-generated/hardwaretests.cpp ================================================ #include "hardwaretests.h" #include <cassert> #include <cfenv> #include <common/bit_cast.h> #include <common/floatutils.h> #include <common/log.h> #include <common/strutils.h> #include <filesystem> #include <fstream> #include <libcpu/cpu.h> #include <libcpu/mem.h> #include <libcpu/espresso/espresso_disassembler.h> #include <libcpu/espresso/espresso_instructionset.h> #include <libcpu/espresso/espresso_registerformats.h> using namespace espresso; static const auto TEST_FPSCR = false; static const auto TEST_FPSCR_FR = false; static const auto TEST_FPSCR_UX = false; static const auto TEST_FMADDSUB = false; namespace hwtest { static void printTestField(InstructionField field, Instruction instr, RegisterState *input, RegisterState *output, cpu::CoreRegs *state) { auto printGPR = [&](uint32_t reg) { assert(reg >= GPR_BASE); gLog->debug("r{:02d} = {:08X} {:08X} {:08X}", reg, input->gpr[reg - GPR_BASE], output->gpr[reg - GPR_BASE], state->gpr[reg]); }; auto printFPR = [&](uint32_t reg) { assert(reg >= FPR_BASE); gLog->debug("f{:02d} = {:16e} {:16e} {:16e}", reg, input->fr[reg - FPR_BASE], output->fr[reg - FPR_BASE], state->fpr[reg].value); gLog->debug(" {:16X} {:16X} {:16X}", bit_cast<uint64_t>(input->fr[reg - FPR_BASE]), bit_cast<uint64_t>(output->fr[reg - FPR_BASE]), bit_cast<uint64_t>(state->fpr[reg].value)); }; switch (field) { case InstructionField::rA: printGPR(instr.rA); break; case InstructionField::rB: printGPR(instr.rB); break; case InstructionField::rD: printGPR(instr.rS); break; case InstructionField::rS: printGPR(instr.rS); break; case InstructionField::frA: printFPR(instr.frA); break; case InstructionField::frB: printFPR(instr.frB); break; case InstructionField::frC: printFPR(instr.frC); break; case InstructionField::frD: printFPR(instr.frD); break; case InstructionField::frS: printFPR(instr.frS); break; case InstructionField::XERC: gLog->debug("xer.ca = {:08X} {:08X} {:08X}", input->xer.ca, output->xer.ca, state->xer.ca); break; case InstructionField::XERSO: gLog->debug("xer.so = {:08X} {:08X} {:08X}", input->xer.so, output->xer.so, state->xer.so); break; case InstructionField::FPSCR: gLog->debug("fpscr = {:08X} {:08x} {:08X}", input->fpscr.value, output->fpscr.value, state->fpscr.value); break; default: break; } } #define CompareFPSCRField(field) \ if (result.field != expected.field) { \ gLog->debug("fpscr." #field " = input {} expected {} found {}", input.field, expected.field, result.field); \ failed = true; \ } // Compare all individual fields in fpscr static bool compareFPSCR(FloatingPointStatusAndControlRegister input, FloatingPointStatusAndControlRegister expected, FloatingPointStatusAndControlRegister result) { auto failed = false; CompareFPSCRField(rn); CompareFPSCRField(ni); CompareFPSCRField(xe); CompareFPSCRField(ze); CompareFPSCRField(ue); CompareFPSCRField(oe); CompareFPSCRField(ve); CompareFPSCRField(vxcvi); CompareFPSCRField(vxsqrt); CompareFPSCRField(vxsoft); CompareFPSCRField(fprf); CompareFPSCRField(fi); CompareFPSCRField(fr); CompareFPSCRField(vxvc); CompareFPSCRField(vximz); CompareFPSCRField(vxzdz); CompareFPSCRField(vxidi); CompareFPSCRField(vxisi); CompareFPSCRField(vxsnan); CompareFPSCRField(xx); CompareFPSCRField(zx); CompareFPSCRField(ux); CompareFPSCRField(ox); CompareFPSCRField(vx); CompareFPSCRField(fex); CompareFPSCRField(fx); return failed; } int runTests(const std::string &path) { uint32_t testsFailed = 0, testsPassed = 0; auto baseAddress = cpu::VirtualAddress { 0x02000000u }; auto basePhysicalAddress = cpu::PhysicalAddress { 0x50000000u }; auto codeSize = 2048u; cpu::allocateVirtualAddress(baseAddress, codeSize); cpu::mapMemory(baseAddress, basePhysicalAddress, codeSize, cpu::MapPermission::ReadWrite); Instruction bclr = encodeInstruction(InstructionID::bclr); bclr.bo = 0x1f; mem::write(baseAddress.getAddress() + 4, bclr.value); auto ec = std::error_code { }; for (auto itr = std::filesystem::directory_iterator { "/tests", ec }; itr != end(itr); ++itr) { std::ifstream file(itr->path().string(), std::ifstream::in | std::ifstream::binary); cereal::BinaryInputArchive cerealInput(file); TestFile testFile; // Parse test file with cereal testFile.name = itr->path().filename().string(); cerealInput(testFile); // Run tests for (auto &test : testFile.tests) { bool failed = false; if (!TEST_FMADDSUB) { auto data = espresso::decodeInstruction(test.instr); switch (data->id) { case InstructionID::fmadd: case InstructionID::fmadds: case InstructionID::fmsub: case InstructionID::fmsubs: case InstructionID::fnmadd: case InstructionID::fnmadds: case InstructionID::fnmsub: case InstructionID::fnmsubs: failed = true; break; } if (failed) { continue; } } // Setup core state from test input cpu::CoreRegs *state = cpu::this_core::state(); memset(state, 0, sizeof(cpu::CoreRegs)); state->cia = 0; state->nia = baseAddress.getAddress(); state->xer = test.input.xer; state->cr = test.input.cr; state->fpscr = test.input.fpscr; state->ctr = test.input.ctr; for (auto i = 0; i < 4; ++i) { state->gpr[i + 3] = test.input.gpr[i]; state->fpr[i + 1].paired0 = test.input.fr[i]; } // Execute test mem::write(baseAddress.getAddress(), test.instr.value); cpu::clearInstructionCache(); cpu::this_core::executeSub(); // Check XER (all bits) if (state->xer.value != test.output.xer.value) { gLog->error("Test failed, xer expected {:08X} found {:08X}", test.output.xer.value, state->xer.value); failed = true; } // Check Condition Register (all bits) if (state->cr.value != test.output.cr.value) { gLog->error("Test failed, cr expected {:08X} found {:08X}", test.output.cr.value, state->cr.value); failed = true; } // Check FPSCR if (TEST_FPSCR) { if (!TEST_FPSCR_FR) { state->fpscr.fr = 0; test.output.fpscr.fr = 0; } if (!TEST_FPSCR_UX) { state->fpscr.ux = 0; test.output.fpscr.ux = 0; } auto state_fpscr = state->fpscr.value; auto test_fpscr = test.output.fpscr.value; if (state_fpscr != test_fpscr) { gLog->error("Test failed, fpscr {:08X} found {:08X}", test.output.fpscr.value, state->fpscr.value); compareFPSCR(test.input.fpscr, state->fpscr, test.output.fpscr); failed = true; } } // Check CTR if (state->ctr != test.output.ctr) { gLog->error("Test failed, ctr expected {:08X} found {:08X}", test.output.ctr, state->ctr); failed = true; } // Check all GPR for (auto i = 0; i < 4; ++i) { auto reg = i + hwtest::GPR_BASE; auto value = state->gpr[reg]; auto expected = test.output.gpr[i]; if (value != expected) { gLog->error("Test failed, r{} expected {:08X} found {:08X}", reg, expected, value); failed = true; } } // Check all FPR for (auto i = 0; i < 4; ++i) { auto reg = i + hwtest::FPR_BASE; auto value = state->fpr[reg].value; auto expected = test.output.fr[i]; if (!is_nan(value) && !is_nan(expected) && !is_infinity(value) && !is_infinity(expected)) { double dval = value / expected; if (dval < 0.999 || dval > 1.001) { gLog->error("Test failed, f{} expected {:16f} found {:16f}", reg, expected, value); failed = true; } } else { if (is_nan(value) && is_nan(expected)) { auto bits = get_float_bits(value); bits.sign = get_float_bits(expected).sign; value = bits.v; } if (bit_cast<uint64_t>(value) != bit_cast<uint64_t>(expected)) { gLog->error("Test failed, f{} expected {:16X} found {:16X}", reg, bit_cast<uint64_t>(expected), bit_cast<uint64_t>(value)); failed = true; } } } if (failed) { Disassembly dis; // Print disassembly disassemble(test.instr, dis, baseAddress.getAddress()); gLog->debug(disassemblyToText(dis)); // Print all test fields gLog->debug("{:08x} Input Hardware Interp", test.instr.value); for (auto field : dis.instruction->read) { printTestField(field, test.instr, &test.input, &test.output, state); } for (auto field : dis.instruction->write) { printTestField(field, test.instr, &test.input, &test.output, state); } for (auto field : dis.instruction->flags) { printTestField(field, test.instr, &test.input, &test.output, state); } gLog->debug(""); ++testsFailed; } else { ++testsPassed; } } } if (testsFailed) { gLog->error("Failed {} of {} tests.", testsFailed, testsFailed + testsPassed); } return testsFailed; } } // namespace hwtest ================================================ FILE: tests/cpu/runner-generated/hardwaretests.h ================================================ #pragma once #include <cereal/types/vector.hpp> #include <cereal/archives/binary.hpp> #include <vector> #include <libcpu/state.h> #include <libcpu/espresso/espresso_instruction.h> #include <libcpu/espresso/espresso_registerformats.h> namespace hwtest { static const auto GPR_BASE = 3; static const auto FPR_BASE = 1; static const auto CRF_BASE = 2; static const auto CRB_BASE = 8; struct RegisterState { espresso::FixedPointExceptionRegister xer; espresso::ConditionRegister cr; espresso::FloatingPointStatusAndControlRegister fpscr; uint32_t ctr; uint32_t gpr[4]; double fr[4]; template <class Archive> void serialize(Archive & ar) { ar(xer.value); ar(cr.value); ar(fpscr.value); ar(ctr); for (auto i = 0; i < 4; ++i) { ar(gpr[i]); } for (auto i = 0; i < 4; ++i) { ar(fr[i]); } } }; struct TestData { espresso::Instruction instr; RegisterState input; RegisterState output; template <class Archive> void serialize(Archive & ar) { ar(instr.value); ar(input); ar(output); } }; struct TestFile { std::string name; std::vector<TestData> tests; template <class Archive> void serialize(Archive & ar) { ar(tests); } }; int runTests(const std::string &path); } // namespace hwtest ================================================ FILE: tests/cpu/runner-generated/main.cpp ================================================ #include "hardwaretests.h" #include <common/decaf_assert.h> #include <common/log.h> #include <libcpu/cpu.h> #include <libcpu/cpu_config.h> #include <libcpu/mem.h> #include <memory> #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_sinks.h> static int runResult; int main(int argc, char *argv[]) { auto logger = std::make_shared<spdlog::logger>("logger", std::make_shared<spdlog::sinks::stdout_sink_st>()); logger->set_level(spdlog::level::debug); gLog = logger; auto cpuConfig = cpu::Settings { }; cpuConfig.jit.enabled = true; cpu::setConfig(cpuConfig); cpu::initialise(); // We need to run the tests on a core. cpu::setCoreEntrypointHandler( [](cpu::Core *core) { if (cpu::this_core::id() == 1) { // Run the tests on only a single core. runResult = hwtest::runTests("data/wiiu"); } }); cpu::start(); cpu::join(); return runResult; } ================================================ FILE: tests/gpu/CMakeLists.txt ================================================ project(tests-gpu) add_subdirectory("tiling") ================================================ FILE: tests/gpu/tiling/CMakeLists.txt ================================================ include_directories(".") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(test-gpu-tiling ${SOURCE_FILES} ${HEADER_FILES}) set_target_properties(test-gpu-tiling PROPERTIES FOLDER tests) target_link_libraries(test-gpu-tiling addrlib catch2 common libcpu libgpu) if(DECAF_VULKAN) target_link_libraries(test-gpu-tiling vulkan SPIRV) endif() add_test(NAME gpu-tiling WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMAND test-gpu-tiling) ================================================ FILE: tests/gpu/tiling/addrlib_helpers.h ================================================ #pragma once #include <addrlib/addrinterface.h> #include <catch.hpp> #include <cstring> #include <libgpu/gpu7_tiling.h> class AddrLib { public: AddrLib() { ADDR_CREATE_INPUT input; ADDR_CREATE_OUTPUT output; std::memset(&input, 0, sizeof(input)); std::memset(&output, 0, sizeof(output)); input.size = sizeof(ADDR_CREATE_INPUT); output.size = sizeof(ADDR_CREATE_OUTPUT); input.chipEngine = CIASICIDGFXENGINE_R600; input.chipFamily = 0x51; input.chipRevision = 71; input.createFlags.fillSizeFields = 1; input.regValue.gbAddrConfig = 0x44902; input.callbacks.allocSysMem = &addrLibAlloc; input.callbacks.freeSysMem = &addrLibFree; REQUIRE(AddrCreate(&input, &output) == ADDR_OK); mHandle = output.hLib; } ~AddrLib() { if (mHandle) { AddrDestroy(mHandle); } } void untileSlices(const gpu7::tiling::SurfaceDescription &desc, uint32_t mipLevel, const uint8_t *src, uint8_t *dst, uint32_t firstSlice, uint32_t numSlices) { for (uint32_t sample = 0; sample < desc.numSamples; ++sample) { for (uint32_t slice = firstSlice; slice < firstSlice + numSlices; ++slice) { const auto info = computeSurfaceInfo(desc, slice, mipLevel); auto srcAddrInput = getTiledAddrFromCoordInput(desc, info); srcAddrInput.sample = sample; srcAddrInput.slice = slice; auto dstAddrInput = getUntiledAddrFromCoordInput(desc, info); dstAddrInput.sample = sample; dstAddrInput.slice = slice; copySurfacePixels(src, srcAddrInput, dst, dstAddrInput); } } } void tileSlices(const gpu7::tiling::SurfaceDescription &desc, uint32_t mipLevel, uint8_t *src, uint8_t *dst, uint32_t firstSlice, uint32_t numSlices) { for (uint32_t sample = 0; sample < desc.numSamples; ++sample) { for (uint32_t slice = firstSlice; slice < firstSlice + numSlices; ++slice) { const auto info = computeSurfaceInfo(desc, 0, mipLevel); auto srcAddrInput = getUntiledAddrFromCoordInput(desc, info); srcAddrInput.sample = sample; srcAddrInput.slice = slice; auto dstAddrInput = getTiledAddrFromCoordInput(desc, info); dstAddrInput.sample = sample; dstAddrInput.slice = slice; copySurfacePixels(src, srcAddrInput, dst, dstAddrInput); } } } ADDR_COMPUTE_SURFACE_INFO_OUTPUT computeSurfaceInfo(const gpu7::tiling::SurfaceDescription &surface, uint32_t slice, uint32_t mipLevel) { auto output = ADDR_COMPUTE_SURFACE_INFO_OUTPUT { }; output.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT); auto input = ADDR_COMPUTE_SURFACE_INFO_INPUT { }; input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT); input.tileMode = static_cast<AddrTileMode > (surface.tileMode); input.format = static_cast<AddrFormat>(surface.format); input.bpp = surface.bpp; input.numSamples = surface.numSamples; input.numFrags = surface.numFrags; input.mipLevel = mipLevel; input.slice = slice; input.numSlices = surface.numSlices; input.width = std::max(surface.width >> mipLevel, 1u); input.height = std::max(surface.height >> mipLevel, 1u); input.flags.inputBaseMap = mipLevel == 0 ? 1 : 0; if (surface.use & gpu7::tiling::SurfaceUse::ScanBuffer) { input.flags.display = 1; } if (surface.use & gpu7::tiling::SurfaceUse::DepthBuffer) { input.flags.depth = 1; } if (surface.dim == gpu7::tiling::SurfaceDim::Texture3D) { input.flags.volume = 1; input.numSlices = std::max(surface.numSlices >> mipLevel, 1u); } if (surface.dim == gpu7::tiling::SurfaceDim::TextureCube) { input.flags.cube = 1; } REQUIRE(AddrComputeSurfaceInfo(mHandle, &input, &output) == ADDR_OK); return output; } private: static void * addrLibAlloc(const ADDR_ALLOCSYSMEM_INPUT *pInput) { return std::malloc(pInput->sizeInBytes); } static ADDR_E_RETURNCODE addrLibFree(const ADDR_FREESYSMEM_INPUT *pInput) { std::free(pInput->pVirtAddr); return ADDR_OK; } void copySurfacePixels(const uint8_t *src, ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &srcAddrInput, uint8_t *dst, ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &dstAddrInput) { auto srcAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { }; auto dstAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { }; srcAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT); dstAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT); assert(srcAddrInput.bpp == dstAddrInput.bpp); assert(srcAddrInput.pitch == dstAddrInput.pitch); assert(srcAddrInput.height == dstAddrInput.height); auto bytesPerElem = dstAddrInput.bpp / 8; for (auto y = 0u; y < dstAddrInput.height; ++y) { for (auto x = 0u; x < dstAddrInput.pitch; ++x) { dstAddrInput.x = x; dstAddrInput.y = y; AddrComputeSurfaceAddrFromCoord(mHandle, &dstAddrInput, &dstAddrOutput); srcAddrInput.x = x; srcAddrInput.y = y; AddrComputeSurfaceAddrFromCoord(mHandle, &srcAddrInput, &srcAddrOutput); std::memcpy(dst + dstAddrOutput.addr, src + srcAddrOutput.addr, bytesPerElem); } } } ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT getUntiledAddrFromCoordInput(const gpu7::tiling::SurfaceDescription &desc, const ADDR_COMPUTE_SURFACE_INFO_OUTPUT &info) { auto input = getTiledAddrFromCoordInput(desc, info); input.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL; input.bankSwizzle = 0; input.pipeSwizzle = 0; return input; } ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT getTiledAddrFromCoordInput(const gpu7::tiling::SurfaceDescription &desc, const ADDR_COMPUTE_SURFACE_INFO_OUTPUT &info) { auto input = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT { }; input.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT); input.bpp = info.bpp; input.pitch = info.pitch; input.height = info.height; input.numSlices = info.depth; input.numSamples = desc.numSamples; input.tileMode = info.tileMode; input.isDepth = !!(desc.use & gpu7::tiling::SurfaceUse::DepthBuffer); input.tileBase = 0; input.compBits = 0; input.numFrags = desc.numFrags; input.bankSwizzle = desc.bankSwizzle; input.pipeSwizzle = desc.pipeSwizzle; return input; } private: ADDR_HANDLE mHandle = nullptr; }; ================================================ FILE: tests/gpu/tiling/cpu_tiling_test.cpp ================================================ #include "tiling_tests.h" #include "addrlib_helpers.h" #include "test_helpers.h" #include <common/align.h> #include <libgpu/gpu7_tiling_cpu.h> static inline void compareTilingToAddrLib(const gpu7::tiling::SurfaceDescription& desc, std::vector<uint8_t>& input, uint32_t firstSlice, uint32_t numSlices) { auto addrLib = AddrLib { }; auto alibUntiled = std::vector<uint8_t> { }; auto gpu7Untiled = std::vector<uint8_t> { }; auto alibTiled = std::vector<uint8_t> { }; auto gpu7Tiled = std::vector<uint8_t> { }; // Compute surface info auto alibInfo = addrLib.computeSurfaceInfo(desc, 0, 0); auto gpu7Info = gpu7::tiling::computeSurfaceInfo(desc, 0); REQUIRE(gpu7Info.surfSize == alibInfo.surfSize); REQUIRE(input.size() >= gpu7Info.surfSize); alibUntiled.resize(alibInfo.surfSize); alibTiled.resize(alibInfo.surfSize); gpu7Untiled.resize(gpu7Info.surfSize); gpu7Tiled.resize(gpu7Info.surfSize); // AddrLib { addrLib.untileSlices(desc, 0, input.data(), alibUntiled.data(), firstSlice, numSlices); addrLib.tileSlices(desc, 0, alibUntiled.data(), alibTiled.data(), firstSlice, numSlices); } // GPU7 { auto retileInfo = gpu7::tiling::computeRetileInfo(gpu7Info); auto tiledFirstSliceIndex = align_down(firstSlice, retileInfo.microTileThickness); auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes; auto untiledSliceOffset = firstSlice * retileInfo.thinSliceBytes; gpu7::tiling::cpu::untile(retileInfo, gpu7Untiled.data() + untiledSliceOffset, input.data() + tiledSliceOffset, firstSlice, numSlices); gpu7::tiling::cpu::tile(retileInfo, gpu7Untiled.data() + untiledSliceOffset, gpu7Tiled.data() + tiledSliceOffset, firstSlice, numSlices); } CHECK(compareImages(gpu7Untiled, alibUntiled)); CHECK(compareImages(gpu7Tiled, alibTiled)); } TEST_CASE("cpuTiling") { for (auto& layout : sTestLayout) { SECTION(fmt::format("{}x{}x{} s{}n{}", layout.width, layout.height, layout.depth, layout.testFirstSlice, layout.testNumSlices)) { for (auto& mode : sTestTilingMode) { SECTION(fmt::format("{}", tileModeToString(mode.tileMode))) { for (auto& format : sTestFormats) { SECTION(fmt::format("{}bpp{}", format.bpp, format.depth ? " depth" : "")) { auto surface = gpu7::tiling::SurfaceDescription { }; surface.tileMode = mode.tileMode; surface.format = format.format; surface.bpp = format.bpp; surface.width = layout.width; surface.height = layout.height; surface.numSlices = layout.depth; surface.numSamples = 1u; surface.numLevels = 1u; surface.bankSwizzle = 0u; surface.pipeSwizzle = 0u; surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray; surface.use = format.depth ? gpu7::tiling::SurfaceUse::DepthBuffer : gpu7::tiling::SurfaceUse::None; compareTilingToAddrLib(surface, sRandomData, layout.testFirstSlice, layout.testNumSlices); } } } } } } } struct ALibPendingCpuPerfEntry { gpu7::tiling::SurfaceDescription desc; uint32_t firstSlice; uint32_t numSlices; ADDR_COMPUTE_SURFACE_INFO_OUTPUT info; }; TEST_CASE("alibTilingPerf", "[!benchmark]") { // Set up AddrLib to generate data to test against auto addrLib = AddrLib { }; // Get some random data to use auto& untiled = sRandomData; // Some place to store pending tests std::vector<ALibPendingCpuPerfEntry> pendingTests; // Generate all the test cases to run auto& layout = sPerfTestLayout; for (auto& mode : sTestTilingMode) { for (auto& format : sTestFormats) { auto surface = gpu7::tiling::SurfaceDescription {}; surface.tileMode = mode.tileMode; surface.format = format.format; surface.bpp = format.bpp; surface.width = layout.width; surface.height = layout.height; surface.numSlices = layout.depth; surface.numSamples = 1u; surface.numLevels = 1u; surface.bankSwizzle = 0u; surface.pipeSwizzle = 0u; surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray; surface.use = format.depth ? gpu7::tiling::SurfaceUse::DepthBuffer : gpu7::tiling::SurfaceUse::None; auto info = addrLib.computeSurfaceInfo(surface, 0, 0); ALibPendingCpuPerfEntry test; test.desc = surface; test.info = info; test.firstSlice = layout.testFirstSlice; test.numSlices = layout.testNumSlices; pendingTests.push_back(test); } } auto addrLibImage = std::vector<uint8_t> { }; addrLibImage.resize(untiled.size()); BENCHMARK(fmt::format("processing ({} retiles)", pendingTests.size())) { for (auto &test : pendingTests) { // Compare image addrLib.untileSlices(test.desc, 0, untiled.data(), addrLibImage.data(), test.firstSlice, test.numSlices); } }; } struct PendingCpuPerfEntry { gpu7::tiling::SurfaceDescription desc; uint32_t firstSlice; uint32_t numSlices; gpu7::tiling::SurfaceInfo info; }; TEST_CASE("cpuTilingPerf", "[!benchmark]") { // Set up AddrLib to generate data to test against auto addrLib = AddrLib { }; // Get some random data to use auto& untiled = sRandomData; // Some place to store pending tests std::vector<PendingCpuPerfEntry> pendingTests; // Generate all the test cases to run auto& layout = sPerfTestLayout; for (auto& mode : sTestTilingMode) { for (auto& format : sTestFormats) { auto surface = gpu7::tiling::SurfaceDescription {}; surface.tileMode = mode.tileMode; surface.format = format.format; surface.bpp = format.bpp; surface.width = layout.width; surface.height = layout.height; surface.numSlices = layout.depth; surface.numSamples = 1u; surface.numLevels = 1u; surface.bankSwizzle = 0u; surface.pipeSwizzle = 0u; surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray; surface.use = format.depth ? gpu7::tiling::SurfaceUse::DepthBuffer : gpu7::tiling::SurfaceUse::None; auto info = gpu7::tiling::computeSurfaceInfo(surface, 0); PendingCpuPerfEntry test; test.desc = surface; test.info = info; test.firstSlice = layout.testFirstSlice; test.numSlices = layout.testNumSlices; pendingTests.push_back(test); } } auto tiledImage = std::vector<uint8_t> { }; tiledImage.resize(untiled.size()); static constexpr auto TestIterMulti = 10; BENCHMARK(fmt::format("processing ({} retiles)", pendingTests.size() * TestIterMulti)) { for (auto i = 0; i < TestIterMulti; ++i) { for (auto& test : pendingTests) { auto retileInfo = gpu7::tiling::computeRetileInfo(test.info); auto tiledFirstSliceIndex = align_down(test.firstSlice, retileInfo.microTileThickness); auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes; auto untiledSliceOffset = test.firstSlice * retileInfo.thinSliceBytes; gpu7::tiling::cpu::untile(retileInfo, untiled.data() + untiledSliceOffset, tiledImage.data() + tiledSliceOffset, test.firstSlice, test.numSlices); } } }; } ================================================ FILE: tests/gpu/tiling/test_helpers.h ================================================ #pragma once #include <catch.hpp> #include <fmt/core.h> #include <random> #include <vector> static inline std::vector<uint8_t> generateRandomData(size_t size) { std::mt19937 eng { 0x0DECAF10 }; std::uniform_int_distribution<uint32_t> urd { 0, 255 }; std::vector<uint8_t> result; result.resize(size); for (auto i = 0; i < size; ++i) { result[i] = static_cast<uint8_t>(urd(eng)); } return result; } static inline bool compareImages(const std::vector<uint8_t> &data, const std::vector<uint8_t> &reference) { REQUIRE(data.size() == reference.size()); for (auto i = 0u; i < data.size(); ++i) { if (data[i] != reference[i]) { WARN(fmt::format("Difference at offset {}, 0x{:02X} != 0x{:02X}", i, data[i], reference[i])); return false; } } return true; } ================================================ FILE: tests/gpu/tiling/tiling_test.cpp ================================================ #define CATCH_CONFIG_RUNNER #include <catch.hpp> #include "test_helpers.h" #include <spdlog/spdlog.h> #ifdef DECAF_VULKAN bool vulkanBeforeStart(); bool vulkanAfterComplete(); #else bool vulkanBeforeStart() { return true; } bool vulkanAfterComplete() { return true; } #endif std::vector<uint8_t> sRandomData = generateRandomData(32 * 1024 * 1024); int main(int argc, char* argv[]) { // Set up a Vulkan instance if (!vulkanBeforeStart()) { printf("Could not initialize Vulkan\n"); return -1; } // Run the test session int result = Catch::Session().run(argc, argv); // Shut down our Vulkan instance if (!vulkanAfterComplete()) { printf("Failed to shut down Vulkan\n"); } return result; } ================================================ FILE: tests/gpu/tiling/tiling_tests.h ================================================ #pragma once #include <addrlib/addrinterface.h> #include <catch.hpp> #include <random> #include <spdlog/spdlog.h> #include <vector> #include <libgpu/gpu7_tiling.h> extern std::vector<uint8_t> sRandomData; struct TestLayout { uint32_t width; uint32_t height; uint32_t depth; uint32_t testFirstSlice; uint32_t testNumSlices; }; struct TestFormat { gpu7::tiling::DataFormat format; uint32_t bpp; bool depth; }; struct TestTilingMode { gpu7::tiling::TileMode tileMode; }; static constexpr TestLayout sTestLayout[] = { { 1u, 1u, 1u, 0u, 1u }, { 1u, 1u, 11u, 5u, 5u }, { 338u, 309u, 1u, 0u, 1u }, { 338u, 309u, 11u, 5u, 5u }, // The variants above cover these, but they are useful for // debugging various errors in the algorithms. The matrix // is already huge though, so disabling by default. //{ 338u, 309u, 4u, 0u, 4u }, }; static constexpr TestLayout sPerfTestLayout = { 338u, 309u, 8u, 0u, 8u }; static constexpr TestTilingMode sTestTilingMode[] = { { gpu7::tiling::TileMode::Micro1DTiledThin1 }, { gpu7::tiling::TileMode::Micro1DTiledThick }, { gpu7::tiling::TileMode::Macro2DTiledThin1 }, { gpu7::tiling::TileMode::Macro2DTiledThin2 }, { gpu7::tiling::TileMode::Macro2DTiledThin4 }, { gpu7::tiling::TileMode::Macro2DTiledThick }, { gpu7::tiling::TileMode::Macro2BTiledThin1 }, { gpu7::tiling::TileMode::Macro2BTiledThin2 }, { gpu7::tiling::TileMode::Macro2BTiledThin4 }, { gpu7::tiling::TileMode::Macro2BTiledThick }, { gpu7::tiling::TileMode::Macro3DTiledThin1 }, { gpu7::tiling::TileMode::Macro3DTiledThick }, { gpu7::tiling::TileMode::Macro3BTiledThin1 }, { gpu7::tiling::TileMode::Macro3BTiledThick }, }; static constexpr TestFormat sTestFormats[] = { { gpu7::tiling::DataFormat::FMT_8, 8u, false }, { gpu7::tiling::DataFormat::FMT_8_8, 16u, false }, { gpu7::tiling::DataFormat::FMT_8_8_8_8, 32u, false }, { gpu7::tiling::DataFormat::FMT_32_32, 64u, false }, { gpu7::tiling::DataFormat::FMT_32_32_32_32, 128u, false }, //{ gpu7::tiling::DataFormat::FMT_16, 16u, true }, //{ gpu7::tiling::DataFormat::FMT_32, 32u, true }, //{ gpu7::tiling::DataFormat::FMT_X24_8_32_FLOAT, 64u, true }, }; static const char* tileModeToString(gpu7::tiling::TileMode mode) { switch (mode) { case gpu7::tiling::TileMode::LinearGeneral: return "LinearGeneral"; case gpu7::tiling::TileMode::LinearAligned: return "LinearAligned"; case gpu7::tiling::TileMode::Micro1DTiledThin1: return "Tiled1DThin1"; case gpu7::tiling::TileMode::Micro1DTiledThick: return "Tiled1DThick"; case gpu7::tiling::TileMode::Macro2DTiledThin1: return "Tiled2DThin1"; case gpu7::tiling::TileMode::Macro2DTiledThin2: return "Tiled2DThin2"; case gpu7::tiling::TileMode::Macro2DTiledThin4: return "Tiled2DThin4"; case gpu7::tiling::TileMode::Macro2DTiledThick: return "Tiled2DThick"; case gpu7::tiling::TileMode::Macro2BTiledThin1: return "Tiled2BThin1"; case gpu7::tiling::TileMode::Macro2BTiledThin2: return "Tiled2BThin2"; case gpu7::tiling::TileMode::Macro2BTiledThin4: return "Tiled2BThin4"; case gpu7::tiling::TileMode::Macro2BTiledThick: return "Tiled2BThick"; case gpu7::tiling::TileMode::Macro3DTiledThin1: return "Tiled3DThin1"; case gpu7::tiling::TileMode::Macro3DTiledThick: return "Tiled3DThick"; case gpu7::tiling::TileMode::Macro3BTiledThin1: return "Tiled3BThin1"; case gpu7::tiling::TileMode::Macro3BTiledThick: return "Tiled3BThick"; default: FAIL(fmt::format("Unknown tiling mode {}", static_cast<int>(mode))); return "Unknown"; } } ================================================ FILE: tests/gpu/tiling/vulkan_helpers.cpp ================================================ #ifdef DECAF_VULKAN #include "vulkan_helpers.h" static constexpr bool ENABLE_VALIDATION = false; vk::Instance gVulkan = {}; vk::PhysicalDevice gPhysDevice = {}; vk::Device gDevice = {}; vk::Queue gQueue = {}; uint32_t gQueueFamilyIndex = static_cast<uint32_t>(-1); vk::CommandPool gCommandPool = {}; static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { platform::debugLog( fmt::format("Vulkan Debug Report: {}, {}, {}, {}, {}, {}, {}\n", vk::to_string(vk::DebugReportFlagsEXT(flags)), vk::to_string(vk::DebugReportObjectTypeEXT(objectType)), object, location, messageCode, pLayerPrefix, pMessage)); if (flags == VK_DEBUG_REPORT_WARNING_BIT_EXT || flags == VK_DEBUG_REPORT_ERROR_BIT_EXT) { platform::debugBreak(); } return VK_FALSE; } bool initialiseVulkan() { // Create our instance std::vector<const char *> instanceLayers = { }; std::vector<const char *> instanceExtensions = { }; if (ENABLE_VALIDATION) { instanceLayers.push_back("VK_LAYER_KHRONOS_validation"); instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); } vk::ApplicationInfo appDesc = {}; appDesc.pApplicationName = "gpu-tile-perf"; appDesc.applicationVersion = 0; appDesc.pEngineName = ""; appDesc.engineVersion = 0; appDesc.apiVersion = VK_API_VERSION_1_0;; vk::InstanceCreateInfo instanceDesc = {}; instanceDesc.pApplicationInfo = &appDesc; instanceDesc.enabledLayerCount = static_cast<uint32_t>(instanceLayers.size()); instanceDesc.ppEnabledLayerNames = instanceLayers.data(); instanceDesc.enabledExtensionCount = static_cast<uint32_t>(instanceExtensions.size()); instanceDesc.ppEnabledExtensionNames = instanceExtensions.data(); gVulkan = vk::createInstance(instanceDesc); // Get our Physical Device auto physDevices = gVulkan.enumeratePhysicalDevices(); gPhysDevice = physDevices[0]; std::vector<const char*> deviceLayers = { }; std::vector<const char*> deviceExtensions = { }; if (ENABLE_VALIDATION) { deviceLayers.push_back("VK_LAYER_KHRONOS_validation"); } // Find an appropriate queue auto queueFamilyProps = gPhysDevice.getQueueFamilyProperties(); uint32_t queueFamilyIndex = 0; for (; queueFamilyIndex < queueFamilyProps.size(); ++queueFamilyIndex) { auto &qfp = queueFamilyProps[queueFamilyIndex]; if (!(qfp.queueFlags & (vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer | vk::QueueFlagBits::eCompute))) { continue; } break; } if (queueFamilyIndex >= queueFamilyProps.size()) { printf("Failed to find a suitable Vulkan queue to use.\n"); return false; } std::array<float, 1> queuePriorities = { 0.0f }; vk::DeviceQueueCreateInfo deviceQueueCreateInfo( vk::DeviceQueueCreateFlags(), queueFamilyIndex, static_cast<uint32_t>(queuePriorities.size()), queuePriorities.data()); vk::PhysicalDeviceFeatures deviceFeatures; vk::DeviceCreateInfo deviceDesc = { }; deviceDesc.queueCreateInfoCount = 1; deviceDesc.pQueueCreateInfos = &deviceQueueCreateInfo; deviceDesc.enabledLayerCount = static_cast<uint32_t>(deviceLayers.size()); deviceDesc.ppEnabledLayerNames = deviceLayers.data(); deviceDesc.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); deviceDesc.ppEnabledExtensionNames = deviceExtensions.data(); deviceDesc.pEnabledFeatures = &deviceFeatures; deviceDesc.pNext = nullptr; gDevice = gPhysDevice.createDevice(deviceDesc); // Pick our queue gQueue = gDevice.getQueue(queueFamilyIndex, 0); gQueueFamilyIndex = queueFamilyIndex; // Grab a command pool gCommandPool = gDevice.createCommandPool( vk::CommandPoolCreateInfo( vk::CommandPoolCreateFlagBits::eTransient, gQueueFamilyIndex)); // Set up our debug reporting callback vk::DispatchLoaderDynamic vkDynLoader; vkDynLoader.init(gVulkan, ::vkGetInstanceProcAddr); if (vkDynLoader.vkCreateDebugReportCallbackEXT) { vk::DebugReportCallbackCreateInfoEXT dbgReportDesc; dbgReportDesc.flags = vk::DebugReportFlagBitsEXT::eDebug | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::ePerformanceWarning; dbgReportDesc.pfnCallback = debugMessageCallback; dbgReportDesc.pUserData = nullptr; gVulkan.createDebugReportCallbackEXT(dbgReportDesc, nullptr, vkDynLoader); } return true; } bool shutdownVulkan() { return true; } SsboBuffer allocateSsboBuffer(uint32_t size, SsboBufferUsage usage) { auto findMemoryType = [&](uint32_t typeFilter, vk::MemoryPropertyFlags props) { auto memProps = gPhysDevice.getMemoryProperties(); for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) { if ((typeFilter & (1 << i)) && (memProps.memoryTypes[i].propertyFlags & props) == props) { return i; } } printf("Failed to find suitable Vulkan memory type.\n"); throw std::runtime_error("invalid memory type"); }; vk::BufferCreateInfo bufferDesc; bufferDesc.size = size; bufferDesc.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst; bufferDesc.sharingMode = vk::SharingMode::eExclusive; bufferDesc.queueFamilyIndexCount = 1; bufferDesc.pQueueFamilyIndices = &gQueueFamilyIndex; auto buffer = gDevice.createBuffer(bufferDesc); auto bufferMemReqs = gDevice.getBufferMemoryRequirements(buffer); // These memory properties are stolen from VMA vk::MemoryPropertyFlags memoryProps; if (usage == SsboBufferUsage::Gpu) { memoryProps |= vk::MemoryPropertyFlagBits::eDeviceLocal; } else if (usage == SsboBufferUsage::CpuToGpu) { memoryProps |= vk::MemoryPropertyFlagBits::eHostVisible; } else if (usage == SsboBufferUsage::GpuToCpu) { memoryProps |= vk::MemoryPropertyFlagBits::eHostVisible; memoryProps |= vk::MemoryPropertyFlagBits::eHostCoherent; memoryProps |= vk::MemoryPropertyFlagBits::eHostCached; } vk::MemoryAllocateInfo allocDesc; allocDesc.allocationSize = bufferMemReqs.size; allocDesc.memoryTypeIndex = findMemoryType(bufferMemReqs.memoryTypeBits, memoryProps); auto bufferMem = gDevice.allocateMemory(allocDesc); gDevice.bindBufferMemory(buffer, bufferMem, 0); return SsboBuffer { buffer, bufferMem }; } void freeSsboBuffer(SsboBuffer buffer) { gDevice.destroyBuffer(buffer.buffer); gDevice.freeMemory(buffer.memory); } void uploadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size) { auto mappedPtr = gDevice.mapMemory(buffer.memory, 0, size); memcpy(mappedPtr, data, size); gDevice.flushMappedMemoryRanges({ vk::MappedMemoryRange{ buffer.memory, 0, size } }); gDevice.unmapMemory(buffer.memory); } void downloadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size) { auto mappedPtr = gDevice.mapMemory(buffer.memory, 0, size); gDevice.invalidateMappedMemoryRanges({ vk::MappedMemoryRange{ buffer.memory, 0, size } }); memcpy(data, mappedPtr, size); gDevice.unmapMemory(buffer.memory); } SyncCmdBuffer allocSyncCmdBuffer() { // Allocate a command buffer to use vk::CommandBufferAllocateInfo cmdBufferDesc = { }; cmdBufferDesc.commandPool = gCommandPool; cmdBufferDesc.level = vk::CommandBufferLevel::ePrimary; cmdBufferDesc.commandBufferCount = 1; auto cmdBuffer = gDevice.allocateCommandBuffers(cmdBufferDesc)[0]; // Preallocate a fence for executing auto waitFence = gDevice.createFence(vk::FenceCreateInfo {}); // Return our object SyncCmdBuffer syncCmdBuffer; syncCmdBuffer.cmds = cmdBuffer; syncCmdBuffer.fence = waitFence; return syncCmdBuffer; } void freeSyncCmdBuffer(SyncCmdBuffer cmdBuffer) { // Free our temporary fence gDevice.destroyFence(cmdBuffer.fence); // Free our command buffer gDevice.freeCommandBuffers(gCommandPool, { cmdBuffer.cmds }); } void beginSyncCmdBuffer(SyncCmdBuffer cmdBuffer) { // Start recording our command buffer cmdBuffer.cmds.begin( vk::CommandBufferBeginInfo( vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); } void endSyncCmdBuffer(SyncCmdBuffer cmdBuffer) { // End recording our command buffer cmdBuffer.cmds.end(); } void execSyncCmdBuffer(SyncCmdBuffer cmdBuffer) { // Submit this command buffer and wait for completion vk::SubmitInfo submitDesc; submitDesc.commandBufferCount = 1; submitDesc.pCommandBuffers = &cmdBuffer.cmds; gQueue.submit(submitDesc, cmdBuffer.fence); // Wait for the command buffer to complete gDevice.waitForFences({ cmdBuffer.fence }, true, -1); } void globalVkMemoryBarrier(vk::CommandBuffer cmdBuffer, vk::AccessFlags srcAccessMask, vk::AccessFlags dstAccessMask) { // Barrier our host writes the transfer reads cmdBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags(), { vk::MemoryBarrier(srcAccessMask, dstAccessMask) }, {}, {}, {} ); } #endif ================================================ FILE: tests/gpu/tiling/vulkan_helpers.h ================================================ #pragma once #ifdef DECAF_VULKAN #include <common/platform.h> #include <common/platform_debug.h> #include <common/vulkan_hpp.h> #include <fmt/core.h> extern vk::Instance gVulkan; extern vk::PhysicalDevice gPhysDevice; extern vk::Device gDevice; extern vk::Queue gQueue; extern uint32_t gQueueFamilyIndex; extern vk::CommandPool gCommandPool; enum class SsboBufferUsage { Gpu, CpuToGpu, GpuToCpu }; struct SsboBuffer { vk::Buffer buffer; vk::DeviceMemory memory; }; struct SyncCmdBuffer { vk::CommandBuffer cmds; vk::Fence fence; }; bool initialiseVulkan(); bool shutdownVulkan(); SsboBuffer allocateSsboBuffer(uint32_t size, SsboBufferUsage usage); void freeSsboBuffer(SsboBuffer buffer); void uploadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size); void downloadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size); SyncCmdBuffer allocSyncCmdBuffer(); void freeSyncCmdBuffer(SyncCmdBuffer cmdBuffer); void beginSyncCmdBuffer(SyncCmdBuffer cmdBuffer); void endSyncCmdBuffer(SyncCmdBuffer cmdBuffer); void execSyncCmdBuffer(SyncCmdBuffer cmdBuffer); void globalVkMemoryBarrier(vk::CommandBuffer cmdBuffer, vk::AccessFlags srcAccessMask, vk::AccessFlags dstAccessMask); #endif // DECAF_VULKAN ================================================ FILE: tests/gpu/tiling/vulkan_tiling_test.cpp ================================================ #ifdef DECAF_VULKAN #include "tiling_tests.h" #include "addrlib_helpers.h" #include "test_helpers.h" #include <common/align.h> #include <libgpu/gpu7_tiling_vulkan.h> #include <vector> #include "vulkan_helpers.h" static gpu7::tiling::vulkan::Retiler gVkRetiler; static inline void compareTilingToAddrLib(const gpu7::tiling::SurfaceDescription& desc, std::vector<uint8_t>& input, uint32_t firstSlice, uint32_t numSlices) { // Set up AddrLib to generate data to test against auto addrLib = AddrLib { }; auto alibUntiled = std::vector<uint8_t> { }; auto gpu7Untiled = std::vector<uint8_t> { }; auto alibTiled = std::vector<uint8_t> { }; auto gpu7Tiled = std::vector<uint8_t> { }; // Compute surface info auto alibInfo = addrLib.computeSurfaceInfo(desc, 0, 0); auto gpu7Info = gpu7::tiling::computeSurfaceInfo(desc, 0); REQUIRE(gpu7Info.surfSize == alibInfo.surfSize); REQUIRE(input.size() >= gpu7Info.surfSize); alibUntiled.resize(alibInfo.surfSize); alibTiled.resize(alibInfo.surfSize); gpu7Untiled.resize(gpu7Info.surfSize); gpu7Tiled.resize(gpu7Info.surfSize); // AddrLib { addrLib.untileSlices(desc, 0, input.data(), alibUntiled.data(), firstSlice, numSlices); addrLib.tileSlices(desc, 0, alibUntiled.data(), alibTiled.data(), firstSlice, numSlices); } // Get the sizes we will use for our buffers. We oversize the work // buffers to avoid errors causing buffer overruns which crash my GPU. auto surfSize = gpu7Info.surfSize; auto uploadSize = gpu7Info.surfSize; auto workSize = gpu7Info.surfSize * 2; // Create input/output buffers auto uploadBuffer = allocateSsboBuffer(uploadSize, SsboBufferUsage::CpuToGpu); auto inputBuffer = allocateSsboBuffer(workSize, SsboBufferUsage::Gpu); auto untiledOutputBuffer = allocateSsboBuffer(workSize, SsboBufferUsage::Gpu); auto tiledOutputBuffer = allocateSsboBuffer(workSize, SsboBufferUsage::Gpu); auto untiledDownloadBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::CpuToGpu); auto tiledDownloadBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::CpuToGpu); // Upload to the upload buffer uploadSsboBuffer(uploadBuffer, input.data(), uploadSize); { // Allocate a command buffer and fence auto cmdBuffer = allocSyncCmdBuffer(); beginSyncCmdBuffer(cmdBuffer); // Barrier our host writes the transfer reads globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eHostWrite, vk::AccessFlagBits::eTransferRead); // Clear the input/output buffers to a known value so its obvious if something goes wrong cmdBuffer.cmds.fillBuffer(inputBuffer.buffer, 0, surfSize, 0xffffffff); cmdBuffer.cmds.fillBuffer(inputBuffer.buffer, surfSize, workSize - surfSize, 0xfefefefe); cmdBuffer.cmds.fillBuffer(untiledOutputBuffer.buffer, 0, surfSize, 0x00000000); cmdBuffer.cmds.fillBuffer(untiledOutputBuffer.buffer, surfSize, workSize - surfSize, 0x01010101); cmdBuffer.cmds.fillBuffer(tiledOutputBuffer.buffer, 0, surfSize, 0x00000000); cmdBuffer.cmds.fillBuffer(tiledOutputBuffer.buffer, surfSize, workSize - surfSize, 0x01010101); // Copy the data cmdBuffer.cmds.copyBuffer(uploadBuffer.buffer, inputBuffer.buffer, { vk::BufferCopy(0, 0, uploadSize) }); // Barrier our transfers to the shader reads globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead); // Dispatch the actual retile auto retileInfo = gpu7::tiling::computeRetileInfo(gpu7Info); auto tiledFirstSliceIndex = align_down(firstSlice, retileInfo.microTileThickness); auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes; auto untiledSliceOffset = firstSlice * retileInfo.thinSliceBytes; auto untileHandle = gVkRetiler.untile(retileInfo, cmdBuffer.cmds, untiledOutputBuffer.buffer, untiledSliceOffset, inputBuffer.buffer, tiledSliceOffset, firstSlice, numSlices); // Barrier between these to force the pipeline flush globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead); auto tileHandle = gVkRetiler.tile(retileInfo, cmdBuffer.cmds, tiledOutputBuffer.buffer, tiledSliceOffset, untiledOutputBuffer.buffer, untiledSliceOffset, firstSlice, numSlices); // Put a barrier from the shader writes to the transfers globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eTransferRead); // Copy the output buffer to the download buffer cmdBuffer.cmds.copyBuffer(untiledOutputBuffer.buffer, untiledDownloadBuffer.buffer, { vk::BufferCopy(0, 0, surfSize) }); cmdBuffer.cmds.copyBuffer(tiledOutputBuffer.buffer, tiledDownloadBuffer.buffer, { vk::BufferCopy(0, 0, surfSize) }); // Put a barrier from the transfer writes to the host reads globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eHostRead); // End, execute and free this buffer endSyncCmdBuffer(cmdBuffer); execSyncCmdBuffer(cmdBuffer); freeSyncCmdBuffer(cmdBuffer); // Free the retiler resources we used gVkRetiler.releaseHandle(untileHandle); gVkRetiler.releaseHandle(tileHandle); } // Capture the retiled data from the GPU downloadSsboBuffer(untiledDownloadBuffer, gpu7Untiled.data(), surfSize); downloadSsboBuffer(tiledDownloadBuffer, gpu7Tiled.data(), surfSize); // Free the buffers associated with this freeSsboBuffer(uploadBuffer); freeSsboBuffer(inputBuffer); freeSsboBuffer(untiledOutputBuffer); freeSsboBuffer(tiledOutputBuffer); freeSsboBuffer(untiledDownloadBuffer); freeSsboBuffer(tiledDownloadBuffer); // Compare that the images match CHECK(compareImages(gpu7Untiled, alibUntiled)); CHECK(compareImages(gpu7Tiled, alibTiled)); } TEST_CASE("vkTiling") { for (auto& layout : sTestLayout) { SECTION(fmt::format("{}x{}x{} s{}n{}", layout.width, layout.height, layout.depth, layout.testFirstSlice, layout.testNumSlices)) { for (auto& mode : sTestTilingMode) { SECTION(fmt::format("{}", tileModeToString(mode.tileMode))) { for (auto& format : sTestFormats) { SECTION(fmt::format("{}bpp{}", format.bpp, format.depth ? " depth" : "")) { auto surface = gpu7::tiling::SurfaceDescription { }; surface.tileMode = mode.tileMode; surface.format = format.format; surface.bpp = format.bpp; surface.width = layout.width; surface.height = layout.height; surface.numSlices = layout.depth; surface.numSamples = 1u; surface.numLevels = 1u; surface.bankSwizzle = 0u; surface.pipeSwizzle = 0u; surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray; surface.use = format.depth ? gpu7::tiling::SurfaceUse::DepthBuffer : gpu7::tiling::SurfaceUse::None; compareTilingToAddrLib(surface, sRandomData, layout.testFirstSlice, layout.testNumSlices); } } } } } } } struct PendingVkPerfEntry { gpu7::tiling::SurfaceDescription desc; uint32_t firstSlice; uint32_t numSlices; gpu7::tiling::SurfaceInfo info; SsboBuffer uploadBuffer; SsboBuffer inputBuffer; SsboBuffer outputBuffer; gpu7::tiling::vulkan::RetileHandle handle; }; TEST_CASE("vkTilingPerf", "[!benchmark]") { // Set up AddrLib to generate data to test against auto addrLib = AddrLib { }; // Get some random data to use auto& untiled = sRandomData; // Some place to store pending tests std::vector<PendingVkPerfEntry> pendingTests; // Generate all the test cases to run auto& layout = sPerfTestLayout; for (auto& mode : sTestTilingMode) { for (auto& format : sTestFormats) { auto surface = gpu7::tiling::SurfaceDescription { }; surface.tileMode = mode.tileMode; surface.format = format.format; surface.bpp = format.bpp; surface.width = layout.width; surface.height = layout.height; surface.numSlices = layout.depth; surface.numSamples = 1u; surface.numLevels = 1u; surface.bankSwizzle = 0u; surface.pipeSwizzle = 0u; surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray; surface.use = format.depth ? gpu7::tiling::SurfaceUse::DepthBuffer : gpu7::tiling::SurfaceUse::None; PendingVkPerfEntry test; test.desc = surface; test.firstSlice = layout.testFirstSlice; test.numSlices = layout.testNumSlices; pendingTests.push_back(test); } } // Set up all the tests for (auto& test : pendingTests) { // Compute some needed surface information auto info = gpu7::tiling::computeSurfaceInfo(test.desc, 0); auto surfSize = static_cast<uint32_t>(info.surfSize); // Make sure our test data is big enough REQUIRE(untiled.size() >= info.surfSize); // Create input/output buffers auto uploadBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::CpuToGpu); auto inputBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::Gpu); auto outputBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::Gpu); // Upload to the upload buffer uploadSsboBuffer(uploadBuffer, untiled.data(), surfSize); // Store the state between setup loops test.info = info; test.uploadBuffer = uploadBuffer; test.inputBuffer = inputBuffer; test.outputBuffer = outputBuffer; } // Copy our uploaded data to the input buffers { auto cmdBuffer = allocSyncCmdBuffer(); beginSyncCmdBuffer(cmdBuffer); for (auto& test : pendingTests) { auto surfSize = static_cast<uint32_t>(test.info.surfSize); // Barrier our host writes the transfer reads globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eHostWrite, vk::AccessFlagBits::eTransferRead); // Set up the input/output buffers on the GPU cmdBuffer.cmds.copyBuffer(test.uploadBuffer.buffer, test.inputBuffer.buffer, { vk::BufferCopy(0, 0, surfSize) }); // Barrier our transfers to the shader reads globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead); } endSyncCmdBuffer(cmdBuffer); execSyncCmdBuffer(cmdBuffer); freeSyncCmdBuffer(cmdBuffer); } // Run the retiles { auto cmdBuffer = allocSyncCmdBuffer(); beginSyncCmdBuffer(cmdBuffer); for (auto& test : pendingTests) { // Calculate data on how to retile auto retileInfo = gpu7::tiling::computeRetileInfo(test.info); auto tiledFirstSliceIndex = align_down(test.firstSlice, retileInfo.microTileThickness); auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes; auto untiledSliceOffset = test.firstSlice * retileInfo.thinSliceBytes; // Dispatch the actual retile auto handle = gVkRetiler.untile(retileInfo, cmdBuffer.cmds, test.outputBuffer.buffer, untiledSliceOffset, test.inputBuffer.buffer, tiledSliceOffset, test.firstSlice, test.numSlices); // Save some information for freeing later test.handle = handle; } endSyncCmdBuffer(cmdBuffer); BENCHMARK(fmt::format("processing ({} retiles)", pendingTests.size())) { execSyncCmdBuffer(cmdBuffer); }; freeSyncCmdBuffer(cmdBuffer); } // Clean up all our resources used... for (auto& test : pendingTests) { // Free the retiler resources we used gVkRetiler.releaseHandle(test.handle); // Free the buffers we allocated freeSsboBuffer(test.uploadBuffer); freeSsboBuffer(test.inputBuffer); freeSsboBuffer(test.outputBuffer); } } bool vulkanBeforeStart() { if (!initialiseVulkan()) { return false; } // Initialize our retiler gVkRetiler.initialise(gDevice); return true; } bool vulkanAfterComplete() { return shutdownVulkan(); } #endif // DECAF_VULKAN ================================================ FILE: tools/CMakeLists.txt ================================================ project(tools) include(ExternalProject) include_directories(".") include_directories("../src") add_subdirectory(gfd-tool) add_subdirectory(latte-assembler) if(DECAF_GL) add_subdirectory(pm4-replay) if(DECAF_QT) # add_subdirectory(pm4-replay-qt) endif() endif() if(DEVKITPPC AND WUT_ROOT) externalproject_add(wiiu-rpc SOURCE_DIR "${PROJECT_SOURCE_DIR}/wiiu-rpc" INSTALL_COMMAND "" CMAKE_GENERATOR "Unix Makefiles" CMAKE_CACHE_ARGS -DDEVKITPPC:string=${DEVKITPPC} -DWUT_ROOT:string=${WUT_ROOT} -DCMAKE_TOOLCHAIN_FILE:string=${WUT_ROOT}/share/wut.toolchain.cmake) set_target_properties(wiiu-rpc PROPERTIES FOLDER tools) externalproject_add_step(wiiu-rpc forcebuild COMMAND ${CMAKE_COMMAND} -E echo "Force build of wiiu-rpc" DEPENDEES "configure" DEPENDERS "build" ALWAYS 1) endif() ================================================ FILE: tools/gfd-tool/CMakeLists.txt ================================================ project(gfd-tool) include_directories(".") include_directories("../../src/libdecaf/src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(gfd-tool ${SOURCE_FILES} ${HEADER_FILES}) set_target_properties(gfd-tool PROPERTIES FOLDER tools) target_link_libraries(gfd-tool common libdecaf libgfd excmd) install(TARGETS gfd-tool RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") ================================================ FILE: tools/gfd-tool/gfdtool.cpp ================================================ #include <libgfd/gfd.h> #include <cassert> #include <common/teenyheap.h> #include <excmd.h> #include <fmt/core.h> #include <fstream> #include <gsl/gsl-lite.hpp> #include <iostream> #include <iterator> #include <libcpu/cpu.h> #include <libgfd/gfd.h> #include <libgpu/gpu7_tiling_cpu.h> #include <libgpu/latte/latte_disassembler.h> #include <libgpu/latte/latte_formats.h> #include <libdecaf/src/cafe/libraries/gx2/gx2_debug_dds.h> #include <libdecaf/src/cafe/libraries/gx2/gx2_enum_string.h> #include <libdecaf/src/cafe/libraries/gx2/gx2_internal_gfd.h> #include <spdlog/spdlog.h> struct OutputState { fmt::memory_buffer writer; std::string indent; }; static void increaseIndent(OutputState &out) { out.indent += " "; } static void decreaseIndent(OutputState &out) { if (out.indent.size() >= 2) { out.indent.resize(out.indent.size() - 2); } } static void startGroup(OutputState &out, const std::string &group) { fmt::format_to(std::back_inserter(out.writer), "{}{}\n", out.indent, group); increaseIndent(out); } static void endGroup(OutputState &out) { decreaseIndent(out); } template<typename Type> static void writeField(OutputState &out, const std::string &field, const Type &value) { fmt::format_to(std::back_inserter(out.writer), "{}{:<30} = {}\n", out.indent, field, value); } static bool printInfo(const std::string &filename) { OutputState out; gfd::GFDFile file; try { if (!gfd::readFile(file, filename)) { return false; } } catch (gfd::GFDReadException ex) { std::cerr << fmt::format("Error reading gfd: {}", ex.what()) << std::endl; return false; } for (auto &shader : file.vertexShaders) { startGroup(out, "VertexShaderHeader"); { writeField(out, "size", shader.data.size()); writeField(out, "mode", cafe::gx2::to_string(shader.mode)); writeField(out, "uniformBlockCount", shader.uniformBlocks.size()); for (auto i = 0u; i < shader.uniformBlocks.size(); ++i) { startGroup(out, fmt::format("uniformBlocks[{}]", i)); { auto &var = shader.uniformBlocks[i]; writeField(out, "name", var.name); writeField(out, "offset", var.offset); writeField(out, "size", var.size); } endGroup(out); } writeField(out, "uniformVarCount", shader.uniformVars.size()); for (auto i = 0u; i < shader.uniformVars.size(); ++i) { startGroup(out, fmt::format("uniformVars[{}]", i)); { auto &var = shader.uniformVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "count", var.count); writeField(out, "offset", var.offset); writeField(out, "block", var.block); } endGroup(out); } writeField(out, "initialValueCount", shader.initialValues.size()); for (auto i = 0u; i < shader.initialValues.size(); ++i) { startGroup(out, fmt::format("initialValues[{}]", i)); { auto &var = shader.initialValues[i]; writeField(out, "value.x", var.value[0]); writeField(out, "value.y", var.value[1]); writeField(out, "value.z", var.value[2]); writeField(out, "value.w", var.value[3]); writeField(out, "offset", var.offset); } endGroup(out); } writeField(out, "loopVarCount", shader.loopVars.size()); for (auto i = 0u; i < shader.loopVars.size(); ++i) { startGroup(out, fmt::format("loopVars[{}]", i)); { auto &var = shader.loopVars[i]; writeField(out, "offset", var.offset); writeField(out, "value", var.value); } endGroup(out); } writeField(out, "samplerVarCount", shader.samplerVars.size()); for (auto i = 0u; i < shader.samplerVars.size(); ++i) { startGroup(out, fmt::format("samplerVars[{}]", i)); { auto &var = shader.samplerVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "location", var.location); } endGroup(out); } writeField(out, "attribVarCount", shader.attribVars.size()); for (auto i = 0u; i < shader.attribVars.size(); ++i) { startGroup(out, fmt::format("attribVars[{}]", i)); { auto &var = shader.attribVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "count", var.count); writeField(out, "location", var.location); } endGroup(out); } writeField(out, "ringItemsize", shader.ringItemSize); writeField(out, "hasStreamOut", shader.hasStreamOut); startGroup(out, "SQ_PGM_RESOURCES_VS"); { auto sq_pgm_resources_vs = shader.regs.sq_pgm_resources_vs; writeField(out, "NUM_GPRS", sq_pgm_resources_vs.NUM_GPRS()); writeField(out, "STACK_SIZE", sq_pgm_resources_vs.STACK_SIZE()); writeField(out, "DX10_CLAMP", sq_pgm_resources_vs.DX10_CLAMP()); writeField(out, "PRIME_CACHE_PGM_EN", sq_pgm_resources_vs.PRIME_CACHE_PGM_EN()); writeField(out, "PRIME_CACHE_ON_DRAW", sq_pgm_resources_vs.PRIME_CACHE_ON_DRAW()); writeField(out, "FETCH_CACHE_LINES", sq_pgm_resources_vs.FETCH_CACHE_LINES()); writeField(out, "UNCACHED_FIRST_INST", sq_pgm_resources_vs.UNCACHED_FIRST_INST()); writeField(out, "PRIME_CACHE_ENABLE", sq_pgm_resources_vs.PRIME_CACHE_ENABLE()); writeField(out, "PRIME_CACHE_ON_CONST", sq_pgm_resources_vs.PRIME_CACHE_ON_CONST()); } endGroup(out); startGroup(out, "VGT_PRIMITIVEID_EN"); { auto vgt_primitiveid_en = shader.regs.vgt_primitiveid_en; writeField(out, "PRIMITIVEID_EN", vgt_primitiveid_en.PRIMITIVEID_EN()); } endGroup(out); startGroup(out, "SPI_VS_OUT_CONFIG"); { auto spi_vs_out_config = shader.regs.spi_vs_out_config; writeField(out, "VS_PER_COMPONENT", spi_vs_out_config.VS_PER_COMPONENT()); writeField(out, "VS_EXPORT_COUNT", spi_vs_out_config.VS_EXPORT_COUNT()); writeField(out, "VS_EXPORTS_FOG", spi_vs_out_config.VS_EXPORTS_FOG()); writeField(out, "VS_OUT_FOG_VEC_ADDR", spi_vs_out_config.VS_OUT_FOG_VEC_ADDR()); } endGroup(out); auto num_spi_vs_out_id = shader.regs.num_spi_vs_out_id; writeField(out, "NUM_SPI_VS_OUT_ID", num_spi_vs_out_id); auto spi_vs_out_id = shader.regs.spi_vs_out_id; for (auto i = 0u; i < std::min<size_t>(num_spi_vs_out_id, spi_vs_out_id.size()); ++i) { startGroup(out, fmt::format("SPI_VS_OUT_ID[{}]", i)); { writeField(out, "SEMANTIC_0", spi_vs_out_id[i].SEMANTIC_0()); writeField(out, "SEMANTIC_1", spi_vs_out_id[i].SEMANTIC_1()); writeField(out, "SEMANTIC_2", spi_vs_out_id[i].SEMANTIC_2()); writeField(out, "SEMANTIC_3", spi_vs_out_id[i].SEMANTIC_3()); } endGroup(out); } startGroup(out, "PA_CL_VS_OUT_CNTL"); { auto pa_cl_vs_out_cntl = shader.regs.pa_cl_vs_out_cntl; writeField(out, "CLIP_DIST_ENA_0", pa_cl_vs_out_cntl.CLIP_DIST_ENA_0()); writeField(out, "CLIP_DIST_ENA_1", pa_cl_vs_out_cntl.CLIP_DIST_ENA_1()); writeField(out, "CLIP_DIST_ENA_2", pa_cl_vs_out_cntl.CLIP_DIST_ENA_2()); writeField(out, "CLIP_DIST_ENA_3", pa_cl_vs_out_cntl.CLIP_DIST_ENA_3()); writeField(out, "CLIP_DIST_ENA_4", pa_cl_vs_out_cntl.CLIP_DIST_ENA_4()); writeField(out, "CLIP_DIST_ENA_5", pa_cl_vs_out_cntl.CLIP_DIST_ENA_5()); writeField(out, "CLIP_DIST_ENA_6", pa_cl_vs_out_cntl.CLIP_DIST_ENA_6()); writeField(out, "CLIP_DIST_ENA_7", pa_cl_vs_out_cntl.CLIP_DIST_ENA_7()); writeField(out, "CULL_DIST_ENA_0", pa_cl_vs_out_cntl.CULL_DIST_ENA_0()); writeField(out, "CULL_DIST_ENA_1", pa_cl_vs_out_cntl.CULL_DIST_ENA_1()); writeField(out, "CULL_DIST_ENA_2", pa_cl_vs_out_cntl.CULL_DIST_ENA_2()); writeField(out, "CULL_DIST_ENA_3", pa_cl_vs_out_cntl.CULL_DIST_ENA_3()); writeField(out, "CULL_DIST_ENA_4", pa_cl_vs_out_cntl.CULL_DIST_ENA_4()); writeField(out, "CULL_DIST_ENA_5", pa_cl_vs_out_cntl.CULL_DIST_ENA_5()); writeField(out, "CULL_DIST_ENA_6", pa_cl_vs_out_cntl.CULL_DIST_ENA_6()); writeField(out, "CULL_DIST_ENA_7", pa_cl_vs_out_cntl.CULL_DIST_ENA_7()); writeField(out, "USE_VTX_POINT_SIZE", pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE()); writeField(out, "USE_VTX_EDGE_FLAG", pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG()); writeField(out, "USE_VTX_RENDER_TARGET_INDX", pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX()); writeField(out, "USE_VTX_VIEWPORT_INDX", pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX()); writeField(out, "USE_VTX_KILL_FLAG", pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG()); writeField(out, "VS_OUT_MISC_VEC_ENA", pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA()); writeField(out, "VS_OUT_CCDIST0_VEC_ENA", pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()); writeField(out, "VS_OUT_CCDIST1_VEC_ENA", pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()); writeField(out, "VS_OUT_MISC_SIDE_BUS_ENA", pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA()); writeField(out, "USE_VTX_GS_CUT_FLAG", pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG()); } endGroup(out); startGroup(out, "SQ_VTX_SEMANTIC_CLEAR"); { auto sq_vtx_semantic_clear = shader.regs.sq_vtx_semantic_clear; writeField(out, "CLEAR", sq_vtx_semantic_clear.CLEAR()); } endGroup(out); auto num_sq_vtx_semantic = shader.regs.num_sq_vtx_semantic; writeField(out, "NUM_SQ_VTX_SEMANTIC", num_sq_vtx_semantic); auto sq_vtx_semantic = shader.regs.sq_vtx_semantic; for (auto i = 0u; i < std::min<size_t>(num_sq_vtx_semantic, sq_vtx_semantic.size()); ++i) { startGroup(out, fmt::format("SQ_VTX_SEMANTIC[{}]", i)); { writeField(out, "SEMANTIC_ID", sq_vtx_semantic[i].SEMANTIC_ID()); } endGroup(out); } startGroup(out, "VGT_STRMOUT_BUFFER_EN"); { auto vgt_strmout_buffer_en = shader.regs.vgt_strmout_buffer_en; writeField(out, "BUFFER_0_EN", vgt_strmout_buffer_en.BUFFER_0_EN()); writeField(out, "BUFFER_1_EN", vgt_strmout_buffer_en.BUFFER_1_EN()); writeField(out, "BUFFER_2_EN", vgt_strmout_buffer_en.BUFFER_2_EN()); writeField(out, "BUFFER_3_EN", vgt_strmout_buffer_en.BUFFER_3_EN()); } endGroup(out); startGroup(out, "VGT_VERTEX_REUSE_BLOCK_CNTL"); { auto vgt_vertex_reuse_block_cntl = shader.regs.vgt_vertex_reuse_block_cntl; writeField(out, "VTX_REUSE_DEPTH", vgt_vertex_reuse_block_cntl.VTX_REUSE_DEPTH()); } endGroup(out); startGroup(out, "VGT_HOS_REUSE_DEPTH"); { auto vgt_hos_reuse_depth = shader.regs.vgt_hos_reuse_depth; writeField(out, "REUSE_DEPTH", vgt_hos_reuse_depth.REUSE_DEPTH()); } endGroup(out); } endGroup(out); startGroup(out, "VertexShaderProgram"); { writeField(out, "size", shader.data.size()); std::string disassembly; disassembly = latte::disassemble(gsl::make_span(shader.data)); fmt::format_to(std::back_inserter(out.writer), "\n{}", disassembly); } endGroup(out); } for (auto &shader : file.pixelShaders) { startGroup(out, "PixelShaderHeader"); { writeField(out, "size", shader.data.size()); writeField(out, "mode", cafe::gx2::to_string(shader.mode)); writeField(out, "uniformBlockCount", shader.uniformBlocks.size()); for (auto i = 0u; i < shader.uniformBlocks.size(); ++i) { startGroup(out, fmt::format("uniformBlocks[{}]", i)); { auto &var = shader.uniformBlocks[i]; writeField(out, "name", var.name); writeField(out, "offset", var.offset); writeField(out, "size", var.size); } endGroup(out); } writeField(out, "uniformVarCount", shader.uniformVars.size()); for (auto i = 0u; i < shader.uniformVars.size(); ++i) { startGroup(out, fmt::format("uniformVars[{}]", i)); { auto &var = shader.uniformVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "count", var.count); writeField(out, "offset", var.offset); writeField(out, "block", var.block); } endGroup(out); } writeField(out, "initialValueCount", shader.initialValues.size()); for (auto i = 0u; i < shader.initialValues.size(); ++i) { startGroup(out, fmt::format("initialValues[{}]", i)); { auto &var = shader.initialValues[i]; writeField(out, "value.x", var.value[0]); writeField(out, "value.y", var.value[1]); writeField(out, "value.z", var.value[2]); writeField(out, "value.w", var.value[3]); writeField(out, "offset", var.offset); } endGroup(out); } writeField(out, "loopVarCount", shader.loopVars.size()); for (auto i = 0u; i < shader.loopVars.size(); ++i) { startGroup(out, fmt::format("loopVars[{}]", i)); { auto &var = shader.loopVars[i]; writeField(out, "offset", var.offset); writeField(out, "value", var.value); } endGroup(out); } writeField(out, "samplerVarCount", shader.samplerVars.size()); for (auto i = 0u; i < shader.samplerVars.size(); ++i) { startGroup(out, fmt::format("samplerVars[{}]", i)); { auto &var = shader.samplerVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "location", var.location); } endGroup(out); } startGroup(out, "SQ_PGM_RESOURCES_PS"); { auto sq_pgm_resources_ps = shader.regs.sq_pgm_resources_ps; writeField(out, "NUM_GPRS", sq_pgm_resources_ps.NUM_GPRS()); writeField(out, "STACK_SIZE", sq_pgm_resources_ps.STACK_SIZE()); writeField(out, "DX10_CLAMP", sq_pgm_resources_ps.DX10_CLAMP()); writeField(out, "PRIME_CACHE_PGM_EN", sq_pgm_resources_ps.PRIME_CACHE_PGM_EN()); writeField(out, "PRIME_CACHE_ON_DRAW", sq_pgm_resources_ps.PRIME_CACHE_ON_DRAW()); writeField(out, "FETCH_CACHE_LINES", sq_pgm_resources_ps.FETCH_CACHE_LINES()); writeField(out, "UNCACHED_FIRST_INST", sq_pgm_resources_ps.UNCACHED_FIRST_INST()); writeField(out, "PRIME_CACHE_ENABLE", sq_pgm_resources_ps.PRIME_CACHE_ENABLE()); writeField(out, "PRIME_CACHE_ON_CONST", sq_pgm_resources_ps.PRIME_CACHE_ON_CONST()); writeField(out, "CLAMP_CONSTS", sq_pgm_resources_ps.CLAMP_CONSTS()); } endGroup(out); startGroup(out, "SQ_PGM_EXPORTS_PS"); { auto sq_pgm_exports_ps = shader.regs.sq_pgm_exports_ps; writeField(out, "EXPORT_MODE", sq_pgm_exports_ps.EXPORT_MODE()); } endGroup(out); startGroup(out, "SPI_PS_IN_CONTROL_0"); { auto spi_ps_in_control_0 = shader.regs.spi_ps_in_control_0; writeField(out, "NUM_INTERP", spi_ps_in_control_0.NUM_INTERP()); writeField(out, "POSITION_ENA", spi_ps_in_control_0.POSITION_ENA()); writeField(out, "POSITION_CENTROID", spi_ps_in_control_0.POSITION_CENTROID()); writeField(out, "POSITION_ADDR", spi_ps_in_control_0.POSITION_ADDR()); writeField(out, "PARAM_GEN", spi_ps_in_control_0.PARAM_GEN()); writeField(out, "PARAM_GEN_ADDR", spi_ps_in_control_0.PARAM_GEN_ADDR()); writeField(out, "BARYC_SAMPLE_CNTL", spi_ps_in_control_0.BARYC_SAMPLE_CNTL()); writeField(out, "PERSP_GRADIENT_ENA", spi_ps_in_control_0.PERSP_GRADIENT_ENA()); writeField(out, "LINEAR_GRADIENT_ENA", spi_ps_in_control_0.LINEAR_GRADIENT_ENA()); writeField(out, "POSITION_SAMPLE", spi_ps_in_control_0.POSITION_SAMPLE()); writeField(out, "BARYC_AT_SAMPLE_ENA", spi_ps_in_control_0.BARYC_AT_SAMPLE_ENA()); } endGroup(out); startGroup(out, "SPI_PS_IN_CONTROL_1"); { auto spi_ps_in_control_1 = shader.regs.spi_ps_in_control_1; writeField(out, "GEN_INDEX_PIX", spi_ps_in_control_1.GEN_INDEX_PIX()); writeField(out, "GEN_INDEX_PIX_ADDR", spi_ps_in_control_1.GEN_INDEX_PIX_ADDR()); writeField(out, "FRONT_FACE_ENA", spi_ps_in_control_1.FRONT_FACE_ENA()); writeField(out, "FRONT_FACE_CHAN", spi_ps_in_control_1.FRONT_FACE_CHAN()); writeField(out, "FRONT_FACE_ALL_BITS", spi_ps_in_control_1.FRONT_FACE_ALL_BITS()); writeField(out, "FRONT_FACE_ADDR", spi_ps_in_control_1.FRONT_FACE_ADDR()); writeField(out, "FOG_ADDR", spi_ps_in_control_1.FOG_ADDR()); writeField(out, "FIXED_PT_POSITION_ENA", spi_ps_in_control_1.FIXED_PT_POSITION_ENA()); writeField(out, "FIXED_PT_POSITION_ADDR", spi_ps_in_control_1.FIXED_PT_POSITION_ADDR()); writeField(out, "POSITION_ULC", spi_ps_in_control_1.POSITION_ULC()); } endGroup(out); auto num_spi_ps_input_cntl = shader.regs.num_spi_ps_input_cntl; auto spi_ps_input_cntls = shader.regs.spi_ps_input_cntls; writeField(out, "NUM_SPI_PS_INPUT_CNTL", num_spi_ps_input_cntl); for (auto i = 0u; i < std::min<size_t>(num_spi_ps_input_cntl, spi_ps_input_cntls.size()); ++i) { startGroup(out, fmt::format("SPI_PS_INPUT_CNTL[{}]", i)); { writeField(out, "SEMANTIC", spi_ps_input_cntls[i].SEMANTIC()); writeField(out, "DEFAULT_VAL", spi_ps_input_cntls[i].DEFAULT_VAL()); writeField(out, "FLAT_SHADE", spi_ps_input_cntls[i].FLAT_SHADE()); writeField(out, "SEL_CENTROID", spi_ps_input_cntls[i].SEL_CENTROID()); writeField(out, "SEL_LINEAR", spi_ps_input_cntls[i].SEL_LINEAR()); writeField(out, "CYL_WRAP", spi_ps_input_cntls[i].CYL_WRAP()); writeField(out, "PT_SPRITE_TEX", spi_ps_input_cntls[i].PT_SPRITE_TEX()); writeField(out, "SEL_SAMPLE", spi_ps_input_cntls[i].SEL_SAMPLE()); } endGroup(out); } startGroup(out, "CB_SHADER_MASK"); { auto cb_shader_mask = shader.regs.cb_shader_mask; writeField(out, "OUTPUT0_ENABLE", cb_shader_mask.OUTPUT0_ENABLE()); writeField(out, "OUTPUT1_ENABLE", cb_shader_mask.OUTPUT1_ENABLE()); writeField(out, "OUTPUT2_ENABLE", cb_shader_mask.OUTPUT2_ENABLE()); writeField(out, "OUTPUT3_ENABLE", cb_shader_mask.OUTPUT3_ENABLE()); writeField(out, "OUTPUT4_ENABLE", cb_shader_mask.OUTPUT4_ENABLE()); writeField(out, "OUTPUT5_ENABLE", cb_shader_mask.OUTPUT5_ENABLE()); writeField(out, "OUTPUT6_ENABLE", cb_shader_mask.OUTPUT6_ENABLE()); writeField(out, "OUTPUT7_ENABLE", cb_shader_mask.OUTPUT7_ENABLE()); } endGroup(out); startGroup(out, "CB_SHADER_CONTROL"); { auto cb_shader_control = shader.regs.cb_shader_control; writeField(out, "RT0_ENABLE", cb_shader_control.RT0_ENABLE()); writeField(out, "RT1_ENABLE", cb_shader_control.RT1_ENABLE()); writeField(out, "RT2_ENABLE", cb_shader_control.RT2_ENABLE()); writeField(out, "RT3_ENABLE", cb_shader_control.RT3_ENABLE()); writeField(out, "RT4_ENABLE", cb_shader_control.RT4_ENABLE()); writeField(out, "RT5_ENABLE", cb_shader_control.RT5_ENABLE()); writeField(out, "RT6_ENABLE", cb_shader_control.RT6_ENABLE()); writeField(out, "RT7_ENABLE", cb_shader_control.RT7_ENABLE()); } endGroup(out); startGroup(out, "DB_SHADER_CONTROL"); { auto db_shader_control = shader.regs.db_shader_control; writeField(out, "Z_EXPORT_ENABLE", db_shader_control.Z_EXPORT_ENABLE()); writeField(out, "STENCIL_REF_EXPORT_ENABLE", db_shader_control.STENCIL_REF_EXPORT_ENABLE()); writeField(out, "Z_ORDER", db_shader_control.Z_ORDER()); writeField(out, "KILL_ENABLE", db_shader_control.KILL_ENABLE()); writeField(out, "COVERAGE_TO_MASK_ENABLE", db_shader_control.COVERAGE_TO_MASK_ENABLE()); writeField(out, "MASK_EXPORT_ENABLE", db_shader_control.MASK_EXPORT_ENABLE()); writeField(out, "DUAL_EXPORT_ENABLE", db_shader_control.DUAL_EXPORT_ENABLE()); writeField(out, "EXEC_ON_HIER_FAIL", db_shader_control.EXEC_ON_HIER_FAIL()); writeField(out, "EXEC_ON_NOOP", db_shader_control.EXEC_ON_NOOP()); writeField(out, "ALPHA_TO_MASK_DISABLE", db_shader_control.ALPHA_TO_MASK_DISABLE()); } endGroup(out); startGroup(out, "SPI_INPUT_Z"); { auto spi_input_z = shader.regs.spi_input_z; writeField(out, "PROVIDE_Z_TO_SPI", spi_input_z.PROVIDE_Z_TO_SPI()); } endGroup(out); } endGroup(out); startGroup(out, "PixelShaderProgram"); { writeField(out, "size", shader.data.size()); std::string disassembly; disassembly = latte::disassemble(gsl::make_span(shader.data)); fmt::format_to(std::back_inserter(out.writer), "\n{}", disassembly); } endGroup(out); } for (auto &shader : file.geometryShaders) { startGroup(out, "GeometryShaderHeader"); { writeField(out, "size", shader.data.size()); writeField(out, "vshSize", shader.vertexShaderData.size()); writeField(out, "mode", cafe::gx2::to_string(shader.mode)); writeField(out, "uniformBlockCount", shader.uniformBlocks.size()); for (auto i = 0u; i < shader.uniformBlocks.size(); ++i) { startGroup(out, fmt::format("uniformBlocks[{}]", i)); { auto &var = shader.uniformBlocks[i]; writeField(out, "name", var.name); writeField(out, "offset", var.offset); writeField(out, "size", var.size); } endGroup(out); } writeField(out, "uniformVarCount", shader.uniformVars.size()); for (auto i = 0u; i < shader.uniformVars.size(); ++i) { startGroup(out, fmt::format("uniformVars[{}]", i)); { auto &var = shader.uniformVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "count", var.count); writeField(out, "offset", var.offset); writeField(out, "block", var.block); } endGroup(out); } writeField(out, "initialValueCount", shader.initialValues.size()); for (auto i = 0u; i < shader.initialValues.size(); ++i) { startGroup(out, fmt::format("initialValues[{}]", i)); { auto &var = shader.initialValues[i]; writeField(out, "value.x", var.value[0]); writeField(out, "value.y", var.value[1]); writeField(out, "value.z", var.value[2]); writeField(out, "value.w", var.value[3]); writeField(out, "offset", var.offset); } endGroup(out); } writeField(out, "loopVarCount", shader.loopVars.size()); for (auto i = 0u; i < shader.loopVars.size(); ++i) { startGroup(out, fmt::format("loopVars[{}]", i)); { auto &var = shader.loopVars[i]; writeField(out, "offset", var.offset); writeField(out, "value", var.value); } endGroup(out); } writeField(out, "samplerVarCount", shader.samplerVars.size()); for (auto i = 0u; i < shader.samplerVars.size(); ++i) { startGroup(out, fmt::format("samplerVars[{}]", i)); { auto &var = shader.samplerVars[i]; writeField(out, "name", var.name); writeField(out, "type", cafe::gx2::to_string(var.type)); writeField(out, "location", var.location); } endGroup(out); } writeField(out, "ringItemSize", shader.ringItemSize); writeField(out, "hasStreamOut", shader.hasStreamOut); for (auto i = 0u; i < shader.streamOutStride.size(); ++i) { writeField(out, fmt::format("streamOutStride[{}]", i), shader.streamOutStride[i]); } startGroup(out, "SQ_PGM_RESOURCES_GS"); { auto sq_pgm_resources_gs = shader.regs.sq_pgm_resources_gs; writeField(out, "NUM_GPRS", sq_pgm_resources_gs.NUM_GPRS()); writeField(out, "STACK_SIZE", sq_pgm_resources_gs.STACK_SIZE()); writeField(out, "DX10_CLAMP", sq_pgm_resources_gs.DX10_CLAMP()); writeField(out, "PRIME_CACHE_PGM_EN", sq_pgm_resources_gs.PRIME_CACHE_PGM_EN()); writeField(out, "PRIME_CACHE_ON_DRAW", sq_pgm_resources_gs.PRIME_CACHE_ON_DRAW()); writeField(out, "FETCH_CACHE_LINES", sq_pgm_resources_gs.FETCH_CACHE_LINES()); writeField(out, "UNCACHED_FIRST_INST", sq_pgm_resources_gs.UNCACHED_FIRST_INST()); writeField(out, "PRIME_CACHE_ENABLE", sq_pgm_resources_gs.PRIME_CACHE_ENABLE()); writeField(out, "PRIME_CACHE_ON_CONST", sq_pgm_resources_gs.PRIME_CACHE_ON_CONST()); } endGroup(out); startGroup(out, "VGT_GS_OUT_PRIM_TYPE"); { auto vgt_gs_out_prim_type = shader.regs.vgt_gs_out_prim_type; writeField(out, "PRIM_TYPE", vgt_gs_out_prim_type.PRIM_TYPE()); } endGroup(out); startGroup(out, "VGT_GS_MODE"); { auto vgt_gs_mode = shader.regs.vgt_gs_mode; writeField(out, "MODE", vgt_gs_mode.MODE()); writeField(out, "ES_PASSTHRU", vgt_gs_mode.ES_PASSTHRU()); writeField(out, "CUT_MODE", vgt_gs_mode.CUT_MODE()); writeField(out, "MODE_HI", vgt_gs_mode.MODE_HI()); writeField(out, "GS_C_PACK_EN", vgt_gs_mode.GS_C_PACK_EN()); writeField(out, "COMPUTE_MODE", vgt_gs_mode.COMPUTE_MODE()); writeField(out, "FAST_COMPUTE_MODE", vgt_gs_mode.FAST_COMPUTE_MODE()); writeField(out, "ELEMENT_INFO_EN", vgt_gs_mode.ELEMENT_INFO_EN()); writeField(out, "PARTIAL_THD_AT_EOI", vgt_gs_mode.PARTIAL_THD_AT_EOI()); } endGroup(out); startGroup(out, "PA_CL_VS_OUT_CNTL"); { auto pa_cl_vs_out_cntl = shader.regs.pa_cl_vs_out_cntl; writeField(out, "CLIP_DIST_ENA_0", pa_cl_vs_out_cntl.CLIP_DIST_ENA_0()); writeField(out, "CLIP_DIST_ENA_1", pa_cl_vs_out_cntl.CLIP_DIST_ENA_1()); writeField(out, "CLIP_DIST_ENA_2", pa_cl_vs_out_cntl.CLIP_DIST_ENA_2()); writeField(out, "CLIP_DIST_ENA_3", pa_cl_vs_out_cntl.CLIP_DIST_ENA_3()); writeField(out, "CLIP_DIST_ENA_4", pa_cl_vs_out_cntl.CLIP_DIST_ENA_4()); writeField(out, "CLIP_DIST_ENA_5", pa_cl_vs_out_cntl.CLIP_DIST_ENA_5()); writeField(out, "CLIP_DIST_ENA_6", pa_cl_vs_out_cntl.CLIP_DIST_ENA_6()); writeField(out, "CLIP_DIST_ENA_7", pa_cl_vs_out_cntl.CLIP_DIST_ENA_7()); writeField(out, "CULL_DIST_ENA_0", pa_cl_vs_out_cntl.CULL_DIST_ENA_0()); writeField(out, "CULL_DIST_ENA_1", pa_cl_vs_out_cntl.CULL_DIST_ENA_1()); writeField(out, "CULL_DIST_ENA_2", pa_cl_vs_out_cntl.CULL_DIST_ENA_2()); writeField(out, "CULL_DIST_ENA_3", pa_cl_vs_out_cntl.CULL_DIST_ENA_3()); writeField(out, "CULL_DIST_ENA_4", pa_cl_vs_out_cntl.CULL_DIST_ENA_4()); writeField(out, "CULL_DIST_ENA_5", pa_cl_vs_out_cntl.CULL_DIST_ENA_5()); writeField(out, "CULL_DIST_ENA_6", pa_cl_vs_out_cntl.CULL_DIST_ENA_6()); writeField(out, "CULL_DIST_ENA_7", pa_cl_vs_out_cntl.CULL_DIST_ENA_7()); writeField(out, "USE_VTX_POINT_SIZE", pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE()); writeField(out, "USE_VTX_EDGE_FLAG", pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG()); writeField(out, "USE_VTX_RENDER_TARGET_INDX", pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX()); writeField(out, "USE_VTX_VIEWPORT_INDX", pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX()); writeField(out, "USE_VTX_KILL_FLAG", pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG()); writeField(out, "VS_OUT_MISC_VEC_ENA", pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA()); writeField(out, "VS_OUT_CCDIST0_VEC_ENA", pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()); writeField(out, "VS_OUT_CCDIST1_VEC_ENA", pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()); writeField(out, "VS_OUT_MISC_SIDE_BUS_ENA", pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA()); writeField(out, "USE_VTX_GS_CUT_FLAG", pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG()); } endGroup(out); auto num_spi_vs_out_id = shader.regs.num_spi_vs_out_id; writeField(out, "NUM_SPI_VS_OUT_ID", num_spi_vs_out_id); auto spi_vs_out_id = shader.regs.spi_vs_out_id; for (auto i = 0u; i < std::min<size_t>(num_spi_vs_out_id, spi_vs_out_id.size()); ++i) { startGroup(out, fmt::format("SPI_VS_OUT_ID[{}]", i)); { writeField(out, "SEMANTIC_0", spi_vs_out_id[i].SEMANTIC_0()); writeField(out, "SEMANTIC_1", spi_vs_out_id[i].SEMANTIC_1()); writeField(out, "SEMANTIC_2", spi_vs_out_id[i].SEMANTIC_2()); writeField(out, "SEMANTIC_3", spi_vs_out_id[i].SEMANTIC_3()); } endGroup(out); } startGroup(out, "SQ_PGM_RESOURCES_VS"); { auto sq_pgm_resources_vs = shader.regs.sq_pgm_resources_vs; writeField(out, "NUM_GPRS", sq_pgm_resources_vs.NUM_GPRS()); writeField(out, "STACK_SIZE", sq_pgm_resources_vs.STACK_SIZE()); writeField(out, "DX10_CLAMP", sq_pgm_resources_vs.DX10_CLAMP()); writeField(out, "PRIME_CACHE_PGM_EN", sq_pgm_resources_vs.PRIME_CACHE_PGM_EN()); writeField(out, "PRIME_CACHE_ON_DRAW", sq_pgm_resources_vs.PRIME_CACHE_ON_DRAW()); writeField(out, "FETCH_CACHE_LINES", sq_pgm_resources_vs.FETCH_CACHE_LINES()); writeField(out, "UNCACHED_FIRST_INST", sq_pgm_resources_vs.UNCACHED_FIRST_INST()); writeField(out, "PRIME_CACHE_ENABLE", sq_pgm_resources_vs.PRIME_CACHE_ENABLE()); writeField(out, "PRIME_CACHE_ON_CONST", sq_pgm_resources_vs.PRIME_CACHE_ON_CONST()); } endGroup(out); startGroup(out, "SQ_GS_VERT_ITEMSIZE"); { auto sq_gs_vert_itemsize = shader.regs.sq_gs_vert_itemsize; writeField(out, "ITEMSIZE", sq_gs_vert_itemsize.ITEMSIZE()); } endGroup(out); startGroup(out, "SPI_VS_OUT_CONFIG"); { auto spi_vs_out_config = shader.regs.spi_vs_out_config; writeField(out, "VS_PER_COMPONENT", spi_vs_out_config.VS_PER_COMPONENT()); writeField(out, "VS_EXPORT_COUNT", spi_vs_out_config.VS_EXPORT_COUNT()); writeField(out, "VS_EXPORTS_FOG", spi_vs_out_config.VS_EXPORTS_FOG()); writeField(out, "VS_OUT_FOG_VEC_ADDR", spi_vs_out_config.VS_OUT_FOG_VEC_ADDR()); } endGroup(out); startGroup(out, "VGT_STRMOUT_BUFFER_EN"); { auto vgt_strmout_buffer_en = shader.regs.vgt_strmout_buffer_en; writeField(out, "BUFFER_0_EN", vgt_strmout_buffer_en.BUFFER_0_EN()); writeField(out, "BUFFER_1_EN", vgt_strmout_buffer_en.BUFFER_1_EN()); writeField(out, "BUFFER_2_EN", vgt_strmout_buffer_en.BUFFER_2_EN()); writeField(out, "BUFFER_3_EN", vgt_strmout_buffer_en.BUFFER_3_EN()); } endGroup(out); } endGroup(out); startGroup(out, "GeometryShaderProgram"); { writeField(out, "size", shader.data.size()); std::string disassembly; disassembly = latte::disassemble(gsl::make_span(shader.data)); fmt::format_to(std::back_inserter(out.writer), "\n{}", disassembly); } endGroup(out); startGroup(out, "GeometryShaderCopyProgram"); { writeField(out, "size", shader.vertexShaderData.size()); std::string disassembly; disassembly = latte::disassemble(gsl::make_span(shader.vertexShaderData)); fmt::format_to(std::back_inserter(out.writer), "\n{}", disassembly); } endGroup(out); } for (auto &tex : file.textures) { startGroup(out, "TextureHeader"); { writeField(out, "dim", cafe::gx2::to_string(tex.surface.dim)); writeField(out, "width", tex.surface.width); writeField(out, "height", tex.surface.height); writeField(out, "depth", tex.surface.depth); writeField(out, "mipLevels", tex.surface.mipLevels); writeField(out, "format", cafe::gx2::to_string(tex.surface.format)); writeField(out, "aa", cafe::gx2::to_string(tex.surface.aa)); writeField(out, "use", cafe::gx2::to_string(tex.surface.use)); writeField(out, "imageSize", tex.surface.image.size()); writeField(out, "mipmapSize", tex.surface.mipmap.size()); writeField(out, "tileMode", cafe::gx2::to_string(tex.surface.tileMode)); writeField(out, "swizzle", tex.surface.swizzle); writeField(out, "alignment", tex.surface.alignment); writeField(out, "pitch", tex.surface.pitch); for (auto i = 0u; i < tex.surface.mipLevelOffset.size(); ++i) { writeField(out, fmt::format("mipLevelOffset[{}]", i), tex.surface.mipLevelOffset[i]); } writeField(out, "viewFirstMip", tex.viewFirstMip); writeField(out, "viewNumMips", tex.viewNumMips); writeField(out, "viewFirstSlice", tex.viewFirstSlice); writeField(out, "viewNumSlices", tex.viewNumSlices); writeField(out, "compMap", tex.compMap); startGroup(out, "SQ_TEX_RESOURCE_WORD0_0"); { auto word0 = tex.regs.word0; writeField(out, "DIM", word0.DIM()); writeField(out, "TILE_MODE", word0.TILE_MODE()); writeField(out, "TILE_TYPE", word0.TILE_TYPE()); writeField(out, "PITCH", word0.PITCH()); writeField(out, "TEX_WIDTH", word0.TEX_WIDTH()); } endGroup(out); startGroup(out, "SQ_TEX_RESOURCE_WORD0_1"); { auto word1 = tex.regs.word1; writeField(out, "TEX_HEIGHT", word1.TEX_HEIGHT()); writeField(out, "TEX_DEPTH", word1.TEX_DEPTH()); writeField(out, "DATA_FORMAT", word1.DATA_FORMAT()); } endGroup(out); startGroup(out, "SQ_TEX_RESOURCE_WORD0_4"); { auto word4 = tex.regs.word4; writeField(out, "FORMAT_COMP_X", word4.FORMAT_COMP_X()); writeField(out, "FORMAT_COMP_Y", word4.FORMAT_COMP_Y()); writeField(out, "FORMAT_COMP_Z", word4.FORMAT_COMP_Z()); writeField(out, "FORMAT_COMP_W", word4.FORMAT_COMP_W()); writeField(out, "NUM_FORMAT_ALL", word4.NUM_FORMAT_ALL()); writeField(out, "SRF_MODE_ALL", word4.SRF_MODE_ALL()); writeField(out, "FORCE_DEGAMMA", word4.FORCE_DEGAMMA()); writeField(out, "ENDIAN_SWAP", word4.ENDIAN_SWAP()); writeField(out, "REQUEST_SIZE", word4.REQUEST_SIZE()); writeField(out, "DST_SEL_X", word4.DST_SEL_X()); writeField(out, "DST_SEL_Y", word4.DST_SEL_Y()); writeField(out, "DST_SEL_Z", word4.DST_SEL_Z()); writeField(out, "DST_SEL_W", word4.DST_SEL_W()); writeField(out, "BASE_LEVEL", word4.BASE_LEVEL()); } endGroup(out); startGroup(out, "SQ_TEX_RESOURCE_WORD0_5"); { auto word5 = tex.regs.word5; writeField(out, "LAST_LEVEL", word5.LAST_LEVEL()); writeField(out, "BASE_ARRAY", word5.BASE_ARRAY()); writeField(out, "LAST_ARRAY", word5.LAST_ARRAY()); writeField(out, "YUV_CONV", word5.YUV_CONV()); } endGroup(out); startGroup(out, "SQ_TEX_RESOURCE_WORD0_6"); { auto word6 = tex.regs.word6; writeField(out, "MPEG_CLAMP", word6.MPEG_CLAMP()); writeField(out, "MAX_ANISO_RATIO", word6.MAX_ANISO_RATIO()); writeField(out, "PERF_MODULATION", word6.PERF_MODULATION()); writeField(out, "INTERLACED", word6.INTERLACED()); writeField(out, "ADVIS_FAULT_LOD", word6.ADVIS_FAULT_LOD()); writeField(out, "ADVIS_CLAMP_LOD", word6.ADVIS_CLAMP_LOD()); writeField(out, "TYPE", word6.TYPE()); } endGroup(out); } endGroup(out); if (tex.surface.image.size()) { startGroup(out, "TextureImage"); writeField(out, "size", tex.surface.image.size()); endGroup(out); } if (tex.surface.mipmap.size()) { startGroup(out, "TextureMipmap"); writeField(out, "size", tex.surface.mipmap.size()); endGroup(out); } } out.writer.push_back('\0'); std::cout << out.writer.data(); return true; } static std::string getFilename(const std::string &path) { auto startBS = path.find_last_of('\\'); auto startFS = path.find_last_of('/'); auto start = startBS; if (startBS == std::string::npos) { start = startFS; } if (start == std::string::npos) { start = 0; } else { start = start + 1; } auto filename = path.substr(start); return filename; } static std::string getFileBasename(const std::string &filename) { auto start = filename.find_last_of('.'); if (start == std::string::npos) { return filename; } else { return filename.substr(0, start); } } static bool convertTexture(const std::string &path) { OutputState out; gfd::GFDFile file; try { if (!gfd::readFile(file, path)) { return false; } } catch (gfd::GFDReadException ex) { std::cerr << fmt::format("Error reading gfd: {}", ex.what()) << std::endl; return false; } auto filename = getFilename(path); auto basename = getFileBasename(path); auto index = 0u; for (auto &tex : file.textures) { auto format = static_cast<latte::SQ_DATA_FORMAT>(tex.surface.format & 0x3f); auto bpp = latte::getDataFormatBitsPerElement(format); // Fill out tiling surface information auto surface = gpu7::tiling::SurfaceDescription { }; surface.tileMode = static_cast<gpu7::tiling::TileMode>(tex.surface.tileMode); surface.format = static_cast<gpu7::tiling::DataFormat>(format); surface.bpp = bpp; surface.numSamples = 1u << static_cast<int>(tex.surface.aa); surface.width = tex.surface.width; surface.height = tex.surface.height; surface.numSlices = tex.surface.depth; surface.use = static_cast<gpu7::tiling::SurfaceUse>(tex.surface.use); surface.dim = static_cast<gpu7::tiling::SurfaceDim>(tex.surface.dim); surface.numFrags = 0; surface.numLevels = tex.surface.mipLevels; /* Not sure if needed or not.*/ if (format >= latte::SQ_DATA_FORMAT::FMT_BC1 && format <= latte::SQ_DATA_FORMAT::FMT_BC5) { surface.width = (surface.width + 3) / 4; surface.height = (surface.height + 3) / 4; } surface.pipeSwizzle = (tex.surface.swizzle >> 8) & 1; surface.bankSwizzle = (tex.surface.swizzle >> 9) & 3; std::vector<uint8_t> untiled; std::vector<uint8_t> imageData; std::vector<uint8_t> mipMapData; // Untile image untiled.resize(tex.surface.image.size()); auto surfaceInfo = gpu7::tiling::computeSurfaceInfo(surface, 0); auto retileInfo = gpu7::tiling::computeRetileInfo(surfaceInfo); gpu7::tiling::cpu::untile(retileInfo, untiled.data(), tex.surface.image.data(), 0, surface.numSlices); // Unpitch image imageData.resize(gpu7::tiling::computeUnpitchedImageSize(surface)); gpu7::tiling::unpitchImage(surface, untiled.data(), imageData.data()); // Untile mipmaps auto mipOffset = 0u; untiled.resize(tex.surface.mipmap.size()); for (auto i = 1u; i < surface.numLevels; ++i) { auto mipSurfaceInfo = gpu7::tiling::computeSurfaceInfo(surface, i); auto mipRetileInfo = gpu7::tiling::computeRetileInfo(mipSurfaceInfo); mipOffset = align_up(mipOffset, mipSurfaceInfo.baseAlign); gpu7::tiling::cpu::untile(mipRetileInfo, untiled.data() + mipOffset, tex.surface.mipmap.data() + mipOffset, 0, surface.numSlices); mipOffset += mipSurfaceInfo.surfSize; } // Unpitch mipmaps mipMapData.resize(gpu7::tiling::computeUnpitchedMipMapSize(surface)); gpu7::tiling::unpitchMipMap(surface, untiled.data(), mipMapData.data()); // Save to DDS file std::string outname; if (file.textures.size() > 1) { outname = fmt::format("{}.gtx.{}.dds", basename, index++); } else { outname = fmt::format("{}.gtx.dds", basename); } cafe::gx2::GX2Surface gx2surface; cafe::gx2::internal::gfdToGX2Surface(tex.surface, &gx2surface); gx2surface.tileMode = cafe::gx2::GX2TileMode::LinearSpecial; gx2surface.pitch = gx2surface.width; gx2surface.imageSize = static_cast<uint32_t>(imageData.size()); gx2surface.mipLevels = tex.surface.mipLevels; gx2surface.mipmapSize = static_cast<uint32_t>(mipMapData.size()); cafe::gx2::debug::saveDDS(outname, &gx2surface, imageData.data(), mipMapData.data()); } return true; } static bool disassembleShaderBinary(const std::string &path) { std::vector<uint8_t> binary; std::ifstream file; file.open(path, std::fstream::binary); if (!file.is_open()) { return false; } file.seekg(0, std::fstream::end); binary.resize(file.tellg()); file.seekg(0, std::fstream::beg); file.read(reinterpret_cast<char *>(binary.data()), binary.size()); file.close(); std::cout << latte::disassemble(gsl::make_span(binary)) << std::endl; return true; } int main(int argc, char **argv) { int result = -1; excmd::parser parser; excmd::option_state options; // Setup command line options parser.global_options() .add_option("h,help", excmd::description { "Show the help." }); parser.add_command("help") .add_argument("command", excmd::value<std::string> { }); parser.add_command("info") .add_argument("file in", excmd::value<std::string> { }); parser.add_command("convert") .add_argument("src", excmd::value<std::string> { }); parser.add_command("disassemble") .add_argument("shader", excmd::value<std::string> { }); // Parse command line try { options = parser.parse(argc, argv); } catch (excmd::exception ex) { std::cout << "Error parsing command line: " << ex.what() << std::endl; std::exit(-1); } // Print help if (argc == 1 || options.has("help")) { if (options.has("command")) { std::cout << parser.format_help("gfdtool", options.get<std::string>("command")) << std::endl; } else { std::cout << parser.format_help("gfdtool") << std::endl; } std::exit(0); } if (options.has("info")) { auto in = options.get<std::string>("file in"); result = printInfo(in) ? 0 : -1; } else if (options.has("convert")) { auto src = options.get<std::string>("src"); result = convertTexture(src) ? 0 : -1; } else if (options.has("disassemble")) { auto src = options.get<std::string>("shader"); result = disassembleShaderBinary(src) ? 0 : -1; } return result; } ================================================ FILE: tools/latte-assembler/CMakeLists.txt ================================================ project(latte-assembler) include_directories(".") include_directories(${PROJECT_BINARY_DIR}) file(GLOB_RECURSE SOURCE_FILES src/*.cpp) file(GLOB_RECURSE HEADER_FILES src/*.h) # Generates a grammar.h with the contents of grammar.txt in a C++ string set(GRAMMAR_FILE ${PROJECT_SOURCE_DIR}/resources/grammar.txt) set(GRAMMAR_CMAKE ${PROJECT_BINARY_DIR}/generate_grammar.cmake) set(GRAMMAR_HEADER ${PROJECT_BINARY_DIR}/grammar.h) file(WRITE ${GRAMMAR_CMAKE} "FILE(WRITE grammar.h \"const char *LatteGrammar = R\\\"(\\n\")\n") file(APPEND ${GRAMMAR_CMAKE} "FILE(READ ") file(APPEND ${GRAMMAR_CMAKE} ${GRAMMAR_FILE}) file(APPEND ${GRAMMAR_CMAKE} " CONTENTS)\n") file(APPEND ${GRAMMAR_CMAKE} "FILE(APPEND grammar.h \${CONTENTS})\n") file(APPEND ${GRAMMAR_CMAKE} "FILE(APPEND grammar.h \")\\\";\\n\")\n") add_custom_command(COMMAND ${CMAKE_COMMAND} -P ${GRAMMAR_CMAKE} DEPENDS ${GRAMMAR_FILE} OUTPUT ${GRAMMAR_HEADER} COMMENT "Generating grammar.h") # Build latte-assembler add_executable(latte-assembler ${SOURCE_FILES} ${HEADER_FILES} ${GRAMMAR_HEADER}) target_link_libraries(latte-assembler common libgfd excmd peglib SPIRV) set_target_properties(latte-assembler PROPERTIES FOLDER tools) GroupSources("Source Files" src) GroupSources("Resource Files" resources) install(TARGETS latte-assembler RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") ================================================ FILE: tools/latte-assembler/resources/example_shader.psh ================================================ ; $MODE = "UniformRegister" ; $SQ_PGM_RESOURCES_PS.NUM_GPRS = 1 ; $SPI_PS_IN_CONTROL_0.NUM_INTERP = 1 ; $NUM_SPI_PS_INPUT_CNTL = 1 ; $SPI_PS_INPUT_CNTL[0].SEMANTIC = 0 ; $SPI_PS_INPUT_CNTL[0].DEFAULT_VAL = 1 00 EXP_DONE: PIX0, R0.xyzw END_OF_PROGRAM ================================================ FILE: tools/latte-assembler/resources/example_shader.vsh ================================================ ; $MODE = "UniformRegister" ; $ATTRIB_VARS[0].name = "aColour" ; $ATTRIB_VARS[0].type = "Float4" ; $ATTRIB_VARS[0].location = 0 ; $ATTRIB_VARS[1].name = "aPosition" ; $ATTRIB_VARS[1].type = "Float4" ; $ATTRIB_VARS[1].location = 1 ; $SQ_PGM_RESOURCES_VS.NUM_GPRS = 3 ; $SQ_PGM_RESOURCES_VS.STACK_SIZE = 1 ; $NUM_SPI_VS_OUT_ID = 1 ; $SPI_VS_OUT_ID[0].SEMANTIC_0 = 0 ; $SQ_VTX_SEMANTIC_CLEAR.CLEAR = 0xFFFFFFFC ; $NUM_SQ_VTX_SEMANTIC = 2 ; $SQ_VTX_SEMANTIC[0].SEMANTIC_ID = 0 ; $SQ_VTX_SEMANTIC[1].SEMANTIC_ID = 1 ; $VGT_VERTEX_REUSE_BLOCK_CNTL.VTX_REUSE_DEPTH = 14 ; $VGT_HOS_REUSE_DEPTH.REUSE_DEPTH = 16 00 CALL_FS NO_BARRIER 01 EXP_DONE: POS0, R2.xyzw 02 EXP_DONE: PARAM0, R1.xyzw NO_BARRIER END_OF_PROGRAM ================================================ FILE: tools/latte-assembler/src/assembler_alu.cpp ================================================ #include "shader_assembler.h" #include "assembler_instructions.h" #include <fmt/core.h> static std::string decodeOpcodeAlias(const std::string &op) { if (op == "SQRT_e") { return "SQRT_IEEE"; } else if (op == "EXP_e") { return "EXP_IEEE"; } else if (op == "LOG_e") { return "LOG_IEEE"; } else if (op == "RSQ_e") { return "RECIPSQRT_IEEE"; } else if (op == "RCP_e") { return "RECIP_IEEE"; } else if (op == "LOG_sat") { return "LOG_CLAMPED"; } else if (op == "MUL_e") { return "MUL_IEEE"; } else if (op == "DOT4_e") { return "DOT4_IEEE"; } else if (op == "MULADD_e") { return "MULADD_IEEE"; } else { return op; } } static void assembleAluInst(Shader &shader, AluGroup &group, peg::Ast &node, unsigned numSrcs) { auto inst = latte::AluInst {}; auto srcIndex = 0; auto aluUnit = latte::SQ_CHAN {}; std::memset(&inst, 0, sizeof(latte::AluInst)); inst.op2 = inst.op2 .WRITE_MASK(true); for (auto &child : node.nodes) { if (child->name == "AluUnit") { aluUnit = parseChan(*child); if (aluUnit != latte::SQ_CHAN::T) { inst.word1 = inst.word1 .DST_CHAN(aluUnit); } } else if (child->name == "AluOpcode0" || child->name == "AluOpcode1" || child->name == "AluOpcode2") { const auto name = decodeOpcodeAlias(child->token); const auto opcode = getAluOp2InstructionByName(name); if (opcode == latte::SQ_OP2_INST_INVALID) { throw invalid_alu_op2_inst_exception { *child }; } inst.word1 = inst.word1 .ENCODING(latte::SQ_ALU_ENCODING::OP2); inst.op2 = inst.op2 .ALU_INST(opcode); } else if (child->name == "AluOpcode3") { const auto name = decodeOpcodeAlias(child->token); const auto opcode = getAluOp3InstructionByName(name); if (opcode == latte::SQ_OP3_INST_INVALID) { throw invalid_alu_op3_inst_exception { *child }; } inst.op3 = inst.op3 .ALU_INST(opcode); } else if (child->name == "AluOutputModifier") { inst.op2 = inst.op2 .OMOD(parseOutputModifier(*child)); } else if (child->name == "AluDst") { for (auto &dst : child->nodes) { if (dst->name == "WriteMask") { if (inst.word1.ENCODING() != latte::SQ_ALU_ENCODING::OP2) { throw node_parse_exception { *dst, fmt::format("Write mask ____ is only valid on an OP2 instruction") }; } inst.op2 = inst.op2 .WRITE_MASK(false); } else if (dst->name == "Gpr") { inst.word1 = inst.word1 .DST_GPR(parseNumber(*dst)); markGprWritten(shader, inst.word1.DST_GPR()); } else if (dst->name == "AluRel") { inst.word0 = inst.word0 .INDEX_MODE(parseAluDstRelIndexMode(*dst)); inst.word1 = inst.word1 .DST_REL(latte::SQ_REL::REL); } else if (dst->name == "OneCompSwizzle") { inst.word1 = inst.word1 .DST_CHAN(parseChan(*dst)); } else { throw unhandled_node_exception { *dst }; } } } else if (child->name == "AluSrc") { auto negate = false; auto srcAbs = false; auto rel = latte::SQ_REL::ABS; auto chan = inst.word1.DST_CHAN(); auto sel = latte::SQ_ALU_SRC::REGISTER_FIRST; for (auto src : child->nodes) { if (src->name == "Negate") { negate = true; } else if (src->name == "AluRel") { rel = latte::SQ_REL::REL; inst.word0 = inst.word0 .INDEX_MODE(parseAluDstRelIndexMode(*src)); } else if (src->name == "OneCompSwizzle") { chan = parseChan(*src); } else if (src->name == "AluAbsSrcValue" || src->name == "AluSrcValue") { auto srcType = src->nodes[0]; if (src->name == "AluAbsSrcValue") { srcAbs = true; srcType = src->nodes[0]->nodes[0]; } if (srcType->name == "Gpr") { sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::REGISTER_FIRST + parseNumber(*srcType)); } else if (srcType->name == "ConstantCache0") { sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::KCACHE_BANK0_FIRST + parseNumber(*srcType)); } else if (srcType->name == "ConstantCache1") { sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::KCACHE_BANK1_FIRST + parseNumber(*srcType)); } else if (srcType->name == "ConstantFile") { sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::CONST_FILE_FIRST + parseNumber(*srcType)); } else if (srcType->name == "PreviousScalar") { sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::PS); } else if (srcType->name == "PreviousVector") { sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::PV); } else if (srcType->name == "Literal") { auto literal = parseLiteral(*srcType); sel = latte::SQ_ALU_SRC::LITERAL; if (literal.flags & LiteralValue::ReadFloat) { if (literal.floatValue == 0.0f) { if ((literal.flags & LiteralValue::ReadHex) == 0 || literal.hexValue == 0) { sel = latte::SQ_ALU_SRC::IMM_0; } } /* AMD ShaderAnalyzer only seems to use IMM_0 else if (literal.floatValue == 1.0f) { sel = latte::SQ_ALU_SRC::IMM_1; } else if (literal.floatValue == 0.5f) { sel = latte::SQ_ALU_SRC::IMM_0_5; } */ } if (sel == latte::SQ_ALU_SRC::LITERAL) { chan = static_cast<latte::SQ_CHAN>(group.literals.size()); group.literals.push_back(literal); } } else { throw unhandled_node_exception { *srcType }; } } else { throw unhandled_node_exception { *src }; } } if (srcIndex == 0) { inst.word0 = inst.word0 .SRC0_CHAN(chan) .SRC0_NEG(negate) .SRC0_SEL(sel) .SRC0_REL(rel); if (srcAbs) { inst.op2 = inst.op2 .SRC0_ABS(true); } } else if (srcIndex == 1) { inst.word0 = inst.word0 .SRC1_CHAN(chan) .SRC1_NEG(negate) .SRC1_SEL(sel) .SRC1_REL(rel); if (srcAbs) { inst.op2 = inst.op2 .SRC1_ABS(true); } } else if (srcIndex == 2) { inst.op3 = inst.op3 .SRC2_CHAN(chan) .SRC2_NEG(negate) .SRC2_SEL(sel) .SRC2_REL(rel); } markSrcRead(shader, sel); srcIndex++; } else if (child->name == "AluProperties") { for (auto &prop : child->nodes) { if (prop->name == "BANK_SWIZZLE") { inst.word1 = inst.word1 .BANK_SWIZZLE(parseAluBankSwizzle(*prop)); } else if (prop->name == "UPDATE_EXEC_MASK") { inst.op2 = inst.op2 .UPDATE_EXECUTE_MASK(true); } else if (prop->name == "UPDATE_PRED") { inst.op2 = inst.op2 .UPDATE_PRED(true); } else if (prop->name == "PRED_SEL") { inst.word0 = inst.word0 .PRED_SEL(parsePredSel(*prop)); } else if (prop->name == "CLAMP") { inst.word1 = inst.word1 .CLAMP(true); } else { throw invalid_alu_property_exception { *prop }; } } } else { throw unhandled_node_exception { *child }; } } group.insts.push_back(inst); } static void assembleAluGroup(Shader &shader, AluClause &clause, peg::Ast &node) { auto group = AluGroup {}; for (auto &child : node.nodes) { if (child->name == "InstCount") { group.clausePC = parseNumber(*child); if (group.clausePC != shader.clausePC) { throw incorrect_clause_pc_exception { *child, group.clausePC, shader.clausePC }; } } else if (child->name == "AluScalar0") { assembleAluInst(shader, group, *child, 0); } else if (child->name == "AluScalar1") { assembleAluInst(shader, group, *child, 1); } else if (child->name == "AluScalar2") { assembleAluInst(shader, group, *child, 2); } else if (child->name == "AluScalar3") { assembleAluInst(shader, group, *child, 3); } else { throw unhandled_node_exception { *child }; } } if (group.insts.size()) { auto &lastInst = group.insts.back(); lastInst.word0 = lastInst.word0 .LAST(true); } shader.clausePC++; clause.groups.push_back(std::move(group)); } void assembleAluClause(Shader &shader, peg::Ast &node) { auto cfInst = latte::ControlFlowInst { }; auto clause = AluClause {}; cfInst.alu.word1 = cfInst.alu.word1 .BARRIER(true); for (auto &child : node.nodes) { if (child->name == "InstCount") { clause.cfPC = parseNumber(*child); if (clause.cfPC != shader.cfInsts.size()) { throw incorrect_cf_pc_exception { *child, clause.cfPC, shader.cfInsts.size() }; } } else if (child->name == "AluClauseInstType") { auto &name = child->token; auto opcode = getCfAluInstructionByName(name); if (opcode == latte::SQ_CF_ALU_INST_INVALID) { throw invalid_cf_alu_inst_exception { *child }; } cfInst.alu.word1 = cfInst.alu.word1 .CF_INST(opcode); } else if (child->name == "AluClauseProperties") { for (auto &prop : child->nodes) { if (prop->name == "ADDR") { clause.addrNode = prop; cfInst.alu.word0 = cfInst.alu.word0 .ADDR(parseNumber(*prop)); } else if (prop->name == "CNT") { clause.countNode = prop; cfInst.alu.word1 = cfInst.alu.word1 .COUNT(parseNumber(*prop) - 1); } else if (prop->name == "KCACHE0") { assert(prop->nodes.size() == 3); auto bank = parseNumber(*prop->nodes[0]); auto start = parseNumber(*prop->nodes[1]); auto end = parseNumber(*prop->nodes[2]); auto mode = latte::SQ_CF_KCACHE_MODE::NOP; if (end - start == 15) { mode = latte::SQ_CF_KCACHE_MODE::LOCK_1; } else if (end - start == 31) { mode = latte::SQ_CF_KCACHE_MODE::LOCK_2; } cfInst.alu.word0 = cfInst.alu.word0 .KCACHE_BANK0(bank) .KCACHE_MODE0(mode); cfInst.alu.word1 = cfInst.alu.word1 .KCACHE_ADDR0(start / 16); } else if (prop->name == "KCACHE1") { assert(prop->nodes.size() == 3); auto bank = parseNumber(*prop->nodes[0]); auto start = parseNumber(*prop->nodes[1]); auto end = parseNumber(*prop->nodes[2]); auto mode = latte::SQ_CF_KCACHE_MODE::NOP; if (end - start == 15) { mode = latte::SQ_CF_KCACHE_MODE::LOCK_1; } else if (end - start == 31) { mode = latte::SQ_CF_KCACHE_MODE::LOCK_2; } cfInst.alu.word0 = cfInst.alu.word0 .KCACHE_BANK1(bank); cfInst.alu.word1 = cfInst.alu.word1 .KCACHE_MODE1(mode) .KCACHE_ADDR1(start / 16); } else if (prop->name == "NO_BARRIER") { cfInst.alu.word1 = cfInst.alu.word1 .BARRIER(false); } else if (prop->name == "WHOLE_QUAD_MODE") { cfInst.alu.word1 = cfInst.alu.word1 .WHOLE_QUAD_MODE(true); } else if (prop->name == "USES_WATERFALL") { cfInst.alu.word1 = cfInst.alu.word1 .ALT_CONST(true); } else { throw invalid_cf_alu_property_exception { *prop }; } } } else if (child->name == "AluGroup") { assembleAluGroup(shader, clause, *child); } else { throw unhandled_node_exception { *child }; } } shader.aluClauses.push_back(std::move(clause)); shader.cfInsts.push_back(cfInst); } ================================================ FILE: tools/latte-assembler/src/assembler_cf.cpp ================================================ #include "shader_assembler.h" #include "assembler_instructions.h" #include <common/align.h> #include <common/bit_cast.h> #include <fmt/core.h> static const size_t AluClauseAlign = 256; static const size_t TexClauseAlign = 128; static void assembleCfInst(Shader &shader, peg::Ast &node) { auto inst = latte::ControlFlowInst { }; inst.word1 = inst.word1 .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_NORMAL) .BARRIER(true); for (auto &child : node.nodes) { if (child->name == "InstCount") { auto cfPC = parseNumber(*child); if (cfPC != shader.cfInsts.size()) { throw incorrect_cf_pc_exception { *child, cfPC, shader.cfInsts.size() }; } } else if (child->name == "CfOpcode") { auto &name = child->token; auto opcode = getCfInstructionByName(name); if (opcode == latte::SQ_CF_INST_INVALID) { throw invalid_cf_inst_exception { *child }; } inst.word1 = inst.word1.CF_INST(opcode); } else if (child->name == "CfInstProperties") { for (auto &prop : child->nodes) { if (prop->name == "NO_BARRIER") { inst.word1 = inst.word1.BARRIER(false); } else if (prop->name == "WHOLE_QUAD_MODE") { inst.word1 = inst.word1.WHOLE_QUAD_MODE(true); } else if (prop->name == "VALID_PIX") { inst.word1 = inst.word1.VALID_PIXEL_MODE(true); } else if (prop->name == "CF_CONST") { inst.word1 = inst.word1.CF_CONST(parseNumber(*prop)); } else if (prop->name == "POP_CNT") { inst.word1 = inst.word1.POP_COUNT(parseNumber(*prop)); } else if (prop->name == "ADDR" || prop->name == "PASS_JUMP_ADDR" || prop->name == "FAIL_JUMP_ADDR") { inst.word0 = inst.word0.ADDR(parseNumber(*prop)); } else { throw invalid_cf_property_exception { *prop }; } } } else { throw unhandled_node_exception { *child }; } } shader.cfInsts.push_back(inst); } static void assembleInstruction(Shader &shader, peg::Ast &node) { if (node.name == "CfInst") { assembleCfInst(shader, node); } else if (node.name == "CfExpInst") { assembleExpInst(shader, node); } else if (node.name == "AluClause") { assembleAluClause(shader, node); } else if (node.name == "TexClause") { assembleTexClause(shader, node); } else { throw unhandled_node_exception { node }; } } static void assembleClauses(Shader &shader) { auto aluClauseData = std::vector<uint32_t> {}; auto texClauseData = std::vector<uint32_t> {}; auto aluClauseBaseAddress = align_up(shader.cfInsts.size(), AluClauseAlign / 8); for (auto &clause : shader.aluClauses) { auto &cfInst = shader.cfInsts[clause.cfPC]; auto offset = aluClauseData.size(); for (auto &group : clause.groups) { for (auto inst : group.insts) { aluClauseData.push_back(inst.word0.value); aluClauseData.push_back(inst.word1.value); } for (auto literal : group.literals) { if (literal.flags & LiteralValue::ReadHex) { aluClauseData.push_back(literal.hexValue); } else { aluClauseData.push_back(bit_cast<uint32_t>(literal.floatValue)); } } if (group.literals.size() % 2) { // Must pad to 64 bit aluClauseData.push_back(0); } } auto addr = aluClauseBaseAddress + (offset / 2); auto count = (aluClauseData.size() - offset) / 2; if (clause.addrNode) { if (cfInst.alu.word0.ADDR() != addr) { throw incorrect_clause_addr_exception { *clause.countNode, cfInst.alu.word0.ADDR(), addr }; } } else { cfInst.alu.word0 = cfInst.alu.word0 .ADDR(static_cast<uint32_t>(addr)); } if (clause.countNode) { auto parsedCount = cfInst.alu.word1.COUNT() + 1; if (parsedCount != count) { throw incorrect_clause_count_exception { *clause.countNode, parsedCount, count }; } } else { cfInst.alu.word1 = cfInst.alu.word1 .COUNT(static_cast<uint32_t>(count - 1)); } } if (aluClauseData.size()) { shader.aluClauseBaseAddress = static_cast<uint32_t>(aluClauseBaseAddress); shader.aluClauseData = std::move(aluClauseData); } else { shader.aluClauseBaseAddress = 0; } auto texClauseBaseAddress = align_up(aluClauseBaseAddress + shader.aluClauseData.size() / 2, TexClauseAlign / 8); for (auto &clause : shader.texClauses) { auto &cfInst = shader.cfInsts[clause.cfPC]; auto offset = texClauseData.size(); for (auto &inst : clause.insts) { texClauseData.push_back(inst.word0.value); texClauseData.push_back(inst.word1.value); texClauseData.push_back(inst.word2.value); texClauseData.push_back(inst.padding); } auto addr = texClauseBaseAddress + (offset / 2); auto count = (texClauseData.size() - offset) / 4; if (clause.addrNode) { if (cfInst.word0.ADDR() != addr) { throw incorrect_clause_addr_exception { *clause.countNode, cfInst.word0.ADDR(), addr }; } } else { cfInst.word0 = cfInst.word0 .ADDR(static_cast<uint32_t>(addr)); } if (clause.countNode) { auto parsedCount = (cfInst.word1.COUNT() | (cfInst.word1.COUNT_3() << 3)) + 1; if (parsedCount != count) { throw incorrect_clause_count_exception { *clause.countNode, parsedCount, count }; } } else { cfInst.word1 = cfInst.word1 .COUNT(static_cast<uint32_t>((count - 1) & 0b111)) .COUNT_3(static_cast<uint32_t>((count - 1) >> 3)); } } if (texClauseData.size()) { shader.texClauseBaseAddress = static_cast<uint32_t>(texClauseBaseAddress); shader.texClauseData = std::move(texClauseData); } else { shader.texClauseBaseAddress = 0; } // TODO: Same for VTX clauses } static void assembleEndOfProgram(Shader &shader) { if (shader.cfInsts.size()) { auto &last = shader.cfInsts.back(); if (last.word1.CF_INST_TYPE() == latte::SQ_CF_INST_TYPE_NORMAL || last.word1.CF_INST_TYPE() == latte::SQ_CF_INST_TYPE_EXPORT) { last.word1 = last.word1 .END_OF_PROGRAM(true); return; } } auto inst = latte::ControlFlowInst { }; inst.word1 = inst.word1 .CF_INST(latte::SQ_CF_INST_NOP) .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_NORMAL) .END_OF_PROGRAM(true); } void assembleAST(Shader &shader, std::shared_ptr<peg::Ast> ast) { auto foundEOP = false; if (ast->name != "Program") { throw node_parse_exception { *ast, fmt::format("Expected root node to be Program, not {}", ast->name) }; } for (auto &node : ast->nodes) { if (node->name == "Instruction") { assert(node->nodes.size() == 1); assembleInstruction(shader, *node->nodes[0]); } else if (node->name == "EndOfProgram") { assembleEndOfProgram(shader); foundEOP = true; } else if (node->name == "Comment") { if (node->token.size()) { shader.comments.push_back(node->token); } } else { throw unhandled_node_exception { *node }; } } // If the user did not add a END_OF_PROGRAM, we should do it automatically if (!foundEOP) { assembleEndOfProgram(shader); } assembleClauses(shader); } ================================================ FILE: tools/latte-assembler/src/assembler_common.cpp ================================================ #include "shader_assembler.h" unsigned long parseNumber(peg::Ast &node) { assert(node.is_token); return std::stoul(node.token); } float parseFloat(peg::Ast &node) { return std::stof(node.token); } uint32_t parseHexNumber(peg::Ast &node) { return static_cast<uint32_t>(std::stoul(node.token, 0, 0)); } LiteralValue parseLiteral(peg::Ast &node) { auto literal = LiteralValue { }; for (auto child : node.nodes) { if (child->name == "HexNumber") { literal.flags |= LiteralValue::ReadHex; literal.hexValue = parseHexNumber(*child); } else if (child->name == "Float") { literal.flags |= LiteralValue::ReadFloat; literal.floatValue = parseFloat(*child); } } return literal; } void markGprRead(Shader &shader, uint32_t gpr) { shader.gprRead[gpr] = true; } void markGprWritten(Shader &shader, uint32_t gpr) { shader.gprWritten[gpr] = true; } void markSrcRead(Shader &shader, latte::SQ_ALU_SRC src) { if (src >= latte::SQ_ALU_SRC::REGISTER_FIRST && src <= latte::SQ_ALU_SRC::REGISTER_LAST) { markGprRead(shader, src - latte::SQ_ALU_SRC::REGISTER_FIRST); } else if (src >= latte::SQ_ALU_SRC::KCACHE_BANK0_FIRST && src <= latte::SQ_ALU_SRC::KCACHE_BANK0_LAST) { shader.uniformBlocksUsed = true; } else if (src >= latte::SQ_ALU_SRC::KCACHE_BANK1_FIRST && src <= latte::SQ_ALU_SRC::KCACHE_BANK1_LAST) { shader.uniformBlocksUsed = true; } else if (src >= latte::SQ_ALU_SRC::CONST_FILE_FIRST && src <= latte::SQ_ALU_SRC::CONST_FILE_LAST) { shader.uniformRegistersUsed = true; } } ================================================ FILE: tools/latte-assembler/src/assembler_exp.cpp ================================================ #include "shader_assembler.h" #include "assembler_instructions.h" void assembleExpInst(Shader &shader, peg::Ast &node) { auto inst = latte::ControlFlowInst { }; inst.word1 = inst.word1 .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_EXPORT) .BARRIER(true); for (auto &child : node.nodes) { if (child->name == "InstCount") { auto cfPC = parseNumber(*child); if (cfPC != shader.cfInsts.size()) { throw incorrect_cf_pc_exception { *child, cfPC, shader.cfInsts.size() }; } } else if (child->name == "ExpOpcode") { auto &name = child->token; auto opcode = getCfExpInstructionByName(name); if (opcode == latte::SQ_CF_EXP_INST_INVALID) { throw invalid_exp_inst_exception { *child }; } if (opcode != latte::SQ_CF_INST_EXP && opcode != latte::SQ_CF_INST_EXP_DONE) { // TODO: Only EXP and EXP_DONE are supported for now. throw invalid_exp_inst_exception { *child }; } inst.exp.word1 = inst.exp.word1.CF_INST(opcode); } else if (child->name == "ExpParamTarget") { auto index = parseNumber(*child); shader.maxParamExport = std::max(shader.maxParamExport, index); inst.exp.word0 = inst.exp.word0 .TYPE(latte::SQ_EXPORT_TYPE::PARAM) .ARRAY_BASE(index); } else if (child->name == "ExpPixTarget") { auto index = parseNumber(*child); shader.maxPixelExport = std::max(shader.maxPixelExport, index); inst.exp.word0 = inst.exp.word0 .TYPE(latte::SQ_EXPORT_TYPE::PIXEL) .ARRAY_BASE(index); } else if (child->name == "ExpPosTarget") { auto index = parseNumber(*child); shader.maxPosExport = std::max(shader.maxPosExport, index); inst.exp.word0 = inst.exp.word0 .TYPE(latte::SQ_EXPORT_TYPE::POS) .ARRAY_BASE(index + 60); } else if (child->name == "ExpSrc") { for (auto &src : child->nodes) { if (src->name == "Gpr") { inst.exp.word0 = inst.exp.word0 .RW_GPR(parseNumber(*src)) .RW_REL(latte::SQ_REL::ABS); // Set default GPR swizzle inst.exp.swiz = inst.exp.swiz .SEL_X(latte::SQ_SEL::SEL_X) .SEL_Y(latte::SQ_SEL::SEL_Y) .SEL_Z(latte::SQ_SEL::SEL_Z) .SEL_W(latte::SQ_SEL::SEL_W); markGprRead(shader, inst.exp.word0.RW_GPR()); } else if (src->name == "GprRel") { inst.exp.word0 = inst.exp.word0 .RW_GPR(parseNumber(*src)) .RW_REL(latte::SQ_REL::REL); // Set default GPR swizzle inst.exp.swiz = inst.exp.swiz .SEL_X(latte::SQ_SEL::SEL_X) .SEL_Y(latte::SQ_SEL::SEL_Y) .SEL_Z(latte::SQ_SEL::SEL_Z) .SEL_W(latte::SQ_SEL::SEL_W); markGprRead(shader, inst.exp.word0.RW_GPR()); } else if (src->name == "FourCompSwizzle") { auto selX = latte::SQ_SEL::SEL_0; auto selY = latte::SQ_SEL::SEL_0; auto selZ = latte::SQ_SEL::SEL_0; auto selW = latte::SQ_SEL::SEL_1; parseFourCompSwizzle(*src, selX, selY, selZ, selW); inst.exp.swiz = inst.exp.swiz .SEL_X(selX) .SEL_Y(selY) .SEL_Z(selZ) .SEL_W(selW); } } } else if (child->name == "CfInstProperties") { for (auto &prop : child->nodes) { if (prop->name == "NO_BARRIER") { inst.word1 = inst.word1.BARRIER(false); } else if (prop->name == "WHOLE_QUAD_MODE") { inst.exp.word1 = inst.exp.word1.WHOLE_QUAD_MODE(true); } else if (prop->name == "VALID_PIX") { inst.exp.word1 = inst.exp.word1.VALID_PIXEL_MODE(true); } else if (prop->name == "ELEM_SIZE") { inst.exp.word0 = inst.exp.word0.ELEM_SIZE(parseNumber(*prop)); } else if (prop->name == "BURSTCNT") { inst.exp.word1 = inst.exp.word1.BURST_COUNT(parseNumber(*prop)); } else { throw invalid_exp_property_exception { *prop }; } } } else { throw unhandled_node_exception { *child }; } } shader.cfInsts.push_back(inst); } ================================================ FILE: tools/latte-assembler/src/assembler_instructions.cpp ================================================ #include "assembler_instructions.h" latte::SQ_CF_INST getCfInstructionByName(const std::string &findName) { #define CF_INST(name, value) if (findName == #name) { return latte::SQ_CF_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef CF_INST return latte::SQ_CF_INST_INVALID; } latte::SQ_CF_EXP_INST getCfExpInstructionByName(const std::string &findName) { #define EXP_INST(name, value) if (findName == #name) { return latte::SQ_CF_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef EXP_INST return latte::SQ_CF_EXP_INST_INVALID; } latte::SQ_CF_ALU_INST getCfAluInstructionByName(const std::string &findName) { #define ALU_INST(name, value) if (findName == #name) { return latte::SQ_CF_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef ALU_INST return latte::SQ_CF_ALU_INST_INVALID; } latte::SQ_OP2_INST getAluOp2InstructionByName(const std::string &findName) { #define ALU_OP2(name, value, srcs, flags) if (findName == #name) { return latte::SQ_OP2_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef ALU_OP2 return latte::SQ_OP2_INST_INVALID; } latte::SQ_OP3_INST getAluOp3InstructionByName(const std::string &findName) { #define ALU_OP3(name, value, srcs, flags) if (findName == #name) { return latte::SQ_OP3_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef ALU_OP3 return latte::SQ_OP3_INST_INVALID; } latte::SQ_TEX_INST getTexInstructionByName(const std::string &findName) { #define TEX_INST(name, value) if (findName == #name) { return latte::SQ_TEX_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef TEX_INST return latte::SQ_TEX_INST_INVALID; } latte::SQ_VTX_INST getVtxInstructionByName(const std::string &findName) { #define VTX_INST(name, value) if (findName == #name) { return latte::SQ_VTX_INST_##name; } #include <libgpu/latte/latte_instructions_def.inl> #undef VTX_INST return latte::SQ_VTX_INST_INVALID; } ================================================ FILE: tools/latte-assembler/src/assembler_instructions.h ================================================ #pragma once #include <libgpu/latte/latte_instructions.h> #include <string> latte::SQ_CF_INST getCfInstructionByName(const std::string &name); latte::SQ_CF_EXP_INST getCfExpInstructionByName(const std::string &name); latte::SQ_CF_ALU_INST getCfAluInstructionByName(const std::string &name); latte::SQ_OP2_INST getAluOp2InstructionByName(const std::string &name); latte::SQ_OP3_INST getAluOp3InstructionByName(const std::string &name); latte::SQ_TEX_INST getTexInstructionByName(const std::string &name); latte::SQ_VTX_INST getVtxInstructionByName(const std::string &name); ================================================ FILE: tools/latte-assembler/src/assembler_latte.cpp ================================================ #include "shader_assembler.h" #include <fmt/core.h> latte::SQ_ALU_VEC_BANK_SWIZZLE parseAluBankSwizzle(peg::Ast &node) { if (node.token == "SCL_210") { return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_210); } else if (node.token == "SCL_122") { return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_122); } else if (node.token == "SCL_212") { return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_212); } else if (node.token == "SCL_221") { return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_221); } else if (node.token == "VEC_012") { return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_012; } else if (node.token == "VEC_021") { return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_021; } else if (node.token == "VEC_120") { return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_120; } else if (node.token == "VEC_102") { return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_102; } else if (node.token == "VEC_201") { return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_201; } else if (node.token == "VEC_210") { return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_210; } else { throw node_parse_exception { node, fmt::format("Invalid SQ_ALU_VEC_BANK_SWIZZLE {}", node.token) }; } } latte::SQ_INDEX_MODE parseAluDstRelIndexMode(peg::Ast &node) { if (node.token == "[AR.x]") { return latte::SQ_INDEX_MODE::AR_X; } else if (node.token == "[AR.y]") { return latte::SQ_INDEX_MODE::AR_Y; } else if (node.token == "[AR.z]") { return latte::SQ_INDEX_MODE::AR_Z; } else if (node.token == "[AR.w]") { return latte::SQ_INDEX_MODE::AR_W; } else if (node.token == "[AL]") { return latte::SQ_INDEX_MODE::LOOP; } else { throw node_parse_exception { node, fmt::format("Invalid SQ_INDEX_MODE {}", node.token) }; } } latte::SQ_CF_COND parseCfCond(peg::Ast &node) { if (node.token == "ACTIVE") { return latte::SQ_CF_COND::ACTIVE; } else if (node.token == "ALWAYS_FALSE") { return latte::SQ_CF_COND::ALWAYS_FALSE; } else if (node.token == "CF_BOOL") { return latte::SQ_CF_COND::CF_BOOL; } else if (node.token == "CF_NOT_BOOL") { return latte::SQ_CF_COND::CF_NOT_BOOL; } else { throw node_parse_exception { node, fmt::format("Invalid SQ_CF_COND {}", node.token) }; } } latte::SQ_CHAN parseChan(peg::Ast &node) { switch (node.token[0]) { case 'x': case 'X': return latte::SQ_CHAN::X; case 'y': case 'Y': return latte::SQ_CHAN::Y; case 'z': case 'Z': return latte::SQ_CHAN::Z; case 'w': case 'W': return latte::SQ_CHAN::W; case 't': case 'T': return latte::SQ_CHAN::T; default: throw node_parse_exception { node, fmt::format("Invalid SQ_CHAN {}", node.token[0]) }; } } size_t parseFourCompSwizzle(peg::Ast &node, latte::SQ_SEL &selX, latte::SQ_SEL &selY, latte::SQ_SEL &selZ, latte::SQ_SEL &selW) { assert(node.is_token); size_t numSel = 0; if (node.token.size() > 0) { selX = parseSel(node, 0); numSel++; } if (node.token.size() > 1) { selY = parseSel(node, 1); numSel++; } if (node.token.size() > 2) { selZ = parseSel(node, 2); numSel++; } if (node.token.size() > 3) { selW = parseSel(node, 3); numSel++; } return numSel; } latte::SQ_ALU_OMOD parseOutputModifier(peg::Ast &node) { if (node.token == "/2") { return latte::SQ_ALU_OMOD::D2; } else if (node.token == "*2") { return latte::SQ_ALU_OMOD::M2; } else if (node.token == "*4") { return latte::SQ_ALU_OMOD::M4; } else { throw node_parse_exception { node, fmt::format("Invalid SQ_ALU_OMOD {}", node.token) }; } } latte::SQ_PRED_SEL parsePredSel(peg::Ast &node) { if (node.token == "PRED_SEL_OFF") { return latte::SQ_PRED_SEL::OFF; } else if (node.token == "PRED_SEL_ZERO") { return latte::SQ_PRED_SEL::ZERO; } else if (node.token == "PRED_SEL_ONE") { return latte::SQ_PRED_SEL::ONE; } else { throw node_parse_exception { node, fmt::format("Invalid SQ_PRED_SEL {}", node.token) }; } } latte::SQ_SEL parseSel(peg::Ast &node, unsigned index) { switch (node.token[index]) { case 'x': case 'X': return latte::SQ_SEL::SEL_X; case 'y': case 'Y': return latte::SQ_SEL::SEL_Y; case 'z': case 'Z': return latte::SQ_SEL::SEL_Z; case 'w': case 'W': return latte::SQ_SEL::SEL_W; case '0': return latte::SQ_SEL::SEL_0; case '1': return latte::SQ_SEL::SEL_1; case '_': return latte::SQ_SEL::SEL_MASK; default: throw node_parse_exception { node, fmt::format("Invalid SQ_SEL {}", node.token[index]) }; } } ================================================ FILE: tools/latte-assembler/src/assembler_parse.cpp ================================================ #include "shader_assembler.h" #include "grammar.h" bool assembleShaderCode(Shader &shader, std::string_view code) { peg::parser parser; parser.log = [&](size_t ln, size_t col, const std::string &msg) { std::cout << "Error parsing grammar:" << ln << ":" << col << ": " << msg << std::endl; }; if (!parser.load_grammar(LatteGrammar)) { return false; } parser.log = [&](size_t ln, size_t col, const std::string &msg) { std::cout << shader.path << ":" << ln << ":" << col << ": " << msg << std::endl; }; parser.enable_ast(); std::shared_ptr<peg::Ast> ast; if (!parser.parse_n(code.data(), code.size(), ast)) { return false; } ast = peg::AstOptimizer(false).optimize(ast); assembleAST(shader, ast); return true; } ================================================ FILE: tools/latte-assembler/src/assembler_tex.cpp ================================================ #include "shader_assembler.h" #include "assembler_instructions.h" static void assembleTexInst(Shader &shader, TexClause &clause, peg::Ast &node) { auto inst = latte::TextureFetchInst { }; std::memset(&inst, 0, sizeof(latte::TextureFetchInst)); inst.word0 = inst.word0 .SRC_REL(latte::SQ_REL::ABS); inst.word1 = inst.word1 .DST_REL(latte::SQ_REL::ABS) .DST_SEL_X(latte::SQ_SEL::SEL_X) .DST_SEL_Y(latte::SQ_SEL::SEL_Y) .DST_SEL_Z(latte::SQ_SEL::SEL_Z) .DST_SEL_W(latte::SQ_SEL::SEL_W); inst.word2 = inst.word2 .SRC_SEL_X(latte::SQ_SEL::SEL_X) .SRC_SEL_Y(latte::SQ_SEL::SEL_Y) .SRC_SEL_Z(latte::SQ_SEL::SEL_Z) .SRC_SEL_W(latte::SQ_SEL::SEL_W); inst.word1 = inst.word1 .COORD_TYPE_X(latte::SQ_TEX_COORD_TYPE::NORMALIZED) .COORD_TYPE_Y(latte::SQ_TEX_COORD_TYPE::NORMALIZED) .COORD_TYPE_Z(latte::SQ_TEX_COORD_TYPE::NORMALIZED) .COORD_TYPE_W(latte::SQ_TEX_COORD_TYPE::NORMALIZED); for (auto &child : node.nodes) { if (child->name == "InstCount") { auto clausePC = parseNumber(*child); if (clausePC != shader.clausePC) { throw incorrect_clause_pc_exception { *child, clausePC, shader.clausePC }; } shader.clausePC++; } else if (child->name == "TexOpcode") { auto &name = child->token; auto opcode = getTexInstructionByName(name); if (opcode == latte::SQ_TEX_INST_INVALID) { throw invalid_tex_inst_exception { *child }; } inst.word0 = inst.word0 .TEX_INST(opcode); } else if (child->name == "TexDst") { for (auto &dst : child->nodes) { if (dst->name == "WriteMask") { inst.word1 = inst.word1 .DST_SEL_X(latte::SQ_SEL::SEL_MASK) .DST_SEL_Y(latte::SQ_SEL::SEL_MASK) .DST_SEL_Z(latte::SQ_SEL::SEL_MASK) .DST_SEL_W(latte::SQ_SEL::SEL_MASK); } else if (dst->name == "Gpr") { inst.word1 = inst.word1 .DST_GPR(parseNumber(*dst)); markGprWritten(shader, inst.word1.DST_GPR()); } else if (dst->name == "TexRel") { inst.word1 = inst.word1 .DST_REL(latte::SQ_REL::REL); } else if (dst->name == "FourCompSwizzle") { auto selX = latte::SQ_SEL::SEL_X; auto selY = latte::SQ_SEL::SEL_Y; auto selZ = latte::SQ_SEL::SEL_Z; auto selW = latte::SQ_SEL::SEL_W; parseFourCompSwizzle(*dst, selX, selY, selZ, selW); inst.word1 = inst.word1 .DST_SEL_X(selX) .DST_SEL_Y(selY) .DST_SEL_Z(selZ) .DST_SEL_W(selW); } else { throw unhandled_node_exception { *dst }; } } } else if (child->name == "TexSrc") { for (auto &src : child->nodes) { if (src->name == "Gpr") { inst.word0 = inst.word0 .SRC_GPR(parseNumber(*src)); markGprRead(shader, inst.word0.SRC_GPR()); } else if (src->name == "TexRel") { inst.word0 = inst.word0 .SRC_REL(latte::SQ_REL::REL); } else if (src->name == "FourCompSwizzle") { auto selX = latte::SQ_SEL::SEL_X; auto selY = latte::SQ_SEL::SEL_Y; auto selZ = latte::SQ_SEL::SEL_Z; auto selW = latte::SQ_SEL::SEL_W; parseFourCompSwizzle(*src, selX, selY, selZ, selW); inst.word2 = inst.word2 .SRC_SEL_X(selX) .SRC_SEL_Y(selY) .SRC_SEL_Z(selZ) .SRC_SEL_W(selW); } else { throw unhandled_node_exception { *src }; } } } else if (child->name == "TexResourceId") { inst.word0 = inst.word0 .RESOURCE_ID(parseNumber(*child)); } else if (child->name == "TexSamplerId") { inst.word2 = inst.word2 .SAMPLER_ID(parseNumber(*child)); } else if (child->name == "TexProperties") { for (auto &prop : child->nodes) { if (prop->name == "ALT_CONST") { inst.word0 = inst.word0 .ALT_CONST(true); } else if (prop->name == "BC_FRAC_MODE") { inst.word0 = inst.word0 .BC_FRAC_MODE(true); } else if (prop->name == "DENORM") { if (prop->token.find_first_of('x') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_X(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED); } if (prop->token.find_first_of('y') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_Y(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED); } if (prop->token.find_first_of('z') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_Z(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED); } if (prop->token.find_first_of('w') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_W(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED); } } else if (prop->name == "NORM") { if (prop->token.find_first_of('x') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_X(latte::SQ_TEX_COORD_TYPE::NORMALIZED); } if (prop->token.find_first_of('y') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_Y(latte::SQ_TEX_COORD_TYPE::NORMALIZED); } if (prop->token.find_first_of('z') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_Z(latte::SQ_TEX_COORD_TYPE::NORMALIZED); } if (prop->token.find_first_of('w') != std::string::npos) { inst.word1 = inst.word1 .COORD_TYPE_W(latte::SQ_TEX_COORD_TYPE::NORMALIZED); } } else if (prop->name == "LOD") { inst.word1 = inst.word1 .LOD_BIAS(sfixed_1_3_3_t { parseFloat(*prop) }); } else if (prop->name == "WHOLE_QUAD_MODE") { inst.word0 = inst.word0 .FETCH_WHOLE_QUAD(true); } else if (prop->name == "XOFFSET") { inst.word2 = inst.word2 .OFFSET_X(sfixed_1_3_1_t { parseFloat(*prop) }); } else if (prop->name == "YOFFSET") { inst.word2 = inst.word2 .OFFSET_Y(sfixed_1_3_1_t { parseFloat(*prop) }); } else if (prop->name == "ZOFFSET") { inst.word2 = inst.word2 .OFFSET_Z(sfixed_1_3_1_t { parseFloat(*prop) }); } else { throw invalid_tex_property_exception { *prop }; } } } else { throw unhandled_node_exception { *child }; } } clause.insts.push_back(inst); } void assembleTexClause(Shader &shader, peg::Ast &node) { auto cfInst = latte::ControlFlowInst { }; auto clause = TexClause {}; clause.clausePC = shader.clausePC; cfInst.word1 = cfInst.word1 .BARRIER(true); for (auto &child : node.nodes) { if (child->name == "InstCount") { clause.cfPC = parseNumber(*child); if (clause.cfPC != shader.cfInsts.size()) { throw incorrect_cf_pc_exception { *child, clause.cfPC, shader.cfInsts.size() }; } } else if (child->name == "TexClauseInstType") { auto &name = child->token; auto opcode = getCfInstructionByName(name); if (opcode == latte::SQ_CF_INST_INVALID) { throw invalid_cf_tex_inst_exception { *child }; } cfInst.word1 = cfInst.word1 .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_NORMAL) .CF_INST(opcode); } else if (child->name == "TexClauseProperties") { for (auto &prop : child->nodes) { if (prop->name == "ADDR") { clause.addrNode = prop; cfInst.word0 = cfInst.word0 .ADDR(parseNumber(*prop)); } else if (prop->name == "CNT") { auto count = parseNumber(*prop) - 1; clause.countNode = prop; cfInst.word1 = cfInst.word1 .COUNT_3(count >> 3) .COUNT(count & 0b111); } else if (prop->name == "CND") { cfInst.word1 = cfInst.word1 .COND(parseCfCond(*prop)); } else if (prop->name == "CF_CONST") { cfInst.word1 = cfInst.word1 .CF_CONST(parseNumber(*prop)); } else if (prop->name == "NO_BARRIER") { cfInst.word1 = cfInst.word1 .BARRIER(false); } else if (prop->name == "WHOLE_QUAD_MODE") { cfInst.word1 = cfInst.word1 .WHOLE_QUAD_MODE(true); } else if (prop->name == "VALID_PIX") { cfInst.word1 = cfInst.word1 .VALID_PIXEL_MODE(true); } else { throw invalid_cf_tex_property_exception { *prop }; } } } else if (child->name == "TexInst") { assembleTexInst(shader, clause, *child); } else { throw unhandled_node_exception { *child }; } } shader.texClauses.push_back(std::move(clause)); shader.cfInsts.push_back(cfInst); } ================================================ FILE: tools/latte-assembler/src/gfd.cpp ================================================ #include "gfd_comment_parser.h" #include <libgpu/latte/latte_constants.h> #include <cstring> #include <fmt/core.h> #include <libgfd/gfd.h> #include <regex> #include <vector> using namespace cafe::gx2; /* Matches: ; $Something = true ; $attribVars[1].type = "Float4" ; $VGT_HOS_REUSE_DEPTH.REUSE_DEPTH = 16 ; $SQ_VTX_SEMANTIC_CLEAR.CLEAR = 0xFFFFFFFC */ static std::regex sCommentKeyValueRegex { ";[[:space:]]*\\$([_[:alnum:]]+)(?:\\[([[:digit:]]+)\\])?(?:\\.([_[:alnum:]]+))?[[:space:]]*=[[:space:]]*(\"[^\"]+\"|[0-9]+|0x[0-9a-fA-F]+|true|false|TRUE|FALSE)" }; static std::regex sCommentKeyValueStartRegex { ";[[:space:]]*\\$" }; bool parseComment(const std::string &comment, CommentKeyValue &out) { std::smatch match; if (!std::regex_match(comment, match, sCommentKeyValueRegex)) { if (std::regex_match(comment, match, sCommentKeyValueStartRegex)) { throw gfd_header_parse_exception { fmt::format("Syntax error in comment {}", comment) }; } return false; } out.obj = match[1]; out.index = match[2]; out.member = match[3]; out.value = match[4]; if (out.value.size() >= 2 && out.value[0] == '"') { // Erase quotes from string value out.value.erase(out.value.begin()); out.value.erase(out.value.end() - 1); } return true; } void ensureArrayOfObjects(const CommentKeyValue &kv) { if (!kv.isArrayOfObjects()) { throw gfd_header_parse_exception { fmt::format("{} is an array of objects", kv.obj) }; } } void ensureArrayOfValues(const CommentKeyValue &kv) { if (!kv.isArrayOfValues()) { throw gfd_header_parse_exception { fmt::format("{} is an array of values", kv.obj) }; } } void ensureObject(const CommentKeyValue &kv) { if (!kv.isObject()) { throw gfd_header_parse_exception { fmt::format("{} is an object", kv.obj) }; } } void ensureValue(const CommentKeyValue &kv) { if (!kv.isValue()) { throw gfd_header_parse_exception { fmt::format("{} is a value", kv.obj) }; } } GX2ShaderVarType parseShaderVarType(const std::string &v) { auto value = v; std::transform(value.begin(), value.end(), value.begin(), [](char x) { return static_cast<char>(::tolower(x)); }); if (value == "void") { return GX2ShaderVarType::Void; } else if (value == "bool") { return GX2ShaderVarType::Bool; } else if (value == "int") { return GX2ShaderVarType::Int; } else if (value == "uint") { return GX2ShaderVarType::Uint; } else if (value == "float") { return GX2ShaderVarType::Float; } else if (value == "double") { return GX2ShaderVarType::Double; } else if (value == "dvec2") { return GX2ShaderVarType::Double2; } else if (value == "dvec3") { return GX2ShaderVarType::Double3; } else if (value == "dvec4") { return GX2ShaderVarType::Double4; } else if (value == "vec2") { return GX2ShaderVarType::Float2; } else if (value == "vec3") { return GX2ShaderVarType::Float3; } else if (value == "vec4") { return GX2ShaderVarType::Float4; } else if (value == "bvec2") { return GX2ShaderVarType::Bool2; } else if (value == "bvec3") { return GX2ShaderVarType::Bool3; } else if (value == "bvec4") { return GX2ShaderVarType::Bool4; } else if (value == "ivec2") { return GX2ShaderVarType::Int2; } else if (value == "ivec3") { return GX2ShaderVarType::Int3; } else if (value == "ivec4") { return GX2ShaderVarType::Int4; } else if (value == "uvec2") { return GX2ShaderVarType::Uint2; } else if (value == "uvec3") { return GX2ShaderVarType::Uint3; } else if (value == "uvec4") { return GX2ShaderVarType::Uint4; } else if (value == "mat2x2" || value == "mat2") { return GX2ShaderVarType::Float2x2; } else if (value == "mat2x3") { return GX2ShaderVarType::Float2x3; } else if (value == "mat2x4") { return GX2ShaderVarType::Float2x4; } else if (value == "mat3x2") { return GX2ShaderVarType::Float3x2; } else if (value == "mat3x3" || value == "mat3") { return GX2ShaderVarType::Float3x3; } else if (value == "mat3x4") { return GX2ShaderVarType::Float3x4; } else if (value == "mat4x2") { return GX2ShaderVarType::Float4x2; } else if (value == "mat4x3") { return GX2ShaderVarType::Float4x3; } else if (value == "mat4x4" || value == "mat4") { return GX2ShaderVarType::Float4x4; } else if (value == "dmat2x2" || value == "dmat2") { return GX2ShaderVarType::Double2x2; } else if (value == "dmat2x3") { return GX2ShaderVarType::Double2x3; } else if (value == "dmat2x4") { return GX2ShaderVarType::Double2x4; } else if (value == "dmat3x2") { return GX2ShaderVarType::Double3x2; } else if (value == "dmat3x3" || value == "dmat3") { return GX2ShaderVarType::Double3x3; } else if (value == "dmat3x4") { return GX2ShaderVarType::Double3x4; } else if (value == "dmat4x2") { return GX2ShaderVarType::Double4x2; } else if (value == "dmat4x3") { return GX2ShaderVarType::Double4x3; } else if (value == "dmat4x4" || value == "dmat4") { return GX2ShaderVarType::Double4x4; } else { throw gfd_header_parse_exception { fmt::format("Invalid GX2ShaderVarType {}", value) }; } } GX2SamplerVarType parseSamplerVarType(const std::string &v) { auto value = v; std::transform(value.begin(), value.end(), value.begin(), ::toupper); if (value == "SAMPLER1D") { return GX2SamplerVarType::Sampler1D; } else if (value == "SAMPLER2D") { return GX2SamplerVarType::Sampler2D; } else if (value == "SAMPLER3D") { return GX2SamplerVarType::Sampler3D; } else if (value == "SAMPLERCUBE") { return GX2SamplerVarType::SamplerCube; } else { throw gfd_header_parse_exception { fmt::format("Invalid GX2SamplerVarType {}", value) }; } } GX2ShaderMode parseShaderMode(const std::string &v) { auto value = v; std::transform(value.begin(), value.end(), value.begin(), ::toupper); if (value == "UNIFORMREGISTER") { return GX2ShaderMode::UniformRegister; } else if (value == "UNIFORMBLOCK") { return GX2ShaderMode::UniformBlock; } else if (value == "GEOMETRYSHADER") { return GX2ShaderMode::GeometryShader; } else if (value == "COMPUTERSHADER") { return GX2ShaderMode::ComputeShader; } else { throw gfd_header_parse_exception { fmt::format("Invalid GX2ShaderMode {}", value) }; } } void parseUniformBlocks(std::vector<gfd::GFDUniformBlock> &UniformBlocks, uint32_t index, const std::string &member, const std::string &value) { if (index >= latte::MaxUniformBlocks) { throw gfd_header_parse_exception { fmt::format("UNIFORM_BLOCKS[{}] invalid index, max: {}", index, latte::MaxUniformBlocks) }; } if (index >= UniformBlocks.size()) { UniformBlocks.resize(index + 1); UniformBlocks[index].offset = index + 1; UniformBlocks[index].size = 16; } if (member == "NAME") { UniformBlocks[index].name = value; } else if (member == "OFFSET") { UniformBlocks[index].offset = parseValueNumber(value); } else if (member == "SIZE") { UniformBlocks[index].size = parseValueNumber(value); if (UniformBlocks[index].size >= latte::MaxUniformBlockSize) { throw gfd_header_parse_exception { fmt::format("UNIFORM_BLOCKS[{}] invalid index, max: {}", index, latte::MaxUniformBlocks) }; } } else { throw gfd_header_parse_exception { fmt::format("UNIFORM_BLOCKS[{}] does not have member {}", index, member) }; } } void parseUniformVars(std::vector<gfd::GFDUniformVar> &uniformVars, uint32_t index, const std::string &member, const std::string &value) { if (index >= latte::MaxUniformRegisters) { throw gfd_header_parse_exception { fmt::format("UNIFORM_VARS[{}] invalid index, max: {}", index, latte::MaxUniformRegisters) }; } if (index >= uniformVars.size()) { uniformVars.resize(index + 1); uniformVars[index].type = GX2ShaderVarType::Float4; uniformVars[index].count = 1; uniformVars[index].block = -1; } if (member == "NAME") { uniformVars[index].name = value; } else if (member == "BLOCK") { uniformVars[index].block = parseValueNumber(value); } else if (member == "COUNT") { uniformVars[index].count = parseValueNumber(value); } else if (member == "OFFSET") { uniformVars[index].offset = parseValueNumber(value); } else if (member == "TYPE") { uniformVars[index].type = parseShaderVarType(value); } else { throw gfd_header_parse_exception { fmt::format("UNIFORM_VARS[{}] does not have member {}", index, member) }; } } void parseInitialValues(std::vector<gfd::GFDUniformInitialValue> &initialValues, uint32_t index, const std::string &member, const std::string &value) { if (index >= latte::MaxUniformRegisters) { throw gfd_header_parse_exception { fmt::format("INITIAL_VALUES[{}] invalid index, max: {}", index, latte::MaxUniformRegisters) }; } if (index >= initialValues.size()) { initialValues.resize(index + 1); } if (member == "OFFSET") { initialValues[index].offset = parseValueNumber(value); } else if (member == "VALUE[0]") { initialValues[index].value[0] = parseValueFloat(value); } else if (member == "VALUE[1]") { initialValues[index].value[1] = parseValueFloat(value); } else if (member == "VALUE[2]") { initialValues[index].value[2] = parseValueFloat(value); } else if (member == "VALUE[3]") { initialValues[index].value[3] = parseValueFloat(value); } else { throw gfd_header_parse_exception { fmt::format("INITIAL_VALUES[{}] does not have member {}", index, member) }; } } void parseLoopVars(std::vector<gfd::GFDLoopVar> &loopVars, uint32_t index, const std::string &member, const std::string &value) { if (index >= loopVars.size()) { loopVars.resize(index + 1); loopVars[index].offset = index; } if (member == "OFFSET") { loopVars[index].offset = parseValueNumber(value); } else if (member == "VALUE") { loopVars[index].value = parseValueNumber(value); } else { throw gfd_header_parse_exception { fmt::format("LOOP_VARS[{}] does not have member {}", index, member) }; } } void parseSamplerVars(std::vector<gfd::GFDSamplerVar> &samplerVars, uint32_t index, const std::string &member, const std::string &value) { if (index >= latte::MaxSamplers) { throw gfd_header_parse_exception { fmt::format("SAMPLER_VARS[{}] invalid index, max: {}", index, latte::MaxSamplers) }; } if (index >= samplerVars.size()) { samplerVars.resize(index + 1); samplerVars[index].type = GX2SamplerVarType::Sampler2D; samplerVars[index].location = index; } if (member == "NAME") { samplerVars[index].name = value; } else if (member == "LOCATION") { samplerVars[index].location = parseValueNumber(value); } else if (member == "TYPE") { samplerVars[index].type = parseSamplerVarType(value); } else { throw gfd_header_parse_exception { fmt::format("SAMPLER_VARS[{}] does not have member {}", index, member) }; } } bool parseValueBool(const std::string &v) { auto value = v; std::transform(value.begin(), value.end(), value.begin(), ::toupper); if (value == "TRUE") { return true; } else if (value == "FALSE") { return false; } else { throw gfd_header_parse_exception { fmt::format("Expected boolean value, found {}", value) }; } } uint32_t parseValueNumber(const std::string &v) { return static_cast<uint32_t>(std::stoul(v, 0, 0)); } float parseValueFloat(const std::string &v) { return static_cast<float>(std::stof(v)); } static std::vector<uint8_t> getShaderBinary(Shader &shader) { std::vector<uint8_t> binary; auto cfStart = size_t { 0 }; auto cfSize = shader.cfInsts.size() * sizeof(shader.cfInsts[0]); auto cfEnd = cfStart + cfSize; auto aluStart = shader.aluClauseBaseAddress * 8; auto aluSize = shader.aluClauseData.size() * sizeof(shader.aluClauseData[0]); auto aluEnd = aluStart + aluSize; auto texStart = shader.texClauseBaseAddress * 8; auto texSize = shader.texClauseData.size() * sizeof(shader.texClauseData[0]); auto texEnd = texStart + texSize; binary.resize(std::max({ cfEnd, aluEnd, texEnd }), 0); std::memcpy(binary.data() + cfStart, shader.cfInsts.data(), cfSize); std::memcpy(binary.data() + aluStart, shader.aluClauseData.data(), aluSize); std::memcpy(binary.data() + texStart, shader.texClauseData.data(), texSize); return std::move(binary); } static uint32_t countNumGpr(Shader &shader) { auto highestRead = uint32_t { 0 }; auto highestWritten = uint32_t { 0 }; for (auto i = 0u; i < shader.gprRead.size(); ++i) { if (!shader.gprRead[i]) { continue; } if (i >= (latte::SQ_ALU_SRC::REGISTER_TEMP_FIRST - latte::SQ_ALU_SRC::REGISTER_FIRST)) { // Ignore temporary registers continue; } highestRead = std::max<uint32_t>(highestRead, i); } for (auto i = 0u; i < shader.gprWritten.size(); ++i) { if (!shader.gprWritten[i]) { continue; } if (i >= (latte::SQ_ALU_SRC::REGISTER_TEMP_FIRST - latte::SQ_ALU_SRC::REGISTER_FIRST)) { // Ignore temporary registers continue; } highestWritten = std::max<uint32_t>(highestWritten, i); } return std::max(highestRead, highestWritten) + 1; } bool gfdAddVertexShader(gfd::GFDFile &file, Shader &shader) { auto out = gfd::GFDVertexShader {}; auto numGpr = countNumGpr(shader); // Initialise some default values out.ringItemSize = 0; out.hasStreamOut = false; out.streamOutStride.fill(0); out.gx2rData.elemCount = 0; out.gx2rData.elemSize = 0; out.gx2rData.flags = static_cast<GX2RResourceFlags>(0); if (shader.uniformBlocksUsed) { out.mode = GX2ShaderMode::UniformBlock; } else { out.mode = GX2ShaderMode::UniformRegister; } std::memset(&out.regs, 0, sizeof(out.regs)); out.regs.spi_vs_out_id.fill(latte::SPI_VS_OUT_ID_N::get(0xFFFFFFFF)); out.regs.sq_pgm_resources_vs = out.regs.sq_pgm_resources_vs .NUM_GPRS(numGpr) .STACK_SIZE(1); out.regs.vgt_hos_reuse_depth = out.regs.vgt_hos_reuse_depth .REUSE_DEPTH(16); out.regs.vgt_vertex_reuse_block_cntl = out.regs.vgt_vertex_reuse_block_cntl .VTX_REUSE_DEPTH(14); // Create binary out.data = getShaderBinary(shader); // Parse shader comments parseShaderComments(out, shader.comments); // Set out pa_cl_vs_out_cntl properly if (out.regs.pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE() || out.regs.pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG() || out.regs.pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX() || out.regs.pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX() || out.regs.pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG() || out.regs.pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG()) { out.regs.pa_cl_vs_out_cntl = out.regs.pa_cl_vs_out_cntl. VS_OUT_MISC_VEC_ENA(true). VS_OUT_MISC_SIDE_BUS_ENA(true); } if (out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_0() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_1() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_2() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_3() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_4() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_5() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_6() || out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_7()) { out.regs.pa_cl_vs_out_cntl = out.regs.pa_cl_vs_out_cntl. VS_OUT_CCDIST0_VEC_ENA(true); } if (out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_0() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_1() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_2() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_3() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_4() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_5() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_6() || out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_7()) { out.regs.pa_cl_vs_out_cntl = out.regs.pa_cl_vs_out_cntl. VS_OUT_CCDIST1_VEC_ENA(true); } // NUM_GPRS should be the number of GPRs used in the shader if (out.regs.sq_pgm_resources_vs.NUM_GPRS() != numGpr) { throw gfd_header_parse_exception { fmt::format("Invalid SQ_PGM_RESOURCES_VS.NUM_GPRS {}, expected {}", out.regs.sq_pgm_resources_vs.NUM_GPRS(), numGpr) }; } // NUM_SQ_VTX_SEMANTIC should reflect the size of ATTRIB_VARS array if (out.regs.num_sq_vtx_semantic == 0) { out.regs.num_sq_vtx_semantic = static_cast<uint32_t>(out.attribVars.size()); for (auto i = 0u; i < out.regs.num_sq_vtx_semantic; ++i) { out.regs.sq_vtx_semantic[i] = out.regs.sq_vtx_semantic[i] .SEMANTIC_ID(out.attribVars[i].location); } } else if (out.regs.num_sq_vtx_semantic != out.attribVars.size()) { throw gfd_header_parse_exception { fmt::format("Invalid NUM_SQ_VTX_SEMANTIC {}, expected {}", out.regs.num_sq_vtx_semantic, out.attribVars.size()) }; } for (auto i = out.regs.num_sq_vtx_semantic; i < out.regs.sq_vtx_semantic.size(); ++i) { out.regs.sq_vtx_semantic[i] = out.regs.sq_vtx_semantic[i] .SEMANTIC_ID(0xFF); } // SQ_VTX_SEMANTIC_CLEAR.CLEAR should reflect the value of NUM_SQ_VTX_SEMANTIC auto semanticClear = static_cast<uint32_t>(~((1 << out.regs.num_sq_vtx_semantic) - 1)); if (out.regs.sq_vtx_semantic_clear.CLEAR() == 0) { out.regs.sq_vtx_semantic_clear = out.regs.sq_vtx_semantic_clear .CLEAR(semanticClear); } else if (out.regs.sq_vtx_semantic_clear.CLEAR() != semanticClear) { throw gfd_header_parse_exception { fmt::format("Invalid SQ_VTX_SEMANTIC_CLEAR {:#x}, expected {:#x}", out.regs.sq_vtx_semantic_clear.CLEAR(), semanticClear) }; } file.vertexShaders.push_back(out); return true; } bool gfdAddPixelShader(gfd::GFDFile &file, Shader &shader) { auto out = gfd::GFDPixelShader {}; auto numGpr = countNumGpr(shader); // Initialise some default values out.gx2rData.elemCount = 0; out.gx2rData.elemSize = 0; out.gx2rData.flags = static_cast<GX2RResourceFlags>(0); if (shader.uniformBlocksUsed) { out.mode = GX2ShaderMode::UniformBlock; } else { out.mode = GX2ShaderMode::UniformRegister; } std::memset(&out.regs, 0, sizeof(out.regs)); out.regs.cb_shader_mask = out.regs.cb_shader_mask .OUTPUT0_ENABLE(0b1111); out.regs.cb_shader_control = out.regs.cb_shader_control .RT0_ENABLE(true); out.regs.db_shader_control = out.regs.db_shader_control .Z_ORDER(latte::DB_Z_ORDER::EARLY_Z_THEN_LATE_Z); out.regs.spi_ps_in_control_0 = out.regs.spi_ps_in_control_0 .BARYC_SAMPLE_CNTL(latte::SPI_BARYC_CNTL::CENTERS_ONLY) .PERSP_GRADIENT_ENA(true); out.regs.sq_pgm_exports_ps = out.regs.sq_pgm_exports_ps .EXPORT_MODE(2); out.regs.sq_pgm_resources_ps = out.regs.sq_pgm_resources_ps .NUM_GPRS(numGpr) .STACK_SIZE(static_cast<uint32_t>(out.loopVars.size() * 2)); // Create binary out.data = getShaderBinary(shader); // Parse shader comments parseShaderComments(out, shader.comments); // NUM_GPRS should be the number of GPRs used in the shader if (out.regs.sq_pgm_resources_ps.NUM_GPRS() != numGpr) { throw gfd_header_parse_exception { fmt::format("Invalid SQ_PGM_RESOURCES_PS.NUM_GPRS {}, expected {}", out.regs.sq_pgm_resources_ps.NUM_GPRS(), numGpr) }; } if (out.regs.spi_ps_in_control_0.NUM_INTERP() == 0) { out.regs.spi_ps_in_control_0 = out.regs.spi_ps_in_control_0 .NUM_INTERP(out.regs.num_spi_ps_input_cntl); } else if (out.regs.spi_ps_in_control_0.NUM_INTERP() != out.regs.num_spi_ps_input_cntl) { throw gfd_header_parse_exception { fmt::format("Expected SPI_PS_IN_CONTROL_0.NUM_INTERP {} to equal NUM_SPI_PS_INPUT_CNTL {}", out.regs.spi_ps_in_control_0.NUM_INTERP(), out.regs.num_spi_ps_input_cntl) }; } file.pixelShaders.push_back(out); return true; } ================================================ FILE: tools/latte-assembler/src/gfd_comment_parser.h ================================================ #pragma once #include "shader.h" #include <cstdint> #include <libgfd/gfd.h> #include <vector> #include <string> class gfd_header_parse_exception : public std::runtime_error { public: gfd_header_parse_exception(const std::string &m) : std::runtime_error(m) { } private: std::string mMessage; }; struct CommentKeyValue { bool isValue() const { return member.empty() && index.empty(); } bool isObject() const { return !member.empty() && index.empty(); } bool isArrayOfValues() const { return !index.empty() && member.empty(); } bool isArrayOfObjects() const { return !member.empty() && !index.empty(); } std::string obj; std::string index; std::string member; std::string value; }; void ensureArrayOfObjects(const CommentKeyValue &kv); void ensureArrayOfValues(const CommentKeyValue &kv); void ensureObject(const CommentKeyValue &kv); void ensureValue(const CommentKeyValue &kv); bool parseComment(const std::string &comment, CommentKeyValue &out); bool parseValueBool(const std::string &value); uint32_t parseValueNumber(const std::string &value); float parseValueFloat(const std::string &v); bool gfdAddVertexShader(gfd::GFDFile &file, Shader &shader); bool gfdAddPixelShader(gfd::GFDFile &file, Shader &shader); cafe::gx2::GX2ShaderVarType parseShaderVarType(const std::string &v); cafe::gx2::GX2SamplerVarType parseSamplerVarType(const std::string &v); cafe::gx2::GX2ShaderMode parseShaderMode(const std::string &v); void parseUniformBlocks(std::vector<gfd::GFDUniformBlock> &UniformBlocks, uint32_t index, const std::string &member, const std::string &value); void parseUniformVars(std::vector<gfd::GFDUniformVar> &uniformVars, uint32_t index, const std::string &member, const std::string &value); void parseInitialValues(std::vector<gfd::GFDUniformInitialValue> &initialValues, uint32_t index, const std::string &member, const std::string &value); void parseLoopVars(std::vector<gfd::GFDLoopVar> &loopVars, uint32_t index, const std::string &member, const std::string &value); void parseSamplerVars(std::vector<gfd::GFDSamplerVar> &samplerVars, uint32_t index, const std::string &member, const std::string &value); bool parseShaderComments(gfd::GFDVertexShader &shader, std::vector<std::string> &comments); bool parseShaderComments(gfd::GFDPixelShader &shader, std::vector<std::string> &comments); ================================================ FILE: tools/latte-assembler/src/gfd_psh_comment_parser.cpp ================================================ #include "gfd_comment_parser.h" #include <fmt/core.h> static void parseRegisterValue(latte::SQ_PGM_RESOURCES_PS ®, const std::string &member, const std::string &value) { if (member == "NUM_GPRS") { reg = reg .NUM_GPRS(parseValueNumber(value)); } else if (member == "STACK_SIZE") { reg = reg .STACK_SIZE(parseValueNumber(value)); } else if (member == "DX10_CLAMP") { reg = reg .DX10_CLAMP(parseValueBool(value)); } else if (member == "PRIME_CACHE_PGM_EN") { reg = reg .PRIME_CACHE_PGM_EN(parseValueBool(value)); } else if (member == "PRIME_CACHE_ON_DRAW") { reg = reg .PRIME_CACHE_ON_DRAW(parseValueBool(value)); } else if (member == "FETCH_CACHE_LINES") { reg = reg .FETCH_CACHE_LINES(parseValueNumber(value)); } else if (member == "UNCACHED_FIRST_INST") { reg = reg .UNCACHED_FIRST_INST(parseValueBool(value)); } else if (member == "PRIME_CACHE_ENABLE") { reg = reg .PRIME_CACHE_ENABLE(parseValueBool(value)); } else if (member == "PRIME_CACHE_ON_CONST") { reg = reg .PRIME_CACHE_ON_CONST(parseValueBool(value)); } else if (member == "CLAMP_CONSTS") { reg = reg .CLAMP_CONSTS(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SQ_PGM_RESOURCES_PS does not have member {}", member) }; } } static void parseRegisterValue(latte::SQ_PGM_EXPORTS_PS ®, const std::string &member, const std::string &value) { if (member == "EXPORT_MODE") { reg = reg .EXPORT_MODE(parseValueNumber(value)); } else { throw gfd_header_parse_exception { fmt::format("SQ_PGM_EXPORTS_PS does not have member {}", member) }; } } static latte::SPI_BARYC_CNTL parseSpiBarycCntl(const std::string &v) { auto value = v; std::transform(value.begin(), value.end(), value.begin(), ::toupper); if (value == "CENTROIDS_ONLY") { return latte::SPI_BARYC_CNTL::CENTROIDS_ONLY; } else if (value == "CENTERS_ONLY") { return latte::SPI_BARYC_CNTL::CENTERS_ONLY; } else if (value == "CENTROIDS_AND_CENTERS") { return latte::SPI_BARYC_CNTL::CENTROIDS_AND_CENTERS; } else { throw gfd_header_parse_exception { fmt::format("Invalid SPI_BARYC_CNTL {}", value) }; } } static latte::DB_Z_ORDER parseDbZOrder(const std::string &v) { auto value = v; std::transform(value.begin(), value.end(), value.begin(), ::toupper); if (value == "LATE_Z") { return latte::DB_Z_ORDER::LATE_Z; } else if (value == "EARLY_Z_THEN_LATE_Z") { return latte::DB_Z_ORDER::EARLY_Z_THEN_LATE_Z; } else if (value == "RE_Z") { return latte::DB_Z_ORDER::RE_Z; } else if (value == "EARLY_Z_THEN_RE_Z") { return latte::DB_Z_ORDER::EARLY_Z_THEN_RE_Z; } else { throw gfd_header_parse_exception { fmt::format("Invalid DB_Z_ORDER {}", value) }; } } static void parseRegisterValue(latte::SPI_PS_IN_CONTROL_0 ®, const std::string &member, const std::string &value) { if (member == "NUM_INTERP") { reg = reg .NUM_INTERP(parseValueNumber(value)); } else if (member == "POSITION_ENA") { reg = reg .POSITION_ENA(parseValueBool(value)); } else if (member == "POSITION_CENTROID") { reg = reg .POSITION_CENTROID(parseValueBool(value)); } else if (member == "POSITION_ADDR") { reg = reg .POSITION_ADDR(parseValueNumber(value)); } else if (member == "PARAM_GEN") { reg = reg .PARAM_GEN(parseValueNumber(value)); } else if (member == "PARAM_GEN_ADDR") { reg = reg .PARAM_GEN_ADDR(parseValueNumber(value)); } else if (member == "BARYC_SAMPLE_CNTL") { reg = reg .BARYC_SAMPLE_CNTL(parseSpiBarycCntl(value)); } else if (member == "PERSP_GRADIENT_ENA") { reg = reg .PERSP_GRADIENT_ENA(parseValueBool(value)); } else if (member == "LINEAR_GRADIENT_ENA") { reg = reg .LINEAR_GRADIENT_ENA(parseValueBool(value)); } else if (member == "POSITION_SAMPLE") { reg = reg .POSITION_SAMPLE(parseValueBool(value)); } else if (member == "BARYC_AT_SAMPLE_ENA") { reg = reg .BARYC_AT_SAMPLE_ENA(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SPI_PS_IN_CONTROL_0 does not have member {}", member) }; } } static void parseRegisterValue(latte::SPI_PS_IN_CONTROL_1 ®, const std::string &member, const std::string &value) { if (member == "GEN_INDEX_PIX") { reg = reg .GEN_INDEX_PIX(parseValueBool(value)); } else if (member == "GEN_INDEX_PIX_ADDR") { reg = reg .GEN_INDEX_PIX_ADDR(parseValueNumber(value)); } else if (member == "FRONT_FACE_ENA") { reg = reg .FRONT_FACE_ENA(parseValueBool(value)); } else if (member == "FRONT_FACE_CHAN") { reg = reg .FRONT_FACE_CHAN(parseValueNumber(value)); } else if (member == "FRONT_FACE_ALL_BITS") { reg = reg .FRONT_FACE_ALL_BITS(parseValueBool(value)); } else if (member == "FRONT_FACE_ADDR") { reg = reg .FRONT_FACE_ADDR(parseValueNumber(value)); } else if (member == "FOG_ADDR") { reg = reg .FOG_ADDR(parseValueNumber(value)); } else if (member == "FIXED_PT_POSITION_ENA") { reg = reg .FIXED_PT_POSITION_ENA(parseValueBool(value)); } else if (member == "FIXED_PT_POSITION_ADDR") { reg = reg .FIXED_PT_POSITION_ADDR(parseValueNumber(value)); } else if (member == "POSITION_ULC") { reg = reg .POSITION_ULC(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SPI_PS_IN_CONTROL_1 does not have member {}", member) }; } } static void parseRegisterValue(std::array<latte::SPI_PS_INPUT_CNTL_N, 32> &spi_ps_input_cntls, uint32_t index, const std::string &member, const std::string &value) { if (index >= spi_ps_input_cntls.size()) { throw gfd_header_parse_exception { fmt::format("SPI_PS_INPUT_CNTL[{}] invalid index, max: {}", index, spi_ps_input_cntls.size()) }; } if (member == "SEMANTIC") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .SEMANTIC(static_cast<uint8_t>(parseValueNumber(value))); } else if (member == "DEFAULT_VAL") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .DEFAULT_VAL(parseValueNumber(value)); } else if (member == "FLAT_SHADE") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .FLAT_SHADE(parseValueBool(value)); } else if (member == "SEL_CENTROID") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .SEL_CENTROID(parseValueBool(value)); } else if (member == "SEL_LINEAR") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .SEL_LINEAR(parseValueBool(value)); } else if (member == "CYL_WRAP") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .CYL_WRAP(parseValueNumber(value)); } else if (member == "PT_SPRITE_TEX") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .PT_SPRITE_TEX(parseValueBool(value)); } else if (member == "SEL_SAMPLE") { spi_ps_input_cntls[index] = spi_ps_input_cntls[index] .SEL_SAMPLE(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SPI_PS_INPUT_CNTL[{}] does not have member {}", index, member) }; } } static void parseRegisterValue(latte::CB_SHADER_MASK ®, const std::string &member, const std::string &value) { if (member == "OUTPUT0_ENABLE") { reg = reg .OUTPUT0_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT1_ENABLE") { reg = reg .OUTPUT1_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT2_ENABLE") { reg = reg .OUTPUT2_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT3_ENABLE") { reg = reg .OUTPUT3_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT4_ENABLE") { reg = reg .OUTPUT4_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT5_ENABLE") { reg = reg .OUTPUT5_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT6_ENABLE") { reg = reg .OUTPUT6_ENABLE(parseValueBool(value)); } else if (member == "OUTPUT7_ENABLE") { reg = reg .OUTPUT7_ENABLE(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("CB_SHADER_MASK does not have member {}", member) }; } } static void parseRegisterValue(latte::CB_SHADER_CONTROL ®, const std::string &member, const std::string &value) { if (member == "RT0_ENABLE") { reg = reg .RT0_ENABLE(parseValueBool(value)); } else if (member == "RT1_ENABLE") { reg = reg .RT1_ENABLE(parseValueBool(value)); } else if (member == "RT2_ENABLE") { reg = reg .RT2_ENABLE(parseValueBool(value)); } else if (member == "RT3_ENABLE") { reg = reg .RT3_ENABLE(parseValueBool(value)); } else if (member == "RT4_ENABLE") { reg = reg .RT4_ENABLE(parseValueBool(value)); } else if (member == "RT5_ENABLE") { reg = reg .RT5_ENABLE(parseValueBool(value)); } else if (member == "RT6_ENABLE") { reg = reg .RT6_ENABLE(parseValueBool(value)); } else if (member == "RT7_ENABLE") { reg = reg .RT7_ENABLE(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("CB_SHADER_CONTROL does not have member {}", member) }; } } static void parseRegisterValue(latte::DB_SHADER_CONTROL ®, const std::string &member, const std::string &value) { if (member == "Z_EXPORT_ENABLE") { reg = reg .Z_EXPORT_ENABLE(parseValueBool(value)); } else if (member == "STENCIL_REF_EXPORT_ENABLE") { reg = reg .STENCIL_REF_EXPORT_ENABLE(parseValueBool(value)); } else if (member == "Z_ORDER") { reg = reg .Z_ORDER(parseDbZOrder(value)); } else if (member == "KILL_ENABLE") { reg = reg .KILL_ENABLE(parseValueBool(value)); } else if (member == "COVERAGE_TO_MASK_ENABLE") { reg = reg .COVERAGE_TO_MASK_ENABLE(parseValueBool(value)); } else if (member == "MASK_EXPORT_ENABLE") { reg = reg .MASK_EXPORT_ENABLE(parseValueBool(value)); } else if (member == "DUAL_EXPORT_ENABLE") { reg = reg .DUAL_EXPORT_ENABLE(parseValueBool(value)); } else if (member == "EXEC_ON_HIER_FAIL") { reg = reg .EXEC_ON_HIER_FAIL(parseValueBool(value)); } else if (member == "EXEC_ON_NOOP") { reg = reg .EXEC_ON_NOOP(parseValueBool(value)); } else if (member == "ALPHA_TO_MASK_DISABLE") { reg = reg .ALPHA_TO_MASK_DISABLE(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("DB_SHADER_CONTROL does not have member {}", member) }; } } static void parseRegisterValue(latte::SPI_INPUT_Z ®, const std::string &member, const std::string &value) { if (member == "PROVIDE_Z_TO_SPI") { reg = reg .PROVIDE_Z_TO_SPI(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SPI_INPUT_Z does not have member {}", member) }; } } bool parseShaderComments(gfd::GFDPixelShader &shader, std::vector<std::string> &comments) { for (auto &comment : comments) { CommentKeyValue kv; if (!parseComment(comment, kv)) { continue; } std::transform(kv.obj.begin(), kv.obj.end(), kv.obj.begin(), ::toupper); std::transform(kv.member.begin(), kv.member.end(), kv.member.begin(), ::toupper); if (kv.obj == "SQ_PGM_RESOURCES_PS") { ensureObject(kv); parseRegisterValue(shader.regs.sq_pgm_resources_ps, kv.member, kv.value); } else if (kv.obj == "SQ_PGM_EXPORTS_PS") { ensureObject(kv); parseRegisterValue(shader.regs.sq_pgm_exports_ps, kv.member, kv.value); } else if (kv.obj == "SPI_PS_IN_CONTROL_0") { ensureObject(kv); parseRegisterValue(shader.regs.spi_ps_in_control_0, kv.member, kv.value); } else if (kv.obj == "SPI_PS_IN_CONTROL_1") { ensureValue(kv); parseRegisterValue(shader.regs.spi_ps_in_control_1, kv.member, kv.value); } else if (kv.obj == "NUM_SPI_PS_INPUT_CNTL") { ensureValue(kv); shader.regs.num_spi_ps_input_cntl = parseValueNumber(kv.value); } else if (kv.obj == "SPI_PS_INPUT_CNTL") { ensureArrayOfObjects(kv); parseRegisterValue(shader.regs.spi_ps_input_cntls, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "CB_SHADER_MASK") { ensureObject(kv); parseRegisterValue(shader.regs.cb_shader_mask, kv.member, kv.value); } else if (kv.obj == "CB_SHADER_CONTROL") { ensureObject(kv); parseRegisterValue(shader.regs.cb_shader_control, kv.member, kv.value); } else if (kv.obj == "DB_SHADER_CONTROL") { ensureObject(kv); parseRegisterValue(shader.regs.db_shader_control, kv.member, kv.value); } else if (kv.obj == "SPI_INPUT_Z") { ensureArrayOfObjects(kv); parseRegisterValue(shader.regs.spi_input_z, kv.member, kv.value); } else if (kv.obj == "UNIFORM_BLOCKS") { ensureArrayOfObjects(kv); parseUniformBlocks(shader.uniformBlocks, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "UNIFORM_VARS") { ensureArrayOfObjects(kv); parseUniformVars(shader.uniformVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "INITIAL_VALUES") { ensureArrayOfObjects(kv); parseInitialValues(shader.initialValues, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "LOOP_VARS") { ensureArrayOfObjects(kv); parseLoopVars(shader.loopVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "SAMPLER_VARS") { ensureArrayOfObjects(kv); parseSamplerVars(shader.samplerVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "MODE") { ensureValue(kv); shader.mode = parseShaderMode(kv.value); } else { throw gfd_header_parse_exception { fmt::format("Unknown key {}", kv.obj) }; } /* TODO: GFDRBuffer gx2rData; */ } return true; } ================================================ FILE: tools/latte-assembler/src/gfd_vsh_comment_parser.cpp ================================================ #include "gfd_comment_parser.h" #include <fmt/core.h> #include <libgpu/latte/latte_constants.h> static void parseRegisterValue(latte::SQ_PGM_RESOURCES_VS ®, const std::string &member, const std::string &value) { if (member == "NUM_GPRS") { reg = reg .NUM_GPRS(parseValueNumber(value)); } else if (member == "STACK_SIZE") { reg = reg .STACK_SIZE(parseValueNumber(value)); } else if (member == "DX10_CLAMP") { reg = reg .DX10_CLAMP(parseValueBool(value)); } else if (member == "PRIME_CACHE_PGM_EN") { reg = reg .PRIME_CACHE_PGM_EN(parseValueBool(value)); } else if (member == "PRIME_CACHE_ON_DRAW") { reg = reg .PRIME_CACHE_ON_DRAW(parseValueBool(value)); } else if (member == "FETCH_CACHE_LINES") { reg = reg .FETCH_CACHE_LINES(parseValueNumber(value)); } else if (member == "UNCACHED_FIRST_INST") { reg = reg .UNCACHED_FIRST_INST(parseValueBool(value)); } else if (member == "PRIME_CACHE_ENABLE") { reg = reg .PRIME_CACHE_ENABLE(parseValueBool(value)); } else if (member == "PRIME_CACHE_ON_CONST") { reg = reg .PRIME_CACHE_ON_CONST(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SQ_PGM_RESOURCES_VS does not have member {}", member) }; } } static void parseRegisterValue(latte::VGT_PRIMITIVEID_EN ®, const std::string &member, const std::string &value) { if (member == "PRIMITIVEID_EN") { reg = reg .PRIMITIVEID_EN(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("VGT_PRIMITIVEID_EN does not have member {}", member) }; } } static void parseRegisterValue(latte::SPI_VS_OUT_CONFIG ®, const std::string &member, const std::string &value) { if (member == "VS_PER_COMPONENT") { reg = reg .VS_PER_COMPONENT(parseValueBool(value)); } else if (member == "VS_EXPORT_COUNT") { reg = reg .VS_EXPORT_COUNT(parseValueNumber(value)); } else if (member == "VS_EXPORTS_FOG") { reg = reg .VS_EXPORTS_FOG(parseValueBool(value)); } else if (member == "VS_OUT_FOG_VEC_ADDR") { reg = reg .VS_OUT_FOG_VEC_ADDR(parseValueNumber(value)); } else { throw gfd_header_parse_exception { fmt::format("SPI_VS_OUT_CONFIG does not have member {}", member) }; } } static void parseRegisterValue(std::array<latte::SPI_VS_OUT_ID_N, 10> &spi_vs_out_id, uint32_t index, const std::string &member, const std::string &value) { if (index >= spi_vs_out_id.size()) { throw gfd_header_parse_exception { fmt::format("SQ_VTX_SEMANTIC[{}] invalid index, max: {}", index, spi_vs_out_id.size()) }; } if (member == "SEMANTIC_0") { spi_vs_out_id[index] = spi_vs_out_id[index] .SEMANTIC_0(static_cast<uint8_t>(parseValueNumber(value))); } else if (member == "SEMANTIC_1") { spi_vs_out_id[index] = spi_vs_out_id[index] .SEMANTIC_1(static_cast<uint8_t>(parseValueNumber(value))); } else if (member == "SEMANTIC_2") { spi_vs_out_id[index] = spi_vs_out_id[index] .SEMANTIC_2(static_cast<uint8_t>(parseValueNumber(value))); } else if (member == "SEMANTIC_3") { spi_vs_out_id[index] = spi_vs_out_id[index] .SEMANTIC_3(static_cast<uint8_t>(parseValueNumber(value))); } else { throw gfd_header_parse_exception { fmt::format("SPI_VS_OUT_ID[{}] does not have member {}", index, member) }; } } static void parseRegisterValue(latte::PA_CL_VS_OUT_CNTL ®, const std::string &member, const std::string &value) { if (member == "CLIP_DIST_ENA_0") { reg = reg .CLIP_DIST_ENA_0(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_1") { reg = reg .CLIP_DIST_ENA_1(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_2") { reg = reg .CLIP_DIST_ENA_2(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_3") { reg = reg .CLIP_DIST_ENA_3(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_4") { reg = reg .CLIP_DIST_ENA_4(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_5") { reg = reg .CLIP_DIST_ENA_5(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_6") { reg = reg .CLIP_DIST_ENA_6(parseValueBool(value)); } else if (member == "CLIP_DIST_ENA_7") { reg = reg .CLIP_DIST_ENA_7(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_0") { reg = reg .CULL_DIST_ENA_0(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_1") { reg = reg .CULL_DIST_ENA_1(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_2") { reg = reg .CULL_DIST_ENA_2(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_3") { reg = reg .CULL_DIST_ENA_3(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_4") { reg = reg .CULL_DIST_ENA_4(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_5") { reg = reg .CULL_DIST_ENA_5(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_6") { reg = reg .CULL_DIST_ENA_6(parseValueBool(value)); } else if (member == "CULL_DIST_ENA_7") { reg = reg .CULL_DIST_ENA_7(parseValueBool(value)); } else if (member == "USE_VTX_POINT_SIZE") { reg = reg .USE_VTX_POINT_SIZE(parseValueBool(value)); } else if (member == "USE_VTX_EDGE_FLAG") { reg = reg .USE_VTX_EDGE_FLAG(parseValueBool(value)); } else if (member == "USE_VTX_RENDER_TARGET_INDX") { reg = reg .USE_VTX_RENDER_TARGET_INDX(parseValueBool(value)); } else if (member == "USE_VTX_VIEWPORT_INDX") { reg = reg .USE_VTX_VIEWPORT_INDX(parseValueBool(value)); } else if (member == "USE_VTX_KILL_FLAG") { reg = reg .USE_VTX_KILL_FLAG(parseValueBool(value)); } else if (member == "VS_OUT_MISC_VEC_ENA") { reg = reg .VS_OUT_MISC_VEC_ENA(parseValueBool(value)); } else if (member == "VS_OUT_CCDIST0_VEC_ENA") { reg = reg .VS_OUT_CCDIST0_VEC_ENA(parseValueBool(value)); } else if (member == "VS_OUT_CCDIST1_VEC_ENA") { reg = reg .VS_OUT_CCDIST1_VEC_ENA(parseValueBool(value)); } else if (member == "VS_OUT_MISC_SIDE_BUS_ENA") { reg = reg .VS_OUT_MISC_SIDE_BUS_ENA(parseValueBool(value)); } else if (member == "USE_VTX_GS_CUT_FLAG") { reg = reg .USE_VTX_GS_CUT_FLAG(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("SPI_VS_OUT_CONFIG does not have member {}", member) }; } } static void parseRegisterValue(std::array<latte::SQ_VTX_SEMANTIC_N, 32> &sq_vtx_semantic, uint32_t index, const std::string &member, const std::string &value) { if (index >= sq_vtx_semantic.size()) { throw gfd_header_parse_exception { fmt::format("SQ_VTX_SEMANTIC[{}] invalid index, max: {}", index, sq_vtx_semantic.size()) }; } if (member == "SEMANTIC_ID") { sq_vtx_semantic[index] = sq_vtx_semantic[index] .SEMANTIC_ID(parseValueNumber(value)); } else { throw gfd_header_parse_exception { fmt::format("SQ_VTX_SEMANTIC[{}] does not have member {}", index, member) }; } } static void parseRegisterValue(latte::VGT_STRMOUT_BUFFER_EN ®, const std::string &member, const std::string &value) { if (member == "BUFFER_0_EN") { reg = reg .BUFFER_0_EN(parseValueBool(value)); } else if(member == "BUFFER_1_EN") { reg = reg .BUFFER_1_EN(parseValueBool(value)); } else if (member == "BUFFER_2_EN") { reg = reg .BUFFER_2_EN(parseValueBool(value)); } else if (member == "BUFFER_3_EN") { reg = reg .BUFFER_3_EN(parseValueBool(value)); } else { throw gfd_header_parse_exception { fmt::format("VGT_STRMOUT_BUFFER_EN does not have member {}", member) }; } } static void parseRegisterValue(latte::VGT_VERTEX_REUSE_BLOCK_CNTL ®, const std::string &member, const std::string &value) { if (member == "VTX_REUSE_DEPTH") { reg = reg .VTX_REUSE_DEPTH(parseValueNumber(value)); } else { throw gfd_header_parse_exception { fmt::format("VGT_VERTEX_REUSE_BLOCK_CNTL does not have member {}", member) }; } } static void parseRegisterValue(latte::VGT_HOS_REUSE_DEPTH ®, const std::string &member, const std::string &value) { if (member == "REUSE_DEPTH") { reg = reg .REUSE_DEPTH(parseValueNumber(value)); } else { throw gfd_header_parse_exception { fmt::format("VGT_HOS_REUSE_DEPTH does not have member {}", member) }; } } static void parseAttribVars(std::vector<gfd::GFDAttribVar> &attribVars, uint32_t index, const std::string &member, const std::string &value) { if (index >= latte::MaxAttribBuffers) { throw gfd_header_parse_exception { fmt::format("ATTRIB_VARS[{}] invalid index, max: {}", index, latte::MaxAttribBuffers) }; } if (index >= attribVars.size()) { attribVars.resize(index + 1); attribVars[index].type = cafe::gx2::GX2ShaderVarType::Float4; attribVars[index].count = 0; attribVars[index].location = index; } if (member == "NAME") { attribVars[index].name = value; } else if (member == "TYPE") { attribVars[index].type = parseShaderVarType(value); } else if (member == "COUNT") { attribVars[index].count = parseValueNumber(value); } else if (member == "LOCATION") { attribVars[index].location = parseValueNumber(value); } else { throw gfd_header_parse_exception { fmt::format("ATTRIB_VARS[{}] does not have member {}", index, member) }; } } bool parseShaderComments(gfd::GFDVertexShader &shader, std::vector<std::string> &comments) { for (auto &comment : comments) { CommentKeyValue kv; if (!parseComment(comment, kv)) { continue; } std::transform(kv.obj.begin(), kv.obj.end(), kv.obj.begin(), ::toupper); std::transform(kv.member.begin(), kv.member.end(), kv.member.begin(), ::toupper); if (kv.obj == "SQ_PGM_RESOURCES_VS") { ensureObject(kv); parseRegisterValue(shader.regs.sq_pgm_resources_vs, kv.member, kv.value); } else if (kv.obj == "VGT_PRIMITIVEID_EN") { ensureObject(kv); parseRegisterValue(shader.regs.vgt_primitiveid_en, kv.member, kv.value); } else if (kv.obj == "SPI_VS_OUT_CONFIG") { ensureObject(kv); parseRegisterValue(shader.regs.spi_vs_out_config, kv.member, kv.value); } else if (kv.obj == "NUM_SPI_VS_OUT_ID") { ensureValue(kv); shader.regs.num_spi_vs_out_id = parseValueNumber(kv.value); } else if (kv.obj == "SPI_VS_OUT_ID") { ensureArrayOfObjects(kv); parseRegisterValue(shader.regs.spi_vs_out_id, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "PA_CL_VS_OUT_CNTL") { ensureObject(kv); parseRegisterValue(shader.regs.pa_cl_vs_out_cntl, kv.member, kv.value); } else if (kv.obj == "SQ_VTX_SEMANTIC_CLEAR") { ensureValue(kv); shader.regs.sq_vtx_semantic_clear = shader.regs.sq_vtx_semantic_clear .CLEAR(parseValueNumber(kv.value)); } else if (kv.obj == "NUM_SQ_VTX_SEMANTIC") { ensureValue(kv); shader.regs.num_sq_vtx_semantic = parseValueNumber(kv.value); } else if (kv.obj == "SQ_VTX_SEMANTIC") { ensureArrayOfObjects(kv); parseRegisterValue(shader.regs.sq_vtx_semantic, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "VGT_STRMOUT_BUFFER_EN") { ensureObject(kv); parseRegisterValue(shader.regs.vgt_strmout_buffer_en, kv.member, kv.value); } else if (kv.obj == "VGT_VERTEX_REUSE_BLOCK_CNTL") { ensureObject(kv); parseRegisterValue(shader.regs.vgt_vertex_reuse_block_cntl, kv.member, kv.value); } else if (kv.obj == "VGT_HOS_REUSE_DEPTH") { ensureObject(kv); parseRegisterValue(shader.regs.vgt_hos_reuse_depth, kv.member, kv.value); } else if (kv.obj == "ATTRIB_VARS") { ensureArrayOfObjects(kv); parseAttribVars(shader.attribVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "UNIFORM_BLOCKS") { ensureArrayOfObjects(kv); parseUniformBlocks(shader.uniformBlocks, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "UNIFORM_VARS") { ensureArrayOfObjects(kv); parseUniformVars(shader.uniformVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "INITIAL_VALUES") { ensureArrayOfObjects(kv); parseInitialValues(shader.initialValues, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "LOOP_VARS") { ensureArrayOfObjects(kv); parseLoopVars(shader.loopVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "SAMPLER_VARS") { ensureArrayOfObjects(kv); parseSamplerVars(shader.samplerVars, std::stoul(kv.index), kv.member, kv.value); } else if (kv.obj == "MODE") { ensureValue(kv); shader.mode = parseShaderMode(kv.value); } else if (kv.obj == "RING_ITEM_SIZE") { ensureValue(kv); shader.ringItemSize = parseValueNumber(kv.value); } else if (kv.obj == "HAS_STREAM_OUT") { ensureValue(kv); shader.hasStreamOut = parseValueBool(kv.value); } else if (kv.obj == "STREAM_OUT_STRIDE") { ensureArrayOfValues(kv); auto index = std::stoul(kv.index); if (index >= shader.streamOutStride.size()) { throw gfd_header_parse_exception { fmt::format("STREAM_OUT_STRIDE[{}] invalid index, max: {}", index, shader.streamOutStride.size()) }; } shader.streamOutStride[index] = parseValueNumber(kv.value); } else { throw gfd_header_parse_exception { fmt::format("Unknown key {}", kv.obj) }; } /* TODO: GFDRBuffer gx2rData; */ } return true; } ================================================ FILE: tools/latte-assembler/src/glsl_compiler.cpp ================================================ #include "shader_assembler.h" #include <common/align.h> #include <common/platform.h> #include <common/platform_winapi_string.h> #include <fmt/format.h> #include <fstream> #include <glslang/Include/Types.h> #include <glslang/Public/ShaderLang.h> #include <glslang/MachineIndependent/localintermediate.h> #include <iterator> #include <iostream> #include <memory> #include <string> #include <vector> #ifdef PLATFORM_WINDOWS #include <Windows.h> #endif static bool readFile(const std::string &path, std::string &buff) { std::ifstream ifs { path, std::ios::in | std::ios::binary }; if (ifs.fail()) { return false; } buff.resize(static_cast<unsigned int>(ifs.seekg(0, std::ios::end).tellg())); if (!buff.empty()) { ifs.seekg(0, std::ios::beg).read(&buff[0], static_cast<std::streamsize>(buff.size())); } return true; } namespace glslang { const TBuiltInResource DefaultTBuiltInResource = { /* .MaxLights = */ 32, /* .MaxClipPlanes = */ 6, /* .MaxTextureUnits = */ 32, /* .MaxTextureCoords = */ 32, /* .MaxVertexAttribs = */ 64, /* .MaxVertexUniformComponents = */ 4096, /* .MaxVaryingFloats = */ 64, /* .MaxVertexTextureImageUnits = */ 32, /* .MaxCombinedTextureImageUnits = */ 80, /* .MaxTextureImageUnits = */ 32, /* .MaxFragmentUniformComponents = */ 4096, /* .MaxDrawBuffers = */ 32, /* .MaxVertexUniformVectors = */ 128, /* .MaxVaryingVectors = */ 8, /* .MaxFragmentUniformVectors = */ 16, /* .MaxVertexOutputVectors = */ 16, /* .MaxFragmentInputVectors = */ 15, /* .MinProgramTexelOffset = */ -8, /* .MaxProgramTexelOffset = */ 7, /* .MaxClipDistances = */ 8, /* .MaxComputeWorkGroupCountX = */ 65535, /* .MaxComputeWorkGroupCountY = */ 65535, /* .MaxComputeWorkGroupCountZ = */ 65535, /* .MaxComputeWorkGroupSizeX = */ 1024, /* .MaxComputeWorkGroupSizeY = */ 1024, /* .MaxComputeWorkGroupSizeZ = */ 64, /* .MaxComputeUniformComponents = */ 1024, /* .MaxComputeTextureImageUnits = */ 16, /* .MaxComputeImageUniforms = */ 8, /* .MaxComputeAtomicCounters = */ 8, /* .MaxComputeAtomicCounterBuffers = */ 1, /* .MaxVaryingComponents = */ 60, /* .MaxVertexOutputComponents = */ 64, /* .MaxGeometryInputComponents = */ 64, /* .MaxGeometryOutputComponents = */ 128, /* .MaxFragmentInputComponents = */ 128, /* .MaxImageUnits = */ 8, /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8, /* .MaxCombinedShaderOutputResources = */ 8, /* .MaxImageSamples = */ 0, /* .MaxVertexImageUniforms = */ 0, /* .MaxTessControlImageUniforms = */ 0, /* .MaxTessEvaluationImageUniforms = */ 0, /* .MaxGeometryImageUniforms = */ 0, /* .MaxFragmentImageUniforms = */ 8, /* .MaxCombinedImageUniforms = */ 8, /* .MaxGeometryTextureImageUnits = */ 16, /* .MaxGeometryOutputVertices = */ 256, /* .MaxGeometryTotalOutputComponents = */ 1024, /* .MaxGeometryUniformComponents = */ 1024, /* .MaxGeometryVaryingComponents = */ 64, /* .MaxTessControlInputComponents = */ 128, /* .MaxTessControlOutputComponents = */ 128, /* .MaxTessControlTextureImageUnits = */ 16, /* .MaxTessControlUniformComponents = */ 1024, /* .MaxTessControlTotalOutputComponents = */ 4096, /* .MaxTessEvaluationInputComponents = */ 128, /* .MaxTessEvaluationOutputComponents = */ 128, /* .MaxTessEvaluationTextureImageUnits = */ 16, /* .MaxTessEvaluationUniformComponents = */ 1024, /* .MaxTessPatchComponents = */ 120, /* .MaxPatchVertices = */ 32, /* .MaxTessGenLevel = */ 64, /* .MaxViewports = */ 16, /* .MaxVertexAtomicCounters = */ 0, /* .MaxTessControlAtomicCounters = */ 0, /* .MaxTessEvaluationAtomicCounters = */ 0, /* .MaxGeometryAtomicCounters = */ 0, /* .MaxFragmentAtomicCounters = */ 8, /* .MaxCombinedAtomicCounters = */ 8, /* .MaxAtomicCounterBindings = */ 1, /* .MaxVertexAtomicCounterBuffers = */ 0, /* .MaxTessControlAtomicCounterBuffers = */ 0, /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, /* .MaxGeometryAtomicCounterBuffers = */ 0, /* .MaxFragmentAtomicCounterBuffers = */ 1, /* .MaxCombinedAtomicCounterBuffers = */ 1, /* .MaxAtomicCounterBufferSize = */ 16384, /* .MaxTransformFeedbackBuffers = */ 4, /* .MaxTransformFeedbackInterleavedComponents = */ 64, /* .MaxCullDistances = */ 8, /* .MaxCombinedClipAndCullDistances = */ 8, /* .MaxSamples = */ 4, /* .maxMeshOutputVerticesNV = */ 256, /* .maxMeshOutputPrimitivesNV = */ 512, /* .maxMeshWorkGroupSizeX_NV = */ 32, /* .maxMeshWorkGroupSizeY_NV = */ 1, /* .maxMeshWorkGroupSizeZ_NV = */ 1, /* .maxTaskWorkGroupSizeX_NV = */ 32, /* .maxTaskWorkGroupSizeY_NV = */ 1, /* .maxTaskWorkGroupSizeZ_NV = */ 1, /* .maxMeshViewCountNV = */ 4, /* .maxDualSourceDrawBuffersEXT */ 1, /* .limits = */ { /* .nonInductiveForLoops = */ true, /* .whileLoops = */ true, /* .doWhileLoops = */ true, /* .generalUniformIndexing = */ true, /* .generalAttributeMatrixVectorIndexing = */ true, /* .generalVaryingIndexing = */ true, /* .generalSamplerIndexing = */ true, /* .generalVariableIndexing = */ true, /* .generalConstantMatrixVectorIndexing = */ true, } }; } // namespace glslang static int getTypeBytes(const glslang::TType *type) { auto size = 0; switch (type->getBasicType()) { case glslang::EbtInt8: case glslang::EbtUint8: size = 1; break; case glslang::EbtInt16: case glslang::EbtUint16: case glslang::EbtFloat16: size = 2; break; case glslang::EbtInt: case glslang::EbtUint: case glslang::EbtFloat: size = 4; break; case glslang::EbtInt64: case glslang::EbtUint64: case glslang::EbtDouble: size = 8; break; default: return 0; } if (type->isMatrix()) { size *= type->getMatrixCols(); size *= type->getMatrixRows(); } if (type->isArray()) { size *= type->getCumulativeArraySize(); } if (type->isVector()) { size *= type->getVectorSize(); } return size; } static std::string getTypeString(const glslang::TType *type) { if (type->isVector()) { auto result = std::string { }; switch (type->getBasicType()) { case glslang::EbtFloat: break; case glslang::EbtBool: result = "b"; break; case glslang::EbtInt: result = "i"; break; case glslang::EbtUint: result = "u"; break; case glslang::EbtDouble: result = "d"; break; default: return {}; // Invalid } result += "vec"; result += std::to_string(type->getVectorSize()); return result; } if (type->isMatrix()) { auto result = std::string { }; if (type->getBasicType() == glslang::EbtDouble) { result += "d"; } else if (type->getBasicType() != glslang::EbtFloat) { return {}; } auto cols = type->getMatrixCols(); auto rows = type->getMatrixRows(); if (cols < 2 || cols > 4 || rows < 2 || rows > 4) { return {}; } result += "mat"; result += std::to_string(cols); if (rows != cols) { result += "x"; result += std::to_string(rows); } return result; } switch (type->getBasicType()) { case glslang::EbtVoid: return "void"; case glslang::EbtBool: return "bool"; case glslang::EbtInt: return "int"; case glslang::EbtUint: return "uint"; case glslang::EbtFloat: return "float"; case glslang::EbtDouble: return "double"; default: return {}; // Invalid } } static std::unique_ptr<glslang::TShader> parseShader(EShLanguage stage, std::string path) { auto source = std::string { }; if (!readFile(path, source)) { std::cout << "Could not read " << path << std::endl; return nullptr; } /*std::string header = "#version 410\n#extension GL_ARB_separate_shader_objects : enable\n"; source.insert(source.begin(), std::begin(header), std::end(header)); source.push_back('\0');*/ const char *texts[1] = { source.c_str() }; const char *paths[1] = { path.c_str() }; auto shader = std::make_unique<glslang::TShader>(stage); shader->setStringsWithLengthsAndNames(texts, nullptr, paths, 1); shader->setUniformLocationBase(0); shader->setEntryPoint("main"); auto resources = glslang::DefaultTBuiltInResource; auto messages = EShMessages::EShMsgDefault; if (!shader->parse(&resources, 120, false, messages)) { std::cout << "glslang failed to parse shader" << std::endl; std::cout << shader->getInfoLog() << std::endl; std::cout << shader->getInfoDebugLog() << std::endl; return nullptr; } shader->getIntermediate()->addRequestedExtension("GL_ARB_separate_shader_objects"); return shader; } static std::string parseGlslFileToHeader(glslang::TShader &shader) { auto messages = EShMessages::EShMsgDefault; auto program = glslang::TProgram { }; program.addShader(&shader); if (!program.link(messages)) { std::cout << "glslang failed to link shader for shader" << std::endl; std::cout << program.getInfoLog() << std::endl; std::cout << program.getInfoDebugLog() << std::endl; return {}; } if (!program.buildReflection(EShReflectionDefault | EShReflectionAllBlockVariables | EShReflectionIntermediateIO)) { std::cout << "glslang failed to build reflection for shader" << std::endl; std::cout << program.getInfoLog() << std::endl; std::cout << program.getInfoDebugLog() << std::endl; return {}; } /* auto mapper = new glslang::TGlslIoMapper { }; auto resolver = new glslang::TDefaultGlslIoResolver { *program.getIntermediate(EShLanguage::EShLangVertex) }; if (!program.mapIO(resolver, mapper)) { std::cout << "glslang failed to map io" << std::endl; std::cout << vertexShader->getInfoLog() << std::endl; std::cout << vertexShader->getInfoDebugLog() << std::endl; return -1; } */ // AMD ShaderAnalyzer puts the in / out / uniforms in alphabetical order std::vector<glslang::TObjectReflection> inputs; std::vector<glslang::TObjectReflection> outputs; std::vector<glslang::TObjectReflection> uniformVars; std::vector<glslang::TObjectReflection> uniformBlocks; for (auto i = 0; i < program.getNumPipeInputs(); ++i) { const auto &input = program.getPipeInput(i); inputs.insert(std::upper_bound(inputs.begin(), inputs.end(), input, [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; }), input); } for (auto i = 0; i < program.getNumPipeOutputs(); ++i) { const auto &output = program.getPipeOutput(i); outputs.insert(std::upper_bound(outputs.begin(), outputs.end(), output, [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; }), output); } for (auto i = 0; i < program.getNumUniformVariables(); ++i) { const auto &uniform = program.getUniform(i); uniformVars.insert(std::upper_bound(uniformVars.begin(), uniformVars.end(), uniform, [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; }), uniform); } for (auto i = 0; i < program.getNumUniformBlocks(); ++i) { const auto &uniform = program.getUniformBlock(i); uniformBlocks.insert(std::upper_bound(uniformBlocks.begin(), uniformBlocks.end(), uniform, [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; }), uniform); } // Generate assembly annotation auto outVsh = fmt::memory_buffer { }; auto outPsh = fmt::memory_buffer{ }; fmt::format_to(std::back_inserter(outVsh), "\n"); fmt::format_to(std::back_inserter(outPsh), "\n"); // Process inputs for fragment shader auto pixelInputCount = 0; for (auto i = 0u; i < inputs.size(); ++i) { if (inputs[i].stages & EShLanguageMask::EShLangFragmentMask) { ++pixelInputCount; } } if (pixelInputCount) { fmt::format_to(std::back_inserter(outPsh), "; $NUM_SPI_PS_INPUT_CNTL = {}\n", pixelInputCount); for (auto i = 0; i < pixelInputCount; ++i) { fmt::format_to(std::back_inserter(outPsh), "; $SPI_PS_INPUT_CNTL[{}].SEMANTIC = {}\n", i, i); } } // Process inputs for vertex shader for (auto i = 0u; i < inputs.size(); ++i) { const auto &input = inputs[i]; auto type = input.getType(); auto typeName = getTypeString(type); auto typeBytes = getTypeBytes(type); if (typeName.empty() || typeBytes == 0) { std::cout << "Invalid type for input " << input.name << std::endl; return {}; } if (input.stages & EShLanguageMask::EShLangVertexMask) { fmt::format_to(std::back_inserter(outVsh), "; $ATTRIB_VARS[{}].name = \"{}\"\n", i, input.name); fmt::format_to(std::back_inserter(outVsh), "; $ATTRIB_VARS[{}].type = \"{}\"\n", i, typeName); fmt::format_to(std::back_inserter(outVsh), "; $ATTRIB_VARS[{}].location = {}\n", i, i); if (type->isArray()) { fmt::format_to(std::back_inserter(outVsh), "; $ATTRIB_VARS[{}].count = {}\n", i, type->getCumulativeArraySize()); } fmt::format_to(std::back_inserter(outVsh), "\n"); } } // Process uniform vars for both shaders auto uniformVarsOffset = 0; for (auto i = 0u; i < uniformVars.size(); ++i) { const auto &uniform = uniformVars[i]; auto type = uniform.getType(); auto typeName = getTypeString(type); auto typeBytes = getTypeBytes(type); if (typeName.empty() || typeBytes == 0) { std::cout << "Invalid type for uniform " << uniform.name << std::endl; return {}; } auto out = fmt::memory_buffer { }; fmt::format_to(std::back_inserter(out), "; $UNIFORM_VARS[{}].name = \"{}\"\n", i, uniform.name); fmt::format_to(std::back_inserter(out), "; $UNIFORM_VARS[{}].type = \"{}\"\n", i, typeName); fmt::format_to(std::back_inserter(out), "; $UNIFORM_VARS[{}].offset = {}\n", i, uniformVarsOffset / 4); uniformVarsOffset += align_up(typeBytes, 4); if (type->isArray()) { fmt::format_to(std::back_inserter(out), "; $UNIFORM_VARS[{}].count = {}\n", i, type->getCumulativeArraySize()); } fmt::format_to(std::back_inserter(out), "\n"); if (uniform.stages & EShLanguageMask::EShLangVertexMask) { outVsh.append(out.begin(), out.end()); } if (uniform.stages & EShLanguageMask::EShLangFragmentMask) { outPsh.append(out.begin(), out.end()); } } if (!uniformBlocks.empty()) { std::cout << "Unimplemented uniform blocks" << std::endl; return {}; } // Process output for vertex shader auto vertexOutputCount = 0; for (auto i = 0u; i < outputs.size(); ++i) { const auto &output = outputs[i]; if (output.stages & EShLanguageMask::EShLangVertexMask) { auto qualifier = output.getType()->getQualifier(); if (qualifier.storage == glslang::EvqVaryingOut) { ++vertexOutputCount; } else if (qualifier.builtIn == glslang::EbvPointSize) { fmt::format_to(std::back_inserter(outVsh), "; $PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE = true\n"); } } } if (vertexOutputCount) { fmt::format_to(std::back_inserter(outVsh), "; $NUM_SPI_VS_OUT_ID = {}\n", (vertexOutputCount + 3) / 4); for (auto i = 0; i < vertexOutputCount; ++i) { fmt::format_to(std::back_inserter(outVsh), "; $SPI_VS_OUT_ID[{}].SEMANTIC_{} = {}\n", i / 4, i % 4, i); } } outVsh.push_back('\0'); outPsh.push_back('\0'); if (shader.getStage() == EShLanguage::EShLangVertex) { return std::string { outVsh.data() }; } else { return std::string { outPsh.data() }; } } static std::string runAmdShaderAnalyzer(std::string shaderAnalyzerPath, std::string shaderPath, ShaderType shaderType) { std::string args; args += "\"" + shaderAnalyzerPath + "\""; args += " \"" + shaderPath + "\""; args += " -ASIC RV730"; if (shaderType == ShaderType::VertexShader) { args += " -P glsl_vs"; } else if (shaderType == ShaderType::PixelShader) { args += " -P glsl_fs"; } args += " -I tmp.txt"; #ifdef PLATFORM_WINDOWS SECURITY_ATTRIBUTES security_attributes; security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = nullptr; STARTUPINFOA si; PROCESS_INFORMATION pi; DWORD exitCode; ZeroMemory(&si, sizeof(STARTUPINFOA)); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); si.cb = sizeof(si); if (!CreateProcessA(nullptr, args.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi)) { std::cout << "Failed to create AMD ShaderAnalyzer process" << std::endl; return {}; } CloseHandle(pi.hThread); WaitForSingleObject(pi.hProcess, INFINITE); if (!GetExitCodeProcess(pi.hProcess, &exitCode)) { exitCode = static_cast<DWORD>(-1); } if (exitCode != 0) { std::cout << "AMD ShaderAnalyzer returned " << exitCode << std::endl; return {}; } #else std::cout << "runAmdShaderAnalyzer unimplemented on this platform" << std::endl; std::cout << "Consider using wine ? ShaderAnalyzer is Windows only anyway :D" << std::endl; return {}; #endif std::string assembly; if (!readFile("tmp.txt", assembly)) { std::cout << "Could not read AMD ShaderAnalyzer output" << std::endl; return {}; } if (std::strncmp(assembly.c_str(), "; -------- Disassembly --------------------", strlen("; -------- Disassembly --------------------"))) { std::cout << "Unexpected AMD ShaderAnalyzer output" << std::endl; return {}; } return assembly; } std::string compileShader(std::string shaderAnalyzerPath, std::string shaderPath, ShaderType shaderType) { EShLanguage language; if (shaderType == ShaderType::VertexShader) { language = EShLangVertex; } else if (shaderType == ShaderType::PixelShader) { language = EShLangFragment; } else { std::cout << "Invalid shader type" << std::endl; return {}; } auto shader = parseShader(language, shaderPath); if (!shader) { std::cout << "Failed to parse shader " << shaderPath << std::endl; return {}; } auto shaderHeader = parseGlslFileToHeader(*shader); if (shaderHeader.empty()) { std::cout << "Failed to generate shader header for " << shaderPath << std::endl; return {}; } auto shaderCode = runAmdShaderAnalyzer(shaderAnalyzerPath, shaderPath, shaderType); if (shaderCode.empty()) { std::cout << "Failed to generate shader code for " << shaderPath << std::endl; return {}; } return shaderHeader + shaderCode; } ================================================ FILE: tools/latte-assembler/src/glsl_compiler.h ================================================ #pragma once #include "shader.h" #include <string> std::string compileShader(std::string shaderAnalyzerPath, std::string shaderPath, ShaderType shaderType); ================================================ FILE: tools/latte-assembler/src/main.cpp ================================================ #include "shader_assembler.h" #include "gfd_comment_parser.h" #include "glsl_compiler.h" #include <excmd.h> #include <fmt/core.h> #include <fstream> #include <glslang/Public/ShaderLang.h> #include <memory> #include <optional> #include <string> #include <spdlog/spdlog.h> static bool readFile(const std::string &path, std::string &buff) { std::ifstream ifs { path, std::ios::in | std::ios::binary }; if (ifs.fail()) { return false; } buff.resize(static_cast<unsigned int>(ifs.seekg(0, std::ios::end).tellg())); if (!buff.empty()) { ifs.seekg(0, std::ios::beg).read(&buff[0], static_cast<std::streamsize>(buff.size())); } return true; } static bool assembleFile(Shader &shader, const std::string &path) { std::string src; if (!readFile(path, src)) { return false; } return assembleShaderCode(shader, src); } int main(int argc, char **argv) { excmd::parser parser; excmd::option_state options; // Setup command line options parser.global_options() .add_option("h,help", excmd::description { "Show the help." }) .add_option("vsh", excmd::description { "Vertex shader input." }, excmd::value<std::string> {}) .add_option("psh", excmd::description { "Pixel shader input." }, excmd::value<std::string> {}) .add_option("align", excmd::description { "Align data in gsh file." }) .add_option("amd-shader-analyzer", excmd::description{ "Path to AMD Shader Analyzer exe." }, excmd::value<std::string> {}); parser.add_command("help") .add_argument("command", excmd::value<std::string> { }); parser.add_command("assemble") .add_argument("gsh", excmd::value<std::string> { }); parser.add_command("compile") .add_argument("gsh", excmd::value<std::string> { }); // Parse command line try { options = parser.parse(argc, argv); } catch (excmd::exception ex) { std::cout << "Error parsing command line: " << ex.what() << std::endl; return -1; } // Print help if (argc == 1 || options.has("help")) { if (options.has("command")) { std::cout << parser.format_help("gfdtool", options.get<std::string>("command")) << std::endl; } else { std::cout << parser.format_help("gfdtool") << std::endl; } return 0; } try { if (options.has("compile")) { if (!options.has("amd-shader-analyzer")) { std::cout << "Compile requires amd-shader-analyzer" << std::endl; return -1; } if (!options.has("vsh") || !options.has("psh")) { std::cout << "Compile requires vsh and psh" << std::endl; return -1; } auto vertexShaderPath = options.get<std::string>("vsh"); auto pixelShaderPath = options.get<std::string>("psh"); auto shaderAnalyzerPath = options.get<std::string>("amd-shader-analyzer"); auto outGshPath = options.get<std::string>("gsh"); glslang::InitializeProcess(); auto vertexShaderAssembly = compileShader(shaderAnalyzerPath, vertexShaderPath, ShaderType::VertexShader); if (vertexShaderAssembly.empty()) { std::cout << "Failed to compile vertex shader " << vertexShaderPath << std::endl; return -1; } auto pixelShaderAssembly = compileShader(shaderAnalyzerPath, pixelShaderPath, ShaderType::PixelShader); if (pixelShaderAssembly.empty()) { std::cout << "Failed to compile pixel shader " << pixelShaderPath << std::endl; return -1; } auto vertexShader = Shader { }; vertexShader.path = vertexShaderPath; vertexShader.type = ShaderType::VertexShader; if (!assembleShaderCode(vertexShader, vertexShaderAssembly)) { std::cout << "Failed to assemble vertex shader" << std::endl; return -1; } auto pixelShader = Shader { }; pixelShader.path = pixelShaderPath; pixelShader.type = ShaderType::PixelShader; if (!assembleShaderCode(pixelShader, pixelShaderAssembly)) { std::cout << "Failed to assemble pixel shader" << std::endl; return -1; } auto gfd = gfd::GFDFile { }; if (!gfdAddVertexShader(gfd, vertexShader)) { std::cout << "Failed to add vertex shader to gfd" << std::endl; return -1; } if (!gfdAddPixelShader(gfd, pixelShader)) { std::cout << "Failed to add pixel shader to gfd" << std::endl; return -1; } if (!gfd::writeFile(gfd, outGshPath, options.has("align"))) { std::cout << "Failed to add write gfd" << std::endl; return -1; } glslang::FinalizeProcess(); } else if (options.has("assemble")) { auto dst = options.get<std::string>("gsh"); gfd::GFDFile gfd; if (options.has("vsh")) { auto src = options.get<std::string>("vsh"); Shader shader; shader.path = src; shader.type = ShaderType::VertexShader; if (!assembleFile(shader, src)) { return -1; } if (!gfdAddVertexShader(gfd, shader)) { return -1; } } if (options.has("psh")) { auto src = options.get<std::string>("psh"); Shader shader; shader.path = src; shader.type = ShaderType::PixelShader; if (!assembleFile(shader, src)) { return -1; } if (!gfdAddPixelShader(gfd, shader)) { return -1; } } if (!gfd::writeFile(gfd, dst, options.has("align"))) { return -1; } } else { return -1; } } catch (parse_exception e) { std::cout << "Parse exception: " << e.what() << std::endl; return -1; } catch (gfd_header_parse_exception e) { std::cout << "GFD header parse exception: " << e.what() << std::endl; return -1; } return 0; } ================================================ FILE: tools/latte-assembler/src/shader.h ================================================ #pragma once #include <array> #include <cstdint> #include <libgpu/latte/latte_instructions.h> #include <peglib.h> #include <vector> #include <string> enum class ShaderType { Invalid, PixelShader, VertexShader, }; struct LiteralValue { enum Flags { ReadHex = 1 << 0, ReadFloat = 1 << 1, }; unsigned flags; uint32_t hexValue = 0; float floatValue = 0.0f; }; struct AluGroup { uint32_t clausePC = 0; std::vector<latte::AluInst> insts; std::vector<LiteralValue> literals; }; struct AluClause { uint32_t cfPC = 0; std::shared_ptr<peg::Ast> addrNode; std::shared_ptr<peg::Ast> countNode; std::vector<AluGroup> groups; }; struct TexClause { uint32_t cfPC = 0; uint32_t clausePC = 0; std::shared_ptr<peg::Ast> addrNode; std::shared_ptr<peg::Ast> countNode; std::vector<latte::TextureFetchInst> insts; }; struct Shader { Shader() { gprRead.fill(false); gprWritten.fill(false); } ShaderType type = ShaderType::Invalid; std::string path; uint32_t clausePC = 0; std::vector<latte::ControlFlowInst> cfInsts; std::vector<AluClause> aluClauses; uint32_t aluClauseBaseAddress; std::vector<uint32_t> aluClauseData; std::vector<TexClause> texClauses; uint32_t texClauseBaseAddress; std::vector<uint32_t> texClauseData; std::vector<std::string> comments; bool uniformBlocksUsed = false; bool uniformRegistersUsed = false; std::array<bool, 128> gprRead; std::array<bool, 128> gprWritten; unsigned long maxGPR = 0; unsigned long maxStack = 0; unsigned long maxPixelExport = 0; unsigned long maxParamExport = 0; unsigned long maxPosExport = 0; }; ================================================ FILE: tools/latte-assembler/src/shader_assembler.h ================================================ #pragma once #include "shader.h" #include <fmt/core.h> #include <peglib.h> #include <stdexcept> #include <string> #include <string_view> #include <vector> class parse_exception : public std::runtime_error { public: parse_exception(const std::string &m) : std::runtime_error { m } { } }; class node_parse_exception : public parse_exception { public: node_parse_exception(peg::Ast &node, const std::string &m) : parse_exception { fmt::format("{}:{} {}", node.line, node.column, m) } { } }; class unhandled_node_exception : public node_parse_exception { public: unhandled_node_exception(peg::Ast &node) : node_parse_exception { node, fmt::format("Unxpected node {}", node.name) } { } }; class invalid_inst_exception : public node_parse_exception { public: invalid_inst_exception(peg::Ast &node, const std::string &instType) : node_parse_exception { node, fmt::format("Invalid {} instruction {}", instType, node.token) } { } }; class invalid_alu_op2_inst_exception : public invalid_inst_exception { public: invalid_alu_op2_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "ALU OP2" } { } }; class invalid_alu_op3_inst_exception : public invalid_inst_exception { public: invalid_alu_op3_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "ALU OP3" } { } }; class invalid_cf_inst_exception : public invalid_inst_exception { public: invalid_cf_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "CF" } { } }; class invalid_cf_alu_inst_exception : public invalid_inst_exception { public: invalid_cf_alu_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "CF ALU" } { } }; class invalid_cf_tex_inst_exception : public invalid_inst_exception { public: invalid_cf_tex_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "CF TEX" } { } }; class invalid_exp_inst_exception : public invalid_inst_exception { public: invalid_exp_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "EXP" } { } }; class invalid_tex_inst_exception : public invalid_inst_exception { public: invalid_tex_inst_exception(peg::Ast &node) : invalid_inst_exception { node, "TEX" } { } }; class invalid_inst_property_exception : public node_parse_exception { public: invalid_inst_property_exception(peg::Ast &node, const std::string &instType) : node_parse_exception { node, fmt::format("Invalid property {} for {} instruction", node.name, instType) } { } }; class invalid_alu_property_exception : public invalid_inst_property_exception { public: invalid_alu_property_exception(peg::Ast &node) : invalid_inst_property_exception { node, "ALU" } { } }; class invalid_cf_property_exception : public invalid_inst_property_exception { public: invalid_cf_property_exception(peg::Ast &node) : invalid_inst_property_exception { node, "CF" } { } }; class invalid_cf_alu_property_exception : public invalid_inst_property_exception { public: invalid_cf_alu_property_exception(peg::Ast &node) : invalid_inst_property_exception { node, "CF ALU" } { } }; class invalid_cf_tex_property_exception : public invalid_inst_property_exception { public: invalid_cf_tex_property_exception(peg::Ast &node) : invalid_inst_property_exception { node, "CF TEX" } { } }; class invalid_exp_property_exception : public invalid_inst_property_exception { public: invalid_exp_property_exception(peg::Ast &node) : invalid_inst_property_exception { node, "EXP" } { } }; class invalid_tex_property_exception : public invalid_inst_property_exception { public: invalid_tex_property_exception(peg::Ast &node) : invalid_inst_property_exception { node, "TEX" } { } }; class incorrect_cf_pc_exception : public node_parse_exception { public: incorrect_cf_pc_exception(peg::Ast &node, size_t found, size_t expected) : node_parse_exception { node, fmt::format("Incorrect CF PC {}, expected {}", found, expected) } { } }; class incorrect_clause_pc_exception : public node_parse_exception { public: incorrect_clause_pc_exception(peg::Ast &node, size_t found, size_t expected) : node_parse_exception { node, fmt::format("Incorrect clause PC {}, expected {}", found, expected) } { } }; class incorrect_clause_addr_exception : public node_parse_exception { public: incorrect_clause_addr_exception(peg::Ast &node, size_t found, size_t expected) : node_parse_exception { node, fmt::format("Incorrect clause addr {}, expected {}", found, expected) } { } }; class incorrect_clause_count_exception : public node_parse_exception { public: incorrect_clause_count_exception(peg::Ast &node, size_t found, size_t expected) : node_parse_exception { node, fmt::format("Incorrect clause count {}, expected {}", found, expected) } { } }; // assembler_parse bool assembleShaderCode(Shader &shader, std::string_view code); // assembler_cf void assembleAST(Shader &shader, std::shared_ptr<peg::Ast> ast); // assembler_alu void assembleAluClause(Shader &shader, peg::Ast &node); // assembler_exp void assembleExpInst(Shader &shader, peg::Ast &node); // assembler_tex void assembleTexClause(Shader &shader, peg::Ast &node); // assembler_latte latte::SQ_ALU_VEC_BANK_SWIZZLE parseAluBankSwizzle(peg::Ast &node); latte::SQ_INDEX_MODE parseAluDstRelIndexMode(peg::Ast &node); latte::SQ_CF_COND parseCfCond(peg::Ast &node); latte::SQ_CHAN parseChan(peg::Ast &node); size_t parseFourCompSwizzle(peg::Ast &node, latte::SQ_SEL &selX, latte::SQ_SEL &selY, latte::SQ_SEL &selZ, latte::SQ_SEL &selW); latte::SQ_ALU_OMOD parseOutputModifier(peg::Ast &node); latte::SQ_PRED_SEL parsePredSel(peg::Ast &node); latte::SQ_SEL parseSel(peg::Ast &node, unsigned index); // assembler_common float parseFloat(peg::Ast &node); uint32_t parseHexNumber(peg::Ast &node); unsigned long parseNumber(peg::Ast &node); LiteralValue parseLiteral(peg::Ast &node); void markGprRead(Shader &shader, uint32_t gpr); void markSrcRead(Shader &shader, latte::SQ_ALU_SRC src); void markGprWritten(Shader &shader, uint32_t gpr); ================================================ FILE: tools/pm4-replay/CMakeLists.txt ================================================ project(pm4-replay) include_directories(".") include_directories("../../src/libdecaf/src") include_directories("../../src/libgpu") include_directories("../../src/libgpu/src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) add_executable(pm4-replay ${SOURCE_FILES} ${HEADER_FILES}) set_target_properties(pm4-replay PROPERTIES FOLDER tools) target_include_directories(pm4-replay PRIVATE ${SDL2_INCLUDE_DIRS}) target_link_libraries(pm4-replay common libconfig libdecaf excmd ${SDL2_LIBRARIES}) if(MSVC) target_link_libraries(pm4-replay Setupapi) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(pm4-replay X11) endif() install(TARGETS pm4-replay RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") ================================================ FILE: tools/pm4-replay/clilog.h ================================================ #pragma once #include <spdlog/spdlog.h> extern std::shared_ptr<spdlog::logger> gCliLog; ================================================ FILE: tools/pm4-replay/config.h ================================================ #pragma once #include <string> namespace config { extern bool dump_drc_frames; extern bool dump_tv_frames; extern std::string dump_frames_dir; } // namespace config ================================================ FILE: tools/pm4-replay/main.cpp ================================================ #include "config.h" #include "sdl_window.h" #include <common/log.h> #include <excmd.h> #include <iostream> #include <libcpu/cpu.h> #include <libcpu/mem.h> #include <libdecaf/decaf.h> #include <libdecaf/decaf_config.h> #include <libdecaf/decaf_log.h> #include <libgpu/gpu_config.h> #include <spdlog/spdlog.h> namespace config { bool dump_drc_frames = false; bool dump_tv_frames = false; std::string dump_frames_dir = "frames"; std::string renderer = "vulkan"; } // namespace config std::shared_ptr<spdlog::logger> gCliLog; static excmd::parser getCommandLineParser() { excmd::parser parser; using excmd::description; using excmd::optional; using excmd::default_value; using excmd::allowed; using excmd::value; using excmd::make_default_value; parser.global_options() .add_option("v,version", description { "Show version." }) .add_option("h,help", description { "Show help." }); auto replayOptions = parser.add_option_group("Replay Options") .add_option("dump-drc-frames", description { "Dump rendered DRC frames to file." }) .add_option("dump-tv-frames", description { "Dump rendered TV frames to file." }) .add_option("dump-frames-dir", description { "Folder to place dumped frames in" }, make_default_value(config::dump_frames_dir)) .add_option("renderer", description { "Which graphics renderer to use." }, make_default_value(config::renderer)); parser.add_command("help") .add_argument("help-command", optional {}, value<std::string> {}); parser.add_command("replay") .add_argument("trace file", value<std::string> {}) .add_option_group(replayOptions); return parser; } static int replay(const std::string &path) { SDLWindow sdl; if (!sdl.initCore()) { gCliLog->error("Failed to initialise SDL"); return -1; } if (!sdl.initGraphics()) { gCliLog->error("Failed to initialise graphics backend."); return -1; } return sdl.run(path); } int start(excmd::parser &parser, excmd::option_state &options) { // Print version if (options.has("version")) { // TODO: print git hash std::cout << "Decaf PM4 Replay tool version 0.0.1" << std::endl; std::exit(0); } // Print help if (options.empty() || options.has("help")) { if (options.has("help-command")) { std::cout << parser.format_help("decaf-pm4-replay", options.get<std::string>("help-command")) << std::endl; } else { std::cout << parser.format_help("decaf-pm4-replay") << std::endl; } std::exit(0); } if (!options.has("replay")) { return 0; } if (options.has("dump-drc-frames")) { config::dump_drc_frames = true; } if (options.has("dump-tv-frames")) { config::dump_tv_frames = true; } if (options.has("dump-frames-dir")) { config::dump_frames_dir = options.get<std::string>("dump-frames-dir"); } if (options.has("renderer")) { config::renderer = options.get<std::string>("renderer"); } auto traceFile = options.get<std::string>("trace file"); // Initialise libdecaf logger auto decafSettings = decaf::Settings { }; decafSettings.log.to_file = true; decafSettings.log.to_stdout = true; decafSettings.log.level = "debug"; decaf::setConfig(decafSettings); decaf::initialiseLogging("pm4-replay.txt"); auto gpuSettings = gpu::Settings { }; gpuSettings.debug.debug_enabled = true; gpu::setConfig(gpuSettings); gCliLog = decaf::makeLogger("decaf-pm4-replay"); gCliLog->set_pattern("[%l] %v"); gCliLog->info("Trace path {}", traceFile); // Initialise CPU to setup physical memory cpu::initialise(); return replay(traceFile); } int main(int argc, char **argv) { auto parser = getCommandLineParser(); excmd::option_state options; try { options = parser.parse(argc, argv); } catch (excmd::exception ex) { std::cout << "Error parsing options: " << ex.what() << std::endl; std::exit(-1); } return start(parser, options); } ================================================ FILE: tools/pm4-replay/replay_parser.h ================================================ #pragma once #include <string_view> class ReplayParser { public: virtual ~ReplayParser() = default; virtual bool runUntilTimestamp(uint64_t timestamp) = 0; }; ================================================ FILE: tools/pm4-replay/replay_parser_pm4.cpp ================================================ #include "replay_parser_pm4.h" #include <libdecaf/src/cafe/cafe_tinyheap.h> #include <libgpu/latte/latte_pm4_commands.h> using namespace latte::pm4; std::unique_ptr<ReplayParser> ReplayParserPM4::Create(gpu::GraphicsDriver *driver, RingBuffer *ringBuffer, phys_ptr<cafe::TinyHeapPhysical> heap, const std::string &path) { // Try open file std::ifstream file; file.open(path, std::ifstream::binary); if (!file.is_open()) { return {}; } // Check magic header std::array<char, 4> magic; file.read(magic.data(), 4); if (magic != decaf::pm4::CaptureMagic) { return {}; } // Allocate register storage auto allocPtr = phys_ptr<void> { nullptr }; cafe::TinyHeap_Alloc(heap, 0x10000 * 4, 0x100, &allocPtr); if (!allocPtr) { return { }; } auto self = new ReplayParserPM4 { }; self->mGraphicsDriver = driver; self->mRingBuffer = ringBuffer; self->mHeap = heap; self->mRegisterStorage = phys_cast<uint32_t *>(allocPtr); self->mFile = std::move(file); return std::unique_ptr<ReplayParser> { self }; } bool ReplayParserPM4::runUntilTimestamp(uint64_t timestamp) { std::vector<char> buffer; bool reachedTimestamp = false; mFile.clear(); mFile.seekg(4, std::fstream::beg); while (true) { decaf::pm4::CapturePacket packet; mFile.read(reinterpret_cast<char *>(&packet), sizeof(decaf::pm4::CapturePacket)); if (!mFile) { return false; } switch (packet.type) { case decaf::pm4::CapturePacket::CommandBuffer: { buffer.resize(packet.size); mFile.read(buffer.data(), buffer.size()); if (!mFile) { return false; } handleCommandBuffer(buffer.data(), packet.size); break; } case decaf::pm4::CapturePacket::RegisterSnapshot: { decaf_check((packet.size % 4) == 0); auto numRegisters = packet.size / 4; mFile.read(reinterpret_cast<char *>(mRegisterStorage.getRawPointer()), packet.size); // Swap it into big endian, so we can write LOAD_ commands for (auto i = 0u; i < numRegisters; ++i) { mRegisterStorage[i] = byte_swap(mRegisterStorage[i]); } handleRegisterSnapshot(mRegisterStorage, numRegisters); mRingBuffer->waitTimestamp(mRingBuffer->flushCommandBuffer()); break; } case decaf::pm4::CapturePacket::SetBuffer: { decaf::pm4::CaptureSetBuffer setBuffer; mFile.read(reinterpret_cast<char *>(&setBuffer), sizeof(decaf::pm4::CaptureSetBuffer)); handleSetBuffer(setBuffer); mRingBuffer->waitTimestamp(mRingBuffer->flushCommandBuffer()); break; } case decaf::pm4::CapturePacket::MemoryLoad: { decaf::pm4::CaptureMemoryLoad load; mFile.read(reinterpret_cast<char *>(&load), sizeof(decaf::pm4::CaptureMemoryLoad)); if (!mFile) { return false; } buffer.resize(packet.size - sizeof(decaf::pm4::CaptureMemoryLoad)); mFile.read(buffer.data(), buffer.size()); if (!mFile) { return false; } handleMemoryLoad(load, buffer); break; } default: mFile.seekg(packet.size, std::ifstream::cur); } if (packet.timestamp >= timestamp) { reachedTimestamp = true; break; } } // Flush ringbuffer to gpu mRingBuffer->waitTimestamp(mRingBuffer->flushCommandBuffer()); return reachedTimestamp; } bool ReplayParserPM4::handleCommandBuffer(void *buffer, uint32_t sizeBytes) { auto numWords = sizeBytes / 4; mRingBuffer->writeBuffer(buffer, numWords); return scanCommandBuffer(buffer, numWords); } void ReplayParserPM4::handleSetBuffer(decaf::pm4::CaptureSetBuffer &setBuffer) { auto isTv = (setBuffer.type == decaf::pm4::CaptureSetBuffer::TvBuffer) ? 1u : 0u; mRingBuffer->writePM4(DecafSetBuffer { isTv ? latte::pm4::ScanTarget::TV : latte::pm4::ScanTarget::DRC, setBuffer.address, setBuffer.bufferingMode, setBuffer.width, setBuffer.height }); } void ReplayParserPM4::handleRegisterSnapshot(phys_ptr<uint32_t> registers, uint32_t count) { // Enable loading of registers auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0) .ENABLE_CONFIG_REG(true) .ENABLE_CONTEXT_REG(true) .ENABLE_ALU_CONST(true) .ENABLE_BOOL_CONST(true) .ENABLE_LOOP_CONST(true) .ENABLE_RESOURCE(true) .ENABLE_SAMPLER(true) .ENABLE_CTL_CONST(true) .ENABLE_ORDINAL(true); auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0); mRingBuffer->writePM4(ContextControl { LOAD_CONTROL, SHADOW_ENABLE }); // Write all the register load packets! static std::pair<uint32_t, uint32_t> LoadConfigRange[] = { { 0, (latte::Register::ConfigRegisterEnd - latte::Register::ConfigRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadConfigReg { phys_cast<phys_addr>(registers + (latte::Register::ConfigRegisterBase / 4)), gsl::make_span(LoadConfigRange) }); static std::pair<uint32_t, uint32_t> LoadContextRange[] = { { 0, (latte::Register::ContextRegisterEnd - latte::Register::ContextRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadContextReg { phys_cast<phys_addr>(registers + (latte::Register::ContextRegisterBase / 4)), gsl::make_span(LoadContextRange) }); static std::pair<uint32_t, uint32_t> LoadAluConstRange[] = { { 0, (latte::Register::AluConstRegisterEnd - latte::Register::AluConstRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadAluConst { phys_cast<phys_addr>(registers + (latte::Register::AluConstRegisterBase / 4)), gsl::make_span(LoadAluConstRange) }); static std::pair<uint32_t, uint32_t> LoadResourceRange[] = { { 0, (latte::Register::ResourceRegisterEnd - latte::Register::ResourceRegisterBase) / 4 }, }; mRingBuffer->writePM4(latte::pm4::LoadResource { phys_cast<phys_addr>(registers + (latte::Register::ResourceRegisterBase / 4)), gsl::make_span(LoadResourceRange) }); static std::pair<uint32_t, uint32_t> LoadSamplerRange[] = { { 0, (latte::Register::SamplerRegisterEnd - latte::Register::SamplerRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadSampler { phys_cast<phys_addr>(registers + (latte::Register::SamplerRegisterBase / 4)), gsl::make_span(LoadSamplerRange) }); static std::pair<uint32_t, uint32_t> LoadControlRange[] = { { 0, (latte::Register::ControlRegisterEnd - latte::Register::ControlRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadControlConst { phys_cast<phys_addr>(registers + (latte::Register::ControlRegisterBase / 4)), gsl::make_span(LoadControlRange) }); static std::pair<uint32_t, uint32_t> LoadLoopRange[] = { { 0, (latte::Register::LoopConstRegisterEnd - latte::Register::LoopConstRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadLoopConst { phys_cast<phys_addr>(registers + (latte::Register::LoopConstRegisterBase / 4)), gsl::make_span(LoadLoopRange) }); static std::pair<uint32_t, uint32_t> LoadBoolRange[] = { { 0, (latte::Register::BoolConstRegisterEnd - latte::Register::BoolConstRegisterBase) / 4 }, }; mRingBuffer->writePM4(LoadLoopConst { phys_cast<phys_addr>(registers + (latte::Register::BoolConstRegisterBase / 4)), gsl::make_span(LoadBoolRange) }); } void ReplayParserPM4::handleMemoryLoad(decaf::pm4::CaptureMemoryLoad &load, std::vector<char> &data) { std::memcpy(phys_cast<void *>(load.address).getRawPointer(), data.data(), data.size()); mGraphicsDriver->notifyCpuFlush(load.address, static_cast<uint32_t>(data.size())); } bool ReplayParserPM4::scanType0(HeaderType0 header, const gsl::span<be2_val<uint32_t>> &data) { return false; } bool ReplayParserPM4::scanType3(HeaderType3 header, const gsl::span<be2_val<uint32_t>> &data) { if (header.opcode() == IT_OPCODE::DECAF_SWAP_BUFFERS) { return true; } if (header.opcode() == IT_OPCODE::INDIRECT_BUFFER || header.opcode() == IT_OPCODE::INDIRECT_BUFFER_PRIV) { return scanCommandBuffer(phys_cast<void *>(phys_addr { data[0].value() }).getRawPointer(), data[2]); } return false; } bool ReplayParserPM4::scanCommandBuffer(void *words, uint32_t numWords) { auto buffer = reinterpret_cast<be2_val<uint32_t> *>(words); auto foundSwap = false; for (auto pos = size_t { 0u }; pos < numWords; ) { auto header = Header::get(buffer[pos]); auto size = size_t { 0u }; switch (header.type()) { case PacketType::Type0: { auto header0 = HeaderType0::get(header.value); size = header0.count() + 1; decaf_check(pos + size < numWords); foundSwap |= scanType0(header0, gsl::make_span(buffer + pos + 1, size)); break; } case PacketType::Type3: { auto header3 = HeaderType3::get(header.value); size = header3.size() + 1; decaf_check(pos + size < numWords); foundSwap |= scanType3(header3, gsl::make_span(buffer + pos + 1, size)); break; } case PacketType::Type2: { // This is a filler packet, like a "nop", ignore it break; } case PacketType::Type1: default: size = numWords; break; } pos += size + 1; } return foundSwap; } ================================================ FILE: tools/pm4-replay/replay_parser_pm4.h ================================================ #pragma once #include "replay_parser.h" #include "replay_ringbuffer.h" #include <libcpu/be2_struct.h> #include <libdecaf/decaf_pm4replay.h> #include <libdecaf/src/cafe/cafe_tinyheap.h> #include <libgpu/gpu_graphicsdriver.h> #include <libgpu/latte/latte_pm4.h> #include <cstdint> #include <fstream> #include <memory> #include <string> class ReplayParserPM4 : public ReplayParser { ReplayParserPM4() = default; ReplayParserPM4(const ReplayParserPM4 &) = delete; ReplayParserPM4(ReplayParserPM4 &&) = delete; ReplayParserPM4 &operator=(const ReplayParserPM4 &) = delete; ReplayParserPM4 &operator=(ReplayParserPM4 &&) = delete; public: virtual ~ReplayParserPM4() = default; bool runUntilTimestamp(uint64_t timestamp) override; static std::unique_ptr<ReplayParser> Create(gpu::GraphicsDriver *driver, RingBuffer *ringBuffer, phys_ptr<cafe::TinyHeapPhysical> heap, const std::string &path); private: bool handleCommandBuffer(void *buffer, uint32_t sizeBytes); void handleSetBuffer(decaf::pm4::CaptureSetBuffer &setBuffer); void handleRegisterSnapshot(phys_ptr<uint32_t> registers, uint32_t count); void handleMemoryLoad(decaf::pm4::CaptureMemoryLoad &load, std::vector<char> &data); bool scanType0(latte::pm4::HeaderType0 header, const gsl::span<be2_val<uint32_t>> &data); bool scanType3(latte::pm4::HeaderType3 header, const gsl::span<be2_val<uint32_t>> &data); bool scanCommandBuffer(void *words, uint32_t numWords); private: gpu::GraphicsDriver *mGraphicsDriver = nullptr; RingBuffer *mRingBuffer = nullptr; std::ifstream mFile; phys_ptr<cafe::TinyHeapPhysical> mHeap = nullptr; phys_ptr<uint32_t> mRegisterStorage = nullptr; }; ================================================ FILE: tools/pm4-replay/replay_ringbuffer.h ================================================ #pragma once #include <libdecaf/src/cafe/cafe_tinyheap.h> #include <libgpu/gpu_ringbuffer.h> #include <libgpu/gpu_ih.h> #include <libgpu/latte/latte_pm4_commands.h> #include <libgpu/latte/latte_pm4_sizer.h> #include <libgpu/latte/latte_pm4_writer.h> #include <atomic> #include <cstring> #include <mutex> #include <condition_variable> class RingBuffer { static constexpr auto BufferSize = 0x1000000u; public: RingBuffer(phys_ptr<cafe::TinyHeapPhysical> replayHeap) : mReplayHeap(replayHeap) { auto allocPtr = phys_ptr<void> { nullptr }; cafe::TinyHeap_Alloc(replayHeap, 8, 0x100, &allocPtr); mRetireTimestampMemory = phys_cast<uint64_t *>(allocPtr); *mRetireTimestampMemory = 0ull; mRetireTimestamp = 0ull; mSubmitTimestamp = 0ull; } ~RingBuffer() { if (mRetireTimestampMemory) { cafe::TinyHeap_Free(mReplayHeap, mRetireTimestampMemory); } } uint64_t insertRetiredTimestamp() { auto submitTimestamp = ++mSubmitTimestamp; auto eventType = latte::VGT_EVENT_TYPE::CACHE_FLUSH_TS; writePM4( latte::pm4::EventWriteEOP { latte::VGT_EVENT_INITIATOR::get(0) .EVENT_TYPE(eventType) .EVENT_INDEX(latte::VGT_EVENT_INDEX::TS), latte::pm4::EW_ADDR_LO::get(0) .ADDR_LO(static_cast<uint32_t>(phys_cast<phys_addr>(mRetireTimestampMemory)) >> 2) .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN64), latte::pm4::EWP_ADDR_HI::get(0) .DATA_SEL(latte::pm4::EWP_DATA_64) .INT_SEL(latte::pm4::EWP_INT_WRITE_CONFIRM), static_cast<uint32_t>(submitTimestamp & 0xFFFFFFFF), static_cast<uint32_t>(submitTimestamp >> 32) }); return submitTimestamp; } uint64_t flushCommandBuffer() { auto timestamp = insertRetiredTimestamp(); gpu::ringbuffer::write(mBuffer); mBuffer.clear(); return timestamp; } void waitTimestamp(uint64_t timestamp) { std::unique_lock<std::mutex> lock{ mRetireMutex }; mRetireCV.wait(lock, [this]() { return mRetireTimestamp >= mSubmitTimestamp.load(); }); } void onGpuInterrupt() { std::unique_lock<std::mutex> lock { mRetireMutex }; mRetireTimestamp = *mRetireTimestampMemory; mRetireCV.notify_all(); } template<typename Type> void writePM4(const Type &value) { auto &ncValue = const_cast<Type &>(value); // Calculate the total size this object will be latte::pm4::PacketSizer sizer; ncValue.serialise(sizer); auto totalSize = sizer.getSize() + 1; // Resize buffer auto writePosition = static_cast<uint32_t>(mBuffer.size()); mBuffer.resize(mBuffer.size() + totalSize); // Serialize the packet to the active command buffer auto writer = latte::pm4::PacketWriter { mBuffer.data(), writePosition, Type::Opcode, totalSize }; ncValue.serialise(writer); decaf_check(writePosition == mBuffer.size()); } void writeBuffer(const void *buffer, uint32_t numWords) { auto offset = mBuffer.size(); mBuffer.resize(mBuffer.size() + numWords); std::memcpy(mBuffer.data() + offset, buffer, numWords * 4); } private: std::vector<uint32_t> mBuffer; phys_ptr<cafe::TinyHeapPhysical> mReplayHeap = nullptr; phys_ptr<uint64_t> mRetireTimestampMemory = nullptr; std::atomic<uint64_t> mSubmitTimestamp = 0ull; std::atomic<uint64_t> mRetireTimestamp = 0ull; std::mutex mRetireMutex; std::condition_variable mRetireCV; }; ================================================ FILE: tools/pm4-replay/sdl_window.cpp ================================================ #include "sdl_window.h" #include "clilog.h" #include "config.h" #include "replay_ringbuffer.h" #include "replay_parser_pm4.h" #include <array> #include <atomic> #include <fstream> #include <common/log.h> #include <common/platform_dir.h> #include <future> #include <libgpu/gpu_config.h> #include <SDL_syswm.h> using namespace latte::pm4; RingBuffer *sRingBuffer = nullptr; // eww is global because of onGpuInterrupt void initialiseRegisters(RingBuffer *ringBuffer); SDLWindow::~SDLWindow() { } bool SDLWindow::initCore() { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) { gCliLog->error("Failed to initialize SDL: {}", SDL_GetError()); return false; } return true; } bool SDLWindow::initGraphics() { auto videoInitialised = false; #ifdef SDL_VIDEO_DRIVER_X11 if (!videoInitialised) { videoInitialised = SDL_VideoInit("x11") == 0; if (!videoInitialised) { gCliLog->error("Failed to initialize SDL Video with x11: {}", SDL_GetError()); } } #endif #ifdef SDL_VIDEO_DRIVER_WAYLAND if (!videoInitialised) { videoInitialised = SDL_VideoInit("wayland") == 0; if (!videoInitialised) { gCliLog->error("Failed to initialize SDL Video with wayland: {}", SDL_GetError()); } } #endif if (!videoInitialised) { if (SDL_VideoInit(NULL) != 0) { gCliLog->error("Failed to initialize SDL Video: {}", SDL_GetError()); return false; } } gCliLog->info("Using SDL video driver {}", SDL_GetCurrentVideoDriver()); mGraphicsDriver = gpu::createGraphicsDriver(); if (!mGraphicsDriver) { return false; } switch (mGraphicsDriver->type()) { case gpu::GraphicsDriverType::Vulkan: mRendererName = "Vulkan"; break; case gpu::GraphicsDriverType::Null: mRendererName = "Null"; break; default: mRendererName = "Unknown"; } return true; } static void onGpuInterrupt() { auto entries = gpu::ih::read(); sRingBuffer->onGpuInterrupt(); } bool SDLWindow::run(const std::string &tracePath) { std::atomic_bool shouldQuit = false; // Setup some basic window stuff mWindow = SDL_CreateWindow("pm4-replay", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WindowWidth, WindowHeight, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE); if (gpu::config()->display.screenMode == gpu::DisplaySettings::Fullscreen) { SDL_SetWindowFullscreen(mWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); } // Setup graphics driver auto wsi = gpu::WindowSystemInfo { }; auto sysWmInfo = SDL_SysWMinfo { }; SDL_VERSION(&sysWmInfo.version); if (!SDL_GetWindowWMInfo(mWindow, &sysWmInfo)) { gCliLog->error("SDL_GetWindowWMInfo failed: {}", SDL_GetError()); } switch (sysWmInfo.subsystem) { #ifdef SDL_VIDEO_DRIVER_WINDOWS case SDL_SYSWM_WINDOWS: wsi.type = gpu::WindowSystemType::Windows; wsi.renderSurface = static_cast<void *>(sysWmInfo.info.win.window); break; #endif #ifdef SDL_VIDEO_DRIVER_X11 case SDL_SYSWM_X11: wsi.type = gpu::WindowSystemType::X11; wsi.renderSurface = reinterpret_cast<void *>(sysWmInfo.info.x11.window); wsi.displayConnection = static_cast<void *>(sysWmInfo.info.x11.display); break; #endif #ifdef SDL_VIDEO_DRIVER_COCOA case SDL_SYSWM_COCOA: wsi.type = gpu::WindowSystemType::Cocoa; wsi.renderSurface = static_cast<void *>(sysWmInfo.info.cocoa.window); break; #endif #ifdef SDL_VIDEO_DRIVER_WAYLAND case SDL_SYSWM_WAYLAND: wsi.type = gpu::WindowSystemType::Wayland; wsi.renderSurface = static_cast<void *>(sysWmInfo.info.wl.surface); wsi.displayConnection = static_cast<void *>(sysWmInfo.info.wl.display); break; #endif default: decaf_abort(fmt::format("Unsupported SDL window subsystem {}", sysWmInfo.subsystem)); } mGraphicsDriver->setWindowSystemInfo(wsi); // Setup replay parser auto replayHeap = phys_cast<cafe::TinyHeapPhysical *>(phys_addr { 0x34000000 }); cafe::TinyHeap_Setup(replayHeap, 0x430, phys_cast<void *>(phys_addr { 0x34000000 + 0x430 }), 0x1C000000 - 0x430); auto ringBuffer = std::make_unique<RingBuffer>(replayHeap); sRingBuffer = ringBuffer.get(); initialiseRegisters(ringBuffer.get()); auto parser = ReplayParserPM4::Create(mGraphicsDriver, ringBuffer.get(), replayHeap, tracePath); if (!parser) { return false; } gpu::ih::enable(latte::CP_INT_CNTL::get(0xFFFFFFFF)); gpu::ih::setInterruptCallback(onGpuInterrupt); auto loopReplay = true; auto replayThread = std::thread { [&]() { do { parser->runUntilTimestamp(0xFFFFFFFFFFFFFFFFull); } while (loopReplay && !shouldQuit); } }; auto graphicsThread = std::thread { [&]() { mGraphicsDriver->run(); } }; while (!shouldQuit) { auto event = SDL_Event { }; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_WINDOWEVENT: if (event.window.event == SDL_WINDOWEVENT_CLOSE) { shouldQuit = true; } break; case SDL_KEYUP: if (event.key.keysym.sym == SDLK_ESCAPE) { shouldQuit = true; } break; case SDL_QUIT: shouldQuit = true; break; } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // We have to wait until replay is finished... replayThread.join(); mGraphicsDriver->stop(); graphicsThread.join(); return true; } void initialiseRegisters(RingBuffer *ringBuffer) { // Straight copied from gx2 std::array<uint32_t, 24> zeroes; zeroes.fill(0); uint32_t values28030_28034[] = { latte::PA_SC_SCREEN_SCISSOR_TL::get(0).value, latte::PA_SC_SCREEN_SCISSOR_BR::get(0) .BR_X(8192) .BR_Y(8192).value }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::PA_SC_SCREEN_SCISSOR_TL, gsl::make_span(values28030_28034) }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_SC_LINE_CNTL, latte::PA_SC_LINE_CNTL::get(0) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_SU_VTX_CNTL, latte::PA_SU_VTX_CNTL::get(0) .PIX_CENTER(latte::PA_SU_VTX_CNTL_PIX_CENTER::OGL) .ROUND_MODE(latte::PA_SU_VTX_CNTL_ROUND_MODE::TRUNCATE) .QUANT_MODE(latte::PA_SU_VTX_CNTL_QUANT_MODE::QUANT_1_256TH) .value }); // PA_CL_POINT_X_RAD, PA_CL_POINT_Y_RAD, PA_CL_POINT_POINT_SIZE, PA_CL_POINT_POINT_CULL_RAD ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::PA_CL_POINT_X_RAD, gsl::make_span(zeroes.data(), 4) }); // PA_CL_UCP_0_X ... PA_CL_UCP_5_W ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::PA_CL_UCP_0_X, gsl::make_span(zeroes.data(), 24) }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_CL_VTE_CNTL, latte::PA_CL_VTE_CNTL::get(0) .VPORT_X_SCALE_ENA(true) .VPORT_X_OFFSET_ENA(true) .VPORT_Y_SCALE_ENA(true) .VPORT_Y_OFFSET_ENA(true) .VPORT_Z_SCALE_ENA(true) .VPORT_Z_OFFSET_ENA(true) .VTX_W0_FMT(true) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_CL_NANINF_CNTL, latte::PA_CL_NANINF_CNTL::get(0) .value }); uint32_t values28200_28208[] = { 0, latte::PA_SC_WINDOW_SCISSOR_TL::get(0) .WINDOW_OFFSET_DISABLE(true) .value, latte::PA_SC_WINDOW_SCISSOR_BR::get(0) .BR_X(8192) .BR_Y(8192) .value, }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::PA_SC_WINDOW_OFFSET, gsl::make_span(values28200_28208) }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_SC_LINE_STIPPLE, latte::PA_SC_LINE_STIPPLE::get(0) .value }); uint32_t values28A0C_28A10[] = { latte::PA_SC_MPASS_PS_CNTL::get(0) .value, latte::PA_SC_MODE_CNTL::get(0) .MSAA_ENABLE(true) .FORCE_EOV_CNTDWN_ENABLE(true) .FORCE_EOV_REZ_ENABLE(true) .value }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::PA_SC_LINE_STIPPLE, gsl::make_span(values28A0C_28A10) }); uint32_t values28250_28254[] = { latte::PA_SC_VPORT_SCISSOR_0_TL::get(0) .WINDOW_OFFSET_DISABLE(true) .value, latte::PA_SC_VPORT_SCISSOR_0_BR::get(0) .BR_X(8192) .BR_Y(8192) .value, }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::PA_SC_VPORT_SCISSOR_0_TL, gsl::make_span(values28250_28254) }); // TODO: Register 0x8B24 unknown ringBuffer->writePM4(latte::pm4::SetConfigReg { static_cast<latte::Register>(0x8B24), 0xFF3FFF }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_SC_CLIPRECT_RULE, latte::PA_SC_CLIPRECT_RULE::get(0) .CLIP_RULE(0xFFFF) .value }); ringBuffer->writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_GS_VERTEX_REUSE, latte::VGT_GS_VERTEX_REUSE::get(0) .VERT_REUSE(16) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_OUTPUT_PATH_CNTL, latte::VGT_OUTPUT_PATH_CNTL::get(0) .PATH_SELECT(latte::VGT_OUTPUT_PATH_SELECT::TESS_EN) .value }); // TODO: This is an unknown value 16 * 0xb14(r31) * 0xb18(r31) ringBuffer->writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_ES_PER_GS, latte::VGT_ES_PER_GS::get(0) .ES_PER_GS(16 * 1 * 1) .value }); ringBuffer->writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_GS_PER_ES, latte::VGT_GS_PER_ES::get(0) .GS_PER_ES(256) .value }); ringBuffer->writePM4(latte::pm4::SetConfigReg { latte::Register::VGT_GS_PER_VS, latte::VGT_GS_PER_VS::get(0) .GS_PER_VS(4) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_INDX_OFFSET, latte::VGT_INDX_OFFSET::get(0) .INDX_OFFSET(0) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_REUSE_OFF, latte::VGT_REUSE_OFF::get(0) .REUSE_OFF(false) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_MULTI_PRIM_IB_RESET_EN, latte::VGT_MULTI_PRIM_IB_RESET_EN::get(0) .RESET_EN(true) .value }); uint32_t values28C58_28C5C[] = { latte::VGT_VERTEX_REUSE_BLOCK_CNTL::get(0) .VTX_REUSE_DEPTH(14) .value, latte::VGT_OUT_DEALLOC_CNTL::get(0) .DEALLOC_DIST(16) .value, }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::VGT_VERTEX_REUSE_BLOCK_CNTL, gsl::make_span(values28C58_28C5C) }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_HOS_REUSE_DEPTH, latte::VGT_HOS_REUSE_DEPTH::get(0) .REUSE_DEPTH(16) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_STRMOUT_DRAW_OPAQUE_OFFSET, latte::VGT_STRMOUT_DRAW_OPAQUE_OFFSET::get(0) .OFFSET(0) .value }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::VGT_VTX_CNT_EN, latte::VGT_VTX_CNT_EN::get(0) .VTX_CNT_EN(false) .value }); uint32_t values28400_28404[] = { latte::VGT_MAX_VTX_INDX::get(0) .MAX_INDX(-1) .value, latte::VGT_MIN_VTX_INDX::get(0) .MIN_INDX(0) .value }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::VGT_MAX_VTX_INDX, gsl::make_span(values28400_28404) }); ringBuffer->writePM4(latte::pm4::SetConfigReg { latte::Register::TA_CNTL_AUX, latte::TA_CNTL_AUX::get(0) .UNK0(true) .SYNC_GRADIENT(true) .SYNC_WALKER(true) .SYNC_ALIGNER(true) .value }); // TODO: Register 0x9714 unknown ringBuffer->writePM4(latte::pm4::SetConfigReg { static_cast<latte::Register>(0x9714), 1 }); // TODO: Register 0x8D8C unknown ringBuffer->writePM4(latte::pm4::SetConfigReg { static_cast<latte::Register>(0x8D8C), 0x4000 }); // SQ_ESTMP_RING_BASE ... SQ_REDUC_RING_SIZE ringBuffer->writePM4(latte::pm4::SetConfigRegs { latte::Register::SQ_ESTMP_RING_BASE, gsl::make_span(zeroes.data(), 12) }); // SQ_ESTMP_RING_ITEMSIZE ... SQ_REDUC_RING_ITEMSIZE ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::SQ_ESTMP_RING_ITEMSIZE, gsl::make_span(zeroes.data(), 6) }); ringBuffer->writePM4(latte::pm4::SetControlConstant { latte::Register::SQ_VTX_START_INST_LOC, latte::SQ_VTX_START_INST_LOC::get(0) .OFFSET(0) .value }); // SPI_FOG_CNTL ... SPI_FOG_FUNC_BIAS ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::SPI_FOG_CNTL, gsl::make_span(zeroes.data(), 3) }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::SPI_INTERP_CONTROL_0, latte::SPI_INTERP_CONTROL_0::get(0) .FLAT_SHADE_ENA(true) .PNT_SPRITE_ENA(false) .PNT_SPRITE_OVRD_X(latte::SPI_PNT_SPRITE_SEL::SEL_S) .PNT_SPRITE_OVRD_Y(latte::SPI_PNT_SPRITE_SEL::SEL_T) .PNT_SPRITE_OVRD_Z(latte::SPI_PNT_SPRITE_SEL::SEL_0) .PNT_SPRITE_OVRD_W(latte::SPI_PNT_SPRITE_SEL::SEL_1) .PNT_SPRITE_TOP_1(true) .value }); ringBuffer->writePM4(latte::pm4::SetConfigReg { latte::Register::SPI_CONFIG_CNTL_1, latte::SPI_CONFIG_CNTL_1::get(0) .value }); // TODO: Register 0x286C8 unknown ringBuffer->writePM4(latte::pm4::SetAllContextsReg { static_cast<latte::Register>(0x286C8), 1 }); // TODO: Register 0x28354 unknown auto unkValue = 0u; // 0x143C(r31) if (unkValue > 0x5270) { ringBuffer->writePM4(latte::pm4::SetContextReg { static_cast<latte::Register>(0x28354), 0xFF }); } else { ringBuffer->writePM4(latte::pm4::SetContextReg { static_cast<latte::Register>(0x28354), 0x1FF }); } uint32_t values28D28_28D2C[] = { latte::DB_SRESULTS_COMPARE_STATE0::get(0) .value, latte::DB_SRESULTS_COMPARE_STATE1::get(0) .value }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::DB_SRESULTS_COMPARE_STATE0, gsl::make_span(values28D28_28D2C) }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::DB_RENDER_OVERRIDE, latte::DB_RENDER_OVERRIDE::get(0) .value }); // TODO: Register 0x9830 unknown ringBuffer->writePM4(latte::pm4::SetConfigReg { static_cast<latte::Register>(0x9830), 0 }); // TODO: Register 0x983C unknown ringBuffer->writePM4(latte::pm4::SetConfigReg { static_cast<latte::Register>(0x983C), 0x1000000 }); uint32_t values28C30_28C3C[] = { latte::CB_CLRCMP_CONTROL::get(0) .CLRCMP_FCN_SEL(latte::CB_CLRCMP_SEL::SRC) .value, latte::CB_CLRCMP_SRC::get(0) .CLRCMP_SRC(0) .value, latte::CB_CLRCMP_DST::get(0) .CLRCMP_DST(0) .value, latte::CB_CLRCMP_MSK::get(0) .CLRCMP_MSK(0xFFFFFFFF) .value }; ringBuffer->writePM4(latte::pm4::SetContextRegs { latte::Register::CB_CLRCMP_CONTROL, gsl::make_span(values28C30_28C3C) }); // TODO: Register 0x9A1C unknown ringBuffer->writePM4(latte::pm4::SetConfigReg { static_cast<latte::Register>(0x9A1C), 0 }); ringBuffer->writePM4(latte::pm4::SetContextReg { latte::Register::PA_SC_AA_MASK, latte::PA_SC_AA_MASK::get(0) .AA_MASK_ULC(0xFF) .AA_MASK_URC(0xFF) .AA_MASK_LLC(0xFF) .AA_MASK_LRC(0xFF) .value }); // TODO: Register 0x28230 unknown ringBuffer->writePM4(latte::pm4::SetContextReg { static_cast<latte::Register>(0x28230), 0xAAAAAAAA }); } ================================================ FILE: tools/pm4-replay/sdl_window.h ================================================ #pragma once #include <SDL.h> #include <libgpu/gpu_graphicsdriver.h> #include <string> #include <thread> class SDLWindow { static const auto WindowWidth = 1420; static const auto WindowHeight = 768; public: ~SDLWindow(); bool initCore(); bool initGraphics(); bool run(const std::string &tracePath); private: SDL_Window *mWindow = nullptr; gpu::GraphicsDriver *mGraphicsDriver = nullptr; std::string mRendererName; bool mToggleDRC = false; }; ================================================ FILE: tools/pm4-replay-qt/CMakeLists.txt ================================================ project(pm4-replay-qt) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(".") include_directories("../../src/libdecaf/src") include_directories("../../src/libgpu") include_directories("../../src/libgpu/src") file(GLOB_RECURSE SOURCE_FILES *.cpp) file(GLOB_RECURSE HEADER_FILES *.h) file(GLOB_RECURSE UI_FILES *.ui) qt5_wrap_ui(UIS_HDRS ${UI_FILES}) add_executable(pm4-replay-qt ${SOURCE_FILES} ${HEADER_FILES} ${UIS_HDRS}) set_target_properties(pm4-replay-qt PROPERTIES FOLDER tools) target_link_libraries(pm4-replay-qt common libdecaf excmd) target_link_libraries(pm4-replay-qt Qt5::Widgets) install(TARGETS pm4-replay-qt RUNTIME DESTINATION "${DECAF_INSTALL_BINDIR}") ================================================ FILE: tools/pm4-replay-qt/decaf.cpp ================================================ #include "decaf.h" #include <QEventLoop> #include <libdecaf/decaf.h> #include <libdecaf/src/kernel/kernel_memory.h> #include <libdecaf/src/modules/gx2/gx2_internal_cbpool.h> #include <libdecaf/src/modules/gx2/gx2_state.h> #include <libcpu/cpu.h> #include <libcpu/be2_struct.h> #include <libgpu/gpu_config.h> void Decaf::start() { // Initialise libdecaf logger decaf::config::log::to_file = true; decaf::config::log::to_stdout = true; decaf::config::log::level = "debug"; decaf::initialiseLogging("pm4-replay-qt"); gpu::config::debug = true; // We need to run the trace on a core. cpu::initialise(); cpu::setCoreEntrypointHandler( [runner = this](cpu::Core *core) { if (core->id == 1) { runner->mainCoreEntry(); } }); cpu::start(); } void Decaf::mainCoreEntry() { mThread = QThread::currentThread(); mGraphicsDriver = reinterpret_cast<gpu::OpenGLDriver *>(gpu::createGLDriver()); // Setup decaf kernel::initialiseVirtualMemory(); kernel::initialiseAppMemory(0x10000, 0, 0); auto systemHeapBounds = kernel::getVirtualRange(kernel::VirtualRegion::CafeOS); mHeap = new TeenyHeap { virt_cast<void *>(static_cast<virt_addr>(systemHeapBounds.start)).getRawPointer(), systemHeapBounds.size }; // Setup pm4 command buffer pool auto cbPoolSize = 0x2000; auto cbPoolBase = mHeap->alloc(cbPoolSize, 0x100); gx2::internal::setMainCore(); gx2::internal::initCommandBufferPool(reinterpret_cast<uint32_t *>(cbPoolBase), cbPoolSize / 4); // Inform listeners we are ready to go! emit started(); // Run a Qt event loop which we can schedule tasks on QEventLoop loop; loop.exec(); // Exit! mThread = nullptr; } ================================================ FILE: tools/pm4-replay-qt/decaf.h ================================================ #pragma once #include <libgpu/gpu_opengldriver.h> #include <QThread> #include <QObject> #include <QOpenGLContext> #include <QOffscreenSurface> #include <QTimer> #include <common/teenyheap.h> class Decaf : public QObject { Q_OBJECT public: void start(); void mainCoreEntry(); QThread *thread() { return mThread; } QOpenGLContext *context() { return mContext; } QOffscreenSurface *surface() { return mSurface; } bool makeCurrent() { return mContext->makeCurrent(mSurface); } void doneCurrent() { mContext->doneCurrent(); } void setContext(QOffscreenSurface *surface, QOpenGLContext *context) { mSurface = surface; mContext = context; } TeenyHeap *heap() { return mHeap; } gpu::OpenGLDriver *graphicsDriver() { return mGraphicsDriver; } Q_SIGNALS: void started(); private: QThread *mThread = nullptr; TeenyHeap *mHeap = nullptr; QOffscreenSurface *mSurface = nullptr; QOpenGLContext *mContext = nullptr; gpu::OpenGLDriver *mGraphicsDriver; }; ================================================ FILE: tools/pm4-replay-qt/main.cpp ================================================ #include "mainwindow.h" #include <QApplication> int main(int argc, char **argv) { QApplication app { argc, argv }; MainWindow mainWin; mainWin.show(); return app.exec(); } ================================================ FILE: tools/pm4-replay-qt/mainwindow.cpp ================================================ #include "replayrunner.h" #include "mainwindow.h" #include "replaycommandsmodel.h" #include <QFileDialog> #include <QOpenGLContext> #include <QOffscreenSurface> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), mErrorMessage(this) { ui.setupUi(this); connect(&mDecaf, &Decaf::started, this, &MainWindow::onDecafStarted); mDecaf.start(); } void MainWindow::onFileOpen() { auto fileName = QFileDialog::getOpenFileName(this, tr("Open PM4 Replay"), "", tr("PM4 Replay (*.pm4);;All Files (*)")); if (fileName.isEmpty()) { return; } mReplay = openReplay(fileName.toStdString()); if (!mReplay) { mErrorMessage.showMessage(QString { "%1 is not a valid pm4 replay file." }.arg(fileName)); return; } // TODO: Thread buildReplayIndex... buildReplayIndex(mReplay); auto model = new ReplayCommandModel { mReplay, nullptr }; ui.tableView->setModel(model); auto runner = new ReplayRunner { &mDecaf, mReplay }; connect(runner, &ReplayRunner::frameFinished, ui.openGLWidget, &ReplayRenderWidget::displayFrame); runner->moveToThread(mDecaf.thread()); QMetaObject::invokeMethod(runner, "initialise"); QMetaObject::invokeMethod(runner, "runFrame"); QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, runner, &ReplayRunner::runFrame); timer->start(100); } void MainWindow::startReplay() { } void MainWindow::onDecafStarted() { ui.openGLWidget->doneCurrent(); auto surface = new QOffscreenSurface { }; surface->setFormat(ui.openGLWidget->context()->format()); surface->create(); auto context = new QOpenGLContext { }; context->setFormat(surface->format()); context->setShareContext(ui.openGLWidget->context()); context->create(); surface->moveToThread(mDecaf.thread()); context->moveToThread(mDecaf.thread()); mDecaf.setContext(surface, context); ui.openGLWidget->makeCurrent(); } ================================================ FILE: tools/pm4-replay-qt/mainwindow.h ================================================ #pragma once #include "decaf.h" #include "replay.h" #include "replayrenderwidget.h" #include "ui_mainwindow.h" #include <QErrorMessage> class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); void startReplay(); protected slots: void onFileOpen(); void onDecafStarted(); private: Ui_MainWindow ui; std::shared_ptr<ReplayFile> mReplay; QErrorMessage mErrorMessage; Decaf mDecaf; }; ================================================ FILE: tools/pm4-replay-qt/replay.cpp ================================================ #include "replay.h" #include <libgpu/latte/latte_enum_as_string.h> std::shared_ptr<ReplayFile> openReplay(const std::string &path) { auto fileSize = size_t { 0 }; auto fileHandle = platform::openMemoryMappedFile(path, platform::ProtectFlags::ReadOnly, &fileSize); if (fileHandle == platform::InvalidMapFileHandle) { return nullptr; } // Map filew auto fileView = platform::mapViewOfFile(fileHandle, platform::ProtectFlags::ReadOnly, 0, fileSize); if (!fileView) { platform::closeMemoryMappedFile(fileHandle); return nullptr; } // Sanity check the magic header auto magic = *reinterpret_cast<std::array<char, 4> *>(fileView); if (magic != decaf::pm4::CaptureMagic) { platform::unmapViewOfFile(fileView, fileSize); platform::closeMemoryMappedFile(fileHandle); return nullptr; } auto replay = std::make_shared<ReplayFile>(); replay->handle = fileHandle; replay->view = reinterpret_cast<uint8_t *>(fileView); replay->size = fileSize; return replay; } static bool buildIndexCommandBuffer(std::shared_ptr<ReplayFile> replay, size_t filePos, size_t numWords) { auto buffer = reinterpret_cast<be2_val<uint32_t> *>(replay->view + filePos); for (auto pos = size_t { 0u }; pos < numWords; ) { auto header = Header::get(buffer[pos]); auto size = size_t { 0u }; switch (header.type()) { case PacketType::Type0: { auto header0 = HeaderType0::get(header.value); size = header0.count() + 1; break; } case PacketType::Type3: { auto header3 = HeaderType3::get(header.value); size = header3.size() + 1; if (header3.opcode() == IT_OPCODE::DECAF_SWAP_BUFFERS) { replay->index.frames.push_back({ ReplayPosition { replay->index.packets.size(), replay->index.commands.size() } }); } break; } case PacketType::Type2: { // This is a filler packet, like a "nop", ignore it break; } case PacketType::Type1: default: size = numWords; break; } replay->index.commands.push_back({ header, buffer + pos }); pos += size + 1; } return true; } bool buildReplayIndex(std::shared_ptr<ReplayFile> replay) { size_t pos = 4; replay->index.frames.push_back({ ReplayPosition { 0, 0 } }); while (pos + sizeof(decaf::pm4::CapturePacket) <= replay->size) { auto packet = reinterpret_cast<decaf::pm4::CapturePacket *>(replay->view + pos); pos += sizeof(decaf::pm4::CapturePacket); if (pos + packet->size > replay->size) { break; } switch (packet->type) { case decaf::pm4::CapturePacket::CommandBuffer: { buildIndexCommandBuffer(replay, pos, packet->size / 4); break; } case decaf::pm4::CapturePacket::RegisterSnapshot: { break; } case decaf::pm4::CapturePacket::SetBuffer: { break; } case decaf::pm4::CapturePacket::MemoryLoad: { break; } default: break; } replay->index.packets.push_back({ packet->type, packet->size, replay->view + pos }); pos += packet->size; } return true; } std::string getCommandName(ReplayIndex::Command &command) { switch (command.header.type()) { case PacketType::Type0: return "PM4::Type0 Unknown"; case PacketType::Type1: return "PM4::Type1 Unknown"; case PacketType::Type2: return "PM4::Type2 Unknown"; case PacketType::Type3: { auto header3 = HeaderType3::get(command.header.value); return latte::pm4::to_string(header3.opcode()); } default: return "Unknown"; } } ================================================ FILE: tools/pm4-replay-qt/replay.h ================================================ #pragma once #include <array> #include <libdecaf/decaf_pm4replay.h> #include <libgpu/latte/latte_pm4.h> #include <libgpu/latte/latte_pm4_commands.h> #include <libgpu/latte/latte_pm4_reader.h> #include <libgpu/latte/latte_pm4_writer.h> #include <string> #include <vector> #include <memory> #include <common/platform_memory.h> using namespace latte::pm4; struct ReplayPosition { size_t packetIndex; size_t commandIndex; }; struct ReplayIndex { struct Frame { ReplayPosition pos; }; struct Packet { decaf::pm4::CapturePacket::Type type; uint32_t size; uint8_t *data; }; struct Command { latte::pm4::Header header; void *command; }; std::vector<Frame> frames; std::vector<Packet> packets; std::vector<Command> commands; }; struct ReplayFile { ~ReplayFile() { if (view) { platform::unmapViewOfFile(view, size); view = nullptr; } if (handle != platform::InvalidMapFileHandle) { platform::closeMemoryMappedFile(handle); handle = platform::InvalidMapFileHandle; } size = 0; } platform::MapFileHandle handle = platform::InvalidMapFileHandle; uint8_t *view = nullptr; size_t size = 0; ReplayIndex index; }; std::shared_ptr<ReplayFile> openReplay(const std::string &path); std::string getCommandName(ReplayIndex::Command &command); bool buildReplayIndex(std::shared_ptr<ReplayFile> replay); ================================================ FILE: tools/pm4-replay-qt/replaycommandsmodel.cpp ================================================ #include "replaycommandsmodel.h" ReplayCommandModel::ReplayCommandModel(std::shared_ptr<ReplayFile> replay, QObject *parent) : QAbstractTableModel(parent), mReplay(replay) { } int ReplayCommandModel::rowCount(const QModelIndex &parent) const { return static_cast<int>(mReplay->index.commands.size()); } int ReplayCommandModel::columnCount(const QModelIndex &parent) const { return 2; } QVariant ReplayCommandModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { if (index.column() == 0) { return QString { "%1" }.arg(index.row() + 1); } else if (index.column() == 1) { return QString::fromStdString(getCommandName(mReplay->index.commands[index.row()])); } } return QVariant {}; } ================================================ FILE: tools/pm4-replay-qt/replaycommandsmodel.h ================================================ #pragma once #include <QAbstractTableModel> #include "replay.h" class ReplayCommandModel : public QAbstractTableModel { Q_OBJECT public: ReplayCommandModel(std::shared_ptr<ReplayFile> replay, QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; private: std::shared_ptr<ReplayFile> mReplay; }; ================================================ FILE: tools/pm4-replay-qt/replayrenderwidget.cpp ================================================ #include "replayrenderwidget.h" ReplayRenderWidget::ReplayRenderWidget(QWidget *parent) : QOpenGLWidget(parent) { QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setVersion(4, 5); format.setProfile(QSurfaceFormat::CompatibilityProfile); setFormat(format); } void ReplayRenderWidget::initializeGL() { static auto vertexCode = R"( #version 420 core in vec2 fs_position; in vec2 fs_texCoord; out vec2 vs_texCoord; out gl_PerVertex { vec4 gl_Position; }; void main() { vs_texCoord = fs_texCoord; gl_Position = vec4(fs_position, 0.0, 1.0); })"; static auto pixelCode = R"( #version 420 core in vec2 vs_texCoord; out vec4 ps_color; uniform sampler2D sampler_0; void main() { ps_color = texture(sampler_0, vs_texCoord); })"; initializeOpenGLFunctions(); // Create vertex program mVertexProgram = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertexCode); // Create pixel program mPixelProgram = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &pixelCode); glBindFragDataLocation(mPixelProgram, 0, "ps_color"); // Create pipeline glGenProgramPipelines(1, &mPipeline); glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertexProgram); glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mPixelProgram); // (TL, TR, BR) (BR, BL, TL) // Create vertex buffer static const GLfloat vertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, }; glCreateBuffers(1, &mVertBuffer); glNamedBufferData(mVertBuffer, sizeof(vertices), vertices, GL_STATIC_DRAW); // Create vertex array glCreateVertexArrays(1, &mVertArray); auto fs_position = glGetAttribLocation(mVertexProgram, "fs_position"); glEnableVertexArrayAttrib(mVertArray, fs_position); glVertexArrayAttribFormat(mVertArray, fs_position, 2, GL_FLOAT, GL_FALSE, 0); glVertexArrayAttribBinding(mVertArray, fs_position, 0); auto fs_texCoord = glGetAttribLocation(mVertexProgram, "fs_texCoord"); glEnableVertexArrayAttrib(mVertArray, fs_texCoord); glVertexArrayAttribFormat(mVertArray, fs_texCoord, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat)); glVertexArrayAttribBinding(mVertArray, fs_texCoord, 0); // Create texture sampler glGenSamplers(1, &mSampler); glSamplerParameteri(mSampler, GL_TEXTURE_WRAP_S, static_cast<int>(GL_CLAMP_TO_EDGE)); glSamplerParameteri(mSampler, GL_TEXTURE_WRAP_T, static_cast<int>(GL_CLAMP_TO_EDGE)); glSamplerParameteri(mSampler, GL_TEXTURE_MIN_FILTER, static_cast<int>(GL_LINEAR)); glSamplerParameteri(mSampler, GL_TEXTURE_MAG_FILTER, static_cast<int>(GL_LINEAR)); } void ReplayRenderWidget::paintGL() { // Set up some needed GL state glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glDisablei(GL_BLEND, 0); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); glDisable(GL_SCISSOR_TEST); glDisable(GL_CULL_FACE); // Clear screen glClearColor(0.6f, 0.2f, 0.2f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Draw displays // Setup screen draw shader glBindVertexArray(mVertArray); glBindVertexBuffer(0, mVertBuffer, 0, 4 * sizeof(GLfloat)); glBindProgramPipeline(mPipeline); // Draw screen quad glBindSampler(0, mSampler); glBindTextureUnit(0, mTvBuffer); glDrawArrays(GL_TRIANGLES, 0, 6); } void ReplayRenderWidget::displayFrame(unsigned int tv, unsigned int drc) { mTvBuffer = tv; mDrcBuffer = drc; update(); } ================================================ FILE: tools/pm4-replay-qt/replayrenderwidget.h ================================================ #pragma once #include <QOpenGLWidget> #include <QOpenGLFunctions_4_5_Core> class ReplayRenderWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core { Q_OBJECT public: ReplayRenderWidget(QWidget *parent = 0); void initializeGL() override; void paintGL() override; public Q_SLOTS: void displayFrame(unsigned int tv, unsigned int drc); private: GLuint mTvBuffer; GLuint mDrcBuffer; GLuint mVertexProgram; GLuint mPixelProgram; GLuint mPipeline; GLuint mVertArray; GLuint mVertBuffer; GLuint mSampler; }; ================================================ FILE: tools/pm4-replay-qt/replayrunner.cpp ================================================ #include <glbinding/Binding.h> #include <glbinding/Meta.h> #include <common/log.h> #include <common/teenyheap.h> #include <libdecaf/decaf.h> #include <libdecaf/src/kernel/kernel_memory.h> #include <libdecaf/src/modules/gx2/gx2_internal_cbpool.h> #include <libdecaf/src/modules/gx2/gx2_state.h> #include <libcpu/cpu.h> #include <libcpu/pointer.h> #include <libcpu/be2_struct.h> #include <libgpu/gpu_config.h> #include <libgpu/gpu_opengldriver.h> #include <spdlog/spdlog.h> #include <condition_variable> #include <queue> #include "replay.h" #include "replayrunner.h" using namespace latte::pm4; static std::string getGlDebugSource(gl::GLenum source) { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINSYS"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "EXTERNAL"; case GL_DEBUG_SOURCE_APPLICATION: return "APP"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; default: return glbinding::Meta::getString(source); } } static std::string getGlDebugType(gl::GLenum severity) { switch (severity) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_PUSH_GROUP: return "PUSH_GROUP"; case GL_DEBUG_TYPE_POP_GROUP: return "POP_GROUP"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; default: return glbinding::Meta::getString(severity); } } static std::string getGlDebugSeverity(gl::GLenum severity) { switch (severity) { case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; case GL_DEBUG_SEVERITY_MEDIUM: return "MED"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIF"; default: return glbinding::Meta::getString(severity); } } static void GL_APIENTRY debugMessageCallback(gl::GLenum source, gl::GLenum type, gl::GLuint id, gl::GLenum severity, gl::GLsizei length, const gl::GLchar* message, const void *userParam) { for (auto filterID : gpu::config::debug_filters) { if (filterID == id) { return; } } auto outputStr = fmt::format("GL Message ({}, {}, {}, {}) {}", id, getGlDebugSource(source), getGlDebugType(type), getGlDebugSeverity(severity), message); if (severity == GL_DEBUG_SEVERITY_HIGH) { gLog->warn(outputStr); } else if (severity == GL_DEBUG_SEVERITY_MEDIUM) { gLog->debug(outputStr); } else if (severity == GL_DEBUG_SEVERITY_LOW) { gLog->trace(outputStr); } else if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { gLog->info(outputStr); } else { gLog->info(outputStr); } } void ReplayRunner::initialise() { mDecaf->context()->makeCurrent(mDecaf->surface()); glbinding::Binding::initialize(); if (gpu::config::debug) { glbinding::setCallbackMaskExcept(glbinding::CallbackMask::After | glbinding::CallbackMask::ParametersAndReturnValue, { "glGetError" }); glbinding::setAfterCallback([](const glbinding::FunctionCall &call) { auto error = glbinding::Binding::GetError.directCall(); if (error != GL_NO_ERROR) { fmt::MemoryWriter writer; writer << call.function->name() << "("; for (unsigned i = 0; i < call.parameters.size(); ++i) { writer << call.parameters[i]->asString(); if (i < call.parameters.size() - 1) writer << ", "; } writer << ")"; if (call.returnValue) { writer << " -> " << call.returnValue->asString(); } gLog->error("OpenGL error: {} with {}", glbinding::Meta::getString(error), writer.str()); } }); gl::glDebugMessageCallback(&debugMessageCallback, nullptr); gl::glEnable(static_cast<gl::GLenum>(GL_DEBUG_OUTPUT)); gl::glEnable(static_cast<gl::GLenum>(GL_DEBUG_OUTPUT_SYNCHRONOUS)); } } void ReplayRunner::runGpu() { mDecaf->context()->makeCurrent(mDecaf->surface()); mDecaf->graphicsDriver()->syncPoll([&](unsigned int tvBuffer, unsigned int drcBuffer) { emit frameFinished(tvBuffer, drcBuffer); }); } void ReplayRunner::runFrame() { auto foundFrameTerminator = false; while (mRunning && mPosition.packetIndex < mReplay->index.packets.size()) { foundFrameTerminator = runPacket(mReplay->index.packets[mPosition.packetIndex]); if (foundFrameTerminator) { break; } mPosition.packetIndex++; } runGpu(); if (!foundFrameTerminator) { emit replayFinished(); } } bool ReplayRunner::runPacket(ReplayIndex::Packet &packet) { auto foundFrameTerminator = false; switch (packet.type) { case decaf::pm4::CapturePacket::CommandBuffer: { auto commandIndex = 0; auto packetEnd = packet.data + packet.size; while (mRunning && !foundFrameTerminator) { auto &command = mReplay->index.commands[mPosition.commandIndex]; if (command.command >= packetEnd) { break; } foundFrameTerminator = runCommand(command); mPosition.commandIndex++; } gx2::internal::flushCommandBuffer(0x100); break; } case decaf::pm4::CapturePacket::MemoryLoad: { auto loadPacket = reinterpret_cast<decaf::pm4::CaptureMemoryLoad *>(packet.data); auto loadData = packet.data + sizeof(decaf::pm4::CaptureMemoryLoad); auto dst = virt_cast<void *>(static_cast<virt_addr>(loadPacket->address)); std::memcpy(dst.getRawPointer(), loadData, packet.size - sizeof(decaf::pm4::CaptureMemoryLoad)); break; } case decaf::pm4::CapturePacket::RegisterSnapshot: { auto registers = reinterpret_cast<uint32_t *>(packet.data); auto count = packet.size / sizeof(uint32_t); for (auto i = 0u; i < count; ++i) { mRegisterStorage[i] = registers[i]; } runRegisterSnapshot(mRegisterStorage, count); gx2::internal::flushCommandBuffer(0x100); break; } case decaf::pm4::CapturePacket::SetBuffer: { auto setBufferPacket = reinterpret_cast<decaf::pm4::CaptureSetBuffer *>(packet.data); auto isTv = (setBufferPacket->type == decaf::pm4::CaptureSetBuffer::TvBuffer) ? 1u : 0u; gx2::internal::writePM4(DecafSetBuffer { isTv, setBufferPacket->bufferingMode, setBufferPacket->width, setBufferPacket->height }); gx2::internal::flushCommandBuffer(0x100); break; } } return foundFrameTerminator; } void ReplayRunner::runRegisterSnapshot(be_val<uint32_t> *registers, uint32_t count) { // Enable loading of registers auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0) .ENABLE_CONFIG_REG(true) .ENABLE_CONTEXT_REG(true) .ENABLE_ALU_CONST(true) .ENABLE_BOOL_CONST(true) .ENABLE_LOOP_CONST(true) .ENABLE_RESOURCE(true) .ENABLE_SAMPLER(true) .ENABLE_CTL_CONST(true) .ENABLE_ORDINAL(true); auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0); gx2::internal::writePM4(ContextControl { LOAD_CONTROL, SHADOW_ENABLE }); // Write all the register load packets! static std::pair<uint32_t, uint32_t> LoadConfigRange[] = { { 0, (latte::Register::ConfigRegisterEnd - latte::Register::ConfigRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadConfigReg { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::ConfigRegisterBase / 4]), gsl::make_span(LoadConfigRange) }); static std::pair<uint32_t, uint32_t> LoadContextRange[] = { { 0, (latte::Register::ContextRegisterEnd - latte::Register::ContextRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadContextReg { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::ContextRegisterBase / 4]), gsl::make_span(LoadContextRange) }); static std::pair<uint32_t, uint32_t> LoadAluConstRange[] = { { 0, (latte::Register::AluConstRegisterEnd - latte::Register::AluConstRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadAluConst { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::AluConstRegisterBase / 4]), gsl::make_span(LoadAluConstRange) }); static std::pair<uint32_t, uint32_t> LoadResourceRange[] = { { 0, (latte::Register::ResourceRegisterEnd - latte::Register::ResourceRegisterBase) / 4 }, }; gx2::internal::writePM4(latte::pm4::LoadResource { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::ResourceRegisterBase / 4]), gsl::make_span(LoadResourceRange) }); static std::pair<uint32_t, uint32_t> LoadSamplerRange[] = { { 0, (latte::Register::SamplerRegisterEnd - latte::Register::SamplerRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadSampler { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::SamplerRegisterBase / 4]), gsl::make_span(LoadSamplerRange) }); static std::pair<uint32_t, uint32_t> LoadControlRange[] = { { 0, (latte::Register::ControlRegisterEnd - latte::Register::ControlRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadControlConst { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::ControlRegisterBase / 4]), gsl::make_span(LoadControlRange) }); static std::pair<uint32_t, uint32_t> LoadLoopRange[] = { { 0, (latte::Register::LoopConstRegisterEnd - latte::Register::LoopConstRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadLoopConst { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::LoopConstRegisterBase / 4]), gsl::make_span(LoadLoopRange) }); static std::pair<uint32_t, uint32_t> LoadBoolRange[] = { { 0, (latte::Register::BoolConstRegisterEnd - latte::Register::BoolConstRegisterBase) / 4 }, }; gx2::internal::writePM4(LoadLoopConst { reinterpret_cast<be_val<uint32_t> *>(®isters[latte::Register::BoolConstRegisterBase / 4]), gsl::make_span(LoadBoolRange) }); } bool ReplayRunner::runCommand(ReplayIndex::Command &command) { auto foundFrameTerminator = false; switch (command.header.type()) { case PacketType::Type3: { auto header3 = HeaderType3::get(command.header.value); auto size = header3.size() + 1; if (header3.opcode() == IT_OPCODE::DECAF_SWAP_BUFFERS) { foundFrameTerminator = true; } if (header3.opcode() == IT_OPCODE::INDIRECT_BUFFER_PRIV) { // Should we iterate through commands in an indirect buffer?? } decaf::pm4::injectCommandBuffer(command.command, (size + 1) * 4); break; } case PacketType::Type0: { auto header0 = HeaderType0::get(command.header.value); auto size = header0.count() + 1; decaf::pm4::injectCommandBuffer(command.command, (size + 1) * 4); break; } default: // FUCK break; } return foundFrameTerminator; } ================================================ FILE: tools/pm4-replay-qt/replayrunner.h ================================================ #pragma once #include "decaf.h" #include "replay.h" #include <QObject> class ReplayRunner : public QObject { Q_OBJECT public: ReplayRunner(Decaf *decaf, std::shared_ptr<ReplayFile> replay) : mDecaf(decaf), mReplay(replay) { mRegisterStorage = reinterpret_cast<be_val<uint32_t> *>(mDecaf->heap()->alloc(0x10000 * 4, 0x100)); } public Q_SLOTS: void initialise(); void runFrame(); Q_SIGNALS: void replayFinished(); void frameFinished(unsigned int tv, unsigned int drc); private: bool runPacket(ReplayIndex::Packet &packet); bool runCommand(ReplayIndex::Command &command); void runRegisterSnapshot(be_val<uint32_t> *registers, uint32_t count); void runGpu(); private: Decaf *mDecaf = nullptr; size_t mPacketIndex = 0; size_t mCommandIndex = 0; size_t mIndirectCommandIndex = 0; std::shared_ptr<ReplayFile> mReplay; be_val<uint32_t> *mRegisterStorage = nullptr; bool mRunning = true; ReplayPosition mPosition = { 0 , 0 }; }; ================================================ FILE: tools/pm4-replay-qt/resources/mainwindow.ui ================================================ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>850</width> <height>693</height> </rect> </property> <property name="windowTitle"> <string>PM4 Replay Tool</string> </property> <widget class="QWidget" name="centralwidget"> <layout class="QHBoxLayout" name="horizontalLayout"> <property name="spacing"> <number>0</number> </property> <property name="leftMargin"> <number>0</number> </property> <property name="topMargin"> <number>0</number> </property> <property name="rightMargin"> <number>0</number> </property> <property name="bottomMargin"> <number>0</number> </property> <item> <widget class="ReplayRenderWidget" name="openGLWidget"/> </item> </layout> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>850</width> <height>21</height> </rect> </property> <widget class="QMenu" name="menuFile"> <property name="title"> <string>&File</string> </property> <addaction name="actionOpen"/> <addaction name="separator"/> <addaction name="actionExit"/> </widget> <addaction name="menuFile"/> </widget> <widget class="QStatusBar" name="statusbar"/> <widget class="QDockWidget" name="dockWidget"> <attribute name="dockWidgetArea"> <number>1</number> </attribute> <widget class="QWidget" name="dockWidgetContents"> <layout class="QVBoxLayout" name="verticalLayout"> <property name="leftMargin"> <number>0</number> </property> <property name="topMargin"> <number>0</number> </property> <property name="rightMargin"> <number>0</number> </property> <property name="bottomMargin"> <number>0</number> </property> <item> <widget class="QTableView" name="tableView"/> </item> </layout> </widget> </widget> <action name="actionOpen"> <property name="text"> <string>&Open</string> </property> </action> <action name="actionExit"> <property name="text"> <string>E&xit</string> </property> </action> </widget> <customwidgets> <customwidget> <class>ReplayRenderWidget</class> <extends>QOpenGLWidget</extends> <header>replayrenderwidget.h</header> </customwidget> </customwidgets> <resources/> <connections> <connection> <sender>actionOpen</sender> <signal>triggered()</signal> <receiver>MainWindow</receiver> <slot>onFileOpen()</slot> <hints> <hint type="sourcelabel"> <x>-1</x> <y>-1</y> </hint> <hint type="destinationlabel"> <x>424</x> <y>346</y> </hint> </hints> </connection> </connections> <slots> <slot>onFileOpen()</slot> </slots> </ui> ================================================ FILE: tools/wiiu-rpc/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2) set(CMAKE_TOOLCHAIN_FILE "$ENV{WUT_ROOT}/share/wut.toolchain.cmake") project(wiiu-rpc) include("${WUT_ROOT}/share/wut.cmake" REQUIRED) set(CMAKE_C_STANDARD 99) add_executable(wiiu-rpc src/main.c src/console.c src/server.c src/packet.c) target_link_libraries(wiiu-rpc whb coreinit sysapp nsysnet nn_ac proc_ui) wut_enable_newlib(wiiu-rpc) wut_default_malloc(wiiu-rpc) wut_create_rpx(wiiu-rpc.rpx wiiu-rpc) ================================================ FILE: tools/wiiu-rpc/client.py ================================================ import socket, struct import binascii from functools import partial CMD_DYNLOAD_ACQUIRE = 1000 CMD_DYNLOAD_RELEASE = 1001 CMD_DYNLOAD_FINDEXPORT = 1002 CMD_READ_MEMORY = 2000 CMD_WRITE_MEMORY = 2001 CMD_CALL_FUNCTION = 2002 CMD_IOS_OPEN = 3000 CMD_IOS_CLOSE = 3001 CMD_IOS_IOCTL = 3002 CMD_IOS_IOCTLV = 3003 ARG_TYPE_INT32 = 0 ARG_TYPE_INT64 = 1 ARG_TYPE_STRING = 2 ARG_TYPE_DATA_IN = 3 ARG_TYPE_DATA_OUT = 4 ARG_TYPE_DATA_IN_OUT = 5 class Packet: def __init__(self, command): self.command = command; self.data = "" self.readPos = 0 def writeUint8(self, value): self.data += struct.pack(">B", value) def writeUint32(self, value): self.data += struct.pack(">I", value) def writeUint64(self, value): self.data += struct.pack(">Q", value) def writeInt32(self, value): self.data += struct.pack(">i", value) def writeInt64(self, value): self.data += struct.pack(">q", value) def writePointer(self, value): self.writeUint32(value) def writeString(self, value): self.writeUint32(len(value) + 1) self.data += value self.writeUint8(0) def writeBinary(self, value): self.writeUint32(len(value)) self.data += value def readUint8(self): [value] = struct.unpack(">B", self.data[self.readPos : self.readPos + 1]) self.readPos += 1 return value def readInt32(self): [value] = struct.unpack(">i", self.data[self.readPos : self.readPos + 4]) self.readPos += 4 return value def readUint32(self): [value] = struct.unpack(">I", self.data[self.readPos : self.readPos + 4]) self.readPos += 4 return value def readPointer(self): return self.readUint32() def readInt64(self): [value] = struct.unpack(">q", self.data[self.readPos : self.readPos + 8]) self.readPos += 8 return value def readUint64(self): [value] = struct.unpack(">Q", self.data[self.readPos : self.readPos + 8]) self.readPos += 8 return value def readString(self): length = self.readUint32() value = self.data[self.readPos : self.readPos + length - 1] self.readUint8() self.readPos += length return value def readBinary(self): length = self.readUint32() value = self.data[self.readPos : self.readPos + length] self.readPos += length return value class InOutData: def __init__(self, size): self.size = size self.data = "" class OutData: def __init__(self, size): self.size = size self.data = "" class Client: def __init__(self): self.fd = None def connect(self, ip, port): self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.fd.settimeout(5) self.fd.connect((ip, port)) def disconnect(self): self.fd.close() self.fd = None def send(self, packet): header = struct.pack(">II", len(packet.data) + 8, packet.command) self.fd.sendall(header + packet.data) def recv(self): [length] = struct.unpack(">I", self.fd.recv(4)) [command] = struct.unpack(">I", self.fd.recv(4)) dataLength = length - 8 packet = Packet(command) packet.readPos = 0 packet.data = "" while len(packet.data) < dataLength: remaining = dataLength - len(packet.data) packet.data += self.fd.recv(remaining) return packet def OSDynLoad_Acquire(self, name): packet = Packet(CMD_DYNLOAD_ACQUIRE) packet.writeString(name) self.send(packet) reply = self.recv() result = reply.readInt32() handle = reply.readPointer() return [result, handle] def OSDynLoad_Release(self, handle): packet = Packet(CMD_DYNLOAD_RELEASE) packet.writePointer(handle) self.send(packet) self.recv() def OSDynLoad_FindExport(self, handle, isData, name): packet = Packet(CMD_DYNLOAD_FINDEXPORT) packet.writePointer(handle) packet.writeInt32(isData) packet.writeString(name) self.send(packet) reply = self.recv() result = reply.readInt32() handle = reply.readPointer() return [result, handle] def readMemory(self, addr, size): packet = Packet(CMD_READ_MEMORY) packet.writePointer(addr) packet.writeUint32(size) self.send(packet) reply = self.recv() data = reply.readBinary() return data def writeMemory(self, addr, data): packet = Packet(CMD_WRITE_MEMORY) packet.writePointer(addr) packet.writeBinary(data) self.send(packet) reply = self.recv() def callFunction(self, addr, *args): packet = Packet(CMD_CALL_FUNCTION) packet.writePointer(addr) packet.writeUint32(len(args)) for arg in args: if type(arg) is int: packet.writeUint32(ARG_TYPE_INT32) packet.writeUint32(arg) elif type(arg) is long: packet.writeUint32(ARG_TYPE_INT64) packet.writeInt64(arg) elif type(arg) is str: packet.writeUint32(ARG_TYPE_STRING) packet.writeString(arg) elif isinstance(arg, OutData): packet.writeUint32(ARG_TYPE_DATA_OUT) packet.writeUint32(arg.size) elif isinstance(arg, InOutData): packet.writeUint32(ARG_TYPE_DATA_IN_OUT) packet.writeBinary(arg.data) self.send(packet) reply = self.recv() result = reply.readUint32() tmpDataOutNum = reply.readUint32() for i in range(0, tmpDataOutNum): argNum = reply.readUint32() data = reply.readBinary() args[argNum].data = data return result def createFunction(self, addr): return partial(self.callFunction, addr) def acquireLibrary(self, name): [result, handle] = self.OSDynLoad_Acquire(name) if result != 0: raise Exception("Module %s not found" % name) return handle def releaseLibrary(self, handle): self.OSDynLoad_Release(handle) def loadFunction(self, handle, name): [result, addr] = self.OSDynLoad_FindExport(handle, 0, name) if result != 0: raise Exception("Function %s not found" % name) return self.createFunction(addr) def iosIoctlv(self, handle, request, vecsIn, vecsOut): packet = Packet(CMD_IOS_IOCTLV) packet.writeUint32(handle) packet.writeUint32(request) packet.writeUint32(len(vecsIn)) packet.writeUint32(len(vecsOut)) for vec in vecsIn: packet.writeBinary(vec) for vec in vecsOut: packet.writeUint32(vec.size) self.send(packet) reply = self.recv() result = reply.readUint32() for vec in vecsOut: vec.data = reply.readBinary() return result print "Connecting..." client = Client() client.connect("192.168.1.132", 1337) # Example usage! print "Connected" coreinit = client.acquireLibrary("coreinit.rpl") MCP_Open = client.loadFunction(coreinit, "MCP_Open") MCP_TitleCount = client.loadFunction(coreinit, "MCP_TitleCount") MCP_TitleList = client.loadFunction(coreinit, "MCP_TitleList") MCP_Close = client.loadFunction(coreinit, "MCP_Close") handle = MCP_Open() titleCount = MCP_TitleCount(handle) print "Title count: %d" % titleCount if titleCount > 0: titleList = OutData(titleCount * 0x61) actualTitleCount = OutData(4) result = MCP_TitleList(handle, actualTitleCount, titleList, titleCount * 0x61) if result == 0: totalTitleCount = int(binascii.hexlify(actualTitleCount.data), 16) print "Total number of titles: %d" % totalTitleCount for i in range(0, totalTitleCount): print "Title " + str(i) + " ID: " + binascii.hexlify(titleList.data[i*0x61:i*0x61+8]) MCP_Close(handle) client.releaseLibrary(coreinit) client.disconnect() ================================================ FILE: tools/wiiu-rpc/src/console.c ================================================ #include "console.h" #include <coreinit/cache.h> #include <coreinit/memheap.h> #include <coreinit/memexpheap.h> #include <coreinit/memfrmheap.h> #include <coreinit/screen.h> #include <string.h> #include <whb/log.h> #define NUM_LINES (16) #define LINE_LENGTH (128) #define FRAME_HEAP_TAG (0x000DECAF) static char sConsoleBuffer[NUM_LINES][LINE_LENGTH]; static int sLineNum = 0; static void *sBufferTV, *sBufferDRC; static uint32_t sBufferSizeTV, sBufferSizeDRC; BOOL consoleInit() { MEMHeapHandle heap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); MEMRecordStateForFrmHeap(heap, FRAME_HEAP_TAG); OSScreenInit(); sBufferSizeTV = OSScreenGetBufferSizeEx(SCREEN_TV); sBufferSizeDRC = OSScreenGetBufferSizeEx(SCREEN_DRC); sBufferTV = MEMAllocFromFrmHeapEx(heap, sBufferSizeTV, 4); if (!sBufferTV) { WHBLogPrintf("sBufferTV = MEMAllocFromFrmHeapEx(heap, 0x%X, 4) returned NULL", sBufferSizeTV); return FALSE; } sBufferDRC = MEMAllocFromFrmHeapEx(heap, sBufferSizeDRC, 4); if (!sBufferDRC) { WHBLogPrintf("sBufferDRC = MEMAllocFromFrmHeapEx(heap, 0x%X, 4) returned NULL", sBufferSizeDRC); return FALSE; } OSScreenSetBufferEx(SCREEN_TV, sBufferTV); OSScreenSetBufferEx(SCREEN_DRC, sBufferDRC); OSScreenEnableEx(SCREEN_TV, 1); OSScreenEnableEx(SCREEN_DRC, 1); WHBAddLogHandler(consoleAddLine); return TRUE; } void consoleFree() { MEMHeapHandle heap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); OSScreenShutdown(); MEMFreeByStateToFrmHeap(heap, FRAME_HEAP_TAG); } void consoleDraw() { OSScreenClearBufferEx(SCREEN_TV, 0x993333FF); OSScreenClearBufferEx(SCREEN_DRC, 0x993333FF); for (int y = 0; y < NUM_LINES; ++y) { OSScreenPutFontEx(SCREEN_TV, 0, y, sConsoleBuffer[y]); OSScreenPutFontEx(SCREEN_DRC, 0, y, sConsoleBuffer[y]); } DCFlushRange(sBufferTV, sBufferSizeTV); DCFlushRange(sBufferDRC, sBufferSizeDRC); OSScreenFlipBuffersEx(SCREEN_TV); OSScreenFlipBuffersEx(SCREEN_DRC); } void consoleAddLine(const char *line) { int length = strlen(line); if (length > LINE_LENGTH) { length = LINE_LENGTH - 1; } if (sLineNum == NUM_LINES) { for (int i = 0; i < NUM_LINES - 1; ++i) { memcpy(sConsoleBuffer[i], sConsoleBuffer[i + 1], LINE_LENGTH); } memcpy(sConsoleBuffer[sLineNum - 1], line, length); sConsoleBuffer[sLineNum - 1][length] = 0; } else { memcpy(sConsoleBuffer[sLineNum], line, length); sConsoleBuffer[sLineNum][length] = 0; ++sLineNum; } } ================================================ FILE: tools/wiiu-rpc/src/console.h ================================================ #ifndef CONSOLE_H #define CONSOLE_H #include <coreinit/internal.h> BOOL consoleInit(); void consoleFree(); void consoleDraw(); void consoleAddLine(const char *buffer); #endif // CONSOLE_H ================================================ FILE: tools/wiiu-rpc/src/main.c ================================================ #include "console.h" #include "server.h" #include "packet.h" #include <coreinit/core.h> #include <coreinit/dynload.h> #include <coreinit/filesystem.h> #include <coreinit/foreground.h> #include <coreinit/ios.h> #include <coreinit/memexpheap.h> #include <coreinit/memheap.h> #include <coreinit/screen.h> #include <coreinit/systeminfo.h> #include <coreinit/thread.h> #include <coreinit/time.h> #include <nn/ac/ac_c.h> #include <nsysnet/socket.h> #include <sysapp/launch.h> #include <whb/crash.h> #include <whb/file.h> #include <whb/gfx.h> #include <whb/log.h> #include <whb/log_udp.h> #include <whb/proc.h> #include <string.h> #include <malloc.h> typedef enum PacketCommand { CMD_DYNLOAD_ACQUIRE = 1000, CMD_DYNLOAD_RELEASE = 1001, CMD_DYNLOAD_FINDEXPORT = 1002, CMD_READ_MEMORY = 2000, CMD_WRITE_MEMORY = 2001, CMD_CALL_FUNCTION = 2002, CMD_IOS_OPEN = 3000, CMD_IOS_CLOSE = 3001, CMD_IOS_IOCTL = 3002, CMD_IOS_IOCTLV = 3003, } PacketCommand; typedef enum ArgTypes { ARG_TYPE_INT32 = 0, ARG_TYPE_INT64 = 1, ARG_TYPE_STRING = 2, ARG_TYPE_DATA_IN = 3, ARG_TYPE_DATA_OUT = 4, ARG_TYPE_DATA_IN_OUT = 5, } ArgTypes; #define MAX_NUM_ARGS 5 static uint32_t callFuncArgs0(void *func) { WHBLogPrintf("%p()", func); return ((uint32_t (*)())func)(); } static uint32_t callFuncArgs1(void *func, uint32_t *args) { WHBLogPrintf("%p(%x)", func, args[0]); return ((uint32_t (*)(uint32_t))func)(args[0]); } static uint32_t callFuncArgs2(void *func, uint32_t *args) { WHBLogPrintf("%p(%x, %x)", func, args[0], args[1]); return ((uint32_t (*)(uint32_t, uint32_t))func)(args[0], args[1]); } static uint32_t callFuncArgs3(void *func, uint32_t *args) { WHBLogPrintf("%p(%x, %x, %x)", func, args[0], args[1], args[2]); return ((uint32_t (*)(uint32_t, uint32_t, uint32_t))func)(args[0], args[1], args[2]); } static uint32_t callFuncArgs4(void *func, uint32_t *args) { WHBLogPrintf("%p(%x, %x, %x, %x)", func, args[0], args[1], args[2], args[3]); return ((uint32_t (*)(uint32_t, uint32_t, uint32_t, uint32_t))func)(args[0], args[1], args[2], args[3]); } static uint32_t callFuncArgs5(void *func, uint32_t *args) { WHBLogPrintf("%p(%x, %x, %x, %x, %x)", func, args[0], args[1], args[2], args[3], args[4]); return ((uint32_t (*)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t))func)(args[0], args[1], args[2], args[3], args[4]); } int packetHandler(Server *server, PacketReader *packet) { PacketWriter out; MEMHeapHandle mem2 = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2); switch (packet->command) { case CMD_DYNLOAD_ACQUIRE: { const char *name = pakReadString(packet); OSDynLoad_Module module = NULL; int result; WHBLogPrintf("OSDynLoad_Acquire(\"%s\")", name); result = OSDynLoad_Acquire(name, &module); pakWriteAlloc(&out, CMD_DYNLOAD_ACQUIRE); pakWriteInt32(&out, result); pakWritePointer(&out, module); serverSendPacket(server, &out); pakWriteFree(&out); break; } case CMD_DYNLOAD_RELEASE: { OSDynLoad_Module *module = (OSDynLoad_Module *)pakReadPointer(packet); WHBLogPrintf("OSDynLoad_Release(%p)", module); OSDynLoad_Release(module); pakWriteAlloc(&out, CMD_DYNLOAD_RELEASE); serverSendPacket(server, &out); pakWriteFree(&out); break; } case CMD_DYNLOAD_FINDEXPORT: { OSDynLoad_Module *module = (OSDynLoad_Module *)pakReadPointer(packet); int32_t isData = pakReadInt32(packet); const char *name = pakReadString(packet); void *addr = NULL; int result = 0; WHBLogPrintf("OSDynLoad_FindExport(%p, %d, \"%s\")", module, isData, name); result = OSDynLoad_FindExport(module, isData, name, &addr); pakWriteAlloc(&out, CMD_DYNLOAD_FINDEXPORT); pakWriteInt32(&out, result); pakWritePointer(&out, addr); serverSendPacket(server, &out); pakWriteFree(&out); break; } case CMD_READ_MEMORY: { void *src = pakReadPointer(packet); uint32_t size = pakReadUint32(packet); pakWriteAlloc(&out, CMD_DYNLOAD_FINDEXPORT); pakWriteData(&out, src, size); serverSendPacket(server, &out); pakWriteFree(&out); break; } case CMD_WRITE_MEMORY: { void *dst = pakReadPointer(packet); uint32_t size; const uint8_t *data = pakReadData(packet, &size); memcpy(dst, data, size); pakWriteAlloc(&out, CMD_WRITE_MEMORY); serverSendPacket(server, &out); pakWriteFree(&out); break; } case CMD_CALL_FUNCTION: { void *func = pakReadPointer(packet); uint32_t numArgs = pakReadUint32(packet); uint32_t argsArray[MAX_NUM_ARGS * 2] = { 0 }; uint8_t *argsTmpData[MAX_NUM_ARGS] = { 0 }; uint32_t argsTmpDataSize[MAX_NUM_ARGS] = { 0 }; uint32_t numCallArgs = 0; uint32_t numTmpData = 0; uint32_t result = 0; WHBLogPrintf("Call function %p", func); for (int i = 0; i < numArgs; i++) { uint32_t argType = pakReadUint32(packet); if (argType == ARG_TYPE_INT32) { argsArray[numCallArgs++] = pakReadUint32(packet); } else if (argType == ARG_TYPE_INT64) { uint64_t value = pakReadUint64(packet); argsArray[numCallArgs++] = (uint32_t)((value >> 32) & 0xFFFFFFFF); argsArray[numCallArgs++] = (uint32_t)(value & 0xFFFFFFFF); } else if (argType == ARG_TYPE_STRING) { const char *str = pakReadString(packet); argsArray[numCallArgs++] = (uint32_t)str; } else if (argType == ARG_TYPE_DATA_IN) { uint32_t size; const uint8_t *data = pakReadData(packet, &size); argsArray[numCallArgs++] = (uint32_t)data; } else if (argType == ARG_TYPE_DATA_OUT) { argsTmpDataSize[i] = pakReadUint32(packet); argsTmpData[i] = MEMAllocFromExpHeapEx(mem2, argsTmpDataSize[i], 0x100); argsArray[numCallArgs++] = (uint32_t)argsTmpData[i]; numTmpData++; } else if (argType == ARG_TYPE_DATA_IN_OUT) { uint32_t size; const uint8_t *data = pakReadData(packet, &size); argsTmpDataSize[i] = size; argsTmpData[i] = MEMAllocFromExpHeapEx(mem2, argsTmpDataSize[i], 0x100); memcpy(argsTmpData[i], data, argsTmpDataSize[i]); argsArray[numCallArgs++] = (uint32_t)argsTmpData[i]; numTmpData++; } } if (numCallArgs == 0) { result = callFuncArgs0(func); } else if (numCallArgs == 1) { result = callFuncArgs1(func, argsArray); } else if (numCallArgs == 2) { result = callFuncArgs2(func, argsArray); } else if (numCallArgs == 3) { result = callFuncArgs3(func, argsArray); } else if (numCallArgs == 4) { result = callFuncArgs4(func, argsArray); } else if (numCallArgs == 5) { result = callFuncArgs5(func, argsArray); } pakWriteAlloc(&out, CMD_CALL_FUNCTION); pakWriteInt32(&out, result); pakWriteUint32(&out, numTmpData); for (int i = 0; i < numArgs; ++i) { if (argsTmpData[i]) { pakWriteUint32(&out, i); pakWriteData(&out, argsTmpData[i], argsTmpDataSize[i]); MEMFreeToExpHeap(mem2, argsTmpData[i]); } } serverSendPacket(server, &out); pakWriteFree(&out); break; } case CMD_IOS_IOCTLV: { IOSHandle handle = (IOSHandle)pakReadUint32(packet); uint32_t request = pakReadUint32(packet); uint32_t vecIn = pakReadUint32(packet); uint32_t vecOut = pakReadUint32(packet); IOSVec *vecs = MEMAllocFromExpHeapEx(mem2, sizeof(IOSVec) * (vecIn + vecOut), 0x100); void **vecsPtrs = malloc(sizeof(void *) * (vecIn + vecOut)); uint32_t *vecsLen = malloc(sizeof(uint32_t) * (vecIn + vecOut)); IOSError result; for (int i = 0; i < vecIn; ++i) { const uint8_t *data = pakReadData(packet, &vecsLen[i]); vecsPtrs[i] = MEMAllocFromExpHeapEx(mem2, vecsLen[i], 0x100); vecs[i].len = vecsLen[i]; vecs[i].paddr = vecsPtrs[i]; memcpy(vecsPtrs[i], data, vecsLen[i]); } for (int i = vecIn; i < vecIn + vecOut; ++i) { vecsLen[i] = pakReadUint32(packet); vecsPtrs[i] = MEMAllocFromExpHeapEx(mem2, vecsLen[i], 0x100); vecs[i].len = vecsLen[i]; vecs[i].paddr = vecsPtrs[i]; memset(vecsPtrs[i], 0, vecsLen[i]); } result = IOS_Ioctlv(handle, request, vecIn, vecOut, vecs); pakWriteAlloc(&out, CMD_IOS_IOCTLV); pakWriteInt32(&out, (int32_t)result); for (int i = vecIn; i < vecIn + vecOut; ++i) { pakWriteData(&out, vecsPtrs[i], vecsLen[i]); } serverSendPacket(server, &out); pakWriteFree(&out); for (int i = 0; i < vecIn + vecOut; ++i) { MEMFreeToExpHeap(mem2, vecsPtrs[i]); } MEMFreeToExpHeap(mem2, vecs); free(vecsPtrs); free(vecsLen); break; } default: WHBLogPrintf("Unknown packet command: %d", packet->command); return -1; } return 0; } int main(int argc, char **argv) { Server server; int result = 0; WHBProcInit(TRUE); WHBLogUdpInit(); WHBInitCrashHandler(); if (!consoleInit()) { result = -1; goto exit; } socket_lib_init(); ACInitialize(); if (serverStart(&server, 1337) == 0) { while(WHBProcIsRunning()) { consoleDraw(); serverProcess(&server, &packetHandler); OSSleepTicks(OSMillisecondsToTicks(30)); } } else { WHBLogPrintf("Failed to start server."); } exit: WHBLogPrintf("Exiting..."); serverClose(&server); socket_lib_finish(); ACFinalize(); consoleFree(); WHBProcShutdown(); return result; } ================================================ FILE: tools/wiiu-rpc/src/packet.c ================================================ #include "packet.h" #include "console.h" #include <string.h> #include <malloc.h> #include <whb/log.h> #define INITIAL_WRITE_PACKET_SIZE 1024 uint32_t pakReadUint32(PacketReader *packet) { uint32_t value; memcpy(&value, packet->data + packet->pos, sizeof(uint32_t)); packet->pos += sizeof(uint32_t); if (packet->pos > packet->dataLength) { WHBLogPrintf("Read past end of packet! pos: %d length: %d", packet->pos, packet->dataLength); } return value; } uint64_t pakReadUint64(PacketReader *packet) { uint64_t value; memcpy(&value, packet->data + packet->pos, sizeof(uint64_t)); packet->pos += sizeof(uint64_t); if (packet->pos > packet->dataLength) { WHBLogPrintf("Read past end of packet! pos: %d length: %d", packet->pos, packet->dataLength); } return value; } int32_t pakReadInt32(PacketReader *packet) { return (int32_t)pakReadUint32(packet); } int64_t pakReadInt64(PacketReader *packet) { return (int64_t)pakReadUint32(packet); } void * pakReadPointer(PacketReader *packet) { return (void *)pakReadUint32(packet); } const uint8_t * pakReadData(PacketReader *packet, uint32_t *length) { const uint8_t *data; *length = pakReadUint32(packet); data = packet->data + packet->pos; packet->pos += *length; if (packet->pos > packet->dataLength) { WHBLogPrintf("Read past end of packet! pos: %d length: %d", packet->pos, packet->dataLength); } return data; } const char * pakReadString(PacketReader *packet) { uint32_t length = pakReadUint32(packet); const char *str = packet->data + packet->pos; packet->pos += length; if (packet->pos > packet->dataLength) { WHBLogPrintf("Read past end of packet! pos: %d length: %d", packet->pos, packet->dataLength); } return str; } void pakWriteAlloc(PacketWriter *packet, uint32_t command) { packet->command = command; packet->pos = 0; packet->bufferSize = INITIAL_WRITE_PACKET_SIZE; packet->buffer = malloc(packet->bufferSize); pakWriteUint32(packet, 8); pakWriteUint32(packet, command); } void pakWriteFree(PacketWriter *packet) { free(packet->buffer); packet->buffer = NULL; } static void pakWriteIncreaseSize(PacketWriter *packet, uint32_t size) { uint32_t newSize = packet->pos + size; if (newSize > packet->bufferSize) { uint32_t newBufferSize = packet->bufferSize; while (newSize > newBufferSize) { newBufferSize += INITIAL_WRITE_PACKET_SIZE; } char *newBuffer = malloc(newBufferSize); memcpy(newBuffer, packet->buffer, packet->bufferSize); free(packet->buffer); packet->buffer = newBuffer; packet->bufferSize = newBufferSize; } // Update size at word 0 memcpy(packet->buffer, &newSize, sizeof(uint32_t)); } void pakWriteUint32(PacketWriter *packet, uint32_t value) { char *dst; pakWriteIncreaseSize(packet, sizeof(uint32_t)); dst = packet->buffer + packet->pos; memcpy(dst, &value, sizeof(uint32_t)); packet->pos += sizeof(uint32_t); } void pakWriteUint64(PacketWriter *packet, uint64_t value) { char *dst; pakWriteIncreaseSize(packet, sizeof(uint64_t)); dst = packet->buffer + packet->pos; memcpy(dst, &value, sizeof(uint64_t)); packet->pos += sizeof(uint64_t); } void pakWriteInt32(PacketWriter *packet, int32_t value) { pakWriteUint32(packet, (uint32_t)value); } void pakWriteInt64(PacketWriter *packet, int64_t value) { pakWriteUint64(packet, (uint64_t)value); } void pakWritePointer(PacketWriter *packet, void *value) { pakWriteUint32(packet, (uint32_t)value); } void pakWriteString(PacketWriter *packet, const char *str) { uint32_t length = str ? strlen(str) : 0; pakWriteData(packet, (const uint8_t *)str, length + 1); } void pakWriteData(PacketWriter *packet, const uint8_t *data, uint32_t length) { char *dst; pakWriteUint32(packet, length); if (data && length > 0) { pakWriteIncreaseSize(packet, length); dst = packet->buffer + packet->pos; memcpy(dst, data, length); packet->pos += length; } } ================================================ FILE: tools/wiiu-rpc/src/packet.h ================================================ #ifndef PACKET_H #define PACKET_H #include <wut_types.h> typedef struct PacketReader { uint32_t command; uint8_t *data; uint32_t dataLength; uint32_t pos; } PacketReader; typedef struct PacketWriter { uint32_t command; uint32_t pos; void *buffer; uint32_t bufferSize; } PacketWriter; uint32_t pakReadUint32(PacketReader *packet); uint64_t pakReadUint64(PacketReader *packet); int32_t pakReadInt32(PacketReader *packet); int64_t pakReadInt64(PacketReader *packet); void * pakReadPointer(PacketReader *packet); const uint8_t * pakReadData(PacketReader *packet, uint32_t *length); const char * pakReadString(PacketReader *packet); void pakWriteAlloc(PacketWriter *packet, uint32_t command); void pakWriteFree(PacketWriter *packet); void pakWriteUint32(PacketWriter *packet, uint32_t value); void pakWriteUint64(PacketWriter *packet, uint64_t value); void pakWriteInt32(PacketWriter *packet, int32_t value); void pakWriteInt64(PacketWriter *packet, int64_t value); void pakWritePointer(PacketWriter *packet, void *value); void pakWriteString(PacketWriter *packet, const char *str); void pakWriteData(PacketWriter *packet, const uint8_t *data, uint32_t length); #endif // PACKET_H ================================================ FILE: tools/wiiu-rpc/src/server.c ================================================ #include "server.h" #include "console.h" #include "packet.h" #include <coreinit/memexpheap.h> #include <coreinit/memheap.h> #include <coreinit/systeminfo.h> #include <coreinit/thread.h> #include <coreinit/time.h> #include <nn/ac/ac_c.h> #include <nsysnet/socket.h> #include <stdbool.h> #include <stdbool.h> #include <string.h> #include <whb/log.h> #define SEND_MAX_BYTES (1024) int serverStart(Server *server, int port) { int fd, opt; struct sockaddr_in addr; struct in_addr ip_addr; uint32_t ipAddress; ACConfigId startupId; MEMHeapHandle mem2 = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2); server->fd = -1; server->client = -1; server->readPos = 0; server->readLength = 0; server->readMax = 1024 * 1024; server->readBuffer = (uint8_t *)MEMAllocFromExpHeapEx(mem2, server->readMax, 0x100); fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (fd < 0) { WHBLogPrintf("Error creating socket: %d", socketlasterr()); return -1; } setsockopt(fd, SOL_SOCKET, SO_NBIO, NULL, 0); opt = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { socketclose(fd); WHBLogPrintf("Error binding socket: %d", socketlasterr()); return -1; } if (listen(fd, 3) < 0) { socketclose(fd); WHBLogPrintf("Error listening on socket: %d", socketlasterr()); return -1; } ACGetStartupId(&startupId); ACConnectWithConfigId(startupId); ACGetAssignedAddress(&ipAddress); ip_addr.s_addr = ipAddress; WHBLogPrintf("Started server on %s:%d", inet_ntoa(ip_addr), port); server->fd = fd; return 0; } int serverClose(Server *server) { MEMHeapHandle mem2 = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2); serverCloseClient(server); if (server->fd != -1) { socketclose(server->fd); server->fd = -1; } server->readMax = 0; MEMFreeToExpHeap(mem2, server->readBuffer); return 0; } void serverCloseClient(Server *server) { if (server->client != -1) { socketclose(server->client); server->client = -1; } } static int serverAccept(Server *server) { struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); int clientFd; while (true) { clientFd = accept(server->fd, (struct sockaddr *)&addr, &addrlen); if (clientFd < 0) { int error = socketlasterr(); if (error != NSN_EWOULDBLOCK) { WHBLogPrintf("Error, accept returned %d", clientFd); return error; } return 0; } if (server->client != -1) { WHBLogPrintf("Max clients reached, reject %s", inet_ntoa(addr.sin_addr)); socketclose(clientFd); return 0; } WHBLogPrintf("Accepted connection from %s", inet_ntoa(addr.sin_addr)); server->client = clientFd; } return 0; } int serverSendPacket(Server *server, PacketWriter *packet) { int sent = 0; while (sent < packet->pos) { int sendBytes = packet->pos - sent; int result = 0; if (sendBytes > SEND_MAX_BYTES) { sendBytes = SEND_MAX_BYTES; } result = send(server->client, (uint8_t *)packet->buffer + sent, sendBytes, 0); if (result > 0) { sent += sendBytes; } if (result != sendBytes) { int error = socketlasterr(); if (error == NSN_EWOULDBLOCK) { OSSleepTicks(OSMillisecondsToTicks(1)); continue; } WHBLogPrintf("send returned %d instead of %d, error: %d", result, sendBytes, error); break; } } return sent; } static int serverHandlePacket(Server *server, PacketHandlerFn fn) { PacketReader reader; reader.dataLength = *(uint32_t *)(server->readBuffer + 0) - 8; reader.command = *(uint32_t *)(server->readBuffer + 4); reader.data = server->readBuffer + 8; reader.pos = 0; return fn(server, &reader); } static int serverRead(Server *server, PacketHandlerFn fn) { while (server->client != -1) { int read; int length; if (server->readLength == 0) { length = 4; } else { length = server->readLength; } read = recv(server->client, server->readBuffer + server->readPos, length, 0); if (read < 0) { int error = socketlasterr(); if (error != NSN_EWOULDBLOCK) { WHBLogPrintf("Error receiving from socket: %d", error); serverCloseClient(server); return error; } return 0; } else if (read == 0) { WHBLogPrintf("Client disconnected gracefully."); serverCloseClient(server); return read; } server->readPos += read; if (server->readLength == 0 && server->readPos >= 2) { server->readLength = *(uint32_t *)server->readBuffer; } if (server->readPos >= server->readLength) { serverHandlePacket(server, fn); server->readPos = 0; server->readLength = 0; } } return 0; } int serverProcess(Server *server, PacketHandlerFn fn) { int error = serverAccept(server); if (error) { return error; } return serverRead(server, fn); } ================================================ FILE: tools/wiiu-rpc/src/server.h ================================================ #ifndef SERVER_H #define SERVER_H #include <wut_types.h> typedef struct Server { int fd; int client; int readPos; int readLength; int readMax; uint8_t *readBuffer; } Server; typedef struct PacketReader PacketReader; typedef struct PacketWriter PacketWriter; typedef int (*PacketHandlerFn)(Server *server, PacketReader *reader); int serverStart(Server *server, int port); int serverClose(Server *server); void serverCloseClient(Server *server); int serverProcess(Server *server, PacketHandlerFn fn); int serverSendPacket(Server *server, PacketWriter *packet); #endif // SERVER_H ================================================ FILE: vcpkg.json ================================================ { "name": "decaf-emu", "version-string": "0.2.0", "builtin-baseline": "06c79a9afa6f99f02f44d20df9e0848b2a56bf1b", "dependencies": [ { "name": "ffmpeg", "default-features": false, "features": ["avcodec", "avfilter", "swscale"] }, "c-ares", "curl", "libuv", "openssl", "sdl2", "zlib" ] }