Merge branch 'master' into feature/savestates-2

This commit is contained in:
Hamish Milne 2020-03-07 21:23:08 +00:00
commit da3ab3d56e
80 changed files with 7297 additions and 2608 deletions

View file

@ -72,6 +72,8 @@ add_library(core STATIC
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
@ -469,9 +471,17 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives Boost::serialization)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
get_directory_property(OPENSSL_LIBS
DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
DEFINITION OPENSSL_LIBS)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(core PRIVATE web_service ${OPENSSL_LIBS} httplib lurlparser)
if (ANDROID)
target_link_libraries(core PRIVATE ifaddrs)
endif()
endif()
if (ARCHITECTURE_x86_64)

View file

@ -10,6 +10,7 @@
#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"
namespace Memory {
struct PageTable;
@ -18,6 +19,8 @@ struct PageTable;
/// 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() {}
class ThreadContext {
@ -222,11 +225,26 @@ public:
virtual void PurgeState() = 0;
std::shared_ptr<Core::Timing::Timer> GetTimer() {
return timer;
}
u32 GetID() const {
return id;
}
protected:
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;
auto page_table = GetPageTable();
ar << page_table;
for (auto i = 0; i < 15; i++) {
@ -254,6 +272,8 @@ private:
template <class Archive>
void load(Archive& ar, const unsigned int file_version) {
PurgeState();
ar >> timer;
ar >> id;
std::shared_ptr<Memory::PageTable> page_table = nullptr;
ar >> page_table;
SetPageTable(page_table);

View file

@ -72,8 +72,7 @@ private:
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicUserCallbacks(ARM_Dynarmic& parent)
: parent(parent), timing(parent.system.CoreTiming()), svc_context(parent.system),
memory(parent.memory) {}
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
~DynarmicUserCallbacks() = default;
std::uint8_t MemoryRead8(VAddr vaddr) override {
@ -137,7 +136,7 @@ public:
parent.jit->HaltExecution();
parent.SetPC(pc);
Kernel::Thread* thread =
parent.system.Kernel().GetThreadManager().GetCurrentThread();
parent.system.Kernel().GetCurrentThreadManager().GetCurrentThread();
parent.SaveContext(thread->context);
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
@ -150,22 +149,23 @@ public:
}
void AddTicks(std::uint64_t ticks) override {
timing.AddTicks(ticks);
parent.GetTimer()->AddTicks(ticks);
}
std::uint64_t GetTicksRemaining() override {
s64 ticks = timing.GetDowncount();
s64 ticks = parent.GetTimer()->GetDowncount();
return static_cast<u64>(ticks <= 0 ? 0 : ticks);
}
ARM_Dynarmic& parent;
Core::Timing& timing;
Kernel::SVCContext svc_context;
Memory::MemorySystem& memory;
};
ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory,
PrivilegeMode initial_mode)
: system(*system), memory(memory), cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
PrivilegeMode initial_mode, u32 id,
std::shared_ptr<Core::Timing::Timer> timer)
: ARM_Interface(id, timer), system(*system), memory(memory),
cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
interpreter_state = std::make_shared<ARMul_State>(system, memory, initial_mode);
SetPageTable(memory.GetCurrentPageTable());
}

View file

@ -24,7 +24,8 @@ class DynarmicUserCallbacks;
class ARM_Dynarmic final : public ARM_Interface {
public:
ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, PrivilegeMode initial_mode);
ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, PrivilegeMode initial_mode,
u32 id, std::shared_ptr<Core::Timing::Timer> timer);
~ARM_Dynarmic() override;
void Run() override;

View file

@ -69,8 +69,9 @@ private:
};
ARM_DynCom::ARM_DynCom(Core::System* system, Memory::MemorySystem& memory,
PrivilegeMode initial_mode)
: system(system) {
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);
}
@ -78,7 +79,7 @@ ARM_DynCom::~ARM_DynCom() {}
void ARM_DynCom::Run() {
DEBUG_ASSERT(system != nullptr);
ExecuteInstructions(std::max<s64>(system->CoreTiming().GetDowncount(), 0));
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
}
void ARM_DynCom::Step() {
@ -156,7 +157,7 @@ void ARM_DynCom::ExecuteInstructions(u64 num_instructions) {
state->NumInstrsToExecute = num_instructions;
unsigned ticks_executed = InterpreterMainLoop(state.get());
if (system != nullptr) {
system->CoreTiming().AddTicks(ticks_executed);
timer->AddTicks(ticks_executed);
}
state->ServeBreak();
}

View file

@ -21,7 +21,8 @@ class MemorySystem;
class ARM_DynCom final : public ARM_Interface {
public:
explicit ARM_DynCom(Core::System* system, Memory::MemorySystem& memory,
PrivilegeMode initial_mode);
PrivilegeMode initial_mode, u32 id,
std::shared_ptr<Core::Timing::Timer> timer);
~ARM_DynCom() override;
void Run() override;

View file

@ -3865,7 +3865,7 @@ SWI_INST : {
if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) {
DEBUG_ASSERT(cpu->system != nullptr);
swi_inst* const inst_cream = (swi_inst*)inst_base->component;
cpu->system->CoreTiming().AddTicks(num_instrs);
cpu->system->GetRunningCore().GetTimer()->AddTicks(num_instrs);
cpu->NumInstrsToExecute =
num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs;
num_instrs = 0;

View file

@ -607,8 +607,8 @@ void ARMul_State::ServeBreak() {
}
DEBUG_ASSERT(system != nullptr);
Kernel::Thread* thread = system->Kernel().GetThreadManager().GetCurrentThread();
system->CPU().SaveContext(thread->context);
Kernel::Thread* thread = system->Kernel().GetCurrentThreadManager().GetCurrentThread();
system->GetRunningCore().SaveContext(thread->context);
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;

View file

@ -35,7 +35,7 @@ static inline std::enable_if_t<std::is_integral_v<T>> WriteOp(const GatewayCheat
Core::System& system) {
u32 addr = line.address + state.offset;
write_func(addr, static_cast<T>(line.value));
system.CPU().InvalidateCacheRange(addr, sizeof(T));
system.InvalidateCacheRange(addr, sizeof(T));
}
template <typename T, typename ReadFunction, typename CompareFunc>
@ -105,7 +105,7 @@ static inline std::enable_if_t<std::is_integral_v<T>> IncrementiveWriteOp(
Core::System& system) {
u32 addr = line.value + state.offset;
write_func(addr, static_cast<T>(state.reg));
system.CPU().InvalidateCacheRange(addr, sizeof(T));
system.InvalidateCacheRange(addr, sizeof(T));
state.offset += sizeof(T);
}
@ -143,7 +143,8 @@ static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Co
}
u32 num_bytes = line.value;
u32 addr = line.address + state.offset;
system.CPU().InvalidateCacheRange(addr, num_bytes);
system.InvalidateCacheRange(addr, num_bytes);
bool first = true;
u32 bit_offset = 0;
if (num_bytes > 0)

View file

@ -5,6 +5,7 @@
#include <fstream>
#include <memory>
#include <stdexcept>
#include <utility>
#include <boost/serialization/array.hpp>
#include "audio_core/dsp_interface.h"
@ -65,7 +66,8 @@ System::~System() = default;
System::ResultStatus System::RunLoop(bool tight_loop) {
status = ResultStatus::Success;
if (!cpu_core) {
if (std::any_of(cpu_cores.begin(), cpu_cores.end(),
[](std::shared_ptr<ARM_Interface> ptr) { return ptr == nullptr; })) {
return ResultStatus::ErrorNotInitialized;
}
@ -83,22 +85,73 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
}
}
// 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->GetThreadManager().GetCurrentThread() == nullptr) {
LOG_TRACE(Core_ARM11, "Idling");
timing->Idle();
timing->Advance();
PrepareReschedule();
} else {
timing->Advance();
if (tight_loop) {
cpu_core->Run();
} else {
cpu_core->Step();
// 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;
std::shared_ptr<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();
cpu_core->GetTimer()->Advance(delay);
if (max_delay < delay) {
max_delay = delay;
current_core_to_execute = cpu_core;
}
}
}
if (max_delay > 0) {
LOG_TRACE(Core_ARM11, "Core {} running (delayed) for {} ticks",
current_core_to_execute->GetID(),
current_core_to_execute->GetTimer()->GetDowncount());
running_core = current_core_to_execute.get();
kernel->SetRunningCPU(current_core_to_execute);
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) {
max_slice = std::min(max_slice, cpu_core->GetTimer()->GetMaxSliceLength());
}
for (auto& cpu_core : cpu_cores) {
cpu_core->GetTimer()->Advance(max_slice);
}
for (auto& cpu_core : cpu_cores) {
LOG_TRACE(Core_ARM11, "Core {} running for {} ticks", cpu_core->GetID(),
cpu_core->GetTimer()->GetDowncount());
running_core = cpu_core.get();
kernel->SetRunningCPU(cpu_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();
}
}
}
timing->AddToGlobalTicks(max_slice);
}
if (GDBStub::IsServerEnabled()) {
GDBStub::SetCpuStepFlag(false);
}
@ -183,7 +236,9 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
}
ASSERT(system_mode.first);
ResultStatus init_result{Init(emu_window, *system_mode.first)};
auto n3ds_mode = app_loader->LoadKernelN3dsMode();
ASSERT(n3ds_mode.first);
ResultStatus init_result{Init(emu_window, *system_mode.first, *n3ds_mode.first)};
if (init_result != ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
static_cast<u32>(init_result));
@ -235,7 +290,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
}
void System::PrepareReschedule() {
cpu_core->PrepareReschedule();
running_core->PrepareReschedule();
reschedule_pending = true;
}
@ -249,31 +304,50 @@ void System::Reschedule() {
}
reschedule_pending = false;
kernel->GetThreadManager().Reschedule();
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, u32 system_mode) {
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode) {
LOG_DEBUG(HW_Memory, "initialized OK");
std::size_t num_cores = 2;
if (Settings::values.is_new_3ds) {
num_cores = 4;
}
memory = std::make_unique<Memory::MemorySystem>();
timing = std::make_unique<Timing>();
timing = std::make_unique<Timing>(num_cores);
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing,
[this] { PrepareReschedule(); }, system_mode);
kernel = std::make_unique<Kernel::KernelSystem>(
*memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores, n3ds_mode);
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
cpu_core = std::make_shared<ARM_Dynarmic>(this, *memory, USER32MODE);
for (std::size_t i = 0; i < num_cores; ++i) {
cpu_cores.push_back(
std::make_shared<ARM_Dynarmic>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
}
#else
cpu_core = std::make_shared<ARM_DynCom>(this, *memory, USER32MODE);
for (std::size_t 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 {
cpu_core = std::make_shared<ARM_DynCom>(this, *memory, USER32MODE);
for (std::size_t 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->SetCPU(cpu_core);
kernel->SetCPUs(cpu_cores);
kernel->SetRunningCPU(cpu_cores[0]);
if (Settings::values.enable_dsp_lle) {
dsp_core = std::make_unique<AudioCore::DspLle>(*memory,
@ -296,7 +370,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
HW::Init(*memory);
Service::Init(*this);
GDBStub::Init();
GDBStub::DeferStart();
VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory);
if (result != VideoCore::ResultStatus::Success) {
@ -318,6 +392,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
LOG_DEBUG(Core, "Initialized OK");
initalized = true;
return ResultStatus::Success;
}
@ -421,9 +497,10 @@ void System::Shutdown() {
perf_stats.reset();
rpc_server.reset();
cheat_engine.reset();
archive_manager.reset();
service_manager.reset();
dsp_core.reset();
cpu_core.reset();
cpu_cores.clear();
kernel.reset();
timing.reset();
app_loader.reset();
@ -452,11 +529,18 @@ void System::Reset() {
template <class Archive>
void System::serialize(Archive& ar, const unsigned int file_version) {
u32 num_cores;
ar& num_cores;
if (num_cores != this->GetNumCores()) {
throw std::runtime_error("Wrong N3DS mode");
}
// flush on save, don't flush on load
bool should_flush = !Archive::is_loading::value;
Memory::RasterizerClearAll(should_flush);
ar&* timing.get();
ar&* cpu_core.get();
for (int i = 0; i < num_cores; i++) {
ar&* cpu_cores[i].get();
}
ar&* service_manager.get();
ar& GPU::g_regs;
ar& LCD::g_regs;

View file

@ -148,7 +148,10 @@ public:
* @returns True if the emulated system is powered on, otherwise false.
*/
bool IsPoweredOn() const {
return cpu_core != nullptr;
return cpu_cores.size() > 0 &&
std::all_of(cpu_cores.begin(), cpu_cores.end(),
[](std::shared_ptr<ARM_Interface> ptr) { return ptr != nullptr; });
;
}
/**
@ -168,8 +171,29 @@ public:
* Gets a reference to the emulated CPU.
* @returns A reference to the emulated CPU.
*/
ARM_Interface& CPU() {
return *cpu_core;
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.
*/
ARM_Interface& GetCore(u32 core_id) {
return *cpu_cores[core_id];
};
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);
}
}
/**
@ -291,7 +315,7 @@ private:
* @param system_mode The system mode.
* @return ResultStatus code, indicating if the operation succeeded.
*/
ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode);
ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode);
/// Reschedule the core emulation
void Reschedule();
@ -300,7 +324,8 @@ private:
std::unique_ptr<Loader::AppLoader> app_loader;
/// ARM11 CPU core
std::shared_ptr<ARM_Interface> 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;
@ -342,6 +367,8 @@ private:
private:
static System s_instance;
bool initalized = false;
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
/// Saved variables for reset
@ -358,8 +385,16 @@ private:
void serialize(Archive& ar, const unsigned int file_version);
};
inline ARM_Interface& CPU() {
return System::GetInstance().CPU();
inline ARM_Interface& GetRunningCore() {
return System::GetInstance().GetRunningCore();
}
inline ARM_Interface& GetCore(u32 core_id) {
return System::GetInstance().GetCore(core_id);
}
inline u32 GetNumCores() {
return System::GetInstance().GetNumCores();
}
inline AudioCore::DspInterface& DSP() {

View file

@ -14,14 +14,22 @@ namespace Core {
Timing* Timing::deserializing = nullptr;
// 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 Event& right) const {
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 Event& right) const {
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) {
timers.resize(num_cores);
for (std::size_t i = 0; i < num_cores; ++i) {
timers[i] = std::make_shared<Timer>();
}
current_timer = timers[0];
}
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.
@ -34,73 +42,102 @@ TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback ca
return event_type;
}
Timing::~Timing() {
void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata,
std::size_t core_id) {
ASSERT(event_type != nullptr);
std::shared_ptr<Timing::Timer> timer;
if (core_id == std::numeric_limits<std::size_t>::max()) {
timer = current_timer;
} else {
ASSERT(core_id < timers.size());
timer = timers.at(core_id);
}
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++, userdata, 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,
userdata, event_type});
}
}
void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) {
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.userdata == userdata; });
// 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) {
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];
}
s64 Timing::GetTicks() const {
return current_timer->GetTicks();
}
s64 Timing::GetGlobalTicks() const {
return global_timer;
}
std::chrono::microseconds Timing::GetGlobalTimeUs() const {
return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE_ARM11};
}
std::shared_ptr<Timing::Timer> Timing::GetTimer(std::size_t cpu_id) {
return timers[cpu_id];
}
Timing::Timer::~Timer() {
MoveEvents();
}
u64 Timing::GetTicks() const {
u64 ticks = static_cast<u64>(global_timer);
if (!is_global_timer_sane) {
u64 Timing::Timer::GetTicks() const {
u64 ticks = static_cast<u64>(executed_ticks);
if (!is_timer_sane) {
ticks += slice_length - downcount;
}
return ticks;
}
void Timing::AddTicks(u64 ticks) {
void Timing::Timer::AddTicks(u64 ticks) {
downcount -= ticks;
}
u64 Timing::GetIdleTicks() const {
u64 Timing::Timer::GetIdleTicks() const {
return static_cast<u64>(idled_cycles);
}
void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type,
u64 userdata) {
ASSERT(event_type != nullptr);
s64 timeout = GetTicks() + cycles_into_future;
// If this event needs to be scheduled before the next advance(), force one early
if (!is_global_timer_sane)
ForceExceptionCheck(cycles_into_future);
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
void Timing::ScheduleEventThreadsafe(s64 cycles_into_future, const TimingEventType* event_type,
u64 userdata) {
ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type});
}
void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) {
auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type == event_type && e.userdata == userdata;
});
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
void Timing::RemoveEvent(const TimingEventType* event_type) {
auto itr = std::remove_if(event_queue.begin(), 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 != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
void Timing::RemoveNormalAndThreadsafeEvent(const TimingEventType* event_type) {
MoveEvents();
RemoveEvent(event_type);
}
void Timing::ForceExceptionCheck(s64 cycles) {
void Timing::Timer::ForceExceptionCheck(s64 cycles) {
cycles = std::max<s64>(0, cycles);
if (downcount > cycles) {
slice_length -= downcount - cycles;
@ -108,7 +145,7 @@ void Timing::ForceExceptionCheck(s64 cycles) {
}
}
void Timing::MoveEvents() {
void Timing::Timer::MoveEvents() {
for (Event ev; ts_queue.Pop(ev);) {
ev.fifo_order = event_fifo_id++;
event_queue.emplace_back(std::move(ev));
@ -116,50 +153,54 @@ void Timing::MoveEvents() {
}
}
void Timing::Advance() {
s64 Timing::Timer::GetMaxSliceLength() const {
auto next_event = std::find_if(event_queue.begin(), event_queue.end(),
[&](const Event& e) { return e.time - executed_ticks > 0; });
if (next_event != event_queue.end()) {
return next_event->time - executed_ticks;
}
return MAX_SLICE_LENGTH;
}
void Timing::Timer::Advance(s64 max_slice_length) {
MoveEvents();
s64 cycles_executed = slice_length - downcount;
global_timer += cycles_executed;
slice_length = MAX_SLICE_LENGTH;
idled_cycles = 0;
executed_ticks += cycles_executed;
slice_length = max_slice_length;
is_global_timer_sane = true;
is_timer_sane = true;
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
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 (event_types.find(*evt.type->name) == event_types.end()) {
LOG_ERROR(Core, "Unknown queued event {}", *evt.type->name);
} else if (evt.type->callback == nullptr) {
if (evt.type->callback == nullptr) {
LOG_ERROR(Core, "Event '{}' has no callback", *evt.type->name);
}
if (evt.type->callback != nullptr) {
evt.type->callback(evt.userdata, global_timer - evt.time);
evt.type->callback(evt.userdata, executed_ticks - evt.time);
}
}
is_global_timer_sane = false;
is_timer_sane = false;
// Still events left (scheduled in the future)
if (!event_queue.empty()) {
slice_length = static_cast<int>(
std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH));
std::min<s64>(event_queue.front().time - executed_ticks, max_slice_length));
}
downcount = slice_length;
}
void Timing::Idle() {
void Timing::Timer::Idle() {
idled_cycles += downcount;
downcount = 0;
}
std::chrono::microseconds Timing::GetGlobalTimeUs() const {
return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE_ARM11};
}
s64 Timing::GetDowncount() const {
s64 Timing::Timer::GetDowncount() const {
return downcount;
}

View file

@ -135,65 +135,10 @@ struct TimingEventType {
};
class Timing {
public:
~Timing();
/**
* This should only be called from the emu thread, if you are calling it any other thread, you
* are doing something evil
*/
u64 GetTicks() const;
u64 GetIdleTicks() const;
void AddTicks(u64 ticks);
/**
* Returns the event_type identifier. if name is not unique, it will assert.
*/
TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback);
/**
* After the first Advance, the slice lengths and the downcount will be reduced whenever an
* event is scheduled earlier than the current values. Scheduling from a callback will not
* update the downcount until the Advance() completes.
*/
void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata = 0);
/**
* This is to be called when outside of hle threads, such as the graphics thread, wants to
* schedule things to be executed on the main thread.
* Not that this doesn't change slice_length and thus events scheduled by this might be called
* with a delay of up to MAX_SLICE_LENGTH
*/
void ScheduleEventThreadsafe(s64 cycles_into_future, const TimingEventType* event_type,
u64 userdata);
void UnscheduleEvent(const TimingEventType* event_type, u64 userdata);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const TimingEventType* event_type);
void RemoveNormalAndThreadsafeEvent(const TimingEventType* event_type);
/** Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
* the previous timing slice and begins the next one, you must Advance from the previous
* slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an
* Advance() is required to initialize the slice length before the first cycle of emulated
* instructions is executed.
*/
void Advance();
void MoveEvents();
/// Pretend that the main CPU has executed enough cycles to reach the next event.
void Idle();
void ForceExceptionCheck(s64 cycles);
std::chrono::microseconds GetGlobalTimeUs() const;
s64 GetDowncount() const;
private:
static Timing* deserializing;
public:
struct Event {
s64 time;
u64 fifo_order;
@ -229,48 +174,116 @@ private:
static constexpr int MAX_SLICE_LENGTH = 20000;
class Timer {
public:
~Timer();
s64 GetMaxSliceLength() const;
void Advance(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
// accomodated 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;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
MoveEvents();
ar& slice_length;
ar& downcount;
ar& event_queue;
ar& event_fifo_id;
ar& idled_cycles;
}
friend class boost::serialization::access;
};
explicit Timing(std::size_t num_cores);
~Timing(){};
/**
* Returns the event_type identifier. if name is not unique, it will assert.
*/
TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback);
void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata = 0,
std::size_t core_id = std::numeric_limits<std::size_t>::max());
void UnscheduleEvent(const TimingEventType* event_type, u64 userdata);
/// 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;
void AddToGlobalTicks(s64 ticks) {
global_timer += ticks;
}
std::chrono::microseconds GetGlobalTimeUs() const;
std::shared_ptr<Timer> GetTimer(std::size_t cpu_id);
private:
s64 global_timer = 0;
s64 slice_length = MAX_SLICE_LENGTH;
s64 downcount = MAX_SLICE_LENGTH;
// 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;
// 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
// accomodated 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;
s64 idled_cycles = 0;
// 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_global_timer_sane = true;
std::vector<std::shared_ptr<Timer>> timers;
std::shared_ptr<Timer> current_timer;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
// event_types set during initialization of other things
deserializing = this;
MoveEvents();
ar& global_timer;
ar& slice_length;
ar& downcount;
ar& event_queue;
ar& event_fifo_id;
ar& idled_cycles;
ar& timers;
ar& current_timer;
deserializing = nullptr;
}
friend class boost::serialization::access;
};
} // namespace Core

View file

@ -0,0 +1,604 @@
// 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/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"
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(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_)) {
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;
void 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);
if (metadata.first_file_offset != 0xFFFFFFFF) {
LoadFile(current, metadata.first_file_offset);
}
if (metadata.first_child_directory_offset != 0xFFFFFFFF) {
auto child = std::make_unique<Directory>();
auto& directory = *child;
directory.parent = &current;
current.directories.emplace_back(std::move(child));
LoadDirectory(directory, metadata.first_child_directory_offset);
}
if (metadata.next_sibling_offset != 0xFFFFFFFF) {
auto sibling = std::make_unique<Directory>();
auto& directory = *sibling;
directory.parent = current.parent;
current.parent->directories.emplace_back(std::move(sibling));
LoadDirectory(directory, metadata.next_sibling_offset);
}
}
void 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));
if (metadata.next_sibling_offset != 0xFFFFFFFF) {
LoadFile(parent, 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](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 directory = std::make_unique<Directory>();
directory->name = virtual_name;
directory->path = path;
directory->parent = parent;
directory_path_map.emplace(path, directory.get());
parent->directories.emplace_back(std::move(directory));
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);
}
}
}
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, 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, 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
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;
}
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 = 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 = 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
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,123 @@
// 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 "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);
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 siblings, and then its children.
void LoadDirectory(Directory& current, u32 offset);
// Load the file at offset, and then its siblings.
void 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();
std::shared_ptr<RomFSReader> romfs;
std::string patch_path;
std::string patch_ext_path;
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
};
} // namespace FileSys

View file

@ -11,6 +11,7 @@
#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"
@ -25,6 +26,14 @@ 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
@ -303,8 +312,22 @@ Loader::ResultStatus NCCHContainer::Load() {
}
}
FileUtil::IOFile exheader_override_file{filepath + ".exheader", "rb"};
const bool has_exheader_override = read_exheader(exheader_override_file);
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) {
@ -512,7 +535,15 @@ Loader::ResultStatus NCCHContainer::ApplyCodePatch(std::vector<u8>& code) const
std::string path;
bool (*patch_fn)(const std::vector<u8>& patch, std::vector<u8>& code);
};
const std::array<PatchLocation, 2> patch_paths{{
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<PatchLocation, 6> 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},
{filepath + ".exefsdir/code.ips", Patch::ApplyIpsPatch},
{filepath + ".exefsdir/code.bps", Patch::ApplyBpsPatch},
}};
@ -551,23 +582,34 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name,
else
return Loader::ResultStatus::Error;
std::string section_override = filepath + ".exefsdir/" + override_name;
FileUtil::IOFile section_file(section_override, "rb");
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,
}};
if (section_file.IsOpen()) {
auto section_size = section_file.GetSize();
buffer.resize(section_size);
for (const auto& path : override_paths) {
FileUtil::IOFile section_file(path, "rb");
section_file.Seek(0, SEEK_SET);
if (section_file.ReadBytes(&buffer[0], section_size) == section_size) {
LOG_WARNING(Service_FS, "File {} overriding built-in ExeFS file", section_override);
return Loader::ResultStatus::Success;
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[0], 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) {
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;
@ -597,14 +639,43 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
if (!romfs_file_inner.IsOpen())
return Loader::ResultStatus::Error;
std::shared_ptr<RomFSReader> direct_romfs;
if (is_encrypted) {
romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset,
romfs_size, secondary_key, romfs_ctr, 0x1000);
direct_romfs =
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset,
romfs_size, secondary_key, romfs_ctr, 0x1000);
} else {
romfs_file =
std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size);
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;
}
@ -614,9 +685,10 @@ Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReade
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", split_filepath);
romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), 0,
romfs_file_inner.GetSize());
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;
}
}

View file

@ -149,7 +149,8 @@ struct ExHeader_StorageInfo {
struct ExHeader_ARM11_SystemLocalCaps {
u64_le program_id;
u32_le core_version;
u8 reserved_flags[2];
u8 reserved_flag;
u8 n3ds_mode;
union {
u8 flags0;
BitField<0, 2, u8> ideal_processor;
@ -247,7 +248,15 @@ public:
* @param size The size of the romfs
* @return ResultStatus result of function
*/
Loader::ResultStatus ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file);
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

View file

@ -5,7 +5,7 @@
namespace FileSys {
std::size_t RomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
if (length == 0)
return 0; // Crypto++ does not like zero size buffer
file.Seek(file_offset + offset, SEEK_SET);

View file

@ -7,23 +7,39 @@
namespace FileSys {
/**
* Interface for reading RomFS data.
*/
class RomFSReader {
public:
RomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
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;
};
/**
* 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) {}
RomFSReader(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)
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) {}
std::size_t GetSize() const {
~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);
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
private:
bool is_encrypted;
@ -34,7 +50,7 @@ private:
u64 crypto_offset;
u64 data_size;
RomFSReader() = default;
DirectRomFSReader() = default;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {

View file

@ -121,6 +121,7 @@ constexpr char target_xml[] =
)";
int gdbserver_socket = -1;
bool defer_start = false;
u8 command_buffer[GDB_BUFFER_SIZE];
u32 command_length;
@ -160,10 +161,14 @@ BreakpointMap breakpoints_write;
} // Anonymous namespace
static Kernel::Thread* FindThreadById(int id) {
const auto& threads = Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
return thread.get();
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
return thread.get();
}
}
}
return nullptr;
@ -414,7 +419,10 @@ static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), bp->second.addr,
bp->second.inst.data(), bp->second.inst.size());
Core::CPU().ClearInstructionCache();
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
Core::GetCore(i).ClearInstructionCache();
}
}
p.erase(addr);
}
@ -540,10 +548,13 @@ static void HandleQuery() {
SendReply(target_xml);
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
std::string val = "m";
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x},", thread->GetThreadId());
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x},", thread->GetThreadId());
}
}
val.pop_back();
SendReply(val.c_str());
@ -553,11 +564,14 @@ static void HandleQuery() {
std::string buffer;
buffer += "l<?xml version=\"1.0\"?>";
buffer += "<threads>";
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
for (const auto& thread : threads) {
buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*",
thread->GetThreadId(), thread->GetThreadId());
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (const auto& thread : threads) {
buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*",
thread->GetThreadId(), thread->GetThreadId());
}
}
buffer += "</threads>";
SendReply(buffer.c_str());
@ -619,9 +633,9 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
if (full) {
buffer = fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};{:02x}:{:08x}", latest_signal,
PC_REGISTER, htonl(Core::CPU().GetPC()), SP_REGISTER,
htonl(Core::CPU().GetReg(SP_REGISTER)), LR_REGISTER,
htonl(Core::CPU().GetReg(LR_REGISTER)));
PC_REGISTER, htonl(Core::GetRunningCore().GetPC()), SP_REGISTER,
htonl(Core::GetRunningCore().GetReg(SP_REGISTER)), LR_REGISTER,
htonl(Core::GetRunningCore().GetReg(LR_REGISTER)));
} else {
buffer = fmt::format("T{:02x}", latest_signal);
}
@ -782,7 +796,7 @@ static void WriteRegister() {
return SendReply("E01");
}
Core::CPU().LoadContext(current_thread->context);
Core::GetRunningCore().LoadContext(current_thread->context);
SendReply("OK");
}
@ -812,7 +826,7 @@ static void WriteRegisters() {
}
}
Core::CPU().LoadContext(current_thread->context);
Core::GetRunningCore().LoadContext(current_thread->context);
SendReply("OK");
}
@ -869,7 +883,7 @@ static void WriteMemory() {
GdbHexToMem(data.data(), len_pos + 1, len);
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, data.data(), len);
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
SendReply("OK");
}
@ -883,12 +897,12 @@ void Break(bool is_memory_break) {
static void Step() {
if (command_length > 1) {
RegWrite(PC_REGISTER, GdbHexToInt(command_buffer + 1), current_thread);
Core::CPU().LoadContext(current_thread->context);
Core::GetRunningCore().LoadContext(current_thread->context);
}
step_loop = true;
halt_loop = true;
send_trap = true;
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
}
bool IsMemoryBreak() {
@ -904,7 +918,7 @@ static void Continue() {
memory_break = false;
step_loop = false;
halt_loop = false;
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
}
/**
@ -930,7 +944,7 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u32 len) {
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, btrap.data(),
btrap.size());
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
}
p.insert({addr, breakpoint});
@ -1030,6 +1044,9 @@ static void RemoveBreakpoint() {
void HandlePacket() {
if (!IsConnected()) {
if (defer_start) {
ToggleServer(true);
}
return;
}
@ -1120,6 +1137,10 @@ void ToggleServer(bool status) {
}
}
void DeferStart() {
defer_start = true;
}
static void Init(u16 port) {
if (!server_enabled) {
// Set the halt loop to false in case the user enabled the gdbstub mid-execution.
@ -1203,6 +1224,7 @@ void Shutdown() {
if (!server_enabled) {
return;
}
defer_start = false;
LOG_INFO(Debug_GDBStub, "Stopping GDB ...");
if (gdbserver_socket != -1) {

View file

@ -42,6 +42,13 @@ void ToggleServer(bool status);
/// Start the gdbstub server.
void Init();
/**
* Defer initialization of the gdbstub to the first packet processing functions.
* This avoids a case where the gdbstub thread is frozen after initialization
* and fails to respond in time to packets.
*/
void DeferStart();
/// Stop gdbstub server.
void Shutdown();

View file

@ -83,7 +83,7 @@ bool HandleTable::IsValid(Handle handle) const {
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return SharedFrom(kernel.GetThreadManager().GetCurrentThread());
return SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
} else if (handle == CurrentProcess) {
return kernel.GetCurrentProcess();
}

View file

@ -20,22 +20,30 @@ namespace Kernel {
/// Initialize the kernel
KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
std::function<void()> prepare_reschedule_callback, u32 system_mode)
std::function<void()> prepare_reschedule_callback, u32 system_mode,
u32 num_cores, u8 n3ds_mode)
: memory(memory), timing(timing),
prepare_reschedule_callback(std::move(prepare_reschedule_callback)) {
for (auto i = 0; i < memory_regions.size(); i++) {
memory_regions[i] = std::make_shared<MemoryRegionInfo>();
}
MemoryInit(system_mode);
MemoryInit(system_mode, n3ds_mode);
resource_limits = std::make_unique<ResourceLimitList>(*this);
thread_manager = std::make_unique<ThreadManager>(*this);
for (u32 core_id = 0; core_id < num_cores; ++core_id) {
thread_managers.push_back(std::make_unique<ThreadManager>(*this, core_id));
}
timer_manager = std::make_unique<TimerManager>(timing);
ipc_recorder = std::make_unique<IPCDebugger::Recorder>();
stored_processes.assign(num_cores, nullptr);
next_thread_id = 1;
}
/// Shutdown the kernel
KernelSystem::~KernelSystem() = default;
KernelSystem::~KernelSystem() {
ResetThreadIDs();
};
ResourceLimitList& KernelSystem::ResourceLimit() {
return *resource_limits;
@ -58,6 +66,15 @@ void KernelSystem::SetCurrentProcess(std::shared_ptr<Process> process) {
SetCurrentMemoryPageTable(process->vm_manager.page_table);
}
void KernelSystem::SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id) {
if (current_cpu->GetID() == core_id) {
current_process = process;
SetCurrentMemoryPageTable(process->vm_manager.page_table);
} else {
stored_processes[core_id] = process;
}
}
void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table) {
memory.SetCurrentPageTable(page_table);
if (current_cpu != nullptr) {
@ -65,17 +82,39 @@ void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable>
}
}
void KernelSystem::SetCPU(std::shared_ptr<ARM_Interface> cpu) {
void KernelSystem::SetCPUs(std::vector<std::shared_ptr<ARM_Interface>> cpus) {
ASSERT(cpus.size() == thread_managers.size());
u32 i = 0;
for (const auto& cpu : cpus) {
thread_managers[i++]->SetCPU(*cpu);
}
}
void KernelSystem::SetRunningCPU(std::shared_ptr<ARM_Interface> cpu) {
if (current_process) {
stored_processes[current_cpu->GetID()] = current_process;
}
current_cpu = cpu;
thread_manager->SetCPU(*cpu);
timing.SetCurrentTimer(cpu->GetID());
if (stored_processes[current_cpu->GetID()]) {
SetCurrentProcess(stored_processes[current_cpu->GetID()]);
}
}
ThreadManager& KernelSystem::GetThreadManager() {
return *thread_manager;
ThreadManager& KernelSystem::GetThreadManager(u32 core_id) {
return *thread_managers[core_id];
}
const ThreadManager& KernelSystem::GetThreadManager() const {
return *thread_manager;
const ThreadManager& KernelSystem::GetThreadManager(u32 core_id) const {
return *thread_managers[core_id];
}
ThreadManager& KernelSystem::GetCurrentThreadManager() {
return *thread_managers[current_cpu->GetID()];
}
const ThreadManager& KernelSystem::GetCurrentThreadManager() const {
return *thread_managers[current_cpu->GetID()];
}
TimerManager& KernelSystem::GetTimerManager() {
@ -106,6 +145,14 @@ void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> po
named_ports.emplace(std::move(name), std::move(port));
}
u32 KernelSystem::NewThreadId() {
return next_thread_id++;
}
void KernelSystem::ResetThreadIDs() {
next_thread_id = 0;
}
template <class Archive>
void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
ar& memory_regions;
@ -118,9 +165,14 @@ void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
ar& next_process_id;
ar& process_list;
ar& current_process;
ar&* thread_manager.get();
// NB: core count checked in 'core'
for (auto& thread_manager : thread_managers) {
ar&* thread_manager.get();
}
ar& config_mem_handler;
ar& shared_page_handler;
ar& stored_processes;
ar& next_thread_id;
// Deliberately don't include debugger info to allow debugging through loads
}

View file

@ -88,7 +88,8 @@ enum class MemoryRegion : u16 {
class KernelSystem {
public:
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
std::function<void()> prepare_reschedule_callback, u32 system_mode);
std::function<void()> prepare_reschedule_callback, u32 system_mode,
u32 num_cores, u8 n3ds_mode);
~KernelSystem();
using PortPair = std::pair<std::shared_ptr<ServerPort>, std::shared_ptr<ClientPort>>;
@ -214,13 +215,19 @@ public:
std::shared_ptr<Process> GetCurrentProcess() const;
void SetCurrentProcess(std::shared_ptr<Process> process);
void SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id);
void SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table);
void SetCPU(std::shared_ptr<ARM_Interface> cpu);
void SetCPUs(std::vector<std::shared_ptr<ARM_Interface>> cpu);
ThreadManager& GetThreadManager();
const ThreadManager& GetThreadManager() const;
void SetRunningCPU(std::shared_ptr<ARM_Interface> cpu);
ThreadManager& GetThreadManager(u32 core_id);
const ThreadManager& GetThreadManager(u32 core_id) const;
ThreadManager& GetCurrentThreadManager();
const ThreadManager& GetCurrentThreadManager() const;
TimerManager& GetTimerManager();
const TimerManager& GetTimerManager() const;
@ -246,6 +253,10 @@ public:
prepare_reschedule_callback();
}
u32 NewThreadId();
void ResetThreadIDs();
/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort
std::unordered_map<std::string, std::shared_ptr<ClientPort>> named_ports;
@ -256,7 +267,7 @@ public:
Core::Timing& timing;
private:
void MemoryInit(u32 mem_type);
void MemoryInit(u32 mem_type, u8 n3ds_mode);
std::function<void()> prepare_reschedule_callback;
@ -280,14 +291,17 @@ private:
std::vector<std::shared_ptr<Process>> process_list;
std::shared_ptr<Process> current_process;
std::vector<std::shared_ptr<Process>> stored_processes;
std::unique_ptr<ThreadManager> thread_manager;
std::vector<std::unique_ptr<ThreadManager>> thread_managers;
std::shared_ptr<ConfigMem::Handler> config_mem_handler;
std::shared_ptr<SharedPage::Handler> shared_page_handler;
std::unique_ptr<IPCDebugger::Recorder> ipc_recorder;
u32 next_thread_id;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version);

View file

@ -19,6 +19,7 @@
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/result.h"
#include "core/memory.h"
#include "core/settings.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -40,11 +41,32 @@ static const u32 memory_region_sizes[8][3] = {
{0x0B200000, 0x02E00000, 0x02000000}, // 7
};
void KernelSystem::MemoryInit(u32 mem_type) {
// TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead.
ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!");
namespace MemoryMode {
enum N3DSMode : u8 {
Mode6 = 1,
Mode7 = 2,
Mode6_2 = 3,
};
}
void KernelSystem::MemoryInit(u32 mem_type, u8 n3ds_mode) {
ASSERT(mem_type != 1);
const bool is_new_3ds = Settings::values.is_new_3ds;
u32 reported_mem_type = mem_type;
if (is_new_3ds) {
if (n3ds_mode == MemoryMode::Mode6 || n3ds_mode == MemoryMode::Mode6_2) {
mem_type = 6;
reported_mem_type = 6;
} else if (n3ds_mode == MemoryMode::Mode7) {
mem_type = 7;
reported_mem_type = 7;
} else {
// On the N3ds, all O3ds configurations (<=5) are forced to 6 instead.
mem_type = 6;
}
}
// The kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with
// the sizes specified in the memory_region_sizes table.
VAddr base = 0;
@ -55,14 +77,12 @@ void KernelSystem::MemoryInit(u32 mem_type) {
}
// We must've allocated the entire FCRAM by the end
ASSERT(base == Memory::FCRAM_SIZE);
ASSERT(base == (is_new_3ds ? Memory::FCRAM_N3DS_SIZE : Memory::FCRAM_SIZE));
config_mem_handler = std::make_shared<ConfigMem::Handler>();
auto& config_mem = config_mem_handler->GetConfigMem();
config_mem.app_mem_type = mem_type;
// app_mem_malloc does not always match the configured size for memory_region[0]: in case the
// n3DS type override is in effect it reports the size the game expects, not the real one.
config_mem.app_mem_alloc = memory_region_sizes[mem_type][0];
config_mem.app_mem_type = reported_mem_type;
config_mem.app_mem_alloc = memory_region_sizes[reported_mem_type][0];
config_mem.sys_mem_alloc = memory_regions[1]->size;
config_mem.base_mem_alloc = memory_regions[2]->size;

View file

@ -39,7 +39,7 @@ std::shared_ptr<Mutex> KernelSystem::CreateMutex(bool initial_locked, std::strin
// Acquire mutex with current thread if initialized as locked
if (initial_locked)
mutex->Acquire(thread_manager->GetCurrentThread());
mutex->Acquire(thread_managers[current_cpu->GetID()]->GetCurrentThread());
return mutex;
}

View file

@ -70,7 +70,7 @@ Handler::Handler(Core::Timing& timing) : timing(timing) {
using namespace std::placeholders;
update_time_event = timing.RegisterEvent("SharedPage::UpdateTimeCallback",
std::bind(&Handler::UpdateTimeCallback, this, _1, _2));
timing.ScheduleEvent(0, update_time_event);
timing.ScheduleEvent(0, update_time_event, 0, 0);
float slidestate = Settings::values.factor_3d / 100.0f;
shared_page.sliderstate_3d = static_cast<float_le>(slidestate);

View file

@ -280,12 +280,12 @@ void SVC::ExitProcess() {
current_process->status = ProcessStatus::Exited;
// Stop all the process threads that are currently waiting for objects.
auto& thread_list = kernel.GetThreadManager().GetThreadList();
auto& thread_list = kernel.GetCurrentThreadManager().GetThreadList();
for (auto& thread : thread_list) {
if (thread->owner_process != current_process)
continue;
if (thread.get() == kernel.GetThreadManager().GetCurrentThread())
if (thread.get() == kernel.GetCurrentThreadManager().GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
@ -297,7 +297,7 @@ void SVC::ExitProcess() {
}
// Kill the current thread
kernel.GetThreadManager().GetCurrentThread()->Stop();
kernel.GetCurrentThreadManager().GetCurrentThread()->Stop();
system.PrepareReschedule();
}
@ -388,7 +388,7 @@ ResultCode SVC::SendSyncRequest(Handle handle) {
system.PrepareReschedule();
auto thread = SharedFrom(kernel.GetThreadManager().GetCurrentThread());
auto thread = SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
if (kernel.GetIPCRecorder().IsEnabled()) {
kernel.GetIPCRecorder().RegisterRequest(session, thread);
@ -476,7 +476,7 @@ private:
/// Wait for a handle to synchronize, timeout after the specified nanoseconds
ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle);
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
if (object == nullptr)
return ERR_INVALID_HANDLE;
@ -514,7 +514,7 @@ ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count,
bool wait_all, s64 nano_seconds) {
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address))
return ERR_INVALID_POINTER;
@ -684,7 +684,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
// We are also sending a command reply.
// Do not send a reply if the command id in the command buffer is 0xFFFF.
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
u32 cmd_buff_header = memory.Read32(thread->GetCommandBufferAddress());
IPC::Header header{cmd_buff_header};
if (reply_target != 0 && header.command_id != 0xFFFF) {
@ -791,7 +791,7 @@ ResultCode SVC::ArbitrateAddress(Handle handle, u32 address, u32 type, u32 value
return ERR_INVALID_HANDLE;
auto res =
arbiter->ArbitrateAddress(SharedFrom(kernel.GetThreadManager().GetCurrentThread()),
arbiter->ArbitrateAddress(SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()),
static_cast<ArbitrationType>(type), address, value, nanoseconds);
// TODO(Subv): Identify in which specific cases this call should cause a reschedule.
@ -912,14 +912,19 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
break;
case ThreadProcessorIdAll:
LOG_INFO(Kernel_SVC,
"Newly created thread is allowed to be run in any Core, unimplemented.");
"Newly created thread is allowed to be run in any Core, for now run in core 0.");
processor_id = ThreadProcessorId0;
break;
case ThreadProcessorId1:
LOG_ERROR(Kernel_SVC,
"Newly created thread must run in the SysCore (Core1), unimplemented.");
case ThreadProcessorId2:
case ThreadProcessorId3:
// TODO: Check and log for: When processorid==0x2 and the process is not a BASE mem-region
// process, exheader kernel-flags bitmask 0x2000 must be set (otherwise error 0xD9001BEA is
// returned). When processorid==0x3 and the process is not a BASE mem-region process, error
// 0xD9001BEA is returned. These are the only restriction checks done by the kernel for
// processorid.
break;
default:
// TODO(bunnei): Implement support for other processor IDs
ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
break;
}
@ -945,9 +950,9 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
/// Called when a thread exits
void SVC::ExitThread() {
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CPU().GetPC());
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.GetRunningCore().GetPC());
kernel.GetThreadManager().ExitCurrentThread();
kernel.GetCurrentThreadManager().ExitCurrentThread();
system.PrepareReschedule();
}
@ -993,7 +998,7 @@ ResultCode SVC::SetThreadPriority(Handle handle, u32 priority) {
/// Create a mutex
ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) {
std::shared_ptr<Mutex> mutex = kernel.CreateMutex(initial_locked != 0);
mutex->name = fmt::format("mutex-{:08x}", system.CPU().GetReg(14));
mutex->name = fmt::format("mutex-{:08x}", system.GetRunningCore().GetReg(14));
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(mutex)));
LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}",
@ -1010,7 +1015,7 @@ ResultCode SVC::ReleaseMutex(Handle handle) {
if (mutex == nullptr)
return ERR_INVALID_HANDLE;
return mutex->Release(kernel.GetThreadManager().GetCurrentThread());
return mutex->Release(kernel.GetCurrentThreadManager().GetCurrentThread());
}
/// Get the ID of the specified process
@ -1060,7 +1065,7 @@ ResultCode SVC::GetThreadId(u32* thread_id, Handle handle) {
ResultCode SVC::CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) {
CASCADE_RESULT(std::shared_ptr<Semaphore> semaphore,
kernel.CreateSemaphore(initial_count, max_count));
semaphore->name = fmt::format("semaphore-{:08x}", system.CPU().GetReg(14));
semaphore->name = fmt::format("semaphore-{:08x}", system.GetRunningCore().GetReg(14));
CASCADE_RESULT(*out_handle,
kernel.GetCurrentProcess()->handle_table.Create(std::move(semaphore)));
@ -1130,8 +1135,9 @@ ResultCode SVC::QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 ad
/// Create an event
ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) {
std::shared_ptr<Event> evt = kernel.CreateEvent(
static_cast<ResetType>(reset_type), fmt::format("event-{:08x}", system.CPU().GetReg(14)));
std::shared_ptr<Event> evt =
kernel.CreateEvent(static_cast<ResetType>(reset_type),
fmt::format("event-{:08x}", system.GetRunningCore().GetReg(14)));
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(evt)));
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
@ -1173,8 +1179,9 @@ ResultCode SVC::ClearEvent(Handle handle) {
/// Creates a timer
ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) {
std::shared_ptr<Timer> timer = kernel.CreateTimer(
static_cast<ResetType>(reset_type), fmt ::format("timer-{:08x}", system.CPU().GetReg(14)));
std::shared_ptr<Timer> timer =
kernel.CreateTimer(static_cast<ResetType>(reset_type),
fmt ::format("timer-{:08x}", system.GetRunningCore().GetReg(14)));
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(timer)));
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
@ -1228,7 +1235,7 @@ ResultCode SVC::CancelTimer(Handle handle) {
void SVC::SleepThread(s64 nanoseconds) {
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
ThreadManager& thread_manager = kernel.GetThreadManager();
ThreadManager& thread_manager = kernel.GetCurrentThreadManager();
// Don't attempt to yield execution if there are no available threads to run,
// this way we avoid a useless reschedule to the idle thread.
@ -1246,10 +1253,11 @@ void SVC::SleepThread(s64 nanoseconds) {
/// This returns the total CPU ticks elapsed since the CPU was powered-on
s64 SVC::GetSystemTick() {
s64 result = system.CoreTiming().GetTicks();
// TODO: Use globalTicks here?
s64 result = system.GetRunningCore().GetTimer()->GetTicks();
// Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end.
// Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
system.CoreTiming().AddTicks(150);
system.GetRunningCore().GetTimer()->AddTicks(150);
return result;
}
@ -1611,11 +1619,11 @@ void SVC::CallSVC(u32 immediate) {
SVC::SVC(Core::System& system) : system(system), kernel(system.Kernel()), memory(system.Memory()) {}
u32 SVC::GetReg(std::size_t n) {
return system.CPU().GetReg(static_cast<int>(n));
return system.GetRunningCore().GetReg(static_cast<int>(n));
}
void SVC::SetReg(std::size_t n, u32 value) {
system.CPU().SetReg(static_cast<int>(n), value);
system.GetRunningCore().SetReg(static_cast<int>(n), value);
}
SVCContext::SVCContext(Core::System& system) : impl(std::make_unique<SVC>(system)) {}

View file

@ -62,13 +62,10 @@ void Thread::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
u32 ThreadManager::NewThreadId() {
return next_thread_id++;
}
Thread::Thread(KernelSystem& kernel)
: WaitObject(kernel), context(kernel.GetThreadManager().NewContext()),
thread_manager(kernel.GetThreadManager()) {}
Thread::Thread(KernelSystem& kernel, u32 core_id)
: WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()),
core_id(core_id),
thread_manager(kernel.GetThreadManager(core_id)) {}
Thread::~Thread() {}
Thread* ThreadManager::GetCurrentThread() const {
@ -113,7 +110,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
// Save context for previous thread
if (previous_thread) {
previous_thread->last_running_ticks = timing.GetTicks();
previous_thread->last_running_ticks = timing.GetGlobalTicks();
cpu->SaveContext(previous_thread->context);
if (previous_thread->status == ThreadStatus::Running) {
@ -140,7 +137,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
new_thread->status = ThreadStatus::Running;
if (previous_process != current_thread->owner_process) {
kernel.SetCurrentProcess(current_thread->owner_process);
kernel.SetCurrentProcessForCPU(current_thread->owner_process, cpu->GetID());
}
cpu->LoadContext(new_thread->context);
@ -153,7 +150,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
}
Thread* ThreadManager::PopNextReadyThread() {
Thread* next;
Thread* next = nullptr;
Thread* thread = GetCurrentThread();
if (thread && thread->status == ThreadStatus::Running) {
@ -337,22 +334,22 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
}
auto thread{std::make_shared<Thread>(*this)};
auto thread{std::make_shared<Thread>(*this, processor_id)};
thread_manager->thread_list.push_back(thread);
thread_manager->ready_queue.prepare(priority);
thread_managers[processor_id]->thread_list.push_back(thread);
thread_managers[processor_id]->ready_queue.prepare(priority);
thread->thread_id = thread_manager->NewThreadId();
thread->thread_id = NewThreadId();
thread->status = ThreadStatus::Dormant;
thread->entry_point = entry_point;
thread->stack_top = stack_top;
thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = timing.GetTicks();
thread->last_running_ticks = timing.GetGlobalTicks();
thread->processor_id = processor_id;
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
thread_manager->wakeup_callback_table[thread->thread_id] = thread.get();
thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
thread->owner_process = owner_process;
// Find the next available TLS index, and mark it as used
@ -397,7 +394,7 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
// to initialize the context
ResetThreadContext(thread->context, stack_top, entry_point, arg);
thread_manager->ready_queue.push_back(thread->current_priority, thread.get());
thread_managers[processor_id]->ready_queue.push_back(thread->current_priority, thread.get());
thread->status = ThreadStatus::Ready;
return MakeResult<std::shared_ptr<Thread>>(std::move(thread));
@ -463,6 +460,9 @@ void ThreadManager::Reschedule() {
LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
} else if (next) {
LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
} else {
LOG_TRACE(Kernel, "context switch idle -> idle, do nothing");
return;
}
SwitchContext(next);
@ -489,11 +489,10 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + command_header_offset;
}
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel) : kernel(kernel) {
ThreadWakeupEventType =
kernel.timing.RegisterEvent("ThreadWakeupCallback", [this](u64 thread_id, s64 cycle_late) {
ThreadWakeupCallback(thread_id, cycle_late);
});
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) {
ThreadWakeupEventType = kernel.timing.RegisterEvent(
"ThreadWakeupCallback_" + std::to_string(core_id),
[this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); });
}
ThreadManager::~ThreadManager() {

View file

@ -38,7 +38,9 @@ enum ThreadProcessorId : s32 {
ThreadProcessorIdAll = -1, ///< Run thread on either core
ThreadProcessorId0 = 0, ///< Run thread on core 0 (AppCore)
ThreadProcessorId1 = 1, ///< Run thread on core 1 (SysCore)
ThreadProcessorIdMax = 2, ///< Processor ID must be less than this
ThreadProcessorId2 = 2, ///< Run thread on core 2 (additional n3ds core)
ThreadProcessorId3 = 3, ///< Run thread on core 3 (additional n3ds core)
ThreadProcessorIdMax = 4, ///< Processor ID must be less than this
};
enum class ThreadStatus {
@ -75,15 +77,9 @@ private:
class ThreadManager {
public:
explicit ThreadManager(Kernel::KernelSystem& kernel);
explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id);
~ThreadManager();
/**
* Creates a new thread ID
* @return The new thread ID
*/
u32 NewThreadId();
/**
* Gets the current thread
*/
@ -150,7 +146,6 @@ private:
Kernel::KernelSystem& kernel;
ARM_Interface* cpu;
u32 next_thread_id = 1;
std::shared_ptr<Thread> current_thread;
Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue;
std::unordered_map<u64, Thread*> wakeup_callback_table;
@ -167,7 +162,6 @@ private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& next_thread_id;
ar& current_thread;
ar& ready_queue;
ar& wakeup_callback_table;
@ -177,7 +171,7 @@ private:
class Thread final : public WaitObject {
public:
explicit Thread(KernelSystem&);
explicit Thread(KernelSystem&, u32 core_id);
~Thread() override;
std::string GetName() const override {
@ -329,6 +323,8 @@ public:
// available. In case of a timeout, the object will be nullptr.
std::shared_ptr<WakeupCallback> wakeup_callback;
const u32 core_id;
private:
ThreadManager& thread_manager;
@ -351,4 +347,20 @@ std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Thread)
CONSTRUCT_KERNEL_OBJECT(Kernel::Thread)
namespace boost::serialization {
template <class Archive>
inline void save_construct_data(Archive& ar, const Kernel::Thread* t,
const unsigned int file_version) {
ar << t->core_id;
}
template <class Archive>
inline void load_construct_data(Archive& ar, Kernel::Thread* t, const unsigned int file_version) {
u32 core_id;
ar >> core_id;
::new (t) Kernel::Thread(Core::Global<Kernel::KernelSystem>(), core_id);
}
} // namespace boost::serialization

View file

@ -6,9 +6,7 @@
#include <array>
#include <atomic>
#ifndef _MSC_VER
#include <cstddef>
#endif
#include <memory>
#include "common/bit_field.h"
#include "common/common_funcs.h"
@ -177,10 +175,6 @@ struct GyroscopeCalibrateParam {
} x, y, z;
};
// TODO: MSVC does not support using offsetof() on non-static data members even though this
// is technically allowed since C++11. This macro should be enabled once MSVC adds
// support for that.
#ifndef _MSC_VER
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(SharedMem, field_name) == position * 4, \
"Field " #field_name " has invalid position")
@ -189,7 +183,6 @@ ASSERT_REG_POSITION(pad.index_reset_ticks, 0x0);
ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
#undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER)
struct DirectionState {
bool up;

View file

@ -2,9 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#ifdef ENABLE_WEB_SERVICE
#include <LUrlParser.h>
#endif
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include "common/archives.h"
#include "common/assert.h"
#include "core/core.h"
#include "core/file_sys/archive_ncch.h"
#include "core/file_sys/file_backend.h"
@ -52,6 +57,82 @@ const ResultCode ERROR_WRONG_CERT_HANDLE = // 0xD8A0A0C9
const ResultCode ERROR_CERT_ALREADY_SET = // 0xD8A0A03D
ResultCode(61, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent);
void Context::MakeRequest() {
ASSERT(state == RequestState::NotStarted);
#ifdef ENABLE_WEB_SERVICE
LUrlParser::clParseURL parsedUrl = LUrlParser::clParseURL::ParseURL(url);
int port;
std::unique_ptr<httplib::Client> client;
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = 80;
}
// TODO(B3N30): Support for setting timeout
// Figure out what the default timeout on 3DS is
client = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
} else {
if (!parsedUrl.GetPort(&port)) {
port = 443;
}
// TODO(B3N30): Support for setting timeout
// Figure out what the default timeout on 3DS is
auto ssl_client = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host, port);
SSL_CTX* ctx = ssl_client->ssl_context();
client = std::move(ssl_client);
if (auto client_cert = ssl_config.client_cert_ctx.lock()) {
SSL_CTX_use_certificate_ASN1(ctx, client_cert->certificate.size(),
client_cert->certificate.data());
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, client_cert->private_key.data(),
client_cert->private_key.size());
}
// TODO(B3N30): Check for SSLOptions-Bits and set the verify method accordingly
// https://www.3dbrew.org/wiki/SSL_Services#SSLOpt
// Hack: Since for now RootCerts are not implemented we set the VerifyMode to None.
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
}
state = RequestState::InProgress;
static const std::unordered_map<RequestMethod, std::string> request_method_strings{
{RequestMethod::Get, "GET"}, {RequestMethod::Post, "POST"},
{RequestMethod::Head, "HEAD"}, {RequestMethod::Put, "PUT"},
{RequestMethod::Delete, "DELETE"}, {RequestMethod::PostEmpty, "POST"},
{RequestMethod::PutEmpty, "PUT"},
};
httplib::Request request;
request.method = request_method_strings.at(method);
request.path = url;
// TODO(B3N30): Add post data body
request.progress = [this](u64 current, u64 total) -> bool {
// TODO(B3N30): Is there a state that shows response header are available
current_download_size_bytes = current;
total_download_size_bytes = total;
return true;
};
for (const auto& header : headers) {
request.headers.emplace(header.name, header.value);
}
if (!client->send(request, response)) {
LOG_ERROR(Service_HTTP, "Request failed");
state = RequestState::TimedOut;
} else {
LOG_DEBUG(Service_HTTP, "Request successful");
// TODO(B3N30): Verify this state on HW
state = RequestState::ReadyToDownloadContent;
}
#else
LOG_ERROR(Service_HTTP, "Tried to make request but WebServices is not enabled in this build");
state = RequestState::TimedOut;
#endif
}
void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x1, 1, 4);
const u32 shmem_size = rp.Pop<u32>();
@ -156,7 +237,15 @@ void HTTP_C::BeginRequest(Kernel::HLERequestContext& ctx) {
auto itr = contexts.find(context_handle);
ASSERT(itr != contexts.end());
// TODO(B3N30): Make the request
// On a 3DS BeginRequest and BeginRequestAsync will push the Request to a worker queue.
// You can only enqueue 8 requests at the same time.
// trying to enqueue any more will either fail (BeginRequestAsync), or block (BeginRequest)
// Note that you only can have 8 Contexts at a time. So this difference shouldn't matter
// Then there are 3? worker threads that pop the requests from the queue and send them
// For now make every request async in it's own thread.
itr->second.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(itr->second));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
@ -201,7 +290,15 @@ void HTTP_C::BeginRequestAsync(Kernel::HLERequestContext& ctx) {
auto itr = contexts.find(context_handle);
ASSERT(itr != contexts.end());
// TODO(B3N30): Make the request
// On a 3DS BeginRequest and BeginRequestAsync will push the Request to a worker queue.
// You can only enqueue 8 requests at the same time.
// trying to enqueue any more will either fail (BeginRequestAsync), or block (BeginRequest)
// Note that you only can have 8 Contexts at a time. So this difference shouldn't matter
// Then there are 3? worker threads that pop the requests from the queue and send them
// For now make every request async in it's own thread.
itr->second.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(itr->second));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
@ -264,7 +361,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
return;
}
contexts.emplace(++context_counter, Context());
contexts.try_emplace(++context_counter);
contexts[context_counter].url = std::move(url);
contexts[context_counter].method = method;
contexts[context_counter].state = RequestState::NotStarted;
@ -311,10 +408,9 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) {
}
// TODO(Subv): What happens if you try to close a context that's currently being used?
ASSERT(itr->second.state == RequestState::NotStarted);
// TODO(Subv): Make sure that only the session that created the context can close it.
// Note that this will block if a request is still in progress
contexts.erase(itr);
session_data->num_http_contexts--;

View file

@ -4,6 +4,7 @@
#pragma once
#include <future>
#include <memory>
#include <string>
#include <unordered_map>
@ -15,6 +16,12 @@
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/weak_ptr.hpp>
#ifdef ENABLE_WEB_SERVICE
#if defined(__ANDROID__)
#include <ifaddrs.h>
#endif
#include <httplib.h>
#endif
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/service.h"
@ -113,8 +120,7 @@ public:
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
Context(Context&& other) = default;
Context& operator=(Context&&) = default;
void MakeRequest();
struct Proxy {
std::string url;
@ -195,14 +201,21 @@ public:
u32 session_id;
std::string url;
RequestMethod method;
RequestState state = RequestState::NotStarted;
boost::optional<Proxy> proxy;
boost::optional<BasicAuth> basic_auth;
std::atomic<RequestState> state = RequestState::NotStarted;
std::optional<Proxy> proxy;
std::optional<BasicAuth> basic_auth;
SSLConfig ssl_config{};
u32 socket_buffer_size;
std::vector<RequestHeader> headers;
std::vector<PostData> post_data;
std::future<void> request_future;
std::atomic<u64> current_download_size_bytes;
std::atomic<u64> total_download_size_bytes;
#ifdef ENABLE_WEB_SERVICE
httplib::Response response;
#endif
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
@ -219,6 +232,7 @@ private:
ar& post_data;
}
friend class boost::serialization::access;
};
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {

View file

@ -55,7 +55,7 @@ VAddr CROHelper::SegmentTagToAddress(SegmentTag segment_tag) const {
return 0;
SegmentEntry entry;
GetEntry(memory, segment_tag.segment_index, entry);
GetEntry(system.Memory(), segment_tag.segment_index, entry);
if (segment_tag.offset_into_segment >= entry.size)
return 0;
@ -71,12 +71,12 @@ ResultCode CROHelper::ApplyRelocation(VAddr target_address, RelocationType reloc
break;
case RelocationType::AbsoluteAddress:
case RelocationType::AbsoluteAddress2:
memory.Write32(target_address, symbol_address + addend);
cpu.InvalidateCacheRange(target_address, sizeof(u32));
system.Memory().Write32(target_address, symbol_address + addend);
system.InvalidateCacheRange(target_address, sizeof(u32));
break;
case RelocationType::RelativeAddress:
memory.Write32(target_address, symbol_address + addend - target_future_address);
cpu.InvalidateCacheRange(target_address, sizeof(u32));
system.Memory().Write32(target_address, symbol_address + addend - target_future_address);
system.InvalidateCacheRange(target_address, sizeof(u32));
break;
case RelocationType::ThumbBranch:
case RelocationType::ArmBranch:
@ -98,8 +98,8 @@ ResultCode CROHelper::ClearRelocation(VAddr target_address, RelocationType reloc
case RelocationType::AbsoluteAddress:
case RelocationType::AbsoluteAddress2:
case RelocationType::RelativeAddress:
memory.Write32(target_address, 0);
cpu.InvalidateCacheRange(target_address, sizeof(u32));
system.Memory().Write32(target_address, 0);
system.InvalidateCacheRange(target_address, sizeof(u32));
break;
case RelocationType::ThumbBranch:
case RelocationType::ArmBranch:
@ -121,7 +121,8 @@ ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool
VAddr relocation_address = batch;
while (true) {
RelocationEntry relocation;
memory.ReadBlock(process, relocation_address, &relocation, sizeof(RelocationEntry));
system.Memory().ReadBlock(process, relocation_address, &relocation,
sizeof(RelocationEntry));
VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
if (relocation_target == 0) {
@ -142,9 +143,9 @@ ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool
}
RelocationEntry relocation;
memory.ReadBlock(process, batch, &relocation, sizeof(RelocationEntry));
system.Memory().ReadBlock(process, batch, &relocation, sizeof(RelocationEntry));
relocation.is_batch_resolved = reset ? 0 : 1;
memory.WriteBlock(process, batch, &relocation, sizeof(RelocationEntry));
system.Memory().WriteBlock(process, batch, &relocation, sizeof(RelocationEntry));
return RESULT_SUCCESS;
}
@ -154,13 +155,13 @@ VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const {
std::size_t len = name.size();
ExportTreeEntry entry;
GetEntry(memory, 0, entry);
GetEntry(system.Memory(), 0, entry);
ExportTreeEntry::Child next;
next.raw = entry.left.raw;
u32 found_id;
while (true) {
GetEntry(memory, next.next_index, entry);
GetEntry(system.Memory(), next.next_index, entry);
if (next.is_end) {
found_id = entry.export_table_index;
@ -186,9 +187,9 @@ VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const {
u32 export_strings_size = GetField(ExportStringsSize);
ExportNamedSymbolEntry symbol_entry;
GetEntry(memory, found_id, symbol_entry);
GetEntry(system.Memory(), found_id, symbol_entry);
if (memory.ReadCString(symbol_entry.name_offset, export_strings_size) != name)
if (system.Memory().ReadCString(symbol_entry.name_offset, export_strings_size) != name)
return 0;
return SegmentTagToAddress(symbol_entry.symbol_position);
@ -279,7 +280,7 @@ ResultVal<VAddr> CROHelper::RebaseSegmentTable(u32 cro_size, VAddr data_segment_
u32 segment_num = GetField(SegmentNum);
for (u32 i = 0; i < segment_num; ++i) {
SegmentEntry segment;
GetEntry(memory, i, segment);
GetEntry(system.Memory(), i, segment);
if (segment.type == SegmentType::Data) {
if (segment.size != 0) {
if (segment.size > data_segment_size)
@ -298,7 +299,7 @@ ResultVal<VAddr> CROHelper::RebaseSegmentTable(u32 cro_size, VAddr data_segment_
if (segment.offset > module_address + cro_size)
return CROFormatError(0x19);
}
SetEntry(memory, i, segment);
SetEntry(system.Memory(), i, segment);
}
return MakeResult<u32>(prev_data_segment + module_address);
}
@ -310,7 +311,7 @@ ResultCode CROHelper::RebaseExportNamedSymbolTable() {
u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);
for (u32 i = 0; i < export_named_symbol_num; ++i) {
ExportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset += module_address;
@ -320,7 +321,7 @@ ResultCode CROHelper::RebaseExportNamedSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -329,7 +330,7 @@ ResultCode CROHelper::VerifyExportTreeTable() const {
u32 tree_num = GetField(ExportTreeNum);
for (u32 i = 0; i < tree_num; ++i) {
ExportTreeEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) {
return CROFormatError(0x11);
@ -353,7 +354,7 @@ ResultCode CROHelper::RebaseImportModuleTable() {
u32 module_num = GetField(ImportModuleNum);
for (u32 i = 0; i < module_num; ++i) {
ImportModuleEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset += module_address;
@ -379,7 +380,7 @@ ResultCode CROHelper::RebaseImportModuleTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -395,7 +396,7 @@ ResultCode CROHelper::RebaseImportNamedSymbolTable() {
u32 num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset += module_address;
@ -413,7 +414,7 @@ ResultCode CROHelper::RebaseImportNamedSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -427,7 +428,7 @@ ResultCode CROHelper::RebaseImportIndexedSymbolTable() {
u32 num = GetField(ImportIndexedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportIndexedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset += module_address;
@ -437,7 +438,7 @@ ResultCode CROHelper::RebaseImportIndexedSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -451,7 +452,7 @@ ResultCode CROHelper::RebaseImportAnonymousSymbolTable() {
u32 num = GetField(ImportAnonymousSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset += module_address;
@ -461,7 +462,7 @@ ResultCode CROHelper::RebaseImportAnonymousSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -476,14 +477,14 @@ ResultCode CROHelper::ResetExternalRelocations() {
ExternalRelocationEntry relocation;
// Verifies that the last relocation is the end of a batch
GetEntry(memory, external_relocation_num - 1, relocation);
GetEntry(system.Memory(), external_relocation_num - 1, relocation);
if (!relocation.is_batch_end) {
return CROFormatError(0x12);
}
bool batch_begin = true;
for (u32 i = 0; i < external_relocation_num; ++i) {
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
if (relocation_target == 0) {
@ -500,7 +501,7 @@ ResultCode CROHelper::ResetExternalRelocations() {
if (batch_begin) {
// resets to unresolved state
relocation.is_batch_resolved = 0;
SetEntry(memory, i, relocation);
SetEntry(system.Memory(), i, relocation);
}
// if current is an end, then the next is a beginning
@ -516,7 +517,7 @@ ResultCode CROHelper::ClearExternalRelocations() {
bool batch_begin = true;
for (u32 i = 0; i < external_relocation_num; ++i) {
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
if (relocation_target == 0) {
@ -532,7 +533,7 @@ ResultCode CROHelper::ClearExternalRelocations() {
if (batch_begin) {
// resets to unresolved state
relocation.is_batch_resolved = 0;
SetEntry(memory, i, relocation);
SetEntry(system.Memory(), i, relocation);
}
// if current is an end, then the next is a beginning
@ -548,13 +549,13 @@ ResultCode CROHelper::ApplyStaticAnonymousSymbolToCRS(VAddr crs_address) {
static_relocation_table_offset +
GetField(StaticRelocationNum) * sizeof(StaticRelocationEntry);
CROHelper crs(crs_address, process, memory, cpu);
CROHelper crs(crs_address, process, system);
u32 offset_export_num = GetField(StaticAnonymousSymbolNum);
LOG_INFO(Service_LDR, "CRO \"{}\" exports {} static anonymous symbols", ModuleName(),
offset_export_num);
for (u32 i = 0; i < offset_export_num; ++i) {
StaticAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
u32 batch_address = entry.relocation_batch_offset + module_address;
if (batch_address < static_relocation_table_offset ||
@ -579,7 +580,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
u32 internal_relocation_num = GetField(InternalRelocationNum);
for (u32 i = 0; i < internal_relocation_num; ++i) {
InternalRelocationEntry relocation;
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr target_addressB = SegmentTagToAddress(relocation.target_position);
if (target_addressB == 0) {
return CROFormatError(0x15);
@ -587,7 +588,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
VAddr target_address;
SegmentEntry target_segment;
GetEntry(memory, relocation.target_position.segment_index, target_segment);
GetEntry(system.Memory(), relocation.target_position.segment_index, target_segment);
if (target_segment.type == SegmentType::Data) {
// If the relocation is to the .data segment, we need to relocate it in the old buffer
@ -602,7 +603,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
}
SegmentEntry symbol_segment;
GetEntry(memory, relocation.symbol_segment, symbol_segment);
GetEntry(system.Memory(), relocation.symbol_segment, symbol_segment);
LOG_TRACE(Service_LDR, "Internally relocates 0x{:08X} with 0x{:08X}", target_address,
symbol_segment.offset);
ResultCode result = ApplyRelocation(target_address, relocation.type, relocation.addend,
@ -619,7 +620,7 @@ ResultCode CROHelper::ClearInternalRelocations() {
u32 internal_relocation_num = GetField(InternalRelocationNum);
for (u32 i = 0; i < internal_relocation_num; ++i) {
InternalRelocationEntry relocation;
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr target_address = SegmentTagToAddress(relocation.target_position);
if (target_address == 0) {
@ -639,13 +640,13 @@ void CROHelper::UnrebaseImportAnonymousSymbolTable() {
u32 num = GetField(ImportAnonymousSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -653,13 +654,13 @@ void CROHelper::UnrebaseImportIndexedSymbolTable() {
u32 num = GetField(ImportIndexedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportIndexedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -667,7 +668,7 @@ void CROHelper::UnrebaseImportNamedSymbolTable() {
u32 num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset -= module_address;
@ -677,7 +678,7 @@ void CROHelper::UnrebaseImportNamedSymbolTable() {
entry.relocation_batch_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -685,7 +686,7 @@ void CROHelper::UnrebaseImportModuleTable() {
u32 module_num = GetField(ImportModuleNum);
for (u32 i = 0; i < module_num; ++i) {
ImportModuleEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset -= module_address;
@ -699,7 +700,7 @@ void CROHelper::UnrebaseImportModuleTable() {
entry.import_anonymous_symbol_table_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -707,13 +708,13 @@ void CROHelper::UnrebaseExportNamedSymbolTable() {
u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);
for (u32 i = 0; i < export_named_symbol_num; ++i) {
ExportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -721,7 +722,7 @@ void CROHelper::UnrebaseSegmentTable() {
u32 segment_num = GetField(SegmentNum);
for (u32 i = 0; i < segment_num; ++i) {
SegmentEntry segment;
GetEntry(memory, i, segment);
GetEntry(system.Memory(), i, segment);
if (segment.type == SegmentType::BSS) {
segment.offset = 0;
@ -729,7 +730,7 @@ void CROHelper::UnrebaseSegmentTable() {
segment.offset -= module_address;
}
SetEntry(memory, i, segment);
SetEntry(system.Memory(), i, segment);
}
}
@ -751,17 +752,17 @@ ResultCode CROHelper::ApplyImportNamedSymbol(VAddr crs_address) {
u32 symbol_import_num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (!relocation_entry.is_batch_resolved) {
ResultCode result = ForEachAutoLinkCRO(
process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> {
process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> {
std::string symbol_name =
memory.ReadCString(entry.name_offset, import_strings_size);
system.Memory().ReadCString(entry.name_offset, import_strings_size);
u32 symbol_address = source.FindExportNamedSymbol(symbol_name);
if (symbol_address != 0) {
@ -794,11 +795,11 @@ ResultCode CROHelper::ResetImportNamedSymbol() {
u32 symbol_import_num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
if (result.IsError()) {
@ -815,11 +816,11 @@ ResultCode CROHelper::ResetImportIndexedSymbol() {
u32 import_num = GetField(ImportIndexedSymbolNum);
for (u32 i = 0; i < import_num; ++i) {
ImportIndexedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
if (result.IsError()) {
@ -836,11 +837,11 @@ ResultCode CROHelper::ResetImportAnonymousSymbol() {
u32 import_num = GetField(ImportAnonymousSymbolNum);
for (u32 i = 0; i < import_num; ++i) {
ImportAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
if (result.IsError()) {
@ -857,19 +858,20 @@ ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) {
u32 import_module_num = GetField(ImportModuleNum);
for (u32 i = 0; i < import_module_num; ++i) {
ImportModuleEntry entry;
GetEntry(memory, i, entry);
std::string want_cro_name = memory.ReadCString(entry.name_offset, import_strings_size);
GetEntry(system.Memory(), i, entry);
std::string want_cro_name =
system.Memory().ReadCString(entry.name_offset, import_strings_size);
ResultCode result = ForEachAutoLinkCRO(
process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> {
process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> {
if (want_cro_name == source.ModuleName()) {
LOG_INFO(Service_LDR, "CRO \"{}\" imports {} indexed symbols from \"{}\"",
ModuleName(), entry.import_indexed_symbol_num, source.ModuleName());
for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
ImportIndexedSymbolEntry im;
entry.GetImportIndexedSymbolEntry(process, memory, j, im);
entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im);
ExportIndexedSymbolEntry ex;
source.GetEntry(memory, im.index, ex);
source.GetEntry(system.Memory(), im.index, ex);
u32 symbol_address = source.SegmentTagToAddress(ex.symbol_position);
LOG_TRACE(Service_LDR, " Imports 0x{:08X}", symbol_address);
ResultCode result =
@ -884,7 +886,7 @@ ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) {
ModuleName(), entry.import_anonymous_symbol_num, source.ModuleName());
for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
ImportAnonymousSymbolEntry im;
entry.GetImportAnonymousSymbolEntry(process, memory, j, im);
entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im);
u32 symbol_address = source.SegmentTagToAddress(im.symbol_position);
LOG_TRACE(Service_LDR, " Imports 0x{:08X}", symbol_address);
ResultCode result =
@ -913,15 +915,15 @@ ResultCode CROHelper::ApplyExportNamedSymbol(CROHelper target) {
u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < target_symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (!relocation_entry.is_batch_resolved) {
std::string symbol_name =
memory.ReadCString(entry.name_offset, target_import_strings_size);
system.Memory().ReadCString(entry.name_offset, target_import_strings_size);
u32 symbol_address = FindExportNamedSymbol(symbol_name);
if (symbol_address != 0) {
LOG_TRACE(Service_LDR, " exports symbol \"{}\"", symbol_name);
@ -944,15 +946,15 @@ ResultCode CROHelper::ResetExportNamedSymbol(CROHelper target) {
u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < target_symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (relocation_entry.is_batch_resolved) {
std::string symbol_name =
memory.ReadCString(entry.name_offset, target_import_strings_size);
system.Memory().ReadCString(entry.name_offset, target_import_strings_size);
u32 symbol_address = FindExportNamedSymbol(symbol_name);
if (symbol_address != 0) {
LOG_TRACE(Service_LDR, " unexports symbol \"{}\"", symbol_name);
@ -974,18 +976,19 @@ ResultCode CROHelper::ApplyModuleExport(CROHelper target) {
u32 target_import_module_num = target.GetField(ImportModuleNum);
for (u32 i = 0; i < target_import_module_num; ++i) {
ImportModuleEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
if (memory.ReadCString(entry.name_offset, target_import_string_size) != module_name)
if (system.Memory().ReadCString(entry.name_offset, target_import_string_size) !=
module_name)
continue;
LOG_INFO(Service_LDR, "CRO \"{}\" exports {} indexed symbols to \"{}\"", module_name,
entry.import_indexed_symbol_num, target.ModuleName());
for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
ImportIndexedSymbolEntry im;
entry.GetImportIndexedSymbolEntry(process, memory, j, im);
entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im);
ExportIndexedSymbolEntry ex;
GetEntry(memory, im.index, ex);
GetEntry(system.Memory(), im.index, ex);
u32 symbol_address = SegmentTagToAddress(ex.symbol_position);
LOG_TRACE(Service_LDR, " exports symbol 0x{:08X}", symbol_address);
ResultCode result =
@ -1000,7 +1003,7 @@ ResultCode CROHelper::ApplyModuleExport(CROHelper target) {
entry.import_anonymous_symbol_num, target.ModuleName());
for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
ImportAnonymousSymbolEntry im;
entry.GetImportAnonymousSymbolEntry(process, memory, j, im);
entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im);
u32 symbol_address = SegmentTagToAddress(im.symbol_position);
LOG_TRACE(Service_LDR, " exports symbol 0x{:08X}", symbol_address);
ResultCode result =
@ -1023,16 +1026,17 @@ ResultCode CROHelper::ResetModuleExport(CROHelper target) {
u32 target_import_module_num = target.GetField(ImportModuleNum);
for (u32 i = 0; i < target_import_module_num; ++i) {
ImportModuleEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
if (memory.ReadCString(entry.name_offset, target_import_string_size) != module_name)
if (system.Memory().ReadCString(entry.name_offset, target_import_string_size) !=
module_name)
continue;
LOG_DEBUG(Service_LDR, "CRO \"{}\" unexports indexed symbols to \"{}\"", module_name,
target.ModuleName());
for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
ImportIndexedSymbolEntry im;
entry.GetImportIndexedSymbolEntry(process, memory, j, im);
entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im);
ResultCode result =
target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true);
if (result.IsError()) {
@ -1045,7 +1049,7 @@ ResultCode CROHelper::ResetModuleExport(CROHelper target) {
target.ModuleName());
for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
ImportAnonymousSymbolEntry im;
entry.GetImportAnonymousSymbolEntry(process, memory, j, im);
entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im);
ResultCode result =
target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true);
if (result.IsError()) {
@ -1063,15 +1067,16 @@ ResultCode CROHelper::ApplyExitRelocations(VAddr crs_address) {
u32 symbol_import_num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (memory.ReadCString(entry.name_offset, import_strings_size) == "__aeabi_atexit") {
if (system.Memory().ReadCString(entry.name_offset, import_strings_size) ==
"__aeabi_atexit") {
ResultCode result = ForEachAutoLinkCRO(
process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> {
process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> {
u32 symbol_address = source.FindExportNamedSymbol("nnroAeabiAtexit_");
if (symbol_address != 0) {
@ -1126,7 +1131,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment
return result;
}
result = VerifyStringTableLength(memory, GetField(ModuleNameOffset), GetField(ModuleNameSize));
result = VerifyStringTableLength(system.Memory(), GetField(ModuleNameOffset),
GetField(ModuleNameSize));
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying module name {:08X}", result.raw);
return result;
@ -1155,8 +1161,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment
return result;
}
result =
VerifyStringTableLength(memory, GetField(ExportStringsOffset), GetField(ExportStringsSize));
result = VerifyStringTableLength(system.Memory(), GetField(ExportStringsOffset),
GetField(ExportStringsSize));
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying export strings {:08X}", result.raw);
return result;
@ -1192,8 +1198,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment
return result;
}
result =
VerifyStringTableLength(memory, GetField(ImportStringsOffset), GetField(ImportStringsSize));
result = VerifyStringTableLength(system.Memory(), GetField(ImportStringsOffset),
GetField(ImportStringsSize));
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying import strings {:08X}", result.raw);
return result;
@ -1266,11 +1272,11 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
// so we do the same
if (GetField(SegmentNum) >= 2) { // means we have .data segment
SegmentEntry entry;
GetEntry(memory, 2, entry);
GetEntry(system.Memory(), 2, entry);
ASSERT(entry.type == SegmentType::Data);
data_segment_address = entry.offset;
entry.offset = GetField(DataOffset);
SetEntry(memory, 2, entry);
SetEntry(system.Memory(), 2, entry);
}
}
SCOPE_EXIT({
@ -1278,9 +1284,9 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
if (link_on_load_bug_fix) {
if (GetField(SegmentNum) >= 2) {
SegmentEntry entry;
GetEntry(memory, 2, entry);
GetEntry(system.Memory(), 2, entry);
entry.offset = data_segment_address;
SetEntry(memory, 2, entry);
SetEntry(system.Memory(), 2, entry);
}
}
});
@ -1301,7 +1307,7 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
}
// Exports symbols to other modules
result = ForEachAutoLinkCRO(process, memory, cpu, crs_address,
result = ForEachAutoLinkCRO(process, system, crs_address,
[this](CROHelper target) -> ResultVal<bool> {
ResultCode result = ApplyExportNamedSymbol(target);
if (result.IsError())
@ -1346,7 +1352,7 @@ ResultCode CROHelper::Unlink(VAddr crs_address) {
// Resets all symbols in other modules imported from this module
// Note: the RO service seems only searching in auto-link modules
result = ForEachAutoLinkCRO(process, memory, cpu, crs_address,
result = ForEachAutoLinkCRO(process, system, crs_address,
[this](CROHelper target) -> ResultVal<bool> {
ResultCode result = ResetExportNamedSymbol(target);
if (result.IsError())
@ -1387,13 +1393,13 @@ void CROHelper::InitCRS() {
}
void CROHelper::Register(VAddr crs_address, bool auto_link) {
CROHelper crs(crs_address, process, memory, cpu);
CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule(), process, memory, cpu);
CROHelper crs(crs_address, process, system);
CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule(), process, system);
if (head.module_address) {
// there are already CROs registered
// register as the new tail
CROHelper tail(head.PreviousModule(), process, memory, cpu);
CROHelper tail(head.PreviousModule(), process, system);
// link with the old tail
ASSERT(tail.NextModule() == 0);
@ -1419,11 +1425,11 @@ void CROHelper::Register(VAddr crs_address, bool auto_link) {
}
void CROHelper::Unregister(VAddr crs_address) {
CROHelper crs(crs_address, process, memory, cpu);
CROHelper next_head(crs.NextModule(), process, memory, cpu);
CROHelper previous_head(crs.PreviousModule(), process, memory, cpu);
CROHelper next(NextModule(), process, memory, cpu);
CROHelper previous(PreviousModule(), process, memory, cpu);
CROHelper crs(crs_address, process, system);
CROHelper next_head(crs.NextModule(), process, system);
CROHelper previous_head(crs.PreviousModule(), process, system);
CROHelper next(NextModule(), process, system);
CROHelper previous(PreviousModule(), process, system);
if (module_address == next_head.module_address ||
module_address == previous_head.module_address) {
@ -1517,7 +1523,7 @@ std::tuple<VAddr, u32> CROHelper::GetExecutablePages() const {
u32 segment_num = GetField(SegmentNum);
for (u32 i = 0; i < segment_num; ++i) {
SegmentEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.type == SegmentType::Code && entry.size != 0) {
VAddr begin = Common::AlignDown(entry.offset, Memory::PAGE_SIZE);
VAddr end = Common::AlignUp(entry.offset + entry.size, Memory::PAGE_SIZE);

View file

@ -33,12 +33,11 @@ static constexpr u32 CRO_HASH_SIZE = 0x80;
class CROHelper final {
public:
// TODO (wwylele): pass in the process handle for memory access
explicit CROHelper(VAddr cro_address, Kernel::Process& process, Memory::MemorySystem& memory,
ARM_Interface& cpu)
: module_address(cro_address), process(process), memory(memory), cpu(cpu) {}
explicit CROHelper(VAddr cro_address, Kernel::Process& process, Core::System& system)
: module_address(cro_address), process(process), system(system) {}
std::string ModuleName() const {
return memory.ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
return system.Memory().ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
}
u32 GetFileSize() const {
@ -144,8 +143,7 @@ public:
private:
const VAddr module_address; ///< the virtual address of this module
Kernel::Process& process; ///< the owner process of this module
Memory::MemorySystem& memory;
ARM_Interface& cpu;
Core::System& system;
/**
* Each item in this enum represents a u32 field in the header begin from address+0x80,
@ -403,11 +401,11 @@ private:
}
u32 GetField(HeaderField field) const {
return memory.Read32(Field(field));
return system.Memory().Read32(Field(field));
}
void SetField(HeaderField field, u32 value) {
memory.Write32(Field(field), value);
system.Memory().Write32(Field(field), value);
}
/**
@ -474,12 +472,11 @@ private:
* otherwise error code of the last iteration.
*/
template <typename FunctionObject>
static ResultCode ForEachAutoLinkCRO(Kernel::Process& process, Memory::MemorySystem& memory,
ARM_Interface& cpu, VAddr crs_address,
FunctionObject func) {
static ResultCode ForEachAutoLinkCRO(Kernel::Process& process, Core::System& system,
VAddr crs_address, FunctionObject func) {
VAddr current = crs_address;
while (current != 0) {
CROHelper cro(current, process, memory, cpu);
CROHelper cro(current, process, system);
CASCADE_RESULT(bool next, func(cro));
if (!next)
break;

View file

@ -120,7 +120,7 @@ void RO::Initialize(Kernel::HLERequestContext& ctx) {
return;
}
CROHelper crs(crs_address, *process, system.Memory(), system.CPU());
CROHelper crs(crs_address, *process, system);
crs.InitCRS();
result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true);
@ -254,7 +254,7 @@ void RO::LoadCRO(Kernel::HLERequestContext& ctx, bool link_on_load_bug_fix) {
return;
}
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
result = cro.VerifyHash(cro_size, crr_address);
if (result.IsError()) {
@ -318,7 +318,7 @@ void RO::LoadCRO(Kernel::HLERequestContext& ctx, bool link_on_load_bug_fix) {
}
}
system.CPU().InvalidateCacheRange(cro_address, cro_size);
system.InvalidateCacheRange(cro_address, cro_size);
LOG_INFO(Service_LDR, "CRO \"{}\" loaded at 0x{:08X}, fixed_end=0x{:08X}", cro.ModuleName(),
cro_address, cro_address + fix_size);
@ -336,7 +336,7 @@ void RO::UnloadCRO(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}, zero={}, cro_buffer_ptr=0x{:08X}",
cro_address, zero, cro_buffer_ptr);
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -391,7 +391,7 @@ void RO::UnloadCRO(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_LDR, "Error unmapping CRO {:08X}", result.raw);
}
system.CPU().InvalidateCacheRange(cro_address, fixed_size);
system.InvalidateCacheRange(cro_address, fixed_size);
rb.Push(result);
}
@ -403,7 +403,7 @@ void RO::LinkCRO(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}", cro_address);
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -443,7 +443,7 @@ void RO::UnlinkCRO(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}", cro_address);
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -492,7 +492,7 @@ void RO::Shutdown(Kernel::HLERequestContext& ctx) {
return;
}
CROHelper crs(slot->loaded_crs, *process, system.Memory(), system.CPU());
CROHelper crs(slot->loaded_crs, *process, system);
crs.Unrebase(true);
ResultCode result = RESULT_SUCCESS;

View file

@ -402,8 +402,8 @@ inline void Write(u32 addr, const T data) {
switch (index) {
// Memory fills are triggered once the fill value is written.
case GPU_REG_INDEX_WORKAROUND(memory_fill_config[0].trigger, 0x00004 + 0x3):
case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].trigger, 0x00008 + 0x3): {
case GPU_REG_INDEX(memory_fill_config[0].trigger):
case GPU_REG_INDEX(memory_fill_config[1].trigger): {
const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].trigger));
auto& config = g_regs.memory_fill_config[is_second_filler];

View file

@ -22,41 +22,15 @@ namespace GPU {
constexpr float SCREEN_REFRESH_RATE = 60;
// Returns index corresponding to the Regs member labeled by field_name
// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions
// when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])).
// For details cf.
// https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members
// Hopefully, this will be fixed sometime in the future.
// For lack of better alternatives, we currently hardcode the offsets when constant
// expressions are needed via GPU_REG_INDEX_WORKAROUND (on sane compilers, static_asserts
// will then make sure the offsets indeed match the automatically calculated ones).
#define GPU_REG_INDEX(field_name) (offsetof(GPU::Regs, field_name) / sizeof(u32))
#if defined(_MSC_VER)
#define GPU_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index)
#else
// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler
// really is this annoying. This macro just forwards its first argument to GPU_REG_INDEX
// and then performs a (no-op) cast to std::size_t iff the second argument matches the
// expected field offset. Otherwise, the compiler will fail to compile this code.
#define GPU_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \
((typename std::enable_if<backup_workaround_index == GPU_REG_INDEX(field_name), \
std::size_t>::type) GPU_REG_INDEX(field_name))
#endif
// MMIO region 0x1EFxxxxx
struct Regs {
// helper macro to make sure the defined structures are of the expected size.
#if defined(_MSC_VER)
// TODO: MSVC does not support using sizeof() on non-static data members even though this
// is technically allowed since C++11. This macro should be enabled once MSVC adds
// support for that.
#define ASSERT_MEMBER_SIZE(name, size_in_bytes)
#else
#define ASSERT_MEMBER_SIZE(name, size_in_bytes) \
static_assert(sizeof(name) == size_in_bytes, \
"Structure size and register block length don't match")
#endif
// Components are laid out in reverse byte order, most significant bits first.
enum class PixelFormat : u32 {
@ -307,10 +281,6 @@ private:
};
static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout");
// TODO: MSVC does not support using offsetof() on non-static data members even though this
// is technically allowed since C++11. This macro should be enabled once MSVC adds
// support for that.
#ifndef _MSC_VER
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
@ -323,7 +293,6 @@ ASSERT_REG_POSITION(display_transfer_config, 0x00300);
ASSERT_REG_POSITION(command_processor_config, 0x00638);
#undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER)
// The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value
// anyway.

View file

@ -309,8 +309,8 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>
if (!romfs_file_inner.IsOpen())
return ResultStatus::Error;
romfs_file = std::make_shared<FileSys::RomFSReader>(std::move(romfs_file_inner),
romfs_offset, romfs_size);
romfs_file = std::make_shared<FileSys::DirectRomFSReader>(std::move(romfs_file_inner),
romfs_offset, romfs_size);
return ResultStatus::Success;
}

View file

@ -105,13 +105,22 @@ public:
* Loads the system mode that this application needs.
* This function defaults to 2 (96MB allocated to the application) if it can't read the
* information.
* @returns A pair with the optional system mode, and and the status.
* @returns A pair with the optional system mode, and the status.
*/
virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
// 96MB allocated to the application.
return std::make_pair(2, ResultStatus::Success);
}
/**
* Loads the N3ds mode that this application uses.
* It defaults to 0 (O3DS default) if it can't read the information.
* @returns A pair with the optional N3ds mode, and the status.
*/
virtual std::pair<std::optional<u8>, ResultStatus> LoadKernelN3dsMode() {
return std::make_pair(0, ResultStatus::Success);
}
/**
* Get whether this application is executable.
* @param out_executable Reference to store the executable flag into.
@ -186,6 +195,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Dump the RomFS of the applciation
* @param target_path The target path to dump to
* @return ResultStatus result of function
*/
virtual ResultStatus DumpRomFS(const std::string& target_path) {
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the update RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
@ -196,6 +214,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Dump the update RomFS of the applciation
* @param target_path The target path to dump to
* @return ResultStatus result of function
*/
virtual ResultStatus DumpUpdateRomFS(const std::string& target_path) {
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the title of the application
* @param title Reference to store the application title into

View file

@ -61,6 +61,19 @@ std::pair<std::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode
ResultStatus::Success);
}
std::pair<std::optional<u8>, ResultStatus> AppLoader_NCCH::LoadKernelN3dsMode() {
if (!is_loaded) {
ResultStatus res = base_ncch.Load();
if (res != ResultStatus::Success) {
return std::make_pair(std::optional<u8>{}, res);
}
}
// Set the system mode as the one from the exheader.
return std::make_pair(overlay_ncch->exheader_header.arm11_system_local_caps.n3ds_mode,
ResultStatus::Success);
}
ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process) {
using Kernel::CodeSet;
@ -254,6 +267,18 @@ ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReade
return ResultStatus::Success;
}
ResultStatus AppLoader_NCCH::DumpRomFS(const std::string& target_path) {
return base_ncch.DumpRomFS(target_path);
}
ResultStatus AppLoader_NCCH::DumpUpdateRomFS(const std::string& target_path) {
u64 program_id;
ReadProgramId(program_id);
update_ncch.OpenFile(
Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, program_id | UPDATE_MASK));
return update_ncch.DumpRomFS(target_path);
}
ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) {
std::vector<u8> data;
Loader::SMDH smdh;

View file

@ -41,6 +41,8 @@ public:
*/
std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() override;
std::pair<std::optional<u8>, ResultStatus> LoadKernelN3dsMode() override;
ResultStatus IsExecutable(bool& out_executable) override;
ResultStatus ReadCode(std::vector<u8>& buffer) override;
@ -59,6 +61,10 @@ public:
ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;
ResultStatus DumpRomFS(const std::string& target_path) override;
ResultStatus DumpUpdateRomFS(const std::string& target_path) override;
ResultStatus ReadTitle(std::string& title) override;
private:

View file

@ -46,7 +46,9 @@ void RPCServer::HandleWriteMemory(Packet& packet, u32 address, const u8* data, u
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), address, data, data_size);
// If the memory happens to be executable code, make sure the changes become visible
Core::CPU().InvalidateCacheRange(address, data_size);
// Is current core correct here?
Core::System::GetInstance().InvalidateCacheRange(address, data_size);
}
packet.SetPacketDataSize(0);
packet.SendReply();