mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-18 03:31:31 +12:00
Merge branch 'master' into dynapand
This commit is contained in:
commit
3a1a612e8b
27 changed files with 318 additions and 212 deletions
|
@ -43,6 +43,7 @@ add_compile_definitions(SDL_MAIN_HANDLED)
|
||||||
|
|
||||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_TEST OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(third_party/SDL2)
|
add_subdirectory(third_party/SDL2)
|
||||||
add_subdirectory(third_party/glad)
|
add_subdirectory(third_party/glad)
|
||||||
add_subdirectory(third_party/toml11)
|
add_subdirectory(third_party/toml11)
|
||||||
|
@ -91,7 +92,7 @@ endif()
|
||||||
|
|
||||||
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/gl_state.cpp src/config.cpp
|
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/gl_state.cpp src/config.cpp
|
||||||
src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp
|
src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp
|
||||||
src/httpserver.cpp
|
src/httpserver.cpp src/stb_image_write.c
|
||||||
)
|
)
|
||||||
set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
||||||
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
||||||
|
|
|
@ -26,7 +26,7 @@ class GPU {
|
||||||
MAKE_LOG_FUNCTION(log, gpuLogger)
|
MAKE_LOG_FUNCTION(log, gpuLogger)
|
||||||
|
|
||||||
static constexpr u32 maxAttribCount = 12; // Up to 12 vertex attributes
|
static constexpr u32 maxAttribCount = 12; // Up to 12 vertex attributes
|
||||||
static constexpr u32 vramSize = 6_MB;
|
static constexpr u32 vramSize = u32(6_MB);
|
||||||
Registers regs; // GPU internal registers
|
Registers regs; // GPU internal registers
|
||||||
std::array<vec4f, 16> currentAttributes; // Vertex attributes before being passed to the shader
|
std::array<vec4f, 16> currentAttributes; // Vertex attributes before being passed to the shader
|
||||||
|
|
||||||
|
|
|
@ -61,12 +61,18 @@ class Emulator {
|
||||||
std::optional<std::filesystem::path> romPath = std::nullopt;
|
std::optional<std::filesystem::path> romPath = std::nullopt;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// Decides whether to reload or not reload the ROM when resetting. We use enum class over a plain bool for clarity.
|
||||||
|
// If NoReload is selected, the emulator will not reload its selected ROM. This is useful for things like booting up the emulator, or resetting to
|
||||||
|
// change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current ROM
|
||||||
|
// and just resets the emu
|
||||||
|
enum class ReloadOption { NoReload, Reload };
|
||||||
|
|
||||||
Emulator();
|
Emulator();
|
||||||
~Emulator();
|
~Emulator();
|
||||||
|
|
||||||
void step();
|
void step();
|
||||||
void render();
|
void render();
|
||||||
void reset();
|
void reset(ReloadOption reload);
|
||||||
void run();
|
void run();
|
||||||
void runFrame();
|
void runFrame();
|
||||||
|
|
||||||
|
|
|
@ -113,9 +113,16 @@ namespace Helpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract bits from an integer-type
|
/// Extract bits from an integer-type
|
||||||
template <usize offset, usize bits, typename T>
|
template <usize offset, usize bits, typename ReturnT, typename ValueT>
|
||||||
static constexpr T getBits(T value) {
|
static constexpr ReturnT getBits(ValueT value) {
|
||||||
return (value >> offset) & ones<T, bits>();
|
static_assert((offset + bits) <= (CHAR_BIT * sizeof(ValueT)), "Invalid bit range");
|
||||||
|
static_assert(bits > 0, "Invalid bit size");
|
||||||
|
return ReturnT(ValueT(value >> offset) & ones<ValueT, bits>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <usize offset, usize bits, typename ValueT>
|
||||||
|
static constexpr ValueT getBits(ValueT value) {
|
||||||
|
return getBits<offset, bits, ValueT, ValueT>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HELPERS_APPLE_CLANG
|
#ifdef HELPERS_APPLE_CLANG
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
@ -16,7 +18,19 @@ struct HttpServer {
|
||||||
std::mutex actionMutex = {};
|
std::mutex actionMutex = {};
|
||||||
u32 pendingKey = 0;
|
u32 pendingKey = 0;
|
||||||
|
|
||||||
|
HttpServer();
|
||||||
|
|
||||||
void startHttpServer();
|
void startHttpServer();
|
||||||
|
std::string status();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::pair<u32, bool>> keyMap;
|
||||||
|
std::array<bool, 12> pressedKeys = {};
|
||||||
|
bool paused = false;
|
||||||
|
|
||||||
|
u32 stringToKey(const std::string& key_name);
|
||||||
|
bool getKeyState(const std::string& key_name);
|
||||||
|
void setKeyState(const std::string& key_name, bool state);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
|
@ -11,20 +11,16 @@ namespace ConfigMem {
|
||||||
AppMemAlloc = 0x1FF80040,
|
AppMemAlloc = 0x1FF80040,
|
||||||
HardwareType = 0x1FF81004,
|
HardwareType = 0x1FF81004,
|
||||||
Datetime0 = 0x1FF81020,
|
Datetime0 = 0x1FF81020,
|
||||||
|
WifiMac = 0x1FF81060,
|
||||||
NetworkState = 0x1FF81067,
|
NetworkState = 0x1FF81067,
|
||||||
LedState3D = 0x1FF81084,
|
LedState3D = 0x1FF81084,
|
||||||
BatteryState = 0x1FF81085,
|
BatteryState = 0x1FF81085,
|
||||||
Unknown1086 = 0x1FF81086,
|
Unknown1086 = 0x1FF81086,
|
||||||
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
|
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Shows what type of hardware we're running on
|
// Shows what type of hardware we're running on
|
||||||
namespace HardwareCodes {
|
namespace HardwareCodes {
|
||||||
enum : u8 {
|
enum : u8 { Product = 1, Devboard = 2, Debugger = 3, Capture = 4 };
|
||||||
Product = 1,
|
|
||||||
Devboard = 2,
|
|
||||||
Debugger = 3,
|
|
||||||
Capture = 4
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
} // namespace ConfigMem
|
||||||
|
|
|
@ -110,7 +110,7 @@ class Memory {
|
||||||
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
||||||
|
|
||||||
std::array<SharedMemoryBlock, 3> sharedMemBlocks = {
|
std::array<SharedMemoryBlock, 3> sharedMemBlocks = {
|
||||||
SharedMemoryBlock(0, _shared_font_len, KernelHandles::FontSharedMemHandle), // Shared memory for the system font
|
SharedMemoryBlock(0, u32(_shared_font_len), KernelHandles::FontSharedMemHandle), // Shared memory for the system font
|
||||||
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
||||||
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory
|
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory
|
||||||
};
|
};
|
||||||
|
@ -121,14 +121,14 @@ public:
|
||||||
static constexpr u32 pageMask = pageSize - 1;
|
static constexpr u32 pageMask = pageSize - 1;
|
||||||
static constexpr u32 totalPageCount = 1 << (32 - pageShift);
|
static constexpr u32 totalPageCount = 1 << (32 - pageShift);
|
||||||
|
|
||||||
static constexpr u32 FCRAM_SIZE = 128_MB;
|
static constexpr u32 FCRAM_SIZE = u32(128_MB);
|
||||||
static constexpr u32 FCRAM_APPLICATION_SIZE = 64_MB;
|
static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB);
|
||||||
static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize;
|
static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize;
|
||||||
static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize;
|
static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize;
|
||||||
|
|
||||||
static constexpr u32 DSP_RAM_SIZE = 512_KB;
|
static constexpr u32 DSP_RAM_SIZE = u32(512_KB);
|
||||||
static constexpr u32 DSP_CODE_MEMORY_OFFSET = 0_KB;
|
static constexpr u32 DSP_CODE_MEMORY_OFFSET = u32(0_KB);
|
||||||
static constexpr u32 DSP_DATA_MEMORY_OFFSET = 256_KB;
|
static constexpr u32 DSP_DATA_MEMORY_OFFSET = u32(256_KB);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages;
|
std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages;
|
||||||
|
@ -141,8 +141,8 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
u16 kernelVersion = 0;
|
u16 kernelVersion = 0;
|
||||||
u32 usedUserMemory = 0_MB; // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
|
u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
|
||||||
u32 usedSystemMemory = 0_MB; // Similar for the SYSTEM range (reserved for the syscore)
|
u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore)
|
||||||
|
|
||||||
Memory(u64& cpuTicks);
|
Memory(u64& cpuTicks);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -34,6 +34,9 @@ class GPUService {
|
||||||
u32 privilegedProcess;
|
u32 privilegedProcess;
|
||||||
std::optional<Handle> interruptEvent;
|
std::optional<Handle> interruptEvent;
|
||||||
|
|
||||||
|
// Number of threads registered via RegisterInterruptRelayQueue
|
||||||
|
u32 gspThreadCount = 0;
|
||||||
|
|
||||||
MAKE_LOG_FUNCTION(log, gspGPULogger)
|
MAKE_LOG_FUNCTION(log, gspGPULogger)
|
||||||
void processCommandBuffer();
|
void processCommandBuffer();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
|
|
||||||
void EmulatorConfig::load(const std::filesystem::path& path) {
|
void EmulatorConfig::load(const std::filesystem::path& path) {
|
||||||
// If the configuration file does not exist, create it and return
|
// If the configuration file does not exist, create it and return
|
||||||
if (!std::filesystem::exists(path)) {
|
std::error_code error;
|
||||||
|
if (!std::filesystem::exists(path, error)) {
|
||||||
save(path);
|
save(path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -38,13 +39,19 @@ void EmulatorConfig::load(const std::filesystem::path& path) {
|
||||||
void EmulatorConfig::save(const std::filesystem::path& path) {
|
void EmulatorConfig::save(const std::filesystem::path& path) {
|
||||||
toml::basic_value<toml::preserve_comments> data;
|
toml::basic_value<toml::preserve_comments> data;
|
||||||
|
|
||||||
if (std::filesystem::exists(path)) {
|
std::error_code error;
|
||||||
|
if (std::filesystem::exists(path, error)) {
|
||||||
try {
|
try {
|
||||||
data = toml::parse<toml::preserve_comments>(path);
|
data = toml::parse<toml::preserve_comments>(path);
|
||||||
} catch (std::exception& ex) {
|
} catch (std::exception& ex) {
|
||||||
Helpers::warn("Got exception trying to save config file. Exception: %s\n", ex.what());
|
Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
Helpers::warn("Filesystem error accessing %s (error: %s)\n", path.string().c_str(), error.message().c_str());
|
||||||
|
}
|
||||||
|
printf("Saving new configuration file %s\n", path.string().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
|
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
|
||||||
|
@ -52,4 +59,4 @@ void EmulatorConfig::save(const std::filesystem::path& path) {
|
||||||
std::ofstream file(path, std::ios::out);
|
std::ofstream file(path, std::ios::out);
|
||||||
file << data;
|
file << data;
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,8 +138,8 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
||||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PathType::RomFS: {
|
case PathType::RomFS: {
|
||||||
const u32 romFSSize = cxi->romFS.size;
|
const u64 romFSSize = cxi->romFS.size;
|
||||||
const u32 romFSOffset = cxi->romFS.offset;
|
const u64 romFSOffset = cxi->romFS.offset;
|
||||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
||||||
}
|
}
|
||||||
|
@ -161,8 +161,8 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u64 i = 0; i < bytesRead; i++) {
|
for (u64 i = 0; i < bytesRead; i++) {
|
||||||
mem.write8(dataPointer + i, data[i]);
|
mem.write8(u32(dataPointer + i), data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytesRead;
|
return u32(bytesRead);
|
||||||
}
|
}
|
|
@ -76,8 +76,8 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
||||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PathType::RomFS: {
|
case PathType::RomFS: {
|
||||||
const u32 romFSSize = cxi->romFS.size;
|
const u64 romFSSize = cxi->romFS.size;
|
||||||
const u32 romFSOffset = cxi->romFS.offset;
|
const u64 romFSOffset = cxi->romFS.offset;
|
||||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,8 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
||||||
}
|
}
|
||||||
|
|
||||||
case PathType::ExeFS: {
|
case PathType::ExeFS: {
|
||||||
const u32 exeFSSize = cxi->exeFS.size;
|
const u64 exeFSSize = cxi->exeFS.size;
|
||||||
const u32 exeFSOffset = cxi->exeFS.offset;
|
const u64 exeFSOffset = cxi->exeFS.offset;
|
||||||
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
|
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
|
||||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
}
|
}
|
||||||
|
@ -110,8 +110,8 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u64 i = 0; i < bytesRead; i++) {
|
for (u64 i = 0; i < bytesRead; i++) {
|
||||||
mem.write8(dataPointer + i, data[i]);
|
mem.write8(u32(dataPointer + i), data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytesRead;
|
return u32(bytesRead);
|
||||||
}
|
}
|
|
@ -96,4 +96,4 @@ void Kernel::readDirectory(u32 messagePointer, Handle directory) {
|
||||||
|
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write32(messagePointer + 8, count);
|
mem.write32(messagePointer + 8, count);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,11 +97,11 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (size_t i = 0; i < bytesRead; i++) {
|
for (size_t i = 0; i < bytesRead; i++) {
|
||||||
mem.write8(dataPointer + i, data[i]);
|
mem.write8(u32(dataPointer + i), data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write32(messagePointer + 8, bytesRead);
|
mem.write32(messagePointer + 8, u32(bytesRead));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -142,7 +142,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
|
||||||
|
|
||||||
std::unique_ptr<u8[]> data(new u8[size]);
|
std::unique_ptr<u8[]> data(new u8[size]);
|
||||||
for (size_t i = 0; i < size; i++) {
|
for (size_t i = 0; i < size; i++) {
|
||||||
data[i] = mem.read8(dataPointer + i);
|
data[i] = mem.read8(u32(dataPointer + i));
|
||||||
}
|
}
|
||||||
|
|
||||||
IOFile f(file->fd);
|
IOFile f(file->fd);
|
||||||
|
@ -153,7 +153,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
|
||||||
Helpers::panic("Kernel::WriteFile failed");
|
Helpers::panic("Kernel::WriteFile failed");
|
||||||
} else {
|
} else {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write32(messagePointer + 8, bytesWritten);
|
mem.write32(messagePointer + 8, u32(bytesWritten));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -286,7 +286,7 @@ int Kernel::wakeupOneThread(u64 waitlist, Handle handle) {
|
||||||
// Get the index of the event in the object's waitlist, write it to r1
|
// Get the index of the event in the object's waitlist, write it to r1
|
||||||
for (size_t i = 0; i < t.waitList.size(); i++) {
|
for (size_t i = 0; i < t.waitList.size(); i++) {
|
||||||
if (t.waitList[i] == handle) {
|
if (t.waitList[i] == handle) {
|
||||||
t.gprs[1] = i;
|
t.gprs[1] = u32(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,7 @@ void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) {
|
||||||
// Get the index of the event in the object's waitlist, write it to r1
|
// Get the index of the event in the object's waitlist, write it to r1
|
||||||
for (size_t i = 0; i < t.waitList.size(); i++) {
|
for (size_t i = 0; i < t.waitList.size(); i++) {
|
||||||
if (t.waitList[i] == handle) {
|
if (t.waitList[i] == handle) {
|
||||||
t.gprs[1] = i;
|
t.gprs[1] = u32(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ u32 CartLZ77::decompressedSize(const u8* buffer, u32 compressedSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CartLZ77::decompress(std::vector<u8>& output, const std::vector<u8>& input) {
|
bool CartLZ77::decompress(std::vector<u8>& output, const std::vector<u8>& input) {
|
||||||
u32 sizeCompressed = input.size() * sizeof(u8);
|
u32 sizeCompressed = u32(input.size() * sizeof(u8));
|
||||||
u32 sizeDecompressed = decompressedSize(input);
|
u32 sizeDecompressed = decompressedSize(input);
|
||||||
output.resize(sizeDecompressed);
|
output.resize(sizeDecompressed);
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
||||||
// It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header
|
// It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header
|
||||||
// This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra
|
// This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra
|
||||||
if (u32(programID) == u32(jumpID) && encrypted) {
|
if (u32(programID) == u32(jumpID) && encrypted) {
|
||||||
printf("NCSD is supposedly ecrypted but not actually encrypted\n");
|
printf("NCSD is supposedly encrypted but not actually encrypted\n");
|
||||||
encrypted = false;
|
encrypted = false;
|
||||||
|
|
||||||
// Cartridge is not actually encrypted, set all of our encryption info structures to nullopt
|
// Cartridge is not actually encrypted, set all of our encryption info structures to nullopt
|
||||||
|
@ -312,4 +312,4 @@ std::pair<bool, std::size_t> NCCH::readFromFile(IOFile& file, const FSInfo &info
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success, bytes};
|
return { success, bytes};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono> // For time since epoch
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
#include "config_mem.hpp"
|
#include "config_mem.hpp"
|
||||||
#include "resource_limits.hpp"
|
#include "resource_limits.hpp"
|
||||||
#include <cassert>
|
|
||||||
#include <chrono> // For time since epoch
|
|
||||||
#include <ctime>
|
|
||||||
|
|
||||||
using namespace KernelMemoryTypes;
|
using namespace KernelMemoryTypes;
|
||||||
|
|
||||||
|
@ -13,15 +15,15 @@ Memory::Memory(u64& cpuTicks) : cpuTicks(cpuTicks) {
|
||||||
|
|
||||||
readTable.resize(totalPageCount, 0);
|
readTable.resize(totalPageCount, 0);
|
||||||
writeTable.resize(totalPageCount, 0);
|
writeTable.resize(totalPageCount, 0);
|
||||||
memoryInfo.reserve(32); // Pre-allocate some room for memory allocation info to avoid dynamic allocs
|
memoryInfo.reserve(32); // Pre-allocate some room for memory allocation info to avoid dynamic allocs
|
||||||
}
|
}
|
||||||
|
|
||||||
void Memory::reset() {
|
void Memory::reset() {
|
||||||
// Unallocate all memory
|
// Unallocate all memory
|
||||||
memoryInfo.clear();
|
memoryInfo.clear();
|
||||||
usedFCRAMPages.reset();
|
usedFCRAMPages.reset();
|
||||||
usedUserMemory = 0_MB;
|
usedUserMemory = u32(0_MB);
|
||||||
usedSystemMemory = 0_MB;
|
usedSystemMemory = u32(0_MB);
|
||||||
|
|
||||||
for (u32 i = 0; i < totalPageCount; i++) {
|
for (u32 i = 0; i < totalPageCount; i++) {
|
||||||
readTable[i] = 0;
|
readTable[i] = 0;
|
||||||
|
@ -35,7 +37,7 @@ void Memory::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 basePaddrForTLS = tlsBaseOpt.value();
|
u32 basePaddrForTLS = tlsBaseOpt.value();
|
||||||
for (int i = 0; i < appResourceLimits.maxThreads; i++) {
|
for (u32 i = 0; i < appResourceLimits.maxThreads; i++) {
|
||||||
u32 vaddr = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize;
|
u32 vaddr = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize;
|
||||||
allocateMemory(vaddr, basePaddrForTLS, VirtualAddrs::TLSSize, true);
|
allocateMemory(vaddr, basePaddrForTLS, VirtualAddrs::TLSSize, true);
|
||||||
basePaddrForTLS += VirtualAddrs::TLSSize;
|
basePaddrForTLS += VirtualAddrs::TLSSize;
|
||||||
|
@ -48,8 +50,8 @@ void Memory::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map DSP RAM as R/W at [0x1FF00000, 0x1FF7FFFF]
|
// Map DSP RAM as R/W at [0x1FF00000, 0x1FF7FFFF]
|
||||||
constexpr u32 dspRamPages = DSP_RAM_SIZE / pageSize; // Number of DSP RAM pages
|
constexpr u32 dspRamPages = DSP_RAM_SIZE / pageSize; // Number of DSP RAM pages
|
||||||
constexpr u32 initialPage = VirtualAddrs::DSPMemStart / pageSize; // First page of DSP RAM in the virtual address space
|
constexpr u32 initialPage = VirtualAddrs::DSPMemStart / pageSize; // First page of DSP RAM in the virtual address space
|
||||||
|
|
||||||
for (u32 i = 0; i < dspRamPages; i++) {
|
for (u32 i = 0; i < dspRamPages; i++) {
|
||||||
auto pointer = uintptr_t(&dspRam[i * pageSize]);
|
auto pointer = uintptr_t(&dspRam[i * pageSize]);
|
||||||
|
@ -67,7 +69,7 @@ bool Memory::allocateMainThreadStack(u32 size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 stackBottom = VirtualAddrs::StackTop - size;
|
const u32 stackBottom = VirtualAddrs::StackTop - size;
|
||||||
std::optional<u32> result = allocateMemory(stackBottom, basePaddr.value(), size, true); // Should never be nullopt
|
std::optional<u32> result = allocateMemory(stackBottom, basePaddr.value(), size, true); // Should never be nullopt
|
||||||
return result.has_value();
|
return result.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,18 +80,17 @@ u8 Memory::read8(u32 vaddr) {
|
||||||
uintptr_t pointer = readTable[page];
|
uintptr_t pointer = readTable[page];
|
||||||
if (pointer != 0) [[likely]] {
|
if (pointer != 0) [[likely]] {
|
||||||
return *(u8*)(pointer + offset);
|
return *(u8*)(pointer + offset);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
switch (vaddr) {
|
switch (vaddr) {
|
||||||
case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars);
|
case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars);
|
||||||
case ConfigMem::EnvInfo: return envInfo;
|
case ConfigMem::EnvInfo: return envInfo;
|
||||||
case ConfigMem::HardwareType: return ConfigMem::HardwareCodes::Product;
|
case ConfigMem::HardwareType: return ConfigMem::HardwareCodes::Product;
|
||||||
case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff);
|
case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff);
|
||||||
case ConfigMem::KernelVersionMajor: return u8(kernelVersion >> 8);
|
case ConfigMem::KernelVersionMajor: return u8(kernelVersion >> 8);
|
||||||
case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now
|
case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now
|
||||||
case ConfigMem::NetworkState: return 2; // Report that we've got an internet connection
|
case ConfigMem::NetworkState: return 2; // Report that we've got an internet connection
|
||||||
case ConfigMem::HeadphonesConnectedMaybe: return 0;
|
case ConfigMem::HeadphonesConnectedMaybe: return 0;
|
||||||
case ConfigMem::Unknown1086: return 1; // It's unknown what this is but some games want it to be 1
|
case ConfigMem::Unknown1086: return 1; // It's unknown what this is but some games want it to be 1
|
||||||
default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr);
|
default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,9 +103,11 @@ u16 Memory::read16(u32 vaddr) {
|
||||||
uintptr_t pointer = readTable[page];
|
uintptr_t pointer = readTable[page];
|
||||||
if (pointer != 0) [[likely]] {
|
if (pointer != 0) [[likely]] {
|
||||||
return *(u16*)(pointer + offset);
|
return *(u16*)(pointer + offset);
|
||||||
}
|
} else {
|
||||||
else {
|
switch (vaddr) {
|
||||||
Helpers::panic("Unimplemented 16-bit read, addr: %08X", vaddr);
|
case ConfigMem::WifiMac + 4: return 0xEEFF; // Wifi MAC: Last 2 bytes of MAC Address
|
||||||
|
default: Helpers::panic("Unimplemented 16-bit read, addr: %08X", vaddr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,18 +120,21 @@ u32 Memory::read32(u32 vaddr) {
|
||||||
return *(u32*)(pointer + offset);
|
return *(u32*)(pointer + offset);
|
||||||
} else {
|
} else {
|
||||||
switch (vaddr) {
|
switch (vaddr) {
|
||||||
case ConfigMem::Datetime0: return u32(timeSince3DSEpoch()); // ms elapsed since Jan 1 1900, bottom 32 bits
|
case ConfigMem::Datetime0: return u32(timeSince3DSEpoch()); // ms elapsed since Jan 1 1900, bottom 32 bits
|
||||||
case ConfigMem::Datetime0 + 4: return u32(timeSince3DSEpoch() >> 32); // top 32 bits
|
case ConfigMem::Datetime0 + 4:
|
||||||
|
return u32(timeSince3DSEpoch() >> 32); // top 32 bits
|
||||||
// Ticks since time was last updated. For now we return the current tick count
|
// Ticks since time was last updated. For now we return the current tick count
|
||||||
case ConfigMem::Datetime0 + 8: return u32(cpuTicks);
|
case ConfigMem::Datetime0 + 8: return u32(cpuTicks);
|
||||||
case ConfigMem::Datetime0 + 12: return u32(cpuTicks >> 32);
|
case ConfigMem::Datetime0 + 12: return u32(cpuTicks >> 32);
|
||||||
case ConfigMem::Datetime0 + 16: return 0xFFB0FF0; // Unknown, set by PTM
|
case ConfigMem::Datetime0 + 16: return 0xFFB0FF0; // Unknown, set by PTM
|
||||||
case ConfigMem::Datetime0 + 20: case ConfigMem::Datetime0 + 24: case ConfigMem::Datetime0 + 28:
|
case ConfigMem::Datetime0 + 20:
|
||||||
return 0; // Set to 0 by PTM
|
case ConfigMem::Datetime0 + 24:
|
||||||
|
case ConfigMem::Datetime0 + 28: return 0; // Set to 0 by PTM
|
||||||
|
|
||||||
case ConfigMem::AppMemAlloc: return appResourceLimits.maxCommit;
|
case ConfigMem::AppMemAlloc: return appResourceLimits.maxCommit;
|
||||||
case ConfigMem::SyscoreVer: return 2;
|
case ConfigMem::SyscoreVer: return 2;
|
||||||
case 0x1FF81000: return 0; // TODO: Figure out what this config mem address does
|
case 0x1FF81000: return 0; // TODO: Figure out what this config mem address does
|
||||||
|
case ConfigMem::WifiMac: return 0xFF07F440; // Wifi MAC: First 4 bytes of MAC Address
|
||||||
default:
|
default:
|
||||||
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
|
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
|
||||||
Helpers::warn("VRAM read!\n");
|
Helpers::warn("VRAM read!\n");
|
||||||
|
@ -154,13 +160,12 @@ void Memory::write8(u32 vaddr, u8 value) {
|
||||||
uintptr_t pointer = writeTable[page];
|
uintptr_t pointer = writeTable[page];
|
||||||
if (pointer != 0) [[likely]] {
|
if (pointer != 0) [[likely]] {
|
||||||
*(u8*)(pointer + offset) = value;
|
*(u8*)(pointer + offset) = value;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// VRAM write
|
// VRAM write
|
||||||
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
|
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
|
||||||
// TODO: Invalidate renderer caches here
|
// TODO: Invalidate renderer caches here
|
||||||
vram[vaddr - VirtualAddrs::VramStart] = value;
|
vram[vaddr - VirtualAddrs::VramStart] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
Helpers::panic("Unimplemented 8-bit write, addr: %08X, val: %02X", vaddr, value);
|
Helpers::panic("Unimplemented 8-bit write, addr: %08X, val: %02X", vaddr, value);
|
||||||
|
@ -219,11 +224,13 @@ void* Memory::getWritePointer(u32 address) {
|
||||||
std::string Memory::readString(u32 address, u32 maxSize) {
|
std::string Memory::readString(u32 address, u32 maxSize) {
|
||||||
std::string string;
|
std::string string;
|
||||||
string.reserve(maxSize);
|
string.reserve(maxSize);
|
||||||
|
|
||||||
for (std::size_t i = 0; i < maxSize; ++i) {
|
for (std::size_t i = 0; i < maxSize; ++i) {
|
||||||
char c = read8(address++);
|
char c = read8(address++);
|
||||||
if (c == '\0')
|
if (c == '\0') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
string.push_back(c);
|
string.push_back(c);
|
||||||
}
|
}
|
||||||
string.shrink_to_fit();
|
string.shrink_to_fit();
|
||||||
|
@ -233,12 +240,9 @@ std::string Memory::readString(u32 address, u32 maxSize) {
|
||||||
|
|
||||||
// Return a pointer to the linear heap vaddr based on the kernel ver, because it needed to be moved
|
// Return a pointer to the linear heap vaddr based on the kernel ver, because it needed to be moved
|
||||||
// thanks to the New 3DS having more FCRAM
|
// thanks to the New 3DS having more FCRAM
|
||||||
u32 Memory::getLinearHeapVaddr() {
|
u32 Memory::getLinearHeapVaddr() { return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; }
|
||||||
return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x,
|
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x, bool adjustAddrs, bool isMap) {
|
||||||
bool adjustAddrs, bool isMap) {
|
|
||||||
// Kernel-allocated memory & size must always be aligned to a page boundary
|
// Kernel-allocated memory & size must always be aligned to a page boundary
|
||||||
// Additionally assert we don't OoM and that we don't try to allocate physical FCRAM past what's available to userland
|
// Additionally assert we don't OoM and that we don't try to allocate physical FCRAM past what's available to userland
|
||||||
// If we're mapping there's no fear of OoM, because we're not really allocating memory, just binding vaddrs to specific paddrs
|
// If we're mapping there's no fear of OoM, because we're not really allocating memory, just binding vaddrs to specific paddrs
|
||||||
|
@ -259,8 +263,9 @@ std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool l
|
||||||
// Non-linear allocation needs special handling
|
// Non-linear allocation needs special handling
|
||||||
if (paddr == 0 && adjustAddrs) {
|
if (paddr == 0 && adjustAddrs) {
|
||||||
std::optional<u32> newPaddr = findPaddr(size);
|
std::optional<u32> newPaddr = findPaddr(size);
|
||||||
if (!newPaddr.has_value())
|
if (!newPaddr.has_value()) {
|
||||||
Helpers::panic("Failed to find paddr");
|
Helpers::panic("Failed to find paddr");
|
||||||
|
}
|
||||||
|
|
||||||
paddr = newPaddr.value();
|
paddr = newPaddr.value();
|
||||||
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
|
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
|
||||||
|
@ -281,12 +286,13 @@ std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMap)
|
if (!isMap) {
|
||||||
usedUserMemory += size;
|
usedUserMemory += size;
|
||||||
|
}
|
||||||
|
|
||||||
// Do linear mapping
|
// Do linear mapping
|
||||||
u32 virtualPage = vaddr >> pageShift;
|
u32 virtualPage = vaddr >> pageShift;
|
||||||
u32 physPage = paddr >> pageShift; // TODO: Special handle when non-linear mapping is necessary
|
u32 physPage = paddr >> pageShift; // TODO: Special handle when non-linear mapping is necessary
|
||||||
for (u32 i = 0; i < neededPageCount; i++) {
|
for (u32 i = 0; i < neededPageCount; i++) {
|
||||||
if (r) {
|
if (r) {
|
||||||
readTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]);
|
readTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]);
|
||||||
|
@ -320,11 +326,10 @@ std::optional<u32> Memory::findPaddr(u32 size) {
|
||||||
u32 counter = 0;
|
u32 counter = 0;
|
||||||
|
|
||||||
for (u32 i = 0; i < FCRAM_APPLICATION_PAGE_COUNT; i++) {
|
for (u32 i = 0; i < FCRAM_APPLICATION_PAGE_COUNT; i++) {
|
||||||
if (usedFCRAMPages[i]) { // Page is occupied already, go to new candidate
|
if (usedFCRAMPages[i]) { // Page is occupied already, go to new candidate
|
||||||
candidatePage = i + 1;
|
candidatePage = i + 1;
|
||||||
counter = 0;
|
counter = 0;
|
||||||
}
|
} else { // The paddr we're testing has 1 more free page
|
||||||
else { // The paddr we're testing has 1 more free page
|
|
||||||
counter++;
|
counter++;
|
||||||
// Check if there's enough free memory to use this page
|
// Check if there's enough free memory to use this page
|
||||||
// We use == instead of >= because some software does 0-byte allocations
|
// We use == instead of >= because some software does 0-byte allocations
|
||||||
|
@ -351,12 +356,12 @@ u32 Memory::allocateSysMemory(u32 size) {
|
||||||
Helpers::panic("Memory::allocateSysMemory: Overflowed OS FCRAM");
|
Helpers::panic("Memory::allocateSysMemory: Overflowed OS FCRAM");
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 pageCount = size / pageSize; // Number of pages that will be used up
|
const u32 pageCount = size / pageSize; // Number of pages that will be used up
|
||||||
const u32 startIndex = sysFCRAMIndex() + usedSystemMemory; // Starting FCRAM index
|
const u32 startIndex = sysFCRAMIndex() + usedSystemMemory; // Starting FCRAM index
|
||||||
const u32 startingPage = startIndex / pageSize;
|
const u32 startingPage = startIndex / pageSize;
|
||||||
|
|
||||||
for (u32 i = 0; i < pageCount; i++) {
|
for (u32 i = 0; i < pageCount; i++) {
|
||||||
if (usedFCRAMPages[startingPage + i]) // Also a theoretically unreachable panic for safety
|
if (usedFCRAMPages[startingPage + i]) // Also a theoretically unreachable panic for safety
|
||||||
Helpers::panic("Memory::reserveMemory: Trying to reserve already reserved memory");
|
Helpers::panic("Memory::reserveMemory: Trying to reserve already reserved memory");
|
||||||
usedFCRAMPages[startingPage + i] = true;
|
usedFCRAMPages[startingPage + i] = true;
|
||||||
}
|
}
|
||||||
|
@ -419,7 +424,7 @@ void Memory::mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size) {
|
||||||
// Should theoretically be unreachable, only here for safety purposes
|
// Should theoretically be unreachable, only here for safety purposes
|
||||||
assert(isAligned(destAddress) && isAligned(sourceAddress) && isAligned(size));
|
assert(isAligned(destAddress) && isAligned(sourceAddress) && isAligned(size));
|
||||||
|
|
||||||
const u32 pageCount = size / pageSize; // How many pages we need to mirror
|
const u32 pageCount = size / pageSize; // How many pages we need to mirror
|
||||||
for (u32 i = 0; i < pageCount; i++) {
|
for (u32 i = 0; i < pageCount; i++) {
|
||||||
// Redo the shift here to "properly" handle wrapping around the address space instead of reading OoB
|
// Redo the shift here to "properly" handle wrapping around the address space instead of reading OoB
|
||||||
const u32 sourcePage = sourceAddress / pageSize;
|
const u32 sourcePage = sourceAddress / pageSize;
|
||||||
|
@ -437,20 +442,20 @@ void Memory::mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size) {
|
||||||
u64 Memory::timeSince3DSEpoch() {
|
u64 Memory::timeSince3DSEpoch() {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
std::time_t rawTime = std::time(nullptr); // Get current UTC time
|
std::time_t rawTime = std::time(nullptr); // Get current UTC time
|
||||||
auto localTime = std::localtime(&rawTime); // Convert to local time
|
auto localTime = std::localtime(&rawTime); // Convert to local time
|
||||||
|
|
||||||
bool daylightSavings = localTime->tm_isdst > 0; // Get if time includes DST
|
bool daylightSavings = localTime->tm_isdst > 0; // Get if time includes DST
|
||||||
localTime = std::gmtime(&rawTime);
|
localTime = std::gmtime(&rawTime);
|
||||||
|
|
||||||
// Use gmtime + mktime to calculate difference between local time and UTC
|
// Use gmtime + mktime to calculate difference between local time and UTC
|
||||||
auto timezoneDifference = rawTime - std::mktime(localTime);
|
auto timezoneDifference = rawTime - std::mktime(localTime);
|
||||||
if (daylightSavings) {
|
if (daylightSavings) {
|
||||||
timezoneDifference += 60ull * 60ull; // Add 1 hour (60 seconds * 60 minutes)
|
timezoneDifference += 60ull * 60ull; // Add 1 hour (60 seconds * 60 minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// seconds between Jan 1 1900 and Jan 1 1970
|
// seconds between Jan 1 1900 and Jan 1 1970
|
||||||
constexpr u64 offset = 2208988800ull;
|
constexpr u64 offset = 2208988800ull;
|
||||||
milliseconds ms = duration_cast<milliseconds>(seconds(rawTime + timezoneDifference + offset));
|
milliseconds ms = duration_cast<milliseconds>(seconds(rawTime + timezoneDifference + offset));
|
||||||
return ms.count();
|
return ms.count();
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,14 +60,14 @@ u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse colour data for 4x4 block
|
// Parse colour data for 4x4 block
|
||||||
const u32 subindices = getBits<0, 16>(colourData);
|
const u32 subindices = getBits<0, 16, u32>(colourData);
|
||||||
const u32 negationFlags = getBits<16, 16>(colourData);
|
const u32 negationFlags = getBits<16, 16, u32>(colourData);
|
||||||
const bool flip = getBit<32>(colourData);
|
const bool flip = getBit<32>(colourData);
|
||||||
const bool diffMode = getBit<33>(colourData);
|
const bool diffMode = getBit<33>(colourData);
|
||||||
|
|
||||||
// Note: index1 is indeed stored on the higher bits, with index2 in the lower bits
|
// Note: index1 is indeed stored on the higher bits, with index2 in the lower bits
|
||||||
const u32 tableIndex1 = getBits<37, 3>(colourData);
|
const u32 tableIndex1 = getBits<37, 3, u32>(colourData);
|
||||||
const u32 tableIndex2 = getBits<34, 3>(colourData);
|
const u32 tableIndex2 = getBits<34, 3, u32>(colourData);
|
||||||
const u32 texelIndex = u * 4 + v; // Index of the texel in the block
|
const u32 texelIndex = u * 4 + v; // Index of the texel in the block
|
||||||
|
|
||||||
if (flip)
|
if (flip)
|
||||||
|
@ -75,14 +75,14 @@ u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) {
|
||||||
|
|
||||||
s32 r, g, b;
|
s32 r, g, b;
|
||||||
if (diffMode) {
|
if (diffMode) {
|
||||||
r = getBits<59, 5>(colourData);
|
r = getBits<59, 5, s32>(colourData);
|
||||||
g = getBits<51, 5>(colourData);
|
g = getBits<51, 5, s32>(colourData);
|
||||||
b = getBits<43, 5>(colourData);
|
b = getBits<43, 5, s32>(colourData);
|
||||||
|
|
||||||
if (u >= 2) {
|
if (u >= 2) {
|
||||||
r += signExtend3To32(getBits<56, 3>(colourData));
|
r += signExtend3To32(getBits<56, 3, u32>(colourData));
|
||||||
g += signExtend3To32(getBits<48, 3>(colourData));
|
g += signExtend3To32(getBits<48, 3, u32>(colourData));
|
||||||
b += signExtend3To32(getBits<40, 3>(colourData));
|
b += signExtend3To32(getBits<40, 3, u32>(colourData));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand from 5 to 8 bits per channel
|
// Expand from 5 to 8 bits per channel
|
||||||
|
@ -91,13 +91,13 @@ u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) {
|
||||||
b = Colour::convert5To8Bit(b);
|
b = Colour::convert5To8Bit(b);
|
||||||
} else {
|
} else {
|
||||||
if (u < 2) {
|
if (u < 2) {
|
||||||
r = getBits<60, 4>(colourData);
|
r = getBits<60, 4, s32>(colourData);
|
||||||
g = getBits<52, 4>(colourData);
|
g = getBits<52, 4, s32>(colourData);
|
||||||
b = getBits<44, 4>(colourData);
|
b = getBits<44, 4, s32>(colourData);
|
||||||
} else {
|
} else {
|
||||||
r = getBits<56, 4>(colourData);
|
r = getBits<56, 4, s32>(colourData);
|
||||||
g = getBits<48, 4>(colourData);
|
g = getBits<48, 4, s32>(colourData);
|
||||||
b = getBits<40, 4>(colourData);
|
b = getBits<40, 4, s32>(colourData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand from 4 to 8 bits per channel
|
// Expand from 4 to 8 bits per channel
|
||||||
|
|
|
@ -888,8 +888,8 @@ void Renderer::drawVertices(PICA::PrimType primType, std::span<const Vertex> ver
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Actually use this
|
// TODO: Actually use this
|
||||||
float viewportWidth = f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0;
|
GLsizei viewportWidth = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0f);
|
||||||
float viewportHeight = f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0;
|
GLsizei viewportHeight = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0f);
|
||||||
OpenGL::setViewport(viewportWidth, viewportHeight);
|
OpenGL::setViewport(viewportWidth, viewportHeight);
|
||||||
|
|
||||||
// Note: The code below must execute after we've bound the colour buffer & its framebuffer
|
// Note: The code below must execute after we've bound the colour buffer & its framebuffer
|
||||||
|
@ -911,7 +911,7 @@ void Renderer::drawVertices(PICA::PrimType primType, std::span<const Vertex> ver
|
||||||
}
|
}
|
||||||
|
|
||||||
vbo.bufferVertsSub(vertices);
|
vbo.bufferVertsSub(vertices);
|
||||||
OpenGL::draw(primitiveTopology, vertices.size());
|
OpenGL::draw(primitiveTopology, GLsizei(vertices.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 topScreenBuffer = 0x1f000000;
|
constexpr u32 topScreenBuffer = 0x1f000000;
|
||||||
|
@ -929,10 +929,10 @@ void Renderer::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 cont
|
||||||
return;
|
return;
|
||||||
log("GPU: Clear buffer\nStart: %08X End: %08X\nValue: %08X Control: %08X\n", startAddress, endAddress, value, control);
|
log("GPU: Clear buffer\nStart: %08X End: %08X\nValue: %08X Control: %08X\n", startAddress, endAddress, value, control);
|
||||||
|
|
||||||
const float r = float(getBits<24, 8>(value)) / 255.0;
|
const float r = float(getBits<24, 8>(value)) / 255.0f;
|
||||||
const float g = float(getBits<16, 8>(value)) / 255.0;
|
const float g = float(getBits<16, 8>(value)) / 255.0f;
|
||||||
const float b = float(getBits<8, 8>(value)) / 255.0;
|
const float b = float(getBits<8, 8>(value)) / 255.0f;
|
||||||
const float a = float(value & 0xff) / 255.0;
|
const float a = float(value & 0xff) / 255.0f;
|
||||||
|
|
||||||
if (startAddress == topScreenBuffer) {
|
if (startAddress == topScreenBuffer) {
|
||||||
log("GPU: Cleared top screen\n");
|
log("GPU: Cleared top screen\n");
|
||||||
|
|
|
@ -119,10 +119,10 @@ u32 Texture::decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data) {
|
||||||
auto ptr = static_cast<const u8*>(data);
|
auto ptr = static_cast<const u8*>(data);
|
||||||
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
|
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
|
||||||
|
|
||||||
u8 alpha = Colour::convert4To8Bit(getBits<0, 4>(texel));
|
u8 alpha = Colour::convert4To8Bit(getBits<0, 4, u8>(texel));
|
||||||
u8 b = Colour::convert4To8Bit(getBits<4, 4>(texel));
|
u8 b = Colour::convert4To8Bit(getBits<4, 4, u8>(texel));
|
||||||
u8 g = Colour::convert4To8Bit(getBits<8, 4>(texel));
|
u8 g = Colour::convert4To8Bit(getBits<8, 4, u8>(texel));
|
||||||
u8 r = Colour::convert4To8Bit(getBits<12, 4>(texel));
|
u8 r = Colour::convert4To8Bit(getBits<12, 4, u8>(texel));
|
||||||
|
|
||||||
return (alpha << 24) | (b << 16) | (g << 8) | r;
|
return (alpha << 24) | (b << 16) | (g << 8) | r;
|
||||||
}
|
}
|
||||||
|
@ -133,9 +133,9 @@ u32 Texture::decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data) {
|
||||||
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
|
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
|
||||||
|
|
||||||
u8 alpha = getBit<0>(texel) ? 0xff : 0;
|
u8 alpha = getBit<0>(texel) ? 0xff : 0;
|
||||||
u8 b = Colour::convert5To8Bit(getBits<1, 5>(texel));
|
u8 b = Colour::convert5To8Bit(getBits<1, 5, u8>(texel));
|
||||||
u8 g = Colour::convert5To8Bit(getBits<6, 5>(texel));
|
u8 g = Colour::convert5To8Bit(getBits<6, 5, u8>(texel));
|
||||||
u8 r = Colour::convert5To8Bit(getBits<11, 5>(texel));
|
u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel));
|
||||||
|
|
||||||
return (alpha << 24) | (b << 16) | (g << 8) | r;
|
return (alpha << 24) | (b << 16) | (g << 8) | r;
|
||||||
}
|
}
|
||||||
|
@ -145,9 +145,9 @@ u32 Texture::decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data) {
|
||||||
auto ptr = static_cast<const u8*>(data);
|
auto ptr = static_cast<const u8*>(data);
|
||||||
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
|
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
|
||||||
|
|
||||||
u8 b = Colour::convert5To8Bit(getBits<0, 5>(texel));
|
u8 b = Colour::convert5To8Bit(getBits<0, 5, u8>(texel));
|
||||||
u8 g = Colour::convert6To8Bit(getBits<5, 6>(texel));
|
u8 g = Colour::convert6To8Bit(getBits<5, 6, u8>(texel));
|
||||||
u8 r = Colour::convert5To8Bit(getBits<11, 5>(texel));
|
u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel));
|
||||||
|
|
||||||
return (0xff << 24) | (b << 16) | (g << 8) | r;
|
return (0xff << 24) | (b << 16) | (g << 8) | r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ std::vector<u8> DSPService::readPipe(u32 pipe, u32 size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8>& data = pipeData[pipe];
|
std::vector<u8>& data = pipeData[pipe];
|
||||||
size = std::min<u32>(size, data.size()); // Clamp size to the maximum available data size
|
size = std::min<u32>(size, u32(data.size())); // Clamp size to the maximum available data size
|
||||||
|
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return {};
|
return {};
|
||||||
|
@ -168,7 +168,7 @@ void DSPService::readPipeIfPossible(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write16(messagePointer + 8, data.size()); // Number of bytes read
|
mem.write16(messagePointer + 8, u16(data.size())); // Number of bytes read
|
||||||
}
|
}
|
||||||
|
|
||||||
void DSPService::recvData(u32 messagePointer) {
|
void DSPService::recvData(u32 messagePointer) {
|
||||||
|
|
|
@ -219,7 +219,7 @@ void FSService::openArchive(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSService::openFile(u32 messagePointer) {
|
void FSService::openFile(u32 messagePointer) {
|
||||||
const Handle archiveHandle = mem.read64(messagePointer + 8);
|
const Handle archiveHandle = Handle(mem.read64(messagePointer + 8));
|
||||||
const u32 filePathType = mem.read32(messagePointer + 16);
|
const u32 filePathType = mem.read32(messagePointer + 16);
|
||||||
const u32 filePathSize = mem.read32(messagePointer + 20);
|
const u32 filePathSize = mem.read32(messagePointer + 20);
|
||||||
const u32 openFlags = mem.read32(messagePointer + 24);
|
const u32 openFlags = mem.read32(messagePointer + 24);
|
||||||
|
@ -342,7 +342,7 @@ void FSService::openFileDirectly(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSService::createFile(u32 messagePointer) {
|
void FSService::createFile(u32 messagePointer) {
|
||||||
const Handle archiveHandle = mem.read64(messagePointer + 8);
|
const Handle archiveHandle = Handle(mem.read64(messagePointer + 8));
|
||||||
const u32 filePathType = mem.read32(messagePointer + 16);
|
const u32 filePathType = mem.read32(messagePointer + 16);
|
||||||
const u32 filePathSize = mem.read32(messagePointer + 20);
|
const u32 filePathSize = mem.read32(messagePointer + 20);
|
||||||
const u32 attributes = mem.read32(messagePointer + 24);
|
const u32 attributes = mem.read32(messagePointer + 24);
|
||||||
|
@ -367,7 +367,7 @@ void FSService::createFile(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSService::deleteFile(u32 messagePointer) {
|
void FSService::deleteFile(u32 messagePointer) {
|
||||||
const Handle archiveHandle = mem.read64(messagePointer + 8);
|
const Handle archiveHandle = Handle(mem.read64(messagePointer + 8));
|
||||||
const u32 filePathType = mem.read32(messagePointer + 16);
|
const u32 filePathType = mem.read32(messagePointer + 16);
|
||||||
const u32 filePathSize = mem.read32(messagePointer + 20);
|
const u32 filePathSize = mem.read32(messagePointer + 20);
|
||||||
const u32 filePathPointer = mem.read32(messagePointer + 28);
|
const u32 filePathPointer = mem.read32(messagePointer + 28);
|
||||||
|
@ -478,7 +478,7 @@ void FSService::formatThisUserSaveData(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSService::controlArchive(u32 messagePointer) {
|
void FSService::controlArchive(u32 messagePointer) {
|
||||||
const Handle archiveHandle = mem.read64(messagePointer + 4);
|
const Handle archiveHandle = Handle(mem.read64(messagePointer + 4));
|
||||||
const u32 action = mem.read32(messagePointer + 12);
|
const u32 action = mem.read32(messagePointer + 12);
|
||||||
const u32 inputSize = mem.read32(messagePointer + 16);
|
const u32 inputSize = mem.read32(messagePointer + 16);
|
||||||
const u32 outputSize = mem.read32(messagePointer + 20);
|
const u32 outputSize = mem.read32(messagePointer + 20);
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace GXCommands {
|
||||||
void GPUService::reset() {
|
void GPUService::reset() {
|
||||||
privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle
|
privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle
|
||||||
interruptEvent = std::nullopt;
|
interruptEvent = std::nullopt;
|
||||||
|
gspThreadCount = 0;
|
||||||
sharedMem = nullptr;
|
sharedMem = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,9 +78,10 @@ void GPUService::acquireRight(u32 messagePointer) {
|
||||||
// How does the shared memory handle thing work?
|
// How does the shared memory handle thing work?
|
||||||
void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
|
void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
|
||||||
// Detect if this function is called a 2nd time because we'll likely need to impl threads properly for the GSP
|
// Detect if this function is called a 2nd time because we'll likely need to impl threads properly for the GSP
|
||||||
static bool beenHere = false;
|
if (gspThreadCount >= 1) {
|
||||||
if (beenHere) Helpers::panic("RegisterInterruptRelayQueue called a second time. Need to implement GSP threads properly");
|
Helpers::panic("RegisterInterruptRelayQueue called a second time. Need to implement GSP threads properly");
|
||||||
beenHere = true;
|
}
|
||||||
|
gspThreadCount += 1;
|
||||||
|
|
||||||
const u32 flags = mem.read32(messagePointer + 4);
|
const u32 flags = mem.read32(messagePointer + 4);
|
||||||
const u32 eventHandle = mem.read32(messagePointer + 12);
|
const u32 eventHandle = mem.read32(messagePointer + 12);
|
||||||
|
|
114
src/emulator.cpp
114
src/emulator.cpp
|
@ -1,5 +1,5 @@
|
||||||
#include "emulator.hpp"
|
#include "emulator.hpp"
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
||||||
#include <stb_image_write.h>
|
#include <stb_image_write.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -53,13 +53,12 @@ Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory
|
||||||
}
|
}
|
||||||
|
|
||||||
config.load(std::filesystem::current_path() / "config.toml");
|
config.load(std::filesystem::current_path() / "config.toml");
|
||||||
|
reset(ReloadOption::NoReload);
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator::~Emulator() { config.save(std::filesystem::current_path() / "config.toml"); }
|
Emulator::~Emulator() { config.save(std::filesystem::current_path() / "config.toml"); }
|
||||||
|
|
||||||
void Emulator::reset() {
|
void Emulator::reset(ReloadOption reload) {
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
gpu.reset();
|
gpu.reset();
|
||||||
memory.reset();
|
memory.reset();
|
||||||
|
@ -70,8 +69,9 @@ void Emulator::reset() {
|
||||||
// Otherwise resetting the kernel or cpu might nuke them
|
// Otherwise resetting the kernel or cpu might nuke them
|
||||||
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
|
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
|
||||||
|
|
||||||
// If a ROM is active and we reset, reload it. This is necessary to set up stack, executable memory, .data/.rodata/.bss all over again
|
// If a ROM is active and we reset, with the reload option enabled then reload it.
|
||||||
if (romType != ROMType::None && romPath.has_value()) {
|
// This is necessary to set up stack, executable memory, .data/.rodata/.bss all over again
|
||||||
|
if (reload == ReloadOption::Reload && romType != ROMType::None && romPath.has_value()) {
|
||||||
bool success = loadROM(romPath.value());
|
bool success = loadROM(romPath.value());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
romType = ROMType::None;
|
romType = ROMType::None;
|
||||||
|
@ -89,18 +89,21 @@ void Emulator::run() {
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
httpServer.startHttpServer();
|
httpServer.startHttpServer();
|
||||||
#endif
|
#endif
|
||||||
while (running) {
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
|
||||||
pollHttpServer();
|
|
||||||
#endif
|
|
||||||
runFrame(); // Run 1 frame of instructions
|
|
||||||
gpu.display(); // Display graphics
|
|
||||||
|
|
||||||
|
while (running) {
|
||||||
ServiceManager& srv = kernel.getServiceManager();
|
ServiceManager& srv = kernel.getServiceManager();
|
||||||
|
|
||||||
// Send VBlank interrupts
|
if (romType != ROMType::None) {
|
||||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
|
pollHttpServer();
|
||||||
|
#endif
|
||||||
|
runFrame(); // Run 1 frame of instructions
|
||||||
|
gpu.display(); // Display graphics
|
||||||
|
|
||||||
|
// Send VBlank interrupts
|
||||||
|
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
|
||||||
|
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
|
@ -113,6 +116,8 @@ void Emulator::run() {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
case SDLK_l: srv.pressKey(Keys::A); break;
|
case SDLK_l: srv.pressKey(Keys::A); break;
|
||||||
case SDLK_k: srv.pressKey(Keys::B); break;
|
case SDLK_k: srv.pressKey(Keys::B); break;
|
||||||
|
@ -153,6 +158,8 @@ void Emulator::run() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
case SDLK_l: srv.releaseKey(Keys::A); break;
|
case SDLK_l: srv.releaseKey(Keys::A); break;
|
||||||
case SDLK_k: srv.releaseKey(Keys::B); break;
|
case SDLK_k: srv.releaseKey(Keys::B); break;
|
||||||
|
@ -185,7 +192,9 @@ void Emulator::run() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_MOUSEBUTTONDOWN: {
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
const s32 x = event.button.x;
|
const s32 x = event.button.x;
|
||||||
const s32 y = event.button.y;
|
const s32 y = event.button.y;
|
||||||
|
@ -203,10 +212,12 @@ void Emulator::run() {
|
||||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
holdingRightClick = true;
|
holdingRightClick = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
srv.releaseTouchScreen();
|
srv.releaseTouchScreen();
|
||||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
|
@ -231,6 +242,7 @@ void Emulator::run() {
|
||||||
|
|
||||||
case SDL_CONTROLLERBUTTONUP:
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
case SDL_CONTROLLERBUTTONDOWN: {
|
case SDL_CONTROLLERBUTTONDOWN: {
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
u32 key = 0;
|
u32 key = 0;
|
||||||
|
|
||||||
switch (event.cbutton.button) {
|
switch (event.cbutton.button) {
|
||||||
|
@ -255,12 +267,13 @@ void Emulator::run() {
|
||||||
srv.releaseKey(key);
|
srv.releaseKey(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect mouse motion events for gyroscope emulation
|
// Detect mouse motion events for gyroscope emulation
|
||||||
case SDL_MOUSEMOTION: {
|
case SDL_MOUSEMOTION: {
|
||||||
// We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation
|
// We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation
|
||||||
if (!holdingRightClick) break;
|
if (romType == ROMType::None || !holdingRightClick) break;
|
||||||
|
|
||||||
// Relative motion since last mouse motion event
|
// Relative motion since last mouse motion event
|
||||||
const s32 motionX = event.motion.xrel;
|
const s32 motionX = event.motion.xrel;
|
||||||
|
@ -274,32 +287,46 @@ void Emulator::run() {
|
||||||
srv.setPitch(pitch);
|
srv.setPitch(pitch);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SDL_DROPFILE: {
|
||||||
|
char* droppedDir = event.drop.file;
|
||||||
|
|
||||||
|
if (droppedDir) {
|
||||||
|
loadROM(droppedDir);
|
||||||
|
SDL_free(droppedDir);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameController != nullptr) {
|
// Update controller analog sticks and HID service
|
||||||
const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
if (romType != ROMType::None) {
|
||||||
const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
if (gameController != nullptr) {
|
||||||
constexpr s16 deadzone = 3276;
|
const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
constexpr s16 maxValue = 0x9C;
|
const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
constexpr s16 div = 0x8000 / maxValue;
|
constexpr s16 deadzone = 3276;
|
||||||
|
constexpr s16 maxValue = 0x9C;
|
||||||
|
constexpr s16 div = 0x8000 / maxValue;
|
||||||
|
|
||||||
// Avoid overriding the keyboard's circlepad input
|
// Avoid overriding the keyboard's circlepad input
|
||||||
if (abs(stickX) < deadzone && !keyboardAnalogX) {
|
if (abs(stickX) < deadzone && !keyboardAnalogX) {
|
||||||
srv.setCirclepadX(0);
|
srv.setCirclepadX(0);
|
||||||
} else {
|
} else {
|
||||||
srv.setCirclepadX(stickX / div);
|
srv.setCirclepadX(stickX / div);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs(stickY) < deadzone && !keyboardAnalogY) {
|
||||||
|
srv.setCirclepadY(0);
|
||||||
|
} else {
|
||||||
|
srv.setCirclepadY(-(stickY / div));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abs(stickY) < deadzone && !keyboardAnalogY) {
|
srv.updateInputs(cpu.getTicks());
|
||||||
srv.setCirclepadY(0);
|
|
||||||
} else {
|
|
||||||
srv.setCirclepadY(-(stickY / div));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update inputs in the HID module
|
// Update inputs in the HID module
|
||||||
srv.updateInputs(cpu.getTicks());
|
|
||||||
SDL_GL_SwapWindow(window);
|
SDL_GL_SwapWindow(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,6 +334,11 @@ void Emulator::run() {
|
||||||
void Emulator::runFrame() { cpu.runFrame(); }
|
void Emulator::runFrame() { cpu.runFrame(); }
|
||||||
|
|
||||||
bool Emulator::loadROM(const std::filesystem::path& path) {
|
bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
|
// Reset the emulator if we've already loaded a ROM
|
||||||
|
if (romType != ROMType::None) {
|
||||||
|
reset(ReloadOption::NoReload);
|
||||||
|
}
|
||||||
|
|
||||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
||||||
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
||||||
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
||||||
|
@ -326,7 +358,7 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
|
|
||||||
kernel.initializeFS();
|
kernel.initializeFS();
|
||||||
auto extension = path.extension();
|
auto extension = path.extension();
|
||||||
bool success; // Tracks if we loaded the ROM successfully
|
bool success; // Tracks if we loaded the ROM successfully
|
||||||
|
|
||||||
if (extension == ".elf" || extension == ".axf")
|
if (extension == ".elf" || extension == ".axf")
|
||||||
success = loadELF(path);
|
success = loadELF(path);
|
||||||
|
@ -390,26 +422,24 @@ bool Emulator::loadELF(std::ifstream& file) {
|
||||||
if (entrypoint.value() & 1) {
|
if (entrypoint.value() & 1) {
|
||||||
Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode");
|
Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset our graphics context and initialize the GPU's graphics context
|
// Reset our graphics context and initialize the GPU's graphics context
|
||||||
void Emulator::initGraphicsContext() {
|
void Emulator::initGraphicsContext() {
|
||||||
gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL
|
gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL
|
||||||
gpu.initGraphicsContext();
|
gpu.initGraphicsContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
void Emulator::pollHttpServer() {
|
void Emulator::pollHttpServer() {
|
||||||
std::scoped_lock lock(httpServer.actionMutex);
|
std::scoped_lock lock(httpServer.actionMutex);
|
||||||
|
|
||||||
ServiceManager& srv = kernel.getServiceManager();
|
ServiceManager& srv = kernel.getServiceManager();
|
||||||
|
|
||||||
if (httpServer.pendingAction) {
|
if (httpServer.pendingAction) {
|
||||||
switch (httpServer.action) {
|
switch (httpServer.action) {
|
||||||
case HttpAction::Screenshot:
|
case HttpAction::Screenshot: gpu.screenshot(HttpServer::httpServerScreenshotPath); break;
|
||||||
gpu.screenshot(HttpServer::httpServerScreenshotPath);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HttpAction::PressKey:
|
case HttpAction::PressKey:
|
||||||
if (httpServer.pendingKey != 0) {
|
if (httpServer.pendingKey != 0) {
|
||||||
|
|
|
@ -2,41 +2,30 @@
|
||||||
#include "httpserver.hpp"
|
#include "httpserver.hpp"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "httplib.h"
|
#include "httplib.h"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
u32 stringToKey(const std::string& key_name) {
|
HttpServer::HttpServer() : keyMap(
|
||||||
namespace Keys = HID::Keys;
|
{
|
||||||
static std::map<std::string, u32> keyMap = {
|
{"A", { HID::Keys::A, false } },
|
||||||
{"A", Keys::A},
|
{"B", { HID::Keys::B, false } },
|
||||||
{"B", Keys::B},
|
{"Select", { HID::Keys::Select, false } },
|
||||||
{"Select", Keys::Select},
|
{"Start", { HID::Keys::Start, false } },
|
||||||
{"Start", Keys::Start},
|
{"Right", { HID::Keys::Right, false } },
|
||||||
{"Right", Keys::Right},
|
{"Left", { HID::Keys::Left, false } },
|
||||||
{"Left", Keys::Left},
|
{"Up", { HID::Keys::Up, false } },
|
||||||
{"Up", Keys::Up},
|
{"Down", { HID::Keys::Down, false } },
|
||||||
{"Down", Keys::Down},
|
{"R", { HID::Keys::R, false } },
|
||||||
{"R", Keys::R},
|
{"L", { HID::Keys::L, false } },
|
||||||
{"L", Keys::L},
|
{"X", { HID::Keys::X, false } },
|
||||||
{"X", Keys::X},
|
{"Y", { HID::Keys::Y, false } },
|
||||||
{"Y", Keys::Y},
|
|
||||||
{"CirclePadRight", Keys::CirclePadRight},
|
|
||||||
{"CirclePadLeft", Keys::CirclePadLeft},
|
|
||||||
{"CirclePadUp", Keys::CirclePadUp},
|
|
||||||
{"CirclePadDown", Keys::CirclePadDown},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (keyMap.find(key_name) != keyMap.end()) {
|
|
||||||
return keyMap[key_name];
|
|
||||||
}
|
}
|
||||||
|
) {}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpServer::startHttpServer() {
|
void HttpServer::startHttpServer() {
|
||||||
std::thread http_thread([this]() {
|
std::thread http_thread([this]() {
|
||||||
|
@ -70,8 +59,10 @@ void HttpServer::startHttpServer() {
|
||||||
ok = true;
|
ok = true;
|
||||||
if (value == "1") {
|
if (value == "1") {
|
||||||
action = HttpAction::PressKey;
|
action = HttpAction::PressKey;
|
||||||
|
setKeyState(keyStr, true);
|
||||||
} else if (value == "0") {
|
} else if (value == "0") {
|
||||||
action = HttpAction::ReleaseKey;
|
action = HttpAction::ReleaseKey;
|
||||||
|
setKeyState(keyStr, false);
|
||||||
} else {
|
} else {
|
||||||
// Should not happen but just in case
|
// Should not happen but just in case
|
||||||
pendingAction = false;
|
pendingAction = false;
|
||||||
|
@ -92,6 +83,10 @@ void HttpServer::startHttpServer() {
|
||||||
response.set_content("ok", "text/plain");
|
response.set_content("ok", "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.Get("/status", [this](const httplib::Request&, httplib::Response& response) {
|
||||||
|
response.set_content(status(), "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: ability to specify host and port
|
// TODO: ability to specify host and port
|
||||||
printf("Starting HTTP server on port 1234\n");
|
printf("Starting HTTP server on port 1234\n");
|
||||||
server.listen("localhost", 1234);
|
server.listen("localhost", 1234);
|
||||||
|
@ -100,4 +95,38 @@ void HttpServer::startHttpServer() {
|
||||||
http_thread.detach();
|
http_thread.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string HttpServer::status() {
|
||||||
|
std::stringstream stringStream;
|
||||||
|
|
||||||
|
stringStream << "Panda3DS\n";
|
||||||
|
stringStream << "Status: " << (paused ? "Paused" : "Running") << "\n";
|
||||||
|
for (auto& [keyStr, value] : keyMap) {
|
||||||
|
stringStream << keyStr << ": " << value.second << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringStream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 HttpServer::stringToKey(const std::string& key_name) {
|
||||||
|
if (keyMap.find(key_name) != keyMap.end()) {
|
||||||
|
return keyMap[key_name].first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpServer::getKeyState(const std::string& key_name) {
|
||||||
|
if (keyMap.find(key_name) != keyMap.end()) {
|
||||||
|
return keyMap[key_name].second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpServer::setKeyState(const std::string& key_name, bool state) {
|
||||||
|
if (keyMap.find(key_name) != keyMap.end()) {
|
||||||
|
keyMap[key_name].second = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
16
src/main.cpp
16
src/main.cpp
|
@ -5,11 +5,15 @@ int main (int argc, char *argv[]) {
|
||||||
|
|
||||||
emu.initGraphicsContext();
|
emu.initGraphicsContext();
|
||||||
|
|
||||||
auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "teapot.elf");
|
if (argc > 1) {
|
||||||
if (!emu.loadROM(romPath)) {
|
auto romPath = std::filesystem::current_path() / argv[1];
|
||||||
// For some reason just .c_str() doesn't show the proper path
|
if (!emu.loadROM(romPath)) {
|
||||||
Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());
|
// For some reason just .c_str() doesn't show the proper path
|
||||||
}
|
Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("No ROM inserted! Load a ROM by dragging and dropping it into the emulator window!\n");
|
||||||
|
}
|
||||||
|
|
||||||
emu.run();
|
emu.run();
|
||||||
}
|
}
|
2
src/stb_image_write.c
Normal file
2
src/stb_image_write.c
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include <stb_image_write.h>
|
Loading…
Add table
Add a link
Reference in a new issue