First Commit

This commit is contained in:
2025-02-06 22:24:29 +08:00
parent ed7df4c81e
commit 7539e6a53c
18116 changed files with 6181499 additions and 0 deletions

21
src/core/3ds.h Normal file
View 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
View 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()

View 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)

View 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 = &current_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

View 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

View 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;
}

View 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;
};

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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);

File diff suppressed because it is too large Load Diff

View 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);

View 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];
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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;

View 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

View 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

View 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,
};

View 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);
}
}

View 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;
};

View 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;
}

View 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*);

View 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)
};

View 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;
}

View 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);

View 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);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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
View 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
View 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

View 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

View 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
View 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
View 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
View 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
View 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)

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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)

View 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

View 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

View 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)

View 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)

View 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

View 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)

View 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

View 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)

View 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

View 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)

View 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)

View 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)

View 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)

View 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)

View 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)

View 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)

View 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

View 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)

View 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

View 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)

View 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

View 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)

View 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

View 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

View 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

View 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

View 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);

View 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

View 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

View 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)

View 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

View 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

View 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

View 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)

View 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, &current);
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 = &current;
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(&current, static_cast<u32>(current_directory_offset));
directory_list.emplace_back(&current);
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(&current, static_cast<u32>(current_file_offset));
file_list.emplace_back(&current);
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

View 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)

View 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

View 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
View 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
View 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

View 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

View 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

View 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);
}

View 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

View 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};

View 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

View 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)

View 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