First Commit
This commit is contained in:
21
src/core/3ds.h
Normal file
21
src/core/3ds.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
// 3DS Video Constants
|
||||
// -------------------
|
||||
|
||||
// NOTE: The LCDs actually rotate the image 90 degrees when displaying. Because of that the
|
||||
// framebuffers in video memory are stored in column-major order and rendered sideways, causing
|
||||
// the widths and heights of the framebuffers read by the LCD to be switched compared to the
|
||||
// heights and widths of the screens listed here.
|
||||
constexpr int kScreenTopWidth = 400; ///< 3DS top screen width
|
||||
constexpr int kScreenTopHeight = 240; ///< 3DS top screen height
|
||||
constexpr int kScreenBottomWidth = 320; ///< 3DS bottom screen width
|
||||
constexpr int kScreenBottomHeight = 240; ///< 3DS bottom screen height
|
||||
|
||||
} // namespace Core
|
||||
527
src/core/CMakeLists.txt
Normal file
527
src/core/CMakeLists.txt
Normal file
@@ -0,0 +1,527 @@
|
||||
add_library(citra_core STATIC
|
||||
3ds.h
|
||||
arm/arm_interface.h
|
||||
arm/dyncom/arm_dyncom.cpp
|
||||
arm/dyncom/arm_dyncom.h
|
||||
arm/dyncom/arm_dyncom_dec.cpp
|
||||
arm/dyncom/arm_dyncom_dec.h
|
||||
arm/dyncom/arm_dyncom_interpreter.cpp
|
||||
arm/dyncom/arm_dyncom_interpreter.h
|
||||
arm/dyncom/arm_dyncom_run.h
|
||||
arm/dyncom/arm_dyncom_thumb.cpp
|
||||
arm/dyncom/arm_dyncom_thumb.h
|
||||
arm/dyncom/arm_dyncom_trans.cpp
|
||||
arm/dyncom/arm_dyncom_trans.h
|
||||
arm/exclusive_monitor.cpp
|
||||
arm/exclusive_monitor.h
|
||||
arm/skyeye_common/arm_regformat.h
|
||||
arm/skyeye_common/armstate.cpp
|
||||
arm/skyeye_common/armstate.h
|
||||
arm/skyeye_common/armsupp.cpp
|
||||
arm/skyeye_common/armsupp.h
|
||||
arm/skyeye_common/vfp/asm_vfp.h
|
||||
arm/skyeye_common/vfp/vfp.cpp
|
||||
arm/skyeye_common/vfp/vfp.h
|
||||
arm/skyeye_common/vfp/vfp_helper.h
|
||||
arm/skyeye_common/vfp/vfpdouble.cpp
|
||||
arm/skyeye_common/vfp/vfpinstr.cpp
|
||||
arm/skyeye_common/vfp/vfpsingle.cpp
|
||||
cheats/cheat_base.cpp
|
||||
cheats/cheat_base.h
|
||||
cheats/cheats.cpp
|
||||
cheats/cheats.h
|
||||
cheats/gateway_cheat.cpp
|
||||
cheats/gateway_cheat.h
|
||||
core.cpp
|
||||
core.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
dumping/backend.cpp
|
||||
dumping/backend.h
|
||||
dumping/ffmpeg_backend.cpp
|
||||
dumping/ffmpeg_backend.h
|
||||
file_sys/archive_artic.cpp
|
||||
file_sys/archive_artic.h
|
||||
file_sys/archive_backend.cpp
|
||||
file_sys/archive_backend.h
|
||||
file_sys/archive_extsavedata.cpp
|
||||
file_sys/archive_extsavedata.h
|
||||
file_sys/archive_ncch.cpp
|
||||
file_sys/archive_ncch.h
|
||||
file_sys/archive_other_savedata.cpp
|
||||
file_sys/archive_other_savedata.h
|
||||
file_sys/archive_savedata.cpp
|
||||
file_sys/archive_savedata.h
|
||||
file_sys/archive_sdmc.cpp
|
||||
file_sys/archive_sdmc.h
|
||||
file_sys/archive_sdmcwriteonly.cpp
|
||||
file_sys/archive_sdmcwriteonly.h
|
||||
file_sys/archive_selfncch.cpp
|
||||
file_sys/archive_selfncch.h
|
||||
file_sys/archive_source_sd_savedata.cpp
|
||||
file_sys/archive_source_sd_savedata.h
|
||||
file_sys/archive_systemsavedata.cpp
|
||||
file_sys/archive_systemsavedata.h
|
||||
file_sys/artic_cache.cpp
|
||||
file_sys/artic_cache.h
|
||||
file_sys/cia_common.h
|
||||
file_sys/cia_container.cpp
|
||||
file_sys/cia_container.h
|
||||
file_sys/directory_backend.h
|
||||
file_sys/disk_archive.cpp
|
||||
file_sys/disk_archive.h
|
||||
file_sys/errors.h
|
||||
file_sys/file_backend.h
|
||||
file_sys/delay_generator.cpp
|
||||
file_sys/delay_generator.h
|
||||
file_sys/ivfc_archive.cpp
|
||||
file_sys/ivfc_archive.h
|
||||
file_sys/layered_fs.cpp
|
||||
file_sys/layered_fs.h
|
||||
file_sys/ncch_container.cpp
|
||||
file_sys/ncch_container.h
|
||||
file_sys/patch.cpp
|
||||
file_sys/patch.h
|
||||
file_sys/path_parser.cpp
|
||||
file_sys/path_parser.h
|
||||
file_sys/plugin_3gx.cpp
|
||||
file_sys/plugin_3gx.h
|
||||
file_sys/plugin_3gx_bootloader.h
|
||||
file_sys/romfs_reader.cpp
|
||||
file_sys/romfs_reader.h
|
||||
file_sys/savedata_archive.cpp
|
||||
file_sys/savedata_archive.h
|
||||
file_sys/secure_value_backend_artic.cpp
|
||||
file_sys/secure_value_backend_artic.h
|
||||
file_sys/secure_value_backend.cpp
|
||||
file_sys/secure_value_backend.h
|
||||
file_sys/seed_db.cpp
|
||||
file_sys/seed_db.h
|
||||
file_sys/ticket.cpp
|
||||
file_sys/ticket.h
|
||||
file_sys/title_metadata.cpp
|
||||
file_sys/title_metadata.h
|
||||
frontend/applets/default_applets.cpp
|
||||
frontend/applets/default_applets.h
|
||||
frontend/applets/mii_selector.cpp
|
||||
frontend/applets/mii_selector.h
|
||||
frontend/applets/swkbd.cpp
|
||||
frontend/applets/swkbd.h
|
||||
frontend/camera/blank_camera.cpp
|
||||
frontend/camera/blank_camera.h
|
||||
frontend/camera/factory.cpp
|
||||
frontend/camera/factory.h
|
||||
frontend/camera/interface.cpp
|
||||
frontend/camera/interface.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
frontend/framebuffer_layout.h
|
||||
frontend/image_interface.cpp
|
||||
frontend/image_interface.h
|
||||
frontend/input.h
|
||||
gdbstub/gdbstub.cpp
|
||||
gdbstub/gdbstub.h
|
||||
gdbstub/hio.cpp
|
||||
gdbstub/hio.h
|
||||
hle/applets/applet.cpp
|
||||
hle/applets/applet.h
|
||||
hle/applets/erreula.cpp
|
||||
hle/applets/erreula.h
|
||||
hle/applets/mii_selector.cpp
|
||||
hle/applets/mii_selector.h
|
||||
hle/applets/mint.cpp
|
||||
hle/applets/mint.h
|
||||
hle/applets/swkbd.cpp
|
||||
hle/applets/swkbd.h
|
||||
hle/ipc.h
|
||||
hle/ipc_helpers.h
|
||||
hle/kernel/address_arbiter.cpp
|
||||
hle/kernel/address_arbiter.h
|
||||
hle/kernel/client_port.cpp
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/config_mem.cpp
|
||||
hle/kernel/config_mem.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/event.cpp
|
||||
hle/kernel/event.h
|
||||
hle/kernel/handle_table.cpp
|
||||
hle/kernel/handle_table.h
|
||||
hle/kernel/hle_ipc.cpp
|
||||
hle/kernel/hle_ipc.h
|
||||
hle/kernel/ipc.cpp
|
||||
hle/kernel/ipc.h
|
||||
hle/kernel/ipc_debugger/recorder.cpp
|
||||
hle/kernel/ipc_debugger/recorder.h
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory.cpp
|
||||
hle/kernel/memory.h
|
||||
hle/kernel/mutex.cpp
|
||||
hle/kernel/mutex.h
|
||||
hle/kernel/object.cpp
|
||||
hle/kernel/object.h
|
||||
hle/kernel/process.cpp
|
||||
hle/kernel/process.h
|
||||
hle/kernel/resource_limit.cpp
|
||||
hle/kernel/resource_limit.h
|
||||
hle/kernel/semaphore.cpp
|
||||
hle/kernel/semaphore.h
|
||||
hle/kernel/server_port.cpp
|
||||
hle/kernel/server_port.h
|
||||
hle/kernel/server_session.cpp
|
||||
hle/kernel/server_session.h
|
||||
hle/kernel/session.h
|
||||
hle/kernel/session.cpp
|
||||
hle/kernel/shared_memory.cpp
|
||||
hle/kernel/shared_memory.h
|
||||
hle/kernel/shared_page.cpp
|
||||
hle/kernel/shared_page.h
|
||||
hle/kernel/svc.cpp
|
||||
hle/kernel/svc.h
|
||||
hle/kernel/svc_wrapper.h
|
||||
hle/kernel/thread.cpp
|
||||
hle/kernel/thread.h
|
||||
hle/kernel/timer.cpp
|
||||
hle/kernel/timer.h
|
||||
hle/kernel/vm_manager.cpp
|
||||
hle/kernel/vm_manager.h
|
||||
hle/kernel/wait_object.cpp
|
||||
hle/kernel/wait_object.h
|
||||
hle/mii.h
|
||||
hle/mii.cpp
|
||||
hle/result.h
|
||||
hle/romfs.cpp
|
||||
hle/romfs.h
|
||||
hle/service/ac/ac.cpp
|
||||
hle/service/ac/ac.h
|
||||
hle/service/ac/ac_i.cpp
|
||||
hle/service/ac/ac_i.h
|
||||
hle/service/ac/ac_u.cpp
|
||||
hle/service/ac/ac_u.h
|
||||
hle/service/act/act.cpp
|
||||
hle/service/act/act.h
|
||||
hle/service/act/act_errors.cpp
|
||||
hle/service/act/act_errors.h
|
||||
hle/service/act/act_a.cpp
|
||||
hle/service/act/act_a.h
|
||||
hle/service/act/act_u.cpp
|
||||
hle/service/act/act_u.h
|
||||
hle/service/am/am.cpp
|
||||
hle/service/am/am.h
|
||||
hle/service/am/am_app.cpp
|
||||
hle/service/am/am_app.h
|
||||
hle/service/am/am_net.cpp
|
||||
hle/service/am/am_net.h
|
||||
hle/service/am/am_sys.cpp
|
||||
hle/service/am/am_sys.h
|
||||
hle/service/am/am_u.cpp
|
||||
hle/service/am/am_u.h
|
||||
hle/service/apt/applet_manager.cpp
|
||||
hle/service/apt/applet_manager.h
|
||||
hle/service/apt/apt.cpp
|
||||
hle/service/apt/apt.h
|
||||
hle/service/apt/apt_a.cpp
|
||||
hle/service/apt/apt_a.h
|
||||
hle/service/apt/apt_s.cpp
|
||||
hle/service/apt/apt_s.h
|
||||
hle/service/apt/apt_u.cpp
|
||||
hle/service/apt/apt_u.h
|
||||
hle/service/apt/ns.cpp
|
||||
hle/service/apt/ns.h
|
||||
hle/service/apt/ns_c.cpp
|
||||
hle/service/apt/ns_c.h
|
||||
hle/service/apt/ns_s.cpp
|
||||
hle/service/apt/ns_s.h
|
||||
hle/service/apt/bcfnt/bcfnt.cpp
|
||||
hle/service/apt/bcfnt/bcfnt.h
|
||||
hle/service/apt/errors.h
|
||||
hle/service/boss/boss.cpp
|
||||
hle/service/boss/boss.h
|
||||
hle/service/boss/boss_p.cpp
|
||||
hle/service/boss/boss_p.h
|
||||
hle/service/boss/boss_u.cpp
|
||||
hle/service/boss/boss_u.h
|
||||
hle/service/boss/online_service.cpp
|
||||
hle/service/boss/online_service.h
|
||||
hle/service/cam/cam.cpp
|
||||
hle/service/cam/cam.h
|
||||
hle/service/cam/cam_c.cpp
|
||||
hle/service/cam/cam_c.h
|
||||
hle/service/cam/cam_params.h
|
||||
hle/service/cam/cam_q.cpp
|
||||
hle/service/cam/cam_q.h
|
||||
hle/service/cam/cam_s.cpp
|
||||
hle/service/cam/cam_s.h
|
||||
hle/service/cam/cam_u.cpp
|
||||
hle/service/cam/cam_u.h
|
||||
hle/service/cam/y2r_u.cpp
|
||||
hle/service/cam/y2r_u.h
|
||||
hle/service/cecd/cecd.cpp
|
||||
hle/service/cecd/cecd.h
|
||||
hle/service/cecd/cecd_ndm.cpp
|
||||
hle/service/cecd/cecd_ndm.h
|
||||
hle/service/cecd/cecd_s.cpp
|
||||
hle/service/cecd/cecd_s.h
|
||||
hle/service/cecd/cecd_u.cpp
|
||||
hle/service/cecd/cecd_u.h
|
||||
hle/service/cfg/cfg.cpp
|
||||
hle/service/cfg/cfg.h
|
||||
hle/service/cfg/cfg_defaults.cpp
|
||||
hle/service/cfg/cfg_defaults.h
|
||||
hle/service/cfg/cfg_i.cpp
|
||||
hle/service/cfg/cfg_i.h
|
||||
hle/service/cfg/cfg_nor.cpp
|
||||
hle/service/cfg/cfg_nor.h
|
||||
hle/service/cfg/cfg_s.cpp
|
||||
hle/service/cfg/cfg_s.h
|
||||
hle/service/cfg/cfg_u.cpp
|
||||
hle/service/cfg/cfg_u.h
|
||||
hle/service/csnd/csnd_snd.cpp
|
||||
hle/service/csnd/csnd_snd.h
|
||||
hle/service/dlp/dlp.cpp
|
||||
hle/service/dlp/dlp.h
|
||||
hle/service/dlp/dlp_clnt.cpp
|
||||
hle/service/dlp/dlp_clnt.h
|
||||
hle/service/dlp/dlp_fkcl.cpp
|
||||
hle/service/dlp/dlp_fkcl.h
|
||||
hle/service/dlp/dlp_srvr.cpp
|
||||
hle/service/dlp/dlp_srvr.h
|
||||
hle/service/dsp/dsp_dsp.cpp
|
||||
hle/service/dsp/dsp_dsp.h
|
||||
hle/service/err/err_f.cpp
|
||||
hle/service/err/err_f.h
|
||||
hle/service/frd/frd.cpp
|
||||
hle/service/frd/frd.h
|
||||
hle/service/frd/frd_a.cpp
|
||||
hle/service/frd/frd_a.h
|
||||
hle/service/frd/frd_u.cpp
|
||||
hle/service/frd/frd_u.h
|
||||
hle/service/fs/archive.cpp
|
||||
hle/service/fs/archive.h
|
||||
hle/service/fs/directory.cpp
|
||||
hle/service/fs/directory.h
|
||||
hle/service/fs/file.cpp
|
||||
hle/service/fs/file.h
|
||||
hle/service/fs/fs_user.cpp
|
||||
hle/service/fs/fs_user.h
|
||||
hle/service/gsp/gsp.cpp
|
||||
hle/service/gsp/gsp.h
|
||||
hle/service/gsp/gsp_command.h
|
||||
hle/service/gsp/gsp_gpu.cpp
|
||||
hle/service/gsp/gsp_gpu.h
|
||||
hle/service/gsp/gsp_interrupt.h
|
||||
hle/service/gsp/gsp_lcd.cpp
|
||||
hle/service/gsp/gsp_lcd.h
|
||||
hle/service/hid/hid.cpp
|
||||
hle/service/hid/hid.h
|
||||
hle/service/hid/hid_spvr.cpp
|
||||
hle/service/hid/hid_spvr.h
|
||||
hle/service/hid/hid_user.cpp
|
||||
hle/service/hid/hid_user.h
|
||||
hle/service/http/http_c.cpp
|
||||
hle/service/http/http_c.h
|
||||
hle/service/ir/extra_hid.cpp
|
||||
hle/service/ir/extra_hid.h
|
||||
hle/service/ir/ir.cpp
|
||||
hle/service/ir/ir.h
|
||||
hle/service/ir/ir_rst.cpp
|
||||
hle/service/ir/ir_rst.h
|
||||
hle/service/ir/ir_u.cpp
|
||||
hle/service/ir/ir_u.h
|
||||
hle/service/ir/ir_user.cpp
|
||||
hle/service/ir/ir_user.h
|
||||
hle/service/ldr_ro/cro_helper.cpp
|
||||
hle/service/ldr_ro/cro_helper.h
|
||||
hle/service/ldr_ro/ldr_ro.cpp
|
||||
hle/service/ldr_ro/ldr_ro.h
|
||||
hle/service/mcu/mcu_hwc.cpp
|
||||
hle/service/mcu/mcu_hwc.h
|
||||
hle/service/mcu/mcu.cpp
|
||||
hle/service/mcu/mcu.h
|
||||
hle/service/mic/mic_u.cpp
|
||||
hle/service/mic/mic_u.h
|
||||
hle/service/mvd/mvd.cpp
|
||||
hle/service/mvd/mvd.h
|
||||
hle/service/mvd/mvd_std.cpp
|
||||
hle/service/mvd/mvd_std.h
|
||||
hle/service/ndm/ndm_u.cpp
|
||||
hle/service/ndm/ndm_u.h
|
||||
hle/service/news/news.cpp
|
||||
hle/service/news/news.h
|
||||
hle/service/news/news_s.cpp
|
||||
hle/service/news/news_s.h
|
||||
hle/service/news/news_u.cpp
|
||||
hle/service/news/news_u.h
|
||||
hle/service/nfc/amiibo_crypto.cpp
|
||||
hle/service/nfc/amiibo_crypto.h
|
||||
hle/service/nfc/nfc.cpp
|
||||
hle/service/nfc/nfc.h
|
||||
hle/service/nfc/nfc_device.cpp
|
||||
hle/service/nfc/nfc_device.h
|
||||
hle/service/nfc/nfc_m.cpp
|
||||
hle/service/nfc/nfc_m.h
|
||||
hle/service/nfc/nfc_results.h
|
||||
hle/service/nfc/nfc_types.h
|
||||
hle/service/nfc/nfc_u.cpp
|
||||
hle/service/nfc/nfc_u.h
|
||||
hle/service/nim/nim.cpp
|
||||
hle/service/nim/nim.h
|
||||
hle/service/nim/nim_aoc.cpp
|
||||
hle/service/nim/nim_aoc.h
|
||||
hle/service/nim/nim_s.cpp
|
||||
hle/service/nim/nim_s.h
|
||||
hle/service/nim/nim_u.cpp
|
||||
hle/service/nim/nim_u.h
|
||||
hle/service/nwm/nwm.cpp
|
||||
hle/service/nwm/nwm.h
|
||||
hle/service/nwm/nwm_cec.cpp
|
||||
hle/service/nwm/nwm_cec.h
|
||||
hle/service/nwm/nwm_ext.cpp
|
||||
hle/service/nwm/nwm_ext.h
|
||||
hle/service/nwm/nwm_inf.cpp
|
||||
hle/service/nwm/nwm_inf.h
|
||||
hle/service/nwm/nwm_sap.cpp
|
||||
hle/service/nwm/nwm_sap.h
|
||||
hle/service/nwm/nwm_soc.cpp
|
||||
hle/service/nwm/nwm_soc.h
|
||||
hle/service/nwm/nwm_tst.cpp
|
||||
hle/service/nwm/nwm_tst.h
|
||||
hle/service/nwm/nwm_uds.cpp
|
||||
hle/service/nwm/nwm_uds.h
|
||||
hle/service/nwm/uds_beacon.cpp
|
||||
hle/service/nwm/uds_beacon.h
|
||||
hle/service/nwm/uds_connection.cpp
|
||||
hle/service/nwm/uds_connection.h
|
||||
hle/service/nwm/uds_data.cpp
|
||||
hle/service/nwm/uds_data.h
|
||||
hle/service/plgldr/plgldr.cpp
|
||||
hle/service/plgldr/plgldr.h
|
||||
hle/service/pm/pm.cpp
|
||||
hle/service/pm/pm.h
|
||||
hle/service/pm/pm_app.cpp
|
||||
hle/service/pm/pm_app.h
|
||||
hle/service/pm/pm_dbg.cpp
|
||||
hle/service/pm/pm_dbg.h
|
||||
hle/service/ps/ps_ps.cpp
|
||||
hle/service/ps/ps_ps.h
|
||||
hle/service/ptm/ptm.cpp
|
||||
hle/service/ptm/ptm.h
|
||||
hle/service/ptm/ptm_gets.cpp
|
||||
hle/service/ptm/ptm_gets.h
|
||||
hle/service/ptm/ptm_play.cpp
|
||||
hle/service/ptm/ptm_play.h
|
||||
hle/service/ptm/ptm_sets.cpp
|
||||
hle/service/ptm/ptm_sets.h
|
||||
hle/service/ptm/ptm_sysm.cpp
|
||||
hle/service/ptm/ptm_sysm.h
|
||||
hle/service/ptm/ptm_u.cpp
|
||||
hle/service/ptm/ptm_u.h
|
||||
hle/service/pxi/dev.cpp
|
||||
hle/service/pxi/dev.h
|
||||
hle/service/pxi/pxi.cpp
|
||||
hle/service/pxi/pxi.h
|
||||
hle/service/qtm/qtm.cpp
|
||||
hle/service/qtm/qtm.h
|
||||
hle/service/qtm/qtm_c.cpp
|
||||
hle/service/qtm/qtm_c.h
|
||||
hle/service/qtm/qtm_s.cpp
|
||||
hle/service/qtm/qtm_s.h
|
||||
hle/service/qtm/qtm_sp.cpp
|
||||
hle/service/qtm/qtm_sp.h
|
||||
hle/service/qtm/qtm_u.cpp
|
||||
hle/service/qtm/qtm_u.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/sm/sm.cpp
|
||||
hle/service/sm/sm.h
|
||||
hle/service/sm/srv.cpp
|
||||
hle/service/sm/srv.h
|
||||
hle/service/soc/soc_u.cpp
|
||||
hle/service/soc/soc_u.h
|
||||
hle/service/ssl/ssl_c.cpp
|
||||
hle/service/ssl/ssl_c.h
|
||||
hw/aes/arithmetic128.cpp
|
||||
hw/aes/arithmetic128.h
|
||||
hw/aes/ccm.cpp
|
||||
hw/aes/ccm.h
|
||||
hw/aes/key.cpp
|
||||
hw/aes/key.h
|
||||
hw/rsa/rsa.cpp
|
||||
hw/rsa/rsa.h
|
||||
hw/y2r.cpp
|
||||
hw/y2r.h
|
||||
loader/3dsx.cpp
|
||||
loader/3dsx.h
|
||||
loader/artic.cpp
|
||||
loader/artic.h
|
||||
loader/elf.cpp
|
||||
loader/elf.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/ncch.cpp
|
||||
loader/ncch.h
|
||||
loader/smdh.cpp
|
||||
loader/smdh.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
movie.cpp
|
||||
movie.h
|
||||
nus_download.cpp
|
||||
nus_download.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
precompiled_headers.h
|
||||
savestate.cpp
|
||||
savestate.h
|
||||
savestate_data.h
|
||||
system_titles.cpp
|
||||
system_titles.h
|
||||
tracer/citrace.h
|
||||
tracer/recorder.cpp
|
||||
tracer/recorder.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(citra_core)
|
||||
|
||||
target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core)
|
||||
target_link_libraries(citra_core PRIVATE Boost::boost Boost::serialization Boost::iostreams httplib)
|
||||
target_link_libraries(citra_core PUBLIC dds-ktx PRIVATE cryptopp fmt lodepng open_source_archives)
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_link_libraries(citra_core PRIVATE web_service)
|
||||
endif()
|
||||
|
||||
if (ENABLE_SCRIPTING)
|
||||
target_compile_definitions(citra_core PUBLIC -DENABLE_SCRIPTING)
|
||||
target_sources(citra_core PRIVATE
|
||||
rpc/packet.cpp
|
||||
rpc/packet.h
|
||||
rpc/rpc_server.cpp
|
||||
rpc/rpc_server.h
|
||||
rpc/server.cpp
|
||||
rpc/server.h
|
||||
rpc/udp_server.cpp
|
||||
rpc/udp_server.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
||||
target_sources(citra_core PRIVATE
|
||||
arm/dynarmic/arm_dynarmic.cpp
|
||||
arm/dynarmic/arm_dynarmic.h
|
||||
arm/dynarmic/arm_dynarmic_cp15.cpp
|
||||
arm/dynarmic/arm_dynarmic_cp15.h
|
||||
arm/dynarmic/arm_exclusive_monitor.cpp
|
||||
arm/dynarmic/arm_exclusive_monitor.h
|
||||
arm/dynarmic/arm_tick_counts.cpp
|
||||
arm/dynarmic/arm_tick_counts.h
|
||||
)
|
||||
target_link_libraries(citra_core PRIVATE dynarmic)
|
||||
endif()
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(citra_core PRIVATE precompiled_headers.h)
|
||||
endif()
|
||||
307
src/core/arm/arm_interface.h
Normal file
307
src/core/arm/arm_interface.h
Normal file
@@ -0,0 +1,307 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include <boost/serialization/split_member.hpp>
|
||||
#include <boost/serialization/version.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/skyeye_common/arm_regformat.h"
|
||||
#include "core/arm/skyeye_common/vfp/asm_vfp.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Memory {
|
||||
struct PageTable;
|
||||
};
|
||||
|
||||
namespace Core {
|
||||
|
||||
/// Generic ARM11 CPU interface
|
||||
class ARM_Interface : NonCopyable {
|
||||
public:
|
||||
explicit ARM_Interface(u32 id, std::shared_ptr<Core::Timing::Timer> timer)
|
||||
: timer(timer), id(id){};
|
||||
virtual ~ARM_Interface() {}
|
||||
|
||||
struct ThreadContext {
|
||||
u32 GetStackPointer() const {
|
||||
return cpu_registers[13];
|
||||
}
|
||||
void SetStackPointer(u32 value) {
|
||||
cpu_registers[13] = value;
|
||||
}
|
||||
|
||||
u32 GetLinkRegister() const {
|
||||
return cpu_registers[14];
|
||||
}
|
||||
void SetLinkRegister(u32 value) {
|
||||
cpu_registers[14] = value;
|
||||
}
|
||||
|
||||
u32 GetProgramCounter() const {
|
||||
return cpu_registers[15];
|
||||
}
|
||||
void SetProgramCounter(u32 value) {
|
||||
cpu_registers[15] = value;
|
||||
}
|
||||
|
||||
std::array<u32, 16> cpu_registers{};
|
||||
u32 cpsr{};
|
||||
std::array<u32, 64> fpu_registers{};
|
||||
u32 fpscr{};
|
||||
u32 fpexc{};
|
||||
|
||||
private:
|
||||
friend class boost::serialization::access;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int file_version) {
|
||||
ar& cpu_registers;
|
||||
ar& fpu_registers;
|
||||
ar& cpsr;
|
||||
ar& fpscr;
|
||||
ar& fpexc;
|
||||
}
|
||||
};
|
||||
|
||||
/// Runs the CPU until an event happens
|
||||
virtual void Run() = 0;
|
||||
|
||||
/// Step CPU by one instruction
|
||||
virtual void Step() = 0;
|
||||
|
||||
/// Clear all instruction cache
|
||||
virtual void ClearInstructionCache() = 0;
|
||||
|
||||
/**
|
||||
* Invalidate the code cache at a range of addresses.
|
||||
* @param start_address The starting address of the range to invalidate.
|
||||
* @param length The length (in bytes) of the range to invalidate.
|
||||
*/
|
||||
virtual void InvalidateCacheRange(u32 start_address, std::size_t length) = 0;
|
||||
|
||||
/// Clears the exclusive monitor's state.
|
||||
virtual void ClearExclusiveState() = 0;
|
||||
|
||||
/// Notify CPU emulation that page tables have changed
|
||||
virtual void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) = 0;
|
||||
|
||||
/**
|
||||
* Set the Program Counter to an address
|
||||
* @param addr Address to set PC to
|
||||
*/
|
||||
virtual void SetPC(u32 addr) = 0;
|
||||
|
||||
/**
|
||||
* Get the current Program Counter
|
||||
* @return Returns current PC
|
||||
*/
|
||||
virtual u32 GetPC() const = 0;
|
||||
|
||||
/**
|
||||
* Get an ARM register
|
||||
* @param index Register index (0-15)
|
||||
* @return Returns the value in the register
|
||||
*/
|
||||
virtual u32 GetReg(int index) const = 0;
|
||||
|
||||
/**
|
||||
* Set an ARM register
|
||||
* @param index Register index (0-15)
|
||||
* @param value Value to set register to
|
||||
*/
|
||||
virtual void SetReg(int index, u32 value) = 0;
|
||||
|
||||
/**
|
||||
* Gets the value of a VFP register
|
||||
* @param index Register index (0-31)
|
||||
* @return Returns the value in the register
|
||||
*/
|
||||
virtual u32 GetVFPReg(int index) const = 0;
|
||||
|
||||
/**
|
||||
* Sets a VFP register to the given value
|
||||
* @param index Register index (0-31)
|
||||
* @param value Value to set register to
|
||||
*/
|
||||
virtual void SetVFPReg(int index, u32 value) = 0;
|
||||
|
||||
/**
|
||||
* Gets the current value within a given VFP system register
|
||||
* @param reg The VFP system register
|
||||
* @return The value within the VFP system register
|
||||
*/
|
||||
virtual u32 GetVFPSystemReg(VFPSystemRegister reg) const = 0;
|
||||
|
||||
/**
|
||||
* Sets the VFP system register to the given value
|
||||
* @param reg The VFP system register
|
||||
* @param value Value to set the VFP system register to
|
||||
*/
|
||||
virtual void SetVFPSystemReg(VFPSystemRegister reg, u32 value) = 0;
|
||||
|
||||
/**
|
||||
* Get the current CPSR register
|
||||
* @return Returns the value of the CPSR register
|
||||
*/
|
||||
virtual u32 GetCPSR() const = 0;
|
||||
|
||||
/**
|
||||
* Set the current CPSR register
|
||||
* @param cpsr Value to set CPSR to
|
||||
*/
|
||||
virtual void SetCPSR(u32 cpsr) = 0;
|
||||
|
||||
/**
|
||||
* Gets the value stored in a CP15 register.
|
||||
* @param reg The CP15 register to retrieve the value from.
|
||||
* @return the value stored in the given CP15 register.
|
||||
*/
|
||||
virtual u32 GetCP15Register(CP15Register reg) const = 0;
|
||||
|
||||
/**
|
||||
* Stores the given value into the indicated CP15 register.
|
||||
* @param reg The CP15 register to store the value into.
|
||||
* @param value The value to store into the CP15 register.
|
||||
*/
|
||||
virtual void SetCP15Register(CP15Register reg, u32 value) = 0;
|
||||
|
||||
/**
|
||||
* Saves the current CPU context
|
||||
* @param ctx Thread context to save
|
||||
*/
|
||||
virtual void SaveContext(ThreadContext& ctx) = 0;
|
||||
|
||||
/**
|
||||
* Loads a CPU context
|
||||
* @param ctx Thread context to load
|
||||
*/
|
||||
virtual void LoadContext(const ThreadContext& ctx) = 0;
|
||||
|
||||
/// Prepare core for thread reschedule (if needed to correctly handle state)
|
||||
virtual void PrepareReschedule() = 0;
|
||||
|
||||
Core::Timing::Timer& GetTimer() {
|
||||
return *timer;
|
||||
}
|
||||
|
||||
const Core::Timing::Timer& GetTimer() const {
|
||||
return *timer;
|
||||
}
|
||||
|
||||
u32 GetID() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
protected:
|
||||
// This us used for serialization. Returning nullptr is valid if page tables are not used.
|
||||
virtual std::shared_ptr<Memory::PageTable> GetPageTable() const = 0;
|
||||
|
||||
std::shared_ptr<Core::Timing::Timer> timer;
|
||||
|
||||
private:
|
||||
u32 id;
|
||||
|
||||
friend class boost::serialization::access;
|
||||
|
||||
template <class Archive>
|
||||
void save(Archive& ar, const unsigned int file_version) const {
|
||||
ar << timer;
|
||||
ar << id;
|
||||
const auto page_table = GetPageTable();
|
||||
ar << page_table;
|
||||
for (int i = 0; i < 15; i++) {
|
||||
const auto r = GetReg(i);
|
||||
ar << r;
|
||||
}
|
||||
const auto pc = GetPC();
|
||||
ar << pc;
|
||||
const auto cpsr = GetCPSR();
|
||||
ar << cpsr;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
const auto r = GetVFPReg(i);
|
||||
ar << r;
|
||||
}
|
||||
for (std::size_t i = 0; i < VFPSystemRegister::VFP_SYSTEM_REGISTER_COUNT; i++) {
|
||||
const auto reg = static_cast<VFPSystemRegister>(i);
|
||||
u32 r = 0;
|
||||
switch (reg) {
|
||||
case VFP_FPSCR:
|
||||
case VFP_FPEXC:
|
||||
r = GetVFPSystemReg(reg);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ar << r;
|
||||
}
|
||||
for (std::size_t i = 0; i < CP15Register::CP15_REGISTER_COUNT; i++) {
|
||||
const auto reg = static_cast<CP15Register>(i);
|
||||
u32 r = 0;
|
||||
switch (reg) {
|
||||
case CP15_THREAD_UPRW:
|
||||
case CP15_THREAD_URO:
|
||||
r = GetCP15Register(reg);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ar << r;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void load(Archive& ar, const unsigned int file_version) {
|
||||
ClearInstructionCache();
|
||||
ar >> timer;
|
||||
ar >> id;
|
||||
std::shared_ptr<Memory::PageTable> page_table{};
|
||||
ar >> page_table;
|
||||
SetPageTable(page_table);
|
||||
u32 r;
|
||||
for (int i = 0; i < 15; i++) {
|
||||
ar >> r;
|
||||
SetReg(i, r);
|
||||
}
|
||||
ar >> r;
|
||||
SetPC(r);
|
||||
ar >> r;
|
||||
SetCPSR(r);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
ar >> r;
|
||||
SetVFPReg(i, r);
|
||||
}
|
||||
for (std::size_t i = 0; i < VFPSystemRegister::VFP_SYSTEM_REGISTER_COUNT; i++) {
|
||||
ar >> r;
|
||||
const auto reg = static_cast<VFPSystemRegister>(i);
|
||||
switch (reg) {
|
||||
case VFP_FPSCR:
|
||||
case VFP_FPEXC:
|
||||
SetVFPSystemReg(reg, r);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < CP15Register::CP15_REGISTER_COUNT; i++) {
|
||||
ar >> r;
|
||||
const auto reg = static_cast<CP15Register>(i);
|
||||
switch (reg) {
|
||||
case CP15_THREAD_UPRW:
|
||||
case CP15_THREAD_URO:
|
||||
SetCP15Register(reg, r);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
BOOST_CLASS_VERSION(Core::ARM_Interface, 1)
|
||||
BOOST_CLASS_VERSION(Core::ARM_Interface::ThreadContext, 1)
|
||||
317
src/core/arm/dynarmic/arm_dynarmic.cpp
Normal file
317
src/core/arm/dynarmic/arm_dynarmic.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <dynarmic/interface/A32/a32.h>
|
||||
#include <dynarmic/interface/optimization_flags.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#include "core/arm/dynarmic/arm_tick_counts.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
|
||||
public:
|
||||
explicit DynarmicUserCallbacks(ARM_Dynarmic& parent)
|
||||
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
|
||||
~DynarmicUserCallbacks() = default;
|
||||
|
||||
std::uint8_t MemoryRead8(VAddr vaddr) override {
|
||||
return memory.Read8(vaddr);
|
||||
}
|
||||
std::uint16_t MemoryRead16(VAddr vaddr) override {
|
||||
return memory.Read16(vaddr);
|
||||
}
|
||||
std::uint32_t MemoryRead32(VAddr vaddr) override {
|
||||
return memory.Read32(vaddr);
|
||||
}
|
||||
std::uint64_t MemoryRead64(VAddr vaddr) override {
|
||||
return memory.Read64(vaddr);
|
||||
}
|
||||
|
||||
void MemoryWrite8(VAddr vaddr, std::uint8_t value) override {
|
||||
memory.Write8(vaddr, value);
|
||||
}
|
||||
void MemoryWrite16(VAddr vaddr, std::uint16_t value) override {
|
||||
memory.Write16(vaddr, value);
|
||||
}
|
||||
void MemoryWrite32(VAddr vaddr, std::uint32_t value) override {
|
||||
memory.Write32(vaddr, value);
|
||||
}
|
||||
void MemoryWrite64(VAddr vaddr, std::uint64_t value) override {
|
||||
memory.Write64(vaddr, value);
|
||||
}
|
||||
|
||||
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
|
||||
return memory.WriteExclusive8(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
|
||||
return memory.WriteExclusive16(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
|
||||
return memory.WriteExclusive32(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
|
||||
return memory.WriteExclusive64(vaddr, value, expected);
|
||||
}
|
||||
|
||||
void InterpreterFallback(VAddr pc, std::size_t num_instructions) override {
|
||||
// Should never happen.
|
||||
UNREACHABLE_MSG("InterpeterFallback reached with pc = 0x{:08x}, code = 0x{:08x}, num = {}",
|
||||
pc, MemoryReadCode(pc).value(), num_instructions);
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
svc_context.CallSVC(swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(VAddr pc, Dynarmic::A32::Exception exception) override {
|
||||
switch (exception) {
|
||||
case Dynarmic::A32::Exception::UndefinedInstruction:
|
||||
case Dynarmic::A32::Exception::UnpredictableInstruction:
|
||||
case Dynarmic::A32::Exception::DecodeError:
|
||||
case Dynarmic::A32::Exception::NoExecuteFault:
|
||||
break;
|
||||
case Dynarmic::A32::Exception::Breakpoint:
|
||||
if (GDBStub::IsConnected()) {
|
||||
parent.jit->HaltExecution();
|
||||
parent.SetPC(pc);
|
||||
parent.ServeBreak();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Dynarmic::A32::Exception::SendEvent:
|
||||
case Dynarmic::A32::Exception::SendEventLocal:
|
||||
case Dynarmic::A32::Exception::WaitForInterrupt:
|
||||
case Dynarmic::A32::Exception::WaitForEvent:
|
||||
case Dynarmic::A32::Exception::Yield:
|
||||
case Dynarmic::A32::Exception::PreloadData:
|
||||
case Dynarmic::A32::Exception::PreloadDataWithIntentToWrite:
|
||||
case Dynarmic::A32::Exception::PreloadInstruction:
|
||||
return;
|
||||
}
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", exception,
|
||||
pc, MemoryReadCode(pc).value());
|
||||
}
|
||||
|
||||
void AddTicks(std::uint64_t ticks) override {
|
||||
parent.GetTimer().AddTicks(ticks);
|
||||
}
|
||||
std::uint64_t GetTicksRemaining() override {
|
||||
s64 ticks = parent.GetTimer().GetDowncount();
|
||||
return static_cast<u64>(ticks <= 0 ? 0 : ticks);
|
||||
}
|
||||
std::uint64_t GetTicksForCode(bool is_thumb, VAddr, std::uint32_t instruction) override {
|
||||
return Core::TicksForInstruction(is_thumb, instruction);
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
Kernel::SVCContext svc_context;
|
||||
Memory::MemorySystem& memory;
|
||||
};
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(Core::System& system_, Memory::MemorySystem& memory_, u32 core_id_,
|
||||
std::shared_ptr<Core::Timing::Timer> timer_,
|
||||
Core::ExclusiveMonitor& exclusive_monitor_)
|
||||
: ARM_Interface(core_id_, timer_), system(system_), memory(memory_),
|
||||
cb(std::make_unique<DynarmicUserCallbacks>(*this)),
|
||||
exclusive_monitor{dynamic_cast<Core::DynarmicExclusiveMonitor&>(exclusive_monitor_)} {
|
||||
SetPageTable(memory.GetCurrentPageTable());
|
||||
}
|
||||
|
||||
ARM_Dynarmic::~ARM_Dynarmic() = default;
|
||||
|
||||
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
|
||||
|
||||
void ARM_Dynarmic::Run() {
|
||||
ASSERT(memory.GetCurrentPageTable() == current_page_table);
|
||||
MICROPROFILE_SCOPE(ARM_Jit);
|
||||
|
||||
jit->Run();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::Step() {
|
||||
jit->Step();
|
||||
|
||||
if (GDBStub::IsConnected()) {
|
||||
ServeBreak();
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetPC(u32 pc) {
|
||||
jit->Regs()[15] = pc;
|
||||
}
|
||||
|
||||
u32 ARM_Dynarmic::GetPC() const {
|
||||
return jit->Regs()[15];
|
||||
}
|
||||
|
||||
u32 ARM_Dynarmic::GetReg(int index) const {
|
||||
return jit->Regs()[index];
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetReg(int index, u32 value) {
|
||||
jit->Regs()[index] = value;
|
||||
}
|
||||
|
||||
u32 ARM_Dynarmic::GetVFPReg(int index) const {
|
||||
return jit->ExtRegs()[index];
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetVFPReg(int index, u32 value) {
|
||||
jit->ExtRegs()[index] = value;
|
||||
}
|
||||
|
||||
u32 ARM_Dynarmic::GetVFPSystemReg(VFPSystemRegister reg) const {
|
||||
switch (reg) {
|
||||
case VFP_FPSCR:
|
||||
return jit->Fpscr();
|
||||
case VFP_FPEXC:
|
||||
return fpexc;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown VFP system register: {}", reg);
|
||||
}
|
||||
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
|
||||
switch (reg) {
|
||||
case VFP_FPSCR:
|
||||
jit->SetFpscr(value);
|
||||
return;
|
||||
case VFP_FPEXC:
|
||||
fpexc = value;
|
||||
return;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown VFP system register: {}", reg);
|
||||
}
|
||||
}
|
||||
|
||||
u32 ARM_Dynarmic::GetCPSR() const {
|
||||
return jit->Cpsr();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetCPSR(u32 cpsr) {
|
||||
jit->SetCpsr(cpsr);
|
||||
}
|
||||
|
||||
u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) const {
|
||||
switch (reg) {
|
||||
case CP15_THREAD_UPRW:
|
||||
return cp15_state.cp15_thread_uprw;
|
||||
case CP15_THREAD_URO:
|
||||
return cp15_state.cp15_thread_uro;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown CP15 register: {}", reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
|
||||
switch (reg) {
|
||||
case CP15_THREAD_UPRW:
|
||||
cp15_state.cp15_thread_uprw = value;
|
||||
return;
|
||||
case CP15_THREAD_URO:
|
||||
cp15_state.cp15_thread_uro = value;
|
||||
return;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown CP15 register: {}", reg);
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SaveContext(ThreadContext& ctx) {
|
||||
ctx.cpu_registers = jit->Regs();
|
||||
ctx.cpsr = jit->Cpsr();
|
||||
ctx.fpu_registers = jit->ExtRegs();
|
||||
ctx.fpscr = jit->Fpscr();
|
||||
ctx.fpexc = fpexc;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
|
||||
jit->Regs() = ctx.cpu_registers;
|
||||
jit->SetCpsr(ctx.cpsr);
|
||||
jit->ExtRegs() = ctx.fpu_registers;
|
||||
jit->SetFpscr(ctx.fpscr);
|
||||
fpexc = ctx.fpexc;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PrepareReschedule() {
|
||||
if (jit->IsExecuting()) {
|
||||
jit->HaltExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ClearInstructionCache() {
|
||||
for (const auto& j : jits) {
|
||||
j.second->ClearCache();
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::InvalidateCacheRange(u32 start_address, std::size_t length) {
|
||||
jit->InvalidateCacheRange(start_address, length);
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ClearExclusiveState() {
|
||||
jit->ClearExclusiveState();
|
||||
}
|
||||
|
||||
std::shared_ptr<Memory::PageTable> ARM_Dynarmic::GetPageTable() const {
|
||||
return current_page_table;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) {
|
||||
current_page_table = page_table;
|
||||
ThreadContext ctx{};
|
||||
if (jit) {
|
||||
SaveContext(ctx);
|
||||
}
|
||||
|
||||
auto iter = jits.find(current_page_table);
|
||||
if (iter != jits.end()) {
|
||||
jit = iter->second.get();
|
||||
LoadContext(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
auto new_jit = MakeJit();
|
||||
jit = new_jit.get();
|
||||
LoadContext(ctx);
|
||||
jits.emplace(current_page_table, std::move(new_jit));
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::ServeBreak() {
|
||||
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
|
||||
std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {
|
||||
Dynarmic::A32::UserConfig config;
|
||||
config.callbacks = cb.get();
|
||||
if (current_page_table) {
|
||||
config.page_table = ¤t_page_table->GetPointerArray();
|
||||
}
|
||||
config.coprocessors[15] = std::make_shared<DynarmicCP15>(cp15_state);
|
||||
config.define_unpredictable_behaviour = true;
|
||||
|
||||
// Multi-process state
|
||||
config.processor_id = GetID();
|
||||
config.global_monitor = &exclusive_monitor.monitor;
|
||||
|
||||
return std::make_unique<Dynarmic::A32::Jit>(config);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
80
src/core/arm/dynarmic/arm_dynarmic.h
Normal file
80
src/core/arm/dynarmic/arm_dynarmic.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <dynarmic/interface/A32/a32.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
|
||||
|
||||
namespace Memory {
|
||||
struct PageTable;
|
||||
class MemorySystem;
|
||||
} // namespace Memory
|
||||
|
||||
namespace Core {
|
||||
|
||||
class DynarmicUserCallbacks;
|
||||
class DynarmicExclusiveMonitor;
|
||||
class ExclusiveMonitor;
|
||||
class System;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
explicit ARM_Dynarmic(Core::System& system_, Memory::MemorySystem& memory_, u32 core_id_,
|
||||
std::shared_ptr<Core::Timing::Timer> timer,
|
||||
Core::ExclusiveMonitor& exclusive_monitor_);
|
||||
~ARM_Dynarmic() override;
|
||||
|
||||
void Run() override;
|
||||
void Step() override;
|
||||
|
||||
void SetPC(u32 pc) override;
|
||||
u32 GetPC() const override;
|
||||
u32 GetReg(int index) const override;
|
||||
void SetReg(int index, u32 value) override;
|
||||
u32 GetVFPReg(int index) const override;
|
||||
void SetVFPReg(int index, u32 value) override;
|
||||
u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
|
||||
void SetVFPSystemReg(VFPSystemRegister reg, u32 value) override;
|
||||
u32 GetCPSR() const override;
|
||||
void SetCPSR(u32 cpsr) override;
|
||||
u32 GetCP15Register(CP15Register reg) const override;
|
||||
void SetCP15Register(CP15Register reg, u32 value) override;
|
||||
|
||||
void SaveContext(ThreadContext& ctx) override;
|
||||
void LoadContext(const ThreadContext& ctx) override;
|
||||
|
||||
void PrepareReschedule() override;
|
||||
|
||||
void ClearInstructionCache() override;
|
||||
void InvalidateCacheRange(u32 start_address, std::size_t length) override;
|
||||
void ClearExclusiveState() override;
|
||||
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
||||
|
||||
private:
|
||||
void ServeBreak();
|
||||
|
||||
friend class DynarmicUserCallbacks;
|
||||
Core::System& system;
|
||||
Memory::MemorySystem& memory;
|
||||
std::unique_ptr<DynarmicUserCallbacks> cb;
|
||||
std::unique_ptr<Dynarmic::A32::Jit> MakeJit();
|
||||
|
||||
u32 fpexc = 0;
|
||||
CP15State cp15_state;
|
||||
Core::DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Dynarmic::A32::Jit* jit = nullptr;
|
||||
std::shared_ptr<Memory::PageTable> current_page_table = nullptr;
|
||||
std::map<std::shared_ptr<Memory::PageTable>, std::unique_ptr<Dynarmic::A32::Jit>> jits;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
86
src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
Normal file
86
src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
|
||||
#include "core/arm/skyeye_common/arm_regformat.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
|
||||
using Callback = Dynarmic::A32::Coprocessor::Callback;
|
||||
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
|
||||
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
|
||||
|
||||
DynarmicCP15::DynarmicCP15(CP15State& state) : state(state) {}
|
||||
|
||||
DynarmicCP15::~DynarmicCP15() = default;
|
||||
|
||||
std::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1,
|
||||
CoprocReg CRd, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) {
|
||||
// TODO(merry): Privileged CP15 registers
|
||||
|
||||
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) {
|
||||
// This is a dummy write, we ignore the value written here.
|
||||
return &state.cp15_flush_prefetch_buffer;
|
||||
}
|
||||
|
||||
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) {
|
||||
switch (opc2) {
|
||||
case 4:
|
||||
// This is a dummy write, we ignore the value written here.
|
||||
return &state.cp15_data_sync_barrier;
|
||||
case 5:
|
||||
// This is a dummy write, we ignore the value written here.
|
||||
return &state.cp15_data_memory_barrier;
|
||||
default:
|
||||
return std::monostate{};
|
||||
}
|
||||
}
|
||||
|
||||
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) {
|
||||
return &state.cp15_thread_uprw;
|
||||
}
|
||||
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) {
|
||||
// TODO(merry): Privileged CP15 registers
|
||||
|
||||
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) {
|
||||
switch (opc2) {
|
||||
case 2:
|
||||
return &state.cp15_thread_uprw;
|
||||
case 3:
|
||||
return &state.cp15_thread_uro;
|
||||
default:
|
||||
return std::monostate{};
|
||||
}
|
||||
}
|
||||
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
std::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) {
|
||||
return std::nullopt;
|
||||
}
|
||||
42
src/core/arm/dynarmic/arm_dynarmic_cp15.h
Normal file
42
src/core/arm/dynarmic/arm_dynarmic_cp15.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <dynarmic/interface/A32/coprocessor.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
struct CP15State {
|
||||
u32 cp15_thread_uprw = 0;
|
||||
u32 cp15_thread_uro = 0;
|
||||
u32 cp15_flush_prefetch_buffer = 0; ///< dummy value
|
||||
u32 cp15_data_sync_barrier = 0; ///< dummy value
|
||||
u32 cp15_data_memory_barrier = 0; ///< dummy value
|
||||
};
|
||||
|
||||
class DynarmicCP15 final : public Dynarmic::A32::Coprocessor {
|
||||
public:
|
||||
using CoprocReg = Dynarmic::A32::CoprocReg;
|
||||
|
||||
explicit DynarmicCP15(CP15State&);
|
||||
~DynarmicCP15() override;
|
||||
|
||||
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
|
||||
CoprocReg CRn, CoprocReg CRm,
|
||||
unsigned opc2) override;
|
||||
CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) override;
|
||||
CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
|
||||
CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm,
|
||||
unsigned opc2) override;
|
||||
CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
|
||||
std::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) override;
|
||||
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) override;
|
||||
|
||||
private:
|
||||
CP15State& state;
|
||||
};
|
||||
59
src/core/arm/dynarmic/arm_exclusive_monitor.cpp
Normal file
59
src/core/arm/dynarmic/arm_exclusive_monitor.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::MemorySystem& memory_,
|
||||
std::size_t core_count_)
|
||||
: monitor{core_count_}, memory{memory_} {}
|
||||
|
||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
|
||||
|
||||
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
|
||||
}
|
||||
|
||||
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
|
||||
}
|
||||
|
||||
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
|
||||
}
|
||||
|
||||
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
|
||||
}
|
||||
|
||||
void DynarmicExclusiveMonitor::ClearExclusive(std::size_t core_index) {
|
||||
monitor.ClearProcessor(core_index);
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
|
||||
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
|
||||
return memory.WriteExclusive8(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
|
||||
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
|
||||
return memory.WriteExclusive16(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
|
||||
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
|
||||
return memory.WriteExclusive32(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
|
||||
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
|
||||
return memory.WriteExclusive64(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
40
src/core/arm/dynarmic/arm_exclusive_monitor.h
Normal file
40
src/core/arm/dynarmic/arm_exclusive_monitor.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dynarmic/interface/exclusive_monitor.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
public:
|
||||
explicit DynarmicExclusiveMonitor(Memory::MemorySystem& memory_, std::size_t core_count_);
|
||||
~DynarmicExclusiveMonitor() override;
|
||||
|
||||
u8 ExclusiveRead8(std::size_t core_index, VAddr addr) override;
|
||||
u16 ExclusiveRead16(std::size_t core_index, VAddr addr) override;
|
||||
u32 ExclusiveRead32(std::size_t core_index, VAddr addr) override;
|
||||
u64 ExclusiveRead64(std::size_t core_index, VAddr addr) override;
|
||||
void ClearExclusive(std::size_t core_index) override;
|
||||
|
||||
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
|
||||
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
|
||||
bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
|
||||
bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
|
||||
|
||||
private:
|
||||
friend class Core::ARM_Dynarmic;
|
||||
Dynarmic::ExclusiveMonitor monitor;
|
||||
Memory::MemorySystem& memory;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
491
src/core/arm/dynarmic/arm_tick_counts.cpp
Normal file
491
src/core/arm/dynarmic/arm_tick_counts.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <bit>
|
||||
#include <functional>
|
||||
#include "common/common_types.h"
|
||||
#include "common/string_literal.h"
|
||||
#include "core/arm/dynarmic/arm_tick_counts.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <Common::StringLiteral haystack, Common::StringLiteral needle>
|
||||
constexpr u32 GetMatchingBitsFromStringLiteral() {
|
||||
u32 result = 0;
|
||||
for (std::size_t i = 0; i < haystack.strlen; i++) {
|
||||
for (std::size_t a = 0; a < needle.strlen; a++) {
|
||||
if (haystack.value[i] == needle.value[a]) {
|
||||
result |= 1 << (haystack.strlen - 1 - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <u32 mask_>
|
||||
constexpr u32 DepositBits(u32 val) {
|
||||
u32 mask = mask_;
|
||||
u32 res = 0;
|
||||
for (u32 bb = 1; mask; bb += bb) {
|
||||
u32 neg_mask = 0 - mask;
|
||||
if (val & bb)
|
||||
res |= mask & neg_mask;
|
||||
mask &= mask - 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <Common::StringLiteral haystack>
|
||||
struct MatcherArg {
|
||||
template <Common::StringLiteral needle>
|
||||
u32 Get() {
|
||||
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
|
||||
}
|
||||
|
||||
u32 instruction;
|
||||
};
|
||||
|
||||
struct Matcher {
|
||||
u32 mask;
|
||||
u32 expect;
|
||||
std::function<u64(u32)> fn;
|
||||
};
|
||||
|
||||
u64 DataProcessing_imm(auto i) {
|
||||
if (i.template Get<"d">() == 15) {
|
||||
return 7;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
u64 DataProcessing_reg(auto i) {
|
||||
if (i.template Get<"d">() == 15) {
|
||||
return 7;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
u64 DataProcessing_rsr(auto i) {
|
||||
if (i.template Get<"d">() == 15) {
|
||||
return 8;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
u64 LoadStoreSingle_imm(auto) {
|
||||
return 2;
|
||||
}
|
||||
u64 LoadStoreSingle_reg(auto i) {
|
||||
// TODO: Load PC
|
||||
if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 &&
|
||||
(i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) {
|
||||
return 2;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
u64 LoadStoreMultiple(auto i) {
|
||||
// TODO: Load PC
|
||||
return 1 + std::popcount(i.template Get<"x">()) / 2;
|
||||
}
|
||||
|
||||
#define INST(NAME, BS, CYCLES) \
|
||||
Matcher{GetMatchingBitsFromStringLiteral<BS, "01">(), \
|
||||
GetMatchingBitsFromStringLiteral<BS, "1">(), \
|
||||
std::function<u64(u32)>{[](u32 instruction) -> u64 { \
|
||||
[[maybe_unused]] MatcherArg<BS> i{instruction}; \
|
||||
return (CYCLES); \
|
||||
}}},
|
||||
|
||||
const std::array arm_matchers{
|
||||
// clang-format off
|
||||
|
||||
// Branch instructions
|
||||
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5
|
||||
INST("BLX (reg)", "cccc000100101111111111110011mmmm", 6) // v5
|
||||
INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
|
||||
INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
|
||||
INST("BX", "cccc000100101111111111110001mmmm", 5) // v4T
|
||||
INST("BXJ", "cccc000100101111111111110010mmmm", 1) // v5J
|
||||
|
||||
// Coprocessor instructions
|
||||
INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5)
|
||||
INST("LDC", "cccc110pudw1nnnnDDDDppppvvvvvvvv", 1) // v2 (LDC2: v5)
|
||||
INST("MCR", "cccc1110ooo0NNNNttttppppooo1MMMM", 2) // v2 (MCR2: v5)
|
||||
INST("MCRR", "cccc11000100uuuuttttppppooooMMMM", 2) // v5E (MCRR2: v6)
|
||||
INST("MRC", "cccc1110ooo1NNNNttttppppooo1MMMM", 2) // v2 (MRC2: v5)
|
||||
INST("MRRC", "cccc11000101uuuuttttppppooooMMMM", 2) // v5E (MRRC2: v6)
|
||||
INST("STC", "cccc110pudw0nnnnDDDDppppvvvvvvvv", 1) // v2 (STC2: v5)
|
||||
|
||||
// Data Processing instructions
|
||||
INST("ADC (imm)", "cccc0010101Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("ADC (reg)", "cccc0000101Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("ADC (rsr)", "cccc0000101Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("ADD (imm)", "cccc0010100Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("ADD (reg)", "cccc0000100Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("ADD (rsr)", "cccc0000100Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("AND (imm)", "cccc0010000Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("AND (reg)", "cccc0000000Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("AND (rsr)", "cccc0000000Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("BIC (imm)", "cccc0011110Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("BIC (reg)", "cccc0001110Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("BIC (rsr)", "cccc0001110Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("CMN (imm)", "cccc00110111nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("CMN (reg)", "cccc00010111nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("CMN (rsr)", "cccc00010111nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("CMP (imm)", "cccc00110101nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("CMP (reg)", "cccc00010101nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("CMP (rsr)", "cccc00010101nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("EOR (imm)", "cccc0010001Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("EOR (reg)", "cccc0000001Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("MOV (imm)", "cccc0011101S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("MOV (reg)", "cccc0001101S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("MOV (rsr)", "cccc0001101S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("MVN (reg)", "cccc0001111S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("MVN (rsr)", "cccc0001111S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("ORR (imm)", "cccc0011100Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("ORR (reg)", "cccc0001100Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("ORR (rsr)", "cccc0001100Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("RSB (imm)", "cccc0010011Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("RSB (reg)", "cccc0000011Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("RSB (rsr)", "cccc0000011Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("RSC (imm)", "cccc0010111Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("RSC (reg)", "cccc0000111Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("RSC (rsr)", "cccc0000111Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("SBC (imm)", "cccc0010110Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("SBC (reg)", "cccc0000110Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("SBC (rsr)", "cccc0000110Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("SUB (imm)", "cccc0010010Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("SUB (reg)", "cccc0000010Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("SUB (rsr)", "cccc0000010Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("TEQ (imm)", "cccc00110011nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("TEQ (reg)", "cccc00010011nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("TEQ (rsr)", "cccc00010011nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
INST("TST (imm)", "cccc00110001nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
|
||||
INST("TST (reg)", "cccc00010001nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
|
||||
INST("TST (rsr)", "cccc00010001nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
|
||||
|
||||
// Exception Generating instructions
|
||||
INST("BKPT", "cccc00010010vvvvvvvvvvvv0111vvvv", 8) // v5
|
||||
INST("SVC", "cccc1111vvvvvvvvvvvvvvvvvvvvvvvv", 8) // v1
|
||||
INST("UDF", "111001111111------------1111----", 8)
|
||||
|
||||
// Extension instructions
|
||||
INST("SXTB", "cccc011010101111ddddrr000111mmmm", 1) // v6
|
||||
INST("SXTB16", "cccc011010001111ddddrr000111mmmm", 1) // v6
|
||||
INST("SXTH", "cccc011010111111ddddrr000111mmmm", 1) // v6
|
||||
INST("SXTAB", "cccc01101010nnnnddddrr000111mmmm", 1) // v6
|
||||
INST("SXTAB16", "cccc01101000nnnnddddrr000111mmmm", 1) // v6
|
||||
INST("SXTAH", "cccc01101011nnnnddddrr000111mmmm", 1) // v6
|
||||
INST("UXTB", "cccc011011101111ddddrr000111mmmm", 1) // v6
|
||||
INST("UXTB16", "cccc011011001111ddddrr000111mmmm", 1) // v6
|
||||
INST("UXTH", "cccc011011111111ddddrr000111mmmm", 1) // v6
|
||||
INST("UXTAB", "cccc01101110nnnnddddrr000111mmmm", 1) // v6
|
||||
INST("UXTAB16", "cccc01101100nnnnddddrr000111mmmm", 1) // v6
|
||||
INST("UXTAH", "cccc01101111nnnnddddrr000111mmmm", 1) // v6
|
||||
|
||||
// Hint instructions
|
||||
INST("PLD (imm)", "11110101uz01nnnn1111iiiiiiiiiiii", 1) // v5E for PLD; v7 for PLDW
|
||||
INST("PLD (reg)", "11110111uz01nnnn1111iiiiitt0mmmm", 1) // v5E for PLD; v7 for PLDW
|
||||
INST("SEV", "----0011001000001111000000000100", 1) // v6K
|
||||
INST("WFE", "----0011001000001111000000000010", 1) // v6K
|
||||
INST("WFI", "----0011001000001111000000000011", 1) // v6K
|
||||
INST("YIELD", "----0011001000001111000000000001", 1) // v6K
|
||||
|
||||
// Synchronization Primitive instructions
|
||||
INST("CLREX", "11110101011111111111000000011111", 1) // v6K
|
||||
INST("SWP", "cccc00010000nnnntttt00001001uuuu", 4) // v2S (v6: Deprecated)
|
||||
INST("SWPB", "cccc00010100nnnntttt00001001uuuu", 4) // v2S (v6: Deprecated)
|
||||
INST("STREX", "cccc00011000nnnndddd11111001mmmm", 2) // v6
|
||||
INST("LDREX", "cccc00011001nnnndddd111110011111", 2) // v6
|
||||
INST("STREXD", "cccc00011010nnnndddd11111001mmmm", 2) // v6K
|
||||
INST("LDREXD", "cccc00011011nnnndddd111110011111", 2) // v6K
|
||||
INST("STREXB", "cccc00011100nnnndddd11111001mmmm", 2) // v6K
|
||||
INST("LDREXB", "cccc00011101nnnndddd111110011111", 2) // v6K
|
||||
INST("STREXH", "cccc00011110nnnndddd11111001mmmm", 2) // v6K
|
||||
INST("LDREXH", "cccc00011111nnnndddd111110011111", 2) // v6K
|
||||
|
||||
// Load/Store instructions
|
||||
INST("LDRBT (A1)", "----0100-111--------------------", 1) // v1
|
||||
INST("LDRBT (A2)", "----0110-111---------------0----", 1) // v1
|
||||
INST("LDRT (A1)", "----0100-011--------------------", 1) // v1
|
||||
INST("LDRT (A2)", "----0110-011---------------0----", 1) // v1
|
||||
INST("STRBT (A1)", "----0100-110--------------------", 1) // v1
|
||||
INST("STRBT (A2)", "----0110-110---------------0----", 1) // v1
|
||||
INST("STRT (A1)", "----0100-010--------------------", 1) // v1
|
||||
INST("STRT (A2)", "----0110-010---------------0----", 1) // v1
|
||||
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
||||
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
||||
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
||||
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
||||
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
||||
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
||||
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
|
||||
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
|
||||
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v5E
|
||||
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
|
||||
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v4
|
||||
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v4
|
||||
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
||||
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
||||
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
|
||||
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
|
||||
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v5E
|
||||
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v5E
|
||||
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
|
||||
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
|
||||
|
||||
// Load/Store Multiple instructions
|
||||
INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("LDMDA", "cccc100000w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("LDMDB", "cccc100100w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("LDMIB", "cccc100110w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("LDM (usr reg)", "----100--101--------------------", 1) // v1
|
||||
INST("LDM (exce ret)", "----100--1-1----1---------------", 1) // v1
|
||||
INST("STM", "cccc100010w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("STMDA", "cccc100000w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("STMDB", "cccc100100w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("STMIB", "cccc100110w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
|
||||
INST("STM (usr reg)", "----100--100--------------------", 1) // v1
|
||||
|
||||
// Miscellaneous instructions
|
||||
INST("CLZ", "cccc000101101111dddd11110001mmmm", 1) // v5
|
||||
INST("NOP", "----0011001000001111000000000000", 1) // v6K
|
||||
INST("SEL", "cccc01101000nnnndddd11111011mmmm", 1) // v6
|
||||
|
||||
// Unsigned Sum of Absolute Differences instructions
|
||||
INST("USAD8", "cccc01111000dddd1111mmmm0001nnnn", 1) // v6
|
||||
INST("USADA8", "cccc01111000ddddaaaammmm0001nnnn", 1) // v6
|
||||
|
||||
// Packing instructions
|
||||
INST("PKHBT", "cccc01101000nnnnddddvvvvv001mmmm", 1) // v6K
|
||||
INST("PKHTB", "cccc01101000nnnnddddvvvvv101mmmm", 1) // v6K
|
||||
|
||||
// Reversal instructions
|
||||
INST("REV", "cccc011010111111dddd11110011mmmm", 1) // v6
|
||||
INST("REV16", "cccc011010111111dddd11111011mmmm", 1) // v6
|
||||
INST("REVSH", "cccc011011111111dddd11111011mmmm", 1) // v6
|
||||
|
||||
// Saturation instructions
|
||||
INST("SSAT", "cccc0110101vvvvvddddvvvvvr01nnnn", 1) // v6
|
||||
INST("SSAT16", "cccc01101010vvvvdddd11110011nnnn", 1) // v6
|
||||
INST("USAT", "cccc0110111vvvvvddddvvvvvr01nnnn", 1) // v6
|
||||
INST("USAT16", "cccc01101110vvvvdddd11110011nnnn", 1) // v6
|
||||
|
||||
// Multiply (Normal) instructions
|
||||
INST("MLA", "cccc0000001Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 5 : 2)) // v2
|
||||
INST("MUL", "cccc0000000Sdddd0000mmmm1001nnnn", (i.template Get<"S">() ? 5 : 2)) // v2
|
||||
|
||||
// Multiply (Long) instructions
|
||||
INST("SMLAL", "cccc0000111Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
|
||||
INST("SMULL", "cccc0000110Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
|
||||
INST("UMAAL", "cccc00000100ddddaaaammmm1001nnnn", 3) // v6
|
||||
INST("UMLAL", "cccc0000101Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
|
||||
INST("UMULL", "cccc0000100Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
|
||||
|
||||
// Multiply (Halfword) instructions
|
||||
INST("SMLALXY", "cccc00010100ddddaaaammmm1xy0nnnn", 2) // v5xP
|
||||
INST("SMLAXY", "cccc00010000ddddaaaammmm1xy0nnnn", 1) // v5xP
|
||||
INST("SMULXY", "cccc00010110dddd0000mmmm1xy0nnnn", 1) // v5xP
|
||||
|
||||
// Multiply (Word by Halfword) instructions
|
||||
INST("SMLAWY", "cccc00010010ddddaaaammmm1y00nnnn", 1) // v5xP
|
||||
INST("SMULWY", "cccc00010010dddd0000mmmm1y10nnnn", 1) // v5xP
|
||||
|
||||
// Multiply (Most Significant Word) instructions
|
||||
INST("SMMUL", "cccc01110101dddd1111mmmm00R1nnnn", 2) // v6
|
||||
INST("SMMLA", "cccc01110101ddddaaaammmm00R1nnnn", 2) // v6
|
||||
INST("SMMLS", "cccc01110101ddddaaaammmm11R1nnnn", 2) // v6
|
||||
|
||||
// Multiply (Dual) instructions
|
||||
INST("SMLAD", "cccc01110000ddddaaaammmm00M1nnnn", 2) // v6
|
||||
INST("SMLALD", "cccc01110100ddddaaaammmm00M1nnnn", 2) // v6
|
||||
INST("SMLSD", "cccc01110000ddddaaaammmm01M1nnnn", 2) // v6
|
||||
INST("SMLSLD", "cccc01110100ddddaaaammmm01M1nnnn", 2) // v6
|
||||
INST("SMUAD", "cccc01110000dddd1111mmmm00M1nnnn", 2) // v6
|
||||
INST("SMUSD", "cccc01110000dddd1111mmmm01M1nnnn", 2) // v6
|
||||
|
||||
// Parallel Add/Subtract (Modulo) instructions
|
||||
INST("SADD8", "cccc01100001nnnndddd11111001mmmm", 1) // v6
|
||||
INST("SADD16", "cccc01100001nnnndddd11110001mmmm", 1) // v6
|
||||
INST("SASX", "cccc01100001nnnndddd11110011mmmm", 1) // v6
|
||||
INST("SSAX", "cccc01100001nnnndddd11110101mmmm", 1) // v6
|
||||
INST("SSUB8", "cccc01100001nnnndddd11111111mmmm", 1) // v6
|
||||
INST("SSUB16", "cccc01100001nnnndddd11110111mmmm", 1) // v6
|
||||
INST("UADD8", "cccc01100101nnnndddd11111001mmmm", 1) // v6
|
||||
INST("UADD16", "cccc01100101nnnndddd11110001mmmm", 1) // v6
|
||||
INST("UASX", "cccc01100101nnnndddd11110011mmmm", 1) // v6
|
||||
INST("USAX", "cccc01100101nnnndddd11110101mmmm", 1) // v6
|
||||
INST("USUB8", "cccc01100101nnnndddd11111111mmmm", 1) // v6
|
||||
INST("USUB16", "cccc01100101nnnndddd11110111mmmm", 1) // v6
|
||||
|
||||
// Parallel Add/Subtract (Saturating) instructions
|
||||
INST("QADD8", "cccc01100010nnnndddd11111001mmmm", 1) // v6
|
||||
INST("QADD16", "cccc01100010nnnndddd11110001mmmm", 1) // v6
|
||||
INST("QASX", "cccc01100010nnnndddd11110011mmmm", 1) // v6
|
||||
INST("QSAX", "cccc01100010nnnndddd11110101mmmm", 1) // v6
|
||||
INST("QSUB8", "cccc01100010nnnndddd11111111mmmm", 1) // v6
|
||||
INST("QSUB16", "cccc01100010nnnndddd11110111mmmm", 1) // v6
|
||||
INST("UQADD8", "cccc01100110nnnndddd11111001mmmm", 1) // v6
|
||||
INST("UQADD16", "cccc01100110nnnndddd11110001mmmm", 1) // v6
|
||||
INST("UQASX", "cccc01100110nnnndddd11110011mmmm", 1) // v6
|
||||
INST("UQSAX", "cccc01100110nnnndddd11110101mmmm", 1) // v6
|
||||
INST("UQSUB8", "cccc01100110nnnndddd11111111mmmm", 1) // v6
|
||||
INST("UQSUB16", "cccc01100110nnnndddd11110111mmmm", 1) // v6
|
||||
|
||||
// Parallel Add/Subtract (Halving) instructions
|
||||
INST("SHADD8", "cccc01100011nnnndddd11111001mmmm", 1) // v6
|
||||
INST("SHADD16", "cccc01100011nnnndddd11110001mmmm", 1) // v6
|
||||
INST("SHASX", "cccc01100011nnnndddd11110011mmmm", 1) // v6
|
||||
INST("SHSAX", "cccc01100011nnnndddd11110101mmmm", 1) // v6
|
||||
INST("SHSUB8", "cccc01100011nnnndddd11111111mmmm", 1) // v6
|
||||
INST("SHSUB16", "cccc01100011nnnndddd11110111mmmm", 1) // v6
|
||||
INST("UHADD8", "cccc01100111nnnndddd11111001mmmm", 1) // v6
|
||||
INST("UHADD16", "cccc01100111nnnndddd11110001mmmm", 1) // v6
|
||||
INST("UHASX", "cccc01100111nnnndddd11110011mmmm", 1) // v6
|
||||
INST("UHSAX", "cccc01100111nnnndddd11110101mmmm", 1) // v6
|
||||
INST("UHSUB8", "cccc01100111nnnndddd11111111mmmm", 1) // v6
|
||||
INST("UHSUB16", "cccc01100111nnnndddd11110111mmmm", 1) // v6
|
||||
|
||||
// Saturated Add/Subtract instructions
|
||||
INST("QADD", "cccc00010000nnnndddd00000101mmmm", 1) // v5xP
|
||||
INST("QSUB", "cccc00010010nnnndddd00000101mmmm", 1) // v5xP
|
||||
INST("QDADD", "cccc00010100nnnndddd00000101mmmm", 1) // v5xP
|
||||
INST("QDSUB", "cccc00010110nnnndddd00000101mmmm", 1) // v5xP
|
||||
|
||||
// Status Register Access instructions
|
||||
INST("CPS", "111100010000---00000000---0-----", 1) // v6
|
||||
INST("SETEND", "1111000100000001000000e000000000", 1) // v6
|
||||
INST("MRS", "cccc000100001111dddd000000000000", 1) // v3
|
||||
INST("MSR (imm)", "cccc00110010mmmm1111rrrrvvvvvvvv", (i.template Get<"m">() == 0b1000 ? 1 : 4)) // v3
|
||||
INST("MSR (reg)", "cccc00010010mmmm111100000000nnnn", (i.template Get<"m">() == 0b1000 ? 1 : 4)) // v3
|
||||
INST("RFE", "1111100--0-1----0000101000000000", 9) // v6
|
||||
INST("SRS", "1111100--1-0110100000101000-----", 1) // v6
|
||||
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
const std::array thumb_matchers{
|
||||
// clang-format off
|
||||
|
||||
// Shift (immediate) add, subtract, move and compare instructions
|
||||
INST("LSL (imm)", "00000vvvvvmmmddd", 1)
|
||||
INST("LSR (imm)", "00001vvvvvmmmddd", 1)
|
||||
INST("ASR (imm)", "00010vvvvvmmmddd", 1)
|
||||
INST("ADD (reg, T1)", "0001100mmmnnnddd", 1)
|
||||
INST("SUB (reg)", "0001101mmmnnnddd", 1)
|
||||
INST("ADD (imm, T1)", "0001110vvvnnnddd", 1)
|
||||
INST("SUB (imm, T1)", "0001111vvvnnnddd", 1)
|
||||
INST("MOV (imm)", "00100dddvvvvvvvv", 1)
|
||||
INST("CMP (imm)", "00101nnnvvvvvvvv", 1)
|
||||
INST("ADD (imm, T2)", "00110dddvvvvvvvv", 1)
|
||||
INST("SUB (imm, T2)", "00111dddvvvvvvvv", 1)
|
||||
|
||||
// Data-processing instructions
|
||||
INST("AND (reg)", "0100000000mmmddd", 1)
|
||||
INST("EOR (reg)", "0100000001mmmddd", 1)
|
||||
INST("LSL (reg)", "0100000010mmmddd", 1)
|
||||
INST("LSR (reg)", "0100000011mmmddd", 1)
|
||||
INST("ASR (reg)", "0100000100mmmddd", 1)
|
||||
INST("ADC (reg)", "0100000101mmmddd", 1)
|
||||
INST("SBC (reg)", "0100000110mmmddd", 1)
|
||||
INST("ROR (reg)", "0100000111sssddd", 1)
|
||||
INST("TST (reg)", "0100001000mmmnnn", 1)
|
||||
INST("RSB (imm)", "0100001001nnnddd", 1)
|
||||
INST("CMP (reg, T1)", "0100001010mmmnnn", 1)
|
||||
INST("CMN (reg)", "0100001011mmmnnn", 1)
|
||||
INST("ORR (reg)", "0100001100mmmddd", 1)
|
||||
INST("MUL (reg)", "0100001101nnnddd", 1)
|
||||
INST("BIC (reg)", "0100001110mmmddd", 1)
|
||||
INST("MVN (reg)", "0100001111mmmddd", 1)
|
||||
|
||||
// Special data instructions
|
||||
INST("ADD (reg, T2)", "01000100Dmmmmddd", 1) // v4T, Low regs: v6T2
|
||||
INST("CMP (reg, T2)", "01000101Nmmmmnnn", 1) // v4T
|
||||
INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6
|
||||
|
||||
// Store/Load single data item instructions
|
||||
INST("LDR (literal)", "01001tttvvvvvvvv", 2)
|
||||
INST("STR (reg)", "0101000mmmnnnttt", 2)
|
||||
INST("STRH (reg)", "0101001mmmnnnttt", 2)
|
||||
INST("STRB (reg)", "0101010mmmnnnttt", 2)
|
||||
INST("LDRSB (reg)", "0101011mmmnnnttt", 2)
|
||||
INST("LDR (reg)", "0101100mmmnnnttt", 2)
|
||||
INST("LDRH (reg)", "0101101mmmnnnttt", 2)
|
||||
INST("LDRB (reg)", "0101110mmmnnnttt", 2)
|
||||
INST("LDRSH (reg)", "0101111mmmnnnttt", 2)
|
||||
INST("STR (imm, T1)", "01100vvvvvnnnttt", 2)
|
||||
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 2)
|
||||
INST("STRB (imm)", "01110vvvvvnnnttt", 2)
|
||||
INST("LDRB (imm)", "01111vvvvvnnnttt", 2)
|
||||
INST("STRH (imm)", "10000vvvvvnnnttt", 2)
|
||||
INST("LDRH (imm)", "10001vvvvvnnnttt", 2)
|
||||
INST("STR (imm, T2)", "10010tttvvvvvvvv", 2)
|
||||
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 2)
|
||||
|
||||
// Generate relative address instructions
|
||||
INST("ADR", "10100dddvvvvvvvv", 1)
|
||||
INST("ADD (SP plus imm, T1)", "10101dddvvvvvvvv", 1)
|
||||
INST("ADD (SP plus imm, T2)", "101100000vvvvvvv", 1) // v4T
|
||||
INST("SUB (SP minus imm)", "101100001vvvvvvv", 1) // v4T
|
||||
|
||||
// Hint instructions
|
||||
INST("NOP", "10111111--------", (1)) // IT on v7
|
||||
|
||||
// Miscellaneous 16-bit instructions
|
||||
INST("SXTH", "1011001000mmmddd", 1) // v6
|
||||
INST("SXTB", "1011001001mmmddd", 1) // v6
|
||||
INST("UXTH", "1011001010mmmddd", 1) // v6
|
||||
INST("UXTB", "1011001011mmmddd", 1) // v6
|
||||
INST("PUSH", "1011010xxxxxxxxx", LoadStoreMultiple(i)) // v4T
|
||||
INST("POP", "1011110xxxxxxxxx", LoadStoreMultiple(i)) // v4T
|
||||
INST("SETEND", "101101100101x000", 1) // v6
|
||||
INST("CPS", "10110110011m0aif", 1) // v6
|
||||
INST("REV", "1011101000mmmddd", 1) // v6
|
||||
INST("REV16", "1011101001mmmddd", 1) // v6
|
||||
INST("REVSH", "1011101011mmmddd", 1) // v6
|
||||
INST("BKPT", "10111110xxxxxxxx", 8) // v5
|
||||
|
||||
// Store/Load multiple registers
|
||||
INST("STMIA", "11000nnnxxxxxxxx", LoadStoreMultiple(i))
|
||||
INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i))
|
||||
|
||||
// Branch instructions
|
||||
INST("BX", "010001110mmmm000", 5) // v4T
|
||||
INST("BLX (reg)", "010001111mmmm000", 6) // v5T
|
||||
INST("UDF", "11011110--------", 8)
|
||||
INST("SVC", "11011111xxxxxxxx", 8)
|
||||
INST("B (T1)", "1101ccccvvvvvvvv", 4)
|
||||
INST("B (T2)", "11100vvvvvvvvvvv", 4)
|
||||
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T
|
||||
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T
|
||||
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Core {
|
||||
|
||||
u64 TicksForInstruction(bool is_thumb, u32 instruction) {
|
||||
if (is_thumb) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto matches_instruction = [instruction](const auto& matcher) {
|
||||
return (instruction & matcher.mask) == matcher.expect;
|
||||
};
|
||||
|
||||
auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
|
||||
if (iter != arm_matchers.end()) {
|
||||
return iter->fn(instruction);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
13
src/core/arm/dynarmic/arm_tick_counts.h
Normal file
13
src/core/arm/dynarmic/arm_tick_counts.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
u64 TicksForInstruction(bool is_thumb, u32 instruction);
|
||||
|
||||
} // namespace Core
|
||||
128
src/core/arm/dyncom/arm_dyncom.cpp
Normal file
128
src/core/arm/dyncom/arm_dyncom.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include "core/arm/dyncom/arm_dyncom.h"
|
||||
#include "core/arm/dyncom/arm_dyncom_interpreter.h"
|
||||
#include "core/arm/dyncom/arm_dyncom_trans.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
ARM_DynCom::ARM_DynCom(Core::System& system_, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode, u32 id,
|
||||
std::shared_ptr<Core::Timing::Timer> timer)
|
||||
: ARM_Interface(id, timer), system(system_) {
|
||||
state = std::make_unique<ARMul_State>(system, memory, initial_mode);
|
||||
}
|
||||
|
||||
ARM_DynCom::~ARM_DynCom() {}
|
||||
|
||||
void ARM_DynCom::Run() {
|
||||
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
|
||||
}
|
||||
|
||||
void ARM_DynCom::Step() {
|
||||
ExecuteInstructions(1);
|
||||
}
|
||||
|
||||
void ARM_DynCom::ClearInstructionCache() {
|
||||
state->instruction_cache.clear();
|
||||
trans_cache_buf_top = 0;
|
||||
}
|
||||
|
||||
void ARM_DynCom::InvalidateCacheRange(u32, std::size_t) {
|
||||
ClearInstructionCache();
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) {
|
||||
ClearInstructionCache();
|
||||
}
|
||||
|
||||
std::shared_ptr<Memory::PageTable> ARM_DynCom::GetPageTable() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetPC(u32 pc) {
|
||||
state->Reg[15] = pc;
|
||||
}
|
||||
|
||||
u32 ARM_DynCom::GetPC() const {
|
||||
return state->Reg[15];
|
||||
}
|
||||
|
||||
u32 ARM_DynCom::GetReg(int index) const {
|
||||
return state->Reg[index];
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetReg(int index, u32 value) {
|
||||
state->Reg[index] = value;
|
||||
}
|
||||
|
||||
u32 ARM_DynCom::GetVFPReg(int index) const {
|
||||
return state->ExtReg[index];
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetVFPReg(int index, u32 value) {
|
||||
state->ExtReg[index] = value;
|
||||
}
|
||||
|
||||
u32 ARM_DynCom::GetVFPSystemReg(VFPSystemRegister reg) const {
|
||||
return state->VFP[reg];
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
|
||||
state->VFP[reg] = value;
|
||||
}
|
||||
|
||||
u32 ARM_DynCom::GetCPSR() const {
|
||||
return state->Cpsr;
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetCPSR(u32 cpsr) {
|
||||
state->Cpsr = cpsr;
|
||||
}
|
||||
|
||||
u32 ARM_DynCom::GetCP15Register(CP15Register reg) const {
|
||||
return state->CP15[reg];
|
||||
}
|
||||
|
||||
void ARM_DynCom::SetCP15Register(CP15Register reg, u32 value) {
|
||||
state->CP15[reg] = value;
|
||||
}
|
||||
|
||||
void ARM_DynCom::ExecuteInstructions(u64 num_instructions) {
|
||||
state->NumInstrsToExecute = num_instructions;
|
||||
const u32 ticks_executed = InterpreterMainLoop(state.get());
|
||||
if (timer) {
|
||||
timer->AddTicks(ticks_executed);
|
||||
}
|
||||
state->ServeBreak();
|
||||
}
|
||||
|
||||
void ARM_DynCom::SaveContext(ThreadContext& ctx) {
|
||||
ctx.cpu_registers = state->Reg;
|
||||
ctx.cpsr = state->Cpsr;
|
||||
ctx.fpu_registers = state->ExtReg;
|
||||
ctx.fpscr = state->VFP[VFP_FPSCR];
|
||||
ctx.fpexc = state->VFP[VFP_FPEXC];
|
||||
}
|
||||
|
||||
void ARM_DynCom::LoadContext(const ThreadContext& ctx) {
|
||||
state->Reg = ctx.cpu_registers;
|
||||
state->Cpsr = ctx.cpsr;
|
||||
state->ExtReg = ctx.fpu_registers;
|
||||
state->VFP[VFP_FPSCR] = ctx.fpscr;
|
||||
state->VFP[VFP_FPEXC] = ctx.fpexc;
|
||||
}
|
||||
|
||||
void ARM_DynCom::PrepareReschedule() {
|
||||
state->NumInstrsToExecute = 0;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
64
src/core/arm/dyncom/arm_dyncom.h
Normal file
64
src/core/arm/dyncom/arm_dyncom.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/skyeye_common/arm_regformat.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System;
|
||||
|
||||
class ARM_DynCom final : public ARM_Interface {
|
||||
public:
|
||||
explicit ARM_DynCom(Core::System& system, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode, u32 id,
|
||||
std::shared_ptr<Core::Timing::Timer> timer);
|
||||
~ARM_DynCom() override;
|
||||
|
||||
void Run() override;
|
||||
void Step() override;
|
||||
|
||||
void ClearInstructionCache() override;
|
||||
void InvalidateCacheRange(u32 start_address, std::size_t length) override;
|
||||
void ClearExclusiveState() override{};
|
||||
|
||||
void SetPC(u32 pc) override;
|
||||
u32 GetPC() const override;
|
||||
u32 GetReg(int index) const override;
|
||||
void SetReg(int index, u32 value) override;
|
||||
u32 GetVFPReg(int index) const override;
|
||||
void SetVFPReg(int index, u32 value) override;
|
||||
u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
|
||||
void SetVFPSystemReg(VFPSystemRegister reg, u32 value) override;
|
||||
u32 GetCPSR() const override;
|
||||
void SetCPSR(u32 cpsr) override;
|
||||
u32 GetCP15Register(CP15Register reg) const override;
|
||||
void SetCP15Register(CP15Register reg, u32 value) override;
|
||||
|
||||
void SaveContext(ThreadContext& ctx) override;
|
||||
void LoadContext(const ThreadContext& ctx) override;
|
||||
|
||||
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
|
||||
void PrepareReschedule() override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
||||
|
||||
private:
|
||||
void ExecuteInstructions(u64 num_instructions);
|
||||
|
||||
Core::System& system;
|
||||
std::unique_ptr<ARMul_State> state;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
503
src/core/arm/dyncom/arm_dyncom_dec.cpp
Normal file
503
src/core/arm/dyncom/arm_dyncom_dec.cpp
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright 2012 Michael Kang, 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/arm/dyncom/arm_dyncom_dec.h"
|
||||
#include "core/arm/skyeye_common/armsupp.h"
|
||||
|
||||
namespace {
|
||||
struct InstructionSetEncodingItem {
|
||||
const char* name;
|
||||
int attribute_value;
|
||||
int version;
|
||||
u32 content[21];
|
||||
};
|
||||
|
||||
// ARM versions
|
||||
enum {
|
||||
INVALID = 0,
|
||||
ARMALL,
|
||||
ARMV4,
|
||||
ARMV4T,
|
||||
ARMV5T,
|
||||
ARMV5TE,
|
||||
ARMV5TEJ,
|
||||
ARMV6,
|
||||
ARM1176JZF_S,
|
||||
ARMVFP2,
|
||||
ARMVFP3,
|
||||
ARMV6K,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
const InstructionSetEncodingItem arm_instruction[] = {
|
||||
{ "vmla", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x0, 9, 11, 0x5, 6, 6, 0, 4, 4, 0 }},
|
||||
{ "vmls", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x0, 9, 11, 0x5, 6, 6, 1, 4, 4, 0 }},
|
||||
{ "vnmla", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x1, 9, 11, 0x5, 6, 6, 1, 4, 4, 0 }},
|
||||
{ "vnmls", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x1, 9, 11, 0x5, 6, 6, 0, 4, 4, 0 }},
|
||||
{ "vnmul", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x2, 9, 11, 0x5, 6, 6, 1, 4, 4, 0 }},
|
||||
{ "vmul", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x2, 9, 11, 0x5, 6, 6, 0, 4, 4, 0 }},
|
||||
{ "vadd", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x3, 9, 11, 0x5, 6, 6, 0, 4, 4, 0 }},
|
||||
{ "vsub", 5, ARMVFP2, { 23, 27, 0x1C, 20, 21, 0x3, 9, 11, 0x5, 6, 6, 1, 4, 4, 0 }},
|
||||
{ "vdiv", 5, ARMVFP2, { 23, 27, 0x1D, 20, 21, 0x0, 9, 11, 0x5, 6, 6, 0, 4, 4, 0 }},
|
||||
{ "vmov(i)", 4, ARMVFP3, { 23, 27, 0x1D, 20, 21, 0x3, 9, 11, 0x5, 4, 7, 0 }},
|
||||
{ "vmov(r)", 5, ARMVFP3, { 23, 27, 0x1D, 16, 21, 0x30, 9, 11, 0x5, 6, 7, 1, 4, 4, 0 }},
|
||||
{ "vabs", 5, ARMVFP2, { 23, 27, 0x1D, 16, 21, 0x30, 9, 11, 0x5, 6, 7, 3, 4, 4, 0 }},
|
||||
{ "vneg", 5, ARMVFP2, { 23, 27, 0x1D, 17, 21, 0x18, 9, 11, 0x5, 6, 7, 1, 4, 4, 0 }},
|
||||
{ "vsqrt", 5, ARMVFP2, { 23, 27, 0x1D, 16, 21, 0x31, 9, 11, 0x5, 6, 7, 3, 4, 4, 0 }},
|
||||
{ "vcmp", 5, ARMVFP2, { 23, 27, 0x1D, 16, 21, 0x34, 9, 11, 0x5, 6, 6, 1, 4, 4, 0 }},
|
||||
{ "vcmp2", 5, ARMVFP2, { 23, 27, 0x1D, 16, 21, 0x35, 9, 11, 0x5, 0, 6, 0x40 }},
|
||||
{ "vcvt(bds)", 5, ARMVFP2, { 23, 27, 0x1D, 16, 21, 0x37, 9, 11, 0x5, 6, 7, 3, 4, 4, 0 }},
|
||||
{ "vcvt(bff)", 6, ARMVFP3, { 23, 27, 0x1D, 19, 21, 0x7, 17, 17, 0x1, 9, 11, 5, 6, 6, 1 }},
|
||||
{ "vcvt(bfi)", 5, ARMVFP2, { 23, 27, 0x1D, 19, 21, 0x7, 9, 11, 0x5, 6, 6, 1, 4, 4, 0 }},
|
||||
{ "vmovbrs", 3, ARMVFP2, { 21, 27, 0x70, 8, 11, 0xA, 0, 6, 0x10 }},
|
||||
{ "vmsr", 2, ARMVFP2, { 20, 27, 0xEE, 0, 11, 0xA10 }},
|
||||
{ "vmovbrc", 4, ARMVFP2, { 23, 27, 0x1C, 20, 20, 0x0, 8, 11, 0xB, 0, 4, 0x10 }},
|
||||
{ "vmrs", 2, ARMVFP2, { 20, 27, 0xEF, 0, 11, 0xA10 }},
|
||||
{ "vmovbcr", 4, ARMVFP2, { 24, 27, 0xE, 20, 20, 1, 8, 11, 0xB, 0, 4, 0x10 }},
|
||||
{ "vmovbrrss", 3, ARMVFP2, { 21, 27, 0x62, 8, 11, 0xA, 4, 4, 1 }},
|
||||
{ "vmovbrrd", 3, ARMVFP2, { 21, 27, 0x62, 6, 11, 0x2C, 4, 4, 1 }},
|
||||
{ "vstr", 3, ARMVFP2, { 24, 27, 0xD, 20, 21, 0, 9, 11, 5 }},
|
||||
{ "vpush", 3, ARMVFP2, { 23, 27, 0x1A, 16, 21, 0x2D, 9, 11, 5 }},
|
||||
{ "vstm", 3, ARMVFP2, { 25, 27, 0x6, 20, 20, 0, 9, 11, 5 }},
|
||||
{ "vpop", 3, ARMVFP2, { 23, 27, 0x19, 16, 21, 0x3D, 9, 11, 5 }},
|
||||
{ "vldr", 3, ARMVFP2, { 24, 27, 0xD, 20, 21, 1, 9, 11, 5 }},
|
||||
{ "vldm", 3, ARMVFP2, { 25, 27, 0x6, 20, 20, 1, 9, 11, 5 }},
|
||||
|
||||
{ "srs", 4, 6, { 25, 31, 0x0000007c, 22, 22, 0x00000001, 16, 20, 0x0000000d, 8, 11, 0x00000005 }},
|
||||
{ "rfe", 4, 6, { 25, 31, 0x0000007c, 22, 22, 0x00000000, 20, 20, 0x00000001, 8, 11, 0x0000000a }},
|
||||
{ "bkpt", 2, 3, { 20, 27, 0x00000012, 4, 7, 0x00000007 }},
|
||||
{ "blx", 1, 3, { 25, 31, 0x0000007d }},
|
||||
{ "cps", 3, 6, { 20, 31, 0x00000f10, 16, 16, 0x00000000, 5, 5, 0x00000000 }},
|
||||
{ "pld", 4, 4, { 26, 31, 0x0000003d, 24, 24, 0x00000001, 20, 22, 0x00000005, 12, 15, 0x0000000f }},
|
||||
{ "setend", 2, 6, { 16, 31, 0x0000f101, 4, 7, 0x00000000 }},
|
||||
{ "clrex", 1, 6, { 0, 31, 0xf57ff01f }},
|
||||
{ "rev16", 2, 6, { 16, 27, 0x000006bf, 4, 11, 0x000000fb }},
|
||||
{ "usad8", 3, 6, { 20, 27, 0x00000078, 12, 15, 0x0000000f, 4, 7, 0x00000001 }},
|
||||
{ "sxtb", 2, 6, { 16, 27, 0x000006af, 4, 7, 0x00000007 }},
|
||||
{ "uxtb", 2, 6, { 16, 27, 0x000006ef, 4, 7, 0x00000007 }},
|
||||
{ "sxth", 2, 6, { 16, 27, 0x000006bf, 4, 7, 0x00000007 }},
|
||||
{ "sxtb16", 2, 6, { 16, 27, 0x0000068f, 4, 7, 0x00000007 }},
|
||||
{ "uxth", 2, 6, { 16, 27, 0x000006ff, 4, 7, 0x00000007 }},
|
||||
{ "uxtb16", 2, 6, { 16, 27, 0x000006cf, 4, 7, 0x00000007 }},
|
||||
{ "cpy", 2, 6, { 20, 27, 0x0000001a, 4, 11, 0x00000000 }},
|
||||
{ "uxtab", 2, 6, { 20, 27, 0x0000006e, 4, 9, 0x00000007 }},
|
||||
{ "ssub8", 2, 6, { 20, 27, 0x00000061, 4, 7, 0x0000000f }},
|
||||
{ "shsub8", 2, 6, { 20, 27, 0x00000063, 4, 7, 0x0000000f }},
|
||||
{ "ssubaddx", 2, 6, { 20, 27, 0x00000061, 4, 7, 0x00000005 }},
|
||||
{ "strex", 2, 6, { 20, 27, 0x00000018, 4, 7, 0x00000009 }},
|
||||
{ "strexb", 2, 7, { 20, 27, 0x0000001c, 4, 7, 0x00000009 }},
|
||||
{ "swp", 2, 0, { 20, 27, 0x00000010, 4, 7, 0x00000009 }},
|
||||
{ "swpb", 2, 0, { 20, 27, 0x00000014, 4, 7, 0x00000009 }},
|
||||
{ "ssub16", 2, 6, { 20, 27, 0x00000061, 4, 7, 0x00000007 }},
|
||||
{ "ssat16", 2, 6, { 20, 27, 0x0000006a, 4, 7, 0x00000003 }},
|
||||
{ "shsubaddx", 2, 6, { 20, 27, 0x00000063, 4, 7, 0x00000005 }},
|
||||
{ "qsubaddx", 2, 6, { 20, 27, 0x00000062, 4, 7, 0x00000005 }},
|
||||
{ "shaddsubx", 2, 6, { 20, 27, 0x00000063, 4, 7, 0x00000003 }},
|
||||
{ "shadd8", 2, 6, { 20, 27, 0x00000063, 4, 7, 0x00000009 }},
|
||||
{ "shadd16", 2, 6, { 20, 27, 0x00000063, 4, 7, 0x00000001 }},
|
||||
{ "sel", 2, 6, { 20, 27, 0x00000068, 4, 7, 0x0000000b }},
|
||||
{ "saddsubx", 2, 6, { 20, 27, 0x00000061, 4, 7, 0x00000003 }},
|
||||
{ "sadd8", 2, 6, { 20, 27, 0x00000061, 4, 7, 0x00000009 }},
|
||||
{ "sadd16", 2, 6, { 20, 27, 0x00000061, 4, 7, 0x00000001 }},
|
||||
{ "shsub16", 2, 6, { 20, 27, 0x00000063, 4, 7, 0x00000007 }},
|
||||
{ "umaal", 2, 6, { 20, 27, 0x00000004, 4, 7, 0x00000009 }},
|
||||
{ "uxtab16", 2, 6, { 20, 27, 0x0000006c, 4, 7, 0x00000007 }},
|
||||
{ "usubaddx", 2, 6, { 20, 27, 0x00000065, 4, 7, 0x00000005 }},
|
||||
{ "usub8", 2, 6, { 20, 27, 0x00000065, 4, 7, 0x0000000f }},
|
||||
{ "usub16", 2, 6, { 20, 27, 0x00000065, 4, 7, 0x00000007 }},
|
||||
{ "usat16", 2, 6, { 20, 27, 0x0000006e, 4, 7, 0x00000003 }},
|
||||
{ "usada8", 2, 6, { 20, 27, 0x00000078, 4, 7, 0x00000001 }},
|
||||
{ "uqsubaddx", 2, 6, { 20, 27, 0x00000066, 4, 7, 0x00000005 }},
|
||||
{ "uqsub8", 2, 6, { 20, 27, 0x00000066, 4, 7, 0x0000000f }},
|
||||
{ "uqsub16", 2, 6, { 20, 27, 0x00000066, 4, 7, 0x00000007 }},
|
||||
{ "uqaddsubx", 2, 6, { 20, 27, 0x00000066, 4, 7, 0x00000003 }},
|
||||
{ "uqadd8", 2, 6, { 20, 27, 0x00000066, 4, 7, 0x00000009 }},
|
||||
{ "uqadd16", 2, 6, { 20, 27, 0x00000066, 4, 7, 0x00000001 }},
|
||||
{ "sxtab", 2, 6, { 20, 27, 0x0000006a, 4, 7, 0x00000007 }},
|
||||
{ "uhsubaddx", 2, 6, { 20, 27, 0x00000067, 4, 7, 0x00000005 }},
|
||||
{ "uhsub8", 2, 6, { 20, 27, 0x00000067, 4, 7, 0x0000000f }},
|
||||
{ "uhsub16", 2, 6, { 20, 27, 0x00000067, 4, 7, 0x00000007 }},
|
||||
{ "uhaddsubx", 2, 6, { 20, 27, 0x00000067, 4, 7, 0x00000003 }},
|
||||
{ "uhadd8", 2, 6, { 20, 27, 0x00000067, 4, 7, 0x00000009 }},
|
||||
{ "uhadd16", 2, 6, { 20, 27, 0x00000067, 4, 7, 0x00000001 }},
|
||||
{ "uaddsubx", 2, 6, { 20, 27, 0x00000065, 4, 7, 0x00000003 }},
|
||||
{ "uadd8", 2, 6, { 20, 27, 0x00000065, 4, 7, 0x00000009 }},
|
||||
{ "uadd16", 2, 6, { 20, 27, 0x00000065, 4, 7, 0x00000001 }},
|
||||
{ "sxtah", 2, 6, { 20, 27, 0x0000006b, 4, 7, 0x00000007 }},
|
||||
{ "sxtab16", 2, 6, { 20, 27, 0x00000068, 4, 7, 0x00000007 }},
|
||||
{ "qadd8", 2, 6, { 20, 27, 0x00000062, 4, 7, 0x00000009 }},
|
||||
{ "bxj", 2, 5, { 20, 27, 0x00000012, 4, 7, 0x00000002 }},
|
||||
{ "clz", 2, 3, { 20, 27, 0x00000016, 4, 7, 0x00000001 }},
|
||||
{ "uxtah", 2, 6, { 20, 27, 0x0000006f, 4, 7, 0x00000007 }},
|
||||
{ "bx", 2, 2, { 20, 27, 0x00000012, 4, 7, 0x00000001 }},
|
||||
{ "rev", 2, 6, { 20, 27, 0x0000006b, 4, 7, 0x00000003 }},
|
||||
{ "blx", 2, 3, { 20, 27, 0x00000012, 4, 7, 0x00000003 }},
|
||||
{ "revsh", 2, 6, { 20, 27, 0x0000006f, 4, 7, 0x0000000b }},
|
||||
{ "qadd", 2, 4, { 20, 27, 0x00000010, 4, 7, 0x00000005 }},
|
||||
{ "qadd16", 2, 6, { 20, 27, 0x00000062, 4, 7, 0x00000001 }},
|
||||
{ "qaddsubx", 2, 6, { 20, 27, 0x00000062, 4, 7, 0x00000003 }},
|
||||
{ "ldrex", 2, 0, { 20, 27, 0x00000019, 4, 7, 0x00000009 }},
|
||||
{ "qdadd", 2, 4, { 20, 27, 0x00000014, 4, 7, 0x00000005 }},
|
||||
{ "qdsub", 2, 4, { 20, 27, 0x00000016, 4, 7, 0x00000005 }},
|
||||
{ "qsub", 2, 4, { 20, 27, 0x00000012, 4, 7, 0x00000005 }},
|
||||
{ "ldrexb", 2, 7, { 20, 27, 0x0000001d, 4, 7, 0x00000009 }},
|
||||
{ "qsub8", 2, 6, { 20, 27, 0x00000062, 4, 7, 0x0000000f }},
|
||||
{ "qsub16", 2, 6, { 20, 27, 0x00000062, 4, 7, 0x00000007 }},
|
||||
{ "smuad", 4, 6, { 20, 27, 0x00000070, 12, 15, 0x0000000f, 6, 7, 0x00000000, 4, 4, 0x00000001 }},
|
||||
{ "smmul", 4, 6, { 20, 27, 0x00000075, 12, 15, 0x0000000f, 6, 7, 0x00000000, 4, 4, 0x00000001 }},
|
||||
{ "smusd", 4, 6, { 20, 27, 0x00000070, 12, 15, 0x0000000f, 6, 7, 0x00000001, 4, 4, 0x00000001 }},
|
||||
{ "smlsd", 3, 6, { 20, 27, 0x00000070, 6, 7, 0x00000001, 4, 4, 0x00000001 }},
|
||||
{ "smlsld", 3, 6, { 20, 27, 0x00000074, 6, 7, 0x00000001, 4, 4, 0x00000001 }},
|
||||
{ "smmla", 3, 6, { 20, 27, 0x00000075, 6, 7, 0x00000000, 4, 4, 0x00000001 }},
|
||||
{ "smmls", 3, 6, { 20, 27, 0x00000075, 6, 7, 0x00000003, 4, 4, 0x00000001 }},
|
||||
{ "smlald", 3, 6, { 20, 27, 0x00000074, 6, 7, 0x00000000, 4, 4, 0x00000001 }},
|
||||
{ "smlad", 3, 6, { 20, 27, 0x00000070, 6, 7, 0x00000000, 4, 4, 0x00000001 }},
|
||||
{ "smlaw", 3, 4, { 20, 27, 0x00000012, 7, 7, 0x00000001, 4, 5, 0x00000000 }},
|
||||
{ "smulw", 3, 4, { 20, 27, 0x00000012, 7, 7, 0x00000001, 4, 5, 0x00000002 }},
|
||||
{ "pkhtb", 2, 6, { 20, 27, 0x00000068, 4, 6, 0x00000005 }},
|
||||
{ "pkhbt", 2, 6, { 20, 27, 0x00000068, 4, 6, 0x00000001 }},
|
||||
{ "smul", 3, 4, { 20, 27, 0x00000016, 7, 7, 0x00000001, 4, 4, 0x00000000 }},
|
||||
{ "smlalxy", 3, 4, { 20, 27, 0x00000014, 7, 7, 0x00000001, 4, 4, 0x00000000 }},
|
||||
{ "smla", 3, 4, { 20, 27, 0x00000010, 7, 7, 0x00000001, 4, 4, 0x00000000 }},
|
||||
{ "mcrr", 1, 6, { 20, 27, 0x000000c4 }},
|
||||
{ "mrrc", 1, 6, { 20, 27, 0x000000c5 }},
|
||||
{ "cmp", 2, 0, { 26, 27, 0x00000000, 20, 24, 0x00000015 }},
|
||||
{ "tst", 2, 0, { 26, 27, 0x00000000, 20, 24, 0x00000011 }},
|
||||
{ "teq", 2, 0, { 26, 27, 0x00000000, 20, 24, 0x00000013 }},
|
||||
{ "cmn", 2, 0, { 26, 27, 0x00000000, 20, 24, 0x00000017 }},
|
||||
{ "smull", 2, 0, { 21, 27, 0x00000006, 4, 7, 0x00000009 }},
|
||||
{ "umull", 2, 0, { 21, 27, 0x00000004, 4, 7, 0x00000009 }},
|
||||
{ "umlal", 2, 0, { 21, 27, 0x00000005, 4, 7, 0x00000009 }},
|
||||
{ "smlal", 2, 0, { 21, 27, 0x00000007, 4, 7, 0x00000009 }},
|
||||
{ "mul", 2, 0, { 21, 27, 0x00000000, 4, 7, 0x00000009 }},
|
||||
{ "mla", 2, 0, { 21, 27, 0x00000001, 4, 7, 0x00000009 }},
|
||||
{ "ssat", 2, 6, { 21, 27, 0x00000035, 4, 5, 0x00000001 }},
|
||||
{ "usat", 2, 6, { 21, 27, 0x00000037, 4, 5, 0x00000001 }},
|
||||
{ "mrs", 4, 0, { 23, 27, 0x00000002, 20, 21, 0x00000000, 16, 19, 0x0000000f, 0, 11, 0x00000000 }},
|
||||
{ "msr", 3, 0, { 23, 27, 0x00000002, 20, 21, 0x00000002, 4, 7, 0x00000000 }},
|
||||
{ "and", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000000 }},
|
||||
{ "bic", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x0000000e }},
|
||||
{ "ldm", 3, 0, { 25, 27, 0x00000004, 20, 22, 0x00000005, 15, 15, 0x00000000 }},
|
||||
{ "eor", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000001 }},
|
||||
{ "add", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000004 }},
|
||||
{ "rsb", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000003 }},
|
||||
{ "rsc", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000007 }},
|
||||
{ "sbc", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000006 }},
|
||||
{ "adc", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000005 }},
|
||||
{ "sub", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x00000002 }},
|
||||
{ "orr", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x0000000c }},
|
||||
{ "mvn", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x0000000f }},
|
||||
{ "mov", 2, 0, { 26, 27, 0x00000000, 21, 24, 0x0000000d }},
|
||||
{ "stm", 2, 0, { 25, 27, 0x00000004, 20, 22, 0x00000004 }},
|
||||
{ "ldm", 4, 0, { 25, 27, 0x00000004, 22, 22, 0x00000001, 20, 20, 0x00000001, 15, 15, 0x00000001 }},
|
||||
{ "ldrsh", 3, 2, { 25, 27, 0x00000000, 20, 20, 0x00000001, 4, 7, 0x0000000f }},
|
||||
{ "stm", 3, 0, { 25, 27, 0x00000004, 22, 22, 0x00000000, 20, 20, 0x00000000 }},
|
||||
{ "ldm", 3, 0, { 25, 27, 0x00000004, 22, 22, 0x00000000, 20, 20, 0x00000001 }},
|
||||
{ "ldrsb", 3, 2, { 25, 27, 0x00000000, 20, 20, 0x00000001, 4, 7, 0x0000000d }},
|
||||
{ "strd", 3, 4, { 25, 27, 0x00000000, 20, 20, 0x00000000, 4, 7, 0x0000000f }},
|
||||
{ "ldrh", 3, 0, { 25, 27, 0x00000000, 20, 20, 0x00000001, 4, 7, 0x0000000b }},
|
||||
{ "strh", 3, 0, { 25, 27, 0x00000000, 20, 20, 0x00000000, 4, 7, 0x0000000b }},
|
||||
{ "ldrd", 3, 4, { 25, 27, 0x00000000, 20, 20, 0x00000000, 4, 7, 0x0000000d }},
|
||||
{ "strt", 3, 0, { 26, 27, 0x00000001, 24, 24, 0x00000000, 20, 22, 0x00000002 }},
|
||||
{ "strbt", 3, 0, { 26, 27, 0x00000001, 24, 24, 0x00000000, 20, 22, 0x00000006 }},
|
||||
{ "ldrbt", 3, 0, { 26, 27, 0x00000001, 24, 24, 0x00000000, 20, 22, 0x00000007 }},
|
||||
{ "ldrt", 3, 0, { 26, 27, 0x00000001, 24, 24, 0x00000000, 20, 22, 0x00000003 }},
|
||||
{ "mrc", 3, 6, { 24, 27, 0x0000000e, 20, 20, 0x00000001, 4, 4, 0x00000001 }},
|
||||
{ "mcr", 3, 0, { 24, 27, 0x0000000e, 20, 20, 0x00000000, 4, 4, 0x00000001 }},
|
||||
{ "msr", 3, 0, { 23, 27, 0x00000006, 20, 21, 0x00000002, 22, 22, 0x00000001 }},
|
||||
{ "msr", 4, 0, { 23, 27, 0x00000006, 20, 21, 0x00000002, 22, 22, 0x00000000, 16, 19, 0x00000004 }},
|
||||
{ "msr", 5, 0, { 23, 27, 0x00000006, 20, 21, 0x00000002, 22, 22, 0x00000000, 19, 19, 0x00000001, 16, 17, 0x00000000 }},
|
||||
{ "msr", 4, 0, { 23, 27, 0x00000006, 20, 21, 0x00000002, 22, 22, 0x00000000, 16, 17, 0x00000001 }},
|
||||
{ "msr", 4, 0, { 23, 27, 0x00000006, 20, 21, 0x00000002, 22, 22, 0x00000000, 17, 17, 0x00000001 }},
|
||||
{ "ldrb", 3, 0, { 26, 27, 0x00000001, 22, 22, 0x00000001, 20, 20, 0x00000001 }},
|
||||
{ "strb", 3, 0, { 26, 27, 0x00000001, 22, 22, 0x00000001, 20, 20, 0x00000000 }},
|
||||
{ "ldr", 4, 0, { 28, 31, 0x0000000e, 26, 27, 0x00000001, 22, 22, 0x00000000, 20, 20, 0x00000001 }},
|
||||
{ "ldrcond", 3, 0, { 26, 27, 0x00000001, 22, 22, 0x00000000, 20, 20, 0x00000001 }},
|
||||
{ "str", 3, 0, { 26, 27, 0x00000001, 22, 22, 0x00000000, 20, 20, 0x00000000 }},
|
||||
{ "cdp", 2, 0, { 24, 27, 0x0000000e, 4, 4, 0x00000000 }},
|
||||
{ "stc", 2, 0, { 25, 27, 0x00000006, 20, 20, 0x00000000 }},
|
||||
{ "ldc", 2, 0, { 25, 27, 0x00000006, 20, 20, 0x00000001 }},
|
||||
{ "ldrexd", 2, ARMV6K, { 20, 27, 0x0000001B, 4, 7, 0x00000009 }},
|
||||
{ "strexd", 2, ARMV6K, { 20, 27, 0x0000001A, 4, 7, 0x00000009 }},
|
||||
{ "ldrexh", 2, ARMV6K, { 20, 27, 0x0000001F, 4, 7, 0x00000009 }},
|
||||
{ "strexh", 2, ARMV6K, { 20, 27, 0x0000001E, 4, 7, 0x00000009 }},
|
||||
{ "nop", 5, ARMV6K, { 23, 27, 0x00000006, 22, 22, 0x00000000, 20, 21, 0x00000002, 16, 19, 0x00000000, 0, 7, 0x00000000 }},
|
||||
{ "yield", 5, ARMV6K, { 23, 27, 0x00000006, 22, 22, 0x00000000, 20, 21, 0x00000002, 16, 19, 0x00000000, 0, 7, 0x00000001 }},
|
||||
{ "wfe", 5, ARMV6K, { 23, 27, 0x00000006, 22, 22, 0x00000000, 20, 21, 0x00000002, 16, 19, 0x00000000, 0, 7, 0x00000002 }},
|
||||
{ "wfi", 5, ARMV6K, { 23, 27, 0x00000006, 22, 22, 0x00000000, 20, 21, 0x00000002, 16, 19, 0x00000000, 0, 7, 0x00000003 }},
|
||||
{ "sev", 5, ARMV6K, { 23, 27, 0x00000006, 22, 22, 0x00000000, 20, 21, 0x00000002, 16, 19, 0x00000000, 0, 7, 0x00000004 }},
|
||||
{ "swi", 1, 0, { 24, 27, 0x0000000f }},
|
||||
{ "bbl", 1, 0, { 25, 27, 0x00000005 }},
|
||||
};
|
||||
|
||||
|
||||
const InstructionSetEncodingItem arm_exclusion_code[] = {
|
||||
{ "vmla", 0, ARMVFP2, { 0 }},
|
||||
{ "vmls", 0, ARMVFP2, { 0 }},
|
||||
{ "vnmla", 0, ARMVFP2, { 0 }},
|
||||
{ "vnmls", 0, ARMVFP2, { 0 }},
|
||||
{ "vnmul", 0, ARMVFP2, { 0 }},
|
||||
{ "vmul", 0, ARMVFP2, { 0 }},
|
||||
{ "vadd", 0, ARMVFP2, { 0 }},
|
||||
{ "vsub", 0, ARMVFP2, { 0 }},
|
||||
{ "vdiv", 0, ARMVFP2, { 0 }},
|
||||
{ "vmov(i)", 0, ARMVFP3, { 0 }},
|
||||
{ "vmov(r)", 0, ARMVFP3, { 0 }},
|
||||
{ "vabs", 0, ARMVFP2, { 0 }},
|
||||
{ "vneg", 0, ARMVFP2, { 0 }},
|
||||
{ "vsqrt", 0, ARMVFP2, { 0 }},
|
||||
{ "vcmp", 0, ARMVFP2, { 0 }},
|
||||
{ "vcmp2", 0, ARMVFP2, { 0 }},
|
||||
{ "vcvt(bff)", 0, ARMVFP3, { 4, 4, 1 }},
|
||||
{ "vcvt(bds)", 0, ARMVFP2, { 0 }},
|
||||
{ "vcvt(bfi)", 0, ARMVFP2, { 0 }},
|
||||
{ "vmovbrs", 0, ARMVFP2, { 0 }},
|
||||
{ "vmsr", 0, ARMVFP2, { 0 }},
|
||||
{ "vmovbrc", 0, ARMVFP2, { 0 }},
|
||||
{ "vmrs", 0, ARMVFP2, { 0 }},
|
||||
{ "vmovbcr", 0, ARMVFP2, { 0 }},
|
||||
{ "vmovbrrss", 0, ARMVFP2, { 0 }},
|
||||
{ "vmovbrrd", 0, ARMVFP2, { 0 }},
|
||||
{ "vstr", 0, ARMVFP2, { 0 }},
|
||||
{ "vpush", 0, ARMVFP2, { 0 }},
|
||||
{ "vstm", 0, ARMVFP2, { 0 }},
|
||||
{ "vpop", 0, ARMVFP2, { 0 }},
|
||||
{ "vldr", 0, ARMVFP2, { 0 }},
|
||||
{ "vldm", 0, ARMVFP2, { 0 }},
|
||||
|
||||
{ "srs", 0, 6, { 0 }},
|
||||
{ "rfe", 0, 6, { 0 }},
|
||||
{ "bkpt", 0, 3, { 0 }},
|
||||
{ "blx", 0, 3, { 0 }},
|
||||
{ "cps", 0, 6, { 0 }},
|
||||
{ "pld", 0, 4, { 0 }},
|
||||
{ "setend", 0, 6, { 0 }},
|
||||
{ "clrex", 0, 6, { 0 }},
|
||||
{ "rev16", 0, 6, { 0 }},
|
||||
{ "usad8", 0, 6, { 0 }},
|
||||
{ "sxtb", 0, 6, { 0 }},
|
||||
{ "uxtb", 0, 6, { 0 }},
|
||||
{ "sxth", 0, 6, { 0 }},
|
||||
{ "sxtb16", 0, 6, { 0 }},
|
||||
{ "uxth", 0, 6, { 0 }},
|
||||
{ "uxtb16", 0, 6, { 0 }},
|
||||
{ "cpy", 0, 6, { 0 }},
|
||||
{ "uxtab", 0, 6, { 0 }},
|
||||
{ "ssub8", 0, 6, { 0 }},
|
||||
{ "shsub8", 0, 6, { 0 }},
|
||||
{ "ssubaddx", 0, 6, { 0 }},
|
||||
{ "strex", 0, 6, { 0 }},
|
||||
{ "strexb", 0, 7, { 0 }},
|
||||
{ "swp", 0, 0, { 0 }},
|
||||
{ "swpb", 0, 0, { 0 }},
|
||||
{ "ssub16", 0, 6, { 0 }},
|
||||
{ "ssat16", 0, 6, { 0 }},
|
||||
{ "shsubaddx", 0, 6, { 0 }},
|
||||
{ "qsubaddx", 0, 6, { 0 }},
|
||||
{ "shaddsubx", 0, 6, { 0 }},
|
||||
{ "shadd8", 0, 6, { 0 }},
|
||||
{ "shadd16", 0, 6, { 0 }},
|
||||
{ "sel", 0, 6, { 0 }},
|
||||
{ "saddsubx", 0, 6, { 0 }},
|
||||
{ "sadd8", 0, 6, { 0 }},
|
||||
{ "sadd16", 0, 6, { 0 }},
|
||||
{ "shsub16", 0, 6, { 0 }},
|
||||
{ "umaal", 0, 6, { 0 }},
|
||||
{ "uxtab16", 0, 6, { 0 }},
|
||||
{ "usubaddx", 0, 6, { 0 }},
|
||||
{ "usub8", 0, 6, { 0 }},
|
||||
{ "usub16", 0, 6, { 0 }},
|
||||
{ "usat16", 0, 6, { 0 }},
|
||||
{ "usada8", 0, 6, { 0 }},
|
||||
{ "uqsubaddx", 0, 6, { 0 }},
|
||||
{ "uqsub8", 0, 6, { 0 }},
|
||||
{ "uqsub16", 0, 6, { 0 }},
|
||||
{ "uqaddsubx", 0, 6, { 0 }},
|
||||
{ "uqadd8", 0, 6, { 0 }},
|
||||
{ "uqadd16", 0, 6, { 0 }},
|
||||
{ "sxtab", 0, 6, { 0 }},
|
||||
{ "uhsubaddx", 0, 6, { 0 }},
|
||||
{ "uhsub8", 0, 6, { 0 }},
|
||||
{ "uhsub16", 0, 6, { 0 }},
|
||||
{ "uhaddsubx", 0, 6, { 0 }},
|
||||
{ "uhadd8", 0, 6, { 0 }},
|
||||
{ "uhadd16", 0, 6, { 0 }},
|
||||
{ "uaddsubx", 0, 6, { 0 }},
|
||||
{ "uadd8", 0, 6, { 0 }},
|
||||
{ "uadd16", 0, 6, { 0 }},
|
||||
{ "sxtah", 0, 6, { 0 }},
|
||||
{ "sxtab16", 0, 6, { 0 }},
|
||||
{ "qadd8", 0, 6, { 0 }},
|
||||
{ "bxj", 0, 5, { 0 }},
|
||||
{ "clz", 0, 3, { 0 }},
|
||||
{ "uxtah", 0, 6, { 0 }},
|
||||
{ "bx", 0, 2, { 0 }},
|
||||
{ "rev", 0, 6, { 0 }},
|
||||
{ "blx", 0, 3, { 0 }},
|
||||
{ "revsh", 0, 6, { 0 }},
|
||||
{ "qadd", 0, 4, { 0 }},
|
||||
{ "qadd16", 0, 6, { 0 }},
|
||||
{ "qaddsubx", 0, 6, { 0 }},
|
||||
{ "ldrex", 0, 0, { 0 }},
|
||||
{ "qdadd", 0, 4, { 0 }},
|
||||
{ "qdsub", 0, 4, { 0 }},
|
||||
{ "qsub", 0, 4, { 0 }},
|
||||
{ "ldrexb", 0, 7, { 0 }},
|
||||
{ "qsub8", 0, 6, { 0 }},
|
||||
{ "qsub16", 0, 6, { 0 }},
|
||||
{ "smuad", 0, 6, { 0 }},
|
||||
{ "smmul", 0, 6, { 0 }},
|
||||
{ "smusd", 0, 6, { 0 }},
|
||||
{ "smlsd", 0, 6, { 0 }},
|
||||
{ "smlsld", 0, 6, { 0 }},
|
||||
{ "smmla", 0, 6, { 0 }},
|
||||
{ "smmls", 0, 6, { 0 }},
|
||||
{ "smlald", 0, 6, { 0 }},
|
||||
{ "smlad", 0, 6, { 0 }},
|
||||
{ "smlaw", 0, 4, { 0 }},
|
||||
{ "smulw", 0, 4, { 0 }},
|
||||
{ "pkhtb", 0, 6, { 0 }},
|
||||
{ "pkhbt", 0, 6, { 0 }},
|
||||
{ "smul", 0, 4, { 0 }},
|
||||
{ "smlal", 0, 4, { 0 }},
|
||||
{ "smla", 0, 4, { 0 }},
|
||||
{ "mcrr", 0, 6, { 0 }},
|
||||
{ "mrrc", 0, 6, { 0 }},
|
||||
{ "cmp", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "tst", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "teq", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "cmn", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "smull", 0, 0, { 0 }},
|
||||
{ "umull", 0, 0, { 0 }},
|
||||
{ "umlal", 0, 0, { 0 }},
|
||||
{ "smlal", 0, 0, { 0 }},
|
||||
{ "mul", 0, 0, { 0 }},
|
||||
{ "mla", 0, 0, { 0 }},
|
||||
{ "ssat", 0, 6, { 0 }},
|
||||
{ "usat", 0, 6, { 0 }},
|
||||
{ "mrs", 0, 0, { 0 }},
|
||||
{ "msr", 0, 0, { 0 }},
|
||||
{ "and", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "bic", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "ldm", 0, 0, { 0 }},
|
||||
{ "eor", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "add", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "rsb", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "rsc", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "sbc", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "adc", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "sub", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "orr", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "mvn", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "mov", 3, 0, { 4, 4, 0x00000001, 7, 7, 0x00000001, 25, 25, 0x00000000 }},
|
||||
{ "stm", 0, 0, { 0 }},
|
||||
{ "ldm", 0, 0, { 0 }},
|
||||
{ "ldrsh", 0, 2, { 0 }},
|
||||
{ "stm", 0, 0, { 0 }},
|
||||
{ "ldm", 0, 0, { 0 }},
|
||||
{ "ldrsb", 0, 2, { 0 }},
|
||||
{ "strd", 0, 4, { 0 }},
|
||||
{ "ldrh", 0, 0, { 0 }},
|
||||
{ "strh", 0, 0, { 0 }},
|
||||
{ "ldrd", 0, 4, { 0 }},
|
||||
{ "strt", 0, 0, { 0 }},
|
||||
{ "strbt", 0, 0, { 0 }},
|
||||
{ "ldrbt", 0, 0, { 0 }},
|
||||
{ "ldrt", 0, 0, { 0 }},
|
||||
{ "mrc", 0, 6, { 0 }},
|
||||
{ "mcr", 0, 0, { 0 }},
|
||||
{ "msr", 0, 0, { 0 }},
|
||||
{ "msr", 0, 0, { 0 }},
|
||||
{ "msr", 0, 0, { 0 }},
|
||||
{ "msr", 0, 0, { 0 }},
|
||||
{ "msr", 0, 0, { 0 }},
|
||||
{ "ldrb", 0, 0, { 0 }},
|
||||
{ "strb", 0, 0, { 0 }},
|
||||
{ "ldr", 0, 0, { 0 }},
|
||||
{ "ldrcond", 1, 0, { 28, 31, 0x0000000e }},
|
||||
{ "str", 0, 0, { 0 }},
|
||||
{ "cdp", 0, 0, { 0 }},
|
||||
{ "stc", 0, 0, { 0 }},
|
||||
{ "ldc", 0, 0, { 0 }},
|
||||
{ "ldrexd", 0, ARMV6K, { 0 }},
|
||||
{ "strexd", 0, ARMV6K, { 0 }},
|
||||
{ "ldrexh", 0, ARMV6K, { 0 }},
|
||||
{ "strexh", 0, ARMV6K, { 0 }},
|
||||
{ "nop", 0, ARMV6K, { 0 }},
|
||||
{ "yield", 0, ARMV6K, { 0 }},
|
||||
{ "wfe", 0, ARMV6K, { 0 }},
|
||||
{ "wfi", 0, ARMV6K, { 0 }},
|
||||
{ "sev", 0, ARMV6K, { 0 }},
|
||||
{ "swi", 0, 0, { 0 }},
|
||||
{ "bbl", 0, 0, { 0 }},
|
||||
|
||||
{ "bl_1_thumb", 0, INVALID, { 0 }}, // Should be table[-4]
|
||||
{ "bl_2_thumb", 0, INVALID, { 0 }}, // Should be located at the end of the table[-3]
|
||||
{ "blx_1_thumb", 0, INVALID, { 0 }}, // Should be located at table[-2]
|
||||
{ "invalid", 0, INVALID, { 0 }}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
ARMDecodeStatus DecodeARMInstruction(u32 instr, int* idx) {
|
||||
int n = 0;
|
||||
int base = 0;
|
||||
int instr_slots = sizeof(arm_instruction) / sizeof(InstructionSetEncodingItem);
|
||||
ARMDecodeStatus ret = ARMDecodeStatus::FAILURE;
|
||||
|
||||
for (int i = 0; i < instr_slots; i++) {
|
||||
n = arm_instruction[i].attribute_value;
|
||||
base = 0;
|
||||
|
||||
// 3DS has no VFP3 support
|
||||
if (arm_instruction[i].version == ARMVFP3)
|
||||
continue;
|
||||
|
||||
while (n) {
|
||||
if (arm_instruction[i].content[base + 1] == 31 &&
|
||||
arm_instruction[i].content[base] == 0) {
|
||||
// clrex
|
||||
if (instr != arm_instruction[i].content[base + 2]) {
|
||||
break;
|
||||
}
|
||||
} else if (BITS(instr, arm_instruction[i].content[base],
|
||||
arm_instruction[i].content[base + 1]) !=
|
||||
arm_instruction[i].content[base + 2]) {
|
||||
break;
|
||||
}
|
||||
base += 3;
|
||||
n--;
|
||||
}
|
||||
|
||||
// All conditions are satisfied.
|
||||
if (n == 0)
|
||||
ret = ARMDecodeStatus::SUCCESS;
|
||||
|
||||
if (ret == ARMDecodeStatus::SUCCESS) {
|
||||
n = arm_exclusion_code[i].attribute_value;
|
||||
if (n != 0) {
|
||||
base = 0;
|
||||
while (n) {
|
||||
if (BITS(instr, arm_exclusion_code[i].content[base],
|
||||
arm_exclusion_code[i].content[base + 1]) !=
|
||||
arm_exclusion_code[i].content[base + 2]) {
|
||||
break;
|
||||
}
|
||||
base += 3;
|
||||
n--;
|
||||
}
|
||||
|
||||
// All conditions are satisfied.
|
||||
if (n == 0)
|
||||
ret = ARMDecodeStatus::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == ARMDecodeStatus::SUCCESS) {
|
||||
*idx = i;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
11
src/core/arm/dyncom/arm_dyncom_dec.h
Normal file
11
src/core/arm/dyncom/arm_dyncom_dec.h
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2012 Michael Kang, 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
enum class ARMDecodeStatus { SUCCESS, FAILURE };
|
||||
|
||||
ARMDecodeStatus DecodeARMInstruction(u32 instr, int* idx);
|
||||
4590
src/core/arm/dyncom/arm_dyncom_interpreter.cpp
Normal file
4590
src/core/arm/dyncom/arm_dyncom_interpreter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
9
src/core/arm/dyncom/arm_dyncom_interpreter.h
Normal file
9
src/core/arm/dyncom/arm_dyncom_interpreter.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
struct ARMul_State;
|
||||
|
||||
unsigned InterpreterMainLoop(ARMul_State* state);
|
||||
48
src/core/arm/dyncom/arm_dyncom_run.h
Normal file
48
src/core/arm/dyncom/arm_dyncom_run.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Copyright (C)
|
||||
* 2011 - Michael.Kang blackfin.kang@gmail.com
|
||||
* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
|
||||
/**
|
||||
* Checks if the PC is being read, and if so, word-aligns it.
|
||||
* Used with address calculations.
|
||||
*
|
||||
* @param cpu The ARM CPU state instance.
|
||||
* @param Rn The register being read.
|
||||
*
|
||||
* @return If the PC is being read, then the word-aligned PC value is returned.
|
||||
* If the PC is not being read, then the value stored in the register is returned.
|
||||
*/
|
||||
inline u32 CHECK_READ_REG15_WA(const ARMul_State* cpu, int Rn) {
|
||||
return (Rn == 15) ? ((cpu->Reg[15] & ~0x3) + cpu->GetInstructionSize() * 2) : cpu->Reg[Rn];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the PC. Used for data processing operations that use the PC.
|
||||
*
|
||||
* @param cpu The ARM CPU state instance.
|
||||
* @param Rn The register being read.
|
||||
*
|
||||
* @return If the PC is being read, then the incremented PC value is returned.
|
||||
* If the PC is not being read, then the values stored in the register is returned.
|
||||
*/
|
||||
inline u32 CHECK_READ_REG15(const ARMul_State* cpu, int Rn) {
|
||||
return (Rn == 15) ? ((cpu->Reg[15] & ~0x1) + cpu->GetInstructionSize() * 2) : cpu->Reg[Rn];
|
||||
}
|
||||
390
src/core/arm/dyncom/arm_dyncom_thumb.cpp
Normal file
390
src/core/arm/dyncom/arm_dyncom_thumb.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright 2012 Michael Kang, 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
// We can provide simple Thumb simulation by decoding the Thumb instruction into its corresponding
|
||||
// ARM instruction, and using the existing ARM simulator.
|
||||
|
||||
#include "core/arm/dyncom/arm_dyncom_thumb.h"
|
||||
#include "core/arm/skyeye_common/armsupp.h"
|
||||
|
||||
// Decode a 16bit Thumb instruction. The instruction is in the low 16-bits of the tinstr field,
|
||||
// with the following Thumb instruction held in the high 16-bits. Passing in two Thumb instructions
|
||||
// allows easier simulation of the special dual BL instruction.
|
||||
|
||||
ThumbDecodeStatus TranslateThumbInstruction(u32 addr, u32 instr, u32* ainstr, u32* inst_size) {
|
||||
ThumbDecodeStatus valid = ThumbDecodeStatus::UNINITIALIZED;
|
||||
u32 tinstr = GetThumbInstruction(instr, addr);
|
||||
|
||||
*ainstr = 0xDEADC0DE; // Debugging to catch non updates
|
||||
|
||||
switch ((tinstr & 0xF800) >> 11) {
|
||||
case 0: // LSL
|
||||
case 1: // LSR
|
||||
case 2: // ASR
|
||||
*ainstr = 0xE1B00000 // base opcode
|
||||
| ((tinstr & 0x1800) >> (11 - 5)) // shift type
|
||||
| ((tinstr & 0x07C0) << (7 - 6)) // imm5
|
||||
| ((tinstr & 0x0038) >> 3) // Rs
|
||||
| ((tinstr & 0x0007) << 12); // Rd
|
||||
break;
|
||||
|
||||
case 3: // ADD/SUB
|
||||
{
|
||||
static const u32 subset[4] = {
|
||||
0xE0900000, // ADDS Rd,Rs,Rn
|
||||
0xE0500000, // SUBS Rd,Rs,Rn
|
||||
0xE2900000, // ADDS Rd,Rs,#imm3
|
||||
0xE2500000 // SUBS Rd,Rs,#imm3
|
||||
};
|
||||
// It is quicker indexing into a table, than performing switch or conditionals:
|
||||
*ainstr = subset[(tinstr & 0x0600) >> 9] // base opcode
|
||||
| ((tinstr & 0x01C0) >> 6) // Rn or imm3
|
||||
| ((tinstr & 0x0038) << (16 - 3)) // Rs
|
||||
| ((tinstr & 0x0007) << (12 - 0)); // Rd
|
||||
} break;
|
||||
|
||||
case 4: // MOV
|
||||
case 5: // CMP
|
||||
case 6: // ADD
|
||||
case 7: // SUB
|
||||
{
|
||||
static const u32 subset[4] = {
|
||||
0xE3B00000, // MOVS Rd,#imm8
|
||||
0xE3500000, // CMP Rd,#imm8
|
||||
0xE2900000, // ADDS Rd,Rd,#imm8
|
||||
0xE2500000, // SUBS Rd,Rd,#imm8
|
||||
};
|
||||
|
||||
*ainstr = subset[(tinstr & 0x1800) >> 11] // base opcode
|
||||
| ((tinstr & 0x00FF) >> 0) // imm8
|
||||
| ((tinstr & 0x0700) << (16 - 8)) // Rn
|
||||
| ((tinstr & 0x0700) << (12 - 8)); // Rd
|
||||
} break;
|
||||
|
||||
case 8: // Arithmetic and high register transfers
|
||||
|
||||
// TODO: Since the subsets for both Format 4 and Format 5 instructions are made up of
|
||||
// different ARM encodings, we could save the following conditional, and just have one
|
||||
// large subset
|
||||
|
||||
if ((tinstr & (1 << 10)) == 0) {
|
||||
enum otype { t_norm, t_shift, t_neg, t_mul };
|
||||
|
||||
static const struct {
|
||||
u32 opcode;
|
||||
otype type;
|
||||
} subset[16] = {
|
||||
{0xE0100000, t_norm}, // ANDS Rd,Rd,Rs
|
||||
{0xE0300000, t_norm}, // EORS Rd,Rd,Rs
|
||||
{0xE1B00010, t_shift}, // MOVS Rd,Rd,LSL Rs
|
||||
{0xE1B00030, t_shift}, // MOVS Rd,Rd,LSR Rs
|
||||
{0xE1B00050, t_shift}, // MOVS Rd,Rd,ASR Rs
|
||||
{0xE0B00000, t_norm}, // ADCS Rd,Rd,Rs
|
||||
{0xE0D00000, t_norm}, // SBCS Rd,Rd,Rs
|
||||
{0xE1B00070, t_shift}, // MOVS Rd,Rd,ROR Rs
|
||||
{0xE1100000, t_norm}, // TST Rd,Rs
|
||||
{0xE2700000, t_neg}, // RSBS Rd,Rs,#0
|
||||
{0xE1500000, t_norm}, // CMP Rd,Rs
|
||||
{0xE1700000, t_norm}, // CMN Rd,Rs
|
||||
{0xE1900000, t_norm}, // ORRS Rd,Rd,Rs
|
||||
{0xE0100090, t_mul}, // MULS Rd,Rd,Rs
|
||||
{0xE1D00000, t_norm}, // BICS Rd,Rd,Rs
|
||||
{0xE1F00000, t_norm} // MVNS Rd,Rs
|
||||
};
|
||||
|
||||
*ainstr = subset[(tinstr & 0x03C0) >> 6].opcode; // base
|
||||
|
||||
switch (subset[(tinstr & 0x03C0) >> 6].type) {
|
||||
case t_norm:
|
||||
*ainstr |= ((tinstr & 0x0007) << 16) // Rn
|
||||
| ((tinstr & 0x0007) << 12) // Rd
|
||||
| ((tinstr & 0x0038) >> 3); // Rs
|
||||
break;
|
||||
case t_shift:
|
||||
*ainstr |= ((tinstr & 0x0007) << 12) // Rd
|
||||
| ((tinstr & 0x0007) >> 0) // Rm
|
||||
| ((tinstr & 0x0038) << (8 - 3)); // Rs
|
||||
break;
|
||||
case t_neg:
|
||||
*ainstr |= ((tinstr & 0x0007) << 12) // Rd
|
||||
| ((tinstr & 0x0038) << (16 - 3)); // Rn
|
||||
break;
|
||||
case t_mul:
|
||||
*ainstr |= ((tinstr & 0x0007) << 16) // Rd
|
||||
| ((tinstr & 0x0007) << 8) // Rs
|
||||
| ((tinstr & 0x0038) >> 3); // Rm
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
u32 Rd = ((tinstr & 0x0007) >> 0);
|
||||
u32 Rs = ((tinstr & 0x0078) >> 3);
|
||||
|
||||
if (tinstr & (1 << 7))
|
||||
Rd += 8;
|
||||
|
||||
switch ((tinstr & 0x03C0) >> 6) {
|
||||
case 0x0: // ADD Rd,Rd,Rs
|
||||
case 0x1: // ADD Rd,Rd,Hs
|
||||
case 0x2: // ADD Hd,Hd,Rs
|
||||
case 0x3: // ADD Hd,Hd,Hs
|
||||
*ainstr = 0xE0800000 // base
|
||||
| (Rd << 16) // Rn
|
||||
| (Rd << 12) // Rd
|
||||
| (Rs << 0); // Rm
|
||||
break;
|
||||
case 0x4: // CMP Rd,Rs
|
||||
case 0x5: // CMP Rd,Hs
|
||||
case 0x6: // CMP Hd,Rs
|
||||
case 0x7: // CMP Hd,Hs
|
||||
*ainstr = 0xE1500000 // base
|
||||
| (Rd << 16) // Rn
|
||||
| (Rs << 0); // Rm
|
||||
break;
|
||||
case 0x8: // MOV Rd,Rs
|
||||
case 0x9: // MOV Rd,Hs
|
||||
case 0xA: // MOV Hd,Rs
|
||||
case 0xB: // MOV Hd,Hs
|
||||
*ainstr = 0xE1A00000 // base
|
||||
| (Rd << 12) // Rd
|
||||
| (Rs << 0); // Rm
|
||||
break;
|
||||
case 0xC: // BX Rs
|
||||
case 0xD: // BX Hs
|
||||
*ainstr = 0xE12FFF10 // base
|
||||
| ((tinstr & 0x0078) >> 3); // Rd
|
||||
break;
|
||||
case 0xE: // BLX
|
||||
case 0xF: // BLX
|
||||
*ainstr = 0xE1200030 // base
|
||||
| (Rs << 0); // Rm
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 9: // LDR Rd,[PC,#imm8]
|
||||
*ainstr = 0xE59F0000 // base
|
||||
| ((tinstr & 0x0700) << (12 - 8)) // Rd
|
||||
| ((tinstr & 0x00FF) << (2 - 0)); // off8
|
||||
break;
|
||||
|
||||
case 10:
|
||||
case 11: {
|
||||
static const u32 subset[8] = {
|
||||
0xE7800000, // STR Rd,[Rb,Ro]
|
||||
0xE18000B0, // STRH Rd,[Rb,Ro]
|
||||
0xE7C00000, // STRB Rd,[Rb,Ro]
|
||||
0xE19000D0, // LDRSB Rd,[Rb,Ro]
|
||||
0xE7900000, // LDR Rd,[Rb,Ro]
|
||||
0xE19000B0, // LDRH Rd,[Rb,Ro]
|
||||
0xE7D00000, // LDRB Rd,[Rb,Ro]
|
||||
0xE19000F0 // LDRSH Rd,[Rb,Ro]
|
||||
};
|
||||
|
||||
*ainstr = subset[(tinstr & 0xE00) >> 9] // base
|
||||
| ((tinstr & 0x0007) << (12 - 0)) // Rd
|
||||
| ((tinstr & 0x0038) << (16 - 3)) // Rb
|
||||
| ((tinstr & 0x01C0) >> 6); // Ro
|
||||
} break;
|
||||
|
||||
case 12: // STR Rd,[Rb,#imm5]
|
||||
case 13: // LDR Rd,[Rb,#imm5]
|
||||
case 14: // STRB Rd,[Rb,#imm5]
|
||||
case 15: // LDRB Rd,[Rb,#imm5]
|
||||
{
|
||||
static const u32 subset[4] = {
|
||||
0xE5800000, // STR Rd,[Rb,#imm5]
|
||||
0xE5900000, // LDR Rd,[Rb,#imm5]
|
||||
0xE5C00000, // STRB Rd,[Rb,#imm5]
|
||||
0xE5D00000 // LDRB Rd,[Rb,#imm5]
|
||||
};
|
||||
// The offset range defends on whether we are transferring a byte or word value:
|
||||
*ainstr = subset[(tinstr & 0x1800) >> 11] // base
|
||||
| ((tinstr & 0x0007) << (12 - 0)) // Rd
|
||||
| ((tinstr & 0x0038) << (16 - 3)) // Rb
|
||||
| ((tinstr & 0x07C0) >> (6 - ((tinstr & (1 << 12)) ? 0 : 2))); // off5
|
||||
} break;
|
||||
|
||||
case 16: // STRH Rd,[Rb,#imm5]
|
||||
case 17: // LDRH Rd,[Rb,#imm5]
|
||||
*ainstr = ((tinstr & (1 << 11)) // base
|
||||
? 0xE1D000B0 // LDRH
|
||||
: 0xE1C000B0) // STRH
|
||||
| ((tinstr & 0x0007) << (12 - 0)) // Rd
|
||||
| ((tinstr & 0x0038) << (16 - 3)) // Rb
|
||||
| ((tinstr & 0x01C0) >> (6 - 1)) // off5, low nibble
|
||||
| ((tinstr & 0x0600) >> (9 - 8)); // off5, high nibble
|
||||
break;
|
||||
|
||||
case 18: // STR Rd,[SP,#imm8]
|
||||
case 19: // LDR Rd,[SP,#imm8]
|
||||
*ainstr = ((tinstr & (1 << 11)) // base
|
||||
? 0xE59D0000 // LDR
|
||||
: 0xE58D0000) // STR
|
||||
| ((tinstr & 0x0700) << (12 - 8)) // Rd
|
||||
| ((tinstr & 0x00FF) << 2); // off8
|
||||
break;
|
||||
|
||||
case 20: // ADD Rd,PC,#imm8
|
||||
case 21: // ADD Rd,SP,#imm8
|
||||
|
||||
if ((tinstr & (1 << 11)) == 0) {
|
||||
|
||||
// NOTE: The PC value used here should by word aligned. We encode shift-left-by-2 in the
|
||||
// rotate immediate field, so no shift of off8 is needed.
|
||||
|
||||
*ainstr = 0xE28F0F00 // base
|
||||
| ((tinstr & 0x0700) << (12 - 8)) // Rd
|
||||
| (tinstr & 0x00FF); // off8
|
||||
} else {
|
||||
// We encode shift-left-by-2 in the rotate immediate field, so no shift of off8 is
|
||||
// needed.
|
||||
*ainstr = 0xE28D0F00 // base
|
||||
| ((tinstr & 0x0700) << (12 - 8)) // Rd
|
||||
| (tinstr & 0x00FF); // off8
|
||||
}
|
||||
break;
|
||||
|
||||
case 22:
|
||||
case 23:
|
||||
if ((tinstr & 0x0F00) == 0x0000) {
|
||||
// NOTE: The instruction contains a shift left of 2 equivalent (implemented as ROR #30):
|
||||
*ainstr = ((tinstr & (1 << 7)) // base
|
||||
? 0xE24DDF00 // SUB
|
||||
: 0xE28DDF00) // ADD
|
||||
| (tinstr & 0x007F); // off7
|
||||
} else if ((tinstr & 0x0F00) == 0x0e00) {
|
||||
// BKPT
|
||||
*ainstr = 0xEF000000 // base
|
||||
| BITS(tinstr, 0, 3) // imm4 field;
|
||||
| (BITS(tinstr, 4, 7) << 8); // beginning 4 bits of imm12
|
||||
} else if ((tinstr & 0x0F00) == 0x0200) {
|
||||
static const u32 subset[4] = {
|
||||
0xE6BF0070, // SXTH
|
||||
0xE6AF0070, // SXTB
|
||||
0xE6FF0070, // UXTH
|
||||
0xE6EF0070, // UXTB
|
||||
};
|
||||
|
||||
*ainstr = subset[BITS(tinstr, 6, 7)] // base
|
||||
| (BITS(tinstr, 0, 2) << 12) // Rd
|
||||
| BITS(tinstr, 3, 5); // Rm
|
||||
} else if ((tinstr & 0x0F00) == 0x600) {
|
||||
if (BIT(tinstr, 5) == 0) {
|
||||
// SETEND
|
||||
*ainstr = 0xF1010000 // base
|
||||
| (BIT(tinstr, 3) << 9); // endian specifier
|
||||
} else {
|
||||
// CPS
|
||||
*ainstr = 0xF1080000 // base
|
||||
| (BIT(tinstr, 0) << 6) // fiq bit
|
||||
| (BIT(tinstr, 1) << 7) // irq bit
|
||||
| (BIT(tinstr, 2) << 8) // abort bit
|
||||
| (BIT(tinstr, 4) << 18); // enable bit
|
||||
}
|
||||
} else if ((tinstr & 0x0F00) == 0x0a00) {
|
||||
static const u32 subset[4] = {
|
||||
0xE6BF0F30, // REV
|
||||
0xE6BF0FB0, // REV16
|
||||
0, // undefined
|
||||
0xE6FF0FB0, // REVSH
|
||||
};
|
||||
|
||||
std::size_t subset_index = BITS(tinstr, 6, 7);
|
||||
|
||||
if (subset_index == 2) {
|
||||
valid = ThumbDecodeStatus::UNDEFINED;
|
||||
} else {
|
||||
*ainstr = subset[subset_index] // base
|
||||
| (BITS(tinstr, 0, 2) << 12) // Rd
|
||||
| BITS(tinstr, 3, 5); // Rm
|
||||
}
|
||||
} else {
|
||||
static const u32 subset[4] = {
|
||||
0xE92D0000, // STMDB sp!,{rlist}
|
||||
0xE92D4000, // STMDB sp!,{rlist,lr}
|
||||
0xE8BD0000, // LDMIA sp!,{rlist}
|
||||
0xE8BD8000 // LDMIA sp!,{rlist,pc}
|
||||
};
|
||||
*ainstr = subset[((tinstr & (1 << 11)) >> 10) | ((tinstr & (1 << 8)) >> 8)] // base
|
||||
| (tinstr & 0x00FF); // mask8
|
||||
}
|
||||
break;
|
||||
|
||||
case 24: // STMIA
|
||||
case 25: // LDMIA
|
||||
if (tinstr & (1 << 11)) {
|
||||
unsigned int base = 0xE8900000;
|
||||
unsigned int rn = BITS(tinstr, 8, 10);
|
||||
|
||||
// Writeback
|
||||
if ((tinstr & (1 << rn)) == 0)
|
||||
base |= (1 << 21);
|
||||
|
||||
*ainstr = base // base (LDMIA)
|
||||
| (rn << 16) // Rn
|
||||
| (tinstr & 0x00FF); // Register list
|
||||
} else {
|
||||
*ainstr = 0xE8A00000 // base (STMIA)
|
||||
| (BITS(tinstr, 8, 10) << 16) // Rn
|
||||
| (tinstr & 0x00FF); // Register list
|
||||
}
|
||||
break;
|
||||
|
||||
case 26: // Bcc
|
||||
case 27: // Bcc/SWI
|
||||
if ((tinstr & 0x0F00) == 0x0F00) {
|
||||
// Format 17 : SWI
|
||||
*ainstr = 0xEF000000;
|
||||
// Breakpoint must be handled specially.
|
||||
if ((tinstr & 0x00FF) == 0x18)
|
||||
*ainstr |= ((tinstr & 0x00FF) << 16);
|
||||
// New breakpoint value. See gdb/arm-tdep.c
|
||||
else if ((tinstr & 0x00FF) == 0xFE)
|
||||
*ainstr |= 0x180000; // base |= BKPT mask
|
||||
else
|
||||
*ainstr |= (tinstr & 0x00FF);
|
||||
} else if ((tinstr & 0x0F00) != 0x0E00)
|
||||
valid = ThumbDecodeStatus::BRANCH;
|
||||
else // UNDEFINED : cc=1110(AL) uses different format
|
||||
valid = ThumbDecodeStatus::UNDEFINED;
|
||||
|
||||
break;
|
||||
|
||||
case 28: // B
|
||||
valid = ThumbDecodeStatus::BRANCH;
|
||||
break;
|
||||
|
||||
case 29:
|
||||
if (tinstr & 0x1)
|
||||
valid = ThumbDecodeStatus::UNDEFINED;
|
||||
else
|
||||
valid = ThumbDecodeStatus::BRANCH;
|
||||
break;
|
||||
|
||||
case 30: // BL instruction 1
|
||||
|
||||
// There is no single ARM instruction equivalent for this Thumb instruction. To keep the
|
||||
// simulation simple (from the user perspective) we check if the following instruction is
|
||||
// the second half of this BL, and if it is we simulate it immediately
|
||||
|
||||
valid = ThumbDecodeStatus::BRANCH;
|
||||
break;
|
||||
|
||||
case 31: // BL instruction 2
|
||||
|
||||
// There is no single ARM instruction equivalent for this instruction. Also, it should only
|
||||
// ever be matched with the fmt19 "BL instruction 1" instruction. However, we do allow the
|
||||
// simulation of it on its own, with undefined results if r14 is not suitably initialised.
|
||||
|
||||
valid = ThumbDecodeStatus::BRANCH;
|
||||
break;
|
||||
}
|
||||
|
||||
*inst_size = 2;
|
||||
|
||||
return valid;
|
||||
}
|
||||
49
src/core/arm/dyncom/arm_dyncom_thumb.h
Normal file
49
src/core/arm/dyncom/arm_dyncom_thumb.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* Copyright (C)
|
||||
* 2011 - Michael.Kang blackfin.kang@gmail.com
|
||||
* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file arm_dyncom_thumb.h
|
||||
* @brief The thumb dyncom
|
||||
* @author Michael.Kang blackfin.kang@gmail.com
|
||||
* @version 78.77
|
||||
* @date 2011-11-07
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
enum class ThumbDecodeStatus {
|
||||
UNDEFINED, // Undefined Thumb instruction
|
||||
DECODED, // Instruction decoded to ARM equivalent
|
||||
BRANCH, // Thumb branch (already processed)
|
||||
UNINITIALIZED,
|
||||
};
|
||||
|
||||
// Translates a Thumb mode instruction into its ARM equivalent.
|
||||
ThumbDecodeStatus TranslateThumbInstruction(u32 addr, u32 instr, u32* ainstr, u32* inst_size);
|
||||
|
||||
inline u32 GetThumbInstruction(u32 instr, u32 address) {
|
||||
// Normally you would need to handle instruction endianness,
|
||||
// however, it is fixed to little-endian on the MPCore, so
|
||||
// there's no need to check for this beforehand.
|
||||
if ((address & 0x3) != 0)
|
||||
return instr >> 16;
|
||||
|
||||
return instr & 0xFFFF;
|
||||
}
|
||||
2018
src/core/arm/dyncom/arm_dyncom_trans.cpp
Normal file
2018
src/core/arm/dyncom/arm_dyncom_trans.cpp
Normal file
File diff suppressed because it is too large
Load Diff
498
src/core/arm/dyncom/arm_dyncom_trans.h
Normal file
498
src/core/arm/dyncom/arm_dyncom_trans.h
Normal file
@@ -0,0 +1,498 @@
|
||||
#pragma once
|
||||
#ifdef _MSC_VER
|
||||
// nonstandard extension used: zero-sized array in struct/union
|
||||
#pragma warning(disable : 4200)
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include "common/common_types.h"
|
||||
|
||||
struct ARMul_State;
|
||||
typedef unsigned int (*shtop_fp_t)(ARMul_State* cpu, unsigned int sht_oper);
|
||||
|
||||
enum class TransExtData {
|
||||
COND = (1 << 0),
|
||||
NON_BRANCH = (1 << 1),
|
||||
DIRECT_BRANCH = (1 << 2),
|
||||
INDIRECT_BRANCH = (1 << 3),
|
||||
CALL = (1 << 4),
|
||||
RET = (1 << 5),
|
||||
END_OF_PAGE = (1 << 6),
|
||||
THUMB = (1 << 7),
|
||||
SINGLE_STEP = (1 << 8)
|
||||
};
|
||||
|
||||
struct arm_inst {
|
||||
unsigned int idx;
|
||||
unsigned int cond;
|
||||
TransExtData br;
|
||||
char component[0];
|
||||
};
|
||||
|
||||
struct generic_arm_inst {
|
||||
u32 Ra;
|
||||
u32 Rm;
|
||||
u32 Rn;
|
||||
u32 Rd;
|
||||
u8 op1;
|
||||
u8 op2;
|
||||
};
|
||||
|
||||
struct adc_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct add_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct orr_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct and_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct eor_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct bbl_inst {
|
||||
unsigned int L;
|
||||
int signed_immed_24;
|
||||
unsigned int next_addr;
|
||||
unsigned int jmp_addr;
|
||||
};
|
||||
|
||||
struct bx_inst {
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct blx_inst {
|
||||
union {
|
||||
s32 signed_immed_24;
|
||||
u32 Rm;
|
||||
} val;
|
||||
unsigned int inst;
|
||||
};
|
||||
|
||||
struct clz_inst {
|
||||
unsigned int Rm;
|
||||
unsigned int Rd;
|
||||
};
|
||||
|
||||
struct cps_inst {
|
||||
unsigned int imod0;
|
||||
unsigned int imod1;
|
||||
unsigned int mmod;
|
||||
unsigned int A, I, F;
|
||||
unsigned int mode;
|
||||
};
|
||||
|
||||
struct clrex_inst {};
|
||||
|
||||
struct cpy_inst {
|
||||
unsigned int Rm;
|
||||
unsigned int Rd;
|
||||
};
|
||||
|
||||
struct bic_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct sub_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct tst_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct cmn_inst {
|
||||
unsigned int I;
|
||||
unsigned int Rn;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct teq_inst {
|
||||
unsigned int I;
|
||||
unsigned int Rn;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct stm_inst {
|
||||
unsigned int inst;
|
||||
};
|
||||
|
||||
struct bkpt_inst {
|
||||
u32 imm;
|
||||
};
|
||||
|
||||
struct stc_inst {};
|
||||
|
||||
struct ldc_inst {};
|
||||
|
||||
struct swi_inst {
|
||||
unsigned int num;
|
||||
};
|
||||
|
||||
struct cmp_inst {
|
||||
unsigned int I;
|
||||
unsigned int Rn;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct mov_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct mvn_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct rev_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rm;
|
||||
unsigned int op1;
|
||||
unsigned int op2;
|
||||
};
|
||||
|
||||
struct rsb_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct rsc_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct sbc_inst {
|
||||
unsigned int I;
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int shifter_operand;
|
||||
shtop_fp_t shtop_func;
|
||||
};
|
||||
|
||||
struct mul_inst {
|
||||
unsigned int S;
|
||||
unsigned int Rd;
|
||||
unsigned int Rs;
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct smul_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rs;
|
||||
unsigned int Rm;
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
};
|
||||
|
||||
struct umull_inst {
|
||||
unsigned int S;
|
||||
unsigned int RdHi;
|
||||
unsigned int RdLo;
|
||||
unsigned int Rs;
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct smlad_inst {
|
||||
unsigned int m;
|
||||
unsigned int Rm;
|
||||
unsigned int Rd;
|
||||
unsigned int Ra;
|
||||
unsigned int Rn;
|
||||
unsigned int op1;
|
||||
unsigned int op2;
|
||||
};
|
||||
|
||||
struct smla_inst {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
unsigned int Rm;
|
||||
unsigned int Rd;
|
||||
unsigned int Rs;
|
||||
unsigned int Rn;
|
||||
};
|
||||
|
||||
struct smlalxy_inst {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
unsigned int RdLo;
|
||||
unsigned int RdHi;
|
||||
unsigned int Rm;
|
||||
unsigned int Rn;
|
||||
};
|
||||
|
||||
struct ssat_inst {
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int imm5;
|
||||
unsigned int sat_imm;
|
||||
unsigned int shift_type;
|
||||
};
|
||||
|
||||
struct umaal_inst {
|
||||
unsigned int Rn;
|
||||
unsigned int Rm;
|
||||
unsigned int RdHi;
|
||||
unsigned int RdLo;
|
||||
};
|
||||
|
||||
struct umlal_inst {
|
||||
unsigned int S;
|
||||
unsigned int Rm;
|
||||
unsigned int Rs;
|
||||
unsigned int RdHi;
|
||||
unsigned int RdLo;
|
||||
};
|
||||
|
||||
struct smlal_inst {
|
||||
unsigned int S;
|
||||
unsigned int Rm;
|
||||
unsigned int Rs;
|
||||
unsigned int RdHi;
|
||||
unsigned int RdLo;
|
||||
};
|
||||
|
||||
struct smlald_inst {
|
||||
unsigned int RdLo;
|
||||
unsigned int RdHi;
|
||||
unsigned int Rm;
|
||||
unsigned int Rn;
|
||||
unsigned int swap;
|
||||
unsigned int op1;
|
||||
unsigned int op2;
|
||||
};
|
||||
|
||||
struct mla_inst {
|
||||
unsigned int S;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int Rs;
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct mrc_inst {
|
||||
unsigned int opcode_1;
|
||||
unsigned int opcode_2;
|
||||
unsigned int cp_num;
|
||||
unsigned int crn;
|
||||
unsigned int crm;
|
||||
unsigned int Rd;
|
||||
unsigned int inst;
|
||||
};
|
||||
|
||||
struct mcr_inst {
|
||||
unsigned int opcode_1;
|
||||
unsigned int opcode_2;
|
||||
unsigned int cp_num;
|
||||
unsigned int crn;
|
||||
unsigned int crm;
|
||||
unsigned int Rd;
|
||||
unsigned int inst;
|
||||
};
|
||||
|
||||
struct mcrr_inst {
|
||||
unsigned int opcode_1;
|
||||
unsigned int cp_num;
|
||||
unsigned int crm;
|
||||
unsigned int rt;
|
||||
unsigned int rt2;
|
||||
};
|
||||
|
||||
struct mrs_inst {
|
||||
unsigned int R;
|
||||
unsigned int Rd;
|
||||
};
|
||||
|
||||
struct msr_inst {
|
||||
unsigned int field_mask;
|
||||
unsigned int R;
|
||||
unsigned int inst;
|
||||
};
|
||||
|
||||
struct pld_inst {};
|
||||
|
||||
struct sxtb_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rm;
|
||||
unsigned int rotate;
|
||||
};
|
||||
|
||||
struct sxtab_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rn;
|
||||
unsigned int Rm;
|
||||
unsigned rotate;
|
||||
};
|
||||
|
||||
struct sxtah_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rn;
|
||||
unsigned int Rm;
|
||||
unsigned int rotate;
|
||||
};
|
||||
|
||||
struct sxth_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rm;
|
||||
unsigned int rotate;
|
||||
};
|
||||
|
||||
struct uxtab_inst {
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int rotate;
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct uxtah_inst {
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int rotate;
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct uxth_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rm;
|
||||
unsigned int rotate;
|
||||
};
|
||||
|
||||
struct cdp_inst {
|
||||
unsigned int opcode_1;
|
||||
unsigned int CRn;
|
||||
unsigned int CRd;
|
||||
unsigned int cp_num;
|
||||
unsigned int opcode_2;
|
||||
unsigned int CRm;
|
||||
unsigned int inst;
|
||||
};
|
||||
|
||||
struct uxtb_inst {
|
||||
unsigned int Rd;
|
||||
unsigned int Rm;
|
||||
unsigned int rotate;
|
||||
};
|
||||
|
||||
struct swp_inst {
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned int Rm;
|
||||
};
|
||||
|
||||
struct setend_inst {
|
||||
unsigned int set_bigend;
|
||||
};
|
||||
|
||||
struct b_2_thumb {
|
||||
unsigned int imm;
|
||||
};
|
||||
struct b_cond_thumb {
|
||||
unsigned int imm;
|
||||
unsigned int cond;
|
||||
};
|
||||
|
||||
struct bl_1_thumb {
|
||||
unsigned int imm;
|
||||
};
|
||||
struct bl_2_thumb {
|
||||
unsigned int imm;
|
||||
};
|
||||
struct blx_1_thumb {
|
||||
unsigned int imm;
|
||||
unsigned int instr;
|
||||
};
|
||||
|
||||
struct pkh_inst {
|
||||
unsigned int Rm;
|
||||
unsigned int Rn;
|
||||
unsigned int Rd;
|
||||
unsigned char imm;
|
||||
};
|
||||
|
||||
// Floating point VFPv3 structures
|
||||
#define VFP_INTERPRETER_STRUCT
|
||||
#include "core/arm/skyeye_common/vfp/vfpinstr.cpp"
|
||||
#undef VFP_INTERPRETER_STRUCT
|
||||
|
||||
typedef void (*get_addr_fp_t)(ARMul_State* cpu, unsigned int inst, unsigned int& virt_addr);
|
||||
|
||||
struct ldst_inst {
|
||||
unsigned int inst;
|
||||
get_addr_fp_t get_addr;
|
||||
};
|
||||
|
||||
typedef arm_inst* ARM_INST_PTR;
|
||||
typedef ARM_INST_PTR (*transop_fp_t)(unsigned int, int);
|
||||
|
||||
extern const transop_fp_t arm_instruction_trans[];
|
||||
extern const std::size_t arm_instruction_trans_len;
|
||||
|
||||
#define TRANS_CACHE_SIZE (64 * 1024 * 2000)
|
||||
extern char trans_cache_buf[TRANS_CACHE_SIZE];
|
||||
extern std::size_t trans_cache_buf_top;
|
||||
27
src/core/arm/exclusive_monitor.cpp
Normal file
27
src/core/arm/exclusive_monitor.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/arch.h"
|
||||
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#endif
|
||||
#include "common/settings.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
ExclusiveMonitor::~ExclusiveMonitor() = default;
|
||||
|
||||
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::MemorySystem& memory,
|
||||
std::size_t num_cores) {
|
||||
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);
|
||||
}
|
||||
#endif
|
||||
// TODO(merry): Passthrough exclusive monitor
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
35
src/core/arm/exclusive_monitor.h
Normal file
35
src/core/arm/exclusive_monitor.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ExclusiveMonitor {
|
||||
public:
|
||||
virtual ~ExclusiveMonitor();
|
||||
|
||||
virtual u8 ExclusiveRead8(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u16 ExclusiveRead16(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u32 ExclusiveRead32(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u64 ExclusiveRead64(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual void ClearExclusive(std::size_t core_index) = 0;
|
||||
|
||||
virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0;
|
||||
virtual bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) = 0;
|
||||
virtual bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) = 0;
|
||||
virtual bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::MemorySystem& memory,
|
||||
std::size_t num_cores);
|
||||
|
||||
} // namespace Core
|
||||
187
src/core/arm/skyeye_common/arm_regformat.h
Normal file
187
src/core/arm/skyeye_common/arm_regformat.h
Normal file
@@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
|
||||
enum {
|
||||
R0 = 0,
|
||||
R1,
|
||||
R2,
|
||||
R3,
|
||||
R4,
|
||||
R5,
|
||||
R6,
|
||||
R7,
|
||||
R8,
|
||||
R9,
|
||||
R10,
|
||||
R11,
|
||||
R12,
|
||||
R13,
|
||||
LR,
|
||||
R15, // PC,
|
||||
CPSR_REG,
|
||||
SPSR_REG,
|
||||
|
||||
PHYS_PC,
|
||||
R13_USR,
|
||||
R14_USR,
|
||||
R13_SVC,
|
||||
R14_SVC,
|
||||
R13_ABORT,
|
||||
R14_ABORT,
|
||||
R13_UNDEF,
|
||||
R14_UNDEF,
|
||||
R13_IRQ,
|
||||
R14_IRQ,
|
||||
R8_FIRQ,
|
||||
R9_FIRQ,
|
||||
R10_FIRQ,
|
||||
R11_FIRQ,
|
||||
R12_FIRQ,
|
||||
R13_FIRQ,
|
||||
R14_FIRQ,
|
||||
SPSR_INVALID1,
|
||||
SPSR_INVALID2,
|
||||
SPSR_SVC,
|
||||
SPSR_ABORT,
|
||||
SPSR_UNDEF,
|
||||
SPSR_IRQ,
|
||||
SPSR_FIRQ,
|
||||
MODE_REG, /* That is the cpsr[4 : 0], just for calculation easily */
|
||||
BANK_REG,
|
||||
EXCLUSIVE_TAG,
|
||||
EXCLUSIVE_STATE,
|
||||
EXCLUSIVE_RESULT,
|
||||
|
||||
MAX_REG_NUM,
|
||||
};
|
||||
|
||||
// VFP system registers
|
||||
enum VFPSystemRegister {
|
||||
VFP_FPSID,
|
||||
VFP_FPSCR,
|
||||
VFP_FPEXC,
|
||||
VFP_FPINST,
|
||||
VFP_FPINST2,
|
||||
VFP_MVFR0,
|
||||
VFP_MVFR1,
|
||||
|
||||
// Not an actual register.
|
||||
// All VFP system registers should be defined above this.
|
||||
VFP_SYSTEM_REGISTER_COUNT
|
||||
};
|
||||
|
||||
enum CP15Register {
|
||||
// c0 - Information registers
|
||||
CP15_MAIN_ID,
|
||||
CP15_CACHE_TYPE,
|
||||
CP15_TCM_STATUS,
|
||||
CP15_TLB_TYPE,
|
||||
CP15_CPU_ID,
|
||||
CP15_PROCESSOR_FEATURE_0,
|
||||
CP15_PROCESSOR_FEATURE_1,
|
||||
CP15_DEBUG_FEATURE_0,
|
||||
CP15_AUXILIARY_FEATURE_0,
|
||||
CP15_MEMORY_MODEL_FEATURE_0,
|
||||
CP15_MEMORY_MODEL_FEATURE_1,
|
||||
CP15_MEMORY_MODEL_FEATURE_2,
|
||||
CP15_MEMORY_MODEL_FEATURE_3,
|
||||
CP15_ISA_FEATURE_0,
|
||||
CP15_ISA_FEATURE_1,
|
||||
CP15_ISA_FEATURE_2,
|
||||
CP15_ISA_FEATURE_3,
|
||||
CP15_ISA_FEATURE_4,
|
||||
|
||||
// c1 - Control registers
|
||||
CP15_CONTROL,
|
||||
CP15_AUXILIARY_CONTROL,
|
||||
CP15_COPROCESSOR_ACCESS_CONTROL,
|
||||
|
||||
// c2 - Translation table registers
|
||||
CP15_TRANSLATION_BASE_TABLE_0,
|
||||
CP15_TRANSLATION_BASE_TABLE_1,
|
||||
CP15_TRANSLATION_BASE_CONTROL,
|
||||
CP15_DOMAIN_ACCESS_CONTROL,
|
||||
CP15_RESERVED,
|
||||
|
||||
// c5 - Fault status registers
|
||||
CP15_FAULT_STATUS,
|
||||
CP15_INSTR_FAULT_STATUS,
|
||||
CP15_COMBINED_DATA_FSR = CP15_FAULT_STATUS,
|
||||
CP15_INST_FSR,
|
||||
|
||||
// c6 - Fault Address registers
|
||||
CP15_FAULT_ADDRESS,
|
||||
CP15_COMBINED_DATA_FAR = CP15_FAULT_ADDRESS,
|
||||
CP15_WFAR,
|
||||
CP15_IFAR,
|
||||
|
||||
// c7 - Cache operation registers
|
||||
CP15_WAIT_FOR_INTERRUPT,
|
||||
CP15_PHYS_ADDRESS,
|
||||
CP15_INVALIDATE_INSTR_CACHE,
|
||||
CP15_INVALIDATE_INSTR_CACHE_USING_MVA,
|
||||
CP15_INVALIDATE_INSTR_CACHE_USING_INDEX,
|
||||
CP15_FLUSH_PREFETCH_BUFFER,
|
||||
CP15_FLUSH_BRANCH_TARGET_CACHE,
|
||||
CP15_FLUSH_BRANCH_TARGET_CACHE_ENTRY,
|
||||
CP15_INVALIDATE_DATA_CACHE,
|
||||
CP15_INVALIDATE_DATA_CACHE_LINE_USING_MVA,
|
||||
CP15_INVALIDATE_DATA_CACHE_LINE_USING_INDEX,
|
||||
CP15_INVALIDATE_DATA_AND_INSTR_CACHE,
|
||||
CP15_CLEAN_DATA_CACHE,
|
||||
CP15_CLEAN_DATA_CACHE_LINE_USING_MVA,
|
||||
CP15_CLEAN_DATA_CACHE_LINE_USING_INDEX,
|
||||
CP15_DATA_SYNC_BARRIER,
|
||||
CP15_DATA_MEMORY_BARRIER,
|
||||
CP15_CLEAN_AND_INVALIDATE_DATA_CACHE,
|
||||
CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_MVA,
|
||||
CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_INDEX,
|
||||
|
||||
// c8 - TLB operations
|
||||
CP15_INVALIDATE_ITLB,
|
||||
CP15_INVALIDATE_ITLB_SINGLE_ENTRY,
|
||||
CP15_INVALIDATE_ITLB_ENTRY_ON_ASID_MATCH,
|
||||
CP15_INVALIDATE_ITLB_ENTRY_ON_MVA,
|
||||
CP15_INVALIDATE_DTLB,
|
||||
CP15_INVALIDATE_DTLB_SINGLE_ENTRY,
|
||||
CP15_INVALIDATE_DTLB_ENTRY_ON_ASID_MATCH,
|
||||
CP15_INVALIDATE_DTLB_ENTRY_ON_MVA,
|
||||
CP15_INVALIDATE_UTLB,
|
||||
CP15_INVALIDATE_UTLB_SINGLE_ENTRY,
|
||||
CP15_INVALIDATE_UTLB_ENTRY_ON_ASID_MATCH,
|
||||
CP15_INVALIDATE_UTLB_ENTRY_ON_MVA,
|
||||
|
||||
// c9 - Data cache lockdown register
|
||||
CP15_DATA_CACHE_LOCKDOWN,
|
||||
|
||||
// c10 - TLB/Memory map registers
|
||||
CP15_TLB_LOCKDOWN,
|
||||
CP15_PRIMARY_REGION_REMAP,
|
||||
CP15_NORMAL_REGION_REMAP,
|
||||
|
||||
// c13 - Thread related registers
|
||||
CP15_PID,
|
||||
CP15_CONTEXT_ID,
|
||||
CP15_THREAD_UPRW, // Thread ID register - User/Privileged Read/Write
|
||||
CP15_THREAD_URO, // Thread ID register - User Read Only (Privileged R/W)
|
||||
CP15_THREAD_PRW, // Thread ID register - Privileged R/W only.
|
||||
|
||||
// c15 - Performance and TLB lockdown registers
|
||||
CP15_PERFORMANCE_MONITOR_CONTROL,
|
||||
CP15_CYCLE_COUNTER,
|
||||
CP15_COUNT_0,
|
||||
CP15_COUNT_1,
|
||||
CP15_READ_MAIN_TLB_LOCKDOWN_ENTRY,
|
||||
CP15_WRITE_MAIN_TLB_LOCKDOWN_ENTRY,
|
||||
CP15_MAIN_TLB_LOCKDOWN_VIRT_ADDRESS,
|
||||
CP15_MAIN_TLB_LOCKDOWN_PHYS_ADDRESS,
|
||||
CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE,
|
||||
CP15_TLB_DEBUG_CONTROL,
|
||||
|
||||
// Skyeye defined
|
||||
CP15_TLB_FAULT_ADDR,
|
||||
CP15_TLB_FAULT_STATUS,
|
||||
|
||||
// Not an actual register.
|
||||
// All registers should be defined above this.
|
||||
CP15_REGISTER_COUNT,
|
||||
};
|
||||
620
src/core/arm/skyeye_common/armstate.cpp
Normal file
620
src/core/arm/skyeye_common/armstate.cpp
Normal file
@@ -0,0 +1,620 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
#include "core/arm/skyeye_common/vfp/vfp.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
ARMul_State::ARMul_State(Core::System& system_, Memory::MemorySystem& memory_,
|
||||
PrivilegeMode initial_mode)
|
||||
: system{system_}, memory{memory_} {
|
||||
Reset();
|
||||
ChangePrivilegeMode(initial_mode);
|
||||
}
|
||||
|
||||
void ARMul_State::ChangePrivilegeMode(u32 new_mode) {
|
||||
if (Mode == new_mode)
|
||||
return;
|
||||
|
||||
if (new_mode != USERBANK) {
|
||||
switch (Mode) {
|
||||
case SYSTEM32MODE: // Shares registers with user mode
|
||||
case USER32MODE:
|
||||
Reg_usr[0] = Reg[13];
|
||||
Reg_usr[1] = Reg[14];
|
||||
break;
|
||||
case IRQ32MODE:
|
||||
Reg_irq[0] = Reg[13];
|
||||
Reg_irq[1] = Reg[14];
|
||||
Spsr[IRQBANK] = Spsr_copy;
|
||||
break;
|
||||
case SVC32MODE:
|
||||
Reg_svc[0] = Reg[13];
|
||||
Reg_svc[1] = Reg[14];
|
||||
Spsr[SVCBANK] = Spsr_copy;
|
||||
break;
|
||||
case ABORT32MODE:
|
||||
Reg_abort[0] = Reg[13];
|
||||
Reg_abort[1] = Reg[14];
|
||||
Spsr[ABORTBANK] = Spsr_copy;
|
||||
break;
|
||||
case UNDEF32MODE:
|
||||
Reg_undef[0] = Reg[13];
|
||||
Reg_undef[1] = Reg[14];
|
||||
Spsr[UNDEFBANK] = Spsr_copy;
|
||||
break;
|
||||
case FIQ32MODE:
|
||||
std::copy(Reg.begin() + 8, Reg.end() - 1, Reg_firq.begin());
|
||||
Spsr[FIQBANK] = Spsr_copy;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (new_mode) {
|
||||
case USER32MODE:
|
||||
Reg[13] = Reg_usr[0];
|
||||
Reg[14] = Reg_usr[1];
|
||||
Bank = USERBANK;
|
||||
break;
|
||||
case IRQ32MODE:
|
||||
Reg[13] = Reg_irq[0];
|
||||
Reg[14] = Reg_irq[1];
|
||||
Spsr_copy = Spsr[IRQBANK];
|
||||
Bank = IRQBANK;
|
||||
break;
|
||||
case SVC32MODE:
|
||||
Reg[13] = Reg_svc[0];
|
||||
Reg[14] = Reg_svc[1];
|
||||
Spsr_copy = Spsr[SVCBANK];
|
||||
Bank = SVCBANK;
|
||||
break;
|
||||
case ABORT32MODE:
|
||||
Reg[13] = Reg_abort[0];
|
||||
Reg[14] = Reg_abort[1];
|
||||
Spsr_copy = Spsr[ABORTBANK];
|
||||
Bank = ABORTBANK;
|
||||
break;
|
||||
case UNDEF32MODE:
|
||||
Reg[13] = Reg_undef[0];
|
||||
Reg[14] = Reg_undef[1];
|
||||
Spsr_copy = Spsr[UNDEFBANK];
|
||||
Bank = UNDEFBANK;
|
||||
break;
|
||||
case FIQ32MODE:
|
||||
std::copy(Reg_firq.begin(), Reg_firq.end(), Reg.begin() + 8);
|
||||
Spsr_copy = Spsr[FIQBANK];
|
||||
Bank = FIQBANK;
|
||||
break;
|
||||
case SYSTEM32MODE: // Shares registers with user mode.
|
||||
Reg[13] = Reg_usr[0];
|
||||
Reg[14] = Reg_usr[1];
|
||||
Bank = SYSTEMBANK;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the mode bits in the APSR
|
||||
Cpsr = (Cpsr & ~Mode) | new_mode;
|
||||
Mode = new_mode;
|
||||
}
|
||||
}
|
||||
|
||||
// Performs a reset
|
||||
void ARMul_State::Reset() {
|
||||
VFPInit(this);
|
||||
|
||||
// Set stack pointer to the top of the stack
|
||||
Reg[13] = 0x10000000;
|
||||
Reg[15] = 0;
|
||||
|
||||
Cpsr = static_cast<u32>(INTBITS) | static_cast<u32>(SVC32MODE);
|
||||
Mode = SVC32MODE;
|
||||
Bank = SVCBANK;
|
||||
|
||||
ResetMPCoreCP15Registers();
|
||||
|
||||
NresetSig = HIGH;
|
||||
NfiqSig = HIGH;
|
||||
NirqSig = HIGH;
|
||||
NtransSig = (Mode & 3) ? HIGH : LOW;
|
||||
abortSig = LOW;
|
||||
|
||||
NumInstrs = 0;
|
||||
Emulate = RUN;
|
||||
}
|
||||
|
||||
// Resets certain MPCore CP15 values to their ARM-defined reset values.
|
||||
void ARMul_State::ResetMPCoreCP15Registers() {
|
||||
// c0
|
||||
CP15[CP15_MAIN_ID] = 0x410FB024;
|
||||
CP15[CP15_TLB_TYPE] = 0x00000800;
|
||||
CP15[CP15_PROCESSOR_FEATURE_0] = 0x00000111;
|
||||
CP15[CP15_PROCESSOR_FEATURE_1] = 0x00000001;
|
||||
CP15[CP15_DEBUG_FEATURE_0] = 0x00000002;
|
||||
CP15[CP15_MEMORY_MODEL_FEATURE_0] = 0x01100103;
|
||||
CP15[CP15_MEMORY_MODEL_FEATURE_1] = 0x10020302;
|
||||
CP15[CP15_MEMORY_MODEL_FEATURE_2] = 0x01222000;
|
||||
CP15[CP15_MEMORY_MODEL_FEATURE_3] = 0x00000000;
|
||||
CP15[CP15_ISA_FEATURE_0] = 0x00100011;
|
||||
CP15[CP15_ISA_FEATURE_1] = 0x12002111;
|
||||
CP15[CP15_ISA_FEATURE_2] = 0x11221011;
|
||||
CP15[CP15_ISA_FEATURE_3] = 0x01102131;
|
||||
CP15[CP15_ISA_FEATURE_4] = 0x00000141;
|
||||
|
||||
// c1
|
||||
CP15[CP15_CONTROL] = 0x00054078;
|
||||
CP15[CP15_AUXILIARY_CONTROL] = 0x0000000F;
|
||||
CP15[CP15_COPROCESSOR_ACCESS_CONTROL] = 0x00000000;
|
||||
|
||||
// c2
|
||||
CP15[CP15_TRANSLATION_BASE_TABLE_0] = 0x00000000;
|
||||
CP15[CP15_TRANSLATION_BASE_TABLE_1] = 0x00000000;
|
||||
CP15[CP15_TRANSLATION_BASE_CONTROL] = 0x00000000;
|
||||
|
||||
// c3
|
||||
CP15[CP15_DOMAIN_ACCESS_CONTROL] = 0x00000000;
|
||||
|
||||
// c7
|
||||
CP15[CP15_PHYS_ADDRESS] = 0x00000000;
|
||||
|
||||
// c9
|
||||
CP15[CP15_DATA_CACHE_LOCKDOWN] = 0xFFFFFFF0;
|
||||
|
||||
// c10
|
||||
CP15[CP15_TLB_LOCKDOWN] = 0x00000000;
|
||||
CP15[CP15_PRIMARY_REGION_REMAP] = 0x00098AA4;
|
||||
CP15[CP15_NORMAL_REGION_REMAP] = 0x44E048E0;
|
||||
|
||||
// c13
|
||||
CP15[CP15_PID] = 0x00000000;
|
||||
CP15[CP15_CONTEXT_ID] = 0x00000000;
|
||||
CP15[CP15_THREAD_UPRW] = 0x00000000;
|
||||
CP15[CP15_THREAD_URO] = 0x00000000;
|
||||
CP15[CP15_THREAD_PRW] = 0x00000000;
|
||||
|
||||
// c15
|
||||
CP15[CP15_PERFORMANCE_MONITOR_CONTROL] = 0x00000000;
|
||||
CP15[CP15_MAIN_TLB_LOCKDOWN_VIRT_ADDRESS] = 0x00000000;
|
||||
CP15[CP15_MAIN_TLB_LOCKDOWN_PHYS_ADDRESS] = 0x00000000;
|
||||
CP15[CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE] = 0x00000000;
|
||||
CP15[CP15_TLB_DEBUG_CONTROL] = 0x00000000;
|
||||
}
|
||||
#ifdef ANDROID
|
||||
static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) {}
|
||||
#else
|
||||
static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) {
|
||||
if (GDBStub::IsServerEnabled() && GDBStub::CheckBreakpoint(address, type)) {
|
||||
LOG_DEBUG(Debug, "Found memory breakpoint @ {:08x}", address);
|
||||
GDBStub::Break(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
u8 ARMul_State::ReadMemory8(u32 address) const {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
||||
|
||||
return memory.Read8(address);
|
||||
}
|
||||
|
||||
u16 ARMul_State::ReadMemory16(u32 address) const {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
||||
|
||||
u16 data = memory.Read16(address);
|
||||
|
||||
if (InBigEndianMode())
|
||||
data = Common::swap16(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
u32 ARMul_State::ReadMemory32(u32 address) const {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
||||
|
||||
u32 data = memory.Read32(address);
|
||||
|
||||
if (InBigEndianMode())
|
||||
data = Common::swap32(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
u64 ARMul_State::ReadMemory64(u32 address) const {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
||||
|
||||
u64 data = memory.Read64(address);
|
||||
|
||||
if (InBigEndianMode())
|
||||
data = Common::swap64(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void ARMul_State::WriteMemory8(u32 address, u8 data) {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
||||
|
||||
memory.Write8(address, data);
|
||||
}
|
||||
|
||||
void ARMul_State::WriteMemory16(u32 address, u16 data) {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
||||
|
||||
if (InBigEndianMode())
|
||||
data = Common::swap16(data);
|
||||
|
||||
memory.Write16(address, data);
|
||||
}
|
||||
|
||||
void ARMul_State::WriteMemory32(u32 address, u32 data) {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
||||
|
||||
if (InBigEndianMode())
|
||||
data = Common::swap32(data);
|
||||
|
||||
memory.Write32(address, data);
|
||||
}
|
||||
|
||||
void ARMul_State::WriteMemory64(u32 address, u64 data) {
|
||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
||||
|
||||
if (InBigEndianMode())
|
||||
data = Common::swap64(data);
|
||||
|
||||
memory.Write64(address, data);
|
||||
}
|
||||
|
||||
// Reads from the CP15 registers. Used with implementation of the MRC instruction.
|
||||
// Note that since the 3DS does not have the hypervisor extensions, these registers
|
||||
// are not implemented.
|
||||
u32 ARMul_State::ReadCP15Register(u32 crn, u32 opcode_1, u32 crm, u32 opcode_2) const {
|
||||
// Unprivileged registers
|
||||
if (crn == 13 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 2)
|
||||
return CP15[CP15_THREAD_UPRW];
|
||||
|
||||
if (opcode_2 == 3)
|
||||
return CP15[CP15_THREAD_URO];
|
||||
}
|
||||
|
||||
if (InAPrivilegedMode()) {
|
||||
if (crn == 0 && opcode_1 == 0) {
|
||||
if (crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_MAIN_ID];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_CACHE_TYPE];
|
||||
|
||||
if (opcode_2 == 3)
|
||||
return CP15[CP15_TLB_TYPE];
|
||||
|
||||
if (opcode_2 == 5)
|
||||
return CP15[CP15_CPU_ID];
|
||||
} else if (crm == 1) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_PROCESSOR_FEATURE_0];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_PROCESSOR_FEATURE_1];
|
||||
|
||||
if (opcode_2 == 2)
|
||||
return CP15[CP15_DEBUG_FEATURE_0];
|
||||
|
||||
if (opcode_2 == 4)
|
||||
return CP15[CP15_MEMORY_MODEL_FEATURE_0];
|
||||
|
||||
if (opcode_2 == 5)
|
||||
return CP15[CP15_MEMORY_MODEL_FEATURE_1];
|
||||
|
||||
if (opcode_2 == 6)
|
||||
return CP15[CP15_MEMORY_MODEL_FEATURE_2];
|
||||
|
||||
if (opcode_2 == 7)
|
||||
return CP15[CP15_MEMORY_MODEL_FEATURE_3];
|
||||
} else if (crm == 2) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_ISA_FEATURE_0];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_ISA_FEATURE_1];
|
||||
|
||||
if (opcode_2 == 2)
|
||||
return CP15[CP15_ISA_FEATURE_2];
|
||||
|
||||
if (opcode_2 == 3)
|
||||
return CP15[CP15_ISA_FEATURE_3];
|
||||
|
||||
if (opcode_2 == 4)
|
||||
return CP15[CP15_ISA_FEATURE_4];
|
||||
}
|
||||
}
|
||||
|
||||
if (crn == 1 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_CONTROL];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_AUXILIARY_CONTROL];
|
||||
|
||||
if (opcode_2 == 2)
|
||||
return CP15[CP15_COPROCESSOR_ACCESS_CONTROL];
|
||||
}
|
||||
|
||||
if (crn == 2 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_TRANSLATION_BASE_TABLE_0];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_TRANSLATION_BASE_TABLE_1];
|
||||
|
||||
if (opcode_2 == 2)
|
||||
return CP15[CP15_TRANSLATION_BASE_CONTROL];
|
||||
}
|
||||
|
||||
if (crn == 3 && opcode_1 == 0 && crm == 0 && opcode_2 == 0)
|
||||
return CP15[CP15_DOMAIN_ACCESS_CONTROL];
|
||||
|
||||
if (crn == 5 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_FAULT_STATUS];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_INSTR_FAULT_STATUS];
|
||||
}
|
||||
|
||||
if (crn == 6 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_FAULT_ADDRESS];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_WFAR];
|
||||
}
|
||||
|
||||
if (crn == 7 && opcode_1 == 0 && crm == 4 && opcode_2 == 0)
|
||||
return CP15[CP15_PHYS_ADDRESS];
|
||||
|
||||
if (crn == 9 && opcode_1 == 0 && crm == 0 && opcode_2 == 0)
|
||||
return CP15[CP15_DATA_CACHE_LOCKDOWN];
|
||||
|
||||
if (crn == 10 && opcode_1 == 0) {
|
||||
if (crm == 0 && opcode_2 == 0)
|
||||
return CP15[CP15_TLB_LOCKDOWN];
|
||||
|
||||
if (crm == 2) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_PRIMARY_REGION_REMAP];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_NORMAL_REGION_REMAP];
|
||||
}
|
||||
}
|
||||
|
||||
if (crn == 13 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_PID];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_CONTEXT_ID];
|
||||
|
||||
if (opcode_2 == 4)
|
||||
return CP15[CP15_THREAD_PRW];
|
||||
}
|
||||
|
||||
if (crn == 15) {
|
||||
if (opcode_1 == 0 && crm == 12) {
|
||||
if (opcode_2 == 0)
|
||||
return CP15[CP15_PERFORMANCE_MONITOR_CONTROL];
|
||||
|
||||
if (opcode_2 == 1)
|
||||
return CP15[CP15_CYCLE_COUNTER];
|
||||
|
||||
if (opcode_2 == 2)
|
||||
return CP15[CP15_COUNT_0];
|
||||
|
||||
if (opcode_2 == 3)
|
||||
return CP15[CP15_COUNT_1];
|
||||
}
|
||||
|
||||
if (opcode_1 == 5 && opcode_2 == 2) {
|
||||
if (crm == 5)
|
||||
return CP15[CP15_MAIN_TLB_LOCKDOWN_VIRT_ADDRESS];
|
||||
|
||||
if (crm == 6)
|
||||
return CP15[CP15_MAIN_TLB_LOCKDOWN_PHYS_ADDRESS];
|
||||
|
||||
if (crm == 7)
|
||||
return CP15[CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE];
|
||||
}
|
||||
|
||||
if (opcode_1 == 7 && crm == 1 && opcode_2 == 0)
|
||||
return CP15[CP15_TLB_DEBUG_CONTROL];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ERROR(Core_ARM11, "MRC CRn={}, CRm={}, OP1={} OP2={} is not implemented. Returning zero.",
|
||||
crn, crm, opcode_1, opcode_2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write to the CP15 registers. Used with implementation of the MCR instruction.
|
||||
// Note that since the 3DS does not have the hypervisor extensions, these registers
|
||||
// are not implemented.
|
||||
void ARMul_State::WriteCP15Register(u32 value, u32 crn, u32 opcode_1, u32 crm, u32 opcode_2) {
|
||||
if (InAPrivilegedMode()) {
|
||||
if (crn == 1 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_CONTROL] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_AUXILIARY_CONTROL] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_COPROCESSOR_ACCESS_CONTROL] = value;
|
||||
} else if (crn == 2 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_TRANSLATION_BASE_TABLE_0] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_TRANSLATION_BASE_TABLE_1] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_TRANSLATION_BASE_CONTROL] = value;
|
||||
} else if (crn == 3 && opcode_1 == 0 && crm == 0 && opcode_2 == 0) {
|
||||
CP15[CP15_DOMAIN_ACCESS_CONTROL] = value;
|
||||
} else if (crn == 5 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_FAULT_STATUS] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_INSTR_FAULT_STATUS] = value;
|
||||
} else if (crn == 6 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_FAULT_ADDRESS] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_WFAR] = value;
|
||||
} else if (crn == 7 && opcode_1 == 0) {
|
||||
if (crm == 0 && opcode_2 == 4) {
|
||||
CP15[CP15_WAIT_FOR_INTERRUPT] = value;
|
||||
} else if (crm == 4 && opcode_2 == 0) {
|
||||
LOG_ERROR(Core_ARM11, "Unimplemented virtual to physical address");
|
||||
} else if (crm == 5) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_INVALIDATE_INSTR_CACHE] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_INVALIDATE_INSTR_CACHE_USING_MVA] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_INVALIDATE_INSTR_CACHE_USING_INDEX] = value;
|
||||
else if (opcode_2 == 6)
|
||||
CP15[CP15_FLUSH_BRANCH_TARGET_CACHE] = value;
|
||||
else if (opcode_2 == 7)
|
||||
CP15[CP15_FLUSH_BRANCH_TARGET_CACHE_ENTRY] = value;
|
||||
} else if (crm == 6) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_INVALIDATE_DATA_CACHE] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_INVALIDATE_DATA_CACHE_LINE_USING_MVA] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_INVALIDATE_DATA_CACHE_LINE_USING_INDEX] = value;
|
||||
} else if (crm == 7 && opcode_2 == 0) {
|
||||
CP15[CP15_INVALIDATE_DATA_AND_INSTR_CACHE] = value;
|
||||
} else if (crm == 10) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_CLEAN_DATA_CACHE] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_CLEAN_DATA_CACHE_LINE_USING_MVA] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_CLEAN_DATA_CACHE_LINE_USING_INDEX] = value;
|
||||
} else if (crm == 14) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_CLEAN_AND_INVALIDATE_DATA_CACHE] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_MVA] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_INDEX] = value;
|
||||
}
|
||||
} else if (crn == 8 && opcode_1 == 0) {
|
||||
if (crm == 5) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_INVALIDATE_ITLB] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_INVALIDATE_ITLB_SINGLE_ENTRY] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_INVALIDATE_ITLB_ENTRY_ON_ASID_MATCH] = value;
|
||||
else if (opcode_2 == 3)
|
||||
CP15[CP15_INVALIDATE_ITLB_ENTRY_ON_MVA] = value;
|
||||
} else if (crm == 6) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_INVALIDATE_DTLB] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_INVALIDATE_DTLB_SINGLE_ENTRY] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_INVALIDATE_DTLB_ENTRY_ON_ASID_MATCH] = value;
|
||||
else if (opcode_2 == 3)
|
||||
CP15[CP15_INVALIDATE_DTLB_ENTRY_ON_MVA] = value;
|
||||
} else if (crm == 7) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_INVALIDATE_UTLB] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_INVALIDATE_UTLB_SINGLE_ENTRY] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_INVALIDATE_UTLB_ENTRY_ON_ASID_MATCH] = value;
|
||||
else if (opcode_2 == 3)
|
||||
CP15[CP15_INVALIDATE_UTLB_ENTRY_ON_MVA] = value;
|
||||
}
|
||||
} else if (crn == 9 && opcode_1 == 0 && crm == 0 && opcode_2 == 0) {
|
||||
CP15[CP15_DATA_CACHE_LOCKDOWN] = value;
|
||||
} else if (crn == 10 && opcode_1 == 0) {
|
||||
if (crm == 0 && opcode_2 == 0) {
|
||||
CP15[CP15_TLB_LOCKDOWN] = value;
|
||||
} else if (crm == 2) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_PRIMARY_REGION_REMAP] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_NORMAL_REGION_REMAP] = value;
|
||||
}
|
||||
} else if (crn == 13 && opcode_1 == 0 && crm == 0) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_PID] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_CONTEXT_ID] = value;
|
||||
else if (opcode_2 == 3)
|
||||
CP15[CP15_THREAD_URO] = value;
|
||||
else if (opcode_2 == 4)
|
||||
CP15[CP15_THREAD_PRW] = value;
|
||||
} else if (crn == 15) {
|
||||
if (opcode_1 == 0 && crm == 12) {
|
||||
if (opcode_2 == 0)
|
||||
CP15[CP15_PERFORMANCE_MONITOR_CONTROL] = value;
|
||||
else if (opcode_2 == 1)
|
||||
CP15[CP15_CYCLE_COUNTER] = value;
|
||||
else if (opcode_2 == 2)
|
||||
CP15[CP15_COUNT_0] = value;
|
||||
else if (opcode_2 == 3)
|
||||
CP15[CP15_COUNT_1] = value;
|
||||
} else if (opcode_1 == 5) {
|
||||
if (crm == 4) {
|
||||
if (opcode_2 == 2)
|
||||
CP15[CP15_READ_MAIN_TLB_LOCKDOWN_ENTRY] = value;
|
||||
else if (opcode_2 == 4)
|
||||
CP15[CP15_WRITE_MAIN_TLB_LOCKDOWN_ENTRY] = value;
|
||||
} else if (crm == 5 && opcode_2 == 2) {
|
||||
CP15[CP15_MAIN_TLB_LOCKDOWN_VIRT_ADDRESS] = value;
|
||||
} else if (crm == 6 && opcode_2 == 2) {
|
||||
CP15[CP15_MAIN_TLB_LOCKDOWN_PHYS_ADDRESS] = value;
|
||||
} else if (crm == 7 && opcode_2 == 2) {
|
||||
CP15[CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE] = value;
|
||||
}
|
||||
} else if (opcode_1 == 7 && crm == 1 && opcode_2 == 0) {
|
||||
CP15[CP15_TLB_DEBUG_CONTROL] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unprivileged registers
|
||||
if (crn == 7 && opcode_1 == 0 && crm == 5 && opcode_2 == 4) {
|
||||
CP15[CP15_FLUSH_PREFETCH_BUFFER] = value;
|
||||
} else if (crn == 7 && opcode_1 == 0 && crm == 10) {
|
||||
if (opcode_2 == 4)
|
||||
CP15[CP15_DATA_SYNC_BARRIER] = value;
|
||||
else if (opcode_2 == 5)
|
||||
CP15[CP15_DATA_MEMORY_BARRIER] = value;
|
||||
} else if (crn == 13 && opcode_1 == 0 && crm == 0 && opcode_2 == 2) {
|
||||
CP15[CP15_THREAD_UPRW] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void ARMul_State::ServeBreak() {
|
||||
if (!GDBStub::IsServerEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
|
||||
DEBUG_ASSERT(Reg[15] == last_bkpt.address);
|
||||
}
|
||||
|
||||
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
|
||||
system.GetRunningCore().SaveContext(thread->context);
|
||||
|
||||
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
}
|
||||
272
src/core/arm/skyeye_common/armstate.h
Normal file
272
src/core/arm/skyeye_common/armstate.h
Normal file
@@ -0,0 +1,272 @@
|
||||
/* armdefs.h -- ARMulator common definitions: ARM6 Instruction Emulator.
|
||||
Copyright (C) 1994 Advanced RISC Machines Ltd.
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/skyeye_common/arm_regformat.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
}
|
||||
|
||||
// Signal levels
|
||||
enum { LOW = 0, HIGH = 1, LOWHIGH = 1, HIGHLOW = 2 };
|
||||
|
||||
// Cache types
|
||||
enum {
|
||||
NONCACHE = 0,
|
||||
DATACACHE = 1,
|
||||
INSTCACHE = 2,
|
||||
};
|
||||
|
||||
// ARM privilege modes
|
||||
enum PrivilegeMode {
|
||||
USER32MODE = 16,
|
||||
FIQ32MODE = 17,
|
||||
IRQ32MODE = 18,
|
||||
SVC32MODE = 19,
|
||||
ABORT32MODE = 23,
|
||||
UNDEF32MODE = 27,
|
||||
SYSTEM32MODE = 31
|
||||
};
|
||||
|
||||
// ARM privilege mode register banks
|
||||
enum {
|
||||
USERBANK = 0,
|
||||
FIQBANK = 1,
|
||||
IRQBANK = 2,
|
||||
SVCBANK = 3,
|
||||
ABORTBANK = 4,
|
||||
UNDEFBANK = 5,
|
||||
DUMMYBANK = 6,
|
||||
SYSTEMBANK = 7
|
||||
};
|
||||
|
||||
// Hardware vector addresses
|
||||
enum {
|
||||
ARMResetV = 0,
|
||||
ARMUndefinedInstrV = 4,
|
||||
ARMSWIV = 8,
|
||||
ARMPrefetchAbortV = 12,
|
||||
ARMDataAbortV = 16,
|
||||
ARMAddrExceptnV = 20,
|
||||
ARMIRQV = 24,
|
||||
ARMFIQV = 28,
|
||||
ARMErrorV = 32, // This is an offset, not an address!
|
||||
|
||||
ARMul_ResetV = ARMResetV,
|
||||
ARMul_UndefinedInstrV = ARMUndefinedInstrV,
|
||||
ARMul_SWIV = ARMSWIV,
|
||||
ARMul_PrefetchAbortV = ARMPrefetchAbortV,
|
||||
ARMul_DataAbortV = ARMDataAbortV,
|
||||
ARMul_AddrExceptnV = ARMAddrExceptnV,
|
||||
ARMul_IRQV = ARMIRQV,
|
||||
ARMul_FIQV = ARMFIQV
|
||||
};
|
||||
|
||||
// Coprocessor status values
|
||||
enum {
|
||||
ARMul_FIRST = 0,
|
||||
ARMul_TRANSFER = 1,
|
||||
ARMul_BUSY = 2,
|
||||
ARMul_DATA = 3,
|
||||
ARMul_INTERRUPT = 4,
|
||||
ARMul_DONE = 0,
|
||||
ARMul_CANT = 1,
|
||||
ARMul_INC = 3
|
||||
};
|
||||
|
||||
// Instruction condition codes
|
||||
enum ConditionCode {
|
||||
EQ = 0,
|
||||
NE = 1,
|
||||
CS = 2,
|
||||
CC = 3,
|
||||
MI = 4,
|
||||
PL = 5,
|
||||
VS = 6,
|
||||
VC = 7,
|
||||
HI = 8,
|
||||
LS = 9,
|
||||
GE = 10,
|
||||
LT = 11,
|
||||
GT = 12,
|
||||
LE = 13,
|
||||
AL = 14,
|
||||
NV = 15,
|
||||
};
|
||||
|
||||
// Flags for use with the APSR.
|
||||
enum : u32 {
|
||||
NBIT = (1U << 31U),
|
||||
ZBIT = (1 << 30),
|
||||
CBIT = (1 << 29),
|
||||
VBIT = (1 << 28),
|
||||
QBIT = (1 << 27),
|
||||
JBIT = (1 << 24),
|
||||
EBIT = (1 << 9),
|
||||
ABIT = (1 << 8),
|
||||
IBIT = (1 << 7),
|
||||
FBIT = (1 << 6),
|
||||
TBIT = (1 << 5),
|
||||
|
||||
// Masks for groups of bits in the APSR.
|
||||
MODEBITS = 0x1F,
|
||||
INTBITS = 0x1C0,
|
||||
};
|
||||
|
||||
// Values for Emulate.
|
||||
enum {
|
||||
STOP = 0, // Stop
|
||||
CHANGEMODE = 1, // Change mode
|
||||
ONCE = 2, // Execute just one iteration
|
||||
RUN = 3 // Continuous execution
|
||||
};
|
||||
|
||||
struct ARMul_State final {
|
||||
public:
|
||||
explicit ARMul_State(Core::System& system, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode);
|
||||
|
||||
void ChangePrivilegeMode(u32 new_mode);
|
||||
void Reset();
|
||||
|
||||
// Reads/writes data in big/little endian format based on the
|
||||
// state of the E (endian) bit in the APSR.
|
||||
u8 ReadMemory8(u32 address) const;
|
||||
u16 ReadMemory16(u32 address) const;
|
||||
u32 ReadMemory32(u32 address) const;
|
||||
u64 ReadMemory64(u32 address) const;
|
||||
void WriteMemory8(u32 address, u8 data);
|
||||
void WriteMemory16(u32 address, u16 data);
|
||||
void WriteMemory32(u32 address, u32 data);
|
||||
void WriteMemory64(u32 address, u64 data);
|
||||
|
||||
u32 ReadCP15Register(u32 crn, u32 opcode_1, u32 crm, u32 opcode_2) const;
|
||||
void WriteCP15Register(u32 value, u32 crn, u32 opcode_1, u32 crm, u32 opcode_2);
|
||||
|
||||
// Exclusive memory access functions
|
||||
bool IsExclusiveMemoryAccess(u32 address) const {
|
||||
return exclusive_state && exclusive_tag == (address & RESERVATION_GRANULE_MASK);
|
||||
}
|
||||
void SetExclusiveMemoryAddress(u32 address) {
|
||||
exclusive_tag = address & RESERVATION_GRANULE_MASK;
|
||||
exclusive_state = true;
|
||||
}
|
||||
void UnsetExclusiveMemoryAddress() {
|
||||
exclusive_tag = 0xFFFFFFFF;
|
||||
exclusive_state = false;
|
||||
}
|
||||
|
||||
// Whether or not the given CPU is in big endian mode (E bit is set)
|
||||
bool InBigEndianMode() const {
|
||||
return (Cpsr & (1 << 9)) != 0;
|
||||
}
|
||||
// Whether or not the given CPU is in a mode other than user mode.
|
||||
bool InAPrivilegedMode() const {
|
||||
return (Mode != USER32MODE);
|
||||
}
|
||||
// Whether or not the current CPU mode has a Saved Program Status Register
|
||||
bool CurrentModeHasSPSR() const {
|
||||
return Mode != SYSTEM32MODE && InAPrivilegedMode();
|
||||
}
|
||||
// Note that for the 3DS, a Thumb instruction will only ever be
|
||||
// two bytes in size. Thus we don't need to worry about ThumbEE
|
||||
// or Thumb-2 where instructions can be 4 bytes in length.
|
||||
u32 GetInstructionSize() const {
|
||||
return TFlag ? 2 : 4;
|
||||
}
|
||||
|
||||
void RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||
last_bkpt = bkpt;
|
||||
last_bkpt_hit = true;
|
||||
}
|
||||
|
||||
void ServeBreak();
|
||||
|
||||
Core::System& system;
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
std::array<u32, 16> Reg{}; // The current register file
|
||||
std::array<u32, 2> Reg_usr{};
|
||||
std::array<u32, 2> Reg_svc{}; // R13_SVC R14_SVC
|
||||
std::array<u32, 2> Reg_abort{}; // R13_ABORT R14_ABORT
|
||||
std::array<u32, 2> Reg_undef{}; // R13 UNDEF R14 UNDEF
|
||||
std::array<u32, 2> Reg_irq{}; // R13_IRQ R14_IRQ
|
||||
std::array<u32, 7> Reg_firq{}; // R8---R14 FIRQ
|
||||
std::array<u32, 7> Spsr{}; // The exception psr's
|
||||
std::array<u32, CP15_REGISTER_COUNT> CP15{};
|
||||
|
||||
// FPSID, FPSCR, and FPEXC
|
||||
std::array<u32, VFP_SYSTEM_REGISTER_COUNT> VFP{};
|
||||
|
||||
// VFPv2 and VFPv3-D16 has 16 doubleword registers (D0-D16 or S0-S31).
|
||||
// VFPv3-D32/ASIMD may have up to 32 doubleword registers (D0-D31),
|
||||
// and only 32 singleword registers are accessible (S0-S31).
|
||||
std::array<u32, 64> ExtReg{};
|
||||
|
||||
u32 Emulate; // To start and stop emulation
|
||||
u32 Cpsr; // The current PSR
|
||||
u32 Spsr_copy;
|
||||
u32 phys_pc;
|
||||
|
||||
u32 Mode; // The current mode
|
||||
u32 Bank; // The current register bank
|
||||
|
||||
u32 NFlag, ZFlag, CFlag, VFlag, IFFlags; // Dummy flags for speed
|
||||
unsigned int shifter_carry_out;
|
||||
|
||||
u32 TFlag; // Thumb state
|
||||
|
||||
unsigned long long NumInstrs; // The number of instructions executed
|
||||
u64 NumInstrsToExecute;
|
||||
|
||||
unsigned NresetSig; // Reset the processor
|
||||
unsigned NfiqSig;
|
||||
unsigned NirqSig;
|
||||
|
||||
unsigned abortSig;
|
||||
unsigned NtransSig;
|
||||
unsigned bigendSig;
|
||||
unsigned syscallSig;
|
||||
|
||||
// TODO(bunnei): Move this cache to a better place - it should be per codeset (likely per
|
||||
// process for our purposes), not per ARMul_State (which tracks CPU core state).
|
||||
std::unordered_map<u32, std::size_t> instruction_cache;
|
||||
|
||||
private:
|
||||
void ResetMPCoreCP15Registers();
|
||||
|
||||
// Defines a reservation granule of 2 words, which protects the first 2 words starting at the
|
||||
// tag. This is the smallest granule allowed by the v7 spec, and is coincidentally just large
|
||||
// enough to support LDR/STREXD.
|
||||
static const u32 RESERVATION_GRANULE_MASK = 0xFFFFFFF8;
|
||||
|
||||
u32 exclusive_tag; // The address for which the local monitor is in exclusive access mode
|
||||
bool exclusive_state;
|
||||
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit = false;
|
||||
};
|
||||
186
src/core/arm/skyeye_common/armsupp.cpp
Normal file
186
src/core/arm/skyeye_common/armsupp.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/* armsupp.c -- ARMulator support code: ARM6 Instruction Emulator.
|
||||
Copyright (C) 1994 Advanced RISC Machines Ltd.
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
||||
|
||||
#include "core/arm/skyeye_common/armsupp.h"
|
||||
|
||||
// Unsigned sum of absolute difference
|
||||
u8 ARMul_UnsignedAbsoluteDifference(u8 left, u8 right) {
|
||||
if (left > right)
|
||||
return left - right;
|
||||
|
||||
return right - left;
|
||||
}
|
||||
|
||||
// Add with carry, indicates if a carry-out or signed overflow occurred.
|
||||
u32 AddWithCarry(u32 left, u32 right, u32 carry_in, bool* carry_out_occurred,
|
||||
bool* overflow_occurred) {
|
||||
u64 unsigned_sum = (u64)left + (u64)right + (u64)carry_in;
|
||||
s64 signed_sum = (s64)(s32)left + (s64)(s32)right + (s64)carry_in;
|
||||
u64 result = (unsigned_sum & 0xFFFFFFFF);
|
||||
|
||||
if (carry_out_occurred)
|
||||
*carry_out_occurred = (result != unsigned_sum);
|
||||
|
||||
if (overflow_occurred)
|
||||
*overflow_occurred = ((s64)(s32)result != signed_sum);
|
||||
|
||||
return (u32)result;
|
||||
}
|
||||
|
||||
// Compute whether an addition of A and B, giving RESULT, overflowed.
|
||||
bool AddOverflow(u32 a, u32 b, u32 result) {
|
||||
return ((NEG(a) && NEG(b) && POS(result)) || (POS(a) && POS(b) && NEG(result)));
|
||||
}
|
||||
|
||||
// Compute whether a subtraction of A and B, giving RESULT, overflowed.
|
||||
bool SubOverflow(u32 a, u32 b, u32 result) {
|
||||
return ((NEG(a) && POS(b) && POS(result)) || (POS(a) && NEG(b) && NEG(result)));
|
||||
}
|
||||
|
||||
// Returns true if the Q flag should be set as a result of overflow.
|
||||
bool ARMul_AddOverflowQ(u32 a, u32 b) {
|
||||
u32 result = a + b;
|
||||
if (((result ^ a) & (u32)0x80000000) && ((a ^ b) & (u32)0x80000000) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 8-bit signed saturated addition
|
||||
u8 ARMul_SignedSaturatedAdd8(u8 left, u8 right) {
|
||||
u8 result = left + right;
|
||||
|
||||
if (((result ^ left) & 0x80) && ((left ^ right) & 0x80) == 0) {
|
||||
if (left & 0x80)
|
||||
result = 0x80;
|
||||
else
|
||||
result = 0x7F;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 8-bit signed saturated subtraction
|
||||
u8 ARMul_SignedSaturatedSub8(u8 left, u8 right) {
|
||||
u8 result = left - right;
|
||||
|
||||
if (((result ^ left) & 0x80) && ((left ^ right) & 0x80) != 0) {
|
||||
if (left & 0x80)
|
||||
result = 0x80;
|
||||
else
|
||||
result = 0x7F;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 16-bit signed saturated addition
|
||||
u16 ARMul_SignedSaturatedAdd16(u16 left, u16 right) {
|
||||
u16 result = left + right;
|
||||
|
||||
if (((result ^ left) & 0x8000) && ((left ^ right) & 0x8000) == 0) {
|
||||
if (left & 0x8000)
|
||||
result = 0x8000;
|
||||
else
|
||||
result = 0x7FFF;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 16-bit signed saturated subtraction
|
||||
u16 ARMul_SignedSaturatedSub16(u16 left, u16 right) {
|
||||
u16 result = left - right;
|
||||
|
||||
if (((result ^ left) & 0x8000) && ((left ^ right) & 0x8000) != 0) {
|
||||
if (left & 0x8000)
|
||||
result = 0x8000;
|
||||
else
|
||||
result = 0x7FFF;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 8-bit unsigned saturated addition
|
||||
u8 ARMul_UnsignedSaturatedAdd8(u8 left, u8 right) {
|
||||
u8 result = left + right;
|
||||
|
||||
if (result < left)
|
||||
result = 0xFF;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 16-bit unsigned saturated addition
|
||||
u16 ARMul_UnsignedSaturatedAdd16(u16 left, u16 right) {
|
||||
u16 result = left + right;
|
||||
|
||||
if (result < left)
|
||||
result = 0xFFFF;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 8-bit unsigned saturated subtraction
|
||||
u8 ARMul_UnsignedSaturatedSub8(u8 left, u8 right) {
|
||||
if (left <= right)
|
||||
return 0;
|
||||
|
||||
return left - right;
|
||||
}
|
||||
|
||||
// 16-bit unsigned saturated subtraction
|
||||
u16 ARMul_UnsignedSaturatedSub16(u16 left, u16 right) {
|
||||
if (left <= right)
|
||||
return 0;
|
||||
|
||||
return left - right;
|
||||
}
|
||||
|
||||
// Signed saturation.
|
||||
u32 ARMul_SignedSatQ(s32 value, u8 shift, bool* saturation_occurred) {
|
||||
const u32 max = (1 << shift) - 1;
|
||||
const s32 top = (value >> shift);
|
||||
|
||||
if (top > 0) {
|
||||
*saturation_occurred = true;
|
||||
return max;
|
||||
} else if (top < -1) {
|
||||
*saturation_occurred = true;
|
||||
return ~max;
|
||||
}
|
||||
|
||||
*saturation_occurred = false;
|
||||
return (u32)value;
|
||||
}
|
||||
|
||||
// Unsigned saturation
|
||||
u32 ARMul_UnsignedSatQ(s32 value, u8 shift, bool* saturation_occurred) {
|
||||
const u32 max = (1 << shift) - 1;
|
||||
|
||||
if (value < 0) {
|
||||
*saturation_occurred = true;
|
||||
return 0;
|
||||
} else if ((u32)value > max) {
|
||||
*saturation_occurred = true;
|
||||
return max;
|
||||
}
|
||||
|
||||
*saturation_occurred = false;
|
||||
return (u32)value;
|
||||
}
|
||||
32
src/core/arm/skyeye_common/armsupp.h
Normal file
32
src/core/arm/skyeye_common/armsupp.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#define BITS(s, a, b) ((s << ((sizeof(s) * 8 - 1) - b)) >> (sizeof(s) * 8 - b + a - 1))
|
||||
#define BIT(s, n) ((s >> (n)) & 1)
|
||||
|
||||
#define POS(i) ((~(i)) >> 31)
|
||||
#define NEG(i) ((i) >> 31)
|
||||
|
||||
bool AddOverflow(u32, u32, u32);
|
||||
bool SubOverflow(u32, u32, u32);
|
||||
|
||||
u32 AddWithCarry(u32, u32, u32, bool*, bool*);
|
||||
bool ARMul_AddOverflowQ(u32, u32);
|
||||
|
||||
u8 ARMul_SignedSaturatedAdd8(u8, u8);
|
||||
u8 ARMul_SignedSaturatedSub8(u8, u8);
|
||||
u16 ARMul_SignedSaturatedAdd16(u16, u16);
|
||||
u16 ARMul_SignedSaturatedSub16(u16, u16);
|
||||
|
||||
u8 ARMul_UnsignedSaturatedAdd8(u8, u8);
|
||||
u16 ARMul_UnsignedSaturatedAdd16(u16, u16);
|
||||
u8 ARMul_UnsignedSaturatedSub8(u8, u8);
|
||||
u16 ARMul_UnsignedSaturatedSub16(u16, u16);
|
||||
u8 ARMul_UnsignedAbsoluteDifference(u8, u8);
|
||||
u32 ARMul_SignedSatQ(s32, u8, bool*);
|
||||
u32 ARMul_UnsignedSatQ(s32, u8, bool*);
|
||||
83
src/core/arm/skyeye_common/vfp/asm_vfp.h
Normal file
83
src/core/arm/skyeye_common/vfp/asm_vfp.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* arch/arm/include/asm/vfp.h
|
||||
*
|
||||
* VFP register definitions.
|
||||
* First, the standard VFP set.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// ARM11 MPCore FPSID Information
|
||||
// Note that these are used as values and not as flags.
|
||||
enum : u32 {
|
||||
VFP_FPSID_IMPLMEN = 0x41, // Implementation code. Should be the same as cp15 0 c0 0
|
||||
VFP_FPSID_SW = 0, // Software emulation bit value
|
||||
VFP_FPSID_SUBARCH = 0x1, // Subarchitecture version number
|
||||
VFP_FPSID_PARTNUM = 0x20, // Part number
|
||||
VFP_FPSID_VARIANT = 0xB, // Variant number
|
||||
VFP_FPSID_REVISION = 0x4 // Revision number
|
||||
};
|
||||
|
||||
// FPEXC bits
|
||||
enum : u32 {
|
||||
FPEXC_EX = (1U << 31U),
|
||||
FPEXC_EN = (1 << 30),
|
||||
FPEXC_DEX = (1 << 29),
|
||||
FPEXC_FP2V = (1 << 28),
|
||||
FPEXC_VV = (1 << 27),
|
||||
FPEXC_TFV = (1 << 26),
|
||||
FPEXC_LENGTH_BIT = (8),
|
||||
FPEXC_LENGTH_MASK = (7 << FPEXC_LENGTH_BIT),
|
||||
FPEXC_IDF = (1 << 7),
|
||||
FPEXC_IXF = (1 << 4),
|
||||
FPEXC_UFF = (1 << 3),
|
||||
FPEXC_OFF = (1 << 2),
|
||||
FPEXC_DZF = (1 << 1),
|
||||
FPEXC_IOF = (1 << 0),
|
||||
FPEXC_TRAP_MASK = (FPEXC_IDF | FPEXC_IXF | FPEXC_UFF | FPEXC_OFF | FPEXC_DZF | FPEXC_IOF)
|
||||
};
|
||||
|
||||
// FPSCR Flags
|
||||
enum : u32 {
|
||||
FPSCR_NFLAG = (1U << 31U), // Negative condition flag
|
||||
FPSCR_ZFLAG = (1 << 30), // Zero condition flag
|
||||
FPSCR_CFLAG = (1 << 29), // Carry condition flag
|
||||
FPSCR_VFLAG = (1 << 28), // Overflow condition flag
|
||||
|
||||
FPSCR_QC = (1 << 27), // Cumulative saturation bit
|
||||
FPSCR_AHP = (1 << 26), // Alternative half-precision control bit
|
||||
FPSCR_DEFAULT_NAN = (1 << 25), // Default NaN mode control bit
|
||||
FPSCR_FLUSH_TO_ZERO = (1 << 24), // Flush-to-zero mode control bit
|
||||
FPSCR_RMODE_MASK = (3 << 22), // Rounding Mode bit mask
|
||||
FPSCR_STRIDE_MASK = (3 << 20), // Vector stride bit mask
|
||||
FPSCR_LENGTH_MASK = (7 << 16), // Vector length bit mask
|
||||
|
||||
FPSCR_IDE = (1 << 15), // Input Denormal exception trap enable.
|
||||
FPSCR_IXE = (1 << 12), // Inexact exception trap enable
|
||||
FPSCR_UFE = (1 << 11), // Undeflow exception trap enable
|
||||
FPSCR_OFE = (1 << 10), // Overflow exception trap enable
|
||||
FPSCR_DZE = (1 << 9), // Division by Zero exception trap enable
|
||||
FPSCR_IOE = (1 << 8), // Invalid Operation exception trap enable
|
||||
|
||||
FPSCR_IDC = (1 << 7), // Input Denormal cumulative exception bit
|
||||
FPSCR_IXC = (1 << 4), // Inexact cumulative exception bit
|
||||
FPSCR_UFC = (1 << 3), // Undeflow cumulative exception bit
|
||||
FPSCR_OFC = (1 << 2), // Overflow cumulative exception bit
|
||||
FPSCR_DZC = (1 << 1), // Division by Zero cumulative exception bit
|
||||
FPSCR_IOC = (1 << 0), // Invalid Operation cumulative exception bit
|
||||
};
|
||||
|
||||
// FPSCR bit offsets
|
||||
enum : u32 {
|
||||
FPSCR_RMODE_BIT = 22,
|
||||
FPSCR_STRIDE_BIT = 20,
|
||||
FPSCR_LENGTH_BIT = 16,
|
||||
};
|
||||
|
||||
// FPSCR rounding modes
|
||||
enum : u32 {
|
||||
FPSCR_ROUND_NEAREST = (0 << 22),
|
||||
FPSCR_ROUND_PLUSINF = (1 << 22),
|
||||
FPSCR_ROUND_MINUSINF = (2 << 22),
|
||||
FPSCR_ROUND_TOZERO = (3 << 22)
|
||||
};
|
||||
137
src/core/arm/skyeye_common/vfp/vfp.cpp
Normal file
137
src/core/arm/skyeye_common/vfp/vfp.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
armvfp.c - ARM VFPv3 emulation unit
|
||||
Copyright (C) 2003 Skyeye Develop Group
|
||||
for help please send mail to <skyeye-developer@lists.gro.clinux.org>
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/* Note: this file handles interface with arm core and vfp registers */
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
#include "core/arm/skyeye_common/vfp/asm_vfp.h"
|
||||
#include "core/arm/skyeye_common/vfp/vfp.h"
|
||||
|
||||
void VFPInit(ARMul_State* state) {
|
||||
state->VFP[VFP_FPSID] = VFP_FPSID_IMPLMEN << 24 | VFP_FPSID_SW << 23 | VFP_FPSID_SUBARCH << 16 |
|
||||
VFP_FPSID_PARTNUM << 8 | VFP_FPSID_VARIANT << 4 | VFP_FPSID_REVISION;
|
||||
state->VFP[VFP_FPEXC] = 0;
|
||||
state->VFP[VFP_FPSCR] = 0;
|
||||
|
||||
// ARM11 MPCore instruction register reset values.
|
||||
state->VFP[VFP_FPINST] = 0xEE000A00;
|
||||
state->VFP[VFP_FPINST2] = 0;
|
||||
|
||||
// ARM11 MPCore feature register values.
|
||||
state->VFP[VFP_MVFR0] = 0x11111111;
|
||||
state->VFP[VFP_MVFR1] = 0;
|
||||
}
|
||||
|
||||
void VMOVBRS(ARMul_State* state, u32 to_arm, u32 t, u32 n, u32* value) {
|
||||
if (to_arm) {
|
||||
*value = state->ExtReg[n];
|
||||
} else {
|
||||
state->ExtReg[n] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
void VMOVBRRD(ARMul_State* state, u32 to_arm, u32 t, u32 t2, u32 n, u32* value1, u32* value2) {
|
||||
if (to_arm) {
|
||||
*value2 = state->ExtReg[n * 2 + 1];
|
||||
*value1 = state->ExtReg[n * 2];
|
||||
} else {
|
||||
state->ExtReg[n * 2 + 1] = *value2;
|
||||
state->ExtReg[n * 2] = *value1;
|
||||
}
|
||||
}
|
||||
void VMOVBRRSS(ARMul_State* state, u32 to_arm, u32 t, u32 t2, u32 n, u32* value1, u32* value2) {
|
||||
if (to_arm) {
|
||||
*value1 = state->ExtReg[n + 0];
|
||||
*value2 = state->ExtReg[n + 1];
|
||||
} else {
|
||||
state->ExtReg[n + 0] = *value1;
|
||||
state->ExtReg[n + 1] = *value2;
|
||||
}
|
||||
}
|
||||
|
||||
void VMOVI(ARMul_State* state, u32 single, u32 d, u32 imm) {
|
||||
if (single) {
|
||||
state->ExtReg[d] = imm;
|
||||
} else {
|
||||
/* Check endian please */
|
||||
state->ExtReg[d * 2 + 1] = imm;
|
||||
state->ExtReg[d * 2] = 0;
|
||||
}
|
||||
}
|
||||
void VMOVR(ARMul_State* state, u32 single, u32 d, u32 m) {
|
||||
if (single) {
|
||||
state->ExtReg[d] = state->ExtReg[m];
|
||||
} else {
|
||||
/* Check endian please */
|
||||
state->ExtReg[d * 2 + 1] = state->ExtReg[m * 2 + 1];
|
||||
state->ExtReg[d * 2] = state->ExtReg[m * 2];
|
||||
}
|
||||
}
|
||||
|
||||
/* Miscellaneous functions */
|
||||
s32 vfp_get_float(ARMul_State* state, unsigned int reg) {
|
||||
LOG_TRACE(Core_ARM11, "VFP get float: s{}=[{:08x}]", reg, state->ExtReg[reg]);
|
||||
return state->ExtReg[reg];
|
||||
}
|
||||
|
||||
void vfp_put_float(ARMul_State* state, s32 val, unsigned int reg) {
|
||||
LOG_TRACE(Core_ARM11, "VFP put float: s{} <= [{:08x}]", reg, val);
|
||||
state->ExtReg[reg] = val;
|
||||
}
|
||||
|
||||
u64 vfp_get_double(ARMul_State* state, unsigned int reg) {
|
||||
u64 result = ((u64)state->ExtReg[reg * 2 + 1]) << 32 | state->ExtReg[reg * 2];
|
||||
LOG_TRACE(Core_ARM11, "VFP get double: s[{}-{}]=[{:016x}]", reg * 2 + 1, reg * 2, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void vfp_put_double(ARMul_State* state, u64 val, unsigned int reg) {
|
||||
LOG_TRACE(Core_ARM11, "VFP put double: s[{}-{}] <= [{:08x}-{:08x}]", reg * 2 + 1, reg * 2,
|
||||
(u32)(val >> 32), (u32)(val & 0xffffffff));
|
||||
state->ExtReg[reg * 2] = (u32)(val & 0xffffffff);
|
||||
state->ExtReg[reg * 2 + 1] = (u32)(val >> 32);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process bitmask of exception conditions. (from vfpmodule.c)
|
||||
*/
|
||||
void vfp_raise_exceptions(ARMul_State* state, u32 exceptions, u32 inst, u32 fpscr) {
|
||||
LOG_TRACE(Core_ARM11, "VFP: raising exceptions {:08x}", exceptions);
|
||||
|
||||
if (exceptions == VFP_EXCEPTION_ERROR) {
|
||||
LOG_CRITICAL(Core_ARM11, "unhandled bounce {:x}", inst);
|
||||
Crash();
|
||||
}
|
||||
|
||||
/*
|
||||
* If any of the status flags are set, update the FPSCR.
|
||||
* Comparison instructions always return at least one of
|
||||
* these flags set.
|
||||
*/
|
||||
if (exceptions & (FPSCR_NFLAG | FPSCR_ZFLAG | FPSCR_CFLAG | FPSCR_VFLAG))
|
||||
fpscr &= ~(FPSCR_NFLAG | FPSCR_ZFLAG | FPSCR_CFLAG | FPSCR_VFLAG);
|
||||
|
||||
fpscr |= exceptions;
|
||||
|
||||
state->VFP[VFP_FPSCR] = fpscr;
|
||||
}
|
||||
43
src/core/arm/skyeye_common/vfp/vfp.h
Normal file
43
src/core/arm/skyeye_common/vfp/vfp.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
vfp/vfp.h - ARM VFPv3 emulation unit - vfp interface
|
||||
Copyright (C) 2003 Skyeye Develop Group
|
||||
for help please send mail to <skyeye-developer@lists.gro.clinux.org>
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/arm/skyeye_common/vfp/vfp_helper.h" /* for references to cdp SoftFloat functions */
|
||||
|
||||
#define VFP_DEBUG_UNTESTED(x) LOG_TRACE(Core_ARM11, "in func {}, " #x " untested", __FUNCTION__);
|
||||
#define CHECK_VFP_ENABLED
|
||||
#define CHECK_VFP_CDP_RET vfp_raise_exceptions(cpu, ret, inst_cream->instr, cpu->VFP[VFP_FPSCR]);
|
||||
|
||||
void VFPInit(ARMul_State* state);
|
||||
|
||||
s32 vfp_get_float(ARMul_State* state, u32 reg);
|
||||
void vfp_put_float(ARMul_State* state, s32 val, u32 reg);
|
||||
u64 vfp_get_double(ARMul_State* state, u32 reg);
|
||||
void vfp_put_double(ARMul_State* state, u64 val, u32 reg);
|
||||
void vfp_raise_exceptions(ARMul_State* state, u32 exceptions, u32 inst, u32 fpscr);
|
||||
u32 vfp_single_cpdo(ARMul_State* state, u32 inst, u32 fpscr);
|
||||
u32 vfp_double_cpdo(ARMul_State* state, u32 inst, u32 fpscr);
|
||||
|
||||
void VMOVBRS(ARMul_State* state, u32 to_arm, u32 t, u32 n, u32* value);
|
||||
void VMOVBRRD(ARMul_State* state, u32 to_arm, u32 t, u32 t2, u32 n, u32* value1, u32* value2);
|
||||
void VMOVBRRSS(ARMul_State* state, u32 to_arm, u32 t, u32 t2, u32 n, u32* value1, u32* value2);
|
||||
void VMOVI(ARMul_State* state, u32 single, u32 d, u32 imm);
|
||||
void VMOVR(ARMul_State* state, u32 single, u32 d, u32 imm);
|
||||
424
src/core/arm/skyeye_common/vfp/vfp_helper.h
Normal file
424
src/core/arm/skyeye_common/vfp/vfp_helper.h
Normal file
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
vfp/vfp.h - ARM VFPv3 emulation unit - SoftFloat lib helper
|
||||
Copyright (C) 2003 Skyeye Develop Group
|
||||
for help please send mail to <skyeye-developer@lists.gro.clinux.org>
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following code is derivative from Linux Android kernel vfp
|
||||
* floating point support.
|
||||
*
|
||||
* Copyright (C) 2004 ARM Limited.
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
#include "core/arm/skyeye_common/vfp/asm_vfp.h"
|
||||
|
||||
#define do_div(n, base) \
|
||||
{ n /= base; }
|
||||
|
||||
enum : u32 {
|
||||
FOP_MASK = 0x00b00040,
|
||||
FOP_FMAC = 0x00000000,
|
||||
FOP_FNMAC = 0x00000040,
|
||||
FOP_FMSC = 0x00100000,
|
||||
FOP_FNMSC = 0x00100040,
|
||||
FOP_FMUL = 0x00200000,
|
||||
FOP_FNMUL = 0x00200040,
|
||||
FOP_FADD = 0x00300000,
|
||||
FOP_FSUB = 0x00300040,
|
||||
FOP_FDIV = 0x00800000,
|
||||
FOP_EXT = 0x00b00040
|
||||
};
|
||||
|
||||
#define FOP_TO_IDX(inst) ((inst & 0x00b00000) >> 20 | (inst & (1 << 6)) >> 4)
|
||||
|
||||
enum : u32 {
|
||||
FEXT_MASK = 0x000f0080,
|
||||
FEXT_FCPY = 0x00000000,
|
||||
FEXT_FABS = 0x00000080,
|
||||
FEXT_FNEG = 0x00010000,
|
||||
FEXT_FSQRT = 0x00010080,
|
||||
FEXT_FCMP = 0x00040000,
|
||||
FEXT_FCMPE = 0x00040080,
|
||||
FEXT_FCMPZ = 0x00050000,
|
||||
FEXT_FCMPEZ = 0x00050080,
|
||||
FEXT_FCVT = 0x00070080,
|
||||
FEXT_FUITO = 0x00080000,
|
||||
FEXT_FSITO = 0x00080080,
|
||||
FEXT_FTOUI = 0x000c0000,
|
||||
FEXT_FTOUIZ = 0x000c0080,
|
||||
FEXT_FTOSI = 0x000d0000,
|
||||
FEXT_FTOSIZ = 0x000d0080
|
||||
};
|
||||
|
||||
#define FEXT_TO_IDX(inst) ((inst & 0x000f0000) >> 15 | (inst & (1 << 7)) >> 7)
|
||||
|
||||
#define vfp_get_sd(inst) ((inst & 0x0000f000) >> 11 | (inst & (1 << 22)) >> 22)
|
||||
#define vfp_get_dd(inst) ((inst & 0x0000f000) >> 12 | (inst & (1 << 22)) >> 18)
|
||||
#define vfp_get_sm(inst) ((inst & 0x0000000f) << 1 | (inst & (1 << 5)) >> 5)
|
||||
#define vfp_get_dm(inst) ((inst & 0x0000000f) | (inst & (1 << 5)) >> 1)
|
||||
#define vfp_get_sn(inst) ((inst & 0x000f0000) >> 15 | (inst & (1 << 7)) >> 7)
|
||||
#define vfp_get_dn(inst) ((inst & 0x000f0000) >> 16 | (inst & (1 << 7)) >> 3)
|
||||
|
||||
#define vfp_single(inst) (((inst)&0x0000f00) == 0xa00)
|
||||
|
||||
inline u32 vfp_shiftright32jamming(u32 val, unsigned int shift) {
|
||||
if (shift) {
|
||||
if (shift < 32)
|
||||
val = val >> shift | ((val << (32 - shift)) != 0);
|
||||
else
|
||||
val = val != 0;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 vfp_shiftright64jamming(u64 val, unsigned int shift) {
|
||||
if (shift) {
|
||||
if (shift < 64)
|
||||
val = val >> shift | ((val << (64 - shift)) != 0);
|
||||
else
|
||||
val = val != 0;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u32 vfp_hi64to32jamming(u64 val) {
|
||||
u32 v;
|
||||
u32 highval = val >> 32;
|
||||
u32 lowval = val & 0xffffffff;
|
||||
|
||||
if (lowval >= 1)
|
||||
v = highval | 1;
|
||||
else
|
||||
v = highval;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
inline void add128(u64* resh, u64* resl, u64 nh, u64 nl, u64 mh, u64 ml) {
|
||||
*resl = nl + ml;
|
||||
*resh = nh + mh;
|
||||
if (*resl < nl)
|
||||
*resh += 1;
|
||||
}
|
||||
|
||||
inline void sub128(u64* resh, u64* resl, u64 nh, u64 nl, u64 mh, u64 ml) {
|
||||
*resl = nl - ml;
|
||||
*resh = nh - mh;
|
||||
if (*resl > nl)
|
||||
*resh -= 1;
|
||||
}
|
||||
|
||||
inline void mul64to128(u64* resh, u64* resl, u64 n, u64 m) {
|
||||
u32 nh, nl, mh, ml;
|
||||
u64 rh, rma, rmb, rl;
|
||||
|
||||
nl = static_cast<u32>(n);
|
||||
ml = static_cast<u32>(m);
|
||||
rl = (u64)nl * ml;
|
||||
|
||||
nh = n >> 32;
|
||||
rma = (u64)nh * ml;
|
||||
|
||||
mh = m >> 32;
|
||||
rmb = (u64)nl * mh;
|
||||
rma += rmb;
|
||||
|
||||
rh = (u64)nh * mh;
|
||||
rh += ((u64)(rma < rmb) << 32) + (rma >> 32);
|
||||
|
||||
rma <<= 32;
|
||||
rl += rma;
|
||||
rh += (rl < rma);
|
||||
|
||||
*resl = rl;
|
||||
*resh = rh;
|
||||
}
|
||||
|
||||
inline void shift64left(u64* resh, u64* resl, u64 n) {
|
||||
*resh = n >> 63;
|
||||
*resl = n << 1;
|
||||
}
|
||||
|
||||
inline u64 vfp_hi64multiply64(u64 n, u64 m) {
|
||||
u64 rh, rl;
|
||||
mul64to128(&rh, &rl, n, m);
|
||||
return rh | (rl != 0);
|
||||
}
|
||||
|
||||
inline u64 vfp_estimate_div128to64(u64 nh, u64 nl, u64 m) {
|
||||
u64 mh, ml, remh, reml, termh, terml, z;
|
||||
|
||||
if (nh >= m)
|
||||
return ~0ULL;
|
||||
mh = m >> 32;
|
||||
if (mh << 32 <= nh) {
|
||||
z = 0xffffffff00000000ULL;
|
||||
} else {
|
||||
z = nh;
|
||||
do_div(z, mh);
|
||||
z <<= 32;
|
||||
}
|
||||
mul64to128(&termh, &terml, m, z);
|
||||
sub128(&remh, &reml, nh, nl, termh, terml);
|
||||
ml = m << 32;
|
||||
while ((s64)remh < 0) {
|
||||
z -= 0x100000000ULL;
|
||||
add128(&remh, &reml, remh, reml, mh, ml);
|
||||
}
|
||||
remh = (remh << 32) | (reml >> 32);
|
||||
if (mh << 32 <= remh) {
|
||||
z |= 0xffffffff;
|
||||
} else {
|
||||
do_div(remh, mh);
|
||||
z |= remh;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
// Operations on unpacked elements
|
||||
#define vfp_sign_negate(sign) (sign ^ 0x8000)
|
||||
|
||||
// Single-precision
|
||||
struct vfp_single {
|
||||
s16 exponent;
|
||||
u16 sign;
|
||||
u32 significand;
|
||||
};
|
||||
|
||||
// VFP_SINGLE_MANTISSA_BITS - number of bits in the mantissa
|
||||
// VFP_SINGLE_EXPONENT_BITS - number of bits in the exponent
|
||||
// VFP_SINGLE_LOW_BITS - number of low bits in the unpacked significand
|
||||
// which are not propagated to the float upon packing.
|
||||
#define VFP_SINGLE_MANTISSA_BITS (23)
|
||||
#define VFP_SINGLE_EXPONENT_BITS (8)
|
||||
#define VFP_SINGLE_LOW_BITS (32 - VFP_SINGLE_MANTISSA_BITS - 2)
|
||||
#define VFP_SINGLE_LOW_BITS_MASK ((1 << VFP_SINGLE_LOW_BITS) - 1)
|
||||
|
||||
// The bit in an unpacked float which indicates that it is a quiet NaN
|
||||
#define VFP_SINGLE_SIGNIFICAND_QNAN (1 << (VFP_SINGLE_MANTISSA_BITS - 1 + VFP_SINGLE_LOW_BITS))
|
||||
|
||||
// Operations on packed single-precision numbers
|
||||
#define vfp_single_packed_sign(v) ((v)&0x80000000)
|
||||
#define vfp_single_packed_negate(v) ((v) ^ 0x80000000)
|
||||
#define vfp_single_packed_abs(v) ((v) & ~0x80000000)
|
||||
#define vfp_single_packed_exponent(v) \
|
||||
(((v) >> VFP_SINGLE_MANTISSA_BITS) & ((1 << VFP_SINGLE_EXPONENT_BITS) - 1))
|
||||
#define vfp_single_packed_mantissa(v) ((v) & ((1 << VFP_SINGLE_MANTISSA_BITS) - 1))
|
||||
|
||||
enum : u32 {
|
||||
VFP_NUMBER = (1 << 0),
|
||||
VFP_ZERO = (1 << 1),
|
||||
VFP_DENORMAL = (1 << 2),
|
||||
VFP_INFINITY = (1 << 3),
|
||||
VFP_NAN = (1 << 4),
|
||||
VFP_NAN_SIGNAL = (1 << 5),
|
||||
|
||||
VFP_QNAN = (VFP_NAN),
|
||||
VFP_SNAN = (VFP_NAN | VFP_NAN_SIGNAL)
|
||||
};
|
||||
|
||||
inline int vfp_single_type(const vfp_single* s) {
|
||||
int type = VFP_NUMBER;
|
||||
if (s->exponent == 255) {
|
||||
if (s->significand == 0)
|
||||
type = VFP_INFINITY;
|
||||
else if (s->significand & VFP_SINGLE_SIGNIFICAND_QNAN)
|
||||
type = VFP_QNAN;
|
||||
else
|
||||
type = VFP_SNAN;
|
||||
} else if (s->exponent == 0) {
|
||||
if (s->significand == 0)
|
||||
type |= VFP_ZERO;
|
||||
else
|
||||
type |= VFP_DENORMAL;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// Unpack a single-precision float. Note that this returns the magnitude
|
||||
// of the single-precision float mantissa with the 1. if necessary,
|
||||
// aligned to bit 30.
|
||||
inline u32 vfp_single_unpack(vfp_single* s, s32 val, u32 fpscr) {
|
||||
u32 exceptions = 0;
|
||||
s->sign = vfp_single_packed_sign(val) >> 16;
|
||||
s->exponent = vfp_single_packed_exponent(val);
|
||||
|
||||
u32 significand = ((u32)val << (32 - VFP_SINGLE_MANTISSA_BITS)) >> 2;
|
||||
if (s->exponent && s->exponent != 255)
|
||||
significand |= 0x40000000;
|
||||
s->significand = significand;
|
||||
|
||||
// If flush-to-zero mode is enabled, turn the denormal into zero.
|
||||
// On a VFPv2 architecture, the sign of the zero is always positive.
|
||||
if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) {
|
||||
s->sign = 0;
|
||||
s->exponent = 0;
|
||||
s->significand = 0;
|
||||
exceptions |= FPSCR_IDC;
|
||||
}
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
// Re-pack a single-precision float. This assumes that the float is
|
||||
// already normalised such that the MSB is bit 30, _not_ bit 31.
|
||||
inline s32 vfp_single_pack(const vfp_single* s) {
|
||||
u32 val = (s->sign << 16) + (s->exponent << VFP_SINGLE_MANTISSA_BITS) +
|
||||
(s->significand >> VFP_SINGLE_LOW_BITS);
|
||||
return (s32)val;
|
||||
}
|
||||
|
||||
u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, u32 exceptions,
|
||||
const char* func);
|
||||
|
||||
// Double-precision
|
||||
struct vfp_double {
|
||||
s16 exponent;
|
||||
u16 sign;
|
||||
u64 significand;
|
||||
};
|
||||
|
||||
#define VFP_DOUBLE_MANTISSA_BITS (52)
|
||||
#define VFP_DOUBLE_EXPONENT_BITS (11)
|
||||
#define VFP_DOUBLE_LOW_BITS (64 - VFP_DOUBLE_MANTISSA_BITS - 2)
|
||||
#define VFP_DOUBLE_LOW_BITS_MASK ((1 << VFP_DOUBLE_LOW_BITS) - 1)
|
||||
|
||||
// The bit in an unpacked double which indicates that it is a quiet NaN
|
||||
#define VFP_DOUBLE_SIGNIFICAND_QNAN (1ULL << (VFP_DOUBLE_MANTISSA_BITS - 1 + VFP_DOUBLE_LOW_BITS))
|
||||
|
||||
// Operations on packed single-precision numbers
|
||||
#define vfp_double_packed_sign(v) ((v) & (1ULL << 63))
|
||||
#define vfp_double_packed_negate(v) ((v) ^ (1ULL << 63))
|
||||
#define vfp_double_packed_abs(v) ((v) & ~(1ULL << 63))
|
||||
#define vfp_double_packed_exponent(v) \
|
||||
(((v) >> VFP_DOUBLE_MANTISSA_BITS) & ((1 << VFP_DOUBLE_EXPONENT_BITS) - 1))
|
||||
#define vfp_double_packed_mantissa(v) ((v) & ((1ULL << VFP_DOUBLE_MANTISSA_BITS) - 1))
|
||||
|
||||
inline int vfp_double_type(const vfp_double* s) {
|
||||
int type = VFP_NUMBER;
|
||||
if (s->exponent == 2047) {
|
||||
if (s->significand == 0)
|
||||
type = VFP_INFINITY;
|
||||
else if (s->significand & VFP_DOUBLE_SIGNIFICAND_QNAN)
|
||||
type = VFP_QNAN;
|
||||
else
|
||||
type = VFP_SNAN;
|
||||
} else if (s->exponent == 0) {
|
||||
if (s->significand == 0)
|
||||
type |= VFP_ZERO;
|
||||
else
|
||||
type |= VFP_DENORMAL;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// Unpack a double-precision float. Note that this returns the magnitude
|
||||
// of the double-precision float mantissa with the 1. if necessary,
|
||||
// aligned to bit 62.
|
||||
inline u32 vfp_double_unpack(vfp_double* s, s64 val, u32 fpscr) {
|
||||
u32 exceptions = 0;
|
||||
s->sign = vfp_double_packed_sign(val) >> 48;
|
||||
s->exponent = vfp_double_packed_exponent(val);
|
||||
|
||||
u64 significand = ((u64)val << (64 - VFP_DOUBLE_MANTISSA_BITS)) >> 2;
|
||||
if (s->exponent && s->exponent != 2047)
|
||||
significand |= (1ULL << 62);
|
||||
s->significand = significand;
|
||||
|
||||
// If flush-to-zero mode is enabled, turn the denormal into zero.
|
||||
// On a VFPv2 architecture, the sign of the zero is always positive.
|
||||
if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) {
|
||||
s->sign = 0;
|
||||
s->exponent = 0;
|
||||
s->significand = 0;
|
||||
exceptions |= FPSCR_IDC;
|
||||
}
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
// Re-pack a double-precision float. This assumes that the float is
|
||||
// already normalised such that the MSB is bit 30, _not_ bit 31.
|
||||
inline s64 vfp_double_pack(const vfp_double* s) {
|
||||
u64 val = ((u64)s->sign << 48) + ((u64)s->exponent << VFP_DOUBLE_MANTISSA_BITS) +
|
||||
(s->significand >> VFP_DOUBLE_LOW_BITS);
|
||||
return (s64)val;
|
||||
}
|
||||
|
||||
u32 vfp_estimate_sqrt_significand(u32 exponent, u32 significand);
|
||||
|
||||
// A special flag to tell the normalisation code not to normalise.
|
||||
#define VFP_NAN_FLAG 0x100
|
||||
|
||||
// A bit pattern used to indicate the initial (unset) value of the
|
||||
// exception mask, in case nothing handles an instruction. This
|
||||
// doesn't include the NAN flag, which get masked out before
|
||||
// we check for an error.
|
||||
#define VFP_EXCEPTION_ERROR ((u32)-1 & ~VFP_NAN_FLAG)
|
||||
|
||||
// A flag to tell vfp instruction type.
|
||||
// OP_SCALAR - This operation always operates in scalar mode
|
||||
// OP_SD - The instruction exceptionally writes to a single precision result.
|
||||
// OP_DD - The instruction exceptionally writes to a double precision result.
|
||||
// OP_SM - The instruction exceptionally reads from a single precision operand.
|
||||
enum : u32 { OP_SCALAR = (1 << 0), OP_SD = (1 << 1), OP_DD = (1 << 1), OP_SM = (1 << 2) };
|
||||
|
||||
struct op {
|
||||
u32 (*const fn)(ARMul_State* state, int dd, int dn, int dm, u32 fpscr);
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
inline u32 fls(u32 x) {
|
||||
int r = 32;
|
||||
|
||||
if (!x)
|
||||
return 0;
|
||||
if (!(x & 0xffff0000u)) {
|
||||
x <<= 16;
|
||||
r -= 16;
|
||||
}
|
||||
if (!(x & 0xff000000u)) {
|
||||
x <<= 8;
|
||||
r -= 8;
|
||||
}
|
||||
if (!(x & 0xf0000000u)) {
|
||||
x <<= 4;
|
||||
r -= 4;
|
||||
}
|
||||
if (!(x & 0xc0000000u)) {
|
||||
x <<= 2;
|
||||
r -= 2;
|
||||
}
|
||||
if (!(x & 0x80000000u)) {
|
||||
x <<= 1;
|
||||
r -= 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
u32 vfp_double_multiply(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr);
|
||||
u32 vfp_double_add(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr);
|
||||
u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, u32 exceptions,
|
||||
const char* func);
|
||||
1248
src/core/arm/skyeye_common/vfp/vfpdouble.cpp
Normal file
1248
src/core/arm/skyeye_common/vfp/vfpdouble.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1703
src/core/arm/skyeye_common/vfp/vfpinstr.cpp
Normal file
1703
src/core/arm/skyeye_common/vfp/vfpinstr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1273
src/core/arm/skyeye_common/vfp/vfpsingle.cpp
Normal file
1273
src/core/arm/skyeye_common/vfp/vfpsingle.cpp
Normal file
File diff suppressed because it is too large
Load Diff
9
src/core/cheats/cheat_base.cpp
Normal file
9
src/core/cheats/cheat_base.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/cheats/cheat_base.h"
|
||||
|
||||
namespace Cheats {
|
||||
CheatBase::~CheatBase() = default;
|
||||
} // namespace Cheats
|
||||
29
src/core/cheats/cheat_base.h
Normal file
29
src/core/cheats/cheat_base.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Cheats {
|
||||
class CheatBase {
|
||||
public:
|
||||
virtual ~CheatBase();
|
||||
virtual void Execute(Core::System& system) const = 0;
|
||||
|
||||
virtual bool IsEnabled() const = 0;
|
||||
virtual void SetEnabled(bool enabled) = 0;
|
||||
|
||||
virtual std::string GetComments() const = 0;
|
||||
virtual std::string GetName() const = 0;
|
||||
virtual std::string GetType() const = 0;
|
||||
virtual std::string GetCode() const = 0;
|
||||
|
||||
virtual std::string ToString() const = 0;
|
||||
};
|
||||
} // namespace Cheats
|
||||
117
src/core/cheats/cheats.cpp
Normal file
117
src/core/cheats/cheats.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <fmt/format.h>
|
||||
#include "common/file_util.h"
|
||||
#include "core/cheats/cheats.h"
|
||||
#include "core/cheats/gateway_cheat.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Cheats {
|
||||
|
||||
// Luma3DS uses this interval for applying cheats, so to keep consistent behavior
|
||||
// we use the same value
|
||||
constexpr u64 run_interval_ticks = 50'000'000;
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system_) : system{system_} {}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
if (system.IsPoweredOn()) {
|
||||
system.CoreTiming().UnscheduleEvent(event, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatEngine::Connect() {
|
||||
event = system.CoreTiming().RegisterEvent(
|
||||
"CheatCore::run_event",
|
||||
[this](u64 thread_id, s64 cycle_late) { RunCallback(thread_id, cycle_late); });
|
||||
system.CoreTiming().ScheduleEvent(run_interval_ticks, event);
|
||||
}
|
||||
|
||||
std::span<const std::shared_ptr<CheatBase>> CheatEngine::GetCheats() const {
|
||||
std::shared_lock lock{cheats_list_mutex};
|
||||
return cheats_list;
|
||||
}
|
||||
|
||||
void CheatEngine::AddCheat(std::shared_ptr<CheatBase>&& cheat) {
|
||||
std::unique_lock lock{cheats_list_mutex};
|
||||
cheats_list.push_back(std::move(cheat));
|
||||
}
|
||||
|
||||
void CheatEngine::RemoveCheat(std::size_t index) {
|
||||
std::unique_lock lock{cheats_list_mutex};
|
||||
if (index < 0 || index >= cheats_list.size()) {
|
||||
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
|
||||
return;
|
||||
}
|
||||
cheats_list.erase(cheats_list.begin() + index);
|
||||
}
|
||||
|
||||
void CheatEngine::UpdateCheat(std::size_t index, std::shared_ptr<CheatBase>&& new_cheat) {
|
||||
std::unique_lock lock{cheats_list_mutex};
|
||||
if (index < 0 || index >= cheats_list.size()) {
|
||||
LOG_ERROR(Core_Cheats, "Invalid index {}", index);
|
||||
return;
|
||||
}
|
||||
cheats_list[index] = std::move(new_cheat);
|
||||
}
|
||||
|
||||
void CheatEngine::SaveCheatFile(u64 title_id) const {
|
||||
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
|
||||
const std::string filepath = fmt::format("{}{:016X}.txt", cheat_dir, title_id);
|
||||
|
||||
if (!FileUtil::IsDirectory(cheat_dir)) {
|
||||
FileUtil::CreateDir(cheat_dir);
|
||||
}
|
||||
FileUtil::IOFile file(filepath, "w");
|
||||
|
||||
auto cheats = GetCheats();
|
||||
for (const auto& cheat : cheats) {
|
||||
file.WriteString(cheat->ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void CheatEngine::LoadCheatFile(u64 title_id) {
|
||||
{
|
||||
std::unique_lock lock{cheats_list_mutex};
|
||||
if (loaded_title_id.has_value() && loaded_title_id == title_id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir);
|
||||
const std::string filepath = fmt::format("{}{:016X}.txt", cheat_dir, title_id);
|
||||
|
||||
if (!FileUtil::IsDirectory(cheat_dir)) {
|
||||
FileUtil::CreateDir(cheat_dir);
|
||||
}
|
||||
|
||||
if (!FileUtil::Exists(filepath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto gateway_cheats = GatewayCheat::LoadFile(filepath);
|
||||
{
|
||||
std::unique_lock lock{cheats_list_mutex};
|
||||
loaded_title_id = title_id;
|
||||
cheats_list = std::move(gateway_cheats);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatEngine::RunCallback([[maybe_unused]] std::uintptr_t user_data, s64 cycles_late) {
|
||||
{
|
||||
std::shared_lock lock{cheats_list_mutex};
|
||||
for (const auto& cheat : cheats_list) {
|
||||
if (cheat->IsEnabled()) {
|
||||
cheat->Execute(system);
|
||||
}
|
||||
}
|
||||
}
|
||||
system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace Cheats
|
||||
64
src/core/cheats/cheats.h
Normal file
64
src/core/cheats/cheats.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
struct TimingEventType;
|
||||
} // namespace Core
|
||||
|
||||
namespace CoreTiming {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace Cheats {
|
||||
|
||||
class CheatBase;
|
||||
|
||||
class CheatEngine {
|
||||
public:
|
||||
explicit CheatEngine(Core::System& system);
|
||||
~CheatEngine();
|
||||
|
||||
/// Registers the cheat execution callback.
|
||||
void Connect();
|
||||
|
||||
/// Returns a span of the currently active cheats.
|
||||
std::span<const std::shared_ptr<CheatBase>> GetCheats() const;
|
||||
|
||||
/// Adds a cheat to the cheat engine.
|
||||
void AddCheat(std::shared_ptr<CheatBase>&& cheat);
|
||||
|
||||
/// Removes a cheat at the specified index in the cheats list.
|
||||
void RemoveCheat(std::size_t index);
|
||||
|
||||
/// Updates a cheat at the specified index in the cheats list.
|
||||
void UpdateCheat(std::size_t index, std::shared_ptr<CheatBase>&& new_cheat);
|
||||
|
||||
/// Loads the cheat file from disk for the specified title id.
|
||||
void LoadCheatFile(u64 title_id);
|
||||
|
||||
/// Saves currently active cheats to file for the specified title id.
|
||||
void SaveCheatFile(u64 title_id) const;
|
||||
|
||||
private:
|
||||
/// The cheat execution callback.
|
||||
void RunCallback(std::uintptr_t user_data, s64 cycles_late);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
Core::TimingEventType* event;
|
||||
std::optional<u64> loaded_title_id;
|
||||
std::vector<std::shared_ptr<CheatBase>> cheats_list;
|
||||
mutable std::shared_mutex cheats_list_mutex;
|
||||
};
|
||||
} // namespace Cheats
|
||||
519
src/core/cheats/gateway_cheat.cpp
Normal file
519
src/core/cheats/gateway_cheat.cpp
Normal file
@@ -0,0 +1,519 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/iostreams/device/file_descriptor.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/cheats/gateway_cheat.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Cheats {
|
||||
|
||||
struct State {
|
||||
u32 reg = 0;
|
||||
u32 offset = 0;
|
||||
u32 if_flag = 0;
|
||||
u32 loop_count = 0;
|
||||
std::size_t loop_back_line = 0;
|
||||
std::size_t current_line_nr = 0;
|
||||
bool loop_flag = false;
|
||||
};
|
||||
|
||||
template <typename T, typename ReadFunction, typename WriteFunction>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> WriteOp(const GatewayCheat::CheatLine& line,
|
||||
const State& state,
|
||||
ReadFunction read_func,
|
||||
WriteFunction write_func,
|
||||
Core::System& system) {
|
||||
u32 addr = line.address + state.offset;
|
||||
T val = read_func(addr);
|
||||
if (val != static_cast<T>(line.value)) {
|
||||
write_func(addr, static_cast<T>(line.value));
|
||||
system.InvalidateCacheRange(addr, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename ReadFunction, typename CompareFunc>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> CompOp(const GatewayCheat::CheatLine& line,
|
||||
State& state, ReadFunction read_func,
|
||||
CompareFunc comp) {
|
||||
u32 addr = line.address + state.offset;
|
||||
T val = read_func(addr);
|
||||
if (!comp(val)) {
|
||||
state.if_flag++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void LoadOffsetOp(Memory::MemorySystem& memory, const GatewayCheat::CheatLine& line,
|
||||
State& state) {
|
||||
u32 addr = line.address + state.offset;
|
||||
state.offset = memory.Read32(addr);
|
||||
}
|
||||
|
||||
static inline void LoopOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.loop_flag = state.loop_count < line.value;
|
||||
state.loop_count++;
|
||||
state.loop_back_line = state.current_line_nr;
|
||||
}
|
||||
|
||||
static inline void TerminateOp(State& state) {
|
||||
if (state.if_flag > 0) {
|
||||
state.if_flag--;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void LoopExecuteVariantOp(State& state) {
|
||||
if (state.loop_flag) {
|
||||
state.current_line_nr = state.loop_back_line - 1;
|
||||
} else {
|
||||
state.loop_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void FullTerminateOp(State& state) {
|
||||
if (state.loop_flag) {
|
||||
state.current_line_nr = state.loop_back_line - 1;
|
||||
} else {
|
||||
state.offset = 0;
|
||||
state.reg = 0;
|
||||
state.loop_count = 0;
|
||||
state.if_flag = 0;
|
||||
state.loop_flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void SetOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.offset = line.value;
|
||||
}
|
||||
|
||||
static inline void AddValueOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.reg += line.value;
|
||||
}
|
||||
|
||||
static inline void SetValueOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.reg = line.value;
|
||||
}
|
||||
|
||||
template <typename T, typename ReadFunction, typename WriteFunction>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> IncrementiveWriteOp(
|
||||
const GatewayCheat::CheatLine& line, State& state, ReadFunction read_func,
|
||||
WriteFunction write_func, Core::System& system) {
|
||||
u32 addr = line.value + state.offset;
|
||||
T val = read_func(addr);
|
||||
if (val != static_cast<T>(state.reg)) {
|
||||
write_func(addr, static_cast<T>(state.reg));
|
||||
system.InvalidateCacheRange(addr, sizeof(T));
|
||||
}
|
||||
state.offset += sizeof(T);
|
||||
}
|
||||
|
||||
template <typename T, typename ReadFunction>
|
||||
static inline std::enable_if_t<std::is_integral_v<T>> LoadOp(const GatewayCheat::CheatLine& line,
|
||||
State& state, ReadFunction read_func) {
|
||||
|
||||
u32 addr = line.value + state.offset;
|
||||
state.reg = read_func(addr);
|
||||
}
|
||||
|
||||
static inline void AddOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
|
||||
state.offset += line.value;
|
||||
}
|
||||
|
||||
static inline void JokerOp(const GatewayCheat::CheatLine& line, State& state,
|
||||
const Core::System& system) {
|
||||
u32 pad_state = system.ServiceManager()
|
||||
.GetService<Service::HID::Module::Interface>("hid:USER")
|
||||
->GetModule()
|
||||
->GetState()
|
||||
.hex;
|
||||
bool pressed = (pad_state & line.value) == line.value;
|
||||
if (!pressed) {
|
||||
state.if_flag++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Core::System& system,
|
||||
std::span<const GatewayCheat::CheatLine> cheat_lines) {
|
||||
if (state.if_flag > 0) {
|
||||
// Skip over the additional patch lines
|
||||
state.current_line_nr += static_cast<int>(std::ceil(line.value / 8.0));
|
||||
return;
|
||||
}
|
||||
u32 num_bytes = line.value;
|
||||
u32 addr = line.address + state.offset;
|
||||
system.InvalidateCacheRange(addr, num_bytes);
|
||||
|
||||
bool first = true;
|
||||
u32 bit_offset = 0;
|
||||
if (num_bytes > 0)
|
||||
state.current_line_nr++; // skip over the current code
|
||||
while (num_bytes >= 4) {
|
||||
u32 tmp = first ? cheat_lines[state.current_line_nr].first
|
||||
: cheat_lines[state.current_line_nr].value;
|
||||
if (!first && num_bytes > 4) {
|
||||
state.current_line_nr++;
|
||||
}
|
||||
first = !first;
|
||||
system.Memory().Write32(addr, tmp);
|
||||
addr += 4;
|
||||
num_bytes -= 4;
|
||||
}
|
||||
while (num_bytes > 0) {
|
||||
u32 tmp = (first ? cheat_lines[state.current_line_nr].first
|
||||
: cheat_lines[state.current_line_nr].value) >>
|
||||
bit_offset;
|
||||
system.Memory().Write8(addr, tmp);
|
||||
addr += 1;
|
||||
num_bytes -= 1;
|
||||
bit_offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCheat::CheatLine::CheatLine(const std::string& line) {
|
||||
constexpr std::size_t cheat_length = 17;
|
||||
if (line.length() != cheat_length) {
|
||||
type = CheatType::Null;
|
||||
cheat_line = line;
|
||||
LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line);
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
std::string type_temp = line.substr(0, 1);
|
||||
// 0xD types have extra subtype value, i.e. 0xDA
|
||||
std::string sub_type_temp;
|
||||
if (type_temp == "D" || type_temp == "d")
|
||||
sub_type_temp = line.substr(1, 1);
|
||||
type = static_cast<CheatType>(std::stoi(type_temp + sub_type_temp, 0, 16));
|
||||
first = static_cast<u32>(std::stoul(line.substr(0, 8), 0, 16));
|
||||
address = first & 0x0FFFFFFF;
|
||||
value = static_cast<u32>(std::stoul(line.substr(9, 8), 0, 16));
|
||||
cheat_line = line;
|
||||
} catch (const std::logic_error&) {
|
||||
type = CheatType::Null;
|
||||
cheat_line = line;
|
||||
LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCheat::GatewayCheat(std::string name_, std::vector<CheatLine> cheat_lines_,
|
||||
std::string comments_)
|
||||
: name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) {
|
||||
}
|
||||
|
||||
GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comments_)
|
||||
: name(std::move(name_)), comments(std::move(comments_)) {
|
||||
|
||||
const auto code_lines = Common::SplitString(code, '\n');
|
||||
|
||||
std::vector<CheatLine> temp_cheat_lines;
|
||||
for (const std::string& line : code_lines) {
|
||||
if (!line.empty())
|
||||
temp_cheat_lines.emplace_back(line);
|
||||
}
|
||||
cheat_lines = std::move(temp_cheat_lines);
|
||||
}
|
||||
|
||||
GatewayCheat::~GatewayCheat() = default;
|
||||
|
||||
void GatewayCheat::Execute(Core::System& system) const {
|
||||
State state;
|
||||
|
||||
Memory::MemorySystem& memory = system.Memory();
|
||||
auto Read8 = [&memory](VAddr addr) { return memory.Read8(addr); };
|
||||
auto Read16 = [&memory](VAddr addr) { return memory.Read16(addr); };
|
||||
auto Read32 = [&memory](VAddr addr) { return memory.Read32(addr); };
|
||||
auto Write8 = [&memory](VAddr addr, u8 value) { memory.Write8(addr, value); };
|
||||
auto Write16 = [&memory](VAddr addr, u16 value) { memory.Write16(addr, value); };
|
||||
auto Write32 = [&memory](VAddr addr, u32 value) { memory.Write32(addr, value); };
|
||||
|
||||
for (state.current_line_nr = 0; state.current_line_nr < cheat_lines.size();
|
||||
state.current_line_nr++) {
|
||||
auto line = cheat_lines[state.current_line_nr];
|
||||
if (state.if_flag > 0) {
|
||||
switch (line.type) {
|
||||
case CheatType::GreaterThan32:
|
||||
case CheatType::LessThan32:
|
||||
case CheatType::EqualTo32:
|
||||
case CheatType::NotEqualTo32:
|
||||
case CheatType::GreaterThan16WithMask:
|
||||
case CheatType::LessThan16WithMask:
|
||||
case CheatType::EqualTo16WithMask:
|
||||
case CheatType::NotEqualTo16WithMask:
|
||||
case CheatType::Joker:
|
||||
// Increment the if_flag to handle the end if correctly
|
||||
state.if_flag++;
|
||||
break;
|
||||
case CheatType::Patch:
|
||||
// EXXXXXXX YYYYYYYY
|
||||
// Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
|
||||
// We need to call this here to skip the additional patch lines
|
||||
PatchOp(line, state, system, cheat_lines);
|
||||
break;
|
||||
case CheatType::Terminator:
|
||||
// D0000000 00000000 - ENDIF
|
||||
TerminateOp(state);
|
||||
break;
|
||||
case CheatType::FullTerminator:
|
||||
// D2000000 00000000 - END; offset = 0; reg = 0;
|
||||
FullTerminateOp(state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Do not execute any other op code
|
||||
continue;
|
||||
}
|
||||
switch (line.type) {
|
||||
case CheatType::Null:
|
||||
break;
|
||||
case CheatType::Write32:
|
||||
// 0XXXXXXX YYYYYYYY - word[XXXXXXX+offset] = YYYYYYYY
|
||||
WriteOp<u32>(line, state, Read32, Write32, system);
|
||||
break;
|
||||
case CheatType::Write16:
|
||||
// 1XXXXXXX 0000YYYY - half[XXXXXXX+offset] = YYYY
|
||||
WriteOp<u16>(line, state, Read16, Write16, system);
|
||||
break;
|
||||
case CheatType::Write8:
|
||||
// 2XXXXXXX 000000YY - byte[XXXXXXX+offset] = YY
|
||||
WriteOp<u8>(line, state, Read8, Write8, system);
|
||||
break;
|
||||
case CheatType::GreaterThan32:
|
||||
// 3XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY > word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, Read32, [&line](u32 val) -> bool { return line.value > val; });
|
||||
break;
|
||||
case CheatType::LessThan32:
|
||||
// 4XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY < word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, Read32, [&line](u32 val) -> bool { return line.value < val; });
|
||||
break;
|
||||
case CheatType::EqualTo32:
|
||||
// 5XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY == word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, Read32,
|
||||
[&line](u32 val) -> bool { return line.value == val; });
|
||||
break;
|
||||
case CheatType::NotEqualTo32:
|
||||
// 6XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY != word[XXXXXXX] ;unsigned
|
||||
CompOp<u32>(line, state, Read32,
|
||||
[&line](u32 val) -> bool { return line.value != val; });
|
||||
break;
|
||||
case CheatType::GreaterThan16WithMask:
|
||||
// 7XXXXXXX ZZZZYYYY - Execute next block IF YYYY > ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) > (static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::LessThan16WithMask:
|
||||
// 8XXXXXXX ZZZZYYYY - Execute next block IF YYYY < ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) < (static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::EqualTo16WithMask:
|
||||
// 9XXXXXXX ZZZZYYYY - Execute next block IF YYYY = ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) == (static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::NotEqualTo16WithMask:
|
||||
// AXXXXXXX ZZZZYYYY - Execute next block IF YYYY <> ((not ZZZZ) AND half[XXXXXXX])
|
||||
CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
|
||||
return static_cast<u16>(line.value) != (static_cast<u16>(~line.value >> 16) & val);
|
||||
});
|
||||
break;
|
||||
case CheatType::LoadOffset:
|
||||
// BXXXXXXX 00000000 - offset = word[XXXXXXX+offset]
|
||||
LoadOffsetOp(system.Memory(), line, state);
|
||||
break;
|
||||
case CheatType::Loop: {
|
||||
// C0000000 YYYYYYYY - LOOP next block YYYYYYYY times
|
||||
// TODO(B3N30): Support nested loops if necessary
|
||||
LoopOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::Terminator: {
|
||||
// D0000000 00000000 - END IF
|
||||
TerminateOp(state);
|
||||
break;
|
||||
}
|
||||
case CheatType::LoopExecuteVariant: {
|
||||
// D1000000 00000000 - END LOOP
|
||||
LoopExecuteVariantOp(state);
|
||||
break;
|
||||
}
|
||||
case CheatType::FullTerminator: {
|
||||
// D2000000 00000000 - NEXT & Flush
|
||||
FullTerminateOp(state);
|
||||
break;
|
||||
}
|
||||
case CheatType::SetOffset: {
|
||||
// D3000000 XXXXXXXX – Sets the offset to XXXXXXXX
|
||||
SetOffsetOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::AddValue: {
|
||||
// D4000000 XXXXXXXX – reg += XXXXXXXX
|
||||
AddValueOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::SetValue: {
|
||||
// D5000000 XXXXXXXX – reg = XXXXXXXX
|
||||
SetValueOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::IncrementiveWrite32: {
|
||||
// D6000000 XXXXXXXX – (32bit) [XXXXXXXX+offset] = reg ; offset += 4
|
||||
IncrementiveWriteOp<u32>(line, state, Read32, Write32, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::IncrementiveWrite16: {
|
||||
// D7000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xffff ; offset += 2
|
||||
IncrementiveWriteOp<u16>(line, state, Read16, Write16, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::IncrementiveWrite8: {
|
||||
// D8000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xff ; offset++
|
||||
IncrementiveWriteOp<u8>(line, state, Read8, Write8, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::Load32: {
|
||||
// D9000000 XXXXXXXX – reg = [XXXXXXXX+offset]
|
||||
LoadOp<u32>(line, state, Read32);
|
||||
break;
|
||||
}
|
||||
case CheatType::Load16: {
|
||||
// DA000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFFFF
|
||||
LoadOp<u16>(line, state, Read16);
|
||||
break;
|
||||
}
|
||||
case CheatType::Load8: {
|
||||
// DB000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFF
|
||||
LoadOp<u8>(line, state, Read8);
|
||||
break;
|
||||
}
|
||||
case CheatType::AddOffset: {
|
||||
// DC000000 XXXXXXXX – offset + XXXXXXXX
|
||||
AddOffsetOp(line, state);
|
||||
break;
|
||||
}
|
||||
case CheatType::Joker: {
|
||||
// DD000000 XXXXXXXX – if KEYPAD has value XXXXXXXX execute next block
|
||||
JokerOp(line, state, system);
|
||||
break;
|
||||
}
|
||||
case CheatType::Patch: {
|
||||
// EXXXXXXX YYYYYYYY
|
||||
// Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
|
||||
PatchOp(line, state, system, cheat_lines);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayCheat::IsEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void GatewayCheat::SetEnabled(bool enabled_) {
|
||||
enabled = enabled_;
|
||||
if (enabled) {
|
||||
LOG_WARNING(Core_Cheats, "Cheats enabled. This might lead to weird behaviour or crashes");
|
||||
}
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetComments() const {
|
||||
return comments;
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetType() const {
|
||||
return "Gateway";
|
||||
}
|
||||
|
||||
std::string GatewayCheat::GetCode() const {
|
||||
std::string result;
|
||||
for (const auto& line : cheat_lines)
|
||||
result += line.cheat_line + '\n';
|
||||
return result;
|
||||
}
|
||||
|
||||
/// A special marker used to keep track of enabled cheats
|
||||
static constexpr char EnabledText[] = "*citra_enabled";
|
||||
|
||||
std::string GatewayCheat::ToString() const {
|
||||
std::string result;
|
||||
result += '[' + name + "]\n";
|
||||
if (enabled) {
|
||||
result += EnabledText;
|
||||
result += '\n';
|
||||
}
|
||||
const auto comment_lines = Common::SplitString(comments, '\n');
|
||||
for (const auto& comment_line : comment_lines) {
|
||||
result += "*" + comment_line + '\n';
|
||||
}
|
||||
result += GetCode() + '\n';
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string& filepath) {
|
||||
std::vector<std::shared_ptr<CheatBase>> cheats;
|
||||
|
||||
boost::iostreams::stream<boost::iostreams::file_descriptor_source> file;
|
||||
FileUtil::OpenFStream<std::ios_base::in>(file, filepath);
|
||||
if (!file.is_open()) {
|
||||
return cheats;
|
||||
}
|
||||
|
||||
std::string comments;
|
||||
std::vector<CheatLine> cheat_lines;
|
||||
std::string name;
|
||||
bool enabled = false;
|
||||
|
||||
while (!file.eof()) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
|
||||
line = Common::StripSpaces(line); // remove spaces at front and end
|
||||
if (line.length() >= 2 && line.front() == '[') {
|
||||
if (!cheat_lines.empty()) {
|
||||
cheats.push_back(std::make_shared<GatewayCheat>(name, cheat_lines, comments));
|
||||
cheats.back()->SetEnabled(enabled);
|
||||
enabled = false;
|
||||
}
|
||||
name = line.substr(1, line.length() - 2);
|
||||
cheat_lines.clear();
|
||||
comments.erase();
|
||||
} else if (!line.empty() && line.front() == '*') {
|
||||
if (line == EnabledText) {
|
||||
enabled = true;
|
||||
} else {
|
||||
comments += line.substr(1, line.length() - 1) + '\n';
|
||||
}
|
||||
} else if (!line.empty()) {
|
||||
cheat_lines.emplace_back(std::move(line));
|
||||
}
|
||||
}
|
||||
if (!cheat_lines.empty()) {
|
||||
cheats.push_back(std::make_shared<GatewayCheat>(name, cheat_lines, comments));
|
||||
cheats.back()->SetEnabled(enabled);
|
||||
}
|
||||
return cheats;
|
||||
}
|
||||
} // namespace Cheats
|
||||
88
src/core/cheats/gateway_cheat.h
Normal file
88
src/core/cheats/gateway_cheat.h
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/cheats/cheat_base.h"
|
||||
|
||||
namespace Cheats {
|
||||
class GatewayCheat final : public CheatBase {
|
||||
public:
|
||||
enum class CheatType {
|
||||
Null = -0x1,
|
||||
Write32 = 0x00,
|
||||
Write16 = 0x01,
|
||||
Write8 = 0x02,
|
||||
GreaterThan32 = 0x03,
|
||||
LessThan32 = 0x04,
|
||||
EqualTo32 = 0x05,
|
||||
NotEqualTo32 = 0x06,
|
||||
GreaterThan16WithMask = 0x07,
|
||||
LessThan16WithMask = 0x08,
|
||||
EqualTo16WithMask = 0x09,
|
||||
NotEqualTo16WithMask = 0x0A,
|
||||
LoadOffset = 0x0B,
|
||||
Loop = 0x0C,
|
||||
Terminator = 0xD0,
|
||||
LoopExecuteVariant = 0xD1,
|
||||
FullTerminator = 0xD2,
|
||||
SetOffset = 0xD3,
|
||||
AddValue = 0xD4,
|
||||
SetValue = 0xD5,
|
||||
IncrementiveWrite32 = 0xD6,
|
||||
IncrementiveWrite16 = 0xD7,
|
||||
IncrementiveWrite8 = 0xD8,
|
||||
Load32 = 0xD9,
|
||||
Load16 = 0xDA,
|
||||
Load8 = 0xDB,
|
||||
AddOffset = 0xDC,
|
||||
Joker = 0xDD,
|
||||
Patch = 0xE,
|
||||
};
|
||||
|
||||
struct CheatLine {
|
||||
explicit CheatLine(const std::string& line);
|
||||
CheatType type;
|
||||
u32 address;
|
||||
u32 value;
|
||||
u32 first;
|
||||
std::string cheat_line;
|
||||
bool valid = true;
|
||||
};
|
||||
|
||||
GatewayCheat(std::string name, std::vector<CheatLine> cheat_lines, std::string comments);
|
||||
GatewayCheat(std::string name, std::string code, std::string comments);
|
||||
~GatewayCheat();
|
||||
|
||||
void Execute(Core::System& system) const override;
|
||||
|
||||
bool IsEnabled() const override;
|
||||
void SetEnabled(bool enabled) override;
|
||||
|
||||
std::string GetComments() const override;
|
||||
std::string GetName() const override;
|
||||
std::string GetType() const override;
|
||||
std::string GetCode() const override;
|
||||
std::string ToString() const override;
|
||||
|
||||
/// Gateway cheats look like:
|
||||
/// [Name]
|
||||
/// 12345678 90ABCDEF
|
||||
/// 12345678 90ABCDEF
|
||||
/// (there might be multiple lines of those hex numbers)
|
||||
/// Comment lines start with a '*'
|
||||
/// This function will pares the file for such structures
|
||||
static std::vector<std::shared_ptr<CheatBase>> LoadFile(const std::string& filepath);
|
||||
|
||||
private:
|
||||
std::atomic<bool> enabled = false;
|
||||
const std::string name;
|
||||
std::vector<CheatLine> cheat_lines;
|
||||
const std::string comments;
|
||||
};
|
||||
} // namespace Cheats
|
||||
768
src/core/core.cpp
Normal file
768
src/core/core.cpp
Normal file
@@ -0,0 +1,768 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include "audio_core/dsp_interface.h"
|
||||
#include "audio_core/hle/hle.h"
|
||||
#include "audio_core/lle/lle.h"
|
||||
#include "common/arch.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/hle/service/cam/cam.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/ir/ir_user.h"
|
||||
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/dyncom/arm_dyncom.h"
|
||||
#include "core/cheats/cheats.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/dumping/backend.h"
|
||||
#include "core/frontend/image_interface.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/global.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/apt/applet_manager.h"
|
||||
#include "core/hle/service/apt/apt.h"
|
||||
#include "core/hle/service/cam/cam.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/gsp/gsp.h"
|
||||
#include "core/hle/service/gsp/gsp_gpu.h"
|
||||
#include "core/hle/service/ir/ir_rst.h"
|
||||
#include "core/hle/service/mic/mic_u.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/movie.h"
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "core/rpc/server.h"
|
||||
#endif
|
||||
#include "network/network.h"
|
||||
#include "video_core/custom_textures/custom_tex_manager.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
/*static*/ System System::s_instance;
|
||||
|
||||
template <>
|
||||
Core::System& Global() {
|
||||
return System::GetInstance();
|
||||
}
|
||||
|
||||
template <>
|
||||
Kernel::KernelSystem& Global() {
|
||||
return System::GetInstance().Kernel();
|
||||
}
|
||||
|
||||
template <>
|
||||
Core::Timing& Global() {
|
||||
return System::GetInstance().CoreTiming();
|
||||
}
|
||||
|
||||
System::System() : movie{*this}, cheat_engine{*this} {}
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
status = ResultStatus::Success;
|
||||
if (!IsPoweredOn()) {
|
||||
return ResultStatus::ErrorNotInitialized;
|
||||
}
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
Kernel::Thread* thread = kernel->GetCurrentThreadManager().GetCurrentThread();
|
||||
if (thread && running_core) {
|
||||
running_core->SaveContext(thread->context);
|
||||
}
|
||||
GDBStub::HandlePacket(*this);
|
||||
|
||||
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
|
||||
// execute. Otherwise, get out of the loop function.
|
||||
if (GDBStub::GetCpuHaltFlag()) {
|
||||
if (GDBStub::GetCpuStepFlag()) {
|
||||
tight_loop = false;
|
||||
} else {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Signal signal{Signal::None};
|
||||
u32 param{};
|
||||
{
|
||||
std::scoped_lock lock{signal_mutex};
|
||||
if (current_signal != Signal::None) {
|
||||
signal = current_signal;
|
||||
param = signal_param;
|
||||
current_signal = Signal::None;
|
||||
}
|
||||
}
|
||||
switch (signal) {
|
||||
case Signal::Reset:
|
||||
Reset();
|
||||
return ResultStatus::Success;
|
||||
case Signal::Shutdown:
|
||||
return ResultStatus::ShutdownRequested;
|
||||
case Signal::Load: {
|
||||
const u32 slot = param;
|
||||
LOG_INFO(Core, "Begin load of slot {}", slot);
|
||||
try {
|
||||
System::LoadState(slot);
|
||||
LOG_INFO(Core, "Load completed");
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Core, "Error loading: {}", e.what());
|
||||
status_details = e.what();
|
||||
return ResultStatus::ErrorSavestate;
|
||||
}
|
||||
frame_limiter.WaitOnce();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
case Signal::Save: {
|
||||
const u32 slot = param;
|
||||
LOG_INFO(Core, "Begin save to slot {}", slot);
|
||||
try {
|
||||
System::SaveState(slot);
|
||||
LOG_INFO(Core, "Save completed");
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Core, "Error saving: {}", e.what());
|
||||
status_details = e.what();
|
||||
return ResultStatus::ErrorSavestate;
|
||||
}
|
||||
frame_limiter.WaitOnce();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// All cores should have executed the same amount of ticks. If this is not the case an event was
|
||||
// scheduled with a cycles_into_future smaller then the current downcount.
|
||||
// So we have to get those cores to the same global time first
|
||||
u64 global_ticks = timing->GetGlobalTicks();
|
||||
s64 max_delay = 0;
|
||||
ARM_Interface* current_core_to_execute = nullptr;
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
if (cpu_core->GetTimer().GetTicks() < global_ticks) {
|
||||
s64 delay = global_ticks - cpu_core->GetTimer().GetTicks();
|
||||
running_core = cpu_core.get();
|
||||
kernel->SetRunningCPU(running_core);
|
||||
cpu_core->GetTimer().Advance();
|
||||
cpu_core->PrepareReschedule();
|
||||
kernel->GetThreadManager(cpu_core->GetID()).Reschedule();
|
||||
cpu_core->GetTimer().SetNextSlice(delay);
|
||||
if (max_delay < delay) {
|
||||
max_delay = delay;
|
||||
current_core_to_execute = cpu_core.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// jit sometimes overshoot by a few ticks which might lead to a minimal desync in the cores.
|
||||
// This small difference shouldn't make it necessary to sync the cores and would only cost
|
||||
// performance. Thus we don't sync delays below min_delay
|
||||
static constexpr s64 min_delay = 100;
|
||||
if (max_delay > min_delay) {
|
||||
LOG_TRACE(Core_ARM11, "Core {} running (delayed) for {} ticks",
|
||||
current_core_to_execute->GetID(),
|
||||
current_core_to_execute->GetTimer().GetDowncount());
|
||||
if (running_core != current_core_to_execute) {
|
||||
running_core = current_core_to_execute;
|
||||
kernel->SetRunningCPU(running_core);
|
||||
}
|
||||
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core_ARM11, "Core {} idling", current_core_to_execute->GetID());
|
||||
current_core_to_execute->GetTimer().Idle();
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
if (tight_loop) {
|
||||
current_core_to_execute->Run();
|
||||
} else {
|
||||
current_core_to_execute->Step();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Now all cores are at the same global time. So we will run them one after the other
|
||||
// with a max slice that is the minimum of all max slices of all cores
|
||||
// TODO: Make special check for idle since we can easily revert the time of idle cores
|
||||
s64 max_slice = Timing::MAX_SLICE_LENGTH;
|
||||
for (const auto& cpu_core : cpu_cores) {
|
||||
running_core = cpu_core.get();
|
||||
kernel->SetRunningCPU(running_core);
|
||||
cpu_core->GetTimer().Advance();
|
||||
cpu_core->PrepareReschedule();
|
||||
kernel->GetThreadManager(cpu_core->GetID()).Reschedule();
|
||||
max_slice = std::min(max_slice, cpu_core->GetTimer().GetMaxSliceLength());
|
||||
}
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
cpu_core->GetTimer().SetNextSlice(max_slice);
|
||||
auto start_ticks = cpu_core->GetTimer().GetTicks();
|
||||
LOG_TRACE(Core_ARM11, "Core {} running for {} ticks", cpu_core->GetID(),
|
||||
cpu_core->GetTimer().GetDowncount());
|
||||
running_core = cpu_core.get();
|
||||
kernel->SetRunningCPU(running_core);
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core_ARM11, "Core {} idling", cpu_core->GetID());
|
||||
cpu_core->GetTimer().Idle();
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
if (tight_loop) {
|
||||
cpu_core->Run();
|
||||
} else {
|
||||
cpu_core->Step();
|
||||
}
|
||||
}
|
||||
max_slice = cpu_core->GetTimer().GetTicks() - start_ticks;
|
||||
}
|
||||
}
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::SetCpuStepFlag(false);
|
||||
}
|
||||
|
||||
Reschedule();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool System::SendSignal(System::Signal signal, u32 param) {
|
||||
std::scoped_lock lock{signal_mutex};
|
||||
if (current_signal != signal && current_signal != Signal::None) {
|
||||
LOG_ERROR(Core, "Unable to {} as {} is ongoing", signal, current_signal);
|
||||
return false;
|
||||
}
|
||||
current_signal = signal;
|
||||
signal_param = param;
|
||||
return true;
|
||||
}
|
||||
|
||||
System::ResultStatus System::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
||||
Frontend::EmuWindow* secondary_window) {
|
||||
FileUtil::SetCurrentRomPath(filepath);
|
||||
if (early_app_loader) {
|
||||
app_loader = std::move(early_app_loader);
|
||||
} else {
|
||||
app_loader = Loader::GetLoader(filepath);
|
||||
}
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
return ResultStatus::ErrorGetLoader;
|
||||
}
|
||||
|
||||
if (restore_plugin_context.has_value() && restore_plugin_context->is_enabled &&
|
||||
restore_plugin_context->use_user_load_parameters) {
|
||||
u64_le program_id = 0;
|
||||
app_loader->ReadProgramId(program_id);
|
||||
if (restore_plugin_context->user_load_parameters.low_title_Id ==
|
||||
static_cast<u32_le>(program_id) &&
|
||||
restore_plugin_context->user_load_parameters.plugin_memory_strategy ==
|
||||
Service::PLGLDR::PLG_LDR::PluginMemoryStrategy::PLG_STRATEGY_MODE3) {
|
||||
app_loader->SetKernelMemoryModeOverride(Kernel::MemoryMode::Dev2);
|
||||
}
|
||||
}
|
||||
|
||||
auto memory_mode = app_loader->LoadKernelMemoryMode();
|
||||
if (memory_mode.second != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
||||
static_cast<int>(memory_mode.second));
|
||||
|
||||
switch (memory_mode.second) {
|
||||
case Loader::ResultStatus::ErrorEncrypted:
|
||||
return ResultStatus::ErrorLoader_ErrorEncrypted;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||
case Loader::ResultStatus::ErrorGbaTitle:
|
||||
return ResultStatus::ErrorLoader_ErrorGbaTitle;
|
||||
case Loader::ResultStatus::ErrorArtic:
|
||||
return ResultStatus::ErrorArticDisconnected;
|
||||
default:
|
||||
return ResultStatus::ErrorSystemMode;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(memory_mode.first);
|
||||
auto n3ds_hw_caps = app_loader->LoadNew3dsHwCapabilities();
|
||||
ASSERT(n3ds_hw_caps.first);
|
||||
u32 num_cores = 2;
|
||||
if (Settings::values.is_new_3ds) {
|
||||
num_cores = 4;
|
||||
}
|
||||
ResultStatus init_result{
|
||||
Init(emu_window, secondary_window, *memory_mode.first, *n3ds_hw_caps.first, num_cores)};
|
||||
if (init_result != ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||
static_cast<u32>(init_result));
|
||||
System::Shutdown();
|
||||
return init_result;
|
||||
}
|
||||
|
||||
// Restore any parameters that should be carried through a reset.
|
||||
if (restore_deliver_arg.has_value()) {
|
||||
if (auto apt = Service::APT::GetModule(*this)) {
|
||||
apt->GetAppletManager()->SetDeliverArg(restore_deliver_arg);
|
||||
}
|
||||
restore_deliver_arg.reset();
|
||||
}
|
||||
if (restore_plugin_context.has_value()) {
|
||||
if (auto plg_ldr = Service::PLGLDR::GetService(*this)) {
|
||||
plg_ldr->SetPluginLoaderContext(restore_plugin_context.value());
|
||||
}
|
||||
restore_plugin_context.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Kernel::Process> process;
|
||||
const Loader::ResultStatus load_result{app_loader->Load(process)};
|
||||
if (Loader::ResultStatus::Success != load_result) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
|
||||
System::Shutdown();
|
||||
|
||||
switch (load_result) {
|
||||
case Loader::ResultStatus::ErrorEncrypted:
|
||||
return ResultStatus::ErrorLoader_ErrorEncrypted;
|
||||
case Loader::ResultStatus::ErrorInvalidFormat:
|
||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||
case Loader::ResultStatus::ErrorGbaTitle:
|
||||
return ResultStatus::ErrorLoader_ErrorGbaTitle;
|
||||
case Loader::ResultStatus::ErrorArtic:
|
||||
return ResultStatus::ErrorArticDisconnected;
|
||||
default:
|
||||
return ResultStatus::ErrorLoader;
|
||||
}
|
||||
}
|
||||
kernel->SetCurrentProcess(process);
|
||||
title_id = 0;
|
||||
if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
|
||||
cheat_engine.LoadCheatFile(title_id);
|
||||
cheat_engine.Connect();
|
||||
|
||||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
|
||||
if (Settings::values.dump_textures) {
|
||||
custom_tex_manager->PrepareDumping(title_id);
|
||||
}
|
||||
if (Settings::values.custom_textures) {
|
||||
custom_tex_manager->FindCustomTextures();
|
||||
}
|
||||
|
||||
status = ResultStatus::Success;
|
||||
m_emu_window = &emu_window;
|
||||
m_secondary_window = secondary_window;
|
||||
m_filepath = filepath;
|
||||
self_delete_pending = false;
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
[[maybe_unused]] const PerfStats::Results result = GetAndResetPerfStats();
|
||||
perf_stats->BeginSystemFrame();
|
||||
return status;
|
||||
}
|
||||
|
||||
void System::PrepareReschedule() {
|
||||
running_core->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
}
|
||||
|
||||
PerfStats::Results System::GetAndResetPerfStats() {
|
||||
return (perf_stats && timing) ? perf_stats->GetAndResetStats(timing->GetGlobalTimeUs())
|
||||
: PerfStats::Results{};
|
||||
}
|
||||
|
||||
PerfStats::Results System::GetLastPerfStats() {
|
||||
return perf_stats ? perf_stats->GetLastStats() : PerfStats::Results{};
|
||||
}
|
||||
|
||||
void System::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
for (const auto& core : cpu_cores) {
|
||||
LOG_TRACE(Core_ARM11, "Reschedule core {}", core->GetID());
|
||||
kernel->GetThreadManager(core->GetID()).Reschedule();
|
||||
}
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
Frontend::EmuWindow* secondary_window,
|
||||
Kernel::MemoryMode memory_mode,
|
||||
const Kernel::New3dsHwCapabilities& n3ds_hw_caps, u32 num_cores) {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
memory = std::make_unique<Memory::MemorySystem>(*this);
|
||||
|
||||
timing = std::make_unique<Timing>(num_cores, Settings::values.cpu_clock_percentage.GetValue(),
|
||||
movie.GetOverrideBaseTicks());
|
||||
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(
|
||||
*memory, *timing, [this] { PrepareReschedule(); }, memory_mode, num_cores, n3ds_hw_caps,
|
||||
movie.GetOverrideInitTime());
|
||||
|
||||
exclusive_monitor = MakeExclusiveMonitor(*memory, num_cores);
|
||||
cpu_cores.reserve(num_cores);
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
cpu_cores.push_back(std::make_shared<ARM_Dynarmic>(
|
||||
*this, *memory, i, timing->GetTimer(i), *exclusive_monitor));
|
||||
}
|
||||
#else
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
cpu_cores.push_back(
|
||||
std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
|
||||
}
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
cpu_cores.push_back(
|
||||
std::make_shared<ARM_DynCom>(*this, *memory, USER32MODE, i, timing->GetTimer(i)));
|
||||
}
|
||||
}
|
||||
running_core = cpu_cores[0].get();
|
||||
|
||||
kernel->SetCPUs(cpu_cores);
|
||||
kernel->SetRunningCPU(cpu_cores[0].get());
|
||||
|
||||
const auto audio_emulation = Settings::values.audio_emulation.GetValue();
|
||||
if (audio_emulation == Settings::AudioEmulation::HLE) {
|
||||
dsp_core = std::make_unique<AudioCore::DspHle>(*this);
|
||||
} else {
|
||||
const bool multithread = audio_emulation == Settings::AudioEmulation::LLEMultithreaded;
|
||||
dsp_core = std::make_unique<AudioCore::DspLle>(*this, multithread);
|
||||
}
|
||||
|
||||
memory->SetDSP(*dsp_core);
|
||||
|
||||
dsp_core->SetSink(Settings::values.output_type.GetValue(),
|
||||
Settings::values.output_device.GetValue());
|
||||
dsp_core->EnableStretching(Settings::values.enable_audio_stretching.GetValue());
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
rpc_server = std::make_unique<RPC::Server>(*this);
|
||||
#endif
|
||||
|
||||
service_manager = std::make_unique<Service::SM::ServiceManager>(*this);
|
||||
archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
|
||||
|
||||
HW::AES::InitKeys();
|
||||
Service::Init(*this);
|
||||
GDBStub::DeferStart();
|
||||
|
||||
if (!registered_image_interface) {
|
||||
registered_image_interface = std::make_shared<Frontend::ImageInterface>();
|
||||
}
|
||||
|
||||
custom_tex_manager = std::make_unique<VideoCore::CustomTexManager>(*this);
|
||||
|
||||
auto gsp = service_manager->GetService<Service::GSP::GSP_GPU>("gsp::Gpu");
|
||||
gpu = std::make_unique<VideoCore::GPU>(*this, emu_window, secondary_window);
|
||||
gpu->SetInterruptHandler(
|
||||
[gsp](Service::GSP::InterruptId interrupt_id) { gsp->SignalInterrupt(interrupt_id); });
|
||||
|
||||
auto plg_ldr = Service::PLGLDR::GetService(*this);
|
||||
if (plg_ldr) {
|
||||
plg_ldr->SetEnabled(Settings::values.plugin_loader_enabled.GetValue());
|
||||
plg_ldr->SetAllowGameChangeState(Settings::values.allow_plugin_loader.GetValue());
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
is_powered_on = true;
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
VideoCore::GPU& System::GPU() {
|
||||
return *gpu;
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& System::ServiceManager() {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
const Service::SM::ServiceManager& System::ServiceManager() const {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
Service::FS::ArchiveManager& System::ArchiveManager() {
|
||||
return *archive_manager;
|
||||
}
|
||||
|
||||
const Service::FS::ArchiveManager& System::ArchiveManager() const {
|
||||
return *archive_manager;
|
||||
}
|
||||
|
||||
Kernel::KernelSystem& System::Kernel() {
|
||||
return *kernel;
|
||||
}
|
||||
|
||||
const Kernel::KernelSystem& System::Kernel() const {
|
||||
return *kernel;
|
||||
}
|
||||
|
||||
bool System::KernelRunning() {
|
||||
return kernel != nullptr;
|
||||
}
|
||||
|
||||
Timing& System::CoreTiming() {
|
||||
return *timing;
|
||||
}
|
||||
|
||||
const Timing& System::CoreTiming() const {
|
||||
return *timing;
|
||||
}
|
||||
|
||||
Memory::MemorySystem& System::Memory() {
|
||||
return *memory;
|
||||
}
|
||||
|
||||
const Memory::MemorySystem& System::Memory() const {
|
||||
return *memory;
|
||||
}
|
||||
|
||||
Cheats::CheatEngine& System::CheatEngine() {
|
||||
return cheat_engine;
|
||||
}
|
||||
|
||||
const Cheats::CheatEngine& System::CheatEngine() const {
|
||||
return cheat_engine;
|
||||
}
|
||||
|
||||
void System::RegisterVideoDumper(std::shared_ptr<VideoDumper::Backend> dumper) {
|
||||
video_dumper = std::move(dumper);
|
||||
}
|
||||
|
||||
VideoCore::CustomTexManager& System::CustomTexManager() {
|
||||
return *custom_tex_manager;
|
||||
}
|
||||
|
||||
const VideoCore::CustomTexManager& System::CustomTexManager() const {
|
||||
return *custom_tex_manager;
|
||||
}
|
||||
|
||||
Core::Movie& System::Movie() {
|
||||
return movie;
|
||||
}
|
||||
|
||||
const Core::Movie& System::Movie() const {
|
||||
return movie;
|
||||
}
|
||||
|
||||
void System::RegisterMiiSelector(std::shared_ptr<Frontend::MiiSelector> mii_selector) {
|
||||
registered_mii_selector = std::move(mii_selector);
|
||||
}
|
||||
|
||||
void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd) {
|
||||
registered_swkbd = std::move(swkbd);
|
||||
}
|
||||
|
||||
void System::RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> image_interface) {
|
||||
registered_image_interface = std::move(image_interface);
|
||||
}
|
||||
|
||||
void System::Shutdown(bool is_deserializing) {
|
||||
|
||||
// Shutdown emulation session
|
||||
is_powered_on = false;
|
||||
|
||||
gpu.reset();
|
||||
if (!is_deserializing) {
|
||||
GDBStub::Shutdown();
|
||||
perf_stats.reset();
|
||||
app_loader.reset();
|
||||
}
|
||||
custom_tex_manager.reset();
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
rpc_server.reset();
|
||||
#endif
|
||||
archive_manager.reset();
|
||||
service_manager.reset();
|
||||
dsp_core.reset();
|
||||
kernel.reset();
|
||||
cpu_cores.clear();
|
||||
exclusive_monitor.reset();
|
||||
timing.reset();
|
||||
|
||||
if (video_dumper && video_dumper->IsDumping()) {
|
||||
video_dumper->StopDumping();
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
}
|
||||
|
||||
memory.reset();
|
||||
|
||||
if (self_delete_pending)
|
||||
FileUtil::Delete(m_filepath);
|
||||
self_delete_pending = false;
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
void System::Reset() {
|
||||
// This is NOT a proper reset, but a temporary workaround by shutting down the system and
|
||||
// reloading.
|
||||
// TODO: Properly implement the reset
|
||||
|
||||
// Save the APT deliver arg and plugin loader context across resets.
|
||||
// This is needed as we don't currently support proper app jumping.
|
||||
if (auto apt = Service::APT::GetModule(*this)) {
|
||||
restore_deliver_arg = apt->GetAppletManager()->ReceiveDeliverArg();
|
||||
}
|
||||
if (auto plg_ldr = Service::PLGLDR::GetService(*this)) {
|
||||
restore_plugin_context = plg_ldr->GetPluginLoaderContext();
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
|
||||
if (!m_chainloadpath.empty()) {
|
||||
m_filepath = m_chainloadpath;
|
||||
m_chainloadpath.clear();
|
||||
}
|
||||
|
||||
// Reload the system with the same setting
|
||||
[[maybe_unused]] const System::ResultStatus result =
|
||||
Load(*m_emu_window, m_filepath, m_secondary_window);
|
||||
}
|
||||
|
||||
void System::ApplySettings() {
|
||||
GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue());
|
||||
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
|
||||
|
||||
if (gpu) {
|
||||
#ifndef ANDROID
|
||||
gpu->Renderer().UpdateCurrentFramebufferLayout();
|
||||
#endif
|
||||
auto& settings = gpu->Renderer().Settings();
|
||||
settings.bg_color_update_requested = true;
|
||||
settings.shader_update_requested = true;
|
||||
}
|
||||
|
||||
if (IsPoweredOn()) {
|
||||
CoreTiming().UpdateClockSpeed(Settings::values.cpu_clock_percentage.GetValue());
|
||||
dsp_core->SetSink(Settings::values.output_type.GetValue(),
|
||||
Settings::values.output_device.GetValue());
|
||||
dsp_core->EnableStretching(Settings::values.enable_audio_stretching.GetValue());
|
||||
|
||||
auto hid = Service::HID::GetModule(*this);
|
||||
if (hid) {
|
||||
hid->ReloadInputDevices();
|
||||
}
|
||||
|
||||
auto apt = Service::APT::GetModule(*this);
|
||||
if (apt) {
|
||||
apt->GetAppletManager()->ReloadInputDevices();
|
||||
}
|
||||
|
||||
auto ir_user = service_manager->GetService<Service::IR::IR_USER>("ir:USER");
|
||||
if (ir_user)
|
||||
ir_user->ReloadInputDevices();
|
||||
auto ir_rst = service_manager->GetService<Service::IR::IR_RST>("ir:rst");
|
||||
if (ir_rst)
|
||||
ir_rst->ReloadInputDevices();
|
||||
|
||||
auto cam = Service::CAM::GetModule(*this);
|
||||
if (cam) {
|
||||
cam->ReloadCameraDevices();
|
||||
}
|
||||
|
||||
Service::MIC::ReloadMic(*this);
|
||||
}
|
||||
|
||||
auto plg_ldr = Service::PLGLDR::GetService(*this);
|
||||
if (plg_ldr) {
|
||||
plg_ldr->SetEnabled(Settings::values.plugin_loader_enabled.GetValue());
|
||||
plg_ldr->SetAllowGameChangeState(Settings::values.allow_plugin_loader.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
void System::RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader) {
|
||||
early_app_loader = std::move(loader);
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void System::serialize(Archive& ar, const unsigned int file_version) {
|
||||
|
||||
u32 num_cores;
|
||||
if (Archive::is_saving::value) {
|
||||
num_cores = this->GetNumCores();
|
||||
}
|
||||
ar& num_cores;
|
||||
|
||||
if (Archive::is_loading::value) {
|
||||
// When loading, we want to make sure any lingering state gets cleared out before we begin.
|
||||
// Shutdown, but persist a few things between loads...
|
||||
Shutdown(true);
|
||||
|
||||
// Re-initialize everything like it was before
|
||||
auto memory_mode = this->app_loader->LoadKernelMemoryMode();
|
||||
auto n3ds_hw_caps = this->app_loader->LoadNew3dsHwCapabilities();
|
||||
[[maybe_unused]] const System::ResultStatus result = Init(
|
||||
*m_emu_window, m_secondary_window, *memory_mode.first, *n3ds_hw_caps.first, num_cores);
|
||||
}
|
||||
|
||||
// Flush on save, don't flush on load
|
||||
const bool should_flush = !Archive::is_loading::value;
|
||||
gpu->ClearAll(should_flush);
|
||||
ar&* timing.get();
|
||||
for (u32 i = 0; i < num_cores; i++) {
|
||||
ar&* cpu_cores[i].get();
|
||||
}
|
||||
ar&* service_manager.get();
|
||||
ar&* archive_manager.get();
|
||||
|
||||
// NOTE: DSP doesn't like being destroyed and recreated. So instead we do an inline
|
||||
// serialization; this means that the DSP Settings need to match for loading to work.
|
||||
auto dsp_hle = dynamic_cast<AudioCore::DspHle*>(dsp_core.get());
|
||||
if (dsp_hle) {
|
||||
ar&* dsp_hle;
|
||||
} else {
|
||||
throw std::runtime_error("LLE audio not supported for save states");
|
||||
}
|
||||
|
||||
ar&* memory.get();
|
||||
ar&* kernel.get();
|
||||
ar&* gpu.get();
|
||||
ar& movie;
|
||||
|
||||
// This needs to be set from somewhere - might as well be here!
|
||||
if (Archive::is_loading::value) {
|
||||
timing->UnlockEventQueue();
|
||||
memory->SetDSP(*dsp_core);
|
||||
cheat_engine.Connect();
|
||||
gpu->Sync();
|
||||
|
||||
// Re-register gpu callback, because gsp service changed after service_manager got
|
||||
// serialized
|
||||
auto gsp = service_manager->GetService<Service::GSP::GSP_GPU>("gsp::Gpu");
|
||||
gpu->SetInterruptHandler(
|
||||
[gsp](Service::GSP::InterruptId interrupt_id) { gsp->SignalInterrupt(interrupt_id); });
|
||||
}
|
||||
}
|
||||
|
||||
SERIALIZE_IMPL(System)
|
||||
|
||||
} // namespace Core
|
||||
478
src/core/core.h
Normal file
478
src/core/core.h
Normal file
@@ -0,0 +1,478 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/serialization/version.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/cheats/cheats.h"
|
||||
#include "core/hle/service/apt/applet_manager.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
#include "core/movie.h"
|
||||
#include "core/perf_stats.h"
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
class ImageInterface;
|
||||
class MiiSelector;
|
||||
class SoftwareKeyboard;
|
||||
} // namespace Frontend
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
class DspInterface;
|
||||
}
|
||||
|
||||
namespace Core::RPC {
|
||||
class Server;
|
||||
}
|
||||
|
||||
namespace Service {
|
||||
namespace SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
namespace FS {
|
||||
class ArchiveManager;
|
||||
}
|
||||
} // namespace Service
|
||||
|
||||
namespace Kernel {
|
||||
class KernelSystem;
|
||||
struct New3dsHwCapabilities;
|
||||
enum class MemoryMode : u8;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace VideoDumper {
|
||||
class Backend;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class CustomTexManager;
|
||||
class GPU;
|
||||
} // namespace VideoCore
|
||||
|
||||
namespace Pica {
|
||||
class DebugContext;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
class ExclusiveMonitor;
|
||||
class Timing;
|
||||
|
||||
class System {
|
||||
public:
|
||||
/**
|
||||
* Gets the instance of the System singleton class.
|
||||
* @returns Reference to the instance of the System singleton class.
|
||||
*/
|
||||
[[nodiscard]] static System& GetInstance() {
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
/// Enumeration representing the return values of the System Initialize and Load process.
|
||||
enum class ResultStatus : u32 {
|
||||
Success, ///< Succeeded
|
||||
ErrorNotInitialized, ///< Error trying to use core prior to initialization
|
||||
ErrorGetLoader, ///< Error finding the correct application loader
|
||||
ErrorSystemMode, ///< Error determining the system mode
|
||||
ErrorLoader, ///< Error loading the specified application
|
||||
ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption
|
||||
ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an
|
||||
/// invalid format
|
||||
ErrorLoader_ErrorGbaTitle, ///< Error loading the specified application as it is GBA Virtual
|
||||
///< Console
|
||||
ErrorSystemFiles, ///< Error in finding system files
|
||||
ErrorSavestate, ///< Error saving or loading
|
||||
ErrorArticDisconnected, ///< Error when artic base disconnects
|
||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||
ErrorUnknown ///< Any other error
|
||||
};
|
||||
|
||||
explicit System();
|
||||
~System();
|
||||
|
||||
/**
|
||||
* Run the core CPU loop
|
||||
* This function runs the core for the specified number of CPU instructions before trying to
|
||||
* update hardware. This is much faster than SingleStep (and should be equivalent), as the CPU
|
||||
* is not required to do a full dispatch with each instruction. NOTE: the number of instructions
|
||||
* requested is not guaranteed to run, as this will be interrupted preemptively if a hardware
|
||||
* update is requested (e.g. on a thread switch).
|
||||
* @param tight_loop If false, the CPU single-steps.
|
||||
* @return Result status, indicating whethor or not the operation succeeded.
|
||||
*/
|
||||
[[nodiscard]] ResultStatus RunLoop(bool tight_loop = true);
|
||||
|
||||
/**
|
||||
* Step the CPU one instruction
|
||||
* @return Result status, indicating whethor or not the operation succeeded.
|
||||
*/
|
||||
[[nodiscard]] ResultStatus SingleStep();
|
||||
|
||||
/// Shutdown the emulated system.
|
||||
void Shutdown(bool is_deserializing = false);
|
||||
|
||||
/// Shutdown and then load again
|
||||
void Reset();
|
||||
|
||||
enum class Signal : u32 { None, Shutdown, Reset, Save, Load };
|
||||
|
||||
bool SendSignal(Signal signal, u32 param = 0);
|
||||
|
||||
/// Request reset of the system
|
||||
void RequestReset(const std::string& chainload = "") {
|
||||
m_chainloadpath = chainload;
|
||||
SendSignal(Signal::Reset);
|
||||
}
|
||||
|
||||
/// Request shutdown of the system
|
||||
void RequestShutdown() {
|
||||
SendSignal(Signal::Shutdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an executable application.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
* input.
|
||||
* @param filepath String path to the executable application to load on the host file system.
|
||||
* @returns ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
[[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
||||
Frontend::EmuWindow* secondary_window = {});
|
||||
|
||||
/**
|
||||
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
|
||||
* application).
|
||||
* @returns True if the emulated system is powered on, otherwise false.
|
||||
*/
|
||||
[[nodiscard]] bool IsPoweredOn() const {
|
||||
return is_powered_on;
|
||||
}
|
||||
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule();
|
||||
|
||||
[[nodiscard]] PerfStats::Results GetAndResetPerfStats();
|
||||
|
||||
void ReportArticTraffic(u32 bytes) {
|
||||
if (perf_stats) {
|
||||
perf_stats->AddArticBaseTraffic(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void ReportPerfArticEvent(PerfStats::PerfArticEventBits event, bool set) {
|
||||
if (perf_stats) {
|
||||
perf_stats->ReportPerfArticEvent(event, set);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] PerfStats::Results GetLastPerfStats();
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated CPU.
|
||||
* @returns A reference to the emulated CPU.
|
||||
*/
|
||||
|
||||
[[nodiscard]] ARM_Interface& GetRunningCore() {
|
||||
return *running_core;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated CPU.
|
||||
* @param core_id The id of the core requested.
|
||||
* @returns A reference to the emulated CPU.
|
||||
*/
|
||||
|
||||
[[nodiscard]] ARM_Interface& GetCore(u32 core_id) {
|
||||
return *cpu_cores[core_id];
|
||||
};
|
||||
|
||||
[[nodiscard]] const ARM_Interface& GetCore(u32 core_id) const {
|
||||
return *cpu_cores[core_id];
|
||||
};
|
||||
|
||||
[[nodiscard]] u32 GetNumCores() const {
|
||||
return static_cast<u32>(cpu_cores.size());
|
||||
}
|
||||
|
||||
void InvalidateCacheRange(u32 start_address, std::size_t length) {
|
||||
for (const auto& cpu : cpu_cores) {
|
||||
cpu->InvalidateCacheRange(start_address, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated DSP.
|
||||
* @returns A reference to the emulated DSP.
|
||||
*/
|
||||
[[nodiscard]] AudioCore::DspInterface& DSP() {
|
||||
return *dsp_core;
|
||||
}
|
||||
|
||||
[[nodiscard]] VideoCore::GPU& GPU();
|
||||
|
||||
/**
|
||||
* Gets a reference to the service manager.
|
||||
* @returns A reference to the service manager.
|
||||
*/
|
||||
[[nodiscard]] Service::SM::ServiceManager& ServiceManager();
|
||||
|
||||
/**
|
||||
* Gets a const reference to the service manager.
|
||||
* @returns A const reference to the service manager.
|
||||
*/
|
||||
[[nodiscard]] const Service::SM::ServiceManager& ServiceManager() const;
|
||||
|
||||
/// Gets a reference to the archive manager
|
||||
[[nodiscard]] Service::FS::ArchiveManager& ArchiveManager();
|
||||
|
||||
/// Gets a const reference to the archive manager
|
||||
[[nodiscard]] const Service::FS::ArchiveManager& ArchiveManager() const;
|
||||
|
||||
/// Gets a reference to the kernel
|
||||
[[nodiscard]] Kernel::KernelSystem& Kernel();
|
||||
|
||||
/// Gets a const reference to the kernel
|
||||
[[nodiscard]] const Kernel::KernelSystem& Kernel() const;
|
||||
|
||||
/// Get kernel is running
|
||||
[[nodiscard]] bool KernelRunning();
|
||||
|
||||
/// Gets a reference to the timing system
|
||||
[[nodiscard]] Timing& CoreTiming();
|
||||
|
||||
/// Gets a const reference to the timing system
|
||||
[[nodiscard]] const Timing& CoreTiming() const;
|
||||
|
||||
/// Gets a reference to the memory system
|
||||
[[nodiscard]] Memory::MemorySystem& Memory();
|
||||
|
||||
/// Gets a const reference to the memory system
|
||||
[[nodiscard]] const Memory::MemorySystem& Memory() const;
|
||||
|
||||
/// Gets a reference to the cheat engine
|
||||
[[nodiscard]] Cheats::CheatEngine& CheatEngine();
|
||||
|
||||
/// Gets a const reference to the cheat engine
|
||||
[[nodiscard]] const Cheats::CheatEngine& CheatEngine() const;
|
||||
|
||||
/// Gets a reference to the custom texture cache system
|
||||
[[nodiscard]] VideoCore::CustomTexManager& CustomTexManager();
|
||||
|
||||
/// Gets a const reference to the custom texture cache system
|
||||
[[nodiscard]] const VideoCore::CustomTexManager& CustomTexManager() const;
|
||||
|
||||
/// Gets a reference to the movie recorder
|
||||
[[nodiscard]] Core::Movie& Movie();
|
||||
|
||||
/// Gets a const reference to the movie recorder
|
||||
[[nodiscard]] const Core::Movie& Movie() const;
|
||||
|
||||
/// Video Dumper interface
|
||||
|
||||
void RegisterVideoDumper(std::shared_ptr<VideoDumper::Backend> video_dumper);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<VideoDumper::Backend> GetVideoDumper() const {
|
||||
return video_dumper;
|
||||
}
|
||||
|
||||
std::unique_ptr<PerfStats> perf_stats;
|
||||
FrameLimiter frame_limiter;
|
||||
|
||||
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
|
||||
status = new_status;
|
||||
if (details) {
|
||||
status_details = details;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& GetStatusDetails() const {
|
||||
return status_details;
|
||||
}
|
||||
|
||||
[[nodiscard]] Loader::AppLoader& GetAppLoader() const {
|
||||
return *app_loader;
|
||||
}
|
||||
|
||||
/// Frontend Applets
|
||||
|
||||
void RegisterMiiSelector(std::shared_ptr<Frontend::MiiSelector> mii_selector);
|
||||
|
||||
void RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Frontend::MiiSelector> GetMiiSelector() const {
|
||||
return registered_mii_selector;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Frontend::SoftwareKeyboard> GetSoftwareKeyboard() const {
|
||||
return registered_swkbd;
|
||||
}
|
||||
|
||||
/// Image interface
|
||||
|
||||
void RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> image_interface);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Frontend::ImageInterface> GetImageInterface() const {
|
||||
return registered_image_interface;
|
||||
}
|
||||
|
||||
/// Function for checking OS microphone permissions.
|
||||
|
||||
void RegisterMicPermissionCheck(const std::function<bool()>& permission_func) {
|
||||
mic_permission_func = permission_func;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasMicPermission() {
|
||||
return !mic_permission_func || mic_permission_granted ||
|
||||
(mic_permission_granted = mic_permission_func());
|
||||
}
|
||||
|
||||
void SaveState(u32 slot) const;
|
||||
|
||||
void LoadState(u32 slot);
|
||||
|
||||
/// Self delete ncch
|
||||
bool SetSelfDelete(const std::string& file) {
|
||||
if (m_filepath == file) {
|
||||
self_delete_pending = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Applies any changes to settings to this core instance.
|
||||
void ApplySettings();
|
||||
|
||||
void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
* input.
|
||||
* @param system_mode The system mode.
|
||||
* @return ResultStatus code, indicating if the operation succeeded.
|
||||
*/
|
||||
[[nodiscard]] ResultStatus Init(Frontend::EmuWindow& emu_window,
|
||||
Frontend::EmuWindow* secondary_window,
|
||||
Kernel::MemoryMode memory_mode,
|
||||
const Kernel::New3dsHwCapabilities& n3ds_hw_caps,
|
||||
u32 num_cores);
|
||||
|
||||
/// Reschedule the core emulation
|
||||
void Reschedule();
|
||||
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
|
||||
// Temporary app loader passed from frontend
|
||||
std::unique_ptr<Loader::AppLoader> early_app_loader;
|
||||
|
||||
/// ARM11 CPU core
|
||||
std::vector<std::shared_ptr<ARM_Interface>> cpu_cores;
|
||||
ARM_Interface* running_core = nullptr;
|
||||
|
||||
/// DSP core
|
||||
std::unique_ptr<AudioCore::DspInterface> dsp_core;
|
||||
|
||||
/// When true, signals that a reschedule should happen
|
||||
bool reschedule_pending{};
|
||||
|
||||
std::unique_ptr<VideoCore::GPU> gpu;
|
||||
|
||||
/// Service manager
|
||||
std::unique_ptr<Service::SM::ServiceManager> service_manager;
|
||||
|
||||
/// Frontend applets
|
||||
std::shared_ptr<Frontend::MiiSelector> registered_mii_selector;
|
||||
std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd;
|
||||
|
||||
/// Movie recorder
|
||||
Core::Movie movie;
|
||||
|
||||
/// Cheats manager
|
||||
Cheats::CheatEngine cheat_engine;
|
||||
|
||||
/// Video dumper backend
|
||||
std::shared_ptr<VideoDumper::Backend> video_dumper;
|
||||
|
||||
/// Custom texture cache system
|
||||
std::unique_ptr<VideoCore::CustomTexManager> custom_tex_manager;
|
||||
|
||||
/// Image interface
|
||||
std::shared_ptr<Frontend::ImageInterface> registered_image_interface;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
/// RPC Server for scripting support
|
||||
std::unique_ptr<RPC::Server> rpc_server;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Service::FS::ArchiveManager> archive_manager;
|
||||
|
||||
std::unique_ptr<Memory::MemorySystem> memory;
|
||||
std::unique_ptr<Kernel::KernelSystem> kernel;
|
||||
std::unique_ptr<Timing> timing;
|
||||
|
||||
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
|
||||
|
||||
private:
|
||||
static System s_instance;
|
||||
|
||||
std::atomic_bool is_powered_on{};
|
||||
|
||||
ResultStatus status = ResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
/// Saved variables for reset
|
||||
Frontend::EmuWindow* m_emu_window;
|
||||
Frontend::EmuWindow* m_secondary_window;
|
||||
std::string m_filepath;
|
||||
std::string m_chainloadpath;
|
||||
u64 title_id;
|
||||
bool self_delete_pending;
|
||||
|
||||
std::mutex signal_mutex;
|
||||
Signal current_signal;
|
||||
u32 signal_param;
|
||||
|
||||
std::function<bool()> mic_permission_func;
|
||||
bool mic_permission_granted = false;
|
||||
|
||||
boost::optional<Service::APT::DeliverArg> restore_deliver_arg;
|
||||
boost::optional<Service::PLGLDR::PLG_LDR::PluginLoaderContext> restore_plugin_context;
|
||||
|
||||
friend class boost::serialization::access;
|
||||
template <typename Archive>
|
||||
void serialize(Archive& ar, const unsigned int file_version);
|
||||
};
|
||||
|
||||
[[nodiscard]] inline ARM_Interface& GetRunningCore() {
|
||||
return System::GetInstance().GetRunningCore();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline ARM_Interface& GetCore(u32 core_id) {
|
||||
return System::GetInstance().GetCore(core_id);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline u32 GetNumCores() {
|
||||
return System::GetInstance().GetNumCores();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
BOOST_CLASS_VERSION(Core::System, 1)
|
||||
257
src/core/core_timing.cpp
Normal file
257
src/core/core_timing.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <tuple>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Sort by time, unless the times are the same, in which case sort by the order added to the queue
|
||||
bool Timing::Event::operator>(const Timing::Event& right) const {
|
||||
return std::tie(time, fifo_order) > std::tie(right.time, right.fifo_order);
|
||||
}
|
||||
|
||||
bool Timing::Event::operator<(const Timing::Event& right) const {
|
||||
return std::tie(time, fifo_order) < std::tie(right.time, right.fifo_order);
|
||||
}
|
||||
|
||||
Timing::Timing(std::size_t num_cores, u32 cpu_clock_percentage, s64 override_base_ticks) {
|
||||
// Generate non-zero base tick count to simulate time the system ran before launching the game.
|
||||
// This accounts for games that rely on the system tick to seed randomness.
|
||||
const auto base_ticks = override_base_ticks >= 0 ? override_base_ticks : GenerateBaseTicks();
|
||||
|
||||
timers.resize(num_cores);
|
||||
for (std::size_t i = 0; i < num_cores; ++i) {
|
||||
timers[i] = std::make_shared<Timer>(base_ticks);
|
||||
}
|
||||
UpdateClockSpeed(cpu_clock_percentage);
|
||||
current_timer = timers[0].get();
|
||||
}
|
||||
|
||||
s64 Timing::GenerateBaseTicks() {
|
||||
if (Settings::values.init_ticks_type.GetValue() == Settings::InitTicks::Fixed) {
|
||||
return Settings::values.init_ticks_override.GetValue();
|
||||
}
|
||||
// Bounded to 32 bits to make sure we don't generate too high of a counter and risk overflowing.
|
||||
std::mt19937 random_gen(std::random_device{}());
|
||||
return random_gen();
|
||||
}
|
||||
|
||||
void Timing::UpdateClockSpeed(u32 cpu_clock_percentage) {
|
||||
for (auto& timer : timers) {
|
||||
timer->cpu_clock_scale = 100.0 / cpu_clock_percentage;
|
||||
}
|
||||
}
|
||||
|
||||
TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
// check for existing type with same name.
|
||||
// we want event type names to remain unique so that we can use them for serialization.
|
||||
auto info = event_types.emplace(name, TimingEventType{});
|
||||
TimingEventType* event_type = &info.first->second;
|
||||
event_type->name = &info.first->first;
|
||||
if (callback != nullptr) {
|
||||
event_type->callback = callback;
|
||||
}
|
||||
return event_type;
|
||||
}
|
||||
|
||||
void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type,
|
||||
std::uintptr_t user_data, std::size_t core_id, bool thread_safe_mode) {
|
||||
if (event_queue_locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(event_type != nullptr);
|
||||
Timing::Timer* timer = nullptr;
|
||||
if (core_id == std::numeric_limits<std::size_t>::max()) {
|
||||
timer = current_timer;
|
||||
} else {
|
||||
ASSERT(core_id < timers.size());
|
||||
timer = timers.at(core_id).get();
|
||||
}
|
||||
|
||||
if (thread_safe_mode) {
|
||||
// Events scheduled in thread safe mode come after blocking operations with
|
||||
// unpredictable timings in the host machine, so there is no need to be cycle accurate.
|
||||
// To prevent the event from scheduling before the next advance(), we set a minimum time
|
||||
// of MAX_SLICE_LENGTH * 2 cycles into the future.
|
||||
cycles_into_future = std::max(static_cast<s64>(MAX_SLICE_LENGTH * 2), cycles_into_future);
|
||||
|
||||
timer->ts_queue.Push(Event{static_cast<s64>(timer->GetTicks() + cycles_into_future), 0,
|
||||
user_data, event_type});
|
||||
} else {
|
||||
s64 timeout = timer->GetTicks() + cycles_into_future;
|
||||
if (current_timer == timer) {
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
if (!timer->is_timer_sane)
|
||||
timer->ForceExceptionCheck(cycles_into_future);
|
||||
|
||||
timer->event_queue.emplace_back(
|
||||
Event{timeout, timer->event_fifo_id++, user_data, event_type});
|
||||
std::push_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>());
|
||||
} else {
|
||||
timer->ts_queue.Push(Event{static_cast<s64>(timer->GetTicks() + cycles_into_future), 0,
|
||||
user_data, event_type});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Timing::UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data) {
|
||||
if (event_queue_locked) {
|
||||
return;
|
||||
}
|
||||
for (auto timer : timers) {
|
||||
auto itr = std::remove_if(
|
||||
timer->event_queue.begin(), timer->event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type && e.user_data == user_data; });
|
||||
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != timer->event_queue.end()) {
|
||||
timer->event_queue.erase(itr, timer->event_queue.end());
|
||||
std::make_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
// TODO:remove events from ts_queue
|
||||
}
|
||||
|
||||
void Timing::RemoveEvent(const TimingEventType* event_type) {
|
||||
if (event_queue_locked) {
|
||||
return;
|
||||
}
|
||||
for (auto timer : timers) {
|
||||
auto itr = std::remove_if(timer->event_queue.begin(), timer->event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != timer->event_queue.end()) {
|
||||
timer->event_queue.erase(itr, timer->event_queue.end());
|
||||
std::make_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
// TODO:remove events from ts_queue
|
||||
}
|
||||
|
||||
void Timing::SetCurrentTimer(std::size_t core_id) {
|
||||
current_timer = timers[core_id].get();
|
||||
}
|
||||
|
||||
s64 Timing::GetTicks() const {
|
||||
return current_timer->GetTicks();
|
||||
}
|
||||
|
||||
s64 Timing::GetGlobalTicks() const {
|
||||
const auto& timer =
|
||||
std::max_element(timers.cbegin(), timers.cend(), [](const auto& a, const auto& b) {
|
||||
return a->GetTicks() < b->GetTicks();
|
||||
});
|
||||
return (*timer)->GetTicks();
|
||||
}
|
||||
|
||||
std::chrono::microseconds Timing::GetGlobalTimeUs() const {
|
||||
return std::chrono::microseconds{GetGlobalTicks() * 1000000 / BASE_CLOCK_RATE_ARM11};
|
||||
}
|
||||
|
||||
std::shared_ptr<Timing::Timer> Timing::GetTimer(std::size_t cpu_id) {
|
||||
return timers[cpu_id];
|
||||
}
|
||||
|
||||
Timing::Timer::Timer(s64 base_ticks) : executed_ticks(base_ticks) {}
|
||||
|
||||
Timing::Timer::~Timer() {
|
||||
MoveEvents();
|
||||
}
|
||||
|
||||
u64 Timing::Timer::GetTicks() const {
|
||||
u64 ticks = static_cast<u64>(executed_ticks);
|
||||
if (!is_timer_sane) {
|
||||
ticks += slice_length - downcount;
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void Timing::Timer::AddTicks(u64 ticks) {
|
||||
downcount -= static_cast<u64>(ticks * cpu_clock_scale);
|
||||
}
|
||||
|
||||
u64 Timing::Timer::GetIdleTicks() const {
|
||||
return static_cast<u64>(idled_cycles);
|
||||
}
|
||||
|
||||
void Timing::Timer::ForceExceptionCheck(s64 cycles) {
|
||||
cycles = std::max<s64>(0, cycles);
|
||||
if (downcount > cycles) {
|
||||
slice_length -= downcount - cycles;
|
||||
downcount = cycles;
|
||||
}
|
||||
}
|
||||
|
||||
void Timing::Timer::MoveEvents() {
|
||||
for (Event ev; ts_queue.Pop(ev);) {
|
||||
ev.fifo_order = event_fifo_id++;
|
||||
event_queue.emplace_back(std::move(ev));
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
s64 Timing::Timer::GetMaxSliceLength() const {
|
||||
const auto& next_event = event_queue.begin();
|
||||
if (next_event != event_queue.end()) {
|
||||
ASSERT(next_event->time - executed_ticks > 0);
|
||||
return next_event->time - executed_ticks;
|
||||
}
|
||||
return MAX_SLICE_LENGTH;
|
||||
}
|
||||
|
||||
void Timing::Timer::Advance() {
|
||||
MoveEvents();
|
||||
|
||||
s64 cycles_executed = slice_length - downcount;
|
||||
idled_cycles = 0;
|
||||
executed_ticks += cycles_executed;
|
||||
slice_length = 0;
|
||||
downcount = 0;
|
||||
|
||||
is_timer_sane = true;
|
||||
|
||||
while (!event_queue.empty() && event_queue.front().time <= executed_ticks) {
|
||||
Event evt = std::move(event_queue.front());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
event_queue.pop_back();
|
||||
if (evt.type->callback != nullptr) {
|
||||
evt.type->callback(evt.user_data, static_cast<int>(executed_ticks - evt.time));
|
||||
} else {
|
||||
LOG_ERROR(Core, "Event '{}' has no callback", *evt.type->name);
|
||||
}
|
||||
}
|
||||
|
||||
is_timer_sane = false;
|
||||
}
|
||||
|
||||
void Timing::Timer::SetNextSlice(s64 max_slice_length) {
|
||||
slice_length = max_slice_length;
|
||||
|
||||
// Still events left (scheduled in the future)
|
||||
if (!event_queue.empty()) {
|
||||
slice_length = static_cast<int>(
|
||||
std::min<s64>(event_queue.front().time - executed_ticks, max_slice_length));
|
||||
}
|
||||
|
||||
downcount = slice_length;
|
||||
}
|
||||
|
||||
void Timing::Timer::Idle() {
|
||||
idled_cycles += downcount;
|
||||
downcount = 0;
|
||||
}
|
||||
|
||||
s64 Timing::Timer::GetDowncount() const {
|
||||
return downcount;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
322
src/core/core_timing.h
Normal file
322
src/core/core_timing.h
Normal file
@@ -0,0 +1,322 @@
|
||||
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* This is a system to schedule events into the emulated machine's future. Time is measured
|
||||
* in main CPU clock cycles.
|
||||
*
|
||||
* To schedule an event, you first have to register its type. This is where you pass in the
|
||||
* callback. You then schedule events using the type id you get back.
|
||||
*
|
||||
* The int cyclesLate that the callbacks get is how many cycles late it was.
|
||||
* So to schedule a new event on a regular basis:
|
||||
* inside callback:
|
||||
* ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <boost/serialization/split_member.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "core/global.h"
|
||||
|
||||
// The timing we get from the assembly is 268,111,855.956 Hz
|
||||
// It is possible that this number isn't just an integer because the compiler could have
|
||||
// optimized the multiplication by a multiply-by-constant division.
|
||||
// Rounding to the nearest integer should be fine
|
||||
constexpr u64 BASE_CLOCK_RATE_ARM11 = 268111856;
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE_ARM11;
|
||||
|
||||
/// Refresh rate defined by ratio of ARM11 frequency to ARM11 ticks per frame
|
||||
/// (268,111,856) / (4,481,136) = 59.83122493939037Hz
|
||||
constexpr double SCREEN_REFRESH_RATE = BASE_CLOCK_RATE_ARM11 / static_cast<double>(4481136ull);
|
||||
|
||||
constexpr s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
return BASE_CLOCK_RATE_ARM11 * static_cast<s64>(ms) / 1000;
|
||||
}
|
||||
|
||||
constexpr s64 msToCycles(float ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE_ARM11 * (0.001f) * ms);
|
||||
}
|
||||
|
||||
constexpr s64 msToCycles(double ms) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE_ARM11 * (0.001) * ms);
|
||||
}
|
||||
|
||||
constexpr s64 usToCycles(float us) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE_ARM11 * (0.000001f) * us);
|
||||
}
|
||||
|
||||
constexpr s64 usToCycles(int us) {
|
||||
return (BASE_CLOCK_RATE_ARM11 * static_cast<s64>(us) / 1000000);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(s64 us) {
|
||||
if (us / 1000000 > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE_ARM11 * (us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE_ARM11 * us) / 1000000;
|
||||
}
|
||||
|
||||
inline s64 usToCycles(u64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE_ARM11 * static_cast<s64>(us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE_ARM11 * static_cast<s64>(us)) / 1000000;
|
||||
}
|
||||
|
||||
constexpr s64 nsToCycles(float ns) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE_ARM11 * (0.000000001f) * ns);
|
||||
}
|
||||
|
||||
constexpr s64 nsToCycles(int ns) {
|
||||
return BASE_CLOCK_RATE_ARM11 * static_cast<s64>(ns) / 1000000000;
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(s64 ns) {
|
||||
if (ns / 1000000000 > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE_ARM11 * (ns / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE_ARM11 * ns) / 1000000000;
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(u64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE_ARM11 * (static_cast<s64>(ns) / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE_ARM11 * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
constexpr u64 cyclesToNs(s64 cycles) {
|
||||
return cycles * 1000000000 / BASE_CLOCK_RATE_ARM11;
|
||||
}
|
||||
|
||||
constexpr s64 cyclesToUs(s64 cycles) {
|
||||
return cycles * 1000000 / BASE_CLOCK_RATE_ARM11;
|
||||
}
|
||||
|
||||
constexpr u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE_ARM11;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
using TimedCallback = std::function<void(std::uintptr_t user_data, int cycles_late)>;
|
||||
|
||||
struct TimingEventType {
|
||||
TimedCallback callback;
|
||||
const std::string* name;
|
||||
};
|
||||
|
||||
class Timing {
|
||||
|
||||
public:
|
||||
struct Event {
|
||||
s64 time;
|
||||
u64 fifo_order;
|
||||
std::uintptr_t user_data;
|
||||
const TimingEventType* type;
|
||||
|
||||
bool operator>(const Event& right) const;
|
||||
bool operator<(const Event& right) const;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
void save(Archive& ar, const unsigned int) const {
|
||||
ar& time;
|
||||
ar& fifo_order;
|
||||
ar& user_data;
|
||||
std::string name = *(type->name);
|
||||
ar << name;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void load(Archive& ar, const unsigned int) {
|
||||
ar& time;
|
||||
ar& fifo_order;
|
||||
ar& user_data;
|
||||
std::string name;
|
||||
ar >> name;
|
||||
type = Global<Timing>().RegisterEvent(name, nullptr);
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
};
|
||||
|
||||
// currently Service::HID::pad_update_ticks is the smallest interval for an event that gets
|
||||
// always scheduled. Therfore we use this as orientation for the MAX_SLICE_LENGTH
|
||||
// For performance bigger slice length are desired, though this will lead to cores desync
|
||||
// But we never want to schedule events into the current slice, because then cores might to
|
||||
// run small slices to sync up again. This is especially important for events that are always
|
||||
// scheduled and repated.
|
||||
static constexpr int MAX_SLICE_LENGTH = BASE_CLOCK_RATE_ARM11 / 234;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer(s64 base_ticks = 0);
|
||||
~Timer();
|
||||
|
||||
s64 GetMaxSliceLength() const;
|
||||
|
||||
void Advance();
|
||||
|
||||
void SetNextSlice(s64 max_slice_length = MAX_SLICE_LENGTH);
|
||||
|
||||
void Idle();
|
||||
|
||||
u64 GetTicks() const;
|
||||
u64 GetIdleTicks() const;
|
||||
|
||||
void AddTicks(u64 ticks);
|
||||
|
||||
s64 GetDowncount() const;
|
||||
|
||||
void ForceExceptionCheck(s64 cycles);
|
||||
|
||||
void MoveEvents();
|
||||
|
||||
private:
|
||||
friend class Timing;
|
||||
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
||||
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
|
||||
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
|
||||
// accommodated by the standard adaptor class.
|
||||
std::vector<Event> event_queue;
|
||||
u64 event_fifo_id = 0;
|
||||
// the queue for storing the events from other threads threadsafe until they will be added
|
||||
// to the event_queue by the emu thread
|
||||
Common::MPSCQueue<Event> ts_queue;
|
||||
// Are we in a function that has been called from Advance()
|
||||
// If events are sheduled from a function that gets called from Advance(),
|
||||
// don't change slice_length and downcount.
|
||||
// The time between CoreTiming being intialized and the first call to Advance() is
|
||||
// considered the slice boundary between slice -1 and slice 0. Dispatcher loops must call
|
||||
// Advance() before executing the first cycle of each slice to prepare the slice length and
|
||||
// downcount for that slice.
|
||||
bool is_timer_sane = true;
|
||||
|
||||
s64 slice_length = MAX_SLICE_LENGTH;
|
||||
s64 downcount = MAX_SLICE_LENGTH;
|
||||
s64 executed_ticks = 0;
|
||||
u64 idled_cycles = 0;
|
||||
|
||||
// Stores a scaling for the internal clockspeed. Changing this number results in
|
||||
// under/overclocking the guest cpu
|
||||
double cpu_clock_scale = 1.0;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
MoveEvents();
|
||||
ar& event_queue;
|
||||
ar& event_fifo_id;
|
||||
ar& slice_length;
|
||||
ar& downcount;
|
||||
ar& executed_ticks;
|
||||
ar& idled_cycles;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
explicit Timing(std::size_t num_cores, u32 cpu_clock_percentage, s64 override_base_ticks = -1);
|
||||
|
||||
~Timing(){};
|
||||
|
||||
/**
|
||||
* Returns the event_type identifier. if name is not unique, it will assert.
|
||||
*/
|
||||
TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback);
|
||||
|
||||
// Make sure to use thread_safe_mode = true if called from a different thread than the
|
||||
// emulator thread, such as coroutines.
|
||||
void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type,
|
||||
std::uintptr_t user_data = 0,
|
||||
std::size_t core_id = std::numeric_limits<std::size_t>::max(),
|
||||
bool thread_safe_mode = false);
|
||||
|
||||
void UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data);
|
||||
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const TimingEventType* event_type);
|
||||
|
||||
void SetCurrentTimer(std::size_t core_id);
|
||||
|
||||
s64 GetTicks() const;
|
||||
|
||||
s64 GetGlobalTicks() const;
|
||||
|
||||
/**
|
||||
* Updates the value of the cpu clock scaling to the new percentage.
|
||||
*/
|
||||
void UpdateClockSpeed(u32 cpu_clock_percentage);
|
||||
|
||||
std::chrono::microseconds GetGlobalTimeUs() const;
|
||||
|
||||
std::shared_ptr<Timer> GetTimer(std::size_t cpu_id);
|
||||
|
||||
// Used after deserializing to unprotect the event queue.
|
||||
void UnlockEventQueue() {
|
||||
event_queue_locked = false;
|
||||
}
|
||||
|
||||
/// Generates a random tick count to seed the system tick timer with.
|
||||
static s64 GenerateBaseTicks();
|
||||
|
||||
private:
|
||||
// unordered_map stores each element separately as a linked list node so pointers to
|
||||
// elements remain stable regardless of rehashes/resizing.
|
||||
std::unordered_map<std::string, TimingEventType> event_types = {};
|
||||
|
||||
std::vector<std::shared_ptr<Timer>> timers;
|
||||
Timer* current_timer = nullptr;
|
||||
|
||||
// When true, the event queue can't be modified. Used while deserializing to workaround
|
||||
// destructor side effects.
|
||||
bool event_queue_locked = false;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int file_version) {
|
||||
// event_types set during initialization of other things
|
||||
ar& timers;
|
||||
ar& current_timer;
|
||||
if (Archive::is_loading::value) {
|
||||
event_queue_locked = true;
|
||||
}
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
BOOST_CLASS_VERSION(Core::Timing, 1)
|
||||
17
src/core/dumping/backend.cpp
Normal file
17
src/core/dumping/backend.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "core/dumping/backend.h"
|
||||
|
||||
namespace VideoDumper {
|
||||
|
||||
VideoFrame::VideoFrame(std::size_t width_, std::size_t height_, u8* data_)
|
||||
: width(width_), height(height_), stride(static_cast<u32>(width * 4)),
|
||||
data(data_, data_ + width * height * 4) {}
|
||||
|
||||
Backend::~Backend() = default;
|
||||
NullBackend::~NullBackend() = default;
|
||||
|
||||
} // namespace VideoDumper
|
||||
58
src/core/dumping/backend.h
Normal file
58
src/core/dumping/backend.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "audio_core/audio_types.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
|
||||
namespace VideoDumper {
|
||||
/**
|
||||
* Frame dump data for a single screen
|
||||
* data is in RGB888 format, left to right then top to bottom
|
||||
*/
|
||||
class VideoFrame {
|
||||
public:
|
||||
std::size_t width;
|
||||
std::size_t height;
|
||||
u32 stride;
|
||||
std::vector<u8> data;
|
||||
|
||||
VideoFrame(std::size_t width_ = 0, std::size_t height_ = 0, u8* data_ = nullptr);
|
||||
};
|
||||
|
||||
class Backend {
|
||||
public:
|
||||
virtual ~Backend();
|
||||
virtual bool StartDumping(const std::string& path, const Layout::FramebufferLayout& layout) = 0;
|
||||
virtual void AddVideoFrame(VideoFrame frame) = 0;
|
||||
virtual void AddAudioFrame(AudioCore::StereoFrame16 frame) = 0;
|
||||
virtual void AddAudioSample(const std::array<s16, 2>& sample) = 0;
|
||||
virtual void StopDumping() = 0;
|
||||
virtual bool IsDumping() const = 0;
|
||||
virtual Layout::FramebufferLayout GetLayout() const = 0;
|
||||
};
|
||||
|
||||
class NullBackend : public Backend {
|
||||
public:
|
||||
~NullBackend() override;
|
||||
bool StartDumping(const std::string& /*path*/,
|
||||
const Layout::FramebufferLayout& /*layout*/) override {
|
||||
return false;
|
||||
}
|
||||
void AddVideoFrame(VideoFrame /*frame*/) override {}
|
||||
void AddAudioFrame(AudioCore::StereoFrame16 /*frame*/) override {}
|
||||
void AddAudioSample(const std::array<s16, 2>& /*sample*/) override {}
|
||||
void StopDumping() override {}
|
||||
bool IsDumping() const override {
|
||||
return false;
|
||||
}
|
||||
Layout::FramebufferLayout GetLayout() const override {
|
||||
return Layout::FramebufferLayout{};
|
||||
}
|
||||
};
|
||||
} // namespace VideoDumper
|
||||
1104
src/core/dumping/ffmpeg_backend.cpp
Normal file
1104
src/core/dumping/ffmpeg_backend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
261
src/core/dumping/ffmpeg_backend.h
Normal file
261
src/core/dumping/ffmpeg_backend.h
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/dynamic_library/ffmpeg.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "core/dumping/backend.h"
|
||||
|
||||
namespace VideoCore {
|
||||
class RendererBase;
|
||||
}
|
||||
|
||||
namespace VideoDumper {
|
||||
|
||||
using VariableAudioFrame = std::vector<s16>;
|
||||
|
||||
class FFmpegMuxer;
|
||||
|
||||
/**
|
||||
* Wrapper around FFmpeg AVCodecContext + AVStream.
|
||||
* Rescales/Resamples, encodes and writes a frame.
|
||||
*/
|
||||
class FFmpegStream {
|
||||
public:
|
||||
bool Init(FFmpegMuxer& muxer);
|
||||
void Free();
|
||||
void Flush();
|
||||
|
||||
protected:
|
||||
~FFmpegStream();
|
||||
|
||||
void WritePacket(AVPacket* packet);
|
||||
void SendFrame(AVFrame* frame);
|
||||
|
||||
struct AVCodecContextDeleter {
|
||||
void operator()(AVCodecContext* codec_context) const {
|
||||
DynamicLibrary::FFmpeg::avcodec_free_context(&codec_context);
|
||||
}
|
||||
};
|
||||
|
||||
struct AVFrameDeleter {
|
||||
void operator()(AVFrame* frame) const {
|
||||
DynamicLibrary::FFmpeg::av_frame_free(&frame);
|
||||
}
|
||||
};
|
||||
|
||||
struct AVPacketDeleter {
|
||||
void operator()(AVPacket* packet) const {
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
};
|
||||
|
||||
AVFormatContext* format_context{};
|
||||
std::mutex* format_context_mutex{};
|
||||
std::unique_ptr<AVCodecContext, AVCodecContextDeleter> codec_context{};
|
||||
AVStream* stream{};
|
||||
};
|
||||
|
||||
/**
|
||||
* A FFmpegStream used for video data.
|
||||
* Filters (scales), encodes and writes a frame.
|
||||
*/
|
||||
class FFmpegVideoStream : public FFmpegStream {
|
||||
public:
|
||||
~FFmpegVideoStream();
|
||||
|
||||
bool Init(FFmpegMuxer& muxer, const Layout::FramebufferLayout& layout);
|
||||
void Free();
|
||||
void ProcessFrame(VideoFrame& frame);
|
||||
|
||||
private:
|
||||
bool InitHWContext(const AVCodec* codec);
|
||||
bool InitFilters();
|
||||
|
||||
u64 frame_count{};
|
||||
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter> current_frame{};
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter> filtered_frame{};
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter> hw_frame{};
|
||||
Layout::FramebufferLayout layout;
|
||||
|
||||
/// The pixel format the input frames are stored in
|
||||
static constexpr AVPixelFormat pixel_format = AVPixelFormat::AV_PIX_FMT_BGRA;
|
||||
|
||||
// Software pixel format. For normal encoders, this is the format they accept. For HW-acceled
|
||||
// encoders, this is the format the HW frames context accepts.
|
||||
AVPixelFormat sw_pixel_format = AV_PIX_FMT_NONE;
|
||||
|
||||
/// Whether the encoder we are using requires HW frames to be supplied.
|
||||
bool requires_hw_frames = false;
|
||||
|
||||
// Filter related
|
||||
struct AVFilterGraphDeleter {
|
||||
void operator()(AVFilterGraph* filter_graph) const {
|
||||
DynamicLibrary::FFmpeg::avfilter_graph_free(&filter_graph);
|
||||
}
|
||||
};
|
||||
std::unique_ptr<AVFilterGraph, AVFilterGraphDeleter> filter_graph{};
|
||||
// These don't need to be freed apparently
|
||||
AVFilterContext* source_context;
|
||||
AVFilterContext* sink_context;
|
||||
|
||||
/// The filter graph to use. This graph means 'change FPS to 60, convert format if needed'
|
||||
static constexpr std::string_view filter_graph_desc = "fps=60";
|
||||
};
|
||||
|
||||
/**
|
||||
* A FFmpegStream used for audio data.
|
||||
* Resamples (converts), encodes and writes a frame.
|
||||
* This also temporarily stores resampled audio data before there are enough to form a frame.
|
||||
*/
|
||||
class FFmpegAudioStream : public FFmpegStream {
|
||||
public:
|
||||
~FFmpegAudioStream();
|
||||
|
||||
bool Init(FFmpegMuxer& muxer);
|
||||
void Free();
|
||||
void ProcessFrame(const VariableAudioFrame& channel0, const VariableAudioFrame& channel1);
|
||||
void Flush();
|
||||
|
||||
private:
|
||||
struct SwrContextDeleter {
|
||||
void operator()(SwrContext* swr_context) const {
|
||||
DynamicLibrary::FFmpeg::swr_free(&swr_context);
|
||||
}
|
||||
};
|
||||
|
||||
int frame_size{};
|
||||
u64 frame_count{};
|
||||
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter> audio_frame{};
|
||||
std::unique_ptr<SwrContext, SwrContextDeleter> swr_context{};
|
||||
|
||||
u8** resampled_data{};
|
||||
int offset{}; // Number of output samples that are currently in resampled_data.
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper around FFmpeg AVFormatContext.
|
||||
* Manages the video and audio streams, and accepts video and audio data.
|
||||
*/
|
||||
class FFmpegMuxer {
|
||||
public:
|
||||
~FFmpegMuxer();
|
||||
|
||||
bool Init(const std::string& path, const Layout::FramebufferLayout& layout);
|
||||
void Free();
|
||||
void ProcessVideoFrame(VideoFrame& frame);
|
||||
void ProcessAudioFrame(const VariableAudioFrame& channel0, const VariableAudioFrame& channel1);
|
||||
void FlushVideo();
|
||||
void FlushAudio();
|
||||
void WriteTrailer();
|
||||
|
||||
private:
|
||||
struct AVFormatContextDeleter {
|
||||
void operator()(AVFormatContext* format_context) const {
|
||||
DynamicLibrary::FFmpeg::avio_closep(&format_context->pb);
|
||||
DynamicLibrary::FFmpeg::avformat_free_context(format_context);
|
||||
}
|
||||
};
|
||||
|
||||
FFmpegAudioStream audio_stream{};
|
||||
FFmpegVideoStream video_stream{};
|
||||
std::unique_ptr<AVFormatContext, AVFormatContextDeleter> format_context{};
|
||||
std::mutex format_context_mutex;
|
||||
|
||||
friend class FFmpegStream;
|
||||
};
|
||||
|
||||
/**
|
||||
* FFmpeg video dumping backend.
|
||||
* This class implements a double buffer.
|
||||
*/
|
||||
class FFmpegBackend : public Backend {
|
||||
public:
|
||||
FFmpegBackend(VideoCore::RendererBase& renderer);
|
||||
~FFmpegBackend() override;
|
||||
bool StartDumping(const std::string& path, const Layout::FramebufferLayout& layout) override;
|
||||
void AddVideoFrame(VideoFrame frame) override;
|
||||
void AddAudioFrame(AudioCore::StereoFrame16 frame) override;
|
||||
void AddAudioSample(const std::array<s16, 2>& sample) override;
|
||||
void StopDumping() override;
|
||||
bool IsDumping() const override;
|
||||
Layout::FramebufferLayout GetLayout() const override;
|
||||
|
||||
private:
|
||||
void EndDumping();
|
||||
|
||||
VideoCore::RendererBase& renderer;
|
||||
std::atomic_bool is_dumping = false; ///< Whether the backend is currently dumping
|
||||
|
||||
FFmpegMuxer ffmpeg{};
|
||||
|
||||
Layout::FramebufferLayout video_layout;
|
||||
std::array<VideoFrame, 2> video_frame_buffers;
|
||||
u32 current_buffer = 0, next_buffer = 1;
|
||||
Common::Event event1, event2;
|
||||
std::thread video_processing_thread;
|
||||
|
||||
std::array<Common::SPSCQueue<VariableAudioFrame>, 2> audio_frame_queues;
|
||||
std::thread audio_processing_thread;
|
||||
|
||||
Common::Event processing_ended;
|
||||
};
|
||||
|
||||
/// Struct describing encoder/muxer options
|
||||
struct OptionInfo {
|
||||
std::string name;
|
||||
std::string description;
|
||||
AVOptionType type;
|
||||
std::string default_value;
|
||||
struct NamedConstant {
|
||||
std::string name;
|
||||
std::string description;
|
||||
s64 value;
|
||||
};
|
||||
std::vector<NamedConstant> named_constants;
|
||||
|
||||
// If this is a scalar type
|
||||
double min;
|
||||
double max;
|
||||
};
|
||||
|
||||
/// Struct describing an encoder
|
||||
struct EncoderInfo {
|
||||
std::string name;
|
||||
std::string long_name;
|
||||
AVCodecID codec;
|
||||
std::vector<OptionInfo> options;
|
||||
};
|
||||
|
||||
/// Struct describing a format
|
||||
struct FormatInfo {
|
||||
std::string name;
|
||||
std::string long_name;
|
||||
std::vector<std::string> extensions;
|
||||
std::set<AVCodecID> supported_video_codecs;
|
||||
std::set<AVCodecID> supported_audio_codecs;
|
||||
std::vector<OptionInfo> options;
|
||||
};
|
||||
|
||||
std::vector<EncoderInfo> ListEncoders(AVMediaType type);
|
||||
std::vector<OptionInfo> GetEncoderGenericOptions();
|
||||
std::vector<FormatInfo> ListFormats();
|
||||
std::vector<OptionInfo> GetFormatGenericOptions();
|
||||
std::vector<std::string> GetPixelFormats();
|
||||
std::vector<std::string> GetSampleFormats();
|
||||
|
||||
} // namespace VideoDumper
|
||||
557
src/core/file_sys/archive_artic.cpp
Normal file
557
src/core/file_sys/archive_artic.cpp
Normal file
@@ -0,0 +1,557 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "archive_artic.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
std::vector<u8> ArticArchive::BuildFSPath(const Path& path) {
|
||||
std::vector<u8> ret(sizeof(u32) * 2);
|
||||
u32* raw_data = reinterpret_cast<u32*>(ret.data());
|
||||
auto path_type = path.GetType();
|
||||
auto binary = path.AsBinary();
|
||||
raw_data[0] = static_cast<u32>(path_type);
|
||||
raw_data[1] = static_cast<u32>(binary.size());
|
||||
if (!binary.empty()) {
|
||||
ret.insert(ret.end(), binary.begin(), binary.end());
|
||||
}
|
||||
|
||||
// The insert may have invalidated the pointer
|
||||
raw_data = reinterpret_cast<u32*>(ret.data());
|
||||
if (path_type != LowPathType::Binary && path_type != LowPathType::Invalid) {
|
||||
if (path_type == LowPathType::Wchar) {
|
||||
raw_data[1] += 2;
|
||||
ret.push_back(0);
|
||||
ret.push_back(0);
|
||||
} else {
|
||||
raw_data[1] += 1;
|
||||
ret.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result ArticArchive::RespResult(const std::optional<Network::ArticBase::Client::Response>& resp) {
|
||||
if (!resp.has_value() || !resp->Succeeded()) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
return Result(static_cast<u32>(resp->GetMethodResult()));
|
||||
}
|
||||
|
||||
ArticArchive::~ArticArchive() {
|
||||
if (clear_cache_on_close) {
|
||||
cache_provider->ClearAllCache();
|
||||
}
|
||||
if (archive_handle != -1) {
|
||||
auto req = client->NewRequest("FSUSER_CloseArchive");
|
||||
req.AddParameterS64(archive_handle);
|
||||
client->Send(req);
|
||||
if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArticArchive::Open(
|
||||
std::shared_ptr<Network::ArticBase::Client>& client, Service::FS::ArchiveIdCode archive_id,
|
||||
const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event,
|
||||
ArticCacheProvider& cache_provider, bool clear_cache_on_close) {
|
||||
|
||||
auto req = client->NewRequest("FSUSER_OpenArchive");
|
||||
|
||||
req.AddParameterS32(static_cast<s32>(archive_id));
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded()) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
Result res(static_cast<u32>(resp->GetMethodResult()));
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto handle_opt = resp->GetResponseS64(0);
|
||||
if (!handle_opt.has_value()) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
return std::make_unique<ArticArchive>(client, *handle_opt, report_artic_event, cache_provider,
|
||||
path, clear_cache_on_close);
|
||||
}
|
||||
|
||||
void ArticArchive::Close() {
|
||||
if (clear_cache_on_close) {
|
||||
cache_provider->ClearAllCache();
|
||||
}
|
||||
|
||||
auto req = client->NewRequest("FSUSER_CloseArchive");
|
||||
req.AddParameterS64(archive_handle);
|
||||
if (RespResult(client->Send(req)).IsSuccess()) {
|
||||
archive_handle = -1;
|
||||
if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ArticArchive::GetName() const {
|
||||
return "ArticArchive";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> ArticArchive::OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) {
|
||||
if (mode.create_flag) {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, path), false);
|
||||
if (cache != nullptr) {
|
||||
cache->Clear();
|
||||
}
|
||||
}
|
||||
auto req = client->NewRequest("FSUSER_OpenFile");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
req.AddParameterU32(mode.hex);
|
||||
req.AddParameterU32(attributes);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = RespResult(resp);
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto handle_opt = resp->GetResponseS32(0);
|
||||
if (!handle_opt.has_value())
|
||||
return ResultUnknown;
|
||||
|
||||
auto size_opt = resp->GetResponseU64(1);
|
||||
if (size_opt.has_value()) {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, path), true);
|
||||
if (cache != nullptr) {
|
||||
cache->ForceSetSize(static_cast<size_t>(*size_opt));
|
||||
}
|
||||
}
|
||||
|
||||
if (open_reporter->open_files++ == 0 &&
|
||||
report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||
client->ReportArticEvent(static_cast<u64>(report_artic_event) | (1ULL << 32));
|
||||
}
|
||||
|
||||
return std::make_unique<ArticFileBackend>(client, *handle_opt, open_reporter, archive_path,
|
||||
*cache_provider, path);
|
||||
}
|
||||
|
||||
Result ArticArchive::DeleteFile(const Path& path) const {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, path), false);
|
||||
if (cache != nullptr) {
|
||||
cache->Clear();
|
||||
}
|
||||
|
||||
auto req = client->NewRequest("FSUSER_DeleteFile");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
Result ArticArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, src_path), false);
|
||||
if (cache != nullptr) {
|
||||
cache->Clear();
|
||||
}
|
||||
cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, dest_path), false);
|
||||
if (cache != nullptr) {
|
||||
cache->Clear();
|
||||
}
|
||||
|
||||
auto req = client->NewRequest("FSUSER_RenameFile");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto src_path_buf = BuildFSPath(src_path);
|
||||
req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size());
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto dest_path_buf = BuildFSPath(dest_path);
|
||||
req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size());
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
Result ArticArchive::DeleteDirectory(const Path& path) const {
|
||||
cache_provider->ClearAllCache();
|
||||
|
||||
auto req = client->NewRequest("FSUSER_DeleteDirectory");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
Result ArticArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||
cache_provider->ClearAllCache();
|
||||
|
||||
auto req = client->NewRequest("FSUSER_DeleteDirectoryRec");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
Result ArticArchive::CreateFile(const Path& path, u64 size, u32 attributes) const {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, path), false);
|
||||
if (cache != nullptr) {
|
||||
cache->Clear();
|
||||
}
|
||||
|
||||
auto req = client->NewRequest("FSUSER_CreateFile");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
req.AddParameterU32(attributes);
|
||||
req.AddParameterU64(size);
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
Result ArticArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||
auto req = client->NewRequest("FSUSER_CreateDirectory");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
req.AddParameterU32(attributes);
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
Result ArticArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
cache_provider->ClearAllCache();
|
||||
|
||||
auto req = client->NewRequest("FSUSER_RenameDirectory");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto src_path_buf = BuildFSPath(src_path);
|
||||
req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size());
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto dest_path_buf = BuildFSPath(dest_path);
|
||||
req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size());
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> ArticArchive::OpenDirectory(const Path& path) {
|
||||
auto req = client->NewRequest("FSUSER_OpenDirectory");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
auto path_buf = BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = RespResult(resp);
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto handle_opt = resp->GetResponseS32(0);
|
||||
if (!handle_opt.has_value())
|
||||
return ResultUnknown;
|
||||
|
||||
if (open_reporter->open_files++ == 0 &&
|
||||
report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||
client->ReportArticEvent(static_cast<u64>(report_artic_event) | (1ULL << 32));
|
||||
}
|
||||
|
||||
return std::make_unique<ArticDirectoryBackend>(client, *handle_opt, archive_path,
|
||||
open_reporter);
|
||||
}
|
||||
|
||||
u64 ArticArchive::GetFreeBytes() const {
|
||||
auto req = client->NewRequest("FSUSER_GetFreeBytes");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = RespResult(resp);
|
||||
if (res.IsError()) // TODO(PabloMK7): Return error code and not u64
|
||||
return 0;
|
||||
|
||||
auto free_bytes_opt = resp->GetResponseS64(0);
|
||||
return free_bytes_opt.has_value() ? static_cast<u64>(*free_bytes_opt) : 0;
|
||||
}
|
||||
|
||||
Result ArticArchive::Control(u32 action, u8* input, size_t input_size, u8* output,
|
||||
size_t output_size) {
|
||||
auto req = client->NewRequest("FSUSER_ControlArchive");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
req.AddParameterU32(action);
|
||||
req.AddParameterBuffer(input, input_size);
|
||||
req.AddParameterU32(static_cast<u32>(output_size));
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = RespResult(resp);
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto output_buf = resp->GetResponseBuffer(0);
|
||||
if (!output_buf.has_value())
|
||||
return res;
|
||||
|
||||
if (output_buf->second != output_size)
|
||||
return ResultUnknown;
|
||||
|
||||
memcpy(output, output_buf->first, output_buf->second);
|
||||
return res;
|
||||
}
|
||||
|
||||
Result ArticArchive::SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) {
|
||||
auto req = client->NewRequest("FSUSER_SetSaveDataSecureValue");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
req.AddParameterU32(secure_value_slot);
|
||||
req.AddParameterU64(secure_value);
|
||||
req.AddParameterS8(flush != 0);
|
||||
|
||||
return RespResult(client->Send(req));
|
||||
}
|
||||
|
||||
ResultVal<std::tuple<bool, bool, u64>> ArticArchive::GetSaveDataSecureValue(u32 secure_value_slot) {
|
||||
auto req = client->NewRequest("FSUSER_GetSaveDataSecureValue");
|
||||
|
||||
req.AddParameterS64(archive_handle);
|
||||
req.AddParameterU32(secure_value_slot);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = RespResult(resp);
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
struct {
|
||||
bool exists;
|
||||
bool isGamecard;
|
||||
u64 secure_value;
|
||||
} secure_value_result;
|
||||
static_assert(sizeof(secure_value_result) == 0x10);
|
||||
|
||||
auto output_buf = resp->GetResponseBuffer(0);
|
||||
if (!output_buf.has_value())
|
||||
return res;
|
||||
|
||||
if (output_buf->second != sizeof(secure_value_result))
|
||||
return ResultUnknown;
|
||||
|
||||
memcpy(&secure_value_result, output_buf->first, output_buf->second);
|
||||
return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard,
|
||||
secure_value_result.secure_value);
|
||||
}
|
||||
|
||||
void ArticArchive::OpenFileReporter::OnFileClosed() {
|
||||
if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||
}
|
||||
}
|
||||
|
||||
void ArticArchive::OpenFileReporter::OnDirectoryClosed() {
|
||||
if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||
}
|
||||
}
|
||||
|
||||
ArticFileBackend::~ArticFileBackend() {
|
||||
if (file_handle != -1) {
|
||||
auto req = client->NewRequest("FSFILE_Close");
|
||||
req.AddParameterS32(file_handle);
|
||||
client->Send(req);
|
||||
open_reporter->OnFileClosed();
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> ArticFileBackend::Read(u64 offset, std::size_t length, u8* buffer) const {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||
|
||||
if (cache != nullptr && (offset + static_cast<u64>(length)) < GetSize()) {
|
||||
return cache->Read(file_handle, offset, length, buffer);
|
||||
}
|
||||
|
||||
size_t read_amount = 0;
|
||||
while (read_amount != length) {
|
||||
size_t to_read =
|
||||
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, length - read_amount);
|
||||
|
||||
auto req = client->NewRequest("FSFILE_Read");
|
||||
req.AddParameterS32(file_handle);
|
||||
req.AddParameterS64(static_cast<s64>(offset + read_amount));
|
||||
req.AddParameterS32(static_cast<s32>(to_read));
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded())
|
||||
return Result(-1);
|
||||
|
||||
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto read_buff = resp->GetResponseBuffer(0);
|
||||
size_t actually_read = 0;
|
||||
if (read_buff.has_value()) {
|
||||
actually_read = read_buff->second;
|
||||
memcpy(buffer + read_amount, read_buff->first, actually_read);
|
||||
}
|
||||
|
||||
read_amount += actually_read;
|
||||
if (actually_read != to_read)
|
||||
break;
|
||||
}
|
||||
return read_amount;
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> ArticFileBackend::Write(u64 offset, std::size_t length, bool flush,
|
||||
bool update_timestamp, const u8* buffer) {
|
||||
u32 flags = (flush ? 1 : 0) | (update_timestamp ? (1 << 8) : 0);
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||
if (cache != nullptr) {
|
||||
return cache->Write(file_handle, offset, length, buffer, flags);
|
||||
} else {
|
||||
size_t written_amount = 0;
|
||||
while (written_amount != length) {
|
||||
size_t to_write = std::min<size_t>(client->GetServerRequestMaxSize() - 0x100,
|
||||
length - written_amount);
|
||||
|
||||
auto req = client->NewRequest("FSFILE_Write");
|
||||
req.AddParameterS32(file_handle);
|
||||
req.AddParameterS64(static_cast<s64>(offset + written_amount));
|
||||
req.AddParameterS32(static_cast<s32>(to_write));
|
||||
req.AddParameterS32(static_cast<s32>(flags));
|
||||
req.AddParameterBuffer(buffer + written_amount, to_write);
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded())
|
||||
return Result(-1);
|
||||
|
||||
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto actually_written_opt = resp->GetResponseS32(0);
|
||||
if (!actually_written_opt.has_value())
|
||||
return Result(-1);
|
||||
|
||||
size_t actually_written = static_cast<size_t>(actually_written_opt.value());
|
||||
|
||||
written_amount += actually_written;
|
||||
if (actually_written != to_write)
|
||||
break;
|
||||
}
|
||||
return written_amount;
|
||||
}
|
||||
}
|
||||
|
||||
u64 ArticFileBackend::GetSize() const {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||
if (cache != nullptr) {
|
||||
auto res = cache->GetSize(file_handle);
|
||||
if (res.Failed())
|
||||
return 0;
|
||||
return res.Unwrap();
|
||||
} else {
|
||||
|
||||
auto req = client->NewRequest("FSFILE_GetSize");
|
||||
|
||||
req.AddParameterS32(file_handle);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = ArticArchive::RespResult(resp);
|
||||
if (res.IsError())
|
||||
return 0;
|
||||
|
||||
auto size_buf = resp->GetResponseS64(0);
|
||||
if (!size_buf) {
|
||||
return 0;
|
||||
}
|
||||
return *size_buf;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArticFileBackend::SetSize(u64 size) const {
|
||||
auto req = client->NewRequest("FSFILE_SetSize");
|
||||
|
||||
req.AddParameterS32(file_handle);
|
||||
req.AddParameterU64(size);
|
||||
|
||||
return ArticArchive::RespResult(client->Send(req)).IsSuccess();
|
||||
}
|
||||
|
||||
bool ArticFileBackend::Close() {
|
||||
auto req = client->NewRequest("FSFILE_Close");
|
||||
req.AddParameterS32(file_handle);
|
||||
bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess();
|
||||
if (ret) {
|
||||
file_handle = -1;
|
||||
open_reporter->OnFileClosed();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ArticFileBackend::Flush() const {
|
||||
auto req = client->NewRequest("FSFILE_Flush");
|
||||
|
||||
req.AddParameterS32(file_handle);
|
||||
|
||||
client->Send(req);
|
||||
}
|
||||
|
||||
ArticDirectoryBackend::~ArticDirectoryBackend() {
|
||||
if (dir_handle != -1) {
|
||||
auto req = client->NewRequest("FSDIR_Close");
|
||||
req.AddParameterS32(dir_handle);
|
||||
client->Send(req);
|
||||
open_reporter->OnDirectoryClosed();
|
||||
}
|
||||
}
|
||||
|
||||
u32 ArticDirectoryBackend::Read(const u32 count, Entry* entries) {
|
||||
auto req = client->NewRequest("FSDIR_Read");
|
||||
|
||||
req.AddParameterS32(dir_handle);
|
||||
req.AddParameterU32(count);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
auto res = ArticArchive::RespResult(resp);
|
||||
if (res.IsError())
|
||||
return 0;
|
||||
|
||||
auto entry_buf = resp->GetResponseBuffer(0);
|
||||
if (!entry_buf) {
|
||||
return 0;
|
||||
}
|
||||
u32 ret_count = static_cast<u32>(entry_buf->second / sizeof(Entry));
|
||||
|
||||
memcpy(entries, entry_buf->first, ret_count * sizeof(Entry));
|
||||
return ret_count;
|
||||
}
|
||||
|
||||
bool ArticDirectoryBackend::Close() {
|
||||
auto req = client->NewRequest("FSDIR_Close");
|
||||
req.AddParameterS32(dir_handle);
|
||||
bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess();
|
||||
if (ret) {
|
||||
dir_handle = -1;
|
||||
open_reporter->OnDirectoryClosed();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} // namespace FileSys
|
||||
268
src/core/file_sys/archive_artic.h
Normal file
268
src/core/file_sys/archive_artic.h
Normal file
@@ -0,0 +1,268 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "atomic"
|
||||
|
||||
#include <boost/serialization/unique_ptr.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/artic_cache.h"
|
||||
#include "core/file_sys/directory_backend.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ArticArchive : public ArchiveBackend {
|
||||
public:
|
||||
static std::vector<u8> BuildFSPath(const Path& path);
|
||||
static Result RespResult(const std::optional<Network::ArticBase::Client::Response>& resp);
|
||||
|
||||
explicit ArticArchive(std::shared_ptr<Network::ArticBase::Client>& _client, s64 _archive_handle,
|
||||
Core::PerfStats::PerfArticEventBits _report_artic_event,
|
||||
ArticCacheProvider& _cache_provider, const Path& _archive_path,
|
||||
bool _clear_cache_on_close)
|
||||
: client(_client), archive_handle(_archive_handle), report_artic_event(_report_artic_event),
|
||||
cache_provider(&_cache_provider), archive_path(_archive_path),
|
||||
clear_cache_on_close(_clear_cache_on_close) {
|
||||
open_reporter = std::make_shared<OpenFileReporter>(_client, _report_artic_event);
|
||||
}
|
||||
~ArticArchive() override;
|
||||
|
||||
static ResultVal<std::unique_ptr<ArchiveBackend>> Open(
|
||||
std::shared_ptr<Network::ArticBase::Client>& client, Service::FS::ArchiveIdCode archive_id,
|
||||
const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event,
|
||||
ArticCacheProvider& cache_provider, bool clear_cache_on_close);
|
||||
|
||||
void Close() override;
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
std::string GetName() const override;
|
||||
|
||||
/**
|
||||
* Open a file specified by its path, using the specified mode
|
||||
* @param path Path relative to the archive
|
||||
* @param mode Mode to open the file with
|
||||
* @return Opened file, or error code
|
||||
*/
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) override;
|
||||
|
||||
/**
|
||||
* Delete a file specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result DeleteFile(const Path& path) const override;
|
||||
|
||||
/**
|
||||
* Rename a File specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result DeleteDirectory(const Path& path) const override;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path and anything under it
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||
|
||||
/**
|
||||
* Create a file specified by its path
|
||||
* @param path Path relative to the Archive
|
||||
* @param size The size of the new file, filled with zeroes
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||
|
||||
/**
|
||||
* Create a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||
|
||||
/**
|
||||
* Rename a Directory specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
|
||||
/**
|
||||
* Open a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Opened directory, or error code
|
||||
*/
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||
|
||||
/**
|
||||
* Get the free space
|
||||
* @return The number of free bytes in the archive
|
||||
*/
|
||||
u64 GetFreeBytes() const override;
|
||||
|
||||
Result Control(u32 action, u8* input, size_t input_size, u8* output,
|
||||
size_t output_size) override;
|
||||
|
||||
Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) override;
|
||||
|
||||
ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(u32 secure_value_slot) override;
|
||||
|
||||
bool IsSlow() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Path& GetArchivePath() {
|
||||
return archive_path;
|
||||
}
|
||||
|
||||
protected:
|
||||
ArticArchive() = default;
|
||||
|
||||
private:
|
||||
friend class ArticFileBackend;
|
||||
friend class ArticDirectoryBackend;
|
||||
class OpenFileReporter {
|
||||
public:
|
||||
OpenFileReporter(const std::shared_ptr<Network::ArticBase::Client>& cli,
|
||||
Core::PerfStats::PerfArticEventBits _report_artic_event)
|
||||
: client(cli), report_artic_event(_report_artic_event) {}
|
||||
|
||||
void OnFileClosed();
|
||||
|
||||
void OnDirectoryClosed();
|
||||
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
Core::PerfStats::PerfArticEventBits report_artic_event =
|
||||
Core::PerfStats::PerfArticEventBits::NONE;
|
||||
std::atomic<u32> open_files = 0;
|
||||
};
|
||||
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
s64 archive_handle;
|
||||
std::shared_ptr<OpenFileReporter> open_reporter;
|
||||
Core::PerfStats::PerfArticEventBits report_artic_event =
|
||||
Core::PerfStats::PerfArticEventBits::NONE;
|
||||
ArticCacheProvider* cache_provider = nullptr;
|
||||
Path archive_path;
|
||||
bool clear_cache_on_close;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveBackend>(*this);
|
||||
ar& archive_handle;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class ArticFileBackend : public FileBackend {
|
||||
public:
|
||||
explicit ArticFileBackend(std::shared_ptr<Network::ArticBase::Client>& _client,
|
||||
s32 _file_handle,
|
||||
const std::shared_ptr<ArticArchive::OpenFileReporter>& _open_reporter,
|
||||
const Path& _archive_path, ArticCacheProvider& _cache_provider,
|
||||
const Path& _file_path)
|
||||
: client(_client), file_handle(_file_handle), open_reporter(_open_reporter),
|
||||
archive_path(_archive_path), cache_provider(&_cache_provider), file_path(_file_path) {}
|
||||
~ArticFileBackend() override;
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override;
|
||||
|
||||
u64 GetSize() const override;
|
||||
|
||||
bool SetSize(u64 size) const override;
|
||||
|
||||
bool Close() override;
|
||||
|
||||
void Flush() const override;
|
||||
|
||||
bool AllowsCachedReads() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
||||
auto cache = cache_provider->ProvideCache(
|
||||
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||
if (cache == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return cache->CacheReady(file_offset, length);
|
||||
}
|
||||
|
||||
protected:
|
||||
ArticFileBackend() = default;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
s32 file_handle;
|
||||
std::shared_ptr<ArticArchive::OpenFileReporter> open_reporter;
|
||||
Path archive_path;
|
||||
ArticCacheProvider* cache_provider = nullptr;
|
||||
Path file_path;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||
ar& file_handle;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class ArticDirectoryBackend : public DirectoryBackend {
|
||||
public:
|
||||
explicit ArticDirectoryBackend(
|
||||
std::shared_ptr<Network::ArticBase::Client>& _client, s32 _dir_handle,
|
||||
const Path& _archive_path,
|
||||
const std::shared_ptr<ArticArchive::OpenFileReporter>& _open_reporter)
|
||||
: client(_client), dir_handle(_dir_handle), archive_path(_archive_path),
|
||||
open_reporter(_open_reporter) {}
|
||||
~ArticDirectoryBackend() override;
|
||||
|
||||
u32 Read(const u32 count, Entry* entries) override;
|
||||
bool Close() override;
|
||||
|
||||
bool IsSlow() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
ArticDirectoryBackend() = default;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
s32 dir_handle;
|
||||
Path archive_path;
|
||||
std::shared_ptr<ArticArchive::OpenFileReporter> open_reporter;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<DirectoryBackend>(*this);
|
||||
ar& dir_handle;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArticArchive)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArticFileBackend)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArticDirectoryBackend)
|
||||
121
src/core/file_sys/archive_backend.cpp
Normal file
121
src/core/file_sys/archive_backend.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
Path::Path(LowPathType type, std::vector<u8> data) : type(type) {
|
||||
switch (type) {
|
||||
case LowPathType::Binary: {
|
||||
binary = std::move(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case LowPathType::Char: {
|
||||
string.resize(data.size() - 1); // Data is always null-terminated.
|
||||
std::memcpy(string.data(), data.data(), string.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case LowPathType::Wchar: {
|
||||
u16str.resize(data.size() / 2 - 1); // Data is always null-terminated.
|
||||
std::memcpy(u16str.data(), data.data(), u16str.size() * sizeof(char16_t));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Path::DebugStr() const {
|
||||
switch (GetType()) {
|
||||
case LowPathType::Invalid:
|
||||
default:
|
||||
return "[Invalid]";
|
||||
case LowPathType::Empty:
|
||||
return "[Empty]";
|
||||
case LowPathType::Binary: {
|
||||
std::stringstream res;
|
||||
res << "[Binary: ";
|
||||
for (unsigned byte : binary)
|
||||
res << std::hex << std::setw(2) << std::setfill('0') << byte;
|
||||
res << ']';
|
||||
return res.str();
|
||||
}
|
||||
case LowPathType::Char:
|
||||
return "[Char: " + AsString() + ']';
|
||||
case LowPathType::Wchar:
|
||||
return "[Wchar: " + AsString() + ']';
|
||||
}
|
||||
}
|
||||
|
||||
std::string Path::AsString() const {
|
||||
switch (GetType()) {
|
||||
case LowPathType::Char:
|
||||
return string;
|
||||
case LowPathType::Wchar:
|
||||
return Common::UTF16ToUTF8(u16str);
|
||||
case LowPathType::Empty:
|
||||
return {};
|
||||
case LowPathType::Invalid:
|
||||
case LowPathType::Binary:
|
||||
default:
|
||||
// TODO(yuriks): Add assert
|
||||
LOG_ERROR(Service_FS, "LowPathType cannot be converted to string!");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string Path::AsU16Str() const {
|
||||
switch (GetType()) {
|
||||
case LowPathType::Char:
|
||||
return Common::UTF8ToUTF16(string);
|
||||
case LowPathType::Wchar:
|
||||
return u16str;
|
||||
case LowPathType::Empty:
|
||||
return {};
|
||||
case LowPathType::Invalid:
|
||||
case LowPathType::Binary:
|
||||
default:
|
||||
// TODO(yuriks): Add assert
|
||||
LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
|
||||
return {};
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::vector<u8> Path::AsBinary() const {
|
||||
switch (GetType()) {
|
||||
case LowPathType::Binary:
|
||||
return binary;
|
||||
case LowPathType::Char:
|
||||
return std::vector<u8>(string.begin(), string.end());
|
||||
case LowPathType::Wchar: {
|
||||
// use two u8 for each character of u16str
|
||||
std::vector<u8> to_return(u16str.size() * 2);
|
||||
for (std::size_t i = 0; i < u16str.size(); ++i) {
|
||||
u16 tmp_char = u16str.at(i);
|
||||
*reinterpret_cast<u16*>(to_return.data() + i * 2) = tmp_char;
|
||||
}
|
||||
return to_return;
|
||||
}
|
||||
case LowPathType::Empty:
|
||||
return {};
|
||||
case LowPathType::Invalid:
|
||||
default:
|
||||
// TODO(yuriks): Add assert
|
||||
LOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
294
src/core/file_sys/archive_backend.h
Normal file
294
src/core/file_sys/archive_backend.h
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <boost/serialization/string.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/delay_generator.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class FileBackend;
|
||||
class DirectoryBackend;
|
||||
|
||||
// Path string type
|
||||
enum class LowPathType : u32 {
|
||||
Invalid = 0,
|
||||
Empty = 1,
|
||||
Binary = 2,
|
||||
Char = 3,
|
||||
Wchar = 4,
|
||||
};
|
||||
|
||||
union Mode {
|
||||
u32 hex = 0;
|
||||
BitField<0, 1, u32> read_flag;
|
||||
BitField<1, 1, u32> write_flag;
|
||||
BitField<2, 1, u32> create_flag;
|
||||
};
|
||||
|
||||
class Path {
|
||||
public:
|
||||
Path() : type(LowPathType::Invalid) {}
|
||||
Path(const char* path) : type(LowPathType::Char), string(path) {}
|
||||
Path(std::string path) : type(LowPathType::Char), string(std::move(path)) {}
|
||||
Path(std::vector<u8> binary_data) : type(LowPathType::Binary), binary(std::move(binary_data)) {}
|
||||
template <std::size_t size>
|
||||
Path(const std::array<u8, size>& binary_data)
|
||||
: type(LowPathType::Binary), binary(binary_data.begin(), binary_data.end()) {}
|
||||
Path(LowPathType type, std::vector<u8> data);
|
||||
|
||||
LowPathType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation of the path for debugging
|
||||
* @return String representation of the path for debugging
|
||||
*/
|
||||
std::string DebugStr() const;
|
||||
|
||||
std::string AsString() const;
|
||||
std::u16string AsU16Str() const;
|
||||
std::vector<u8> AsBinary() const;
|
||||
|
||||
private:
|
||||
LowPathType type;
|
||||
std::vector<u8> binary;
|
||||
std::string string;
|
||||
std::u16string u16str;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& type;
|
||||
switch (type) {
|
||||
case LowPathType::Binary:
|
||||
ar& binary;
|
||||
break;
|
||||
case LowPathType::Char:
|
||||
ar& string;
|
||||
break;
|
||||
case LowPathType::Wchar: {
|
||||
std::vector<char16_t> data;
|
||||
if (Archive::is_saving::value) {
|
||||
std::copy(u16str.begin(), u16str.end(), std::back_inserter(data));
|
||||
}
|
||||
ar& data;
|
||||
if (Archive::is_loading::value) {
|
||||
u16str = std::u16string(data.data(), data.size());
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/// Parameters of the archive, as specified in the Create or Format call.
|
||||
struct ArchiveFormatInfo {
|
||||
u32_le total_size; ///< The pre-defined size of the archive.
|
||||
u32_le number_directories; ///< The pre-defined number of directories in the archive.
|
||||
u32_le number_files; ///< The pre-defined number of files in the archive.
|
||||
u8 duplicate_data; ///< Whether the archive should duplicate the data.
|
||||
};
|
||||
static_assert(std::is_trivial_v<ArchiveFormatInfo>, "ArchiveFormatInfo is not POD");
|
||||
static_assert(sizeof(ArchiveFormatInfo) == 16, "Invalid ArchiveFormatInfo size");
|
||||
|
||||
class ArchiveBackend : NonCopyable {
|
||||
public:
|
||||
virtual ~ArchiveBackend() {}
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
/**
|
||||
* Open a file specified by its path, using the specified mode
|
||||
* @param path Path relative to the archive
|
||||
* @param mode Mode to open the file with
|
||||
* @return Opened file, or error code
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes = 0) = 0;
|
||||
|
||||
/**
|
||||
* Delete a file specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result DeleteFile(const Path& path) const = 0;
|
||||
|
||||
/**
|
||||
* Rename a File specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result RenameFile(const Path& src_path, const Path& dest_path) const = 0;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result DeleteDirectory(const Path& path) const = 0;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path and anything under it
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result DeleteDirectoryRecursively(const Path& path) const = 0;
|
||||
|
||||
/**
|
||||
* Create a file specified by its path
|
||||
* @param path Path relative to the Archive
|
||||
* @param size The size of the new file, filled with zeroes
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result CreateFile(const Path& path, u64 size, u32 attributes = 0) const = 0;
|
||||
|
||||
/**
|
||||
* Create a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result CreateDirectory(const Path& path, u32 attributes = 0) const = 0;
|
||||
|
||||
/**
|
||||
* Rename a Directory specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual Result RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
|
||||
|
||||
/**
|
||||
* Open a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Opened directory, or error code
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) = 0;
|
||||
|
||||
/**
|
||||
* Get the free space
|
||||
* @return The number of free bytes in the archive
|
||||
*/
|
||||
virtual u64 GetFreeBytes() const = 0;
|
||||
|
||||
/**
|
||||
* Close the archive
|
||||
*/
|
||||
virtual void Close() {}
|
||||
|
||||
virtual Result Control(u32 action, u8* input, size_t input_size, u8* output,
|
||||
size_t output_size) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"(STUBBED) called, archive={}, action={:08X}, input_size={:08X}, "
|
||||
"output_size={:08X}",
|
||||
GetName(), action, input_size, output_size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() {
|
||||
if (delay_generator != nullptr) {
|
||||
return delay_generator->GetOpenDelayNs();
|
||||
}
|
||||
LOG_ERROR(Service_FS, "Delay generator was not initalized. Using default");
|
||||
delay_generator = std::make_unique<DefaultDelayGenerator>();
|
||||
return delay_generator->GetOpenDelayNs();
|
||||
}
|
||||
|
||||
virtual Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) {
|
||||
|
||||
// TODO: Generate and Save the Secure Value
|
||||
|
||||
LOG_WARNING(Service_FS,
|
||||
"(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} "
|
||||
"flush={}",
|
||||
secure_value, secure_value_slot, flush);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
virtual ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(u32 secure_value_slot) {
|
||||
|
||||
// TODO: Implement Secure Value Lookup & Generation
|
||||
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot);
|
||||
|
||||
return std::make_tuple<bool, bool, u64>(false, true, 0);
|
||||
}
|
||||
|
||||
virtual bool IsSlow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<DelayGenerator> delay_generator;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& delay_generator;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class ArchiveFactory : NonCopyable {
|
||||
public:
|
||||
virtual ~ArchiveFactory() {}
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
/**
|
||||
* Tries to open the archive of this type with the specified path
|
||||
* @param path Path to the archive
|
||||
* @param program_id the program ID of the client that requests the operation
|
||||
* @return An ArchiveBackend corresponding operating specified archive path.
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) = 0;
|
||||
|
||||
/**
|
||||
* Deletes the archive contents and then re-creates the base folder
|
||||
* @param path Path to the archive
|
||||
* @param format_info Format information for the new archive
|
||||
* @param program_id the program ID of the client that requests the operation
|
||||
* @return Result of the operation, 0 on success
|
||||
*/
|
||||
virtual Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets, u32 file_buckets) = 0;
|
||||
|
||||
/**
|
||||
* Retrieves the format info about the archive with the specified path
|
||||
* @param path Path to the archive
|
||||
* @param program_id the program ID of the client that requests the operation
|
||||
* @return Format information about the archive or error code
|
||||
*/
|
||||
virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const = 0;
|
||||
|
||||
virtual bool IsSlow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
428
src/core/file_sys/archive_extsavedata.cpp
Normal file
428
src/core/file_sys/archive_extsavedata.cpp
Normal file
@@ -0,0 +1,428 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/archive_artic.h"
|
||||
#include "core/file_sys/archive_extsavedata.h"
|
||||
#include "core/file_sys/disk_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/path_parser.h"
|
||||
#include "core/file_sys/savedata_archive.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_ExtSaveData)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* A modified version of DiskFile for fixed-size file used by ExtSaveData
|
||||
* The file size can't be changed by SetSize or Write.
|
||||
*/
|
||||
class FixSizeDiskFile : public DiskFile {
|
||||
public:
|
||||
FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: DiskFile(std::move(file), mode, std::move(delay_generator_)) {
|
||||
size = GetSize();
|
||||
}
|
||||
|
||||
bool SetSize(u64 size) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override {
|
||||
if (offset > size) {
|
||||
return ResultWriteBeyondEnd;
|
||||
} else if (offset == size) {
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
if (offset + length > size) {
|
||||
length = size - offset;
|
||||
}
|
||||
|
||||
return DiskFile::Write(offset, length, flush, update_timestamp, buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
u64 size{};
|
||||
};
|
||||
|
||||
class ExtSaveDataDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// This is the delay measured for a savedata read,
|
||||
// not for extsaveData
|
||||
// For now we will take that
|
||||
static constexpr u64 slope(183);
|
||||
static constexpr u64 offset(524879);
|
||||
static constexpr u64 minimum(631826);
|
||||
u64 ipc_delay_nanoseconds =
|
||||
std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return ipc_delay_nanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured on N3DS with
|
||||
// https://gist.github.com/FearlessTobi/929b68489f4abb2c6cf81d56970a20b4
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 IPCDelayNanoseconds(3085068);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
/**
|
||||
* Archive backend for general extsave data archive type.
|
||||
* The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for
|
||||
* - file size can't be changed once created (thus creating zero-size file and openning with create
|
||||
* flag are prohibited);
|
||||
* - always open a file with read+write permission.
|
||||
*/
|
||||
class ExtSaveDataArchive : public SaveDataArchive {
|
||||
public:
|
||||
explicit ExtSaveDataArchive(const std::string& mount_point,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: SaveDataArchive(mount_point, false) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
}
|
||||
|
||||
std::string GetName() const override {
|
||||
return "ExtSaveDataArchive: " + mount_point;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) override {
|
||||
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
||||
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
if (mode.hex == 0) {
|
||||
LOG_ERROR(Service_FS, "Empty open mode");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
if (mode.create_flag) {
|
||||
LOG_ERROR(Service_FS, "Create flag is not supported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
LOG_DEBUG(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::DirectoryFound:
|
||||
LOG_DEBUG(Service_FS, "Unexpected file or directory in {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectory;
|
||||
case PathParser::NotFound:
|
||||
LOG_DEBUG(Service_FS, "{} not found", full_path);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::FileFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(full_path, "r+b");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
|
||||
return ResultFileNotFound;
|
||||
}
|
||||
|
||||
Mode rwmode;
|
||||
rwmode.write_flag.Assign(1);
|
||||
rwmode.read_flag.Assign(1);
|
||||
auto delay_generator = std::make_unique<ExtSaveDataDelayGenerator>();
|
||||
return std::make_unique<FixSizeDiskFile>(std::move(file), rwmode,
|
||||
std::move(delay_generator));
|
||||
}
|
||||
|
||||
private:
|
||||
ExtSaveDataArchive() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<SaveDataArchive>(*this);
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
struct ExtSaveDataArchivePath {
|
||||
u32_le media_type;
|
||||
u32_le save_low;
|
||||
u32_le save_high;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ExtSaveDataArchivePath) == 12, "Incorrect path size");
|
||||
|
||||
std::string GetExtSaveDataPath(std::string_view mount_point, const Path& path) {
|
||||
std::vector<u8> vec_data = path.AsBinary();
|
||||
|
||||
ExtSaveDataArchivePath path_data;
|
||||
std::memcpy(&path_data, vec_data.data(), sizeof(path_data));
|
||||
|
||||
return fmt::format("{}{:08X}/{:08X}/", mount_point, path_data.save_high, path_data.save_low);
|
||||
}
|
||||
|
||||
std::string GetExtDataContainerPath(std::string_view mount_point, bool shared) {
|
||||
if (shared) {
|
||||
return fmt::format("{}data/{}/extdata/", mount_point, SYSTEM_ID);
|
||||
}
|
||||
return fmt::format("{}Nintendo 3DS/{}/{}/extdata/", mount_point, SYSTEM_ID, SDCARD_ID);
|
||||
}
|
||||
|
||||
std::string GetExtDataPathFromId(std::string_view mount_point, u64 extdata_id) {
|
||||
const u32 high = static_cast<u32>(extdata_id >> 32);
|
||||
const u32 low = static_cast<u32>(extdata_id & 0xFFFFFFFF);
|
||||
|
||||
return fmt::format("{}{:08x}/{:08x}/", GetExtDataContainerPath(mount_point, false), high, low);
|
||||
}
|
||||
|
||||
Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) {
|
||||
ExtSaveDataArchivePath path;
|
||||
path.media_type = media_type;
|
||||
path.save_high = high;
|
||||
path.save_low = low;
|
||||
|
||||
std::vector<u8> binary_path(sizeof(path));
|
||||
std::memcpy(binary_path.data(), &path, binary_path.size());
|
||||
|
||||
return {binary_path};
|
||||
}
|
||||
|
||||
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location,
|
||||
ExtSaveDataType type_)
|
||||
: type(type_),
|
||||
mount_point(GetExtDataContainerPath(mount_location, type_ == ExtSaveDataType::Shared)) {
|
||||
LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point);
|
||||
}
|
||||
|
||||
Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) {
|
||||
if (type != ExtSaveDataType::Shared) {
|
||||
return path;
|
||||
}
|
||||
|
||||
static constexpr u32 SharedExtDataHigh = 0x48000;
|
||||
|
||||
ExtSaveDataArchivePath new_path;
|
||||
std::memcpy(&new_path, path.AsBinary().data(), sizeof(new_path));
|
||||
|
||||
// The FS module overwrites the high value of the saveid when dealing with the SharedExtSaveData
|
||||
// archive.
|
||||
new_path.save_high = SharedExtDataHigh;
|
||||
|
||||
std::vector<u8> binary_data(sizeof(new_path));
|
||||
std::memcpy(binary_data.data(), &new_path, binary_data.size());
|
||||
|
||||
return {binary_data};
|
||||
}
|
||||
|
||||
static Service::FS::ArchiveIdCode ExtSaveDataTypeToArchiveID(ExtSaveDataType type) {
|
||||
switch (type) {
|
||||
case FileSys::ExtSaveDataType::Normal:
|
||||
return Service::FS::ArchiveIdCode::ExtSaveData;
|
||||
case FileSys::ExtSaveDataType::Shared:
|
||||
return Service::FS::ArchiveIdCode::SharedExtSaveData;
|
||||
case FileSys::ExtSaveDataType::Boss:
|
||||
return Service::FS::ArchiveIdCode::BossExtSaveData;
|
||||
default:
|
||||
return Service::FS::ArchiveIdCode::ExtSaveData;
|
||||
}
|
||||
}
|
||||
|
||||
static Core::PerfStats::PerfArticEventBits ExtSaveDataTypeToPerfArtic(ExtSaveDataType type) {
|
||||
switch (type) {
|
||||
case FileSys::ExtSaveDataType::Normal:
|
||||
return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA;
|
||||
case FileSys::ExtSaveDataType::Shared:
|
||||
return Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA;
|
||||
case FileSys::ExtSaveDataType::Boss:
|
||||
return Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA;
|
||||
default:
|
||||
return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
if (IsUsingArtic()) {
|
||||
EnsureCacheCreated();
|
||||
return ArticArchive::Open(artic_client, ExtSaveDataTypeToArchiveID(type), path,
|
||||
ExtSaveDataTypeToPerfArtic(type), *this,
|
||||
type != FileSys::ExtSaveDataType::Normal);
|
||||
} else {
|
||||
const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/";
|
||||
const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory;
|
||||
if (!FileUtil::Exists(fullpath)) {
|
||||
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
|
||||
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
|
||||
if (type != ExtSaveDataType::Shared) {
|
||||
return ResultNotFoundInvalidState;
|
||||
} else {
|
||||
return ResultNotFormatted;
|
||||
}
|
||||
}
|
||||
std::unique_ptr<DelayGenerator> delay_generator =
|
||||
std::make_unique<ExtSaveDataDelayGenerator>();
|
||||
return std::make_unique<ExtSaveDataArchive>(fullpath, std::move(delay_generator));
|
||||
}
|
||||
}
|
||||
|
||||
Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
u8 unknown, u64 program_id, u64 total_size,
|
||||
std::optional<std::span<const u8>> icon) {
|
||||
if (IsUsingArtic()) {
|
||||
if (!icon.has_value()) {
|
||||
LOG_ERROR(Service_FS, "No icon provided while using Artic Base");
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
ExtSaveDataArchivePath path_data;
|
||||
std::memcpy(&path_data, path.AsBinary().data(), sizeof(path_data));
|
||||
|
||||
Service::FS::ExtSaveDataInfo artic_extdata_path;
|
||||
|
||||
artic_extdata_path.media_type = static_cast<u8>(path_data.media_type);
|
||||
artic_extdata_path.unknown = unknown;
|
||||
artic_extdata_path.save_id_low = path_data.save_low;
|
||||
artic_extdata_path.save_id_high = path_data.save_high;
|
||||
|
||||
auto req = artic_client->NewRequest("FSUSER_CreateExtSaveData");
|
||||
|
||||
req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path));
|
||||
req.AddParameterU32(format_info.number_directories);
|
||||
req.AddParameterU32(format_info.number_files);
|
||||
req.AddParameterU64(total_size);
|
||||
req.AddParameterBuffer(icon->data(), icon->size());
|
||||
|
||||
return ArticArchive::RespResult(artic_client->Send(req));
|
||||
} else {
|
||||
auto corrected_path = GetCorrectedPath(path);
|
||||
|
||||
// These folders are always created with the ExtSaveData
|
||||
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
|
||||
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
|
||||
FileUtil::CreateFullPath(user_path);
|
||||
FileUtil::CreateFullPath(boss_path);
|
||||
|
||||
// Write the format metadata
|
||||
std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata";
|
||||
FileUtil::IOFile file(metadata_path, "wb");
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
// TODO(Subv): Find the correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
file.WriteBytes(&format_info, sizeof(format_info));
|
||||
|
||||
if (icon.has_value()) {
|
||||
FileUtil::IOFile icon_file(FileSys::GetExtSaveDataPath(GetMountPoint(), path) + "icon",
|
||||
"wb");
|
||||
icon_file.WriteBytes(icon->data(), icon->size());
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
Result ArchiveFactory_ExtSaveData::DeleteExtData(Service::FS::MediaType media_type, u8 unknown,
|
||||
u32 high, u32 low) {
|
||||
if (IsUsingArtic()) {
|
||||
Service::FS::ExtSaveDataInfo artic_extdata_path;
|
||||
|
||||
artic_extdata_path.media_type = static_cast<u8>(media_type);
|
||||
artic_extdata_path.unknown = unknown;
|
||||
artic_extdata_path.save_id_low = low;
|
||||
artic_extdata_path.save_id_high = high;
|
||||
|
||||
auto req = artic_client->NewRequest("FSUSER_DeleteExtSaveData");
|
||||
|
||||
req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path));
|
||||
|
||||
return ArticArchive::RespResult(artic_client->Send(req));
|
||||
} else {
|
||||
// Construct the binary path to the archive first
|
||||
FileSys::Path path =
|
||||
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low);
|
||||
|
||||
std::string media_type_directory;
|
||||
if (media_type == Service::FS::MediaType::NAND) {
|
||||
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||
} else if (media_type == Service::FS::MediaType::SDMC) {
|
||||
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
|
||||
} else {
|
||||
LOG_ERROR(Service_FS, "Unsupported media type {}", media_type);
|
||||
return ResultUnknown; // TODO(Subv): Find the right error code
|
||||
}
|
||||
|
||||
// Delete all directories (/user, /boss) and the icon file.
|
||||
std::string base_path = FileSys::GetExtDataContainerPath(
|
||||
media_type_directory, media_type == Service::FS::MediaType::NAND);
|
||||
std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path);
|
||||
if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path))
|
||||
return ResultUnknown; // TODO(Subv): Find the right error code
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path,
|
||||
u64 program_id) const {
|
||||
if (IsUsingArtic()) {
|
||||
auto req = artic_client->NewRequest("FSUSER_GetFormatInfo");
|
||||
|
||||
req.AddParameterS32(static_cast<u32>(ExtSaveDataTypeToArchiveID(type)));
|
||||
auto path_artic = ArticArchive::BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_artic.data(), path_artic.size());
|
||||
|
||||
auto resp = artic_client->Send(req);
|
||||
Result res = ArticArchive::RespResult(resp);
|
||||
if (R_FAILED(res)) {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto info_buf = resp->GetResponseBuffer(0);
|
||||
if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
ArchiveFormatInfo info;
|
||||
memcpy(&info, info_buf->first, sizeof(info));
|
||||
return info;
|
||||
} else {
|
||||
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata";
|
||||
FileUtil::IOFile file(metadata_path, "rb");
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
||||
// TODO(Subv): Verify error code
|
||||
return ResultNotFormatted;
|
||||
}
|
||||
|
||||
ArchiveFormatInfo info = {};
|
||||
file.ReadBytes(&info, sizeof(info));
|
||||
return info;
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataArchive)
|
||||
137
src/core/file_sys/archive_extsavedata.h
Normal file
137
src/core/file_sys/archive_extsavedata.h
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/string.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/artic_cache.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class ExtSaveDataType {
|
||||
Normal, ///< Regular non-shared ext save data
|
||||
Shared, ///< Shared ext save data
|
||||
Boss, ///< SpotPass ext save data
|
||||
};
|
||||
|
||||
/// File system interface to the ExtSaveData archive
|
||||
class ArchiveFactory_ExtSaveData final : public ArchiveFactory, public ArticCacheProvider {
|
||||
public:
|
||||
ArchiveFactory_ExtSaveData(const std::string& mount_point, ExtSaveDataType type_);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "ExtSaveData";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
bool IsSlow() override {
|
||||
return IsUsingArtic();
|
||||
}
|
||||
|
||||
const std::string& GetMountPoint() const {
|
||||
return mount_point;
|
||||
}
|
||||
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override {
|
||||
return UnimplementedFunction(ErrorModule::FS);
|
||||
};
|
||||
|
||||
Result FormatAsExtData(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||
u8 unknown, u64 program_id, u64 total_size,
|
||||
std::optional<std::span<const u8>> icon);
|
||||
|
||||
Result DeleteExtData(Service::FS::MediaType media_type, u8 unknown, u32 high, u32 low);
|
||||
|
||||
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||
artic_client = client;
|
||||
}
|
||||
|
||||
bool IsUsingArtic() const {
|
||||
return artic_client.get() != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Type of ext save data archive being accessed.
|
||||
ExtSaveDataType type;
|
||||
|
||||
/**
|
||||
* This holds the full directory path for this archive, it is only set after a successful call
|
||||
* to Open, this is formed as `<base extsavedatapath>/<type>/<high>/<low>`.
|
||||
* See GetExtSaveDataPath for the code that extracts this data from an archive path.
|
||||
*/
|
||||
std::string mount_point;
|
||||
|
||||
/// Returns a path with the correct SaveIdHigh value for Shared extdata paths.
|
||||
Path GetCorrectedPath(const Path& path);
|
||||
|
||||
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||
|
||||
ArchiveFactory_ExtSaveData() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& boost::serialization::base_object<ArticCacheProvider>(*this);
|
||||
ar& type;
|
||||
ar& mount_point;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a path to the concrete ExtData archive in the host filesystem based on the
|
||||
* input Path and base mount point.
|
||||
* @param mount_point The base mount point of the ExtSaveData archives.
|
||||
* @param path The path that identifies the requested concrete ExtSaveData archive.
|
||||
* @returns The complete path to the specified extdata archive in the host filesystem
|
||||
*/
|
||||
std::string GetExtSaveDataPath(std::string_view mount_point, const Path& path);
|
||||
|
||||
/**
|
||||
* Constructs a path to the concrete ExtData archive in the host filesystem based on the
|
||||
* extdata ID and base mount point.
|
||||
* @param mount_point The base mount point of the ExtSaveData archives.
|
||||
* @param extdata_id The id of the ExtSaveData
|
||||
* @returns The complete path to the specified extdata archive in the host filesystem
|
||||
*/
|
||||
std::string GetExtDataPathFromId(std::string_view mount_point, u64 extdata_id);
|
||||
|
||||
/**
|
||||
* Constructs a path to the base folder to hold concrete ExtSaveData archives in the host file
|
||||
* system.
|
||||
* @param mount_point The base folder where this folder resides, ie. SDMC or NAND.
|
||||
* @param shared Whether this ExtSaveData container is for SharedExtSaveDatas or not.
|
||||
* @returns The path to the base ExtSaveData archives' folder in the host file system
|
||||
*/
|
||||
std::string GetExtDataContainerPath(std::string_view mount_point, bool shared);
|
||||
|
||||
/**
|
||||
* Constructs a FileSys::Path object that refers to the ExtData archive identified by
|
||||
* the specified media type, high save id and low save id.
|
||||
* @param media_type The media type where the archive is located (NAND / SDMC)
|
||||
* @param high The high word of the save id for the archive
|
||||
* @param low The low word of the save id for the archive
|
||||
* @returns A FileSys::Path to the wanted archive
|
||||
*/
|
||||
Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low);
|
||||
|
||||
class ExtSaveDataDelayGenerator;
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_ExtSaveData)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ExtSaveDataDelayGenerator)
|
||||
319
src/core/file_sys/archive_ncch.cpp
Normal file
319
src/core/file_sys/archive_ncch.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "bad_word_list.app.romfs.h"
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/archive_artic.h"
|
||||
#include "core/file_sys/archive_ncch.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/ivfc_archive.h"
|
||||
#include "core/file_sys/ncch_container.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "country_list.app.romfs.h"
|
||||
#include "mii.app.romfs.h"
|
||||
#include "shared_font.app.romfs.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::NCCHArchive)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::NCCHFile)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_NCCH)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct NCCHArchivePath {
|
||||
u64_le tid;
|
||||
u32_le media_type;
|
||||
u32_le unknown;
|
||||
};
|
||||
static_assert(sizeof(NCCHArchivePath) == 0x10, "NCCHArchivePath has wrong size!");
|
||||
|
||||
struct NCCHFilePath {
|
||||
enum_le<NCCHFileOpenType> open_type;
|
||||
u32_le content_index;
|
||||
enum_le<NCCHFilePathType> filepath_type;
|
||||
std::array<char, 8> exefs_filepath;
|
||||
};
|
||||
static_assert(sizeof(NCCHFilePath) == 0x14, "NCCHFilePath has wrong size!");
|
||||
|
||||
Path MakeNCCHArchivePath(u64 tid, Service::FS::MediaType media_type) {
|
||||
NCCHArchivePath path;
|
||||
path.tid = static_cast<u64_le>(tid);
|
||||
path.media_type = static_cast<u32_le>(media_type);
|
||||
path.unknown = 0;
|
||||
std::vector<u8> archive(sizeof(path));
|
||||
std::memcpy(archive.data(), &path, sizeof(path));
|
||||
return FileSys::Path(std::move(archive));
|
||||
}
|
||||
|
||||
Path MakeNCCHFilePath(NCCHFileOpenType open_type, u32 content_index, NCCHFilePathType filepath_type,
|
||||
std::array<char, 8>& exefs_filepath) {
|
||||
NCCHFilePath path;
|
||||
path.open_type = open_type;
|
||||
path.content_index = static_cast<u32_le>(content_index);
|
||||
path.filepath_type = filepath_type;
|
||||
path.exefs_filepath = exefs_filepath;
|
||||
std::vector<u8> file(sizeof(path));
|
||||
std::memcpy(file.data(), &path, sizeof(path));
|
||||
return FileSys::Path(std::move(file));
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) {
|
||||
|
||||
if (path.GetType() != LowPathType::Binary) {
|
||||
LOG_ERROR(Service_FS, "Path need to be Binary");
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
std::vector<u8> binary = path.AsBinary();
|
||||
if (binary.size() != sizeof(NCCHFilePath)) {
|
||||
LOG_ERROR(Service_FS, "Wrong path size {}", binary.size());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
NCCHFilePath openfile_path;
|
||||
std::memcpy(&openfile_path, binary.data(), sizeof(NCCHFilePath));
|
||||
|
||||
std::string file_path;
|
||||
if (Settings::values.is_new_3ds) {
|
||||
// Try the New 3DS specific variant first.
|
||||
file_path = Service::AM::GetTitleContentPath(media_type, title_id | 0x20000000,
|
||||
openfile_path.content_index);
|
||||
}
|
||||
if (!Settings::values.is_new_3ds || !FileUtil::Exists(file_path)) {
|
||||
file_path =
|
||||
Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index);
|
||||
}
|
||||
|
||||
auto ncch_container = NCCHContainer(file_path, 0, openfile_path.content_index);
|
||||
|
||||
Loader::ResultStatus result;
|
||||
std::unique_ptr<FileBackend> file;
|
||||
|
||||
// NCCH RomFS
|
||||
if (openfile_path.filepath_type == NCCHFilePathType::RomFS) {
|
||||
std::shared_ptr<RomFSReader> romfs_file;
|
||||
|
||||
result = ncch_container.ReadRomFS(romfs_file);
|
||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<RomFSDelayGenerator>();
|
||||
file = std::make_unique<IVFCFile>(std::move(romfs_file), std::move(delay_generator));
|
||||
} else if (openfile_path.filepath_type == NCCHFilePathType::Code ||
|
||||
openfile_path.filepath_type == NCCHFilePathType::ExeFS) {
|
||||
std::vector<u8> buffer;
|
||||
|
||||
// Load NCCH .code or icon/banner/logo
|
||||
result = ncch_container.LoadSectionExeFS(openfile_path.exefs_filepath.data(), buffer);
|
||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<ExeFSDelayGenerator>();
|
||||
file = std::make_unique<NCCHFile>(std::move(buffer), std::move(delay_generator));
|
||||
} else {
|
||||
LOG_ERROR(Service_FS, "Unknown NCCH archive type {}!", openfile_path.filepath_type);
|
||||
result = Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
// High Title ID of the archive: The category (https://3dbrew.org/wiki/Title_list).
|
||||
constexpr u32 shared_data_archive = 0x0004009B;
|
||||
constexpr u32 system_data_archive = 0x000400DB;
|
||||
|
||||
// Low Title IDs.
|
||||
constexpr u32 mii_data = 0x00010202;
|
||||
constexpr u32 region_manifest = 0x00010402;
|
||||
constexpr u32 ng_word_list = 0x00010302;
|
||||
constexpr u32 shared_font = 0x00014002;
|
||||
|
||||
u32 high = static_cast<u32>(title_id >> 32);
|
||||
u32 low = static_cast<u32>(title_id & 0xFFFFFFFF);
|
||||
|
||||
LOG_DEBUG(Service_FS, "Full Path: {}. Category: 0x{:X}. Path: 0x{:X}.", path.DebugStr(),
|
||||
high, low);
|
||||
|
||||
std::vector<u8> archive_data;
|
||||
if (high == shared_data_archive) {
|
||||
if (low == mii_data) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Mii data file missing. Loading open source replacement from memory");
|
||||
archive_data = std::vector<u8>(std::begin(MII_DATA), std::end(MII_DATA));
|
||||
} else if (low == region_manifest) {
|
||||
LOG_WARNING(
|
||||
Service_FS,
|
||||
"Country list file missing. Loading open source replacement from memory");
|
||||
archive_data =
|
||||
std::vector<u8>(std::begin(COUNTRY_LIST_DATA), std::end(COUNTRY_LIST_DATA));
|
||||
} else if (low == shared_font) {
|
||||
LOG_WARNING(
|
||||
Service_FS,
|
||||
"Shared Font file missing. Loading open source replacement from memory");
|
||||
archive_data =
|
||||
std::vector<u8>(std::begin(SHARED_FONT_DATA), std::end(SHARED_FONT_DATA));
|
||||
}
|
||||
} else if (high == system_data_archive) {
|
||||
if (low == ng_word_list) {
|
||||
LOG_WARNING(
|
||||
Service_FS,
|
||||
"Bad Word List file missing. Loading open source replacement from memory");
|
||||
archive_data =
|
||||
std::vector<u8>(std::begin(BAD_WORD_LIST_DATA), std::end(BAD_WORD_LIST_DATA));
|
||||
}
|
||||
}
|
||||
|
||||
if (!archive_data.empty()) {
|
||||
u64 romfs_offset = 0;
|
||||
u64 romfs_size = archive_data.size();
|
||||
auto delay_generator = std::make_unique<RomFSDelayGenerator>();
|
||||
return std::make_unique<IVFCFileInMemory>(std::move(archive_data), romfs_offset,
|
||||
romfs_size, std::move(delay_generator));
|
||||
}
|
||||
return ResultNotFound;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
Result NCCHArchive::DeleteFile(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a file from an NCCH archive ({}).", GetName());
|
||||
// TODO(Subv): Verify error code
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
|
||||
ErrorLevel::Status);
|
||||
}
|
||||
|
||||
Result NCCHArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an NCCH archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result NCCHArchive::DeleteDirectory(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an NCCH archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result NCCHArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an NCCH archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result NCCHArchive::CreateFile(const Path& path, u64 size, u32 attributes) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an NCCH archive ({}).", GetName());
|
||||
// TODO: Verify error code
|
||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||
ErrorLevel::Permanent);
|
||||
}
|
||||
|
||||
Result NCCHArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an NCCH archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result NCCHArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an NCCH archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> NCCHArchive::OpenDirectory(const Path& path) {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to open a directory within an NCCH archive ({}).",
|
||||
GetName().c_str());
|
||||
// TODO(shinyquagsire23): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
u64 NCCHArchive::GetFreeBytes() const {
|
||||
LOG_WARNING(Service_FS, "Attempted to get the free space in an NCCH archive");
|
||||
return 0;
|
||||
}
|
||||
|
||||
NCCHFile::NCCHFile(std::vector<u8> buffer, std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: file_buffer(std::move(buffer)) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> NCCHFile::Read(const u64 offset, const std::size_t length,
|
||||
u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
|
||||
std::size_t available_size = static_cast<std::size_t>(file_buffer.size() - offset);
|
||||
std::size_t copy_size = std::min(length, available_size);
|
||||
std::memcpy(buffer, file_buffer.data() + offset, copy_size);
|
||||
|
||||
return copy_size;
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> NCCHFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
||||
const bool update_timestamp, const u8* buffer) {
|
||||
LOG_ERROR(Service_FS, "Attempted to write to NCCH file");
|
||||
// TODO(shinyquagsire23): Find error code
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
u64 NCCHFile::GetSize() const {
|
||||
return file_buffer.size();
|
||||
}
|
||||
|
||||
bool NCCHFile::SetSize(const u64 size) const {
|
||||
LOG_ERROR(Service_FS, "Attempted to set the size of an NCCH file");
|
||||
return false;
|
||||
}
|
||||
|
||||
ArchiveFactory_NCCH::ArchiveFactory_NCCH() {}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
|
||||
if (IsUsingArtic()) {
|
||||
EnsureCacheCreated();
|
||||
return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::NCCH, path,
|
||||
Core::PerfStats::PerfArticEventBits::NONE, *this, false);
|
||||
}
|
||||
|
||||
if (path.GetType() != LowPathType::Binary) {
|
||||
LOG_ERROR(Service_FS, "Path need to be Binary");
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
std::vector<u8> binary = path.AsBinary();
|
||||
if (binary.size() != sizeof(NCCHArchivePath)) {
|
||||
LOG_ERROR(Service_FS, "Wrong path size {}", binary.size());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
NCCHArchivePath open_path;
|
||||
std::memcpy(&open_path, binary.data(), sizeof(NCCHArchivePath));
|
||||
|
||||
return std::make_unique<NCCHArchive>(
|
||||
open_path.tid, static_cast<Service::FS::MediaType>(open_path.media_type & 0xFF));
|
||||
}
|
||||
|
||||
Result ArchiveFactory_NCCH::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||
LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
|
||||
// TODO: Verify error code
|
||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||
ErrorLevel::Permanent);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path,
|
||||
u64 program_id) const {
|
||||
// TODO(Subv): Implement
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
150
src/core/file_sys/archive_ncch.h
Normal file
150
src/core/file_sys/archive_ncch.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/artic_cache.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace Service::FS {
|
||||
enum class MediaType : u32;
|
||||
} // namespace Service::FS
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class NCCHFilePathType : u32 {
|
||||
RomFS = 0,
|
||||
Code = 1,
|
||||
ExeFS = 2,
|
||||
};
|
||||
|
||||
enum class NCCHFileOpenType : u32 {
|
||||
NCCHData = 0,
|
||||
SaveData = 1,
|
||||
};
|
||||
|
||||
/// Helper function to generate a Path for NCCH archives
|
||||
Path MakeNCCHArchivePath(u64 tid, Service::FS::MediaType media_type);
|
||||
|
||||
/// Helper function to generate a Path for NCCH files
|
||||
Path MakeNCCHFilePath(NCCHFileOpenType open_type, u32 content_index, NCCHFilePathType filepath_type,
|
||||
std::array<char, 8>& exefs_filepath);
|
||||
|
||||
/// Archive backend for NCCH Archives (RomFS, ExeFS)
|
||||
class NCCHArchive : public ArchiveBackend {
|
||||
public:
|
||||
explicit NCCHArchive(u64 title_id, Service::FS::MediaType media_type)
|
||||
: title_id(title_id), media_type(media_type) {}
|
||||
|
||||
std::string GetName() const override {
|
||||
return "NCCHArchive";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) override;
|
||||
Result DeleteFile(const Path& path) const override;
|
||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||
Result DeleteDirectory(const Path& path) const override;
|
||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||
u64 GetFreeBytes() const override;
|
||||
|
||||
protected:
|
||||
u64 title_id;
|
||||
Service::FS::MediaType media_type;
|
||||
|
||||
private:
|
||||
NCCHArchive() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveBackend>(*this);
|
||||
ar& title_id;
|
||||
ar& media_type;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
// File backend for NCCH files
|
||||
class NCCHFile : public FileBackend {
|
||||
public:
|
||||
explicit NCCHFile(std::vector<u8> buffer, std::unique_ptr<DelayGenerator> delay_generator_);
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override;
|
||||
u64 GetSize() const override;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() override {
|
||||
return false;
|
||||
}
|
||||
void Flush() const override {}
|
||||
|
||||
private:
|
||||
std::vector<u8> file_buffer;
|
||||
|
||||
NCCHFile() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||
ar& file_buffer;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/// File system interface to the NCCH archive
|
||||
class ArchiveFactory_NCCH final : public ArchiveFactory, public ArticCacheProvider {
|
||||
public:
|
||||
explicit ArchiveFactory_NCCH();
|
||||
|
||||
std::string GetName() const override {
|
||||
return "NCCH";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
bool IsSlow() override {
|
||||
return IsUsingArtic();
|
||||
}
|
||||
|
||||
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||
artic_client = client;
|
||||
}
|
||||
|
||||
bool IsUsingArtic() const {
|
||||
return artic_client.get() != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& boost::serialization::base_object<ArticCacheProvider>(*this);
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::NCCHArchive)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::NCCHFile)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_NCCH)
|
||||
157
src/core/file_sys/archive_other_savedata.cpp
Normal file
157
src/core/file_sys/archive_other_savedata.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include "common/archives.h"
|
||||
#include "core/file_sys/archive_other_savedata.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_OtherSaveDataPermitted)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_OtherSaveDataGeneral)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// TODO(wwylele): The storage info in exheader should be checked before accessing these archives
|
||||
|
||||
using Service::FS::MediaType;
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
ResultVal<std::tuple<MediaType, u64>> ParsePath(const Path& path, T program_id_reader) {
|
||||
if (path.GetType() != LowPathType::Binary) {
|
||||
LOG_ERROR(Service_FS, "Wrong path type {}", path.GetType());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
std::vector<u8> vec_data = path.AsBinary();
|
||||
|
||||
if (vec_data.size() != 12) {
|
||||
LOG_ERROR(Service_FS, "Wrong path length {}", vec_data.size());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const u32* data = reinterpret_cast<const u32*>(vec_data.data());
|
||||
auto media_type = static_cast<MediaType>(data[0]);
|
||||
|
||||
if (media_type != MediaType::SDMC && media_type != MediaType::GameCard) {
|
||||
LOG_ERROR(Service_FS, "Unsupported media type {}", media_type);
|
||||
|
||||
// Note: this is strange, but the error code was verified with a real 3DS
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
return std::make_tuple(media_type, program_id_reader(data));
|
||||
}
|
||||
|
||||
ResultVal<std::tuple<MediaType, u64>> ParsePathPermitted(const Path& path) {
|
||||
return ParsePath(path,
|
||||
[](const u32* data) -> u64 { return (data[1] << 8) | 0x0004000000000000ULL; });
|
||||
}
|
||||
|
||||
ResultVal<std::tuple<MediaType, u64>> ParsePathGeneral(const Path& path) {
|
||||
return ParsePath(
|
||||
path, [](const u32* data) -> u64 { return data[1] | (static_cast<u64>(data[2]) << 32); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ArchiveFactory_OtherSaveDataPermitted::ArchiveFactory_OtherSaveDataPermitted(
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata)
|
||||
: sd_savedata_source(std::move(sd_savedata)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataPermitted::Open(
|
||||
const Path& path, u64 /*client_program_id*/) {
|
||||
MediaType media_type;
|
||||
u64 program_id;
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathPermitted(path));
|
||||
|
||||
if (media_type == MediaType::GameCard) {
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard");
|
||||
return ResultGamecardNotInserted;
|
||||
}
|
||||
|
||||
return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path,
|
||||
program_id);
|
||||
}
|
||||
|
||||
Result ArchiveFactory_OtherSaveDataPermitted::Format(const Path& path,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets,
|
||||
u32 file_buckets) {
|
||||
LOG_ERROR(Service_FS, "Attempted to format a OtherSaveDataPermitted archive.");
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataPermitted::GetFormatInfo(
|
||||
const Path& path, u64 /*client_program_id*/) const {
|
||||
MediaType media_type;
|
||||
u64 program_id;
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathPermitted(path));
|
||||
|
||||
if (media_type == MediaType::GameCard) {
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard");
|
||||
return ResultGamecardNotInserted;
|
||||
}
|
||||
|
||||
return sd_savedata_source->GetFormatInfo(
|
||||
program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path);
|
||||
}
|
||||
|
||||
ArchiveFactory_OtherSaveDataGeneral::ArchiveFactory_OtherSaveDataGeneral(
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata)
|
||||
: sd_savedata_source(std::move(sd_savedata)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataGeneral::Open(
|
||||
const Path& path, u64 /*client_program_id*/) {
|
||||
MediaType media_type;
|
||||
u64 program_id;
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path));
|
||||
|
||||
if (media_type == MediaType::GameCard) {
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard");
|
||||
return ResultGamecardNotInserted;
|
||||
}
|
||||
|
||||
return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataGeneral, path,
|
||||
program_id);
|
||||
}
|
||||
|
||||
Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 /*client_program_id*/, u32 directory_buckets,
|
||||
u32 file_buckets) {
|
||||
MediaType media_type;
|
||||
u64 program_id;
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path));
|
||||
|
||||
if (media_type == MediaType::GameCard) {
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard");
|
||||
return ResultGamecardNotInserted;
|
||||
}
|
||||
|
||||
return sd_savedata_source->Format(program_id, format_info,
|
||||
Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path,
|
||||
directory_buckets, file_buckets);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo(
|
||||
const Path& path, u64 /*client_program_id*/) const {
|
||||
MediaType media_type;
|
||||
u64 program_id;
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path));
|
||||
|
||||
if (media_type == MediaType::GameCard) {
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard");
|
||||
return ResultGamecardNotInserted;
|
||||
}
|
||||
|
||||
return sd_savedata_source->GetFormatInfo(
|
||||
program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
75
src/core/file_sys/archive_other_savedata.h
Normal file
75
src/core/file_sys/archive_other_savedata.h
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the OtherSaveDataPermitted archive
|
||||
class ArchiveFactory_OtherSaveDataPermitted final : public ArchiveFactory {
|
||||
public:
|
||||
explicit ArchiveFactory_OtherSaveDataPermitted(
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "OtherSaveDataPermitted";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
bool IsSlow() override {
|
||||
return sd_savedata_source->IsUsingArtic();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
||||
|
||||
ArchiveFactory_OtherSaveDataPermitted() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& sd_savedata_source;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/// File system interface to the OtherSaveDataGeneral archive
|
||||
class ArchiveFactory_OtherSaveDataGeneral final : public ArchiveFactory {
|
||||
public:
|
||||
explicit ArchiveFactory_OtherSaveDataGeneral(
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "OtherSaveDataGeneral";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
||||
|
||||
ArchiveFactory_OtherSaveDataGeneral() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& sd_savedata_source;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_OtherSaveDataPermitted)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_OtherSaveDataGeneral)
|
||||
37
src/core/file_sys/archive_savedata.cpp
Normal file
37
src/core/file_sys/archive_savedata.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <utility>
|
||||
#include "common/archives.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/archive_savedata.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SaveData)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
ArchiveFactory_SaveData::ArchiveFactory_SaveData(
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata)
|
||||
: sd_savedata_source(std::move(sd_savedata)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
return sd_savedata_source->Open(Service::FS::ArchiveIdCode::SaveData, path, program_id);
|
||||
}
|
||||
|
||||
Result ArchiveFactory_SaveData::Format(const Path& path,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||
return sd_savedata_source->Format(program_id, format_info, Service::FS::ArchiveIdCode::SaveData,
|
||||
path, directory_buckets, file_buckets);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveData::GetFormatInfo(const Path& path,
|
||||
u64 program_id) const {
|
||||
return sd_savedata_source->GetFormatInfo(program_id, Service::FS::ArchiveIdCode::SaveData,
|
||||
path);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
46
src/core/file_sys/archive_savedata.h
Normal file
46
src/core/file_sys/archive_savedata.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SaveData archive
|
||||
class ArchiveFactory_SaveData final : public ArchiveFactory {
|
||||
public:
|
||||
explicit ArchiveFactory_SaveData(std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SaveData";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
bool IsSlow() override {
|
||||
return sd_savedata_source->IsUsingArtic();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
||||
|
||||
ArchiveFactory_SaveData() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& sd_savedata_source;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SaveData)
|
||||
408
src/core/file_sys/archive_sdmc.cpp
Normal file
408
src/core/file_sys/archive_sdmc.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "common/archives.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/file_sys/archive_sdmc.h"
|
||||
#include "core/file_sys/disk_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/path_parser.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SDMCArchive)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SDMC)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class SDMCDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/B3n30/ac40eac20603f519ff106107f4ac9182
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 slope(183);
|
||||
static constexpr u64 offset(524879);
|
||||
static constexpr u64 minimum(631826);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/FearlessTobi/c37e143c314789251f98f2c45cd706d2
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 IPCDelayNanoseconds(269082);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) {
|
||||
Mode modified_mode;
|
||||
modified_mode.hex = mode.hex;
|
||||
|
||||
// SDMC archive always opens a file with at least read permission
|
||||
modified_mode.read_flag.Assign(1);
|
||||
|
||||
return OpenFileBase(path, modified_mode);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& path,
|
||||
const Mode& mode) const {
|
||||
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
||||
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
if (mode.hex == 0) {
|
||||
LOG_ERROR(Service_FS, "Empty open mode");
|
||||
return ResultInvalidOpenFlags;
|
||||
}
|
||||
|
||||
if (mode.create_flag && !mode.write_flag) {
|
||||
LOG_ERROR(Service_FS, "Create flag set but write flag not set");
|
||||
return ResultInvalidOpenFlags;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::FileInPath:
|
||||
LOG_DEBUG(Service_FS, "Path not found {}", full_path);
|
||||
return ResultNotFound;
|
||||
case PathParser::DirectoryFound:
|
||||
LOG_DEBUG(Service_FS, "{} is not a file", full_path);
|
||||
return ResultUnexpectedFileOrDirectorySdmc;
|
||||
case PathParser::NotFound:
|
||||
if (!mode.create_flag) {
|
||||
LOG_DEBUG(Service_FS, "Non-existing file {} can't be open without mode create.",
|
||||
full_path);
|
||||
return ResultNotFound;
|
||||
} else {
|
||||
// Create the file
|
||||
FileUtil::CreateEmptyFile(full_path);
|
||||
}
|
||||
break;
|
||||
case PathParser::FileFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_CRITICAL(Service_FS, "Error opening {}: {}", full_path, Common::GetLastErrorMsg());
|
||||
return ResultNotFound;
|
||||
}
|
||||
|
||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<SDMCDelayGenerator>();
|
||||
return std::make_unique<DiskFile>(std::move(file), mode, std::move(delay_generator));
|
||||
}
|
||||
|
||||
Result SDMCArchive::DeleteFile(const Path& path) const {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::NotFound:
|
||||
LOG_DEBUG(Service_FS, "{} not found", full_path);
|
||||
return ResultNotFound;
|
||||
case PathParser::DirectoryFound:
|
||||
LOG_ERROR(Service_FS, "{} is not a file", full_path);
|
||||
return ResultUnexpectedFileOrDirectorySdmc;
|
||||
case PathParser::FileFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (FileUtil::Delete(full_path)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting {}", full_path);
|
||||
return ResultNotFound;
|
||||
}
|
||||
|
||||
Result SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
const PathParser path_parser_src(src_path);
|
||||
|
||||
// TODO: Verify these return codes with HW
|
||||
if (!path_parser_src.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid src path {}", src_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const PathParser path_parser_dest(dest_path);
|
||||
|
||||
if (!path_parser_dest.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid dest path {}", dest_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
|
||||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
|
||||
|
||||
if (FileUtil::Rename(src_path_full, dest_path_full)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||
// exist or similar. Verify.
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Result DeleteDirectoryHelper(const Path& path, const std::string& mount_point, T deleter) {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
if (path_parser.IsRootDirectory())
|
||||
return ResultNotFound;
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::NotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultNotFound;
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::FileFound:
|
||||
LOG_ERROR(Service_FS, "Unexpected file in path {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectorySdmc;
|
||||
case PathParser::DirectoryFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (deleter(full_path)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS, "Directory not empty {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectorySdmc;
|
||||
}
|
||||
|
||||
Result SDMCArchive::DeleteDirectory(const Path& path) const {
|
||||
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
|
||||
}
|
||||
|
||||
Result SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||
return DeleteDirectoryHelper(
|
||||
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
||||
}
|
||||
|
||||
Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::FileInPath:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultNotFound;
|
||||
case PathParser::DirectoryFound:
|
||||
LOG_ERROR(Service_FS, "{} already exists", full_path);
|
||||
return ResultUnexpectedFileOrDirectorySdmc;
|
||||
case PathParser::FileFound:
|
||||
LOG_ERROR(Service_FS, "{} already exists", full_path);
|
||||
return ResultAlreadyExists;
|
||||
case PathParser::NotFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
FileUtil::CreateEmptyFile(full_path);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(full_path, "wb");
|
||||
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
||||
// We do this by seeking to the right size, then writing a single null byte.
|
||||
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS, "Too large file");
|
||||
return Result(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
|
||||
ErrorLevel::Info);
|
||||
}
|
||||
|
||||
Result SDMCArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::FileInPath:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultNotFound;
|
||||
case PathParser::DirectoryFound:
|
||||
case PathParser::FileFound:
|
||||
LOG_DEBUG(Service_FS, "{} already exists", full_path);
|
||||
return ResultAlreadyExists;
|
||||
case PathParser::NotFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (FileUtil::CreateDir(mount_point + path.AsString())) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", mount_point);
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
|
||||
ErrorLevel::Status);
|
||||
}
|
||||
|
||||
Result SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
const PathParser path_parser_src(src_path);
|
||||
|
||||
// TODO: Verify these return codes with HW
|
||||
if (!path_parser_src.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid src path {}", src_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const PathParser path_parser_dest(dest_path);
|
||||
|
||||
if (!path_parser_dest.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid dest path {}", dest_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
|
||||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
|
||||
|
||||
if (FileUtil::Rename(src_path_full, dest_path_full)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||
// exist or similar. Verify.
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::NotFound:
|
||||
case PathParser::FileFound:
|
||||
LOG_DEBUG(Service_FS, "{} not found", full_path);
|
||||
return ResultNotFound;
|
||||
case PathParser::FileInPath:
|
||||
LOG_DEBUG(Service_FS, "Unexpected file in path {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectorySdmc;
|
||||
case PathParser::DirectoryFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
return std::make_unique<DiskDirectory>(full_path);
|
||||
}
|
||||
|
||||
u64 SDMCArchive::GetFreeBytes() const {
|
||||
// TODO: Stubbed to return 1GiB
|
||||
return 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
|
||||
: sdmc_directory(sdmc_directory) {
|
||||
|
||||
LOG_DEBUG(Service_FS, "Directory {} set as SDMC.", sdmc_directory);
|
||||
}
|
||||
|
||||
bool ArchiveFactory_SDMC::Initialize() {
|
||||
if (!Settings::values.use_virtual_sd) {
|
||||
LOG_WARNING(Service_FS, "SDMC disabled by config.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FileUtil::CreateFullPath(sdmc_directory)) {
|
||||
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<SDMCDelayGenerator>();
|
||||
return std::make_unique<SDMCArchive>(sdmc_directory, std::move(delay_generator));
|
||||
}
|
||||
|
||||
Result ArchiveFactory_SDMC::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||
// This is kind of an undesirable operation, so let's just ignore it. :)
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMC::GetFormatInfo(const Path& path,
|
||||
u64 program_id) const {
|
||||
// TODO(Subv): Implement
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
return ResultUnknown;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SDMCDelayGenerator)
|
||||
93
src/core/file_sys/archive_sdmc.h
Normal file
93
src/core/file_sys/archive_sdmc.h
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/string.hpp>
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// Archive backend for SDMC archive
|
||||
class SDMCArchive : public ArchiveBackend {
|
||||
public:
|
||||
explicit SDMCArchive(const std::string& mount_point_,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: mount_point(mount_point_) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
}
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMCArchive: " + mount_point;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) override;
|
||||
Result DeleteFile(const Path& path) const override;
|
||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||
Result DeleteDirectory(const Path& path) const override;
|
||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||
u64 GetFreeBytes() const override;
|
||||
|
||||
protected:
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const;
|
||||
std::string mount_point;
|
||||
|
||||
SDMCArchive() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveBackend>(*this);
|
||||
ar& mount_point;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/// File system interface to the SDMC archive
|
||||
class ArchiveFactory_SDMC final : public ArchiveFactory {
|
||||
public:
|
||||
explicit ArchiveFactory_SDMC(const std::string& mount_point);
|
||||
|
||||
/**
|
||||
* Initialize the archive.
|
||||
* @return true if it initialized successfully
|
||||
*/
|
||||
bool Initialize();
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMC";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
private:
|
||||
std::string sdmc_directory;
|
||||
|
||||
ArchiveFactory_SDMC() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& sdmc_directory;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class SDMCDelayGenerator;
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::SDMCArchive)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SDMC)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::SDMCDelayGenerator)
|
||||
102
src/core/file_sys/archive_sdmcwriteonly.cpp
Normal file
102
src/core/file_sys/archive_sdmcwriteonly.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "common/archives.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/file_sys/archive_sdmcwriteonly.h"
|
||||
#include "core/file_sys/directory_backend.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SDMCWriteOnlyArchive)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SDMCWriteOnly)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class SDMCWriteOnlyDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/B3n30/ac40eac20603f519ff106107f4ac9182
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 slope(183);
|
||||
static constexpr u64 offset(524879);
|
||||
static constexpr u64 minimum(631826);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/FearlessTobi/c37e143c314789251f98f2c45cd706d2
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 IPCDelayNanoseconds(269082);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
|
||||
const Mode& mode,
|
||||
u32 attributes) {
|
||||
if (mode.read_flag) {
|
||||
LOG_ERROR(Service_FS, "Read flag is not supported");
|
||||
return ResultInvalidReadFlag;
|
||||
}
|
||||
return SDMCArchive::OpenFileBase(path, mode);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Not supported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point)
|
||||
: sdmc_directory(mount_point) {
|
||||
LOG_DEBUG(Service_FS, "Directory {} set as SDMCWriteOnly.", sdmc_directory);
|
||||
}
|
||||
|
||||
bool ArchiveFactory_SDMCWriteOnly::Initialize() {
|
||||
if (!Settings::values.use_virtual_sd) {
|
||||
LOG_WARNING(Service_FS, "SDMC disabled by config.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FileUtil::CreateFullPath(sdmc_directory)) {
|
||||
LOG_ERROR(Service_FS, "Unable to create SDMC path.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
std::unique_ptr<DelayGenerator> delay_generator =
|
||||
std::make_unique<SDMCWriteOnlyDelayGenerator>();
|
||||
return std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory, std::move(delay_generator));
|
||||
}
|
||||
|
||||
Result ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets,
|
||||
u32 file_buckets) {
|
||||
// TODO(wwylele): hwtest this
|
||||
LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path,
|
||||
u64 program_id) const {
|
||||
// TODO(Subv): Implement
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SDMCWriteOnlyDelayGenerator)
|
||||
79
src/core/file_sys/archive_sdmcwriteonly.h
Normal file
79
src/core/file_sys/archive_sdmcwriteonly.h
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/archive_sdmc.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* Archive backend for SDMC write-only archive.
|
||||
* The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for
|
||||
* - OpenDirectory is unsupported;
|
||||
* - OpenFile with read flag is unsupported.
|
||||
*/
|
||||
class SDMCWriteOnlyArchive : public SDMCArchive {
|
||||
public:
|
||||
explicit SDMCWriteOnlyArchive(const std::string& mount_point,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: SDMCArchive(mount_point, std::move(delay_generator_)) {}
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMCWriteOnlyArchive: " + mount_point;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) override;
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||
|
||||
private:
|
||||
SDMCWriteOnlyArchive() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<SDMCArchive>(*this);
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/// File system interface to the SDMC write-only archive
|
||||
class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory {
|
||||
public:
|
||||
explicit ArchiveFactory_SDMCWriteOnly(const std::string& mount_point);
|
||||
|
||||
/**
|
||||
* Initialize the archive.
|
||||
* @return true if it initialized successfully
|
||||
*/
|
||||
bool Initialize();
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMCWriteOnly";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
private:
|
||||
std::string sdmc_directory;
|
||||
|
||||
ArchiveFactory_SDMCWriteOnly() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& sdmc_directory;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class SDMCWriteOnlyDelayGenerator;
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::SDMCWriteOnlyArchive)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SDMCWriteOnly)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::SDMCWriteOnlyDelayGenerator)
|
||||
315
src/core/file_sys/archive_selfncch.cpp
Normal file
315
src/core/file_sys/archive_selfncch.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/archive_selfncch.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/ivfc_archive.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SelfNCCH)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class SelfNCCHFilePathType : u32 {
|
||||
RomFS = 0,
|
||||
Code = 1, // This is not supported by SelfNCCHArchive but by archive 0x2345678E
|
||||
ExeFS = 2,
|
||||
UpdateRomFS = 5, // This is presumably for accessing the RomFS of the update patch.
|
||||
};
|
||||
|
||||
struct SelfNCCHFilePath {
|
||||
enum_le<SelfNCCHFilePathType> type;
|
||||
std::array<char, 8> exefs_filename;
|
||||
};
|
||||
static_assert(sizeof(SelfNCCHFilePath) == 12, "NCCHFilePath has wrong size!");
|
||||
|
||||
// A read-only file created from a block of data. It only allows you to read the entire file at
|
||||
// once, in a single read operation.
|
||||
class ExeFSSectionFile final : public FileBackend {
|
||||
public:
|
||||
explicit ExeFSSectionFile(std::shared_ptr<std::vector<u8>> data_) : data(std::move(data_)) {}
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override {
|
||||
if (offset != 0) {
|
||||
LOG_ERROR(Service_FS, "offset must be zero!");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
if (length != data->size()) {
|
||||
LOG_ERROR(Service_FS, "size must match the file size!");
|
||||
return ResultIncorrectExefsReadSize;
|
||||
}
|
||||
|
||||
std::memcpy(buffer, data->data(), data->size());
|
||||
return data->size();
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override {
|
||||
LOG_ERROR(Service_FS, "The file is read-only!");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
u64 GetSize() const override {
|
||||
return data->size();
|
||||
}
|
||||
|
||||
bool SetSize(u64 size) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Close() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Flush() const override {}
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::vector<u8>> data;
|
||||
|
||||
ExeFSSectionFile() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||
ar& data;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
// SelfNCCHArchive represents the running application itself. From this archive the application can
|
||||
// open RomFS and ExeFS, excluding the .code section.
|
||||
class SelfNCCHArchive final : public ArchiveBackend {
|
||||
public:
|
||||
explicit SelfNCCHArchive(const NCCHData& ncch_data_) : ncch_data(ncch_data_) {}
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SelfNCCHArchive";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&,
|
||||
u32 attributes) override {
|
||||
// Note: SelfNCCHArchive doesn't check the open mode.
|
||||
|
||||
if (path.GetType() != LowPathType::Binary) {
|
||||
LOG_ERROR(Service_FS, "Path need to be Binary");
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
std::vector<u8> binary = path.AsBinary();
|
||||
if (binary.size() != sizeof(SelfNCCHFilePath)) {
|
||||
LOG_ERROR(Service_FS, "Wrong path size {}", binary.size());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
SelfNCCHFilePath file_path;
|
||||
std::memcpy(&file_path, binary.data(), sizeof(SelfNCCHFilePath));
|
||||
|
||||
switch (file_path.type) {
|
||||
case SelfNCCHFilePathType::UpdateRomFS:
|
||||
return OpenUpdateRomFS();
|
||||
|
||||
case SelfNCCHFilePathType::RomFS:
|
||||
return OpenRomFS();
|
||||
|
||||
case SelfNCCHFilePathType::Code:
|
||||
LOG_ERROR(Service_FS, "Reading the code section is not supported!");
|
||||
return ResultCommandNotAllowed;
|
||||
|
||||
case SelfNCCHFilePathType::ExeFS: {
|
||||
const auto& raw = file_path.exefs_filename;
|
||||
auto end = std::find(raw.begin(), raw.end(), '\0');
|
||||
std::string filename(raw.begin(), end);
|
||||
return OpenExeFS(filename);
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Service_FS, "Unknown file type {}!", file_path.type);
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
}
|
||||
|
||||
Result DeleteFile(const Path& path) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
Result DeleteDirectory(const Path& path) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
Result DeleteDirectoryRecursively(const Path& path) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
Result CreateFile(const Path& path, u64 size, u32 attributes) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
Result CreateDirectory(const Path& path, u32 attributes) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override {
|
||||
LOG_ERROR(Service_FS, "Unsupported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
u64 GetFreeBytes() const override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenRomFS() const {
|
||||
if (ncch_data.romfs_file) {
|
||||
std::unique_ptr<DelayGenerator> delay_generator =
|
||||
std::make_unique<RomFSDelayGenerator>();
|
||||
return std::make_unique<IVFCFile>(ncch_data.romfs_file, std::move(delay_generator));
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Unable to read RomFS");
|
||||
return ResultRomfsNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenUpdateRomFS() const {
|
||||
if (ncch_data.update_romfs_file) {
|
||||
std::unique_ptr<DelayGenerator> delay_generator =
|
||||
std::make_unique<RomFSDelayGenerator>();
|
||||
return std::make_unique<IVFCFile>(ncch_data.update_romfs_file,
|
||||
std::move(delay_generator));
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Unable to read update RomFS");
|
||||
return ResultRomfsNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const {
|
||||
if (filename == "icon") {
|
||||
if (ncch_data.icon) {
|
||||
return std::make_unique<ExeFSSectionFile>(ncch_data.icon);
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_FS, "Unable to read icon");
|
||||
return ResultExefsSectionNotFound;
|
||||
}
|
||||
|
||||
if (filename == "logo") {
|
||||
if (ncch_data.logo) {
|
||||
return std::make_unique<ExeFSSectionFile>(ncch_data.logo);
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_FS, "Unable to read logo");
|
||||
return ResultExefsSectionNotFound;
|
||||
}
|
||||
|
||||
if (filename == "banner") {
|
||||
if (ncch_data.banner) {
|
||||
return std::make_unique<ExeFSSectionFile>(ncch_data.banner);
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_FS, "Unable to read banner");
|
||||
return ResultExefsSectionNotFound;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS, "Unknown ExeFS section {}!", filename);
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
NCCHData ncch_data;
|
||||
|
||||
SelfNCCHArchive() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveBackend>(*this);
|
||||
ar& ncch_data;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) {
|
||||
u64 program_id = 0;
|
||||
if (app_loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) {
|
||||
LOG_WARNING(
|
||||
Service_FS,
|
||||
"Could not read program id when registering with SelfNCCH, this might be a 3dsx file");
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Registering program {:016X} with the SelfNCCH archive factory",
|
||||
program_id);
|
||||
|
||||
if (ncch_data.find(program_id) != ncch_data.end()) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Registering program {:016X} with SelfNCCH will override existing mapping",
|
||||
program_id);
|
||||
}
|
||||
|
||||
NCCHData& data = ncch_data[program_id];
|
||||
|
||||
std::shared_ptr<RomFSReader> romfs_file_;
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadRomFS(romfs_file_)) {
|
||||
|
||||
data.romfs_file = std::move(romfs_file_);
|
||||
}
|
||||
|
||||
std::shared_ptr<RomFSReader> update_romfs_file;
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadUpdateRomFS(update_romfs_file)) {
|
||||
|
||||
data.update_romfs_file = std::move(update_romfs_file);
|
||||
}
|
||||
|
||||
std::vector<u8> buffer;
|
||||
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer))
|
||||
data.icon = std::make_shared<std::vector<u8>>(std::move(buffer));
|
||||
|
||||
buffer.clear();
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer))
|
||||
data.logo = std::make_shared<std::vector<u8>>(std::move(buffer));
|
||||
|
||||
buffer.clear();
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer))
|
||||
data.banner = std::make_shared<std::vector<u8>>(std::move(buffer));
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
return std::make_unique<SelfNCCHArchive>(ncch_data[program_id]);
|
||||
}
|
||||
|
||||
Result ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&,
|
||||
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||
LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive.");
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SelfNCCH::GetFormatInfo(const Path&,
|
||||
u64 program_id) const {
|
||||
LOG_ERROR(Service_FS, "Attempted to get format info of a SelfNCCH archive");
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ExeFSSectionFile)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SelfNCCHArchive)
|
||||
76
src/core/file_sys/archive_selfncch.h
Normal file
76
src/core/file_sys/archive_selfncch.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include <boost/serialization/unordered_map.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct NCCHData {
|
||||
std::shared_ptr<std::vector<u8>> icon;
|
||||
std::shared_ptr<std::vector<u8>> logo;
|
||||
std::shared_ptr<std::vector<u8>> banner;
|
||||
std::shared_ptr<RomFSReader> romfs_file;
|
||||
std::shared_ptr<RomFSReader> update_romfs_file;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& icon;
|
||||
ar& logo;
|
||||
ar& banner;
|
||||
ar& romfs_file;
|
||||
ar& update_romfs_file;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/// File system interface to the SelfNCCH archive
|
||||
class ArchiveFactory_SelfNCCH final : public ArchiveFactory {
|
||||
public:
|
||||
ArchiveFactory_SelfNCCH() = default;
|
||||
|
||||
/// Registers a loaded application so that we can open its SelfNCCH archive when requested.
|
||||
void Register(Loader::AppLoader& app_loader);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SelfNCCH";
|
||||
}
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
private:
|
||||
/// Mapping of ProgramId -> NCCHData
|
||||
std::unordered_map<u64, NCCHData> ncch_data;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& ncch_data;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class ExeFSSectionFile;
|
||||
class SelfNCCHArchive;
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SelfNCCH)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ExeFSSectionFile)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::SelfNCCHArchive)
|
||||
146
src/core/file_sys/archive_source_sd_savedata.cpp
Normal file
146
src/core/file_sys/archive_source_sd_savedata.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/archives.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/archive_artic.h"
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/savedata_archive.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveSource_SDSaveData)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetSaveDataContainerPath(const std::string& sdmc_directory) {
|
||||
return fmt::format("{}Nintendo 3DS/{}/{}/title/", sdmc_directory, SYSTEM_ID, SDCARD_ID);
|
||||
}
|
||||
|
||||
std::string GetSaveDataPath(const std::string& mount_location, u64 program_id) {
|
||||
u32 high = static_cast<u32>(program_id >> 32);
|
||||
u32 low = static_cast<u32>(program_id & 0xFFFFFFFF);
|
||||
return fmt::format("{}{:08x}/{:08x}/data/00000001/", mount_location, high, low);
|
||||
}
|
||||
|
||||
std::string GetSaveDataMetadataPath(const std::string& mount_location, u64 program_id) {
|
||||
u32 high = static_cast<u32>(program_id >> 32);
|
||||
u32 low = static_cast<u32>(program_id & 0xFFFFFFFF);
|
||||
return fmt::format("{}{:08x}/{:08x}/data/00000001.metadata", mount_location, high, low);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_directory)
|
||||
: mount_point(GetSaveDataContainerPath(sdmc_directory)) {
|
||||
LOG_DEBUG(Service_FS, "Directory {} set as SaveData.", mount_point);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(
|
||||
Service::FS::ArchiveIdCode archive_id, const Path& path, u64 program_id) {
|
||||
if (IsUsingArtic()) {
|
||||
EnsureCacheCreated();
|
||||
return ArticArchive::Open(artic_client, archive_id, path,
|
||||
Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA, *this,
|
||||
archive_id != Service::FS::ArchiveIdCode::SaveData);
|
||||
} else {
|
||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
||||
if (!FileUtil::Exists(concrete_mount_point)) {
|
||||
// When a SaveData archive is created for the first time, it is not yet formatted and
|
||||
// the save file/directory structure expected by the game has not yet been initialized.
|
||||
// Returning the NotFormatted error code will signal the game to provision the SaveData
|
||||
// archive with the files and folders that it expects.
|
||||
return ResultNotFormatted;
|
||||
}
|
||||
|
||||
return std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
|
||||
}
|
||||
}
|
||||
|
||||
Result ArchiveSource_SDSaveData::Format(u64 program_id,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
Service::FS::ArchiveIdCode archive_id, const Path& path,
|
||||
u32 directory_buckets, u32 file_buckets) {
|
||||
if (IsUsingArtic()) {
|
||||
ClearAllCache();
|
||||
auto req = artic_client->NewRequest("FSUSER_FormatSaveData");
|
||||
|
||||
req.AddParameterS32(static_cast<u32>(archive_id));
|
||||
auto artic_path = ArticArchive::BuildFSPath(path);
|
||||
req.AddParameterBuffer(artic_path.data(), artic_path.size());
|
||||
req.AddParameterU32(format_info.total_size / 512);
|
||||
req.AddParameterU32(format_info.number_directories);
|
||||
req.AddParameterU32(format_info.number_files);
|
||||
req.AddParameterU32(directory_buckets);
|
||||
req.AddParameterU32(file_buckets);
|
||||
req.AddParameterU8(format_info.duplicate_data);
|
||||
|
||||
auto resp = artic_client->Send(req);
|
||||
return ArticArchive::RespResult(resp);
|
||||
} else {
|
||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
||||
FileUtil::DeleteDirRecursively(concrete_mount_point);
|
||||
FileUtil::CreateFullPath(concrete_mount_point);
|
||||
|
||||
// Write the format metadata
|
||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
||||
FileUtil::IOFile file(metadata_path, "wb");
|
||||
|
||||
if (file.IsOpen()) {
|
||||
file.WriteBytes(&format_info, sizeof(format_info));
|
||||
return ResultSuccess;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(
|
||||
u64 program_id, Service::FS::ArchiveIdCode archive_id, const Path& path) const {
|
||||
if (IsUsingArtic()) {
|
||||
auto req = artic_client->NewRequest("FSUSER_GetFormatInfo");
|
||||
|
||||
req.AddParameterS32(static_cast<u32>(archive_id));
|
||||
auto path_artic = ArticArchive::BuildFSPath(path);
|
||||
req.AddParameterBuffer(path_artic.data(), path_artic.size());
|
||||
|
||||
auto resp = artic_client->Send(req);
|
||||
Result res = ArticArchive::RespResult(resp);
|
||||
if (R_FAILED(res)) {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto info_buf = resp->GetResponseBuffer(0);
|
||||
if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
ArchiveFormatInfo info;
|
||||
memcpy(&info, info_buf->first, sizeof(info));
|
||||
return info;
|
||||
} else {
|
||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
||||
FileUtil::IOFile file(metadata_path, "rb");
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
||||
// TODO(Subv): Verify error code
|
||||
return ResultNotFormatted;
|
||||
}
|
||||
|
||||
ArchiveFormatInfo info = {};
|
||||
file.ReadBytes(&info, sizeof(info));
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point,
|
||||
u64 program_id) {
|
||||
return GetSaveDataPath(GetSaveDataContainerPath(mount_point), program_id);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
61
src/core/file_sys/archive_source_sd_savedata.h
Normal file
61
src/core/file_sys/archive_source_sd_savedata.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/string.hpp>
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/artic_cache.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace Service::FS {
|
||||
enum class ArchiveIdCode : u32;
|
||||
} // namespace Service::FS
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// A common source of SD save data archive
|
||||
class ArchiveSource_SDSaveData : public ArticCacheProvider {
|
||||
public:
|
||||
explicit ArchiveSource_SDSaveData(const std::string& mount_point);
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(Service::FS::ArchiveIdCode archive_id,
|
||||
const Path& path, u64 program_id);
|
||||
Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info,
|
||||
Service::FS::ArchiveIdCode archive_id, const Path& path, u32 directory_buckets,
|
||||
u32 file_buckets);
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id,
|
||||
Service::FS::ArchiveIdCode archive_id,
|
||||
const Path& path) const;
|
||||
|
||||
static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id);
|
||||
|
||||
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||
artic_client = client;
|
||||
}
|
||||
|
||||
bool IsUsingArtic() const {
|
||||
return artic_client.get() != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mount_point;
|
||||
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||
|
||||
ArchiveSource_SDSaveData() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArticCacheProvider>(*this);
|
||||
ar& mount_point;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveSource_SDSaveData)
|
||||
145
src/core/file_sys/archive_systemsavedata.cpp
Normal file
145
src/core/file_sys/archive_systemsavedata.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/archive_artic.h"
|
||||
#include "core/file_sys/archive_systemsavedata.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/savedata_archive.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SystemSaveData)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
std::string GetSystemSaveDataPath(std::string_view mount_point, const Path& path) {
|
||||
const std::vector<u8> vec_data = path.AsBinary();
|
||||
u32 save_low;
|
||||
u32 save_high;
|
||||
std::memcpy(&save_low, &vec_data[4], sizeof(u32));
|
||||
std::memcpy(&save_high, &vec_data[0], sizeof(u32));
|
||||
return fmt::format("{}{:08X}/{:08X}/", mount_point, save_low, save_high);
|
||||
}
|
||||
|
||||
std::string GetSystemSaveDataContainerPath(std::string_view mount_point) {
|
||||
return fmt::format("{}data/{}/sysdata/", mount_point, SYSTEM_ID);
|
||||
}
|
||||
|
||||
Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low) {
|
||||
std::vector<u8> binary_path;
|
||||
binary_path.reserve(8);
|
||||
|
||||
// Append each word byte by byte
|
||||
|
||||
// First is the high word
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
binary_path.push_back((high >> (8 * i)) & 0xFF);
|
||||
|
||||
// Next is the low word
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
binary_path.push_back((low >> (8 * i)) & 0xFF);
|
||||
|
||||
return {std::move(binary_path)};
|
||||
}
|
||||
|
||||
ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string& nand_path)
|
||||
: base_path(GetSystemSaveDataContainerPath(nand_path)) {}
|
||||
|
||||
static bool AllowArticSystemSaveData(const Path& path) {
|
||||
constexpr u32 APP_SYSTEM_SAVE_DATA_MASK = 0x00020000;
|
||||
if (path.GetType() == FileSys::LowPathType::Binary) {
|
||||
std::vector<u8> path_data = path.AsBinary();
|
||||
return path_data.size() == 8 &&
|
||||
(*reinterpret_cast<u32*>(path_data.data() + 4) & APP_SYSTEM_SAVE_DATA_MASK) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path,
|
||||
u64 program_id) {
|
||||
if (IsUsingArtic() && AllowArticSystemSaveData(path)) {
|
||||
EnsureCacheCreated();
|
||||
return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::SystemSaveData, path,
|
||||
Core::PerfStats::PerfArticEventBits::ARTIC_SYSTEM_SAVE_DATA,
|
||||
*this, false);
|
||||
} else {
|
||||
std::string fullpath = GetSystemSaveDataPath(base_path, path);
|
||||
if (!FileUtil::Exists(fullpath)) {
|
||||
// TODO(Subv): Check error code, this one is probably wrong
|
||||
return ResultNotFound;
|
||||
}
|
||||
return std::make_unique<SaveDataArchive>(fullpath);
|
||||
}
|
||||
}
|
||||
|
||||
Result ArchiveFactory_SystemSaveData::Format(const Path& path,
|
||||
const FileSys::ArchiveFormatInfo& format_info,
|
||||
u64 program_id, u32 directory_buckets,
|
||||
u32 file_buckets) {
|
||||
const std::vector<u8> vec_data = path.AsBinary();
|
||||
u32 save_low;
|
||||
u32 save_high;
|
||||
std::memcpy(&save_low, &vec_data[4], sizeof(u32));
|
||||
std::memcpy(&save_high, &vec_data[0], sizeof(u32));
|
||||
return FormatAsSysData(save_high, save_low, format_info.total_size, 0x1000,
|
||||
format_info.number_directories, format_info.number_files,
|
||||
directory_buckets, file_buckets, format_info.duplicate_data);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SystemSaveData::GetFormatInfo(const Path& path,
|
||||
u64 program_id) const {
|
||||
// TODO(Subv): Implement
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result ArchiveFactory_SystemSaveData::FormatAsSysData(u32 high, u32 low, u32 total_size,
|
||||
u32 block_size, u32 number_directories,
|
||||
u32 number_files,
|
||||
u32 number_directory_buckets,
|
||||
u32 number_file_buckets, u8 duplicate_data) {
|
||||
if (IsUsingArtic() &&
|
||||
AllowArticSystemSaveData(FileSys::ConstructSystemSaveDataBinaryPath(high, low))) {
|
||||
auto req = artic_client->NewRequest("FSUSER_CreateSysSaveData");
|
||||
|
||||
req.AddParameterU32(high);
|
||||
req.AddParameterU32(low);
|
||||
req.AddParameterU32(total_size);
|
||||
req.AddParameterU32(block_size);
|
||||
req.AddParameterU32(number_directories);
|
||||
req.AddParameterU32(number_files);
|
||||
req.AddParameterU32(number_directory_buckets);
|
||||
req.AddParameterU32(number_file_buckets);
|
||||
req.AddParameterU8(duplicate_data);
|
||||
|
||||
auto resp = artic_client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded()) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result res(static_cast<u32>(resp->GetMethodResult()));
|
||||
return res;
|
||||
|
||||
} else {
|
||||
// Construct the binary path to the archive first
|
||||
const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low);
|
||||
|
||||
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||
const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory);
|
||||
const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path);
|
||||
if (!FileUtil::CreateFullPath(systemsavedata_path)) {
|
||||
return ResultUnknown; // TODO(Subv): Find the right error code
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
92
src/core/file_sys/archive_systemsavedata.h
Normal file
92
src/core/file_sys/archive_systemsavedata.h
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/string.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/artic_cache.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SystemSaveData archive
|
||||
class ArchiveFactory_SystemSaveData final : public ArchiveFactory, public ArticCacheProvider {
|
||||
public:
|
||||
explicit ArchiveFactory_SystemSaveData(const std::string& mount_point);
|
||||
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||
u32 directory_buckets, u32 file_buckets) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||
|
||||
Result FormatAsSysData(u32 high, u32 low, u32 total_size, u32 block_size,
|
||||
u32 number_directories, u32 number_files, u32 number_directory_buckets,
|
||||
u32 number_file_buckets, u8 duplicate_data);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SystemSaveData";
|
||||
}
|
||||
|
||||
bool IsSlow() override {
|
||||
return IsUsingArtic();
|
||||
}
|
||||
|
||||
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||
artic_client = client;
|
||||
}
|
||||
|
||||
bool IsUsingArtic() const {
|
||||
return artic_client.get() != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string base_path;
|
||||
|
||||
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||
|
||||
ArchiveFactory_SystemSaveData() = default;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||
ar& base_path;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a path to the concrete SystemSaveData archive in the host filesystem based on the
|
||||
* input Path and base mount point.
|
||||
* @param mount_point The base mount point of the SystemSaveData archives.
|
||||
* @param path The path that identifies the requested concrete SystemSaveData archive.
|
||||
* @returns The complete path to the specified SystemSaveData archive in the host filesystem
|
||||
*/
|
||||
std::string GetSystemSaveDataPath(std::string_view mount_point, const Path& path);
|
||||
|
||||
/**
|
||||
* Constructs a path to the base folder to hold concrete SystemSaveData archives in the host file
|
||||
* system.
|
||||
* @param mount_point The base folder where this folder resides, ie. SDMC or NAND.
|
||||
* @returns The path to the base SystemSaveData archives' folder in the host file system
|
||||
*/
|
||||
std::string GetSystemSaveDataContainerPath(std::string_view mount_point);
|
||||
|
||||
/**
|
||||
* Constructs a FileSys::Path object that refers to the SystemSaveData archive identified by
|
||||
* the specified high save id and low save id.
|
||||
* @param high The high word of the save id for the archive
|
||||
* @param low The low word of the save id for the archive
|
||||
* @returns A FileSys::Path to the wanted archive
|
||||
*/
|
||||
Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low);
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SystemSaveData)
|
||||
236
src/core/file_sys/artic_cache.cpp
Normal file
236
src/core/file_sys/artic_cache.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "artic_cache.h"
|
||||
|
||||
namespace FileSys {
|
||||
ResultVal<std::size_t> ArticCache::Read(s32 file_handle, std::size_t offset, std::size_t length,
|
||||
u8* buffer) {
|
||||
if (length == 0)
|
||||
return size_t();
|
||||
|
||||
const auto segments = BreakupRead(offset, length);
|
||||
std::size_t read_progress = 0;
|
||||
|
||||
// Skip cache if the read is too big
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
if (segments[0].second < big_cache_skip) {
|
||||
std::unique_lock big_read_guard(big_cache_mutex);
|
||||
auto big_cache_entry = big_cache.request(std::make_pair(offset, length));
|
||||
if (!big_cache_entry.first) {
|
||||
LOG_TRACE(Service_FS, "ArticCache BMISS: offset={}, length={}", offset, length);
|
||||
big_cache_entry.second.clear();
|
||||
big_cache_entry.second.resize(length);
|
||||
auto res =
|
||||
ReadFromArtic(file_handle, reinterpret_cast<u8*>(big_cache_entry.second.data()),
|
||||
length, offset);
|
||||
if (res.Failed())
|
||||
return res;
|
||||
length = res.Unwrap();
|
||||
} else {
|
||||
LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, length);
|
||||
}
|
||||
memcpy(buffer, big_cache_entry.second.data(), length);
|
||||
} else {
|
||||
if (segments[0].second < very_big_cache_skip) {
|
||||
std::unique_lock very_big_read_guard(very_big_cache_mutex);
|
||||
auto very_big_cache_entry = very_big_cache.request(std::make_pair(offset, length));
|
||||
if (!very_big_cache_entry.first) {
|
||||
LOG_TRACE(Service_FS, "ArticCache VBMISS: offset={}, length={}", offset,
|
||||
length);
|
||||
very_big_cache_entry.second.clear();
|
||||
very_big_cache_entry.second.resize(length);
|
||||
auto res = ReadFromArtic(
|
||||
file_handle, reinterpret_cast<u8*>(very_big_cache_entry.second.data()),
|
||||
length, offset);
|
||||
if (res.Failed())
|
||||
return res;
|
||||
length = res.Unwrap();
|
||||
} else {
|
||||
LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, length);
|
||||
}
|
||||
memcpy(buffer, very_big_cache_entry.second.data(), length);
|
||||
} else {
|
||||
LOG_TRACE(Service_FS, "ArticCache SKIP: offset={}, length={}", offset, length);
|
||||
|
||||
auto res = ReadFromArtic(file_handle, buffer, length, offset);
|
||||
if (res.Failed())
|
||||
return res;
|
||||
length = res.Unwrap();
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
std::unique_lock read_guard(cache_mutex);
|
||||
for (const auto& seg : segments) {
|
||||
std::size_t read_size = cache_line_size;
|
||||
std::size_t page = OffsetToPage(seg.first);
|
||||
// Check if segment is in cache
|
||||
auto cache_entry = cache.request(page);
|
||||
if (!cache_entry.first) {
|
||||
// If not found, read from artic and cache the data
|
||||
auto res = ReadFromArtic(file_handle, cache_entry.second.data(), read_size, page);
|
||||
if (res.Failed())
|
||||
return res;
|
||||
read_size = res.Unwrap();
|
||||
LOG_TRACE(Service_FS, "ArticCache MISS: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
} else {
|
||||
LOG_TRACE(Service_FS, "ArticCache HIT: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
}
|
||||
std::size_t copy_amount =
|
||||
(read_size > (seg.first - page))
|
||||
? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page)
|
||||
: 0;
|
||||
std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page),
|
||||
copy_amount);
|
||||
read_progress += copy_amount;
|
||||
}
|
||||
return read_progress;
|
||||
}
|
||||
|
||||
bool ArticCache::CacheReady(std::size_t file_offset, std::size_t length) {
|
||||
auto segments = BreakupRead(file_offset, length);
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
return false;
|
||||
} else {
|
||||
std::shared_lock read_guard(cache_mutex);
|
||||
for (auto it = segments.begin(); it != segments.end(); it++) {
|
||||
if (!cache.contains(OffsetToPage(it->first)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void ArticCache::Clear() {
|
||||
std::unique_lock l1(cache_mutex), l2(big_cache_mutex), l3(very_big_cache_mutex);
|
||||
cache.clear();
|
||||
big_cache.clear();
|
||||
very_big_cache.clear();
|
||||
data_size = std::nullopt;
|
||||
}
|
||||
|
||||
ResultVal<size_t> ArticCache::Write(s32 file_handle, std::size_t offset, std::size_t length,
|
||||
const u8* buffer, u32 flags) {
|
||||
// Can probably do better, but write operations are usually done at the end, so it doesn't
|
||||
// matter much
|
||||
Clear();
|
||||
|
||||
size_t written_amount = 0;
|
||||
while (written_amount != length) {
|
||||
size_t to_write =
|
||||
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, length - written_amount);
|
||||
|
||||
auto req = client->NewRequest("FSFILE_Write");
|
||||
req.AddParameterS32(file_handle);
|
||||
req.AddParameterS64(static_cast<s64>(offset + written_amount));
|
||||
req.AddParameterS32(static_cast<s32>(to_write));
|
||||
req.AddParameterS32(static_cast<s32>(flags));
|
||||
req.AddParameterBuffer(buffer + written_amount, to_write);
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded())
|
||||
return Result(-1);
|
||||
|
||||
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto actually_written_opt = resp->GetResponseS32(0);
|
||||
if (!actually_written_opt.has_value())
|
||||
return Result(-1);
|
||||
|
||||
size_t actually_written = static_cast<size_t>(actually_written_opt.value());
|
||||
|
||||
written_amount += actually_written;
|
||||
if (actually_written != to_write)
|
||||
break;
|
||||
}
|
||||
return written_amount;
|
||||
}
|
||||
|
||||
ResultVal<size_t> ArticCache::GetSize(s32 file_handle) {
|
||||
std::unique_lock l1(cache_mutex);
|
||||
|
||||
if (data_size.has_value())
|
||||
return data_size.value();
|
||||
|
||||
auto req = client->NewRequest("FSFILE_GetSize");
|
||||
|
||||
req.AddParameterS32(file_handle);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded())
|
||||
return Result(-1);
|
||||
|
||||
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto size_buf = resp->GetResponseS64(0);
|
||||
if (!size_buf) {
|
||||
return Result(-1);
|
||||
}
|
||||
|
||||
data_size = static_cast<size_t>(*size_buf);
|
||||
return data_size.value();
|
||||
}
|
||||
|
||||
ResultVal<size_t> ArticCache::ReadFromArtic(s32 file_handle, u8* buffer, size_t len,
|
||||
size_t offset) {
|
||||
size_t read_amount = 0;
|
||||
while (read_amount != len) {
|
||||
size_t to_read =
|
||||
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, len - read_amount);
|
||||
|
||||
auto req = client->NewRequest("FSFILE_Read");
|
||||
req.AddParameterS32(file_handle);
|
||||
req.AddParameterS64(static_cast<s64>(offset + read_amount));
|
||||
req.AddParameterS32(static_cast<s32>(to_read));
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value() || !resp->Succeeded())
|
||||
return Result(-1);
|
||||
|
||||
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||
if (res.IsError())
|
||||
return res;
|
||||
|
||||
auto read_buff = resp->GetResponseBuffer(0);
|
||||
size_t actually_read = 0;
|
||||
if (read_buff.has_value()) {
|
||||
actually_read = read_buff->second;
|
||||
memcpy(buffer + read_amount, read_buff->first, actually_read);
|
||||
}
|
||||
|
||||
read_amount += actually_read;
|
||||
if (actually_read != to_read)
|
||||
break;
|
||||
}
|
||||
return read_amount;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> ArticCache::BreakupRead(std::size_t offset,
|
||||
std::size_t length) {
|
||||
std::vector<std::pair<std::size_t, std::size_t>> ret;
|
||||
|
||||
// Reads bigger than the cache line size will probably never hit again
|
||||
if (length > max_breakup_size) {
|
||||
ret.push_back(std::make_pair(offset, length));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::size_t curr_offset = offset;
|
||||
while (length) {
|
||||
std::size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
||||
std::size_t curr_page_len = std::min(length, next_page - curr_offset);
|
||||
ret.push_back(std::make_pair(curr_offset, curr_page_len));
|
||||
curr_offset = next_page;
|
||||
length -= curr_page_len;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} // namespace FileSys
|
||||
154
src/core/file_sys/artic_cache.h
Normal file
154
src/core/file_sys/artic_cache.h
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <shared_mutex>
|
||||
#include "vector"
|
||||
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/static_lru_cache.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace FileSys {
|
||||
class ArticCache {
|
||||
public:
|
||||
ArticCache() = default;
|
||||
|
||||
ArticCache(const std::shared_ptr<Network::ArticBase::Client>& cli) : client(cli) {}
|
||||
|
||||
ResultVal<std::size_t> Read(s32 file_handle, std::size_t offset, std::size_t length,
|
||||
u8* buffer);
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length);
|
||||
|
||||
void Clear();
|
||||
|
||||
ResultVal<std::size_t> Write(s32 file_handle, std::size_t offset, std::size_t length,
|
||||
const u8* buffer, u32 flags);
|
||||
|
||||
ResultVal<size_t> GetSize(s32 file_handle);
|
||||
|
||||
void ForceSetSize(const std::optional<size_t>& size) {
|
||||
data_size = size;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
std::optional<size_t> data_size;
|
||||
|
||||
// Total cache size: 32MB small, 512MB big (worst case), 160MB very big (worst case).
|
||||
// The worst case values are unrealistic, they will never happen in any real game.
|
||||
static constexpr std::size_t cache_line_size = 4 * 1024;
|
||||
static constexpr std::size_t cache_line_count = 256;
|
||||
static constexpr std::size_t max_breakup_size = 8 * 1024;
|
||||
|
||||
static constexpr std::size_t big_cache_skip = 1 * 1024 * 1024;
|
||||
static constexpr std::size_t big_cache_lines = 1024;
|
||||
|
||||
static constexpr std::size_t very_big_cache_skip = 10 * 1024 * 1024;
|
||||
static constexpr std::size_t very_big_cache_lines = 24;
|
||||
|
||||
Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache;
|
||||
std::shared_mutex cache_mutex;
|
||||
|
||||
struct NoInitChar {
|
||||
u8 value;
|
||||
NoInitChar() noexcept {
|
||||
// do nothing
|
||||
static_assert(sizeof *this == sizeof value, "invalid size");
|
||||
}
|
||||
};
|
||||
Common::StaticLRUCache<std::pair<std::size_t, std::size_t>, std::vector<NoInitChar>,
|
||||
big_cache_lines>
|
||||
big_cache;
|
||||
std::shared_mutex big_cache_mutex;
|
||||
Common::StaticLRUCache<std::pair<std::size_t, std::size_t>, std::vector<NoInitChar>,
|
||||
very_big_cache_lines>
|
||||
very_big_cache;
|
||||
std::shared_mutex very_big_cache_mutex;
|
||||
|
||||
ResultVal<std::size_t> ReadFromArtic(s32 file_handle, u8* buffer, size_t len, size_t offset);
|
||||
|
||||
std::size_t OffsetToPage(std::size_t offset) {
|
||||
return Common::AlignDown<std::size_t>(offset, cache_line_size);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> BreakupRead(std::size_t offset,
|
||||
std::size_t length);
|
||||
|
||||
protected:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class ArticCacheProvider {
|
||||
public:
|
||||
virtual ~ArticCacheProvider() {}
|
||||
|
||||
std::vector<u8> PathsToVector(const Path& archive_path, const Path& file_path) {
|
||||
auto archive_path_binary = archive_path.AsBinary();
|
||||
auto file_path_binary = file_path.AsBinary();
|
||||
|
||||
std::vector<u8> ret;
|
||||
ret.push_back(static_cast<u8>(file_path.GetType()));
|
||||
ret.insert(ret.end(), archive_path_binary.begin(), archive_path_binary.end());
|
||||
ret.push_back(static_cast<u8>(archive_path.GetType()));
|
||||
ret.insert(ret.end(), file_path_binary.begin(), file_path_binary.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<ArticCache> ProvideCache(
|
||||
const std::shared_ptr<Network::ArticBase::Client>& cli, const std::vector<u8>& path,
|
||||
bool create) {
|
||||
if (file_caches == nullptr)
|
||||
return nullptr;
|
||||
|
||||
auto it = file_caches->find(path);
|
||||
if (it == file_caches->end()) {
|
||||
if (!create) {
|
||||
return nullptr;
|
||||
}
|
||||
auto res = std::make_shared<ArticCache>(cli);
|
||||
file_caches->insert({path, res});
|
||||
return res;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
virtual void ClearAllCache() {
|
||||
if (file_caches != nullptr) {
|
||||
file_caches->clear();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void EnsureCacheCreated() {
|
||||
if (file_caches == nullptr) {
|
||||
file_caches =
|
||||
std::make_unique<std::map<std::vector<u8>, std::shared_ptr<ArticCache>>>();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {}
|
||||
friend class boost::serialization::access;
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::map<std::vector<u8>, std::shared_ptr<ArticCache>>> file_caches = nullptr;
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArticCache)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArticCacheProvider)
|
||||
40
src/core/file_sys/cia_common.h
Normal file
40
src/core/file_sys/cia_common.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum TMDSignatureType : u32 {
|
||||
Rsa4096Sha1 = 0x10000,
|
||||
Rsa2048Sha1 = 0x10001,
|
||||
EllipticSha1 = 0x10002,
|
||||
Rsa4096Sha256 = 0x10003,
|
||||
Rsa2048Sha256 = 0x10004,
|
||||
EcdsaSha256 = 0x10005
|
||||
};
|
||||
|
||||
inline u32 GetSignatureSize(u32 signature_type) {
|
||||
switch (signature_type) {
|
||||
case Rsa4096Sha1:
|
||||
case Rsa4096Sha256:
|
||||
return 0x200;
|
||||
|
||||
case Rsa2048Sha1:
|
||||
case Rsa2048Sha256:
|
||||
return 0x100;
|
||||
|
||||
case EllipticSha1:
|
||||
case EcdsaSha256:
|
||||
return 0x3C;
|
||||
}
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "Tried to read ticket with bad signature {}", signature_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
258
src/core/file_sys/cia_container.cpp
Normal file
258
src/core/file_sys/cia_container.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cryptopp/sha.h>
|
||||
#include "common/alignment.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/cia_container.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
|
||||
std::vector<u8> header_data(sizeof(Header));
|
||||
|
||||
// Load the CIA Header
|
||||
ResultVal<std::size_t> read_result = backend.Read(0, sizeof(Header), header_data.data());
|
||||
if (read_result.Failed() || *read_result != sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
Loader::ResultStatus result = LoadHeader(header_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load Ticket
|
||||
std::vector<u8> ticket_data(cia_header.tik_size);
|
||||
read_result = backend.Read(GetTicketOffset(), cia_header.tik_size, ticket_data.data());
|
||||
if (read_result.Failed() || *read_result != cia_header.tik_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadTicket(ticket_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load Title Metadata
|
||||
std::vector<u8> tmd_data(cia_header.tmd_size);
|
||||
read_result = backend.Read(GetTitleMetadataOffset(), cia_header.tmd_size, tmd_data.data());
|
||||
if (read_result.Failed() || *read_result != cia_header.tmd_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadTitleMetadata(tmd_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load CIA Metadata
|
||||
if (cia_header.meta_size) {
|
||||
std::vector<u8> meta_data(sizeof(Metadata));
|
||||
read_result = backend.Read(GetMetadataOffset(), sizeof(Metadata), meta_data.data());
|
||||
if (read_result.Failed() || *read_result != sizeof(Metadata))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadMetadata(meta_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
|
||||
FileUtil::IOFile file(filepath, "rb");
|
||||
if (!file.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// Load CIA Header
|
||||
std::vector<u8> header_data(sizeof(Header));
|
||||
if (file.ReadBytes(header_data.data(), sizeof(Header)) != sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
Loader::ResultStatus result = LoadHeader(header_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load Ticket
|
||||
std::vector<u8> ticket_data(cia_header.tik_size);
|
||||
file.Seek(GetTicketOffset(), SEEK_SET);
|
||||
if (file.ReadBytes(ticket_data.data(), cia_header.tik_size) != cia_header.tik_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadTicket(ticket_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load Title Metadata
|
||||
std::vector<u8> tmd_data(cia_header.tmd_size);
|
||||
file.Seek(GetTitleMetadataOffset(), SEEK_SET);
|
||||
if (file.ReadBytes(tmd_data.data(), cia_header.tmd_size) != cia_header.tmd_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadTitleMetadata(tmd_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load CIA Metadata
|
||||
if (cia_header.meta_size) {
|
||||
std::vector<u8> meta_data(sizeof(Metadata));
|
||||
file.Seek(GetMetadataOffset(), SEEK_SET);
|
||||
if (file.ReadBytes(meta_data.data(), sizeof(Metadata)) != sizeof(Metadata))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadMetadata(meta_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::Load(std::span<const u8> file_data) {
|
||||
Loader::ResultStatus result = LoadHeader(file_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load Ticket
|
||||
result = LoadTicket(file_data, GetTicketOffset());
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load Title Metadata
|
||||
result = LoadTitleMetadata(file_data, GetTitleMetadataOffset());
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Load CIA Metadata
|
||||
if (cia_header.meta_size) {
|
||||
result = LoadMetadata(file_data, GetMetadataOffset());
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::LoadHeader(std::span<const u8> header_data, std::size_t offset) {
|
||||
if (header_data.size() - offset < sizeof(Header)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
std::memcpy(&cia_header, header_data.data(), sizeof(Header));
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::LoadTicket(std::span<const u8> ticket_data, std::size_t offset) {
|
||||
return cia_ticket.Load(ticket_data, offset);
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::LoadTitleMetadata(std::span<const u8> tmd_data,
|
||||
std::size_t offset) {
|
||||
return cia_tmd.Load(tmd_data, offset);
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::LoadMetadata(std::span<const u8> meta_data, std::size_t offset) {
|
||||
if (meta_data.size() - offset < sizeof(Metadata)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
std::memcpy(&cia_metadata, meta_data.data(), sizeof(Metadata));
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
const Ticket& CIAContainer::GetTicket() const {
|
||||
return cia_ticket;
|
||||
}
|
||||
|
||||
const TitleMetadata& CIAContainer::GetTitleMetadata() const {
|
||||
return cia_tmd;
|
||||
}
|
||||
|
||||
std::array<u64, 0x30>& CIAContainer::GetDependencies() {
|
||||
return cia_metadata.dependencies;
|
||||
}
|
||||
|
||||
u32 CIAContainer::GetCoreVersion() const {
|
||||
return cia_metadata.core_version;
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetCertificateOffset() const {
|
||||
return Common::AlignUp(cia_header.header_size, CIA_SECTION_ALIGNMENT);
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetTicketOffset() const {
|
||||
return Common::AlignUp(GetCertificateOffset() + cia_header.cert_size, CIA_SECTION_ALIGNMENT);
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetTitleMetadataOffset() const {
|
||||
return Common::AlignUp(GetTicketOffset() + cia_header.tik_size, CIA_SECTION_ALIGNMENT);
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetMetadataOffset() const {
|
||||
u64 tmd_end_offset = GetContentOffset();
|
||||
|
||||
// Meta exists after all content in the CIA
|
||||
u64 offset = Common::AlignUp(tmd_end_offset + cia_header.content_size, CIA_SECTION_ALIGNMENT);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetContentOffset(std::size_t index) const {
|
||||
u64 offset =
|
||||
Common::AlignUp(GetTitleMetadataOffset() + cia_header.tmd_size, CIA_SECTION_ALIGNMENT);
|
||||
for (std::size_t i = 0; i < index; i++) {
|
||||
offset += GetContentSize(i);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
u32 CIAContainer::GetCertificateSize() const {
|
||||
return cia_header.cert_size;
|
||||
}
|
||||
|
||||
u32 CIAContainer::GetTicketSize() const {
|
||||
return cia_header.tik_size;
|
||||
}
|
||||
|
||||
u32 CIAContainer::GetTitleMetadataSize() const {
|
||||
return cia_header.tmd_size;
|
||||
}
|
||||
|
||||
u32 CIAContainer::GetMetadataSize() const {
|
||||
return cia_header.meta_size;
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetTotalContentSize() const {
|
||||
return cia_header.content_size;
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetContentSize(std::size_t index) const {
|
||||
// If the content doesn't exist in the CIA, it doesn't have a size.
|
||||
if (!cia_header.IsContentPresent(index)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cia_tmd.GetContentSizeByIndex(index);
|
||||
}
|
||||
|
||||
void CIAContainer::Print() const {
|
||||
LOG_DEBUG(Service_FS, "Type: {}", static_cast<u32>(cia_header.type));
|
||||
LOG_DEBUG(Service_FS, "Version: {}\n", static_cast<u32>(cia_header.version));
|
||||
|
||||
LOG_DEBUG(Service_FS, "Certificate Size: 0x{:08x} bytes", GetCertificateSize());
|
||||
LOG_DEBUG(Service_FS, "Ticket Size: 0x{:08x} bytes", GetTicketSize());
|
||||
LOG_DEBUG(Service_FS, "TMD Size: 0x{:08x} bytes", GetTitleMetadataSize());
|
||||
LOG_DEBUG(Service_FS, "Meta Size: 0x{:08x} bytes", GetMetadataSize());
|
||||
LOG_DEBUG(Service_FS, "Content Size: 0x{:08x} bytes\n", GetTotalContentSize());
|
||||
|
||||
LOG_DEBUG(Service_FS, "Certificate Offset: 0x{:08x} bytes", GetCertificateOffset());
|
||||
LOG_DEBUG(Service_FS, "Ticket Offset: 0x{:08x} bytes", GetTicketOffset());
|
||||
LOG_DEBUG(Service_FS, "TMD Offset: 0x{:08x} bytes", GetTitleMetadataOffset());
|
||||
LOG_DEBUG(Service_FS, "Meta Offset: 0x{:08x} bytes", GetMetadataOffset());
|
||||
for (u16 i = 0; i < cia_tmd.GetContentCount(); i++) {
|
||||
LOG_DEBUG(Service_FS, "Content {:x} Offset: 0x{:08x} bytes", i, GetContentOffset(i));
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
110
src/core/file_sys/cia_container.h
Normal file
110
src/core/file_sys/cia_container.h
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ticket.h"
|
||||
#include "core/file_sys/title_metadata.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class FileBackend;
|
||||
|
||||
constexpr std::size_t CIA_CONTENT_MAX_COUNT = 0x10000;
|
||||
constexpr std::size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8);
|
||||
constexpr std::size_t CIA_HEADER_SIZE = 0x2020;
|
||||
constexpr std::size_t CIA_DEPENDENCY_SIZE = 0x300;
|
||||
constexpr std::size_t CIA_METADATA_SIZE = 0x400;
|
||||
constexpr u32 CIA_SECTION_ALIGNMENT = 0x40;
|
||||
|
||||
/**
|
||||
* Helper which implements an interface to read and write CTR Installable Archive (CIA) files.
|
||||
* Data can either be loaded from a FileBackend, a string path, or from a data array. Data can
|
||||
* also be partially loaded for CIAs which are downloading/streamed in and need some metadata
|
||||
* read out.
|
||||
*/
|
||||
class CIAContainer {
|
||||
public:
|
||||
// Load whole CIAs outright
|
||||
Loader::ResultStatus Load(const FileBackend& backend);
|
||||
Loader::ResultStatus Load(const std::string& filepath);
|
||||
Loader::ResultStatus Load(std::span<const u8> header_data);
|
||||
|
||||
// Load parts of CIAs (for CIAs streamed in)
|
||||
Loader::ResultStatus LoadHeader(std::span<const u8> header_data, std::size_t offset = 0);
|
||||
Loader::ResultStatus LoadTicket(std::span<const u8> ticket_data, std::size_t offset = 0);
|
||||
Loader::ResultStatus LoadTitleMetadata(std::span<const u8> tmd_data, std::size_t offset = 0);
|
||||
Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0);
|
||||
|
||||
const Ticket& GetTicket() const;
|
||||
const TitleMetadata& GetTitleMetadata() const;
|
||||
std::array<u64, 0x30>& GetDependencies();
|
||||
u32 GetCoreVersion() const;
|
||||
|
||||
u64 GetCertificateOffset() const;
|
||||
u64 GetTicketOffset() const;
|
||||
u64 GetTitleMetadataOffset() const;
|
||||
u64 GetMetadataOffset() const;
|
||||
u64 GetContentOffset(std::size_t index = 0) const;
|
||||
|
||||
u32 GetCertificateSize() const;
|
||||
u32 GetTicketSize() const;
|
||||
u32 GetTitleMetadataSize() const;
|
||||
u32 GetMetadataSize() const;
|
||||
u64 GetTotalContentSize() const;
|
||||
u64 GetContentSize(std::size_t index = 0) const;
|
||||
|
||||
void Print() const;
|
||||
|
||||
struct Header {
|
||||
u32_le header_size;
|
||||
u16_le type;
|
||||
u16_le version;
|
||||
u32_le cert_size;
|
||||
u32_le tik_size;
|
||||
u32_le tmd_size;
|
||||
u32_le meta_size;
|
||||
u64_le content_size;
|
||||
std::array<u8, CIA_CONTENT_BITS_SIZE> content_present;
|
||||
|
||||
bool IsContentPresent(std::size_t index) const {
|
||||
// The content_present is a bit array which defines which content in the TMD
|
||||
// is included in the CIA, so check the bit for this index and add if set.
|
||||
// The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc.
|
||||
return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0;
|
||||
}
|
||||
void SetContentPresent(u16 index) {
|
||||
content_present[index >> 3] |= (0x80 >> (index & 7));
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong");
|
||||
|
||||
private:
|
||||
struct Metadata {
|
||||
std::array<u64_le, 0x30> dependencies;
|
||||
std::array<u8, 0x180> reserved;
|
||||
u32_le core_version;
|
||||
std::array<u8, 0xfc> reserved_2;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong");
|
||||
|
||||
Header cia_header;
|
||||
Metadata cia_metadata;
|
||||
Ticket cia_ticket;
|
||||
TitleMetadata cia_tmd;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
32
src/core/file_sys/delay_generator.cpp
Normal file
32
src/core/file_sys/delay_generator.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/archives.h"
|
||||
#include "core/file_sys/delay_generator.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::DefaultDelayGenerator)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
DelayGenerator::~DelayGenerator() = default;
|
||||
|
||||
u64 DefaultDelayGenerator::GetReadDelayNs(std::size_t length) {
|
||||
// This is the delay measured for a romfs read.
|
||||
// For now we will take that as a default
|
||||
static constexpr u64 slope(94);
|
||||
static constexpr u64 offset(582778);
|
||||
static constexpr u64 minimum(663124);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 DefaultDelayGenerator::GetOpenDelayNs() {
|
||||
// This is the delay measured for a romfs open.
|
||||
// For now we will take that as a default
|
||||
static constexpr u64 IPCDelayNanoseconds(9438006);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
45
src/core/file_sys/delay_generator.h
Normal file
45
src/core/file_sys/delay_generator.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include "common/common_types.h"
|
||||
|
||||
#define SERIALIZE_DELAY_GENERATOR \
|
||||
private: \
|
||||
template <class Archive> \
|
||||
void serialize(Archive& ar, const unsigned int) { \
|
||||
ar& boost::serialization::base_object<DelayGenerator>(*this); \
|
||||
} \
|
||||
friend class boost::serialization::access;
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class DelayGenerator {
|
||||
public:
|
||||
virtual ~DelayGenerator();
|
||||
virtual u64 GetReadDelayNs(std::size_t length) = 0;
|
||||
virtual u64 GetOpenDelayNs() = 0;
|
||||
|
||||
// TODO (B3N30): Add getter for all other file/directory io operations
|
||||
private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class DefaultDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override;
|
||||
u64 GetOpenDelayNs() override;
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::DefaultDelayGenerator);
|
||||
64
src/core/file_sys/directory_backend.h
Normal file
64
src/core/file_sys/directory_backend.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format
|
||||
const std::size_t FILENAME_LENGTH = 0x20C / 2;
|
||||
struct Entry {
|
||||
char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated)
|
||||
std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated)
|
||||
char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD)
|
||||
std::array<char, 4>
|
||||
extension; // 8.3 file extension (set to spaces for directories, null-terminated)
|
||||
char unknown2; // unknown (always 0x01)
|
||||
char unknown3; // unknown (0x00 or 0x08)
|
||||
char is_directory; // directory flag
|
||||
char is_hidden; // hidden flag
|
||||
char is_archive; // archive flag
|
||||
char is_read_only; // read-only flag
|
||||
u64 file_size; // file size (for files only)
|
||||
};
|
||||
static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!");
|
||||
static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry.");
|
||||
static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry.");
|
||||
static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry.");
|
||||
static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry.");
|
||||
|
||||
class DirectoryBackend : NonCopyable {
|
||||
public:
|
||||
DirectoryBackend() {}
|
||||
virtual ~DirectoryBackend() {}
|
||||
|
||||
/**
|
||||
* List files contained in the directory
|
||||
* @param count Number of entries to return at once in entries
|
||||
* @param entries Buffer to read data into
|
||||
* @return Number of entries listed
|
||||
*/
|
||||
virtual u32 Read(const u32 count, Entry* entries) = 0;
|
||||
|
||||
/**
|
||||
* Close the directory
|
||||
* @return true if the directory closed correctly
|
||||
*/
|
||||
virtual bool Close() = 0;
|
||||
|
||||
virtual bool IsSlow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
96
src/core/file_sys/disk_archive.cpp
Normal file
96
src/core/file_sys/disk_archive.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/disk_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::DiskFile)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::DiskDirectory)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
ResultVal<std::size_t> DiskFile::Read(const u64 offset, const std::size_t length,
|
||||
u8* buffer) const {
|
||||
if (!mode.read_flag)
|
||||
return ResultInvalidOpenFlags;
|
||||
|
||||
file->Seek(offset, SEEK_SET);
|
||||
return file->ReadBytes(buffer, length);
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> DiskFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
||||
const bool update_timestamp, const u8* buffer) {
|
||||
if (!mode.write_flag)
|
||||
return ResultInvalidOpenFlags;
|
||||
|
||||
file->Seek(offset, SEEK_SET);
|
||||
std::size_t written = file->WriteBytes(buffer, length);
|
||||
if (flush)
|
||||
file->Flush();
|
||||
return written;
|
||||
}
|
||||
|
||||
u64 DiskFile::GetSize() const {
|
||||
return file->GetSize();
|
||||
}
|
||||
|
||||
bool DiskFile::SetSize(const u64 size) const {
|
||||
file->Resize(size);
|
||||
file->Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskFile::Close() {
|
||||
return file->Close();
|
||||
}
|
||||
|
||||
DiskDirectory::DiskDirectory(const std::string& path) {
|
||||
directory.size = FileUtil::ScanDirectoryTree(path, directory);
|
||||
directory.isDirectory = true;
|
||||
children_iterator = directory.children.begin();
|
||||
}
|
||||
|
||||
u32 DiskDirectory::Read(const u32 count, Entry* entries) {
|
||||
u32 entries_read = 0;
|
||||
|
||||
while (entries_read < count && children_iterator != directory.children.cend()) {
|
||||
const FileUtil::FSTEntry& file = *children_iterator;
|
||||
const std::string& filename = file.virtualName;
|
||||
Entry& entry = entries[entries_read];
|
||||
|
||||
LOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory);
|
||||
|
||||
// TODO(Link Mauve): use a proper conversion to UTF-16.
|
||||
for (std::size_t j = 0; j < FILENAME_LENGTH; ++j) {
|
||||
entry.filename[j] = filename[j];
|
||||
if (!filename[j])
|
||||
break;
|
||||
}
|
||||
|
||||
FileUtil::SplitFilename83(filename, entry.short_name, entry.extension);
|
||||
|
||||
entry.is_directory = file.isDirectory;
|
||||
entry.is_hidden = (filename[0] == '.');
|
||||
entry.is_read_only = 0;
|
||||
entry.file_size = file.size;
|
||||
|
||||
// We emulate a SD card where the archive bit has never been cleared, as it would be on
|
||||
// most user SD cards.
|
||||
// Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a
|
||||
// file bit.
|
||||
entry.is_archive = !file.isDirectory;
|
||||
|
||||
++entries_read;
|
||||
++children_iterator;
|
||||
}
|
||||
return entries_read;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
102
src/core/file_sys/disk_archive.h
Normal file
102
src/core/file_sys/disk_archive.h
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/unique_ptr.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/directory_backend.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class DiskFile : public FileBackend {
|
||||
public:
|
||||
DiskFile(FileUtil::IOFile&& file_, const Mode& mode_,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: file(new FileUtil::IOFile(std::move(file_))) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
mode.hex = mode_.hex;
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override;
|
||||
u64 GetSize() const override;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() override;
|
||||
|
||||
void Flush() const override {
|
||||
file->Flush();
|
||||
}
|
||||
|
||||
protected:
|
||||
Mode mode;
|
||||
std::unique_ptr<FileUtil::IOFile> file;
|
||||
|
||||
private:
|
||||
DiskFile() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||
ar& mode.hex;
|
||||
ar& file;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class DiskDirectory : public DirectoryBackend {
|
||||
public:
|
||||
explicit DiskDirectory(const std::string& path);
|
||||
|
||||
~DiskDirectory() override {
|
||||
Close();
|
||||
}
|
||||
|
||||
u32 Read(u32 count, Entry* entries) override;
|
||||
|
||||
bool Close() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
FileUtil::FSTEntry directory{};
|
||||
|
||||
// We need to remember the last entry we returned, so a subsequent call to Read will continue
|
||||
// from the next one. This iterator will always point to the next unread entry.
|
||||
std::vector<FileUtil::FSTEntry>::iterator children_iterator;
|
||||
|
||||
private:
|
||||
DiskDirectory() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<DirectoryBackend>(*this);
|
||||
ar& directory;
|
||||
u64 child_index;
|
||||
if (Archive::is_saving::value) {
|
||||
child_index = children_iterator - directory.children.begin();
|
||||
}
|
||||
ar& child_index;
|
||||
if (Archive::is_loading::value) {
|
||||
children_iterator = directory.children.begin() + child_index;
|
||||
}
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::DiskFile)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::DiskDirectory)
|
||||
94
src/core/file_sys/errors.h
Normal file
94
src/core/file_sys/errors.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
RomFSNotFound = 100,
|
||||
ArchiveNotMounted = 101,
|
||||
FileNotFound = 112,
|
||||
PathNotFound = 113,
|
||||
GameCardNotInserted = 141,
|
||||
NotFound = 120,
|
||||
FileAlreadyExists = 180,
|
||||
DirectoryAlreadyExists = 185,
|
||||
AlreadyExists = 190,
|
||||
InsufficientSpace = 210,
|
||||
InvalidOpenFlags = 230,
|
||||
DirectoryNotEmpty = 240,
|
||||
NotAFile = 250,
|
||||
NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
|
||||
ExeFSSectionNotFound = 567,
|
||||
CommandNotAllowed = 630,
|
||||
InvalidReadFlag = 700,
|
||||
InvalidPath = 702,
|
||||
WriteBeyondEnd = 705,
|
||||
UnsupportedOpenFlags = 760,
|
||||
IncorrectExeFSReadSize = 761,
|
||||
UnexpectedFileOrDirectory = 770,
|
||||
};
|
||||
}
|
||||
|
||||
constexpr Result ResultInvalidPath(ErrCodes::InvalidPath, ErrorModule::FS,
|
||||
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
|
||||
constexpr Result ResultUnsupportedOpenFlags(ErrCodes::UnsupportedOpenFlags, ErrorModule::FS,
|
||||
ErrorSummary::NotSupported, ErrorLevel::Usage);
|
||||
constexpr Result ResultInvalidOpenFlags(ErrCodes::InvalidOpenFlags, ErrorModule::FS,
|
||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
||||
constexpr Result ResultInvalidReadFlag(ErrCodes::InvalidReadFlag, ErrorModule::FS,
|
||||
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
|
||||
constexpr Result ResultFileNotFound(ErrCodes::FileNotFound, ErrorModule::FS, ErrorSummary::NotFound,
|
||||
ErrorLevel::Status);
|
||||
constexpr Result ResultPathNotFound(ErrCodes::PathNotFound, ErrorModule::FS, ErrorSummary::NotFound,
|
||||
ErrorLevel::Status);
|
||||
constexpr Result ResultNotFound(ErrCodes::NotFound, ErrorModule::FS, ErrorSummary::NotFound,
|
||||
ErrorLevel::Status);
|
||||
constexpr Result ResultUnexpectedFileOrDirectory(ErrCodes::UnexpectedFileOrDirectory,
|
||||
ErrorModule::FS, ErrorSummary::NotSupported,
|
||||
ErrorLevel::Usage);
|
||||
constexpr Result ResultUnexpectedFileOrDirectorySdmc(ErrCodes::NotAFile, ErrorModule::FS,
|
||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
||||
constexpr Result ResultDirectoryAlreadyExists(ErrCodes::DirectoryAlreadyExists, ErrorModule::FS,
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
constexpr Result ResultFileAlreadyExists(ErrCodes::FileAlreadyExists, ErrorModule::FS,
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
constexpr Result ResultAlreadyExists(ErrCodes::AlreadyExists, ErrorModule::FS,
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
constexpr Result ResultDirectoryNotEmpty(ErrCodes::DirectoryNotEmpty, ErrorModule::FS,
|
||||
ErrorSummary::Canceled, ErrorLevel::Status);
|
||||
constexpr Result ResultGamecardNotInserted(ErrCodes::GameCardNotInserted, ErrorModule::FS,
|
||||
ErrorSummary::NotFound, ErrorLevel::Status);
|
||||
constexpr Result ResultIncorrectExefsReadSize(ErrCodes::IncorrectExeFSReadSize, ErrorModule::FS,
|
||||
ErrorSummary::NotSupported, ErrorLevel::Usage);
|
||||
constexpr Result ResultRomfsNotFound(ErrCodes::RomFSNotFound, ErrorModule::FS,
|
||||
ErrorSummary::NotFound, ErrorLevel::Status);
|
||||
constexpr Result ResultCommandNotAllowed(ErrCodes::CommandNotAllowed, ErrorModule::FS,
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Permanent);
|
||||
constexpr Result ResultExefsSectionNotFound(ErrCodes::ExeFSSectionNotFound, ErrorModule::FS,
|
||||
ErrorSummary::NotFound, ErrorLevel::Status);
|
||||
constexpr Result ResultInsufficientSpace(ErrCodes::InsufficientSpace, ErrorModule::FS,
|
||||
ErrorSummary::OutOfResource, ErrorLevel::Status);
|
||||
|
||||
/// Returned when a function is passed an invalid archive handle.
|
||||
constexpr Result ResultInvalidArchiveHandle(ErrCodes::ArchiveNotMounted, ErrorModule::FS,
|
||||
ErrorSummary::NotFound,
|
||||
ErrorLevel::Status); // 0xC8804465
|
||||
constexpr Result ResultWriteBeyondEnd(ErrCodes::WriteBeyondEnd, ErrorModule::FS,
|
||||
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
|
||||
|
||||
/**
|
||||
* Variant of ResultNotFound returned in some places in the code. Unknown if these usages are
|
||||
* correct or a bug.
|
||||
*/
|
||||
constexpr Result ResultNotFoundInvalidState(ErrCodes::NotFound, ErrorModule::FS,
|
||||
ErrorSummary::InvalidState, ErrorLevel::Status);
|
||||
constexpr Result ResultNotFormatted(ErrCodes::NotFormatted, ErrorModule::FS,
|
||||
ErrorSummary::InvalidState, ErrorLevel::Status);
|
||||
|
||||
} // namespace FileSys
|
||||
113
src/core/file_sys/file_backend.h
Normal file
113
src/core/file_sys/file_backend.h
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <boost/serialization/unique_ptr.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "delay_generator.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class FileBackend : NonCopyable {
|
||||
public:
|
||||
FileBackend() {}
|
||||
virtual ~FileBackend() {}
|
||||
|
||||
/**
|
||||
* Read data from the file
|
||||
* @param offset Offset in bytes to start reading data from
|
||||
* @param length Length in bytes of data to read from file
|
||||
* @param buffer Buffer to read data into
|
||||
* @return Number of bytes read, or error code
|
||||
*/
|
||||
virtual ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const = 0;
|
||||
|
||||
/**
|
||||
* Write data to the file
|
||||
* @param offset Offset in bytes to start writing data to
|
||||
* @param length Length in bytes of data to write to file
|
||||
* @param flush The flush parameters (0 == do not flush)
|
||||
* @param buffer Buffer to read data from
|
||||
* @return Number of bytes written, or error code
|
||||
*/
|
||||
virtual ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
||||
bool update_timestamp, const u8* buffer) = 0;
|
||||
|
||||
/**
|
||||
* Get the amount of time a 3ds needs to read those data
|
||||
* @param length Length in bytes of data read from file
|
||||
* @return Nanoseconds for the delay
|
||||
*/
|
||||
u64 GetReadDelayNs(std::size_t length) {
|
||||
if (delay_generator != nullptr) {
|
||||
return delay_generator->GetReadDelayNs(length);
|
||||
}
|
||||
LOG_ERROR(Service_FS, "Delay generator was not initalized. Using default");
|
||||
delay_generator = std::make_unique<DefaultDelayGenerator>();
|
||||
return delay_generator->GetReadDelayNs(length);
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() {
|
||||
if (delay_generator != nullptr) {
|
||||
return delay_generator->GetOpenDelayNs();
|
||||
}
|
||||
LOG_ERROR(Service_FS, "Delay generator was not initalized. Using default");
|
||||
delay_generator = std::make_unique<DefaultDelayGenerator>();
|
||||
return delay_generator->GetOpenDelayNs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the file in bytes
|
||||
* @return Size of the file in bytes
|
||||
*/
|
||||
virtual u64 GetSize() const = 0;
|
||||
|
||||
/**
|
||||
* Set the size of the file in bytes
|
||||
* @param size New size of the file
|
||||
* @return true if successful
|
||||
*/
|
||||
virtual bool SetSize(u64 size) const = 0;
|
||||
|
||||
/**
|
||||
* Close the file
|
||||
* @return true if the file closed correctly
|
||||
*/
|
||||
virtual bool Close() = 0;
|
||||
|
||||
/**
|
||||
* Flushes the file
|
||||
*/
|
||||
virtual void Flush() const = 0;
|
||||
|
||||
/**
|
||||
* Whether the backend supports cached reads.
|
||||
*/
|
||||
virtual bool AllowsCachedReads() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the cache is ready for a specified offset and length.
|
||||
*/
|
||||
virtual bool CacheReady(std::size_t file_offset, std::size_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<DelayGenerator> delay_generator;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& delay_generator;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
152
src/core/file_sys/ivfc_archive.cpp
Normal file
152
src/core/file_sys/ivfc_archive.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/ivfc_archive.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::IVFCFile)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::IVFCFileInMemory)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::IVFCDelayGenerator)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::RomFSDelayGenerator)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::ExeFSDelayGenerator)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
IVFCArchive::IVFCArchive(std::shared_ptr<RomFSReader> file,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: romfs_file(std::move(file)) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
}
|
||||
|
||||
std::string IVFCArchive::GetName() const {
|
||||
return "IVFC";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) {
|
||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<IVFCDelayGenerator>();
|
||||
return std::make_unique<IVFCFile>(romfs_file, std::move(delay_generator));
|
||||
}
|
||||
|
||||
Result IVFCArchive::DeleteFile(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a file from an IVFC archive ({}).", GetName());
|
||||
// TODO(Subv): Verify error code
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
|
||||
ErrorLevel::Status);
|
||||
}
|
||||
|
||||
Result IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result IVFCArchive::DeleteDirectory(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result IVFCArchive::CreateFile(const Path& path, u64 size, u32 attributes) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive ({}).", GetName());
|
||||
// TODO: Verify error code
|
||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||
ErrorLevel::Permanent);
|
||||
}
|
||||
|
||||
Result IVFCArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
Result IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) {
|
||||
return std::make_unique<IVFCDirectory>();
|
||||
}
|
||||
|
||||
u64 IVFCArchive::GetFreeBytes() const {
|
||||
LOG_WARNING(Service_FS, "Attempted to get the free space in an IVFC archive");
|
||||
return 0;
|
||||
}
|
||||
|
||||
IVFCFile::IVFCFile(std::shared_ptr<RomFSReader> file,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: romfs_file(std::move(file)) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> IVFCFile::Read(const u64 offset, const std::size_t length,
|
||||
u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
return romfs_file->ReadFile(offset, length, buffer);
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> IVFCFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
||||
const bool update_timestamp, const u8* buffer) {
|
||||
LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
|
||||
// TODO(Subv): Find error code
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
u64 IVFCFile::GetSize() const {
|
||||
return romfs_file->GetSize();
|
||||
}
|
||||
|
||||
bool IVFCFile::SetSize(const u64 size) const {
|
||||
LOG_ERROR(Service_FS, "Attempted to set the size of an IVFC file");
|
||||
return false;
|
||||
}
|
||||
|
||||
IVFCFileInMemory::IVFCFileInMemory(std::vector<u8> bytes, u64 offset, u64 size,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_)
|
||||
: romfs_file(std::move(bytes)), data_offset(offset), data_size(size) {
|
||||
delay_generator = std::move(delay_generator_);
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> IVFCFileInMemory::Read(const u64 offset, const std::size_t length,
|
||||
u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
std::size_t read_length = (std::size_t)std::min((u64)length, data_size - offset);
|
||||
|
||||
std::memcpy(buffer, romfs_file.data() + data_offset + offset, read_length);
|
||||
return read_length;
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> IVFCFileInMemory::Write(const u64 offset, const std::size_t length,
|
||||
const bool flush, const bool update_timestamp,
|
||||
const u8* buffer) {
|
||||
LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
|
||||
// TODO(Subv): Find error code
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
u64 IVFCFileInMemory::GetSize() const {
|
||||
return data_size;
|
||||
}
|
||||
|
||||
bool IVFCFileInMemory::SetSize(const u64 size) const {
|
||||
LOG_ERROR(Service_FS, "Attempted to set the size of an IVFC file");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
203
src/core/file_sys/ivfc_archive.h
Normal file
203
src/core/file_sys/ivfc_archive.h
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/directory_backend.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/file_sys/romfs_reader.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class IVFCDelayGenerator : public DelayGenerator {
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// This is the delay measured for a romfs read.
|
||||
// For now we will take that as a default
|
||||
static constexpr u64 slope(94);
|
||||
static constexpr u64 offset(582778);
|
||||
static constexpr u64 minimum(663124);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured for a romfs open.
|
||||
// For now we will take that as a default
|
||||
static constexpr u64 IPCDelayNanoseconds(9438006);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
class RomFSDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// The delay was measured on O3DS and O2DS with
|
||||
// https://gist.github.com/B3n30/ac40eac20603f519ff106107f4ac9182
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 slope(94);
|
||||
static constexpr u64 offset(582778);
|
||||
static constexpr u64 minimum(663124);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/FearlessTobi/eb1d70619c65c7e6f02141d71e79a36e
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 IPCDelayNanoseconds(9438006);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
class ExeFSDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// The delay was measured on O3DS and O2DS with
|
||||
// https://gist.github.com/B3n30/ac40eac20603f519ff106107f4ac9182
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 slope(94);
|
||||
static constexpr u64 offset(582778);
|
||||
static constexpr u64 minimum(663124);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/FearlessTobi/eb1d70619c65c7e6f02141d71e79a36e
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 IPCDelayNanoseconds(9438006);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper which implements an interface to deal with IVFC images used in some archives
|
||||
* This should be subclassed by concrete archive types, which will provide the
|
||||
* input data (load the raw IVFC archive) and override any required methods
|
||||
*/
|
||||
class IVFCArchive : public ArchiveBackend {
|
||||
public:
|
||||
IVFCArchive(std::shared_ptr<RomFSReader> file,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_);
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||
u32 attributes) override;
|
||||
Result DeleteFile(const Path& path) const override;
|
||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||
Result DeleteDirectory(const Path& path) const override;
|
||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||
u64 GetFreeBytes() const override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<RomFSReader> romfs_file;
|
||||
};
|
||||
|
||||
class IVFCFile : public FileBackend {
|
||||
public:
|
||||
IVFCFile(std::shared_ptr<RomFSReader> file, std::unique_ptr<DelayGenerator> delay_generator_);
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override;
|
||||
u64 GetSize() const override;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() override {
|
||||
return false;
|
||||
}
|
||||
void Flush() const override {}
|
||||
|
||||
bool AllowsCachedReads() const override {
|
||||
return romfs_file->AllowsCachedReads();
|
||||
}
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
||||
return romfs_file->CacheReady(file_offset, length);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<RomFSReader> romfs_file;
|
||||
|
||||
IVFCFile() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||
ar& romfs_file;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
class IVFCDirectory : public DirectoryBackend {
|
||||
public:
|
||||
u32 Read(const u32 count, Entry* entries) override {
|
||||
return 0;
|
||||
}
|
||||
bool Close() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class IVFCFileInMemory : public FileBackend {
|
||||
public:
|
||||
IVFCFileInMemory(std::vector<u8> bytes, u64 offset, u64 size,
|
||||
std::unique_ptr<DelayGenerator> delay_generator_);
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
const u8* buffer) override;
|
||||
u64 GetSize() const override;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() override {
|
||||
return false;
|
||||
}
|
||||
void Flush() const override {}
|
||||
|
||||
private:
|
||||
std::vector<u8> romfs_file;
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
|
||||
IVFCFileInMemory() = default;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||
ar& romfs_file;
|
||||
ar& data_offset;
|
||||
ar& data_size;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::IVFCFile)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::IVFCFileInMemory)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::IVFCDelayGenerator)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::RomFSDelayGenerator)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ExeFSDelayGenerator)
|
||||
606
src/core/file_sys/layered_fs.cpp
Normal file
606
src/core/file_sys/layered_fs.cpp
Normal file
@@ -0,0 +1,606 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/alignment.h"
|
||||
#include "common/archives.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/layered_fs.h"
|
||||
#include "core/file_sys/patch.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::LayeredFS)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct FileRelocationInfo {
|
||||
int type; // 0 - none, 1 - replaced / created, 2 - patched, 3 - removed
|
||||
u64 original_offset; // Type 0. Offset is absolute
|
||||
std::string replace_file_path; // Type 1
|
||||
std::vector<u8> patched_file; // Type 2
|
||||
u64 size; // Relocated file size
|
||||
};
|
||||
struct LayeredFS::File {
|
||||
std::string name;
|
||||
std::string path;
|
||||
FileRelocationInfo relocation{};
|
||||
Directory* parent;
|
||||
};
|
||||
|
||||
struct DirectoryMetadata {
|
||||
u32_le parent_directory_offset;
|
||||
u32_le next_sibling_offset;
|
||||
u32_le first_child_directory_offset;
|
||||
u32_le first_file_offset;
|
||||
u32_le hash_bucket_next;
|
||||
u32_le name_length;
|
||||
// Followed by a name of name length (aligned up to 4)
|
||||
};
|
||||
static_assert(sizeof(DirectoryMetadata) == 0x18, "Size of DirectoryMetadata is not correct");
|
||||
|
||||
struct FileMetadata {
|
||||
u32_le parent_directory_offset;
|
||||
u32_le next_sibling_offset;
|
||||
u64_le file_data_offset;
|
||||
u64_le file_data_length;
|
||||
u32_le hash_bucket_next;
|
||||
u32_le name_length;
|
||||
// Followed by a name of name length (aligned up to 4)
|
||||
};
|
||||
static_assert(sizeof(FileMetadata) == 0x20, "Size of FileMetadata is not correct");
|
||||
|
||||
LayeredFS::LayeredFS() = default;
|
||||
|
||||
LayeredFS::LayeredFS(std::shared_ptr<RomFSReader> romfs_, std::string patch_path_,
|
||||
std::string patch_ext_path_, bool load_relocations_)
|
||||
: romfs(std::move(romfs_)), patch_path(std::move(patch_path_)),
|
||||
patch_ext_path(std::move(patch_ext_path_)), load_relocations(load_relocations_) {
|
||||
Load();
|
||||
}
|
||||
|
||||
void LayeredFS::Load() {
|
||||
romfs->ReadFile(0, sizeof(header), reinterpret_cast<u8*>(&header));
|
||||
|
||||
ASSERT_MSG(header.header_length == sizeof(header), "Header size is incorrect");
|
||||
|
||||
// TODO: is root always the first directory in table?
|
||||
root.parent = &root;
|
||||
LoadDirectory(root, 0);
|
||||
|
||||
if (load_relocations) {
|
||||
LoadRelocations();
|
||||
LoadExtRelocations();
|
||||
}
|
||||
|
||||
RebuildMetadata();
|
||||
}
|
||||
|
||||
LayeredFS::~LayeredFS() = default;
|
||||
|
||||
u32 LayeredFS::LoadDirectory(Directory& current, u32 offset) {
|
||||
DirectoryMetadata metadata;
|
||||
romfs->ReadFile(header.directory_metadata_table.offset + offset, sizeof(metadata),
|
||||
reinterpret_cast<u8*>(&metadata));
|
||||
|
||||
current.name = ReadName(header.directory_metadata_table.offset + offset + sizeof(metadata),
|
||||
metadata.name_length);
|
||||
current.path = current.parent->path + current.name + DIR_SEP;
|
||||
directory_path_map.emplace(current.path, ¤t);
|
||||
|
||||
u32 file_offset = metadata.first_file_offset;
|
||||
while (file_offset != 0xFFFFFFFF) {
|
||||
file_offset = LoadFile(current, file_offset);
|
||||
}
|
||||
|
||||
u32 child_directory_offset = metadata.first_child_directory_offset;
|
||||
while (child_directory_offset != 0xFFFFFFFF) {
|
||||
auto child = std::make_unique<Directory>();
|
||||
auto& directory = *child;
|
||||
directory.parent = ¤t;
|
||||
current.directories.emplace_back(std::move(child));
|
||||
child_directory_offset = LoadDirectory(directory, child_directory_offset);
|
||||
}
|
||||
|
||||
return metadata.next_sibling_offset;
|
||||
}
|
||||
|
||||
u32 LayeredFS::LoadFile(Directory& parent, u32 offset) {
|
||||
FileMetadata metadata;
|
||||
romfs->ReadFile(header.file_metadata_table.offset + offset, sizeof(metadata),
|
||||
reinterpret_cast<u8*>(&metadata));
|
||||
|
||||
auto file = std::make_unique<File>();
|
||||
file->name = ReadName(header.file_metadata_table.offset + offset + sizeof(metadata),
|
||||
metadata.name_length);
|
||||
file->path = parent.path + file->name;
|
||||
file->relocation.original_offset = header.file_data_offset + metadata.file_data_offset;
|
||||
file->relocation.size = metadata.file_data_length;
|
||||
file->parent = &parent;
|
||||
|
||||
file_path_map.emplace(file->path, file.get());
|
||||
parent.files.emplace_back(std::move(file));
|
||||
|
||||
return metadata.next_sibling_offset;
|
||||
}
|
||||
|
||||
std::string LayeredFS::ReadName(u32 offset, u32 name_length) {
|
||||
std::vector<u16_le> buffer(name_length / sizeof(u16_le));
|
||||
romfs->ReadFile(offset, name_length, reinterpret_cast<u8*>(buffer.data()));
|
||||
|
||||
std::u16string name(buffer.size(), 0);
|
||||
std::transform(buffer.begin(), buffer.end(), name.begin(), [](u16_le character) {
|
||||
return static_cast<char16_t>(static_cast<u16>(character));
|
||||
});
|
||||
return Common::UTF16ToUTF8(name);
|
||||
}
|
||||
|
||||
void LayeredFS::LoadRelocations() {
|
||||
if (!FileUtil::Exists(patch_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FileUtil::DirectoryEntryCallable callback =
|
||||
[this, &callback]([[maybe_unused]] u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
auto* parent = directory_path_map.at(directory.substr(patch_path.size() - 1));
|
||||
|
||||
if (FileUtil::IsDirectory(directory + virtual_name + DIR_SEP)) {
|
||||
const auto path =
|
||||
(directory + virtual_name + DIR_SEP).substr(patch_path.size() - 1);
|
||||
if (!directory_path_map.count(path)) { // Add this directory
|
||||
auto child_dir = std::make_unique<Directory>();
|
||||
child_dir->name = virtual_name;
|
||||
child_dir->path = path;
|
||||
child_dir->parent = parent;
|
||||
directory_path_map.emplace(path, child_dir.get());
|
||||
parent->directories.emplace_back(std::move(child_dir));
|
||||
LOG_INFO(Service_FS, "LayeredFS created directory {}", path);
|
||||
}
|
||||
return FileUtil::ForeachDirectoryEntry(nullptr, directory + virtual_name + DIR_SEP,
|
||||
callback);
|
||||
}
|
||||
|
||||
const auto path = (directory + virtual_name).substr(patch_path.size() - 1);
|
||||
if (!file_path_map.count(path)) { // Newly created file
|
||||
auto file = std::make_unique<File>();
|
||||
file->name = virtual_name;
|
||||
file->path = path;
|
||||
file->parent = parent;
|
||||
file_path_map.emplace(path, file.get());
|
||||
parent->files.emplace_back(std::move(file));
|
||||
LOG_INFO(Service_FS, "LayeredFS created file {}", path);
|
||||
}
|
||||
|
||||
auto* file = file_path_map.at(path);
|
||||
file->relocation.type = 1;
|
||||
file->relocation.replace_file_path = directory + virtual_name;
|
||||
file->relocation.size = FileUtil::GetSize(directory + virtual_name);
|
||||
LOG_INFO(Service_FS, "LayeredFS replacement file in use for {}", path);
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, patch_path, callback);
|
||||
}
|
||||
|
||||
void LayeredFS::LoadExtRelocations() {
|
||||
if (!FileUtil::Exists(patch_ext_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (patch_ext_path.back() == '/' || patch_ext_path.back() == '\\') {
|
||||
// ScanDirectoryTree expects a path without trailing '/'
|
||||
patch_ext_path.erase(patch_ext_path.size() - 1, 1);
|
||||
}
|
||||
|
||||
FileUtil::FSTEntry result;
|
||||
FileUtil::ScanDirectoryTree(patch_ext_path, result, 256);
|
||||
|
||||
for (const auto& entry : result.children) {
|
||||
if (FileUtil::IsDirectory(entry.physicalName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto path = entry.physicalName.substr(patch_ext_path.size());
|
||||
if (path.size() >= 5 && path.substr(path.size() - 5) == ".stub") {
|
||||
// Remove the corresponding file if exists
|
||||
const auto file_path = path.substr(0, path.size() - 5);
|
||||
if (file_path_map.count(file_path)) {
|
||||
auto& file = *file_path_map[file_path];
|
||||
file.relocation.type = 3;
|
||||
file.relocation.size = 0;
|
||||
file_path_map.erase(file_path);
|
||||
LOG_INFO(Service_FS, "LayeredFS removed file {}", file_path);
|
||||
} else {
|
||||
LOG_WARNING(Service_FS, "LayeredFS file for stub {} not found", path);
|
||||
}
|
||||
} else if (path.size() >= 4) {
|
||||
const auto extension = path.substr(path.size() - 4);
|
||||
if (extension != ".ips" && extension != ".bps") {
|
||||
LOG_WARNING(Service_FS, "LayeredFS unknown ext file {}", path);
|
||||
}
|
||||
|
||||
const auto file_path = path.substr(0, path.size() - 4);
|
||||
if (!file_path_map.count(file_path)) {
|
||||
LOG_WARNING(Service_FS, "LayeredFS original file for patch {} not found", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
FileUtil::IOFile patch_file(entry.physicalName, "rb");
|
||||
if (!patch_file) {
|
||||
LOG_ERROR(Service_FS, "LayeredFS Could not open file {}", entry.physicalName);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto size = patch_file.GetSize();
|
||||
std::vector<u8> patch(size);
|
||||
if (patch_file.ReadBytes(patch.data(), size) != size) {
|
||||
LOG_ERROR(Service_FS, "LayeredFS Could not read file {}", entry.physicalName);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& file = *file_path_map[file_path];
|
||||
std::vector<u8> buffer(file.relocation.size); // Original size
|
||||
romfs->ReadFile(file.relocation.original_offset, buffer.size(), buffer.data());
|
||||
|
||||
bool ret = false;
|
||||
if (extension == ".ips") {
|
||||
ret = Patch::ApplyIpsPatch(patch, buffer);
|
||||
} else {
|
||||
ret = Patch::ApplyBpsPatch(patch, buffer);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
LOG_INFO(Service_FS, "LayeredFS patched file {}", file_path);
|
||||
|
||||
file.relocation.type = 2;
|
||||
file.relocation.size = buffer.size();
|
||||
file.relocation.patched_file = std::move(buffer);
|
||||
} else {
|
||||
LOG_ERROR(Service_FS, "LayeredFS failed to patch file {}", file_path);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_FS, "LayeredFS unknown ext file {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::size_t GetNameSize(const std::string& name) {
|
||||
std::u16string u16name = Common::UTF8ToUTF16(name);
|
||||
return Common::AlignUp(u16name.size() * 2, 4);
|
||||
}
|
||||
|
||||
void LayeredFS::PrepareBuildDirectory(Directory& current) {
|
||||
directory_metadata_offset_map.emplace(¤t, static_cast<u32>(current_directory_offset));
|
||||
directory_list.emplace_back(¤t);
|
||||
current_directory_offset += sizeof(DirectoryMetadata) + GetNameSize(current.name);
|
||||
}
|
||||
|
||||
void LayeredFS::PrepareBuildFile(File& current) {
|
||||
if (current.relocation.type == 3) { // Deleted files are not counted
|
||||
return;
|
||||
}
|
||||
file_metadata_offset_map.emplace(¤t, static_cast<u32>(current_file_offset));
|
||||
file_list.emplace_back(¤t);
|
||||
current_file_offset += sizeof(FileMetadata) + GetNameSize(current.name);
|
||||
}
|
||||
|
||||
void LayeredFS::PrepareBuild(Directory& current) {
|
||||
for (const auto& child : current.files) {
|
||||
PrepareBuildFile(*child);
|
||||
}
|
||||
|
||||
for (const auto& child : current.directories) {
|
||||
PrepareBuildDirectory(*child);
|
||||
}
|
||||
|
||||
for (const auto& child : current.directories) {
|
||||
PrepareBuild(*child);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation from 3dbrew
|
||||
static u32 CalcHash(const std::string& name, u32 parent_offset) {
|
||||
u32 hash = parent_offset ^ 123456789;
|
||||
std::u16string u16name = Common::UTF8ToUTF16(name);
|
||||
for (char16_t c : u16name) {
|
||||
hash = (hash >> 5) | (hash << 27);
|
||||
hash ^= static_cast<u16>(c);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::size_t WriteName(u8* dest, std::u16string name) {
|
||||
const auto buffer_size = Common::AlignUp(name.size() * 2, 4);
|
||||
std::vector<u16_le> buffer(buffer_size / 2);
|
||||
std::transform(name.begin(), name.end(), buffer.begin(), [](char16_t character) {
|
||||
return static_cast<u16_le>(static_cast<u16>(character));
|
||||
});
|
||||
std::memcpy(dest, buffer.data(), buffer_size);
|
||||
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
void LayeredFS::BuildDirectories() {
|
||||
directory_metadata_table.resize(current_directory_offset, 0xFF);
|
||||
|
||||
std::size_t written = 0;
|
||||
for (const auto& directory : directory_list) {
|
||||
DirectoryMetadata metadata;
|
||||
std::memset(&metadata, 0xFF, sizeof(metadata));
|
||||
metadata.parent_directory_offset = directory_metadata_offset_map.at(directory->parent);
|
||||
|
||||
if (directory->parent != directory) {
|
||||
bool flag = false;
|
||||
for (const auto& sibling : directory->parent->directories) {
|
||||
if (flag) {
|
||||
metadata.next_sibling_offset = directory_metadata_offset_map.at(sibling.get());
|
||||
break;
|
||||
} else if (sibling.get() == directory) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!directory->directories.empty()) {
|
||||
metadata.first_child_directory_offset =
|
||||
directory_metadata_offset_map.at(directory->directories.front().get());
|
||||
}
|
||||
|
||||
if (!directory->files.empty()) {
|
||||
metadata.first_file_offset =
|
||||
file_metadata_offset_map.at(directory->files.front().get());
|
||||
}
|
||||
|
||||
const auto bucket = CalcHash(directory->name, metadata.parent_directory_offset) %
|
||||
directory_hash_table.size();
|
||||
metadata.hash_bucket_next = directory_hash_table[bucket];
|
||||
directory_hash_table[bucket] = directory_metadata_offset_map.at(directory);
|
||||
|
||||
// Write metadata and name
|
||||
std::u16string u16name = Common::UTF8ToUTF16(directory->name);
|
||||
metadata.name_length = static_cast<u32_le>(u16name.size() * 2);
|
||||
|
||||
std::memcpy(directory_metadata_table.data() + written, &metadata, sizeof(metadata));
|
||||
written += sizeof(metadata);
|
||||
|
||||
written += WriteName(directory_metadata_table.data() + written, u16name);
|
||||
}
|
||||
|
||||
ASSERT_MSG(written == directory_metadata_table.size(),
|
||||
"Calculated size for directory metadata table is wrong");
|
||||
}
|
||||
|
||||
void LayeredFS::BuildFiles() {
|
||||
file_metadata_table.resize(current_file_offset, 0xFF);
|
||||
|
||||
std::size_t written = 0;
|
||||
for (const auto& file : file_list) {
|
||||
FileMetadata metadata;
|
||||
std::memset(&metadata, 0xFF, sizeof(metadata));
|
||||
|
||||
metadata.parent_directory_offset = directory_metadata_offset_map.at(file->parent);
|
||||
|
||||
bool flag = false;
|
||||
for (const auto& sibling : file->parent->files) {
|
||||
if (sibling->relocation.type == 3) { // removed file
|
||||
continue;
|
||||
}
|
||||
if (flag) {
|
||||
metadata.next_sibling_offset = file_metadata_offset_map.at(sibling.get());
|
||||
break;
|
||||
} else if (sibling.get() == file) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
metadata.file_data_offset = current_data_offset;
|
||||
metadata.file_data_length = file->relocation.size;
|
||||
current_data_offset += Common::AlignUp(metadata.file_data_length, 16);
|
||||
if (metadata.file_data_length != 0) {
|
||||
data_offset_map.emplace(metadata.file_data_offset, file);
|
||||
}
|
||||
|
||||
const auto bucket =
|
||||
CalcHash(file->name, metadata.parent_directory_offset) % file_hash_table.size();
|
||||
metadata.hash_bucket_next = file_hash_table[bucket];
|
||||
file_hash_table[bucket] = file_metadata_offset_map.at(file);
|
||||
|
||||
// Write metadata and name
|
||||
std::u16string u16name = Common::UTF8ToUTF16(file->name);
|
||||
metadata.name_length = static_cast<u32_le>(u16name.size() * 2);
|
||||
|
||||
std::memcpy(file_metadata_table.data() + written, &metadata, sizeof(metadata));
|
||||
written += sizeof(metadata);
|
||||
|
||||
written += WriteName(file_metadata_table.data() + written, u16name);
|
||||
}
|
||||
|
||||
ASSERT_MSG(written == file_metadata_table.size(),
|
||||
"Calculated size for file metadata table is wrong");
|
||||
}
|
||||
|
||||
// Implementation from 3dbrew
|
||||
static std::size_t GetHashTableSize(std::size_t entry_count) {
|
||||
if (entry_count < 3) {
|
||||
return 3;
|
||||
} else if (entry_count < 19) {
|
||||
return entry_count | 1;
|
||||
} else {
|
||||
std::size_t count = entry_count;
|
||||
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
|
||||
count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
void LayeredFS::RebuildMetadata() {
|
||||
PrepareBuildDirectory(root);
|
||||
PrepareBuild(root);
|
||||
|
||||
directory_hash_table.resize(GetHashTableSize(directory_list.size()), 0xFFFFFFFF);
|
||||
file_hash_table.resize(GetHashTableSize(file_list.size()), 0xFFFFFFFF);
|
||||
|
||||
BuildDirectories();
|
||||
BuildFiles();
|
||||
|
||||
// Create header
|
||||
RomFSHeader header;
|
||||
header.header_length = sizeof(header);
|
||||
header.directory_hash_table = {
|
||||
/*offset*/ sizeof(header),
|
||||
/*length*/ static_cast<u32_le>(directory_hash_table.size() * sizeof(u32_le))};
|
||||
header.directory_metadata_table = {
|
||||
/*offset*/
|
||||
header.directory_hash_table.offset + header.directory_hash_table.length,
|
||||
/*length*/ static_cast<u32_le>(directory_metadata_table.size())};
|
||||
header.file_hash_table = {
|
||||
/*offset*/
|
||||
header.directory_metadata_table.offset + header.directory_metadata_table.length,
|
||||
/*length*/ static_cast<u32_le>(file_hash_table.size() * sizeof(u32_le))};
|
||||
header.file_metadata_table = {/*offset*/ header.file_hash_table.offset +
|
||||
header.file_hash_table.length,
|
||||
/*length*/ static_cast<u32_le>(file_metadata_table.size())};
|
||||
header.file_data_offset =
|
||||
Common::AlignUp(header.file_metadata_table.offset + header.file_metadata_table.length, 16);
|
||||
|
||||
// Write hash table and metadata table
|
||||
metadata.resize(header.file_data_offset);
|
||||
std::memcpy(metadata.data(), &header, header.header_length);
|
||||
std::memcpy(metadata.data() + header.directory_hash_table.offset, directory_hash_table.data(),
|
||||
header.directory_hash_table.length);
|
||||
std::memcpy(metadata.data() + header.directory_metadata_table.offset,
|
||||
directory_metadata_table.data(), header.directory_metadata_table.length);
|
||||
std::memcpy(metadata.data() + header.file_hash_table.offset, file_hash_table.data(),
|
||||
header.file_hash_table.length);
|
||||
std::memcpy(metadata.data() + header.file_metadata_table.offset, file_metadata_table.data(),
|
||||
header.file_metadata_table.length);
|
||||
}
|
||||
|
||||
std::size_t LayeredFS::GetSize() const {
|
||||
return metadata.size() + current_data_offset;
|
||||
}
|
||||
|
||||
std::size_t LayeredFS::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
||||
ASSERT_MSG(offset + length <= GetSize(), "Out of bound");
|
||||
|
||||
std::size_t read_size = 0;
|
||||
if (offset < metadata.size()) {
|
||||
// First read the metadata
|
||||
const auto to_read = std::min(metadata.size() - offset, length);
|
||||
std::memcpy(buffer, metadata.data() + offset, to_read);
|
||||
read_size += to_read;
|
||||
offset = 0;
|
||||
} else {
|
||||
offset -= metadata.size();
|
||||
}
|
||||
|
||||
// Read files
|
||||
auto current = (--data_offset_map.upper_bound(offset));
|
||||
while (read_size < length) {
|
||||
const auto relative_offset = offset - current->first;
|
||||
std::size_t to_read{};
|
||||
if (current->second->relocation.size > relative_offset) {
|
||||
to_read = std::min<std::size_t>(current->second->relocation.size - relative_offset,
|
||||
length - read_size);
|
||||
}
|
||||
const auto alignment =
|
||||
std::min<std::size_t>(Common::AlignUp(current->second->relocation.size, 16) -
|
||||
relative_offset,
|
||||
length - read_size) -
|
||||
to_read;
|
||||
|
||||
// Read the file in different ways depending on relocation type
|
||||
auto& relocation = current->second->relocation;
|
||||
if (relocation.type == 0) { // none
|
||||
romfs->ReadFile(relocation.original_offset + relative_offset, to_read,
|
||||
buffer + read_size);
|
||||
} else if (relocation.type == 1) { // replace
|
||||
FileUtil::IOFile replace_file(relocation.replace_file_path, "rb");
|
||||
if (replace_file) {
|
||||
replace_file.Seek(relative_offset, SEEK_SET);
|
||||
replace_file.ReadBytes(buffer + read_size, to_read);
|
||||
} else {
|
||||
LOG_ERROR(Service_FS, "Could not open replacement file for {}",
|
||||
current->second->path);
|
||||
}
|
||||
} else if (relocation.type == 2) { // patch
|
||||
std::memcpy(buffer + read_size, relocation.patched_file.data() + relative_offset,
|
||||
to_read);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::memset(buffer + read_size + to_read, 0, alignment);
|
||||
|
||||
read_size += to_read + alignment;
|
||||
offset += to_read + alignment;
|
||||
current++;
|
||||
}
|
||||
|
||||
return read_size;
|
||||
}
|
||||
|
||||
bool LayeredFS::ExtractDirectory(Directory& current, const std::string& target_path) {
|
||||
if (!FileUtil::CreateFullPath(target_path + current.path)) {
|
||||
LOG_ERROR(Service_FS, "Could not create path {}", target_path + current.path);
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr std::size_t BufferSize = 0x10000;
|
||||
std::array<u8, BufferSize> buffer;
|
||||
for (const auto& file : current.files) {
|
||||
// Extract file
|
||||
const auto path = target_path + file->path;
|
||||
LOG_INFO(Service_FS, "Extracting {} to {}", file->path, path);
|
||||
|
||||
FileUtil::IOFile target_file(path, "wb");
|
||||
if (!target_file) {
|
||||
LOG_ERROR(Service_FS, "Could not open file {}", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t written = 0;
|
||||
while (written < file->relocation.size) {
|
||||
const auto to_read =
|
||||
std::min<std::size_t>(buffer.size(), file->relocation.size - written);
|
||||
if (romfs->ReadFile(file->relocation.original_offset + written, to_read,
|
||||
buffer.data()) != to_read) {
|
||||
LOG_ERROR(Service_FS, "Could not read from RomFS");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target_file.WriteBytes(buffer.data(), to_read) != to_read) {
|
||||
LOG_ERROR(Service_FS, "Could not write to file {}", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
written += to_read;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& directory : current.directories) {
|
||||
if (!ExtractDirectory(*directory, target_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayeredFS::DumpRomFS(const std::string& target_path) {
|
||||
std::string path = target_path;
|
||||
if (path.back() == '/' || path.back() == '\\') {
|
||||
path.erase(path.size() - 1, 1);
|
||||
}
|
||||
|
||||
return ExtractDirectory(root, path);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
158
src/core/file_sys/layered_fs.h
Normal file
158
src/core/file_sys/layered_fs.h
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include <boost/serialization/string.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/romfs_reader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct RomFSHeader {
|
||||
struct Descriptor {
|
||||
u32_le offset;
|
||||
u32_le length;
|
||||
};
|
||||
u32_le header_length;
|
||||
Descriptor directory_hash_table;
|
||||
Descriptor directory_metadata_table;
|
||||
Descriptor file_hash_table;
|
||||
Descriptor file_metadata_table;
|
||||
u32_le file_data_offset;
|
||||
};
|
||||
static_assert(sizeof(RomFSHeader) == 0x28, "Size of RomFSHeader is not correct");
|
||||
|
||||
/**
|
||||
* LayeredFS implementation. This basically adds a layer to another RomFSReader.
|
||||
*
|
||||
* patch_path: Path for RomFS replacements. Files present in this path replace or create
|
||||
* corresponding files in RomFS.
|
||||
* patch_ext_path: Path for RomFS extensions. Files present in this path:
|
||||
* - When with an extension of ".stub", remove the corresponding file in the RomFS.
|
||||
* - When with an extension of ".ips" or ".bps", patch the file in the RomFS.
|
||||
*/
|
||||
class LayeredFS : public RomFSReader {
|
||||
public:
|
||||
explicit LayeredFS(std::shared_ptr<RomFSReader> romfs, std::string patch_path,
|
||||
std::string patch_ext_path, bool load_relocations = true);
|
||||
~LayeredFS() override;
|
||||
|
||||
std::size_t GetSize() const override;
|
||||
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
||||
|
||||
bool DumpRomFS(const std::string& target_path);
|
||||
|
||||
bool AllowsCachedReads() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
struct File;
|
||||
struct Directory {
|
||||
std::string name;
|
||||
std::string path; // with trailing '/'
|
||||
std::vector<std::unique_ptr<File>> files;
|
||||
std::vector<std::unique_ptr<Directory>> directories;
|
||||
Directory* parent;
|
||||
};
|
||||
|
||||
std::string ReadName(u32 offset, u32 name_length);
|
||||
|
||||
// Loads the current directory, then its children.
|
||||
// Returns offset of the next sibling directory to load (0xFFFFFFFF if the last directory)
|
||||
u32 LoadDirectory(Directory& current, u32 offset);
|
||||
|
||||
// Load the file at offset.
|
||||
// Returns offset of the next sibling file to load (0xFFFFFFFF if the last file)
|
||||
u32 LoadFile(Directory& parent, u32 offset);
|
||||
|
||||
// Load replace/create relocations
|
||||
void LoadRelocations();
|
||||
|
||||
// Load patch/remove relocations
|
||||
void LoadExtRelocations();
|
||||
|
||||
// Calculate the offset of a single directory add it to the map and list of directories
|
||||
void PrepareBuildDirectory(Directory& current);
|
||||
|
||||
// Calculate the offset of a single file add it to the map and list of files
|
||||
void PrepareBuildFile(File& current);
|
||||
|
||||
// Recursively generate a sequence of files and directories and their offsets for all
|
||||
// children of current. (The current directory itself is not handled.)
|
||||
void PrepareBuild(Directory& current);
|
||||
|
||||
void BuildDirectories();
|
||||
void BuildFiles();
|
||||
|
||||
// Recursively extract a directory and all its contents to target_path
|
||||
// target_path should be without trailing '/'.
|
||||
bool ExtractDirectory(Directory& current, const std::string& target_path);
|
||||
|
||||
void RebuildMetadata();
|
||||
|
||||
void Load();
|
||||
|
||||
std::shared_ptr<RomFSReader> romfs;
|
||||
std::string patch_path;
|
||||
std::string patch_ext_path;
|
||||
bool load_relocations;
|
||||
|
||||
RomFSHeader header;
|
||||
Directory root;
|
||||
std::unordered_map<std::string, File*> file_path_map;
|
||||
std::unordered_map<std::string, Directory*> directory_path_map;
|
||||
std::map<u64, File*> data_offset_map; // assigned data offset -> file
|
||||
std::vector<u8> metadata; // Includes header, hash table and metadata
|
||||
|
||||
// Used for rebuilding header
|
||||
std::vector<u32_le> directory_hash_table;
|
||||
std::vector<u32_le> file_hash_table;
|
||||
|
||||
std::unordered_map<Directory*, u32>
|
||||
directory_metadata_offset_map; // directory -> metadata offset
|
||||
std::vector<Directory*> directory_list; // sequence of directories to be written to metadata
|
||||
u64 current_directory_offset{}; // current directory metadata offset
|
||||
std::vector<u8> directory_metadata_table; // rebuilt directory metadata table
|
||||
|
||||
std::unordered_map<File*, u32> file_metadata_offset_map; // file -> metadata offset
|
||||
std::vector<File*> file_list; // sequence of files to be written to metadata
|
||||
u64 current_file_offset{}; // current file metadata offset
|
||||
std::vector<u8> file_metadata_table; // rebuilt file metadata table
|
||||
u64 current_data_offset{}; // current assigned data offset
|
||||
|
||||
LayeredFS();
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||
ar& romfs;
|
||||
ar& patch_path;
|
||||
ar& patch_ext_path;
|
||||
ar& load_relocations;
|
||||
if (Archive::is_loading::value) {
|
||||
Load();
|
||||
}
|
||||
// NOTE: Everything else is essentially cached, updated when we call Load
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::LayeredFS)
|
||||
823
src/core/file_sys/ncch_container.cpp
Normal file
823
src/core/file_sys/ncch_container.cpp
Normal file
@@ -0,0 +1,823 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/layered_fs.h"
|
||||
#include "core/file_sys/ncch_container.h"
|
||||
#include "core/file_sys/patch.h"
|
||||
#include "core/file_sys/seed_db.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs
|
||||
static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes)
|
||||
|
||||
u64 GetModId(u64 program_id) {
|
||||
constexpr u64 UPDATE_MASK = 0x0000000e'00000000;
|
||||
if ((program_id & 0x000000ff'00000000) == UPDATE_MASK) { // Apply the mods to updates
|
||||
return program_id & ~UPDATE_MASK;
|
||||
}
|
||||
return program_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the decompressed size of an LZSS compressed ExeFS file
|
||||
* @param buffer Buffer of compressed file
|
||||
* @param size Size of compressed buffer
|
||||
* @return Size of decompressed buffer
|
||||
*/
|
||||
static std::size_t LZSS_GetDecompressedSize(std::span<const u8> buffer) {
|
||||
u32 offset_size;
|
||||
std::memcpy(&offset_size, buffer.data() + buffer.size() - sizeof(u32), sizeof(u32));
|
||||
return offset_size + buffer.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress ExeFS file (compressed with LZSS)
|
||||
* @param compressed Compressed buffer
|
||||
* @param compressed_size Size of compressed buffer
|
||||
* @param decompressed Decompressed buffer
|
||||
* @param decompressed_size Size of decompressed buffer
|
||||
* @return True on success, otherwise false
|
||||
*/
|
||||
static bool LZSS_Decompress(std::span<const u8> compressed, std::span<u8> decompressed) {
|
||||
const u8* footer = compressed.data() + compressed.size() - 8;
|
||||
|
||||
u32 buffer_top_and_bottom;
|
||||
std::memcpy(&buffer_top_and_bottom, footer, sizeof(u32));
|
||||
|
||||
std::size_t out = decompressed.size();
|
||||
std::size_t index = compressed.size() - ((buffer_top_and_bottom >> 24) & 0xFF);
|
||||
std::size_t stop_index = compressed.size() - (buffer_top_and_bottom & 0xFFFFFF);
|
||||
|
||||
std::memset(decompressed.data(), 0, decompressed.size());
|
||||
std::memcpy(decompressed.data(), compressed.data(), compressed.size());
|
||||
|
||||
while (index > stop_index) {
|
||||
u8 control = compressed[--index];
|
||||
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
if (index <= stop_index)
|
||||
break;
|
||||
if (index <= 0)
|
||||
break;
|
||||
if (out <= 0)
|
||||
break;
|
||||
|
||||
if (control & 0x80) {
|
||||
// Check if compression is out of bounds
|
||||
if (index < 2)
|
||||
return false;
|
||||
index -= 2;
|
||||
|
||||
u32 segment_offset = compressed[index] | (compressed[index + 1] << 8);
|
||||
u32 segment_size = ((segment_offset >> 12) & 15) + 3;
|
||||
segment_offset &= 0x0FFF;
|
||||
segment_offset += 2;
|
||||
|
||||
// Check if compression is out of bounds
|
||||
if (out < segment_size)
|
||||
return false;
|
||||
|
||||
for (unsigned j = 0; j < segment_size; j++) {
|
||||
// Check if compression is out of bounds
|
||||
if (out + segment_offset >= decompressed.size())
|
||||
return false;
|
||||
|
||||
u8 data = decompressed[out + segment_offset];
|
||||
decompressed[--out] = data;
|
||||
}
|
||||
} else {
|
||||
// Check if compression is out of bounds
|
||||
if (out < 1)
|
||||
return false;
|
||||
decompressed[--out] = compressed[--index];
|
||||
}
|
||||
control <<= 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset, u32 partition)
|
||||
: ncch_offset(ncch_offset), partition(partition), filepath(filepath) {
|
||||
file = FileUtil::IOFile(filepath, "rb");
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath_, u32 ncch_offset_,
|
||||
u32 partition_) {
|
||||
filepath = filepath_;
|
||||
ncch_offset = ncch_offset_;
|
||||
partition = partition_;
|
||||
file = FileUtil::IOFile(filepath_, "rb");
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
LOG_WARNING(Service_FS, "Failed to open {}", filepath);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Opened {}", filepath);
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::LoadHeader() {
|
||||
if (has_header) {
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
if (!file.IsOpen()) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
|
||||
if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
|
||||
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
|
||||
NCSD_Header ncsd_header;
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncsd_header, sizeof(NCSD_Header));
|
||||
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
|
||||
ASSERT(partition < 8);
|
||||
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
|
||||
LOG_ERROR(Service_FS, "{}", ncch_offset);
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
|
||||
}
|
||||
|
||||
// Verify we are loading the correct file type...
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
has_header = true;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::Load() {
|
||||
if (is_loaded)
|
||||
return Loader::ResultStatus::Success;
|
||||
|
||||
if (file.IsOpen()) {
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
|
||||
if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
|
||||
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
|
||||
NCSD_Header ncsd_header;
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncsd_header, sizeof(NCSD_Header));
|
||||
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
|
||||
ASSERT(partition < 8);
|
||||
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
|
||||
}
|
||||
|
||||
// Verify we are loading the correct file type...
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic)
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
|
||||
has_header = true;
|
||||
bool failed_to_decrypt = false;
|
||||
if (!ncch_header.no_crypto) {
|
||||
is_encrypted = true;
|
||||
|
||||
// Find primary and secondary keys
|
||||
if (ncch_header.fixed_key) {
|
||||
LOG_DEBUG(Service_FS, "Fixed-key crypto");
|
||||
primary_key.fill(0);
|
||||
secondary_key.fill(0);
|
||||
} else {
|
||||
using namespace HW::AES;
|
||||
InitKeys();
|
||||
std::array<u8, 16> key_y_primary, key_y_secondary;
|
||||
|
||||
std::copy(ncch_header.signature, ncch_header.signature + key_y_primary.size(),
|
||||
key_y_primary.begin());
|
||||
|
||||
if (!ncch_header.seed_crypto) {
|
||||
key_y_secondary = key_y_primary;
|
||||
} else {
|
||||
auto opt{FileSys::GetSeed(ncch_header.program_id)};
|
||||
if (!opt.has_value()) {
|
||||
LOG_ERROR(Service_FS, "Seed for program {:016X} not found",
|
||||
ncch_header.program_id);
|
||||
failed_to_decrypt = true;
|
||||
} else {
|
||||
auto seed{*opt};
|
||||
std::array<u8, 32> input;
|
||||
std::memcpy(input.data(), key_y_primary.data(), key_y_primary.size());
|
||||
std::memcpy(input.data() + key_y_primary.size(), seed.data(), seed.size());
|
||||
CryptoPP::SHA256 sha;
|
||||
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
|
||||
sha.CalculateDigest(hash.data(), input.data(), input.size());
|
||||
std::memcpy(key_y_secondary.data(), hash.data(), key_y_secondary.size());
|
||||
}
|
||||
}
|
||||
|
||||
SetKeyY(KeySlotID::NCCHSecure1, key_y_primary);
|
||||
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure1)) {
|
||||
LOG_ERROR(Service_FS, "Secure1 KeyX missing");
|
||||
failed_to_decrypt = true;
|
||||
}
|
||||
primary_key = GetNormalKey(KeySlotID::NCCHSecure1);
|
||||
|
||||
switch (ncch_header.secondary_key_slot) {
|
||||
case 0:
|
||||
LOG_DEBUG(Service_FS, "Secure1 crypto");
|
||||
SetKeyY(KeySlotID::NCCHSecure1, key_y_secondary);
|
||||
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure1)) {
|
||||
LOG_ERROR(Service_FS, "Secure1 KeyX missing");
|
||||
failed_to_decrypt = true;
|
||||
}
|
||||
secondary_key = GetNormalKey(KeySlotID::NCCHSecure1);
|
||||
break;
|
||||
case 1:
|
||||
LOG_DEBUG(Service_FS, "Secure2 crypto");
|
||||
SetKeyY(KeySlotID::NCCHSecure2, key_y_secondary);
|
||||
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure2)) {
|
||||
LOG_ERROR(Service_FS, "Secure2 KeyX missing");
|
||||
failed_to_decrypt = true;
|
||||
}
|
||||
secondary_key = GetNormalKey(KeySlotID::NCCHSecure2);
|
||||
break;
|
||||
case 10:
|
||||
LOG_DEBUG(Service_FS, "Secure3 crypto");
|
||||
SetKeyY(KeySlotID::NCCHSecure3, key_y_secondary);
|
||||
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure3)) {
|
||||
LOG_ERROR(Service_FS, "Secure3 KeyX missing");
|
||||
failed_to_decrypt = true;
|
||||
}
|
||||
secondary_key = GetNormalKey(KeySlotID::NCCHSecure3);
|
||||
break;
|
||||
case 11:
|
||||
LOG_DEBUG(Service_FS, "Secure4 crypto");
|
||||
SetKeyY(KeySlotID::NCCHSecure4, key_y_secondary);
|
||||
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure4)) {
|
||||
LOG_ERROR(Service_FS, "Secure4 KeyX missing");
|
||||
failed_to_decrypt = true;
|
||||
}
|
||||
secondary_key = GetNormalKey(KeySlotID::NCCHSecure4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find CTR for each section
|
||||
// Written with reference to
|
||||
// https://github.com/d0k3/GodMode9/blob/99af6a73be48fa7872649aaa7456136da0df7938/arm9/source/game/ncch.c#L34-L52
|
||||
if (ncch_header.version == 0 || ncch_header.version == 2) {
|
||||
LOG_DEBUG(Loader, "NCCH version 0/2");
|
||||
// In this version, CTR for each section is a magic number prefixed by partition ID
|
||||
// (reverse order)
|
||||
std::reverse_copy(ncch_header.partition_id, ncch_header.partition_id + 8,
|
||||
exheader_ctr.begin());
|
||||
exefs_ctr = romfs_ctr = exheader_ctr;
|
||||
exheader_ctr[8] = 1;
|
||||
exefs_ctr[8] = 2;
|
||||
romfs_ctr[8] = 3;
|
||||
} else if (ncch_header.version == 1) {
|
||||
LOG_DEBUG(Loader, "NCCH version 1");
|
||||
// In this version, CTR for each section is the section offset prefixed by partition
|
||||
// ID, as if the entire NCCH image is encrypted using a single CTR stream.
|
||||
std::copy(ncch_header.partition_id, ncch_header.partition_id + 8,
|
||||
exheader_ctr.begin());
|
||||
exefs_ctr = romfs_ctr = exheader_ctr;
|
||||
auto u32ToBEArray = [](u32 value) -> std::array<u8, 4> {
|
||||
return std::array<u8, 4>{
|
||||
static_cast<u8>(value >> 24),
|
||||
static_cast<u8>((value >> 16) & 0xFF),
|
||||
static_cast<u8>((value >> 8) & 0xFF),
|
||||
static_cast<u8>(value & 0xFF),
|
||||
};
|
||||
};
|
||||
auto offset_exheader = u32ToBEArray(0x200); // exheader offset
|
||||
auto offset_exefs = u32ToBEArray(ncch_header.exefs_offset * kBlockSize);
|
||||
auto offset_romfs = u32ToBEArray(ncch_header.romfs_offset * kBlockSize);
|
||||
std::copy(offset_exheader.begin(), offset_exheader.end(),
|
||||
exheader_ctr.begin() + 12);
|
||||
std::copy(offset_exefs.begin(), offset_exefs.end(), exefs_ctr.begin() + 12);
|
||||
std::copy(offset_romfs.begin(), offset_romfs.end(), romfs_ctr.begin() + 12);
|
||||
} else {
|
||||
LOG_ERROR(Service_FS, "Unknown NCCH version {}", ncch_header.version);
|
||||
failed_to_decrypt = true;
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Service_FS, "No crypto");
|
||||
is_encrypted = false;
|
||||
}
|
||||
|
||||
// System archives and DLC don't have an extended header but have RomFS
|
||||
if (ncch_header.extended_header_size) {
|
||||
auto read_exheader = [this](FileUtil::IOFile& file) {
|
||||
const std::size_t size = sizeof(exheader_header);
|
||||
return file && file.ReadBytes(&exheader_header, size) == size;
|
||||
};
|
||||
|
||||
if (!read_exheader(file)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
if (is_encrypted) {
|
||||
// This ID check is masked to low 32-bit as a toleration to ill-formed ROM created
|
||||
// by merging games and its updates.
|
||||
if ((exheader_header.system_info.jump_id & 0xFFFFFFFF) ==
|
||||
(ncch_header.program_id & 0xFFFFFFFF)) {
|
||||
LOG_WARNING(Service_FS, "NCCH is marked as encrypted but with decrypted "
|
||||
"exheader. Force no crypto scheme.");
|
||||
is_encrypted = false;
|
||||
} else {
|
||||
if (failed_to_decrypt) {
|
||||
LOG_ERROR(Service_FS, "Failed to decrypt");
|
||||
return Loader::ResultStatus::ErrorEncrypted;
|
||||
}
|
||||
CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(&exheader_header);
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption(
|
||||
primary_key.data(), primary_key.size(), exheader_ctr.data())
|
||||
.ProcessData(data, data, sizeof(exheader_header));
|
||||
}
|
||||
}
|
||||
|
||||
const auto mods_path =
|
||||
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
GetModId(ncch_header.program_id));
|
||||
const std::array<std::string, 2> exheader_override_paths{{
|
||||
mods_path + "exheader.bin",
|
||||
filepath + ".exheader",
|
||||
}};
|
||||
|
||||
bool has_exheader_override = false;
|
||||
for (const auto& path : exheader_override_paths) {
|
||||
FileUtil::IOFile exheader_override_file{path, "rb"};
|
||||
if (read_exheader(exheader_override_file)) {
|
||||
has_exheader_override = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_exheader_override) {
|
||||
if (exheader_header.system_info.jump_id !=
|
||||
exheader_header.arm11_system_local_caps.program_id) {
|
||||
LOG_WARNING(Service_FS, "Jump ID and Program ID don't match. "
|
||||
"The override exheader might not be decrypted.");
|
||||
}
|
||||
is_tainted = true;
|
||||
}
|
||||
|
||||
is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1;
|
||||
u32 entry_point = exheader_header.codeset_info.text.address;
|
||||
u32 code_size = exheader_header.codeset_info.text.code_size;
|
||||
u32 stack_size = exheader_header.codeset_info.stack_size;
|
||||
u32 bss_size = exheader_header.codeset_info.bss_size;
|
||||
u32 core_version = exheader_header.arm11_system_local_caps.core_version;
|
||||
u8 priority = exheader_header.arm11_system_local_caps.priority;
|
||||
u8 resource_limit_category =
|
||||
exheader_header.arm11_system_local_caps.resource_limit_category;
|
||||
|
||||
LOG_DEBUG(Service_FS, "Name: {}",
|
||||
reinterpret_cast<const char*>(exheader_header.codeset_info.name));
|
||||
LOG_DEBUG(Service_FS, "Program ID: {:016X}", ncch_header.program_id);
|
||||
LOG_DEBUG(Service_FS, "Code compressed: {}", is_compressed ? "yes" : "no");
|
||||
LOG_DEBUG(Service_FS, "Entry point: 0x{:08X}", entry_point);
|
||||
LOG_DEBUG(Service_FS, "Code size: 0x{:08X}", code_size);
|
||||
LOG_DEBUG(Service_FS, "Stack size: 0x{:08X}", stack_size);
|
||||
LOG_DEBUG(Service_FS, "Bss size: 0x{:08X}", bss_size);
|
||||
LOG_DEBUG(Service_FS, "Core version: {}", core_version);
|
||||
LOG_DEBUG(Service_FS, "Thread priority: 0x{:X}", priority);
|
||||
LOG_DEBUG(Service_FS, "Resource limit category: {}", resource_limit_category);
|
||||
LOG_DEBUG(Service_FS, "System Mode: {}",
|
||||
static_cast<int>(exheader_header.arm11_system_local_caps.system_mode));
|
||||
|
||||
has_exheader = true;
|
||||
}
|
||||
|
||||
// DLC can have an ExeFS and a RomFS but no extended header
|
||||
if (ncch_header.exefs_size) {
|
||||
exefs_offset = ncch_header.exefs_offset * kBlockSize;
|
||||
u32 exefs_size = ncch_header.exefs_size * kBlockSize;
|
||||
|
||||
LOG_DEBUG(Service_FS, "ExeFS offset: 0x{:08X}", exefs_offset);
|
||||
LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size);
|
||||
|
||||
file.Seek(exefs_offset + ncch_offset, SEEK_SET);
|
||||
if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
if (is_encrypted) {
|
||||
CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(&exefs_header);
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption(primary_key.data(),
|
||||
primary_key.size(), exefs_ctr.data())
|
||||
.ProcessData(data, data, sizeof(exefs_header));
|
||||
}
|
||||
|
||||
exefs_file = FileUtil::IOFile(filepath, "rb");
|
||||
has_exefs = true;
|
||||
}
|
||||
|
||||
if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0)
|
||||
has_romfs = true;
|
||||
}
|
||||
|
||||
LoadOverrides();
|
||||
|
||||
// We need at least one of these or overrides, practically
|
||||
if (!(has_exefs || has_romfs || is_tainted))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
is_loaded = true;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::LoadOverrides() {
|
||||
// Check for split-off files, mark the archive as tainted if we will use them
|
||||
std::string romfs_override = filepath + ".romfs";
|
||||
if (FileUtil::Exists(romfs_override)) {
|
||||
is_tainted = true;
|
||||
}
|
||||
|
||||
// If we have a split-off exefs file/folder, it takes priority
|
||||
std::string exefs_override = filepath + ".exefs";
|
||||
std::string exefsdir_override = filepath + ".exefsdir/";
|
||||
if (FileUtil::Exists(exefs_override)) {
|
||||
exefs_file = FileUtil::IOFile(exefs_override, "rb");
|
||||
|
||||
if (exefs_file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) == sizeof(ExeFs_Header)) {
|
||||
LOG_DEBUG(Service_FS, "Loading ExeFS section from {}", exefs_override);
|
||||
exefs_offset = 0;
|
||||
is_tainted = true;
|
||||
has_exefs = true;
|
||||
} else {
|
||||
exefs_file = FileUtil::IOFile(filepath, "rb");
|
||||
}
|
||||
} else if (FileUtil::Exists(exefsdir_override) && FileUtil::IsDirectory(exefsdir_override)) {
|
||||
is_tainted = true;
|
||||
}
|
||||
|
||||
if (is_tainted)
|
||||
LOG_WARNING(Service_FS,
|
||||
"Loaded NCCH {} is tainted, application behavior may not be as expected!",
|
||||
filepath);
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector<u8>& buffer) {
|
||||
Loader::ResultStatus result = Load();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
// Check if we have files that can drop-in and replace
|
||||
result = LoadOverrideExeFSSection(name, buffer);
|
||||
if (result == Loader::ResultStatus::Success || !has_exefs)
|
||||
return result;
|
||||
|
||||
// As of firmware 5.0.0-11 the logo is stored between the access descriptor and the plain region
|
||||
// instead of the ExeFS.
|
||||
if (std::strcmp(name, "logo") == 0) {
|
||||
if (ncch_header.logo_region_offset && ncch_header.logo_region_size) {
|
||||
std::size_t logo_offset = ncch_header.logo_region_offset * kBlockSize;
|
||||
std::size_t logo_size = ncch_header.logo_region_size * kBlockSize;
|
||||
|
||||
buffer.resize(logo_size);
|
||||
file.Seek(ncch_offset + logo_offset, SEEK_SET);
|
||||
|
||||
if (file.ReadBytes(buffer.data(), logo_size) != logo_size) {
|
||||
LOG_ERROR(Service_FS, "Could not read NCCH logo");
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
return Loader::ResultStatus::Success;
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Attempting to load logo from the ExeFS");
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have any separate files, we'll need a full ExeFS
|
||||
if (!exefs_file.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
LOG_DEBUG(Service_FS, "{} sections:", kMaxSections);
|
||||
// Iterate through the ExeFs archive until we find a section with the specified name...
|
||||
for (unsigned section_number = 0; section_number < kMaxSections; section_number++) {
|
||||
const auto& section = exefs_header.section[section_number];
|
||||
|
||||
// Load the specified section...
|
||||
if (strcmp(section.name, name) == 0) {
|
||||
LOG_DEBUG(Service_FS, "{} - offset: 0x{:08X}, size: 0x{:08X}, name: {}", section_number,
|
||||
section.offset, section.size, section.name);
|
||||
|
||||
s64 section_offset =
|
||||
(section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset);
|
||||
exefs_file.Seek(section_offset, SEEK_SET);
|
||||
|
||||
std::array<u8, 16> key;
|
||||
if (strcmp(section.name, "icon") == 0 || strcmp(section.name, "banner") == 0) {
|
||||
key = primary_key;
|
||||
} else {
|
||||
key = secondary_key;
|
||||
}
|
||||
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption dec(key.data(), key.size(),
|
||||
exefs_ctr.data());
|
||||
dec.Seek(section.offset + sizeof(ExeFs_Header));
|
||||
|
||||
if (strcmp(section.name, ".code") == 0 && is_compressed) {
|
||||
// Section is compressed, read compressed .code section...
|
||||
std::vector<u8> temp_buffer(section.size);
|
||||
if (exefs_file.ReadBytes(temp_buffer.data(), temp_buffer.size()) !=
|
||||
temp_buffer.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
if (is_encrypted) {
|
||||
dec.ProcessData(&temp_buffer[0], &temp_buffer[0], section.size);
|
||||
}
|
||||
|
||||
// Decompress .code section...
|
||||
buffer.resize(LZSS_GetDecompressedSize(temp_buffer));
|
||||
if (!LZSS_Decompress(temp_buffer, buffer)) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
} else {
|
||||
// Section is uncompressed...
|
||||
buffer.resize(section.size);
|
||||
if (exefs_file.ReadBytes(buffer.data(), section.size) != section.size)
|
||||
return Loader::ResultStatus::Error;
|
||||
if (is_encrypted) {
|
||||
dec.ProcessData(buffer.data(), buffer.data(), section.size);
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::ApplyCodePatch(std::vector<u8>& code) const {
|
||||
struct PatchLocation {
|
||||
std::string path;
|
||||
bool (*patch_fn)(const std::vector<u8>& patch, std::vector<u8>& code);
|
||||
};
|
||||
|
||||
const auto mods_path =
|
||||
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
GetModId(ncch_header.program_id));
|
||||
|
||||
constexpr u32 system_module_tid_high = 0x00040130;
|
||||
|
||||
std::string luma_ips_location;
|
||||
if ((static_cast<u32>(ncch_header.program_id >> 32) & system_module_tid_high) ==
|
||||
system_module_tid_high) {
|
||||
luma_ips_location =
|
||||
fmt::format("{}luma/sysmodules/{:016X}.ips",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), ncch_header.program_id);
|
||||
} else {
|
||||
luma_ips_location =
|
||||
fmt::format("{}luma/titles/{:016X}/code.ips",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), ncch_header.program_id);
|
||||
}
|
||||
const std::array<PatchLocation, 7> patch_paths{{
|
||||
{mods_path + "exefs/code.ips", Patch::ApplyIpsPatch},
|
||||
{mods_path + "exefs/code.bps", Patch::ApplyBpsPatch},
|
||||
{mods_path + "code.ips", Patch::ApplyIpsPatch},
|
||||
{mods_path + "code.bps", Patch::ApplyBpsPatch},
|
||||
{luma_ips_location, Patch::ApplyIpsPatch},
|
||||
{filepath + ".exefsdir/code.ips", Patch::ApplyIpsPatch},
|
||||
{filepath + ".exefsdir/code.bps", Patch::ApplyBpsPatch},
|
||||
}};
|
||||
|
||||
for (const PatchLocation& info : patch_paths) {
|
||||
FileUtil::IOFile patch_file{info.path, "rb"};
|
||||
if (!patch_file)
|
||||
continue;
|
||||
|
||||
std::vector<u8> patch(patch_file.GetSize());
|
||||
if (patch_file.ReadBytes(patch.data(), patch.size()) != patch.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
LOG_INFO(Service_FS, "File {} patching code.bin", info.path);
|
||||
if (!info.patch_fn(patch, code))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name,
|
||||
std::vector<u8>& buffer) {
|
||||
std::string override_name;
|
||||
|
||||
// Map our section name to the extracted equivalent
|
||||
if (!strcmp(name, ".code"))
|
||||
override_name = "code.bin";
|
||||
else if (!strcmp(name, "icon"))
|
||||
override_name = "icon.bin";
|
||||
else if (!strcmp(name, "banner"))
|
||||
override_name = "banner.bnr";
|
||||
else if (!strcmp(name, "logo"))
|
||||
override_name = "logo.bcma.lz";
|
||||
else
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
const auto mods_path =
|
||||
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
GetModId(ncch_header.program_id));
|
||||
const std::array<std::string, 3> override_paths{{
|
||||
mods_path + "exefs/" + override_name,
|
||||
mods_path + override_name,
|
||||
filepath + ".exefsdir/" + override_name,
|
||||
}};
|
||||
|
||||
for (const auto& path : override_paths) {
|
||||
FileUtil::IOFile section_file(path, "rb");
|
||||
|
||||
if (section_file.IsOpen()) {
|
||||
auto section_size = section_file.GetSize();
|
||||
buffer.resize(section_size);
|
||||
|
||||
section_file.Seek(0, SEEK_SET);
|
||||
if (section_file.ReadBytes(buffer.data(), section_size) == section_size) {
|
||||
LOG_WARNING(Service_FS, "File {} overriding built-in ExeFS file", path);
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file,
|
||||
bool use_layered_fs) {
|
||||
Loader::ResultStatus result = Load();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
if (ReadOverrideRomFS(romfs_file) == Loader::ResultStatus::Success)
|
||||
return Loader::ResultStatus::Success;
|
||||
|
||||
if (!has_romfs) {
|
||||
LOG_DEBUG(Service_FS, "RomFS requested from NCCH which has no RomFS");
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
if (!file.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000;
|
||||
u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000;
|
||||
|
||||
LOG_DEBUG(Service_FS, "RomFS offset: 0x{:08X}", romfs_offset);
|
||||
LOG_DEBUG(Service_FS, "RomFS size: 0x{:08X}", romfs_size);
|
||||
|
||||
if (file.GetSize() < romfs_offset + romfs_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// We reopen the file, to allow its position to be independent from file's
|
||||
FileUtil::IOFile romfs_file_inner(filepath, "rb");
|
||||
if (!romfs_file_inner.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
std::shared_ptr<RomFSReader> direct_romfs;
|
||||
if (is_encrypted) {
|
||||
direct_romfs =
|
||||
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset,
|
||||
romfs_size, secondary_key, romfs_ctr, 0x1000);
|
||||
} else {
|
||||
direct_romfs = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner),
|
||||
romfs_offset, romfs_size);
|
||||
}
|
||||
|
||||
const auto path =
|
||||
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
GetModId(ncch_header.program_id));
|
||||
if (use_layered_fs &&
|
||||
(FileUtil::Exists(path + "romfs/") || FileUtil::Exists(path + "romfs_ext/"))) {
|
||||
|
||||
romfs_file = std::make_shared<LayeredFS>(std::move(direct_romfs), path + "romfs/",
|
||||
path + "romfs_ext/");
|
||||
} else {
|
||||
romfs_file = std::move(direct_romfs);
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::DumpRomFS(const std::string& target_path) {
|
||||
std::shared_ptr<RomFSReader> direct_romfs;
|
||||
Loader::ResultStatus result = ReadRomFS(direct_romfs, false);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
std::shared_ptr<LayeredFS> layered_fs =
|
||||
std::make_shared<LayeredFS>(std::move(direct_romfs), "", "", false);
|
||||
|
||||
if (!layered_fs->DumpRomFS(target_path)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReader>& romfs_file) {
|
||||
// Check for RomFS overrides
|
||||
std::string split_filepath = filepath + ".romfs";
|
||||
if (FileUtil::Exists(split_filepath)) {
|
||||
FileUtil::IOFile romfs_file_inner(split_filepath, "rb");
|
||||
if (romfs_file_inner.IsOpen()) {
|
||||
LOG_WARNING(Service_FS, "File {} overriding built-in RomFS; LayeredFS not enabled",
|
||||
split_filepath);
|
||||
romfs_file = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), 0,
|
||||
romfs_file_inner.GetSize());
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) {
|
||||
Loader::ResultStatus result = LoadHeader();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
if (!has_header)
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
|
||||
program_id = ncch_header.program_id;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::ReadExtdataId(u64& extdata_id) {
|
||||
Loader::ResultStatus result = Load();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
if (!has_exheader)
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
|
||||
if (exheader_header.arm11_system_local_caps.storage_info.other_attributes >> 1) {
|
||||
// Using extended save data access
|
||||
// There would be multiple possible extdata IDs in this case. The best we can do for now is
|
||||
// guessing that the first one would be the main save.
|
||||
const std::array<u64, 6> extdata_ids{{
|
||||
exheader_header.arm11_system_local_caps.storage_info.extdata_id0.Value(),
|
||||
exheader_header.arm11_system_local_caps.storage_info.extdata_id1.Value(),
|
||||
exheader_header.arm11_system_local_caps.storage_info.extdata_id2.Value(),
|
||||
exheader_header.arm11_system_local_caps.storage_info.extdata_id3.Value(),
|
||||
exheader_header.arm11_system_local_caps.storage_info.extdata_id4.Value(),
|
||||
exheader_header.arm11_system_local_caps.storage_info.extdata_id5.Value(),
|
||||
}};
|
||||
for (u64 id : extdata_ids) {
|
||||
if (id) {
|
||||
// Found a non-zero ID, use it
|
||||
extdata_id = id;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
extdata_id = exheader_header.arm11_system_local_caps.storage_info.ext_save_data_id;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
bool NCCHContainer::HasExeFS() {
|
||||
Loader::ResultStatus result = Load();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
|
||||
return has_exefs;
|
||||
}
|
||||
|
||||
bool NCCHContainer::HasRomFS() {
|
||||
Loader::ResultStatus result = Load();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
|
||||
return has_romfs;
|
||||
}
|
||||
|
||||
bool NCCHContainer::HasExHeader() {
|
||||
Loader::ResultStatus result = Load();
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
|
||||
return has_exheader;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
367
src/core/file_sys/ncch_container.h
Normal file
367
src/core/file_sys/ncch_container.h
Normal file
@@ -0,0 +1,367 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/romfs_reader.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
enum NCSDContentIndex { Main = 0, Manual = 1, DLP = 2, New3DSUpdate = 6, Update = 7 };
|
||||
|
||||
struct NCSD_Partitions {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct NCSD_Header {
|
||||
u8 signature[0x100];
|
||||
u32_le magic;
|
||||
u32_le media_size;
|
||||
u8 media_id[8];
|
||||
u8 partition_fs_type[8];
|
||||
u8 partition_crypt_type[8];
|
||||
NCSD_Partitions partitions[8];
|
||||
u8 extended_header_hash[0x20];
|
||||
u32_le additional_header_size;
|
||||
u32_le sector_zero_offset;
|
||||
u8 partition_flags[8];
|
||||
u8 partition_id_table[0x40];
|
||||
u8 reserved[0x30];
|
||||
};
|
||||
|
||||
static_assert(sizeof(NCSD_Header) == 0x200, "NCCH header structure size is wrong");
|
||||
|
||||
struct NCCH_Header {
|
||||
u8 signature[0x100];
|
||||
u32_le magic;
|
||||
u32_le content_size;
|
||||
u8 partition_id[8];
|
||||
u16_le maker_code;
|
||||
u16_le version;
|
||||
u8 reserved_0[4];
|
||||
u64_le program_id;
|
||||
u8 reserved_1[0x10];
|
||||
u8 logo_region_hash[0x20];
|
||||
u8 product_code[0x10];
|
||||
u8 extended_header_hash[0x20];
|
||||
u32_le extended_header_size;
|
||||
u8 reserved_2[4];
|
||||
u8 reserved_flag[3];
|
||||
u8 secondary_key_slot;
|
||||
u8 platform;
|
||||
enum class ContentType : u8 {
|
||||
Application = 0,
|
||||
SystemUpdate = 1,
|
||||
Manual = 2,
|
||||
Child = 3,
|
||||
Trial = 4,
|
||||
};
|
||||
union {
|
||||
BitField<0, 1, u8> is_data;
|
||||
BitField<1, 1, u8> is_executable;
|
||||
BitField<2, 3, ContentType> content_type;
|
||||
};
|
||||
u8 content_unit_size;
|
||||
union {
|
||||
BitField<0, 1, u8> fixed_key;
|
||||
BitField<1, 1, u8> no_romfs;
|
||||
BitField<2, 1, u8> no_crypto;
|
||||
BitField<5, 1, u8> seed_crypto;
|
||||
};
|
||||
u32_le plain_region_offset;
|
||||
u32_le plain_region_size;
|
||||
u32_le logo_region_offset;
|
||||
u32_le logo_region_size;
|
||||
u32_le exefs_offset;
|
||||
u32_le exefs_size;
|
||||
u32_le exefs_hash_region_size;
|
||||
u8 reserved_3[4];
|
||||
u32_le romfs_offset;
|
||||
u32_le romfs_size;
|
||||
u32_le romfs_hash_region_size;
|
||||
u8 reserved_4[4];
|
||||
u8 exefs_super_block_hash[0x20];
|
||||
u8 romfs_super_block_hash[0x20];
|
||||
};
|
||||
|
||||
static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong");
|
||||
|
||||
struct ExeFs_SectionHeader {
|
||||
char name[8];
|
||||
u32 offset;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct ExeFs_Header {
|
||||
ExeFs_SectionHeader section[8];
|
||||
u8 reserved[0x80];
|
||||
u8 hashes[8][0x20];
|
||||
};
|
||||
|
||||
struct ExHeader_SystemInfoFlags {
|
||||
u8 reserved[5];
|
||||
u8 flag;
|
||||
u8 remaster_version[2];
|
||||
};
|
||||
|
||||
struct ExHeader_CodeSegmentInfo {
|
||||
u32 address;
|
||||
u32 num_max_pages;
|
||||
u32 code_size;
|
||||
};
|
||||
|
||||
struct ExHeader_CodeSetInfo {
|
||||
u8 name[8];
|
||||
ExHeader_SystemInfoFlags flags;
|
||||
ExHeader_CodeSegmentInfo text;
|
||||
u32 stack_size;
|
||||
ExHeader_CodeSegmentInfo ro;
|
||||
u8 reserved[4];
|
||||
ExHeader_CodeSegmentInfo data;
|
||||
u32 bss_size;
|
||||
};
|
||||
|
||||
struct ExHeader_DependencyList {
|
||||
u8 program_id[0x30][8];
|
||||
};
|
||||
|
||||
struct ExHeader_SystemInfo {
|
||||
u64 save_data_size;
|
||||
u64_le jump_id;
|
||||
u8 reserved_2[0x30];
|
||||
};
|
||||
|
||||
struct ExHeader_StorageInfo {
|
||||
union {
|
||||
u64_le ext_save_data_id;
|
||||
// When using extended savedata access
|
||||
// Prefer the ID specified in the most significant bits
|
||||
BitField<40, 20, u64> extdata_id3;
|
||||
BitField<20, 20, u64> extdata_id4;
|
||||
BitField<0, 20, u64> extdata_id5;
|
||||
};
|
||||
u8 system_save_data_id[8];
|
||||
union {
|
||||
u64_le storage_accessible_unique_ids;
|
||||
// When using extended savedata access
|
||||
// Prefer the ID specified in the most significant bits
|
||||
BitField<40, 20, u64> extdata_id0;
|
||||
BitField<20, 20, u64> extdata_id1;
|
||||
BitField<0, 20, u64> extdata_id2;
|
||||
};
|
||||
u8 access_info[7];
|
||||
u8 other_attributes;
|
||||
};
|
||||
|
||||
struct ExHeader_ARM11_SystemLocalCaps {
|
||||
u64_le program_id;
|
||||
u32_le core_version;
|
||||
union {
|
||||
u8 n3ds_cpu_flags;
|
||||
BitField<0, 1, u8> enable_l2_cache;
|
||||
BitField<1, 1, u8> enable_804MHz_cpu;
|
||||
};
|
||||
u8 n3ds_mode;
|
||||
union {
|
||||
u8 flags0;
|
||||
BitField<0, 2, u8> ideal_processor;
|
||||
BitField<2, 2, u8> affinity_mask;
|
||||
BitField<4, 4, u8> system_mode;
|
||||
};
|
||||
u8 priority;
|
||||
u8 resource_limit_descriptor[0x10][2];
|
||||
ExHeader_StorageInfo storage_info;
|
||||
u8 service_access_control[0x20][8];
|
||||
u8 ex_service_access_control[0x2][8];
|
||||
u8 reserved[0xf];
|
||||
u8 resource_limit_category;
|
||||
};
|
||||
|
||||
struct ExHeader_ARM11_KernelCaps {
|
||||
static constexpr std::size_t NUM_DESCRIPTORS = 28;
|
||||
|
||||
u32_le descriptors[NUM_DESCRIPTORS];
|
||||
u8 reserved[0x10];
|
||||
};
|
||||
|
||||
struct ExHeader_ARM9_AccessControl {
|
||||
static constexpr std::size_t NUM_DESCRIPTORS = 15;
|
||||
|
||||
u8 descriptors[NUM_DESCRIPTORS];
|
||||
u8 descversion;
|
||||
};
|
||||
|
||||
struct ExHeader_Header {
|
||||
ExHeader_CodeSetInfo codeset_info;
|
||||
ExHeader_DependencyList dependency_list;
|
||||
ExHeader_SystemInfo system_info;
|
||||
ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps;
|
||||
ExHeader_ARM11_KernelCaps arm11_kernel_caps;
|
||||
ExHeader_ARM9_AccessControl arm9_access_control;
|
||||
struct {
|
||||
u8 signature[0x100];
|
||||
u8 ncch_public_key_modulus[0x100];
|
||||
ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps;
|
||||
ExHeader_ARM11_KernelCaps arm11_kernel_caps;
|
||||
ExHeader_ARM9_AccessControl arm9_access_control;
|
||||
} access_desc;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong");
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* Helper which implements an interface to deal with NCCH containers which can
|
||||
* contain ExeFS archives or RomFS archives for games or other applications.
|
||||
*/
|
||||
class NCCHContainer {
|
||||
public:
|
||||
NCCHContainer(const std::string& filepath, u32 ncch_offset = 0, u32 partition = 0);
|
||||
NCCHContainer() {}
|
||||
|
||||
Loader::ResultStatus OpenFile(const std::string& filepath, u32 ncch_offset = 0,
|
||||
u32 partition = 0);
|
||||
|
||||
/**
|
||||
* Ensure NCCH header is loaded and ready for reading sections
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus LoadHeader();
|
||||
|
||||
/**
|
||||
* Ensure ExeFS and exheader is loaded and ready for reading sections
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus Load();
|
||||
|
||||
/**
|
||||
* Attempt to find overridden sections for the NCCH and mark the container as tainted
|
||||
* if any are found.
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus LoadOverrides();
|
||||
|
||||
/**
|
||||
* Reads an application ExeFS section of an NCCH file (e.g. .code, .logo, etc.)
|
||||
* @param name Name of section to read out of NCCH file
|
||||
* @param buffer Vector to read data into
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus LoadSectionExeFS(const char* name, std::vector<u8>& buffer);
|
||||
|
||||
/**
|
||||
* Reads an application ExeFS section from external files instead of an NCCH file,
|
||||
* (e.g. code.bin, logo.bcma.lz, icon.icn, banner.bnr)
|
||||
* @param name Name of section to read from external files
|
||||
* @param buffer Vector to read data into
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus LoadOverrideExeFSSection(const char* name, std::vector<u8>& buffer);
|
||||
|
||||
/**
|
||||
* Get the RomFS of the NCCH container
|
||||
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
|
||||
* @param romfs_file The file containing the RomFS
|
||||
* @param offset The offset the romfs begins on
|
||||
* @param size The size of the romfs
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file,
|
||||
bool use_layered_fs = true);
|
||||
|
||||
/**
|
||||
* Dump the RomFS of the NCCH container to the user folder.
|
||||
* @param target_path target path to dump to
|
||||
* @return ResultStatus result of function.
|
||||
*/
|
||||
Loader::ResultStatus DumpRomFS(const std::string& target_path);
|
||||
|
||||
/**
|
||||
* Get the override RomFS of the NCCH container
|
||||
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
|
||||
* @param romfs_file The file containing the RomFS
|
||||
* @param offset The offset the romfs begins on
|
||||
* @param size The size of the romfs
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus ReadOverrideRomFS(std::shared_ptr<RomFSReader>& romfs_file);
|
||||
|
||||
/**
|
||||
* Get the Program ID of the NCCH container
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus ReadProgramId(u64_le& program_id);
|
||||
|
||||
/**
|
||||
* Get the Extdata ID of the NCCH container
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
Loader::ResultStatus ReadExtdataId(u64& extdata_id);
|
||||
|
||||
/**
|
||||
* Apply a patch for .code (if it exists).
|
||||
* This should only be called after allocating .bss.
|
||||
* @return ResultStatus success if a patch was applied, ErrorNotUsed if no patch was found
|
||||
*/
|
||||
Loader::ResultStatus ApplyCodePatch(std::vector<u8>& code) const;
|
||||
|
||||
/**
|
||||
* Checks whether the NCCH container contains an ExeFS
|
||||
* @return bool check result
|
||||
*/
|
||||
bool HasExeFS();
|
||||
|
||||
/**
|
||||
* Checks whether the NCCH container contains a RomFS
|
||||
* @return bool check result
|
||||
*/
|
||||
bool HasRomFS();
|
||||
|
||||
/**
|
||||
* Checks whether the NCCH container contains an ExHeader
|
||||
* @return bool check result
|
||||
*/
|
||||
bool HasExHeader();
|
||||
|
||||
NCCH_Header ncch_header;
|
||||
ExeFs_Header exefs_header;
|
||||
ExHeader_Header exheader_header;
|
||||
|
||||
private:
|
||||
bool has_header = false;
|
||||
bool has_exheader = false;
|
||||
bool has_exefs = false;
|
||||
bool has_romfs = false;
|
||||
|
||||
bool is_tainted = false; // Are there parts of this container being overridden?
|
||||
bool is_loaded = false;
|
||||
bool is_compressed = false;
|
||||
|
||||
bool is_encrypted = false;
|
||||
// for decrypting exheader, exefs header and icon/banner section
|
||||
std::array<u8, 16> primary_key{};
|
||||
std::array<u8, 16> secondary_key{}; // for decrypting romfs and .code section
|
||||
std::array<u8, 16> exheader_ctr{};
|
||||
std::array<u8, 16> exefs_ctr{};
|
||||
std::array<u8, 16> romfs_ctr{};
|
||||
|
||||
u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs
|
||||
u32 exefs_offset = 0;
|
||||
u32 partition = 0;
|
||||
|
||||
std::string filepath;
|
||||
FileUtil::IOFile file;
|
||||
FileUtil::IOFile exefs_file;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
283
src/core/file_sys/patch.cpp
Normal file
283
src/core/file_sys/patch.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <boost/crc.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/patch.h"
|
||||
|
||||
namespace FileSys::Patch {
|
||||
|
||||
bool ApplyIpsPatch(const std::vector<u8>& ips, std::vector<u8>& buffer) {
|
||||
std::size_t cursor = 5;
|
||||
std::size_t patch_length = ips.size() - 3;
|
||||
std::string ips_header(ips.begin(), ips.begin() + 5);
|
||||
|
||||
if (ips_header != "PATCH") {
|
||||
LOG_INFO(Service_FS, "Attempted to load invalid IPS");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (cursor < patch_length) {
|
||||
std::string eof_check(ips.begin() + cursor, ips.begin() + cursor + 3);
|
||||
|
||||
if (eof_check == "EOF")
|
||||
return false;
|
||||
|
||||
std::size_t offset = ips[cursor] << 16 | ips[cursor + 1] << 8 | ips[cursor + 2];
|
||||
std::size_t length = ips[cursor + 3] << 8 | ips[cursor + 4];
|
||||
|
||||
// check for an rle record
|
||||
if (length == 0) {
|
||||
length = ips[cursor + 5] << 8 | ips[cursor + 6];
|
||||
|
||||
if (buffer.size() < offset + length)
|
||||
return false;
|
||||
|
||||
for (u32 i = 0; i < length; ++i)
|
||||
buffer[offset + i] = ips[cursor + 7];
|
||||
|
||||
cursor += 8;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (buffer.size() < offset + length)
|
||||
return false;
|
||||
|
||||
std::memcpy(&buffer[offset], &ips[cursor + 5], length);
|
||||
cursor += length + 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace Bps {
|
||||
|
||||
// The BPS format uses variable length encoding for all integers.
|
||||
// Realistically uint32s are more than enough for code patching.
|
||||
using Number = u32;
|
||||
|
||||
constexpr std::size_t MagicSize = 4;
|
||||
constexpr std::size_t FooterSize = 12;
|
||||
|
||||
// The BPS format uses CRC32 checksums.
|
||||
static u32 crc32(const u8* data, std::size_t size) {
|
||||
boost::crc_32_type result;
|
||||
result.process_bytes(data, size);
|
||||
return result.checksum();
|
||||
}
|
||||
|
||||
// Utility class to make keeping track of offsets and bound checks less error prone.
|
||||
template <typename T>
|
||||
class Stream {
|
||||
public:
|
||||
Stream(T* ptr, std::size_t size) : m_ptr{ptr}, m_size{size} {}
|
||||
|
||||
bool Read(void* buffer, std::size_t length) {
|
||||
if (m_offset + length > m_size)
|
||||
return false;
|
||||
std::memcpy(buffer, m_ptr + m_offset, length);
|
||||
m_offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename OtherType>
|
||||
bool CopyFrom(Stream<OtherType>& other, std::size_t length) {
|
||||
if (m_offset + length > m_size)
|
||||
return false;
|
||||
if (!other.Read(m_ptr + m_offset, length))
|
||||
return false;
|
||||
m_offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
std::optional<ValueType> Read() {
|
||||
static_assert(std::is_trivial_v<ValueType>);
|
||||
ValueType val{};
|
||||
if (!Read(&val, sizeof(val)))
|
||||
return std::nullopt;
|
||||
return val;
|
||||
}
|
||||
|
||||
Number ReadNumber() {
|
||||
Number data = 0, shift = 1;
|
||||
std::optional<u8> x;
|
||||
while ((x = Read<u8>())) {
|
||||
data += (*x & 0x7f) * shift;
|
||||
if (*x & 0x80)
|
||||
break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
auto data() const {
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
std::size_t Tell() const {
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
bool Seek(std::size_t offset) {
|
||||
if (offset > m_size)
|
||||
return false;
|
||||
m_offset = offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
T* m_ptr = nullptr;
|
||||
std::size_t m_size = 0;
|
||||
std::size_t m_offset = 0;
|
||||
};
|
||||
|
||||
class PatchApplier {
|
||||
public:
|
||||
PatchApplier(Stream<const u8> source, Stream<u8> target, Stream<const u8> patch)
|
||||
: m_source{source}, m_target{target}, m_patch{patch} {}
|
||||
|
||||
bool Apply() {
|
||||
const auto magic = *m_patch.Read<std::array<char, MagicSize>>();
|
||||
if (std::string_view(magic.data(), magic.size()) != "BPS1") {
|
||||
LOG_ERROR(Service_FS, "Invalid BPS magic");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Bps::Number source_size = m_patch.ReadNumber();
|
||||
const Bps::Number target_size = m_patch.ReadNumber();
|
||||
const Bps::Number metadata_size = m_patch.ReadNumber();
|
||||
if (source_size > m_source.size() || target_size > m_target.size() || metadata_size != 0) {
|
||||
LOG_ERROR(Service_FS, "Invalid sizes");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t command_start_offset = m_patch.Tell();
|
||||
const std::size_t command_end_offset = m_patch.size() - FooterSize;
|
||||
m_patch.Seek(command_end_offset);
|
||||
const u32 source_crc32 = *m_patch.Read<u32>();
|
||||
const u32 target_crc32 = *m_patch.Read<u32>();
|
||||
m_patch.Seek(command_start_offset);
|
||||
|
||||
if (crc32(m_source.data(), source_size) != source_crc32) {
|
||||
LOG_ERROR(Service_FS, "Unexpected source hash");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process all patch commands.
|
||||
std::memset(m_target.data(), 0, m_target.size());
|
||||
while (m_patch.Tell() < command_end_offset) {
|
||||
if (!HandleCommand())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (crc32(m_target.data(), target_size) != target_crc32) {
|
||||
LOG_ERROR(Service_FS, "Unexpected target hash");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool HandleCommand() {
|
||||
const std::size_t offset = m_patch.Tell();
|
||||
const Number data = m_patch.ReadNumber();
|
||||
const Number command = data & 3;
|
||||
const Number length = (data >> 2) + 1;
|
||||
|
||||
const bool ok = [&] {
|
||||
switch (command) {
|
||||
case 0:
|
||||
return SourceRead(length);
|
||||
case 1:
|
||||
return TargetRead(length);
|
||||
case 2:
|
||||
return SourceCopy(length);
|
||||
case 3:
|
||||
return TargetCopy(length);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
if (!ok)
|
||||
LOG_ERROR(Service_FS, "Failed to process command {} at 0x{:x}", command, offset);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SourceRead(Number length) {
|
||||
return m_source.Seek(m_target.Tell()) && m_target.CopyFrom(m_source, length);
|
||||
}
|
||||
|
||||
bool TargetRead(Number length) {
|
||||
return m_target.CopyFrom(m_patch, length);
|
||||
}
|
||||
|
||||
bool SourceCopy(Number length) {
|
||||
const Number data = m_patch.ReadNumber();
|
||||
m_source_relative_offset += (data & 1 ? -1 : +1) * int(data >> 1);
|
||||
if (!m_source.Seek(m_source_relative_offset) || !m_target.CopyFrom(m_source, length))
|
||||
return false;
|
||||
m_source_relative_offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TargetCopy(Number length) {
|
||||
const Number data = m_patch.ReadNumber();
|
||||
m_target_relative_offset += (data & 1 ? -1 : +1) * int(data >> 1);
|
||||
if (m_target.Tell() + length > m_target.size())
|
||||
return false;
|
||||
if (m_target_relative_offset + length > m_target.size())
|
||||
return false;
|
||||
// Byte by byte copy.
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
m_target.data()[m_target.Tell() + i] = m_target.data()[m_target_relative_offset++];
|
||||
m_target.Seek(m_target.Tell() + length);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t m_source_relative_offset = 0;
|
||||
std::size_t m_target_relative_offset = 0;
|
||||
Stream<const u8> m_source;
|
||||
Stream<u8> m_target;
|
||||
Stream<const u8> m_patch;
|
||||
};
|
||||
|
||||
} // namespace Bps
|
||||
|
||||
bool ApplyBpsPatch(const std::vector<u8>& patch, std::vector<u8>& buffer) {
|
||||
Bps::Stream patch_stream{patch.data(), patch.size()};
|
||||
|
||||
// Move the offset past the file format marker (i.e. "BPS1")
|
||||
patch_stream.Seek(Bps::MagicSize);
|
||||
|
||||
const Bps::Number source_size = patch_stream.ReadNumber();
|
||||
const Bps::Number target_size = patch_stream.ReadNumber();
|
||||
|
||||
if (target_size > source_size) {
|
||||
LOG_INFO(Service_FS, "Resizing target to {}", target_size);
|
||||
buffer.resize(target_size);
|
||||
}
|
||||
|
||||
patch_stream.Seek(0);
|
||||
|
||||
const std::vector<u8> source = buffer;
|
||||
Bps::Stream source_stream{source.data(), source.size()};
|
||||
Bps::Stream target_stream{buffer.data(), buffer.size()};
|
||||
Bps::PatchApplier applier{source_stream, target_stream, patch_stream};
|
||||
return applier.Apply();
|
||||
}
|
||||
|
||||
} // namespace FileSys::Patch
|
||||
17
src/core/file_sys/patch.h
Normal file
17
src/core/file_sys/patch.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys::Patch {
|
||||
|
||||
bool ApplyIpsPatch(const std::vector<u8>& patch, std::vector<u8>& buffer);
|
||||
|
||||
bool ApplyBpsPatch(const std::vector<u8>& patch, std::vector<u8>& buffer);
|
||||
|
||||
} // namespace FileSys::Patch
|
||||
98
src/core/file_sys/path_parser.cpp
Normal file
98
src/core/file_sys/path_parser.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include "common/file_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/path_parser.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
PathParser::PathParser(const Path& path) {
|
||||
if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto path_string = path.AsString();
|
||||
if (path_string.size() == 0 || path_string[0] != '/') {
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out invalid characters for the host system.
|
||||
// Although some of these characters are valid on 3DS, they are unlikely to be used by games.
|
||||
if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
|
||||
static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
|
||||
return invalid_chars.find(c) != invalid_chars.end();
|
||||
}) != path_string.end()) {
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
path_sequence = Common::SplitString(path_string, '/');
|
||||
|
||||
auto begin = path_sequence.begin();
|
||||
auto end = path_sequence.end();
|
||||
end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
|
||||
path_sequence = std::vector<std::string>(begin, end);
|
||||
|
||||
// checks if the path is out of bounds.
|
||||
int level = 0;
|
||||
for (auto& node : path_sequence) {
|
||||
if (node == "..") {
|
||||
--level;
|
||||
if (level < 0) {
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
++level;
|
||||
}
|
||||
}
|
||||
|
||||
is_valid = true;
|
||||
is_root = level == 0;
|
||||
}
|
||||
|
||||
PathParser::HostStatus PathParser::GetHostStatus(std::string_view mount_point) const {
|
||||
std::string path{mount_point};
|
||||
if (!FileUtil::IsDirectory(path))
|
||||
return InvalidMountPoint;
|
||||
if (path_sequence.empty()) {
|
||||
return DirectoryFound;
|
||||
}
|
||||
|
||||
for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
|
||||
if (path.back() != '/')
|
||||
path += '/';
|
||||
path += *iter;
|
||||
|
||||
if (!FileUtil::Exists(path))
|
||||
return PathNotFound;
|
||||
if (FileUtil::IsDirectory(path))
|
||||
continue;
|
||||
return FileInPath;
|
||||
}
|
||||
|
||||
path += "/" + path_sequence.back();
|
||||
if (!FileUtil::Exists(path))
|
||||
return NotFound;
|
||||
if (FileUtil::IsDirectory(path))
|
||||
return DirectoryFound;
|
||||
return FileFound;
|
||||
}
|
||||
|
||||
std::string PathParser::BuildHostPath(std::string_view mount_point) const {
|
||||
std::string path{mount_point};
|
||||
for (auto& node : path_sequence) {
|
||||
if (path.back() != '/')
|
||||
path += '/';
|
||||
path += node;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
61
src/core/file_sys/path_parser.h
Normal file
61
src/core/file_sys/path_parser.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* A helper class parsing and verifying a string-type Path.
|
||||
* Every archives with a sub file system should use this class to parse the path argument and check
|
||||
* the status of the file / directory in question on the host file system.
|
||||
*/
|
||||
class PathParser {
|
||||
public:
|
||||
explicit PathParser(const Path& path);
|
||||
|
||||
/**
|
||||
* Checks if the Path is valid.
|
||||
* This function should be called once a PathParser is constructed.
|
||||
* A Path is valid if:
|
||||
* - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
|
||||
* - it starts with "/" (this seems a hard requirement in real 3DS),
|
||||
* - it doesn't contain invalid characters, and
|
||||
* - it doesn't go out of the root directory using "..".
|
||||
*/
|
||||
bool IsValid() const {
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
/// Checks if the Path represents the root directory.
|
||||
bool IsRootDirectory() const {
|
||||
return is_root;
|
||||
}
|
||||
|
||||
enum HostStatus {
|
||||
InvalidMountPoint,
|
||||
PathNotFound, // "/a/b/c" when "a" doesn't exist
|
||||
FileInPath, // "/a/b/c" when "a" is a file
|
||||
FileFound, // "/a/b/c" when "c" is a file
|
||||
DirectoryFound, // "/a/b/c" when "c" is a directory
|
||||
NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist
|
||||
};
|
||||
|
||||
/// Checks the status of the specified file / directory by the Path on the host file system.
|
||||
HostStatus GetHostStatus(std::string_view mount_point) const;
|
||||
|
||||
/// Builds a full path on the host file system.
|
||||
std::string BuildHostPath(std::string_view mount_point) const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> path_sequence;
|
||||
bool is_valid{};
|
||||
bool is_root{};
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
371
src/core/file_sys/plugin_3gx.cpp
Normal file
371
src/core/file_sys/plugin_3gx.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/file_sys/plugin_3gx.h"
|
||||
#include "core/file_sys/plugin_3gx_bootloader.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
static std::string ReadTextInfo(FileUtil::IOFile& file, std::size_t offset, std::size_t max_size) {
|
||||
if (offset == 0 || max_size == 0 ||
|
||||
max_size > 0x400) { // Limit read string size to 0x400 bytes, just in case
|
||||
return "";
|
||||
}
|
||||
std::vector<char> char_data(max_size);
|
||||
|
||||
const u64 prev_offset = file.Tell();
|
||||
if (!file.Seek(offset, SEEK_SET)) {
|
||||
return "";
|
||||
}
|
||||
if (file.ReadBytes(char_data.data(), max_size) != max_size) {
|
||||
file.Seek(prev_offset, SEEK_SET);
|
||||
return "";
|
||||
}
|
||||
char_data[max_size - 1] = '\0';
|
||||
return std::string(char_data.data());
|
||||
}
|
||||
|
||||
static bool ReadSection(std::vector<u8>& data_out, FileUtil::IOFile& file, std::size_t offset,
|
||||
std::size_t size) {
|
||||
if (size > 0x5000000) { // Limit read section size to 5MiB, just in case
|
||||
return false;
|
||||
}
|
||||
data_out.resize(size);
|
||||
|
||||
const u64 prev_offset = file.Tell();
|
||||
|
||||
if (!file.Seek(offset, SEEK_SET)) {
|
||||
return false;
|
||||
}
|
||||
if (file.ReadBytes(data_out.data(), size) != size) {
|
||||
file.Seek(prev_offset, SEEK_SET);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Loader::ResultStatus FileSys::Plugin3GXLoader::Load(
|
||||
Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process,
|
||||
Kernel::KernelSystem& kernel, Service::PLGLDR::PLG_LDR& plg_ldr) {
|
||||
FileUtil::IOFile file(plg_context.plugin_path, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not found: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load CIA Header
|
||||
std::vector<u8> header_data(sizeof(_3gx_Header));
|
||||
if (file.ReadBytes(header_data.data(), sizeof(_3gx_Header)) != sizeof(_3gx_Header)) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
std::memcpy(&header, header_data.data(), sizeof(_3gx_Header));
|
||||
|
||||
// Check magic value
|
||||
if (std::memcmp(&header.magic, _3GX_magic, 8) != 0) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Outdated or invalid 3GX plugin: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
if (header.infos.flags.compatibility == static_cast<u32>(_3gx_Infos::Compatibility::CONSOLE)) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not compatible with Citra: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load strings
|
||||
author = ReadTextInfo(file, header.infos.author_msg_offset, header.infos.author_len);
|
||||
title = ReadTextInfo(file, header.infos.title_msg_offset, header.infos.title_len);
|
||||
description =
|
||||
ReadTextInfo(file, header.infos.description_msg_offset, header.infos.description_len);
|
||||
summary = ReadTextInfo(file, header.infos.summary_msg_offset, header.infos.summary_len);
|
||||
|
||||
LOG_INFO(Service_PLGLDR, "Trying to load plugin - Title: {} - Author: {}", title, author);
|
||||
|
||||
// Load compatible TIDs
|
||||
{
|
||||
std::vector<u8> raw_TID_data;
|
||||
if (!ReadSection(raw_TID_data, file, header.targets.title_offsets,
|
||||
header.targets.count * sizeof(u32))) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
compatible_TID.reserve(header.targets.count); // compatible_TID should be empty right now
|
||||
for (u32 i = 0; i < u32(header.targets.count); i++) {
|
||||
compatible_TID.push_back(
|
||||
u32_le(*reinterpret_cast<u32*>(raw_TID_data.data() + i * sizeof(u32))));
|
||||
}
|
||||
}
|
||||
|
||||
if (!compatible_TID.empty() &&
|
||||
std::find(compatible_TID.begin(), compatible_TID.end(),
|
||||
static_cast<u32>(process.codeset->program_id)) == compatible_TID.end()) {
|
||||
LOG_ERROR(Service_PLGLDR,
|
||||
"Failed to load 3GX plugin. Not compatible with loaded process: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load exe load func and args
|
||||
if (header.infos.flags.embedded_exe_func.Value() &&
|
||||
header.executable.exe_load_func_offset != 0) {
|
||||
exe_load_func.clear();
|
||||
std::vector<u8> out;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
ReadSection(out, file, header.executable.exe_load_func_offset + i * sizeof(u32),
|
||||
sizeof(u32));
|
||||
u32 instruction = *reinterpret_cast<u32_le*>(out.data());
|
||||
if (instruction == 0xE320F000) {
|
||||
break;
|
||||
}
|
||||
exe_load_func.push_back(instruction);
|
||||
}
|
||||
std::memcpy(exe_load_args, header.infos.builtin_load_exe_args,
|
||||
sizeof(_3gx_Infos::builtin_load_exe_args));
|
||||
}
|
||||
|
||||
// Load code sections
|
||||
if (!ReadSection(text_section, file, header.executable.code_offset,
|
||||
header.executable.code_size) ||
|
||||
!ReadSection(rodata_section, file, header.executable.rodata_offset,
|
||||
header.executable.rodata_size) ||
|
||||
!ReadSection(data_section, file, header.executable.data_offset,
|
||||
header.executable.data_size)) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. File corrupted: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
return Map(plg_context, process, kernel, plg_ldr);
|
||||
}
|
||||
|
||||
Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context, Kernel::Process& process,
|
||||
Kernel::KernelSystem& kernel, Service::PLGLDR::PLG_LDR& plg_ldr) {
|
||||
|
||||
// Verify exe load checksum function is available
|
||||
if (exe_load_func.empty() && plg_context.load_exe_func.empty()) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Missing checksum function: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
const std::array<u32, 4> mem_region_sizes = {
|
||||
5 * 1024 * 1024, // 5 MiB
|
||||
2 * 1024 * 1024, // 2 MiB
|
||||
3 * 1024 * 1024, // 3 MiB
|
||||
4 * 1024 * 1024 // 4 MiB
|
||||
};
|
||||
|
||||
const bool is_mem_private = header.infos.flags.use_private_memory != 0;
|
||||
|
||||
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
|
||||
// Calculate the sizes of the different memory regions
|
||||
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
|
||||
const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() +
|
||||
data_section.size() + header.executable.bss_size + 0x1000) &
|
||||
~0xFFFu;
|
||||
|
||||
// Allocate the framebuffer block so that is in the highest FCRAM position possible
|
||||
auto offset_fb =
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size);
|
||||
if (!offset_fb) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb);
|
||||
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb);
|
||||
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
|
||||
|
||||
auto vma_heap_fb = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap_fb.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(block_size - _3GX_fb_size);
|
||||
if (!offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
auto backing_memory = kernel.memory.GetFCRAMRef(*offset);
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0);
|
||||
|
||||
// Then we map part of the memory, which contains the executable
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private
|
||||
: Kernel::MemoryState::Shared);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Write text section
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
|
||||
text_section.data(), header.executable.code_size);
|
||||
// Write rodata section
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
|
||||
rodata_section.data(), header.executable.rodata_size);
|
||||
// Write data section
|
||||
kernel.memory.WriteBlock(process,
|
||||
_3GX_exe_load_addr + sizeof(PluginHeader) +
|
||||
header.executable.code_size + header.executable.rodata_size,
|
||||
data_section.data(), header.executable.data_size);
|
||||
// Prepare plugin header and write it
|
||||
PluginHeader plugin_header = {0};
|
||||
plugin_header.version = header.version;
|
||||
plugin_header.exe_size = exe_size;
|
||||
plugin_header.heap_VA = _3GX_heap_load_addr;
|
||||
plugin_header.heap_size = block_size - exe_size;
|
||||
plg_context.plg_event = _3GX_exe_load_addr - 0x4;
|
||||
plg_context.plg_reply = _3GX_exe_load_addr - 0x8;
|
||||
plugin_header.plgldr_event = plg_context.plg_event;
|
||||
plugin_header.plgldr_reply = plg_context.plg_reply;
|
||||
plugin_header.is_default_plugin = plg_context.is_default_path;
|
||||
if (plg_context.use_user_load_parameters) {
|
||||
std::memcpy(plugin_header.config, plg_context.user_load_parameters.config,
|
||||
sizeof(PluginHeader::config));
|
||||
}
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader));
|
||||
|
||||
// Map plugin heap
|
||||
auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size);
|
||||
|
||||
// Map the rest of the memory at the heap location
|
||||
auto vma_heap = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
|
||||
block_size - exe_size - _3GX_fb_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(bootloader_memory_size);
|
||||
if (!bootloader_offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->Free(*offset, block_size - _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
const bool use_internal = plg_context.load_exe_func.empty();
|
||||
MapBootloader(
|
||||
process, kernel, *bootloader_offset,
|
||||
(use_internal) ? exe_load_func : plg_context.load_exe_func,
|
||||
(use_internal) ? exe_load_args : plg_context.load_exe_args,
|
||||
header.executable.code_size + header.executable.rodata_size + header.executable.data_size,
|
||||
header.infos.exe_load_checksum,
|
||||
plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0);
|
||||
|
||||
plg_context.plugin_loaded = true;
|
||||
plg_context.use_user_load_parameters = false;
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
u32 memory_offset, std::span<const u32> exe_load_func,
|
||||
const u32_le* exe_load_args, u32 checksum_size,
|
||||
u32 exe_checksum, bool no_flash) {
|
||||
|
||||
u32_le game_instructions[2];
|
||||
kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, game_instructions,
|
||||
sizeof(u32) * 2);
|
||||
|
||||
std::array<u32_le, g_plugin_loader_bootloader.size() / sizeof(u32)> bootloader;
|
||||
std::memcpy(bootloader.data(), g_plugin_loader_bootloader.data(),
|
||||
g_plugin_loader_bootloader.size());
|
||||
|
||||
for (auto it = bootloader.begin(); it < bootloader.end(); it++) {
|
||||
switch (static_cast<u32>(*it)) {
|
||||
case 0xDEAD0000: {
|
||||
*it = game_instructions[0];
|
||||
} break;
|
||||
case 0xDEAD0001: {
|
||||
*it = game_instructions[1];
|
||||
} break;
|
||||
case 0xDEAD0002: {
|
||||
*it = process.codeset->CodeSegment().addr;
|
||||
} break;
|
||||
case 0xDEAD0003: {
|
||||
for (u32 i = 0;
|
||||
i <
|
||||
sizeof(Service::PLGLDR::PLG_LDR::PluginLoaderContext::load_exe_args) / sizeof(u32);
|
||||
i++) {
|
||||
bootloader[i + (it - bootloader.begin())] = exe_load_args[i];
|
||||
}
|
||||
} break;
|
||||
case 0xDEAD0004: {
|
||||
*it = _3GX_exe_load_addr + sizeof(PluginHeader);
|
||||
} break;
|
||||
case 0xDEAD0005: {
|
||||
*it = _3GX_exe_load_addr + sizeof(PluginHeader) + checksum_size;
|
||||
} break;
|
||||
case 0xDEAD0006: {
|
||||
*it = exe_checksum;
|
||||
} break;
|
||||
case 0xDEAD0007: {
|
||||
*it = _3GX_exe_load_addr - 0xC;
|
||||
} break;
|
||||
case 0xDEAD0008: {
|
||||
*it = _3GX_exe_load_addr + sizeof(PluginHeader);
|
||||
} break;
|
||||
case 0xDEAD0009: {
|
||||
*it = no_flash ? 1 : 0;
|
||||
} break;
|
||||
case 0xDEAD000A: {
|
||||
for (u32 i = 0; i < exe_load_func.size(); i++) {
|
||||
bootloader[i + (it - bootloader.begin())] = exe_load_func[i];
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Map bootloader to the offset provided
|
||||
auto backing_memory = kernel.memory.GetFCRAMRef(memory_offset);
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0);
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size,
|
||||
backing_memory, bootloader_memory_size,
|
||||
Kernel::MemoryState::Continuous);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Write bootloader
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr - bootloader_memory_size, bootloader.data(),
|
||||
std::min<std::size_t>(bootloader.size() * sizeof(u32), bootloader_memory_size));
|
||||
|
||||
game_instructions[0] = 0xE51FF004; // ldr pc, [pc, #-4]
|
||||
game_instructions[1] = _3GX_exe_load_addr - bootloader_memory_size;
|
||||
kernel.memory.WriteBlock(process, process.codeset->CodeSegment().addr, game_instructions,
|
||||
sizeof(u32) * 2);
|
||||
}
|
||||
155
src/core/file_sys/plugin_3gx.h
Normal file
155
src/core/file_sys/plugin_3gx.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
}
|
||||
|
||||
namespace FileUtil {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class FileBackend;
|
||||
|
||||
class Plugin3GXLoader {
|
||||
public:
|
||||
Loader::ResultStatus Load(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context,
|
||||
Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
Service::PLGLDR::PLG_LDR& plg_ldr);
|
||||
|
||||
struct PluginHeader {
|
||||
u32_le magic;
|
||||
u32_le version;
|
||||
u32_le heap_VA;
|
||||
u32_le heap_size;
|
||||
u32_le exe_size; // Include sizeof(PluginHeader) + .text + .rodata + .data + .bss (0x1000
|
||||
// aligned too)
|
||||
u32_le is_default_plugin;
|
||||
u32_le plgldr_event; ///< Used for synchronization, unused in citra
|
||||
u32_le plgldr_reply; ///< Used for synchronization, unused in citra
|
||||
u32_le reserved[24];
|
||||
u32_le config[32];
|
||||
};
|
||||
|
||||
static_assert(sizeof(PluginHeader) == 0x100, "Invalid plugin header size");
|
||||
|
||||
static constexpr const char* _3GX_magic = "3GX$0002";
|
||||
static constexpr u32 _3GX_exe_load_addr = 0x07000000;
|
||||
static constexpr u32 _3GX_heap_load_addr = 0x06000000;
|
||||
static constexpr u32 _3GX_fb_size = 0xA9000;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus Map(Service::PLGLDR::PLG_LDR::PluginLoaderContext& plg_context,
|
||||
Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
Service::PLGLDR::PLG_LDR& plg_ldr);
|
||||
|
||||
static constexpr std::size_t bootloader_memory_size = 0x1000;
|
||||
static void MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
u32 memory_offset, std::span<const u32> exe_load_func,
|
||||
const u32_le* exe_load_args, u32 checksum_size, u32 exe_checksum,
|
||||
bool no_flash);
|
||||
|
||||
struct _3gx_Infos {
|
||||
enum class Compatibility { CONSOLE = 0, CITRA = 1, CONSOLE_CITRA = 2 };
|
||||
u32_le author_len;
|
||||
u32_le author_msg_offset;
|
||||
u32_le title_len;
|
||||
u32_le title_msg_offset;
|
||||
u32_le summary_len;
|
||||
u32_le summary_msg_offset;
|
||||
u32_le description_len;
|
||||
u32_le description_msg_offset;
|
||||
union {
|
||||
u32_le raw;
|
||||
BitField<0, 1, u32_le> embedded_exe_func;
|
||||
BitField<1, 1, u32_le> embedded_swap_func;
|
||||
BitField<2, 2, u32_le> memory_region_size;
|
||||
BitField<4, 2, u32_le> compatibility;
|
||||
BitField<6, 1, u32_le> events_self_managed;
|
||||
BitField<7, 1, u32_le> swap_not_needed;
|
||||
BitField<8, 1, u32_le> use_private_memory;
|
||||
} flags;
|
||||
u32_le exe_load_checksum;
|
||||
u32_le builtin_load_exe_args[4];
|
||||
u32_le builtin_swap_load_args[4];
|
||||
};
|
||||
|
||||
struct _3gx_Targets {
|
||||
u32_le count;
|
||||
u32_le title_offsets;
|
||||
};
|
||||
|
||||
struct _3gx_Symtable {
|
||||
u32_le nb_symbols;
|
||||
u32_le symbols_offset;
|
||||
u32_le name_table_offset;
|
||||
};
|
||||
|
||||
struct _3gx_Executable {
|
||||
u32_le code_offset;
|
||||
u32_le rodata_offset;
|
||||
u32_le data_offset;
|
||||
u32_le code_size;
|
||||
u32_le rodata_size;
|
||||
u32_le data_size;
|
||||
u32_le bss_size;
|
||||
u32_le exe_load_func_offset; // NOP terminated
|
||||
u32_le swap_save_func_offset; // NOP terminated
|
||||
u32_le swap_load_func_offset; // NOP terminated
|
||||
};
|
||||
|
||||
struct _3gx_Header {
|
||||
u64_le magic;
|
||||
u32_le version;
|
||||
u32_le reserved;
|
||||
_3gx_Infos infos;
|
||||
_3gx_Executable executable;
|
||||
_3gx_Targets targets;
|
||||
_3gx_Symtable symtable;
|
||||
};
|
||||
|
||||
_3gx_Header header;
|
||||
|
||||
std::string author;
|
||||
std::string title;
|
||||
std::string summary;
|
||||
std::string description;
|
||||
|
||||
std::vector<u32> compatible_TID;
|
||||
std::vector<u8> text_section;
|
||||
std::vector<u8> data_section;
|
||||
std::vector<u8> rodata_section;
|
||||
|
||||
std::vector<u32> exe_load_func;
|
||||
u32_le exe_load_args[4];
|
||||
};
|
||||
} // namespace FileSys
|
||||
186
src/core/file_sys/plugin_3gx_bootloader.h
Normal file
186
src/core/file_sys/plugin_3gx_bootloader.h
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Plugin bootloader payload
|
||||
// Compiled with https://shell-storm.org/online/Online-Assembler-and-Disassembler/
|
||||
/*
|
||||
; Backup registers
|
||||
|
||||
stmfd sp!, {r0-r12}
|
||||
mrs r0, cpsr
|
||||
stmfd sp!, {r0}
|
||||
|
||||
; Check plugin validity and exit if invalid (also set a flag)
|
||||
|
||||
adr r0, g_plgstartendptr
|
||||
ldr r1, [r0, #4]
|
||||
ldr r0, [r0]
|
||||
adr r2, g_plgloadexeargs
|
||||
mov lr, pc
|
||||
adr pc, g_loadexefunc
|
||||
adr r1, g_loadexechecksum
|
||||
ldr r1, [r1]
|
||||
cmp r0, r1
|
||||
adr r0, g_plgldrlaunchstatus
|
||||
ldr r0, [r0]
|
||||
moveq r1, #1
|
||||
movne r1, #0
|
||||
str r1, [r0]
|
||||
svcne 0x3
|
||||
|
||||
; Flash top screen light blue
|
||||
|
||||
adr r0, g_plgnoflash
|
||||
ldrb r0, [r0]
|
||||
cmp r0, #1
|
||||
beq skipflash
|
||||
ldr r4, =0x90202204
|
||||
ldr r5, =0x01FF9933
|
||||
mov r6, #64
|
||||
flashloop:
|
||||
str r5, [r4]
|
||||
ldr r0, =0xFF4B40
|
||||
mov r1, #0
|
||||
svc 0xA
|
||||
subs r6, r6, #1
|
||||
bne flashloop
|
||||
str r6, [r4]
|
||||
skipflash:
|
||||
|
||||
; Set all memory regions to RWX
|
||||
|
||||
ldr r0, =0xFFFF8001
|
||||
mov r1, #1
|
||||
svc 0xB3
|
||||
|
||||
; Restore instructions at entrypoint
|
||||
|
||||
adr r0, g_savedGameInstr
|
||||
adr r1, g_gameentrypoint
|
||||
ldr r1, [r1]
|
||||
ldr r2, [r0]
|
||||
str r2, [r1]
|
||||
ldr r2, [r0, #4]
|
||||
str r2, [r1, #4]
|
||||
svc 0x94
|
||||
|
||||
; Launch the plugin
|
||||
|
||||
adr r0, g_savedGameInstr
|
||||
push {r0}
|
||||
adr r5, g_plgentrypoint
|
||||
ldr r5, [r5]
|
||||
blx r5
|
||||
add sp, sp, #4
|
||||
|
||||
; Restore registers and return to the game
|
||||
|
||||
ldmfd sp!, {r0}
|
||||
msr cpsr, r0
|
||||
ldmfd sp!, {r0-r12}
|
||||
adr lr, g_gameentrypoint
|
||||
ldr pc, [lr]
|
||||
|
||||
.pool
|
||||
|
||||
g_savedGameInstr:
|
||||
.word 0xDEAD0000, 0xDEAD0001
|
||||
g_gameentrypoint:
|
||||
.word 0xDEAD0002
|
||||
g_plgloadexeargs:
|
||||
.word 0xDEAD0003, 0, 0, 0
|
||||
g_plgstartendptr:
|
||||
.word 0xDEAD0004, 0xDEAD0005
|
||||
g_loadexechecksum:
|
||||
.word 0xDEAD0006
|
||||
g_plgldrlaunchstatus:
|
||||
.word 0xDEAD0007
|
||||
g_plgentrypoint:
|
||||
.word 0xDEAD0008
|
||||
g_plgnoflash:
|
||||
.word 0xDEAD0009
|
||||
g_loadexefunc:
|
||||
.word 0xDEAD000A
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
bx lr
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
|
||||
constexpr std::array<u8, 412> g_plugin_loader_bootloader = {
|
||||
0xff, 0x1f, 0x2d, 0xe9, 0x00, 0x00, 0x0f, 0xe1, 0x01, 0x00, 0x2d, 0xe9, 0xf0, 0x00, 0x8f, 0xe2,
|
||||
0x04, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x90, 0xe5, 0xd4, 0x20, 0x8f, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0xf4, 0xf0, 0x8f, 0xe2, 0xe0, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x01, 0x00, 0x50, 0xe1,
|
||||
0xd8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x01, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13,
|
||||
0x00, 0x10, 0x80, 0xe5, 0x03, 0x00, 0x00, 0x1f, 0xc8, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0xd0, 0xe5,
|
||||
0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x78, 0x40, 0x9f, 0xe5, 0x78, 0x50, 0x9f, 0xe5,
|
||||
0x40, 0x60, 0xa0, 0xe3, 0x00, 0x50, 0x84, 0xe5, 0x70, 0x00, 0x9f, 0xe5, 0x00, 0x10, 0xa0, 0xe3,
|
||||
0x0a, 0x00, 0x00, 0xef, 0x01, 0x60, 0x56, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x00, 0x60, 0x84, 0xe5,
|
||||
0x5c, 0x00, 0x9f, 0xe5, 0x01, 0x10, 0xa0, 0xe3, 0xb3, 0x00, 0x00, 0xef, 0x54, 0x00, 0x8f, 0xe2,
|
||||
0x58, 0x10, 0x8f, 0xe2, 0x00, 0x10, 0x91, 0xe5, 0x00, 0x20, 0x90, 0xe5, 0x00, 0x20, 0x81, 0xe5,
|
||||
0x04, 0x20, 0x90, 0xe5, 0x04, 0x20, 0x81, 0xe5, 0x94, 0x00, 0x00, 0xef, 0x34, 0x00, 0x8f, 0xe2,
|
||||
0x04, 0x00, 0x2d, 0xe5, 0x58, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x35, 0xff, 0x2f, 0xe1,
|
||||
0x04, 0xd0, 0x8d, 0xe2, 0x01, 0x00, 0xbd, 0xe8, 0x00, 0xf0, 0x29, 0xe1, 0xff, 0x1f, 0xbd, 0xe8,
|
||||
0x18, 0xe0, 0x8f, 0xe2, 0x00, 0xf0, 0x9e, 0xe5, 0x04, 0x22, 0x20, 0x90, 0x33, 0x99, 0xff, 0x01,
|
||||
0x40, 0x4b, 0xff, 0x00, 0x01, 0x80, 0xff, 0xff, 0x00, 0x00, 0xad, 0xde, 0x01, 0x00, 0xad, 0xde,
|
||||
0x02, 0x00, 0xad, 0xde, 0x03, 0x00, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xad, 0xde, 0x05, 0x00, 0xad, 0xde, 0x06, 0x00, 0xad, 0xde,
|
||||
0x07, 0x00, 0xad, 0xde, 0x08, 0x00, 0xad, 0xde, 0x09, 0x00, 0xad, 0xde, 0x0a, 0x00, 0xad, 0xde,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3,
|
||||
0x00, 0xf0, 0x20, 0xe3, 0x00, 0xf0, 0x20, 0xe3, 0x1e, 0xff, 0x2f, 0xe1};
|
||||
214
src/core/file_sys/romfs_reader.cpp
Normal file
214
src/core/file_sys/romfs_reader.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include "common/archives.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/archive_artic.h"
|
||||
#include "core/file_sys/archive_backend.h"
|
||||
#include "core/file_sys/romfs_reader.h"
|
||||
#include "core/hle/service/fs/fs_user.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
||||
length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||
if (length == 0)
|
||||
return 0; // Crypto++ does not like zero size buffer
|
||||
|
||||
const auto segments = BreakupRead(offset, length);
|
||||
std::size_t read_progress = 0;
|
||||
|
||||
// Skip cache if the read is too big
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
length = file.ReadAtBytes(buffer, length, file_offset + offset);
|
||||
if (is_encrypted) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + offset);
|
||||
d.ProcessData(buffer, buffer, length);
|
||||
}
|
||||
LOG_TRACE(Service_FS, "RomFS Cache SKIP: offset={}, length={}", offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
// std::unique_lock<std::shared_mutex> read_guard(cache_mutex);
|
||||
for (const auto& seg : segments) {
|
||||
std::size_t read_size = cache_line_size;
|
||||
std::size_t page = OffsetToPage(seg.first);
|
||||
// Check if segment is in cache
|
||||
auto cache_entry = cache.request(page);
|
||||
if (!cache_entry.first) {
|
||||
// If not found, read from disk and cache the data
|
||||
read_size = file.ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page);
|
||||
if (is_encrypted && read_size) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + page);
|
||||
d.ProcessData(cache_entry.second.data(), cache_entry.second.data(), read_size);
|
||||
}
|
||||
LOG_TRACE(Service_FS, "RomFS Cache MISS: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
} else {
|
||||
LOG_TRACE(Service_FS, "RomFS Cache HIT: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
}
|
||||
std::size_t copy_amount =
|
||||
(read_size > (seg.first - page))
|
||||
? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page)
|
||||
: 0;
|
||||
std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page),
|
||||
copy_amount);
|
||||
read_progress += copy_amount;
|
||||
}
|
||||
return read_progress;
|
||||
}
|
||||
|
||||
bool DirectRomFSReader::AllowsCachedReads() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) {
|
||||
auto segments = BreakupRead(file_offset, length);
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
return false;
|
||||
} else {
|
||||
// TODO(PabloMK7): Since the LRU cache is not thread safe, a lock must be used.
|
||||
// However, this completely breaks the point of using a cache, because
|
||||
// smaller reads may be blocked by bigger reads. For now, always return
|
||||
// data being in cache to prevent the need of a lock, and only read data
|
||||
// asynchronously if it is too big to use the cache.
|
||||
/*
|
||||
std::shared_lock<std::shared_mutex> read_guard(cache_mutex);
|
||||
for (auto it = segments.begin(); it != segments.end(); it++) {
|
||||
if (!cache.contains(OffsetToPage(it->first)))
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> DirectRomFSReader::BreakupRead(
|
||||
std::size_t offset, std::size_t length) {
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> ret;
|
||||
|
||||
// Reads bigger than the cache line size will probably never hit again
|
||||
if (length > cache_line_size) {
|
||||
ret.push_back(std::make_pair(offset, length));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::size_t curr_offset = offset;
|
||||
while (length) {
|
||||
std::size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
||||
std::size_t curr_page_len = std::min(length, next_page - curr_offset);
|
||||
ret.push_back(std::make_pair(curr_offset, curr_page_len));
|
||||
curr_offset = next_page;
|
||||
length -= curr_page_len;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ArticRomFSReader::ArticRomFSReader(std::shared_ptr<Network::ArticBase::Client>& cli,
|
||||
bool is_update_romfs)
|
||||
: client(cli), cache(cli) {
|
||||
auto req = client->NewRequest("FSUSER_OpenFileDirectly");
|
||||
|
||||
FileSys::Path archive(FileSys::LowPathType::Empty, {});
|
||||
std::vector<u8> fileVec(0xC);
|
||||
fileVec[0] = static_cast<u8>(is_update_romfs ? 5 : 0);
|
||||
FileSys::Path file(FileSys::LowPathType::Binary, fileVec);
|
||||
|
||||
req.AddParameterS32(static_cast<s32>(Service::FS::ArchiveIdCode::SelfNCCH));
|
||||
|
||||
auto archive_buf = ArticArchive::BuildFSPath(archive);
|
||||
req.AddParameterBuffer(archive_buf.data(), archive_buf.size());
|
||||
auto file_buf = ArticArchive::BuildFSPath(file);
|
||||
req.AddParameterBuffer(file_buf.data(), file_buf.size());
|
||||
|
||||
req.AddParameterS32(1);
|
||||
req.AddParameterS32(0);
|
||||
|
||||
auto resp = client->Send(req);
|
||||
|
||||
if (!resp.has_value() || !resp->Succeeded()) {
|
||||
load_status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
if (resp->GetMethodResult() != 0) {
|
||||
load_status = Loader::ResultStatus::ErrorNotUsed;
|
||||
return;
|
||||
}
|
||||
|
||||
auto handle_buf = resp->GetResponseBuffer(0);
|
||||
if (!handle_buf.has_value() || handle_buf->second != sizeof(s32)) {
|
||||
load_status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
|
||||
romfs_handle = *reinterpret_cast<s32*>(handle_buf->first);
|
||||
|
||||
req = client->NewRequest("FSFILE_GetSize");
|
||||
|
||||
req.AddParameterS32(romfs_handle);
|
||||
|
||||
resp = client->Send(req);
|
||||
|
||||
if (!resp.has_value() || !resp->Succeeded()) {
|
||||
load_status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
if (resp->GetMethodResult() != 0) {
|
||||
load_status = Loader::ResultStatus::ErrorNotUsed;
|
||||
return;
|
||||
}
|
||||
|
||||
auto size_buf = resp->GetResponseBuffer(0);
|
||||
if (!size_buf.has_value() || size_buf->second != sizeof(u64)) {
|
||||
load_status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
|
||||
data_size = static_cast<size_t>(*reinterpret_cast<u64*>(size_buf->first));
|
||||
load_status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
ArticRomFSReader::~ArticRomFSReader() {
|
||||
if (romfs_handle != -1) {
|
||||
auto req = client->NewRequest("FSFILE_Close");
|
||||
req.AddParameterS32(romfs_handle);
|
||||
client->Send(req);
|
||||
romfs_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t ArticRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
||||
length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||
auto res = cache.Read(romfs_handle, offset, length, buffer);
|
||||
if (res.Failed())
|
||||
return 0;
|
||||
return res.Unwrap();
|
||||
}
|
||||
|
||||
bool ArticRomFSReader::AllowsCachedReads() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArticRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) {
|
||||
return cache.CacheReady(file_offset, length);
|
||||
}
|
||||
|
||||
void ArticRomFSReader::CloseFile() {
|
||||
if (romfs_handle != -1) {
|
||||
auto req = client->NewRequest("FSFILE_Close");
|
||||
req.AddParameterS32(romfs_handle);
|
||||
client->Send(req);
|
||||
romfs_handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
155
src/core/file_sys/romfs_reader.h
Normal file
155
src/core/file_sys/romfs_reader.h
Normal file
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <shared_mutex>
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/static_lru_cache.h"
|
||||
#include "core/file_sys/artic_cache.h"
|
||||
#include "network/artic_base/artic_base_client.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* Interface for reading RomFS data.
|
||||
*/
|
||||
class RomFSReader {
|
||||
public:
|
||||
virtual ~RomFSReader() = default;
|
||||
|
||||
virtual std::size_t GetSize() const = 0;
|
||||
virtual std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) = 0;
|
||||
virtual bool AllowsCachedReads() const = 0;
|
||||
virtual bool CacheReady(std::size_t file_offset, std::size_t length) = 0;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int file_version) {}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/**
|
||||
* A RomFS reader that directly reads the RomFS file.
|
||||
*/
|
||||
class DirectRomFSReader : public RomFSReader {
|
||||
public:
|
||||
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
|
||||
: is_encrypted(false), file(std::move(file)), file_offset(file_offset),
|
||||
data_size(data_size) {}
|
||||
|
||||
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size,
|
||||
const std::array<u8, 16>& key, const std::array<u8, 16>& ctr,
|
||||
std::size_t crypto_offset)
|
||||
: is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset),
|
||||
crypto_offset(crypto_offset), data_size(data_size) {}
|
||||
|
||||
~DirectRomFSReader() override = default;
|
||||
|
||||
std::size_t GetSize() const override {
|
||||
return data_size;
|
||||
}
|
||||
|
||||
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
||||
|
||||
bool AllowsCachedReads() const override;
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
||||
|
||||
private:
|
||||
bool is_encrypted;
|
||||
FileUtil::IOFile file;
|
||||
std::array<u8, 16> key;
|
||||
std::array<u8, 16> ctr;
|
||||
u64 file_offset;
|
||||
u64 crypto_offset;
|
||||
u64 data_size;
|
||||
|
||||
// Total cache size: 128KB
|
||||
static constexpr std::size_t cache_line_size = (1 << 13); // About 8KB
|
||||
static constexpr std::size_t cache_line_count = 16;
|
||||
|
||||
Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache;
|
||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
// std::shared_mutex cache_mutex;
|
||||
|
||||
DirectRomFSReader() = default;
|
||||
|
||||
std::size_t OffsetToPage(std::size_t offset) {
|
||||
return Common::AlignDown<std::size_t>(offset, cache_line_size);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::size_t, std::size_t>> BreakupRead(std::size_t offset,
|
||||
std::size_t length);
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||
ar& is_encrypted;
|
||||
ar& file;
|
||||
ar& key;
|
||||
ar& ctr;
|
||||
ar& file_offset;
|
||||
ar& crypto_offset;
|
||||
ar& data_size;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
/**
|
||||
* A RomFS reader that reads from an artic base server.
|
||||
*/
|
||||
class ArticRomFSReader : public RomFSReader {
|
||||
public:
|
||||
ArticRomFSReader() = default;
|
||||
ArticRomFSReader(std::shared_ptr<Network::ArticBase::Client>& cli, bool is_update_romfs);
|
||||
|
||||
~ArticRomFSReader() override;
|
||||
|
||||
std::size_t GetSize() const override {
|
||||
return data_size;
|
||||
}
|
||||
|
||||
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
||||
|
||||
bool AllowsCachedReads() const override;
|
||||
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
||||
|
||||
Loader::ResultStatus OpenStatus() {
|
||||
return load_status;
|
||||
}
|
||||
|
||||
void ClearCache() {
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
void CloseFile();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Network::ArticBase::Client> client;
|
||||
size_t data_size = 0;
|
||||
s32 romfs_handle = -1;
|
||||
Loader::ResultStatus load_status;
|
||||
|
||||
ArticCache cache;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||
ar& data_size;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::DirectRomFSReader)
|
||||
BOOST_CLASS_EXPORT_KEY(FileSys::ArticRomFSReader)
|
||||
360
src/core/file_sys/savedata_archive.cpp
Normal file
360
src/core/file_sys/savedata_archive.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/archives.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/disk_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/path_parser.h"
|
||||
#include "core/file_sys/savedata_archive.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class SaveDataDelayGenerator : public DelayGenerator {
|
||||
public:
|
||||
u64 GetReadDelayNs(std::size_t length) override {
|
||||
// The delay was measured on O3DS and O2DS with
|
||||
// https://gist.github.com/B3n30/ac40eac20603f519ff106107f4ac9182
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 slope(183);
|
||||
static constexpr u64 offset(524879);
|
||||
static constexpr u64 minimum(631826);
|
||||
u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
u64 GetOpenDelayNs() override {
|
||||
// This is the delay measured on O3DS and O2DS with
|
||||
// https://gist.github.com/FearlessTobi/c37e143c314789251f98f2c45cd706d2
|
||||
// from the results the average of each length was taken.
|
||||
static constexpr u64 IPCDelayNanoseconds(269082);
|
||||
return IPCDelayNanoseconds;
|
||||
}
|
||||
|
||||
SERIALIZE_DELAY_GENERATOR
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
|
||||
const Mode& mode,
|
||||
u32 attributes) {
|
||||
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
||||
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
if (mode.hex == 0) {
|
||||
LOG_ERROR(Service_FS, "Empty open mode");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
if (mode.create_flag && !mode.write_flag) {
|
||||
LOG_ERROR(Service_FS, "Create flag set but write flag not set");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::DirectoryFound:
|
||||
LOG_ERROR(Service_FS, "Unexpected file or directory in {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectory;
|
||||
case PathParser::NotFound:
|
||||
if (!mode.create_flag) {
|
||||
LOG_ERROR(Service_FS, "Non-existing file {} can't be open without mode create.",
|
||||
full_path);
|
||||
return ResultFileNotFound;
|
||||
} else {
|
||||
// Create the file
|
||||
FileUtil::CreateEmptyFile(full_path);
|
||||
}
|
||||
break;
|
||||
case PathParser::FileFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
|
||||
return ResultFileNotFound;
|
||||
}
|
||||
|
||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<SaveDataDelayGenerator>();
|
||||
return std::make_unique<DiskFile>(std::move(file), mode, std::move(delay_generator));
|
||||
}
|
||||
|
||||
Result SaveDataArchive::DeleteFile(const Path& path) const {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::DirectoryFound:
|
||||
case PathParser::NotFound:
|
||||
LOG_ERROR(Service_FS, "File not found {}", full_path);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::FileFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (FileUtil::Delete(full_path)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting {}", full_path);
|
||||
return ResultFileNotFound;
|
||||
}
|
||||
|
||||
Result SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
const PathParser path_parser_src(src_path);
|
||||
|
||||
// TODO: Verify these return codes with HW
|
||||
if (!path_parser_src.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid src path {}", src_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const PathParser path_parser_dest(dest_path);
|
||||
|
||||
if (!path_parser_dest.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid dest path {}", dest_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
|
||||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
|
||||
|
||||
if (FileUtil::Rename(src_path_full, dest_path_full)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||
// exist or similar. Verify.
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Result DeleteDirectoryHelper(const Path& path, const std::string& mount_point, T deleter) {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
if (path_parser.IsRootDirectory())
|
||||
return ResultDirectoryNotEmpty;
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::NotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::FileFound:
|
||||
LOG_ERROR(Service_FS, "Unexpected file or directory {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectory;
|
||||
case PathParser::DirectoryFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (deleter(full_path)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS, "Directory not empty {}", full_path);
|
||||
return ResultDirectoryNotEmpty;
|
||||
}
|
||||
|
||||
Result SaveDataArchive::DeleteDirectory(const Path& path) const {
|
||||
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
|
||||
}
|
||||
|
||||
Result SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||
return DeleteDirectoryHelper(
|
||||
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
||||
}
|
||||
|
||||
Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
LOG_ERROR(Service_FS, "Unexpected file in path {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectory;
|
||||
case PathParser::DirectoryFound:
|
||||
case PathParser::FileFound:
|
||||
LOG_ERROR(Service_FS, "{} already exists", full_path);
|
||||
return ResultFileAlreadyExists;
|
||||
case PathParser::NotFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
if (allow_zero_size_create) {
|
||||
FileUtil::CreateEmptyFile(full_path);
|
||||
return ResultSuccess;
|
||||
} else {
|
||||
LOG_DEBUG(Service_FS, "Zero-size file is not supported");
|
||||
return ResultUnsupportedOpenFlags;
|
||||
}
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(full_path, "wb");
|
||||
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
||||
// We do this by seeking to the right size, then writing a single null byte.
|
||||
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS, "Too large file");
|
||||
return Result(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
|
||||
ErrorLevel::Info);
|
||||
}
|
||||
|
||||
Result SaveDataArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
LOG_ERROR(Service_FS, "Unexpected file in path {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectory;
|
||||
case PathParser::DirectoryFound:
|
||||
case PathParser::FileFound:
|
||||
LOG_ERROR(Service_FS, "{} already exists", full_path);
|
||||
return ResultDirectoryAlreadyExists;
|
||||
case PathParser::NotFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
if (FileUtil::CreateDir(mount_point + path.AsString())) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", mount_point);
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
|
||||
ErrorLevel::Status);
|
||||
}
|
||||
|
||||
Result SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
const PathParser path_parser_src(src_path);
|
||||
|
||||
// TODO: Verify these return codes with HW
|
||||
if (!path_parser_src.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid src path {}", src_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const PathParser path_parser_dest(dest_path);
|
||||
|
||||
if (!path_parser_dest.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid dest path {}", dest_path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
|
||||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
|
||||
|
||||
if (FileUtil::Rename(src_path_full, dest_path_full)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
||||
// exist or similar. Verify.
|
||||
return Result(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(const Path& path) {
|
||||
const PathParser path_parser(path);
|
||||
|
||||
if (!path_parser.IsValid()) {
|
||||
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
|
||||
return ResultInvalidPath;
|
||||
}
|
||||
|
||||
const auto full_path = path_parser.BuildHostPath(mount_point);
|
||||
|
||||
switch (path_parser.GetHostStatus(mount_point)) {
|
||||
case PathParser::InvalidMountPoint:
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
|
||||
return ResultFileNotFound;
|
||||
case PathParser::PathNotFound:
|
||||
case PathParser::NotFound:
|
||||
LOG_ERROR(Service_FS, "Path not found {}", full_path);
|
||||
return ResultPathNotFound;
|
||||
case PathParser::FileInPath:
|
||||
case PathParser::FileFound:
|
||||
LOG_ERROR(Service_FS, "Unexpected file in path {}", full_path);
|
||||
return ResultUnexpectedFileOrDirectory;
|
||||
case PathParser::DirectoryFound:
|
||||
break; // Expected 'success' case
|
||||
}
|
||||
|
||||
return std::make_unique<DiskDirectory>(full_path);
|
||||
}
|
||||
|
||||
u64 SaveDataArchive::GetFreeBytes() const {
|
||||
// TODO: Stubbed to return 32MiB
|
||||
return 1024 * 1024 * 32;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SaveDataArchive)
|
||||
SERIALIZE_EXPORT_IMPL(FileSys::SaveDataDelayGenerator)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user