mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-06 06:05:40 +12:00
Merge branch 'master' into tev-uniforms
This commit is contained in:
commit
6b8c7ede31
98 changed files with 1954 additions and 262544 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -33,4 +33,4 @@
|
|||
url = https://github.com/g-truc/glm
|
||||
[submodule "third_party/discord-rpc"]
|
||||
path = third_party/discord-rpc
|
||||
url = https://github.com/discord/discord-rpc
|
||||
url = https://github.com/Panda3DS-emu/discord-rpc
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# We need to be able to use enable_language(OBJC) on Mac, so we need CMake 3.16 vs the 3.10 we use otherwise. Blame Apple.
|
||||
if (APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum OS X deployment version")
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
else()
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
@ -49,12 +50,13 @@ include_directories(third_party/result/include)
|
|||
include_directories(third_party/xxhash/include)
|
||||
include_directories(third_party/httplib)
|
||||
include_directories(third_party/stb)
|
||||
include_directories(third_party/opengl)
|
||||
|
||||
add_compile_definitions(NOMINMAX) # Make windows.h not define min/max macros because third-party deps don't like it
|
||||
add_compile_definitions(WIN32_LEAN_AND_MEAN) # Make windows.h not include literally everything
|
||||
add_compile_definitions(SDL_MAIN_HANDLED)
|
||||
|
||||
if(ENABLE_DISCORD_RPC)
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
add_subdirectory(third_party/discord-rpc)
|
||||
include_directories(third_party/discord-rpc/include)
|
||||
endif()
|
||||
|
@ -124,17 +126,18 @@ set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limi
|
|||
src/core/kernel/events.cpp src/core/kernel/threads.cpp
|
||||
src/core/kernel/address_arbiter.cpp src/core/kernel/error.cpp
|
||||
src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp
|
||||
src/core/kernel/idle_thread.cpp
|
||||
src/core/kernel/idle_thread.cpp src/core/kernel/timers.cpp
|
||||
)
|
||||
set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp
|
||||
src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp
|
||||
src/core/services/ndm.cpp src/core/services/dsp.cpp src/core/services/cfg.cpp
|
||||
src/core/services/ptm.cpp src/core/services/mic.cpp src/core/services/cecd.cpp
|
||||
src/core/services/ac.cpp src/core/services/am.cpp src/core/services/boss.cpp
|
||||
src/core/services/frd.cpp src/core/services/nim.cpp src/core/services/shared_font.cpp
|
||||
src/core/services/frd.cpp src/core/services/nim.cpp src/core/services/mcu/mcu_hwc.cpp
|
||||
src/core/services/y2r.cpp src/core/services/cam.cpp src/core/services/ldr_ro.cpp
|
||||
src/core/services/act.cpp src/core/services/nfc.cpp src/core/services/dlp_srvr.cpp
|
||||
src/core/services/ir_user.cpp src/core/services/http.cpp src/core/services/soc.cpp
|
||||
src/core/services/ssl.cpp src/core/services/news_u.cpp
|
||||
)
|
||||
set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp
|
||||
src/core/PICA/shader_interpreter.cpp src/core/PICA/dynapica/shader_rec.cpp
|
||||
|
@ -163,8 +166,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
|||
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp
|
||||
include/services/mic.hpp include/services/cecd.hpp include/services/ac.hpp
|
||||
include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.hpp
|
||||
include/fs/archive_ext_save_data.hpp include/services/shared_font.hpp include/fs/archive_ncch.hpp
|
||||
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp
|
||||
include/fs/archive_ext_save_data.hpp include/fs/archive_ncch.hpp include/services/mcu/mcu_hwc.hpp
|
||||
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ssl.hpp
|
||||
include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp
|
||||
include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp
|
||||
include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp
|
||||
|
@ -175,7 +178,14 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
|||
include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
|
||||
include/action_replay.hpp include/renderer_sw/renderer_sw.hpp include/compiler_builtins.hpp
|
||||
include/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp
|
||||
include/services/soc.hpp
|
||||
include/math_util.hpp include/services/soc.hpp include/services/news_u.hpp
|
||||
)
|
||||
|
||||
cmrc_add_resource_library(
|
||||
resources_console_fonts
|
||||
NAMESPACE ConsoleFonts
|
||||
WHENCE "src/core/services/fonts/"
|
||||
"src/core/services/fonts/CitraSharedFontUSRelocated.bin"
|
||||
)
|
||||
|
||||
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
||||
|
@ -201,7 +211,8 @@ set(RENDERER_GL_SOURCE_FILES "") # Empty by default unless we are compiling with
|
|||
set(RENDERER_VK_SOURCE_FILES "") # Empty by default unless we are compiling with the VK renderer
|
||||
|
||||
if(ENABLE_OPENGL)
|
||||
set(RENDERER_GL_INCLUDE_FILES include/renderer_gl/opengl.hpp
|
||||
# This may look weird but opengl.hpp is our header even if it's in the third_party folder
|
||||
set(RENDERER_GL_INCLUDE_FILES third_party/opengl/opengl.hpp
|
||||
include/renderer_gl/renderer_gl.hpp include/renderer_gl/textures.hpp
|
||||
include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp
|
||||
include/renderer_gl/gl_state.hpp
|
||||
|
@ -272,9 +283,9 @@ if(ENABLE_LTO OR ENABLE_USER_BUILD)
|
|||
set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(Alber PRIVATE dynarmic SDL2-static cryptopp glad)
|
||||
target_link_libraries(Alber PRIVATE dynarmic SDL2-static cryptopp glad resources_console_fonts)
|
||||
|
||||
if(ENABLE_DISCORD_RPC)
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_DISCORD_RPC=1")
|
||||
target_link_libraries(Alber PRIVATE discord-rpc)
|
||||
endif()
|
||||
|
|
BIN
docs/img/KirbyRobobot.png
Normal file
BIN
docs/img/KirbyRobobot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
docs/img/MK7.png
BIN
docs/img/MK7.png
Binary file not shown.
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 389 KiB |
|
@ -14,8 +14,11 @@
|
|||
|
||||
class GPU {
|
||||
static constexpr u32 regNum = 0x300;
|
||||
static constexpr u32 extRegNum = 0x1000;
|
||||
|
||||
using vec4f = std::array<Floats::f24, 4>;
|
||||
using Registers = std::array<u32, regNum>;
|
||||
using Registers = std::array<u32, regNum>; // Internal registers (named registers in short since they're the main ones)
|
||||
using ExternalRegisters = std::array<u32, extRegNum>;
|
||||
|
||||
Memory& mem;
|
||||
EmulatorConfig& config;
|
||||
|
@ -91,12 +94,16 @@ class GPU {
|
|||
void reset();
|
||||
|
||||
Registers& getRegisters() { return regs; }
|
||||
ExternalRegisters& getExtRegisters() { return externalRegs; }
|
||||
void startCommandList(u32 addr, u32 size);
|
||||
|
||||
// Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked
|
||||
u32 readReg(u32 address);
|
||||
void writeReg(u32 address, u32 value);
|
||||
|
||||
u32 readExternalReg(u32 index);
|
||||
void writeExternalReg(u32 index, u32 value);
|
||||
|
||||
// Used when processing GPU command lists
|
||||
u32 readInternalReg(u32 index);
|
||||
void writeInternalReg(u32 index, u32 value, u32 mask);
|
||||
|
@ -111,6 +118,10 @@ class GPU {
|
|||
renderer->displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
|
||||
}
|
||||
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
renderer->textureCopy(inputAddr, outputAddr, totalBytes, inputSize, outputSize, flags);
|
||||
}
|
||||
|
||||
// Read a value of type T from physical address paddr
|
||||
// This is necessary because vertex attribute fetching uses physical addresses
|
||||
template <typename T>
|
||||
|
@ -140,4 +151,10 @@ class GPU {
|
|||
Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// GPU external registers
|
||||
// We have them in the end of the struct for cache locality reasons. Tl;dr we want the more commonly used things to be packed in the start
|
||||
// Of the struct, instead of externalRegs being in the middle
|
||||
ExternalRegisters externalRegs;
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace PICA {
|
|||
ShaderOutputCount = 0x4F,
|
||||
ShaderOutmap0 = 0x50,
|
||||
|
||||
ViewportXY = 0x68,
|
||||
DepthmapEnable = 0x6D,
|
||||
|
||||
// Texture registers
|
||||
|
@ -178,6 +179,53 @@ namespace PICA {
|
|||
};
|
||||
}
|
||||
|
||||
namespace ExternalRegs {
|
||||
enum : u32 {
|
||||
MemFill1BufferStartPaddr = 0x3,
|
||||
MemFill1BufferEndPAddr = 0x4,
|
||||
MemFill1Value = 0x5,
|
||||
MemFill1Control = 0x6,
|
||||
MemFill2BufferStartPaddr = 0x7,
|
||||
MemFill2BufferEndPAddr = 0x8,
|
||||
MemFill2Value = 0x9,
|
||||
MemFill2Control = 0xA,
|
||||
VramBankControl = 0xB,
|
||||
GPUBusy = 0xC,
|
||||
BacklightControl = 0xBC,
|
||||
Framebuffer0Size = 0x118,
|
||||
Framebuffer0AFirstAddr = 0x119,
|
||||
Framebuffer0ASecondAddr = 0x11A,
|
||||
Framebuffer0Config = 0x11B,
|
||||
Framebuffer0Select = 0x11D,
|
||||
Framebuffer0Stride = 0x123,
|
||||
Framebuffer0BFirstAddr = 0x124,
|
||||
Framebuffer0BSecondAddr = 0x125,
|
||||
Framebuffer1Size = 0x156,
|
||||
Framebuffer1AFirstAddr = 0x159,
|
||||
Framebuffer1ASecondAddr = 0x15A,
|
||||
Framebuffer1Config = 0x15B,
|
||||
Framebuffer1Select = 0x15D,
|
||||
Framebuffer1Stride = 0x163,
|
||||
Framebuffer1BFirstAddr = 0x164,
|
||||
Framebuffer1BSecondAddr = 0x165,
|
||||
TransferInputPAddr = 0x2FF,
|
||||
TransferOutputPAddr = 0x300,
|
||||
DisplayTransferOutputDim = 0x301,
|
||||
DisplayTransferInputDim = 0x302,
|
||||
TransferFlags = 0x303,
|
||||
TransferTrigger = 0x305,
|
||||
TextureCopyTotalBytes = 0x307,
|
||||
TextureCopyInputLineGap = 0x308,
|
||||
TextureCopyOutputLineGap = 0x309,
|
||||
};
|
||||
}
|
||||
|
||||
enum class Scaling : u32 {
|
||||
None = 0,
|
||||
X = 1,
|
||||
XY = 2,
|
||||
};
|
||||
|
||||
namespace Lights {
|
||||
enum : u32 {
|
||||
LUT_D0 = 0,
|
||||
|
|
|
@ -159,6 +159,7 @@ class PICAShader {
|
|||
void mul(u32 instruction);
|
||||
void rcp(u32 instruction);
|
||||
void rsq(u32 instruction);
|
||||
void sge(u32 instruction);
|
||||
void sgei(u32 instruction);
|
||||
void slt(u32 instruction);
|
||||
void slti(u32 instruction);
|
||||
|
|
|
@ -5,10 +5,17 @@
|
|||
|
||||
// Remember to initialize every field here to its default value otherwise bad things will happen
|
||||
struct EmulatorConfig {
|
||||
bool shaderJitEnabled = false;
|
||||
bool shaderJitEnabled = true;
|
||||
bool discordRpcEnabled = false;
|
||||
RendererType rendererType = RendererType::OpenGL;
|
||||
|
||||
bool sdCardInserted = true;
|
||||
bool sdWriteProtected = false;
|
||||
|
||||
bool chargerPlugged = true;
|
||||
// Default to 3% battery to make users suffer
|
||||
int batteryPercentage = 3;
|
||||
|
||||
EmulatorConfig(const std::filesystem::path& path);
|
||||
void load(const std::filesystem::path& path);
|
||||
void save(const std::filesystem::path& path);
|
||||
|
|
|
@ -105,6 +105,7 @@ namespace Crypto {
|
|||
AESEngine() {}
|
||||
void loadKeys(const std::filesystem::path& path);
|
||||
bool haveKeys() { return keysLoaded; }
|
||||
bool haveGenerator() { return m_generator.has_value(); }
|
||||
|
||||
constexpr bool hasKeyX(std::size_t slotId) {
|
||||
if (slotId >= AesKeySlotCount) {
|
||||
|
|
|
@ -5,7 +5,7 @@ class SaveDataArchive : public ArchiveBase {
|
|||
public:
|
||||
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||
|
||||
u64 getFreeBytes() override { Helpers::panic("SaveData::GetFreeBytes unimplemented"); return 0; }
|
||||
u64 getFreeBytes() override { return 32_MB; }
|
||||
std::string name() override { return "SaveData"; }
|
||||
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "termcolor.hpp"
|
||||
|
||||
|
@ -30,6 +31,17 @@ using s32 = std::int32_t;
|
|||
using s64 = std::int64_t;
|
||||
|
||||
namespace Helpers {
|
||||
template <class... Args>
|
||||
std::string format(const std::string& fmt, Args&&... args) {
|
||||
const int size = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1;
|
||||
if (size <= 0) {
|
||||
return {};
|
||||
}
|
||||
const auto buf = std::make_unique<char[]>(size);
|
||||
std::snprintf(buf.get(), size, fmt.c_str(), args ...);
|
||||
return std::string(buf.get(), buf.get() + size - 1);
|
||||
}
|
||||
|
||||
// Unconditional panic, unlike panicDev which does not panic on user builds
|
||||
template <class... Args>
|
||||
[[noreturn]] static void panic(const char* fmt, Args&&... args) {
|
||||
|
|
|
@ -29,12 +29,15 @@ namespace KernelHandles {
|
|||
GPU, // GPU service
|
||||
LCD, // LCD service (Used for configuring the displays)
|
||||
LDR_RO, // Loader service. Used for loading CROs.
|
||||
MCU_HWC, // Used for various MCU hardware-related things like battery control
|
||||
MIC, // MIC service (Controls the microphone)
|
||||
NFC, // NFC (Duh), used for Amiibo
|
||||
NIM, // Updates, DLC, etc
|
||||
NDM, // ?????
|
||||
NEWS_U, // This service literally has 1 command (AddNotification) and I don't even understand what it does
|
||||
PTM, // PTM service (Used for accessing various console info, such as battery, shell and pedometer state)
|
||||
SOC, // Socket service
|
||||
SSL, // SSL service (Totally didn't expect that)
|
||||
Y2R, // Also does camera stuff
|
||||
|
||||
MinServiceHandle = AC,
|
||||
|
@ -80,12 +83,15 @@ namespace KernelHandles {
|
|||
case GPU: return "GSP::GPU";
|
||||
case LCD: return "GSP::LCD";
|
||||
case LDR_RO: return "LDR:RO";
|
||||
case MCU_HWC: return "MCU::HWC";
|
||||
case MIC: return "MIC";
|
||||
case NDM: return "NDM";
|
||||
case NEWS_U: return "NEWS_U";
|
||||
case NFC: return "NFC";
|
||||
case NIM: return "NIM";
|
||||
case PTM: return "PTM";
|
||||
case SOC: return "SOC";
|
||||
case SSL: return "SSL";
|
||||
case Y2R: return "Y2R";
|
||||
default: return "Unknown";
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
|
@ -60,13 +61,14 @@ class Kernel {
|
|||
Handle makeProcess(u32 id);
|
||||
Handle makePort(const char* name);
|
||||
Handle makeSession(Handle port);
|
||||
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
||||
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
||||
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
||||
|
||||
public:
|
||||
Handle makeEvent(ResetType resetType); // Needs to be public to be accessible to the APT/HID services
|
||||
Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services
|
||||
Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port
|
||||
Handle makeTimer(ResetType resetType);
|
||||
|
||||
// Signals an event, returns true on success or false if the event does not exist
|
||||
bool signalEvent(Handle e);
|
||||
|
@ -124,6 +126,7 @@ private:
|
|||
void exitThread();
|
||||
void mapMemoryBlock();
|
||||
void queryMemory();
|
||||
void getCurrentProcessorNumber();
|
||||
void getProcessID();
|
||||
void getProcessInfo();
|
||||
void getResourceLimit();
|
||||
|
@ -132,17 +135,22 @@ private:
|
|||
void getSystemInfo();
|
||||
void getSystemTick();
|
||||
void getThreadID();
|
||||
void getThreadIdealProcessor();
|
||||
void getThreadPriority();
|
||||
void sendSyncRequest();
|
||||
void setThreadPriority();
|
||||
void svcCancelTimer();
|
||||
void svcClearEvent();
|
||||
void svcClearTimer();
|
||||
void svcCloseHandle();
|
||||
void svcCreateEvent();
|
||||
void svcCreateMutex();
|
||||
void svcCreateSemaphore();
|
||||
void svcCreateTimer();
|
||||
void svcReleaseMutex();
|
||||
void svcReleaseSemaphore();
|
||||
void svcSignalEvent();
|
||||
void svcSetTimer();
|
||||
void svcSleepThread();
|
||||
void connectToPort();
|
||||
void outputDebugString();
|
||||
|
@ -166,7 +174,7 @@ private:
|
|||
void readDirectory(u32 messagePointer, Handle directory);
|
||||
|
||||
public:
|
||||
Kernel(CPU& cpu, Memory& mem, GPU& gpu);
|
||||
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config);
|
||||
void initializeFS() { return serviceManager.initializeFS(); }
|
||||
void setVersion(u8 major, u8 minor);
|
||||
void serviceSVC(u32 svc);
|
||||
|
|
|
@ -34,6 +34,16 @@ enum class ArbitrationType {
|
|||
DecrementAndWaitIfLessTimeout = 4
|
||||
};
|
||||
|
||||
enum class ProcessorID : s32 {
|
||||
AllCPUs = -1,
|
||||
Default = -2,
|
||||
|
||||
AppCore = 0,
|
||||
Syscore = 1,
|
||||
New3DSExtra1 = 2,
|
||||
New3DSExtra2 = 3
|
||||
};
|
||||
|
||||
struct AddressArbiter {};
|
||||
|
||||
struct ResourceLimits {
|
||||
|
@ -95,7 +105,7 @@ struct Thread {
|
|||
u32 entrypoint; // Initial r15 value
|
||||
u32 priority;
|
||||
u32 arg;
|
||||
s32 processorID;
|
||||
ProcessorID processorID;
|
||||
ThreadStatus status;
|
||||
Handle handle; // OS handle for this thread
|
||||
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "io_file.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "crypto/aes_engine.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
struct NCCH {
|
||||
struct EncryptionInfo {
|
||||
|
@ -26,10 +28,10 @@ struct NCCH {
|
|||
u32 size = 0;
|
||||
|
||||
// Extract the code set info from the relevant header data
|
||||
void extract(const u8* headerEntry) {
|
||||
address = *(u32*)&headerEntry[0];
|
||||
pageCount = *(u32*)&headerEntry[4];
|
||||
size = *(u32*)&headerEntry[8];
|
||||
void extract(const u8 *headerEntry) {
|
||||
address = *(u32 *)&headerEntry[0];
|
||||
pageCount = *(u32 *)&headerEntry[4];
|
||||
size = *(u32 *)&headerEntry[8];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -60,10 +62,12 @@ struct NCCH {
|
|||
std::vector<u8> codeFile;
|
||||
// Contains of the cart's save data
|
||||
std::vector<u8> saveData;
|
||||
// The cart region. Only the CXI's region matters to us. Necessary to get past region locking
|
||||
std::optional<Regions> region = std::nullopt;
|
||||
|
||||
// Returns true on success, false on failure
|
||||
// Partition index/offset/size must have been set before this
|
||||
bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSInfo &info);
|
||||
bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile &file, const FSInfo &info);
|
||||
|
||||
bool hasExtendedHeader() { return exheaderSize != 0; }
|
||||
bool hasExeFS() { return exeFS.size != 0; }
|
||||
|
@ -71,8 +75,11 @@ struct NCCH {
|
|||
bool hasCode() { return codeFile.size() != 0; }
|
||||
bool hasSaveData() { return saveData.size() != 0; }
|
||||
|
||||
// Parse SMDH for region info and such. Returns false on failure, true on success
|
||||
bool parseSMDH(const std::vector<u8> &smdh);
|
||||
|
||||
std::pair<bool, Crypto::AESKey> getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
||||
std::pair<bool, Crypto::AESKey> getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
||||
|
||||
std::pair<bool, std::size_t> readFromFile(IOFile& file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size);
|
||||
std::pair<bool, std::size_t> readFromFile(IOFile &file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size);
|
||||
};
|
|
@ -48,12 +48,15 @@ namespace Log {
|
|||
static Logger<false> gspGPULogger;
|
||||
static Logger<false> gspLCDLogger;
|
||||
static Logger<false> ldrLogger;
|
||||
static Logger<false> mcuLogger;
|
||||
static Logger<false> micLogger;
|
||||
static Logger<false> newsLogger;
|
||||
static Logger<false> nfcLogger;
|
||||
static Logger<false> nimLogger;
|
||||
static Logger<false> ndmLogger;
|
||||
static Logger<false> ptmLogger;
|
||||
static Logger<false> socLogger;
|
||||
static Logger<false> sslLogger;
|
||||
static Logger<false> y2rLogger;
|
||||
static Logger<false> srvLogger;
|
||||
|
||||
|
|
73
include/math_util.hpp
Normal file
73
include/math_util.hpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project / 2023 Panda3DS Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Math {
|
||||
|
||||
template <class T>
|
||||
struct Rectangle {
|
||||
T left{};
|
||||
T top{};
|
||||
T right{};
|
||||
T bottom{};
|
||||
|
||||
constexpr Rectangle() = default;
|
||||
|
||||
constexpr Rectangle(T left, T top, T right, T bottom)
|
||||
: left(left), top(top), right(right), bottom(bottom) {}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
|
||||
return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
|
||||
(bottom == rhs.bottom);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Rectangle<T> operator*(const T value) const {
|
||||
return Rectangle{left * value, top * value, right * value, bottom * value};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Rectangle<T> operator/(const T value) const {
|
||||
return Rectangle{left / value, top / value, right / value, bottom / value};
|
||||
}
|
||||
|
||||
[[nodiscard]] T getWidth() const {
|
||||
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
|
||||
}
|
||||
|
||||
[[nodiscard]] T getHeight() const {
|
||||
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
|
||||
}
|
||||
|
||||
[[nodiscard]] T getArea() const {
|
||||
return getWidth() * getHeight();
|
||||
}
|
||||
|
||||
[[nodiscard]] Rectangle<T> translateX(const T x) const {
|
||||
return Rectangle{left + x, top, right + x, bottom};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rectangle<T> translateY(const T y) const {
|
||||
return Rectangle{left, top + y, right, bottom + y};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rectangle<T> scale(const float s) const {
|
||||
return Rectangle{left, top, static_cast<T>(left + getWidth() * s),
|
||||
static_cast<T>(top + getHeight() * s)};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Rectangle(T, T, T, T) -> Rectangle<T>;
|
||||
|
||||
template <typename T>
|
||||
using Rect = Rectangle<T>;
|
||||
|
||||
} // end namespace Math
|
|
@ -5,11 +5,13 @@
|
|||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "crypto/aes_engine.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "handles.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "loader/ncsd.hpp"
|
||||
#include "services/shared_font.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
namespace PhysicalAddrs {
|
||||
enum : u32 {
|
||||
|
@ -110,7 +112,7 @@ class Memory {
|
|||
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
||||
|
||||
std::array<SharedMemoryBlock, 3> sharedMemBlocks = {
|
||||
SharedMemoryBlock(0, u32(_shared_font_len), KernelHandles::FontSharedMemHandle), // Shared memory for the system font
|
||||
SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem
|
||||
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
||||
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory
|
||||
};
|
||||
|
@ -151,13 +153,16 @@ private:
|
|||
|
||||
// Values taken from 3DBrew and Citra
|
||||
static constexpr FirmwareInfo firm{.unk = 0, .revision = 0, .minor = 0x34, .major = 2, .syscoreVer = 2, .sdkVer = 0x0000F297};
|
||||
// Adjusted upon loading a ROM based on the ROM header. Used by CFG::SecureInfoGetArea to get past region locks
|
||||
Regions region = Regions::USA;
|
||||
const EmulatorConfig& config;
|
||||
|
||||
public:
|
||||
u16 kernelVersion = 0;
|
||||
u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
|
||||
u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore)
|
||||
|
||||
Memory(u64& cpuTicks);
|
||||
Memory(u64& cpuTicks, const EmulatorConfig& config);
|
||||
void reset();
|
||||
void* getReadPointer(u32 address);
|
||||
void* getWritePointer(u32 address);
|
||||
|
@ -261,4 +266,6 @@ public:
|
|||
|
||||
void setVRAM(u8* pointer) { vram = pointer; }
|
||||
bool allocateMainThreadStack(u32 size);
|
||||
Regions getConsoleRegion();
|
||||
void copySharedFont(u8* ptr);
|
||||
};
|
|
@ -22,7 +22,10 @@ class Renderer {
|
|||
protected:
|
||||
GPU& gpu;
|
||||
static constexpr u32 regNum = 0x300; // Number of internal PICA registers
|
||||
static constexpr u32 extRegNum = 0x1000; // Number of external PICA registers
|
||||
|
||||
const std::array<u32, regNum>& regs;
|
||||
const std::array<u32, extRegNum>& externalRegs;
|
||||
|
||||
std::array<u32, 2> fbSize; // The size of the framebuffer (ie both the colour and depth buffer)'
|
||||
|
||||
|
@ -34,7 +37,7 @@ class Renderer {
|
|||
PICA::DepthFmt depthBufferFormat;
|
||||
|
||||
public:
|
||||
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs);
|
||||
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
virtual ~Renderer();
|
||||
|
||||
static constexpr u32 vertexBufferSize = 0x10000;
|
||||
|
@ -46,6 +49,7 @@ class Renderer {
|
|||
virtual void initGraphicsContext(SDL_Window* window) = 0; // Initialize graphics context
|
||||
virtual void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) = 0; // Clear a GPU buffer in VRAM
|
||||
virtual void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) = 0; // Perform display transfer
|
||||
virtual void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) = 0;
|
||||
virtual void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) = 0; // Draw the given vertices
|
||||
|
||||
virtual void screenshot(const std::string& name) = 0;
|
||||
|
|
|
@ -34,6 +34,8 @@ struct GLStateManager {
|
|||
bool redMask, greenMask, blueMask, alphaMask;
|
||||
bool depthMask;
|
||||
|
||||
float clearRed, clearBlue, clearGreen, clearAlpha;
|
||||
|
||||
GLuint stencilMask;
|
||||
GLuint boundVAO;
|
||||
GLuint boundVBO;
|
||||
|
@ -44,6 +46,7 @@ struct GLStateManager {
|
|||
|
||||
void reset();
|
||||
void resetBlend();
|
||||
void resetClearing();
|
||||
void resetClipping();
|
||||
void resetColourMask();
|
||||
void resetDepth();
|
||||
|
@ -209,6 +212,17 @@ struct GLStateManager {
|
|||
}
|
||||
}
|
||||
|
||||
void setClearColour(float r, float g, float b, float a) {
|
||||
if (clearRed != r || clearGreen != g || clearBlue != b || clearAlpha != a) {
|
||||
clearRed = r;
|
||||
clearGreen = g;
|
||||
clearBlue = b;
|
||||
clearAlpha = a;
|
||||
|
||||
OpenGL::setClearColor(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
void setDepthFunc(OpenGL::DepthFunc func) { setDepthFunc(static_cast<GLenum>(func)); }
|
||||
};
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ class RendererGL final : public Renderer {
|
|||
float oldDepthOffset = 0.0;
|
||||
bool oldDepthmapEnable = false;
|
||||
|
||||
SurfaceCache<DepthBuffer, 10, true> depthBufferCache;
|
||||
SurfaceCache<ColourBuffer, 10, true> colourBufferCache;
|
||||
SurfaceCache<DepthBuffer, 16, true> depthBufferCache;
|
||||
SurfaceCache<ColourBuffer, 16, true> colourBufferCache;
|
||||
SurfaceCache<Texture, 256, true> textureCache;
|
||||
|
||||
// Dummy VAO/VBO for blitting the final output
|
||||
|
@ -48,6 +48,7 @@ class RendererGL final : public Renderer {
|
|||
OpenGL::Texture screenTexture;
|
||||
GLuint lightLUTTextureArray;
|
||||
OpenGL::Framebuffer screenFramebuffer;
|
||||
OpenGL::Texture blankTexture;
|
||||
|
||||
OpenGL::Framebuffer getColourFBO();
|
||||
OpenGL::Texture getTexture(Texture& tex);
|
||||
|
@ -60,7 +61,8 @@ class RendererGL final : public Renderer {
|
|||
void updateLightingLUT();
|
||||
|
||||
public:
|
||||
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
~RendererGL() override;
|
||||
|
||||
void reset() override;
|
||||
|
@ -68,8 +70,11 @@ class RendererGL final : public Renderer {
|
|||
void initGraphicsContext(SDL_Window* window) override; // Initialize graphics context
|
||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; // Clear a GPU buffer in VRAM
|
||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; // Perform display transfer
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override; // Draw the given vertices
|
||||
|
||||
std::optional<ColourBuffer> getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);
|
||||
|
||||
// Take a screenshot of the screen and store it in a file
|
||||
void screenshot(const std::string& name) override;
|
||||
};
|
||||
|
|
|
@ -76,6 +76,16 @@ public:
|
|||
|
||||
size++;
|
||||
|
||||
// Find an existing surface we completely invalidate and overwrite it with the new surface
|
||||
for (auto& e : buffer) {
|
||||
if (e.valid && e.range.lower() >= surface.range.lower() && e.range.upper() <= surface.range.upper()) {
|
||||
e.free();
|
||||
e = surface;
|
||||
e.allocate();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
// Find an invalid entry in the cache and overwrite it with the new surface
|
||||
for (auto& e : buffer) {
|
||||
if (!e.valid) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "PICA/regs.hpp"
|
||||
#include "boost/icl/interval.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "math_util.hpp"
|
||||
#include "opengl.hpp"
|
||||
|
||||
template <typename T>
|
||||
|
@ -21,9 +22,7 @@ struct ColourBuffer {
|
|||
|
||||
ColourBuffer() : valid(false) {}
|
||||
|
||||
ColourBuffer(u32 loc, PICA::ColorFmt format, u32 x, u32 y, bool valid = true)
|
||||
: location(loc), format(format), size({x, y}), valid(valid) {
|
||||
|
||||
ColourBuffer(u32 loc, PICA::ColorFmt format, u32 x, u32 y, bool valid = true) : location(loc), format(format), size({x, y}), valid(valid) {
|
||||
u64 endLoc = (u64)loc + sizeInBytes();
|
||||
// Check if start and end are valid here
|
||||
range = Interval<u32>(loc, (u32)endLoc);
|
||||
|
@ -40,21 +39,30 @@ struct ColourBuffer {
|
|||
texture.setMagFilter(OpenGL::Linear);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
|
||||
//Helpers::panic("Creating FBO: %d, %d\n", size.x(), size.y());
|
||||
#ifdef GPU_DEBUG_INFO
|
||||
const auto name = Helpers::format("Surface %dx%d %s from 0x%08X", size.x(), size.y(), PICA::textureFormatToString(format), location);
|
||||
OpenGL::setObjectLabel(GL_TEXTURE, texture.handle(), name.c_str());
|
||||
#endif
|
||||
|
||||
fbo.createWithDrawTexture(texture);
|
||||
fbo.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
Helpers::panic("Incomplete framebuffer");
|
||||
}
|
||||
|
||||
// TODO: This should not clear the framebuffer contents. It should load them from VRAM.
|
||||
GLint oldViewport[4];
|
||||
GLfloat oldClearColour[4];
|
||||
|
||||
glGetIntegerv(GL_VIEWPORT, oldViewport);
|
||||
glGetFloatv(GL_COLOR_CLEAR_VALUE, oldClearColour);
|
||||
|
||||
OpenGL::setViewport(size.x(), size.y());
|
||||
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
OpenGL::clearColor();
|
||||
OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
|
||||
OpenGL::setClearColor(oldClearColour[0], oldClearColour[1], oldClearColour[2], oldClearColour[3]);
|
||||
}
|
||||
|
||||
void free() {
|
||||
|
@ -66,9 +74,17 @@ struct ColourBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
Math::Rect<u32> getSubRect(u32 inputAddress, u32 width, u32 height) {
|
||||
// PICA textures have top-left origin while OpenGL has bottom-left origin.
|
||||
// Flip the rectangle on the x axis to account for this.
|
||||
const u32 startOffset = (inputAddress - location) / sizePerPixel(format);
|
||||
const u32 x0 = (startOffset % (size.x() * 8)) / 8;
|
||||
const u32 y0 = (startOffset / (size.x() * 8)) * 8;
|
||||
return Math::Rect<u32>{x0, size.y() - y0, x0 + width, size.y() - height - y0};
|
||||
}
|
||||
|
||||
bool matches(ColourBuffer& other) {
|
||||
return location == other.location && format == other.format &&
|
||||
size.x() == other.size.x() && size.y() == other.size.y();
|
||||
return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y();
|
||||
}
|
||||
|
||||
size_t sizeInBytes() {
|
||||
|
@ -90,9 +106,7 @@ struct DepthBuffer {
|
|||
|
||||
DepthBuffer() : valid(false) {}
|
||||
|
||||
DepthBuffer(u32 loc, PICA::DepthFmt format, u32 x, u32 y, bool valid = true) :
|
||||
location(loc), format(format), size({x, y}), valid(valid) {
|
||||
|
||||
DepthBuffer(u32 loc, PICA::DepthFmt format, u32 x, u32 y, bool valid = true) : location(loc), format(format), size({x, y}), valid(valid) {
|
||||
u64 endLoc = (u64)loc + sizeInBytes();
|
||||
// Check if start and end are valid here
|
||||
range = Interval<u32>(loc, (u32)endLoc);
|
||||
|
@ -106,16 +120,25 @@ struct DepthBuffer {
|
|||
|
||||
// Internal formats for the texture based on format
|
||||
static constexpr std::array<GLenum, 4> internalFormats = {
|
||||
GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT24, GL_DEPTH24_STENCIL8
|
||||
GL_DEPTH_COMPONENT16,
|
||||
GL_DEPTH_COMPONENT24,
|
||||
GL_DEPTH_COMPONENT24,
|
||||
GL_DEPTH24_STENCIL8,
|
||||
};
|
||||
|
||||
// Format of the texture
|
||||
static constexpr std::array<GLenum, 4> formats = {
|
||||
GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL
|
||||
GL_DEPTH_COMPONENT,
|
||||
GL_DEPTH_COMPONENT,
|
||||
GL_DEPTH_COMPONENT,
|
||||
GL_DEPTH_STENCIL,
|
||||
};
|
||||
|
||||
static constexpr std::array<GLenum, 4> types = {
|
||||
GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8
|
||||
GL_UNSIGNED_SHORT,
|
||||
GL_UNSIGNED_INT,
|
||||
GL_UNSIGNED_INT,
|
||||
GL_UNSIGNED_INT_24_8,
|
||||
};
|
||||
|
||||
auto internalFormat = internalFormats[(int)format];
|
||||
|
@ -128,12 +151,12 @@ struct DepthBuffer {
|
|||
texture.setMagFilter(OpenGL::Nearest);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
|
||||
fbo.createWithDrawTexture(texture, fmt == GL_DEPTH_STENCIL ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
Helpers::panic("Incomplete framebuffer");
|
||||
}
|
||||
}
|
||||
|
||||
void free() {
|
||||
valid = false;
|
||||
|
@ -143,8 +166,7 @@ struct DepthBuffer {
|
|||
}
|
||||
|
||||
bool matches(DepthBuffer& other) {
|
||||
return location == other.location && format == other.format &&
|
||||
size.x() == other.size.x() && size.y() == other.size.y();
|
||||
return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y();
|
||||
}
|
||||
|
||||
size_t sizeInBytes() {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "PICA/regs.hpp"
|
||||
#include "boost/icl/interval.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "math_util.hpp"
|
||||
#include "opengl.hpp"
|
||||
|
||||
template <typename T>
|
||||
|
@ -53,7 +54,7 @@ struct Texture {
|
|||
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
|
||||
|
||||
// Returns the format of this texture as a string
|
||||
std::string formatToString() {
|
||||
std::string_view formatToString() {
|
||||
return PICA::textureFormatToString(format);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ class GPU;
|
|||
|
||||
class RendererNull final : public Renderer {
|
||||
public:
|
||||
RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs);
|
||||
RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
~RendererNull() override;
|
||||
|
||||
void reset() override;
|
||||
|
@ -12,6 +12,7 @@ class RendererNull final : public Renderer {
|
|||
void initGraphicsContext(SDL_Window* window) override;
|
||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||
void screenshot(const std::string& name) override;
|
||||
};
|
|
@ -4,7 +4,7 @@ class GPU;
|
|||
|
||||
class RendererSw final : public Renderer {
|
||||
public:
|
||||
RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs);
|
||||
RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
~RendererSw() override;
|
||||
|
||||
void reset() override;
|
||||
|
@ -12,6 +12,7 @@ class RendererSw final : public Renderer {
|
|||
void initGraphicsContext(SDL_Window* window) override;
|
||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||
void screenshot(const std::string& name) override;
|
||||
};
|
|
@ -44,7 +44,7 @@ class RendererVK final : public Renderer {
|
|||
|
||||
u64 currentFrame = 0;
|
||||
public:
|
||||
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs);
|
||||
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
~RendererVK() override;
|
||||
|
||||
void reset() override;
|
||||
|
@ -52,6 +52,7 @@ class RendererVK final : public Renderer {
|
|||
void initGraphicsContext(SDL_Window* window) override;
|
||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||
void screenshot(const std::string& name) override;
|
||||
};
|
|
@ -11,7 +11,11 @@ class ACService {
|
|||
MAKE_LOG_FUNCTION(log, acLogger)
|
||||
|
||||
// Service commands
|
||||
void cancelConnectAsync(u32 messagePointer);
|
||||
void closeAsync(u32 messagePointer);
|
||||
void createDefaultConfig(u32 messagePointer);
|
||||
void getLastErrorCode(u32 messagePointer);
|
||||
void registerDisconnectEvent(u32 messagePointer);
|
||||
void setClientVersion(u32 messagePointer);
|
||||
|
||||
public:
|
||||
|
|
|
@ -12,6 +12,7 @@ class AMService {
|
|||
|
||||
// Service commands
|
||||
void getDLCTitleInfo(u32 messagePointer);
|
||||
void getPatchTitleInfo(u32 messagePointer);
|
||||
void listTitleInfo(u32 messagePointer);
|
||||
|
||||
public:
|
||||
|
|
|
@ -13,14 +13,22 @@ class BOSSService {
|
|||
// Service commands
|
||||
void cancelTask(u32 messagePointer);
|
||||
void initializeSession(u32 messagePointer);
|
||||
void getNsDataIdList(u32 messagePointer);
|
||||
void getErrorCode(u32 messagePointer);
|
||||
void getNsDataIdList(u32 messagePointer, u32 commandWord);
|
||||
void getOptoutFlag(u32 messagePointer);
|
||||
void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra
|
||||
void getTaskIdList(u32 messagePointer);
|
||||
void getTaskInfo(u32 messagePOinter);
|
||||
void getTaskInfo(u32 messagePointer);
|
||||
void getTaskServiceStatus(u32 messagePointer);
|
||||
void getTaskState(u32 messagePointer);
|
||||
void getTaskStatus(u32 messagePointer);
|
||||
void getTaskStorageInfo(u32 messagePointer);
|
||||
void receiveProperty(u32 messagePointer);
|
||||
void registerNewArrivalEvent(u32 messagePointer);
|
||||
void registerStorageEntry(u32 messagePointer);
|
||||
void registerTask(u32 messagePointer);
|
||||
void sendProperty(u32 messagePointer);
|
||||
void startTask(u32 messagePointer);
|
||||
void unregisterStorage(u32 messagePointer);
|
||||
void unregisterTask(u32 messagePointer);
|
||||
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
// Yay, circular dependencies!
|
||||
class Kernel;
|
||||
|
||||
class CAMService {
|
||||
Handle handle = KernelHandles::CAM;
|
||||
Memory& mem;
|
||||
Kernel& kernel;
|
||||
MAKE_LOG_FUNCTION(log, camLogger)
|
||||
|
||||
using Event = std::optional<Handle>;
|
||||
static constexpr size_t portCount = 4; // PORT_NONE, PORT_CAM1, PORT_CAM2, PORT_BOTH
|
||||
std::array<Event, portCount> bufferErrorInterruptEvents;
|
||||
|
||||
// Service commands
|
||||
void driverInitialize(u32 messagePointer);
|
||||
void getMaxLines(u32 messagePointer);
|
||||
void getBufferErrorInterruptEvent(u32 messagePointer);
|
||||
|
||||
public:
|
||||
CAMService(Memory& mem) : mem(mem) {}
|
||||
public:
|
||||
CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
};
|
|
@ -18,6 +18,7 @@ class CECDService {
|
|||
|
||||
// Service commands
|
||||
void getInfoEventHandle(u32 messagePointer);
|
||||
void openAndRead(u32 messagePointer);
|
||||
|
||||
public:
|
||||
CECDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||
|
|
|
@ -19,6 +19,8 @@ class FRDService {
|
|||
Memory& mem;
|
||||
MAKE_LOG_FUNCTION(log, frdLogger)
|
||||
|
||||
bool loggedIn = false;
|
||||
|
||||
// Service commands
|
||||
void attachToEventNotification(u32 messagePointer);
|
||||
void getFriendKeyList(u32 messagePointer);
|
||||
|
@ -27,8 +29,11 @@ class FRDService {
|
|||
void getMyPresence(u32 messagePointer);
|
||||
void getMyProfile(u32 messagePointer);
|
||||
void getMyScreenName(u32 messsagePointer);
|
||||
void hasLoggedIn(u32 messagePointer);
|
||||
void logout(u32 messagePointer);
|
||||
void setClientSDKVersion(u32 messagePointer);
|
||||
void setNotificationMask(u32 messagePointer);
|
||||
void updateGameModeDescription(u32 messagePointer);
|
||||
|
||||
public:
|
||||
FRDService(Memory& mem) : mem(mem) {}
|
||||
|
|
|
@ -37,9 +37,11 @@ class FSService {
|
|||
|
||||
// Service commands
|
||||
void createDirectory(u32 messagePointer);
|
||||
void createExtSaveData(u32 messagePointer);
|
||||
void createFile(u32 messagePointer);
|
||||
void closeArchive(u32 messagePointer);
|
||||
void controlArchive(u32 messagePointer);
|
||||
void deleteExtSaveData(u32 messagePointer);
|
||||
void deleteFile(u32 messagePointer);
|
||||
void formatSaveData(u32 messagePointer);
|
||||
void formatThisUserSaveData(u32 messagePointer);
|
||||
|
|
|
@ -40,11 +40,32 @@ class GPUService {
|
|||
MAKE_LOG_FUNCTION(log, gspGPULogger)
|
||||
void processCommandBuffer();
|
||||
|
||||
struct FramebufferInfo {
|
||||
u32 activeFb;
|
||||
u32 leftFramebufferVaddr;
|
||||
u32 rightFramebufferVaddr;
|
||||
u32 stride;
|
||||
u32 format;
|
||||
u32 displayFb;
|
||||
u32 attribute;
|
||||
};
|
||||
static_assert(sizeof(FramebufferInfo) == 28, "GSP::GPU::FramebufferInfo has the wrong size");
|
||||
|
||||
struct FramebufferUpdate {
|
||||
u8 index;
|
||||
u8 dirtyFlag;
|
||||
u16 pad0;
|
||||
std::array<FramebufferInfo, 2> framebufferInfo;
|
||||
u32 pad1;
|
||||
};
|
||||
static_assert(sizeof(FramebufferUpdate) == 64, "GSP::GPU::FramebufferUpdate has the wrong size");
|
||||
|
||||
// Service commands
|
||||
void acquireRight(u32 messagePointer);
|
||||
void flushDataCache(u32 messagePointer);
|
||||
void registerInterruptRelayQueue(u32 messagePointer);
|
||||
void setAxiConfigQoSMode(u32 messagePointer);
|
||||
void setBufferSwap(u32 messagePointer);
|
||||
void setInternalPriorities(u32 messagePointer);
|
||||
void setLCDForceBlack(u32 messagePointer);
|
||||
void storeDataCache(u32 messagePointer);
|
||||
|
@ -60,6 +81,8 @@ class GPUService {
|
|||
void triggerTextureCopy(u32* cmd);
|
||||
void flushCacheRegions(u32* cmd);
|
||||
|
||||
void setBufferSwapImpl(u32 screen_id, const FramebufferInfo& info);
|
||||
|
||||
public:
|
||||
GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu),
|
||||
kernel(kernel), currentPID(currentPID) {}
|
||||
|
|
|
@ -71,6 +71,7 @@ class HIDService {
|
|||
void getGyroscopeLowCalibrateParam(u32 messagePointer);
|
||||
void getGyroscopeCoefficient(u32 messagePointer);
|
||||
void getIPCHandles(u32 messagePointer);
|
||||
void getSoundVolume(u32 messagePointer);
|
||||
|
||||
// Don't call these prior to initializing shared mem pls
|
||||
template <typename T>
|
||||
|
@ -141,4 +142,6 @@ class HIDService {
|
|||
void releaseTouchScreen() {
|
||||
touchScreenPressed = false;
|
||||
}
|
||||
|
||||
bool isTouchScreenPressed() { return touchScreenPressed; }
|
||||
};
|
||||
|
|
24
include/services/mcu/mcu_hwc.hpp
Normal file
24
include/services/mcu/mcu_hwc.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include "config.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace MCU {
|
||||
class HWCService {
|
||||
Handle handle = KernelHandles::MCU_HWC;
|
||||
Memory& mem;
|
||||
MAKE_LOG_FUNCTION(log, mcuLogger)
|
||||
|
||||
const EmulatorConfig& config;
|
||||
|
||||
// Service commands
|
||||
void getBatteryLevel(u32 messagePointer);
|
||||
|
||||
public:
|
||||
HWCService(Memory& mem, const EmulatorConfig& config) : mem(mem), config(config) {}
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
};
|
||||
} // namespace MCU
|
|
@ -18,11 +18,13 @@ class MICService {
|
|||
void setIirFilter(u32 messagePointer);
|
||||
void setPower(u32 messagePointer);
|
||||
void startSampling(u32 messagePointer);
|
||||
void stopSampling(u32 messagePointer);
|
||||
void theCaptainToadFunction(u32 messagePointer);
|
||||
|
||||
u8 gain = 0; // How loud our microphone input signal is
|
||||
bool micEnabled = false;
|
||||
bool shouldClamp = false;
|
||||
bool isSampling = false;
|
||||
|
||||
public:
|
||||
MICService(Memory& mem) : mem(mem) {}
|
||||
|
|
|
@ -11,6 +11,7 @@ class NDMService {
|
|||
MAKE_LOG_FUNCTION(log, ndmLogger)
|
||||
|
||||
// Service commands
|
||||
void clearHalfAwakeMacFilter(u32 messagePointer);
|
||||
void overrideDefaultDaemons(u32 messagePointer);
|
||||
void resumeDaemons(u32 messagePointer);
|
||||
void resumeScheduler(u32 messagePointer);
|
||||
|
|
18
include/services/news_u.hpp
Normal file
18
include/services/news_u.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
class NewsUService {
|
||||
Handle handle = KernelHandles::NEWS_U;
|
||||
Memory& mem;
|
||||
MAKE_LOG_FUNCTION(log, newsLogger)
|
||||
|
||||
// Service commands
|
||||
|
||||
public:
|
||||
NewsUService(Memory& mem) : mem(mem) {}
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
};
|
|
@ -14,13 +14,38 @@ class NFCService {
|
|||
Kernel& kernel;
|
||||
MAKE_LOG_FUNCTION(log, nfcLogger)
|
||||
|
||||
enum class Old3DSAdapterStatus : u32 {
|
||||
Idle = 0,
|
||||
AttemptingToInitialize = 1,
|
||||
InitializationComplete = 2,
|
||||
Active = 3,
|
||||
};
|
||||
|
||||
enum class TagStatus : u8 {
|
||||
NotInitialized = 0,
|
||||
Initialized = 1,
|
||||
Scanning = 2,
|
||||
InRange = 3,
|
||||
OutOfRange = 4,
|
||||
Loaded = 5,
|
||||
};
|
||||
|
||||
// Kernel events signaled when an NFC tag goes in and out of range respectively
|
||||
std::optional<Handle> tagInRangeEvent, tagOutOfRangeEvent;
|
||||
|
||||
Old3DSAdapterStatus adapterStatus;
|
||||
TagStatus tagStatus;
|
||||
bool initialized = false;
|
||||
|
||||
// Service commands
|
||||
void communicationGetResult(u32 messagePointer);
|
||||
void communicationGetStatus(u32 messagePointer);
|
||||
void initialize(u32 messagePointer);
|
||||
void getTagInRangeEvent(u32 messagePointer);
|
||||
void getTagOutOfRangeEvent(u32 messagePointer);
|
||||
void getTagState(u32 messagePointer);
|
||||
void startCommunication(u32 messagePointer);
|
||||
void stopCommunication(u32 messagePointer);
|
||||
|
||||
public:
|
||||
NFCService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include "config.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
|
@ -10,13 +11,39 @@ class PTMService {
|
|||
Memory& mem;
|
||||
MAKE_LOG_FUNCTION(log, ptmLogger)
|
||||
|
||||
const EmulatorConfig& config;
|
||||
|
||||
// Service commands
|
||||
void configureNew3DSCPU(u32 messagePointer);
|
||||
void getAdapterState(u32 messagePointer);
|
||||
void getBatteryLevel(u32 messagePointer);
|
||||
void getStepHistory(u32 messagePointer);
|
||||
void getTotalStepCount(u32 messagePointer);
|
||||
|
||||
public:
|
||||
PTMService(Memory& mem) : mem(mem) {}
|
||||
PTMService(Memory& mem, const EmulatorConfig& config) : mem(mem), config(config) {}
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
|
||||
// 0% -> 0 (shutting down)
|
||||
// 1-5% -> 1
|
||||
// 6-10% -> 2
|
||||
// 11-30% -> 3
|
||||
// 31-60% -> 4
|
||||
// 61-100% -> 5
|
||||
static constexpr u8 batteryPercentToLevel(u8 percent) {
|
||||
if (percent == 0) {
|
||||
return 0;
|
||||
} else if (percent >= 1 && percent <= 5) {
|
||||
return 1;
|
||||
} else if (percent >= 6 && percent <= 10) {
|
||||
return 2;
|
||||
} else if (percent >= 11 && percent <= 30) {
|
||||
return 3;
|
||||
} else if (percent >= 31 && percent <= 60) {
|
||||
return 4;
|
||||
} else {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -24,14 +24,18 @@
|
|||
#include "services/http.hpp"
|
||||
#include "services/ir_user.hpp"
|
||||
#include "services/ldr_ro.hpp"
|
||||
#include "services/mcu/mcu_hwc.hpp"
|
||||
#include "services/mic.hpp"
|
||||
#include "services/ndm.hpp"
|
||||
#include "services/news_u.hpp"
|
||||
#include "services/nfc.hpp"
|
||||
#include "services/nim.hpp"
|
||||
#include "services/ptm.hpp"
|
||||
#include "services/soc.hpp"
|
||||
#include "services/ssl.hpp"
|
||||
#include "services/y2r.hpp"
|
||||
|
||||
struct EmulatorConfig;
|
||||
// More circular dependencies!!
|
||||
class Kernel;
|
||||
|
||||
|
@ -63,13 +67,17 @@ class ServiceManager {
|
|||
LCDService gsp_lcd;
|
||||
LDRService ldr;
|
||||
MICService mic;
|
||||
NDMService ndm;
|
||||
NewsUService news_u;
|
||||
NFCService nfc;
|
||||
NIMService nim;
|
||||
NDMService ndm;
|
||||
PTMService ptm;
|
||||
SOCService soc;
|
||||
SSLService ssl;
|
||||
Y2RService y2r;
|
||||
|
||||
MCU::HWCService mcu_hwc;
|
||||
|
||||
// "srv:" commands
|
||||
void enableNotification(u32 messagePointer);
|
||||
void getServiceHandle(u32 messagePointer);
|
||||
|
@ -78,7 +86,7 @@ class ServiceManager {
|
|||
void subscribe(u32 messagePointer);
|
||||
|
||||
public:
|
||||
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel);
|
||||
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config);
|
||||
void reset();
|
||||
void initializeFS() { fs.initializeFilesystem(); }
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
#include <cstddef>
|
||||
|
||||
extern unsigned char _shared_font_bin[];
|
||||
extern size_t _shared_font_len;
|
25
include/services/ssl.hpp
Normal file
25
include/services/ssl.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
class SSLService {
|
||||
Handle handle = KernelHandles::SSL;
|
||||
Memory& mem;
|
||||
MAKE_LOG_FUNCTION(log, sslLogger)
|
||||
|
||||
std::mt19937 rng; // Use a Mersenne Twister for RNG since this service is supposed to have better rng than just rand()
|
||||
bool initialized;
|
||||
|
||||
// Service commands
|
||||
void initialize(u32 messagePointer);
|
||||
void generateRandomData(u32 messagePointer);
|
||||
|
||||
public:
|
||||
SSLService(Memory& mem) : mem(mem) {}
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
};
|
|
@ -75,6 +75,7 @@ class Y2RService {
|
|||
void setInputLineWidth(u32 messagePointer);
|
||||
void setInputLines(u32 messagePointer);
|
||||
void setOutputFormat(u32 messagePointer);
|
||||
void setPackageParameter(u32 messagePointer);
|
||||
void setReceiving(u32 messagePointer);
|
||||
void setRotation(u32 messagePointer);
|
||||
void setSendingY(u32 messagePointer);
|
||||
|
|
23
readme.md
23
readme.md
|
@ -8,17 +8,27 @@ Join our Discord server by pressing on the banner below!
|
|||
|
||||
[](https://discord.gg/ZYbugsEmsw)
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
# Download
|
||||
You can download stable builds from the Releases tab, or you can download the latest build from the table below
|
||||
|
||||
|Platform|Status|Download|
|
||||
|--------|------------|--------|
|
||||
|Windows build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Windows_Build/master/Windows%20executable.zip)|
|
||||
|MacOS build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/MacOS_Build/master/MacOS%20Alber%20App%20Bundle.zip)|
|
||||
|Linux build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Linux_Build/master/Linux%20executable.zip)|
|
||||
|
||||
# Compatibility
|
||||
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, performance leaves a bit to be desired mainly thanks to lack of shader acceleration, and most QoL features (including a GUI) are missing.
|
||||
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, and some QoL features (including a GUI) are missing.
|
||||
|
||||
In addition, some games don't quiiite work with the upstream code. A lot of them might need some panics in the source code to be commented out before they work, etc. However, just the fact things can work as well as they do now is promising in itself.
|
||||
|
||||
Check out [this Google spreadsheet](https://docs.google.com/spreadsheets/d/1nWZTzfaMPkZdyhqHEawMRBaP0qSMmQdxrVfAbgapYrM/edit?usp=sharing) for an unofficial compatibility list.
|
||||
# Why?
|
||||
The 3DS emulation scene is already pretty mature, with offerings such as [Citra](https://github.com/citra-emu/citra) which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as:
|
||||
|
||||
- Virtualization. What motivated the creation of this emulator was actually a discussion on whether it is possible to get fast 3DS emulation on low-end hardware such as the Raspberry Pi 4, using the KVM API. At the moment, Panda3DS is powered by dynarmic rather than using virtualization, but this is definitely a concept I want to explore in the future.
|
||||
- Virtualization. What motivated the creation of this emulator was actually a discussion on whether it is possible to get fast 3DS emulation on low-end hardware such as the Raspberry Pi 4, using the KVM API. At the moment, Panda3DS is powered by Dynarmic rather than using virtualization, but this is definitely a concept I want to explore in the future.
|
||||
|
||||
- Debugging, reverse engineering and modding tools. While contributing to [PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) and collaborating with the other developers, I had the chance to find out how useful tools like these can be. They can serve as indispensable tools for the homebrew devs, modders, reverse engineers, as well as emulator developers themselves. Some tools can even become fun toys the casual user can mess around with. As such, I think they can really improve the experience in a project like this. Of course, I'd like to thank @nicolasnoble and the entire Redux team for helping me learn the value of these tools, as well as making me improve as a programmer.
|
||||
|
||||
|
@ -70,6 +80,9 @@ Keyboard & Mouse
|
|||
- Select button Backspace
|
||||
- Touch Screen Left click
|
||||
- Gyroscope Hold right click and swipe your mouse left and right (support is kind of shaky atm, but games that require gyro here and there like Kirby should work)
|
||||
- Pause/Resume F4
|
||||
- Reload F5
|
||||
|
||||
|
||||
Panda3DS also supports controller input using the SDL2 GameController API.
|
||||
|
||||
|
@ -91,6 +104,8 @@ Panda3DS also supports controller input using the SDL2 GameController API.
|
|||
- [MelonDS](https://github.com/melonDS-emu/melonDS): "DS emulator, sorta" - Arisotura
|
||||
- [Kaizen](https://github.com/SimoneN64/Kaizen): Experimental work-in-progress low-level N64 emulator
|
||||
- [ChonkyStation](https://github.com/liuk7071/ChonkyStation): Work-in-progress PlayStation emulator
|
||||
- [shadPS4](https://github.com/georgemoralis/shadPS4): Work-in-progress PS4 emulator by the founder of PCSX, PCSX2 and more
|
||||
- [Hydra](https://github.com/hydra-emu/hydra): Cross-platform GameBoy, NES, N64 and Chip-8 emulator
|
||||
|
||||
# Support
|
||||
If you find this project exciting and want to support the founder, check out [his Patreon page](https://www.patreon.com/wheremyfoodat)
|
||||
|
@ -99,5 +114,5 @@ Keep in mind, funding is only aimed to cover various life costs and support deve
|
|||
|
||||
Nintendo 3DS is a registered trademark of Nintendo Co., Ltd.
|
||||
|
||||

|
||||

|
||||
Here's a panda it go blep
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "config.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
@ -54,7 +55,30 @@ void EmulatorConfig::load(const std::filesystem::path& path) {
|
|||
rendererType = RendererType::OpenGL;
|
||||
}
|
||||
|
||||
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", false);
|
||||
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", true);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.contains("Battery")) {
|
||||
auto batteryResult = toml::expect<toml::value>(data.at("Battery"));
|
||||
if (batteryResult.is_ok()) {
|
||||
auto battery = batteryResult.unwrap();
|
||||
|
||||
chargerPlugged = toml::find_or<toml::boolean>(battery, "ChargerPlugged", true);
|
||||
batteryPercentage = toml::find_or<toml::integer>(battery, "BatteryPercentage", 3);
|
||||
|
||||
// Clamp battery % to [0, 100] to make sure it's a valid value
|
||||
batteryPercentage = std::clamp(batteryPercentage, 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.contains("SD")) {
|
||||
auto sdResult = toml::expect<toml::value>(data.at("SD"));
|
||||
if (sdResult.is_ok()) {
|
||||
auto sd = sdResult.unwrap();
|
||||
|
||||
sdCardInserted = toml::find_or<toml::boolean>(sd, "UseVirtualSD", true);
|
||||
sdWriteProtected = toml::find_or<toml::boolean>(sd, "WriteProtectVirtualSD", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +105,12 @@ void EmulatorConfig::save(const std::filesystem::path& path) {
|
|||
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
|
||||
data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType));
|
||||
|
||||
data["Battery"]["ChargerPlugged"] = chargerPlugged;
|
||||
data["Battery"]["BatteryPercentage"] = batteryPercentage;
|
||||
|
||||
data["SD"]["UseVirtualSD"] = sdCardInserted;
|
||||
data["SD"]["WriteProtectVirtualSD"] = sdWriteProtected;
|
||||
|
||||
std::ofstream file(path, std::ios::out);
|
||||
file << data;
|
||||
file.close();
|
||||
|
|
|
@ -143,7 +143,9 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) {
|
|||
break;
|
||||
case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DPH: recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DPH:
|
||||
case ShaderOpcodes::DPHI:
|
||||
recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::END: recEND(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::EX2: recEX2(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break;
|
||||
|
@ -531,15 +533,17 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) {
|
|||
}
|
||||
|
||||
void ShaderEmitter::recDPH(const PICAShader& shader, u32 instruction) {
|
||||
const bool isDPHI = (instruction >> 26) == ShaderOpcodes::DPHI;
|
||||
|
||||
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||
const u32 src1 = getBits<12, 7>(instruction);
|
||||
const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment
|
||||
const u32 src1 = isDPHI ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction);
|
||||
const u32 src2 = isDPHI ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction);
|
||||
const u32 idx = getBits<19, 2>(instruction);
|
||||
const u32 dest = getBits<21, 5>(instruction);
|
||||
|
||||
// TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA)
|
||||
loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor);
|
||||
loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor);
|
||||
loadRegister<1>(src1_xmm, shader, src1, isDPHI ? 0 : idx, operandDescriptor);
|
||||
loadRegister<2>(src2_xmm, shader, src2, isDPHI ? idx : 0, operandDescriptor);
|
||||
|
||||
// Attach 1.0 to the w component of src1
|
||||
if (haveSSE4_1) {
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
#include "renderer_vk/renderer_vk.hpp"
|
||||
#endif
|
||||
|
||||
constexpr u32 topScreenWidth = 240;
|
||||
constexpr u32 topScreenHeight = 400;
|
||||
|
||||
constexpr u32 bottomScreenWidth = 240;
|
||||
constexpr u32 bottomScreenHeight = 300;
|
||||
|
||||
using namespace Floats;
|
||||
|
||||
// Note: For when we have multiple backends, the GL state manager can stay here and have the constructor for the Vulkan-or-whatever renderer ignore it
|
||||
|
@ -26,24 +32,24 @@ GPU::GPU(Memory& mem, EmulatorConfig& config) : mem(mem), config(config) {
|
|||
|
||||
switch (config.rendererType) {
|
||||
case RendererType::Null: {
|
||||
renderer.reset(new RendererNull(*this, regs));
|
||||
renderer.reset(new RendererNull(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
|
||||
case RendererType::Software: {
|
||||
renderer.reset(new RendererSw(*this, regs));
|
||||
renderer.reset(new RendererSw(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||
case RendererType::OpenGL: {
|
||||
renderer.reset(new RendererGL(*this, regs));
|
||||
renderer.reset(new RendererGL(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef PANDA3DS_ENABLE_VULKAN
|
||||
case RendererType::Vulkan: {
|
||||
renderer.reset(new RendererVK(*this, regs));
|
||||
renderer.reset(new RendererVK(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
@ -78,6 +84,27 @@ void GPU::reset() {
|
|||
e.config2 = 0;
|
||||
}
|
||||
|
||||
// Initialize the framebuffer registers. Values taken from Citra.
|
||||
|
||||
using namespace PICA::ExternalRegs;
|
||||
// Top screen addresses and dimentions.
|
||||
externalRegs[Framebuffer0AFirstAddr] = 0x181E6000;
|
||||
externalRegs[Framebuffer0ASecondAddr] = 0x1822C800;
|
||||
externalRegs[Framebuffer0BFirstAddr] = 0x18273000;
|
||||
externalRegs[Framebuffer0BSecondAddr] = 0x182B9800;
|
||||
externalRegs[Framebuffer0Size] = (topScreenHeight << 16) | topScreenWidth;
|
||||
externalRegs[Framebuffer0Stride] = 720;
|
||||
externalRegs[Framebuffer0Config] = static_cast<u32>(PICA::ColorFmt::RGB8);
|
||||
externalRegs[Framebuffer0Select] = 0;
|
||||
|
||||
// Bottom screen addresses and dimentions.
|
||||
externalRegs[Framebuffer1AFirstAddr] = 0x1848F000;
|
||||
externalRegs[Framebuffer1ASecondAddr] = 0x184C7800;
|
||||
externalRegs[Framebuffer1Size] = (bottomScreenHeight << 16) | bottomScreenWidth;
|
||||
externalRegs[Framebuffer1Stride] = 720;
|
||||
externalRegs[Framebuffer1Config] = static_cast<u32>(PICA::ColorFmt::RGB8);
|
||||
externalRegs[Framebuffer1Select] = 0;
|
||||
|
||||
renderer->reset();
|
||||
}
|
||||
|
||||
|
@ -321,15 +348,17 @@ PICA::Vertex GPU::getImmediateModeVertex() {
|
|||
|
||||
// Run VS and return vertex data. TODO: Don't hardcode offsets for each attribute
|
||||
shaderUnit.vs.run();
|
||||
std::memcpy(&v.s.positions, &shaderUnit.vs.outputs[0], sizeof(vec4f));
|
||||
std::memcpy(&v.s.colour, &shaderUnit.vs.outputs[1], sizeof(vec4f));
|
||||
std::memcpy(&v.s.texcoord0, &shaderUnit.vs.outputs[2], 2 * sizeof(f24));
|
||||
|
||||
printf(
|
||||
"(x, y, z, w) = (%f, %f, %f, %f)\n", (double)v.s.positions[0], (double)v.s.positions[1], (double)v.s.positions[2], (double)v.s.positions[3]
|
||||
);
|
||||
printf("(r, g, b, a) = (%f, %f, %f, %f)\n", (double)v.s.colour[0], (double)v.s.colour[1], (double)v.s.colour[2], (double)v.s.colour[3]);
|
||||
printf("(u, v ) = (%f, %f)\n", (double)v.s.texcoord0[0], (double)v.s.texcoord0[1]);
|
||||
// Map shader outputs to fixed function properties
|
||||
const u32 totalShaderOutputs = regs[PICA::InternalRegs::ShaderOutputCount] & 7;
|
||||
for (int i = 0; i < totalShaderOutputs; i++) {
|
||||
const u32 config = regs[PICA::InternalRegs::ShaderOutmap0 + i];
|
||||
|
||||
for (int j = 0; j < 4; j++) { // pls unroll
|
||||
const u32 mapping = (config >> (j * 8)) & 0x1F;
|
||||
v.raw[mapping] = shaderUnit.vs.outputs[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
|
|
@ -19,11 +19,36 @@ void GPU::writeReg(u32 address, u32 value) {
|
|||
if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers
|
||||
const u32 index = (address - 0x1EF01000) / sizeof(u32);
|
||||
writeInternalReg(index, value, 0xffffffff);
|
||||
} else if (address >= 0x1EF00004 && address < 0x1EF01000) {
|
||||
const u32 index = (address - 0x1EF00004) / sizeof(u32);
|
||||
writeExternalReg(index, value);
|
||||
} else {
|
||||
log("Ignoring write to external GPU register %08X. Value: %08X\n", address, value);
|
||||
log("Ignoring write to unknown GPU register %08X. Value: %08X\n", address, value);
|
||||
}
|
||||
}
|
||||
|
||||
u32 GPU::readExternalReg(u32 index) {
|
||||
using namespace PICA::ExternalRegs;
|
||||
|
||||
if (index > 0x1000) [[unlikely]] {
|
||||
Helpers::panic("Tried to read invalid external GPU register. Index: %X\n", index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return externalRegs[index];
|
||||
}
|
||||
|
||||
void GPU::writeExternalReg(u32 index, u32 value) {
|
||||
using namespace PICA::ExternalRegs;
|
||||
|
||||
if (index > 0x1000) [[unlikely]] {
|
||||
Helpers::panic("Tried to write to invalid external GPU register. Index: %X, value: %08X\n", index, value);
|
||||
return;
|
||||
}
|
||||
|
||||
externalRegs[index] = value;
|
||||
}
|
||||
|
||||
u32 GPU::readInternalReg(u32 index) {
|
||||
using namespace PICA::InternalRegs;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ void PICAShader::run() {
|
|||
case ShaderOpcodes::NOP: break; // Do nothing
|
||||
case ShaderOpcodes::RCP: rcp(instruction); break;
|
||||
case ShaderOpcodes::RSQ: rsq(instruction); break;
|
||||
case ShaderOpcodes::SGE: sge(instruction); break;
|
||||
case ShaderOpcodes::SGEI: sgei(instruction); break;
|
||||
case ShaderOpcodes::SLT: slt(instruction); break;
|
||||
case ShaderOpcodes::SLTI: slti(instruction); break;
|
||||
|
@ -517,6 +518,26 @@ void PICAShader::slt(u32 instruction) {
|
|||
}
|
||||
}
|
||||
|
||||
void PICAShader::sge(u32 instruction) {
|
||||
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
||||
u32 src1 = getBits<12, 7>(instruction);
|
||||
const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment
|
||||
const u32 idx = getBits<19, 2>(instruction);
|
||||
const u32 dest = getBits<21, 5>(instruction);
|
||||
|
||||
src1 = getIndexedSource(src1, idx);
|
||||
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
|
||||
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
|
||||
auto& destVector = getDest(dest);
|
||||
|
||||
u32 componentMask = operandDescriptor & 0xf;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (componentMask & (1 << i)) {
|
||||
destVector[3 - i] = srcVec1[3 - i] >= srcVec2[3 - i] ? f24::fromFloat32(1.0) : f24::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PICAShader::sgei(u32 instruction) {
|
||||
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
||||
const u32 src1 = getBits<14, 5>(instruction);
|
||||
|
|
|
@ -20,9 +20,11 @@ HorizonResult ExtSaveDataArchive::createFile(const FSPath& path, u64 size) {
|
|||
// Create a file of size "size" by creating an empty one, seeking to size - 1 and just writing a 0 there
|
||||
IOFile file(p.string().c_str(), "wb");
|
||||
if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
||||
file.close();
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
file.close();
|
||||
return Result::FS::FileTooLarge;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,14 +19,17 @@ HorizonResult SaveDataArchive::createFile(const FSPath& path, u64 size) {
|
|||
|
||||
// If the size is 0, leave the file empty and return success
|
||||
if (size == 0) {
|
||||
file.close();
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
// If it is not empty, seek to size - 1 and write a 0 to create a file of size "size"
|
||||
else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
||||
file.close();
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
file.close();
|
||||
return Result::FS::FileTooLarge;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
namespace PathType {
|
||||
enum : u32 {
|
||||
RomFS = 0,
|
||||
ExeFS = 2
|
||||
ExeFS = 2,
|
||||
UpdateRomFS = 5,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -33,8 +34,8 @@ FileDescriptor SelfNCCHArchive::openFile(const FSPath& path, const FilePerms& pe
|
|||
// Where to read the file from. (https://www.3dbrew.org/wiki/Filesystem_services#SelfNCCH_File_Path_Data_Format)
|
||||
// We currently only know how to read from an NCCH's RomFS, ie type = 0
|
||||
const u32 type = *(u32*)&path.binary[0]; // TODO: Get rid of UB here
|
||||
if (type != PathType::RomFS && type != PathType::ExeFS) {
|
||||
Helpers::panic("Read from NCCH's non-RomFS & non-exeFS section!");
|
||||
if (type != PathType::RomFS && type != PathType::ExeFS && type != PathType::UpdateRomFS) {
|
||||
Helpers::panic("Read from NCCH's non-RomFS & non-exeFS section! Path type: %d", type);
|
||||
}
|
||||
|
||||
return NoFile; // No file descriptor needed for RomFS
|
||||
|
@ -98,8 +99,23 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
|||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
||||
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
|
||||
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
|
||||
case PathType::UpdateRomFS: {
|
||||
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
|
||||
|
||||
const u64 romFSSize = cxi->romFS.size;
|
||||
const u64 romFSOffset = cxi->romFS.offset;
|
||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
}
|
||||
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
||||
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
||||
}
|
||||
|
||||
std::unique_ptr<u8[]> data(new u8[size]);
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace IVFC {
|
|||
|
||||
// According to 3DBrew, this is usually the case but not guaranteed
|
||||
if (ivfcActualSize != ivfcDescriptorSize) {
|
||||
printf("IVFC descriptor size mismatch: %lx != %lx\n", ivfcActualSize, ivfcDescriptorSize);
|
||||
printf("IVFC descriptor size mismatch: %llx != %llx\n", ivfcActualSize, ivfcDescriptorSize);
|
||||
}
|
||||
|
||||
if (magicIdentifier == 0x10000 && ivfcActualSize != 0x5C) {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#include "kernel_types.hpp"
|
||||
#include "cpu.hpp"
|
||||
|
||||
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu)
|
||||
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this) {
|
||||
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
|
||||
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) {
|
||||
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
|
||||
mutexHandles.reserve(8);
|
||||
portHandles.reserve(32);
|
||||
|
@ -35,6 +35,8 @@ void Kernel::serviceSVC(u32 svc) {
|
|||
case 0x0A: svcSleepThread(); break;
|
||||
case 0x0B: getThreadPriority(); break;
|
||||
case 0x0C: setThreadPriority(); break;
|
||||
case 0x0F: getThreadIdealProcessor(); break;
|
||||
case 0x11: getCurrentProcessorNumber(); break;
|
||||
case 0x13: svcCreateMutex(); break;
|
||||
case 0x14: svcReleaseMutex(); break;
|
||||
case 0x15: svcCreateSemaphore(); break;
|
||||
|
@ -42,6 +44,10 @@ void Kernel::serviceSVC(u32 svc) {
|
|||
case 0x17: svcCreateEvent(); break;
|
||||
case 0x18: svcSignalEvent(); break;
|
||||
case 0x19: svcClearEvent(); break;
|
||||
case 0x1A: svcCreateTimer(); break;
|
||||
case 0x1B: svcSetTimer(); break;
|
||||
case 0x1C: svcCancelTimer(); break;
|
||||
case 0x1D: svcClearTimer(); break;
|
||||
case 0x1E: createMemoryBlock(); break;
|
||||
case 0x1F: mapMemoryBlock(); break;
|
||||
case 0x21: createAddressArbiter(); break;
|
||||
|
@ -154,7 +160,7 @@ void Kernel::reset() {
|
|||
// Make main thread object. We do not have to set the entrypoint and SP for it as the ROM loader does.
|
||||
// Main thread seems to have a priority of 0x30. TODO: This creates a dummy context for thread 0,
|
||||
// which is thankfully not used. Maybe we should prevent this
|
||||
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, -2, 0, ThreadStatus::Running);
|
||||
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, ProcessorID::Default, 0, ThreadStatus::Running);
|
||||
currentThreadIndex = 0;
|
||||
setupIdleThread();
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "kernel.hpp"
|
||||
#include "services/shared_font.hpp"
|
||||
|
||||
namespace Operation {
|
||||
enum : u32 {
|
||||
|
@ -137,7 +136,7 @@ void Kernel::mapMemoryBlock() {
|
|||
break;
|
||||
|
||||
case KernelHandles::FontSharedMemHandle:
|
||||
std::memcpy(ptr, _shared_font_bin, _shared_font_len);
|
||||
mem.copySharedFont(ptr);
|
||||
break;
|
||||
|
||||
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
|
||||
|
|
|
@ -109,7 +109,7 @@ void Kernel::sendSyncRequest() {
|
|||
// If we're actually communicating with a port
|
||||
const auto session = getObject(handle, KernelObjectType::Session);
|
||||
if (session == nullptr) [[unlikely]] {
|
||||
Helpers::panic("SendSyncRequest: Invalid handle");
|
||||
Helpers::warn("SendSyncRequest: Invalid handle");
|
||||
regs[0] = Result::Kernel::InvalidHandle;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ s32 Kernel::getCurrentResourceValue(const KernelObject* limit, u32 resourceName)
|
|||
u32 Kernel::getMaxForResource(const KernelObject* limit, u32 resourceName) {
|
||||
switch (resourceName) {
|
||||
case ResourceType::Commit: return appResourceLimits.maxCommit;
|
||||
case ResourceType::Thread: return appResourceLimits.maxThreads;
|
||||
default: Helpers::panic("Attempted to get the max of unknown kernel resource: %d\n", resourceName);
|
||||
}
|
||||
}
|
|
@ -106,7 +106,7 @@ void Kernel::rescheduleThreads() {
|
|||
}
|
||||
|
||||
// Internal OS function to spawn a thread
|
||||
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg, ThreadStatus status) {
|
||||
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status) {
|
||||
int index; // Index of the created thread in the threads array
|
||||
|
||||
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
|
||||
|
@ -389,8 +389,12 @@ void Kernel::createThread() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (id < -2 || id > 3) {
|
||||
Helpers::panic("Invalid processor ID in CreateThread");
|
||||
}
|
||||
|
||||
regs[0] = Result::Success;
|
||||
regs[1] = makeThread(entrypoint, initialSP, priority, id, arg, ThreadStatus::Ready);
|
||||
regs[1] = makeThread(entrypoint, initialSP, priority, static_cast<ProcessorID>(id), arg, ThreadStatus::Ready);
|
||||
requireReschedule();
|
||||
}
|
||||
|
||||
|
@ -441,6 +445,15 @@ void Kernel::getThreadPriority() {
|
|||
}
|
||||
}
|
||||
|
||||
void Kernel::getThreadIdealProcessor() {
|
||||
const Handle handle = regs[1]; // Thread handle
|
||||
logSVC("GetThreadIdealProcessor (handle = %X)\n", handle);
|
||||
|
||||
// TODO: Not documented what this is or what it does. Citra doesn't implement it at all. Return AppCore as the ideal processor for now
|
||||
regs[0] = Result::Success;
|
||||
regs[1] = static_cast<u32>(ProcessorID::AppCore);
|
||||
}
|
||||
|
||||
void Kernel::setThreadPriority() {
|
||||
const Handle handle = regs[0];
|
||||
const u32 priority = regs[1];
|
||||
|
@ -468,6 +481,33 @@ void Kernel::setThreadPriority() {
|
|||
requireReschedule();
|
||||
}
|
||||
|
||||
void Kernel::getCurrentProcessorNumber() {
|
||||
logSVC("GetCurrentProcessorNumber()\n");
|
||||
const ProcessorID id = threads[currentThreadIndex].processorID;
|
||||
s32 ret;
|
||||
|
||||
// Until we properly implement per-core schedulers, return whatever processor ID passed to svcCreateThread
|
||||
switch (id) {
|
||||
// TODO: This is picked from exheader
|
||||
case ProcessorID::Default:
|
||||
ret = static_cast<s32>(ProcessorID::AppCore);
|
||||
break;
|
||||
|
||||
case ProcessorID::AllCPUs:
|
||||
ret = static_cast<s32>(ProcessorID::AppCore);
|
||||
Helpers::warn("GetCurrentProcessorNumber on thread created to run on all CPUs...?\n");
|
||||
break;
|
||||
|
||||
default: ret = static_cast<s32>(id); break;
|
||||
}
|
||||
|
||||
if (ret != static_cast<s32>(ProcessorID::AppCore)) {
|
||||
Helpers::warn("GetCurrentProcessorNumber: Thread not running on appcore\n");
|
||||
}
|
||||
|
||||
regs[0] = static_cast<u32>(ret);
|
||||
}
|
||||
|
||||
void Kernel::exitThread() {
|
||||
logSVC("ExitThread\n");
|
||||
|
||||
|
|
6
src/core/kernel/timers.cpp
Normal file
6
src/core/kernel/timers.cpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "kernel.hpp"
|
||||
|
||||
void Kernel::svcCreateTimer() { Helpers::panic("Kernel::CreateTimer"); }
|
||||
void Kernel::svcSetTimer() { Helpers::panic("Kernel::SetTimer"); }
|
||||
void Kernel::svcClearTimer() { Helpers::panic("Kernel::ClearTimer"); }
|
||||
void Kernel::svcCancelTimer() { Helpers::panic("Kernel::CancelTimer"); }
|
|
@ -63,5 +63,7 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
|
|||
allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x);
|
||||
}
|
||||
|
||||
// ELF can't specify a region, make it default to USA
|
||||
region = Regions::USA;
|
||||
return static_cast<u32>(reader.get_entry());
|
||||
}
|
|
@ -131,6 +131,13 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!aesEngine.haveGenerator()) {
|
||||
Helpers::panic(
|
||||
"Loading an encrypted ROM but your AES keys don't seem to provide the \"generator\" constant which Panda3DS requires for decryption\n"
|
||||
"Please add it to your aes_keys.txt in a line like \"generator=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\" where the Xs are replaced with the actual generator constant value"
|
||||
);
|
||||
}
|
||||
|
||||
if (!gotCryptoKeys) {
|
||||
Helpers::panic("ROM is encrypted but it seems we couldn't get either the primary or the secondary key");
|
||||
return false;
|
||||
|
@ -209,9 +216,24 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
codeFile.resize(fileSize);
|
||||
readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
}
|
||||
} else if (std::strcmp(name, "icon") == 0) {
|
||||
// Parse icon file to extract region info and more in the future (logo, etc)
|
||||
std::vector<u8> tmp;
|
||||
tmp.resize(fileSize);
|
||||
readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
|
||||
if (!parseSMDH(tmp)) {
|
||||
printf("Failed to parse SMDH!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no region has been detected for CXI, set the region to USA by default
|
||||
if (!region.has_value() && partitionIndex == 0) {
|
||||
printf("No region detected for CXI, defaulting to USA\n");
|
||||
region = Regions::USA;
|
||||
}
|
||||
|
||||
if (hasRomFS()) {
|
||||
printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size);
|
||||
|
@ -221,6 +243,52 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
return true;
|
||||
}
|
||||
|
||||
bool NCCH::parseSMDH(const std::vector<u8>& smdh) {
|
||||
if (smdh.size() < 0x36C0) {
|
||||
printf("The cartridge .icon file is too small, considered invalid. Must be 0x36C0 bytes minimum\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (char(smdh[0]) != 'S' || char(smdh[1]) != 'M' || char(smdh[2]) != 'D' || char(smdh[3]) != 'H') {
|
||||
printf("Invalid SMDH magic!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bitmask showing which regions are allowed.
|
||||
// https://www.3dbrew.org/wiki/SMDH#Region_Lockout
|
||||
const u32 regionMasks = *(u32*)&smdh[0x2018];
|
||||
// Detect when games are region free (ie all regions are allowed) for future use
|
||||
[[maybe_unused]] const bool isRegionFree = (regionMasks & 0x7f) == 0x7f;
|
||||
|
||||
// See which countries are allowed
|
||||
const bool japan = (regionMasks & 0x1) != 0;
|
||||
const bool northAmerica = (regionMasks & 0x2) != 0;
|
||||
const bool europe = (regionMasks & 0x4) != 0;
|
||||
const bool australia = (regionMasks & 0x8) != 0;
|
||||
const bool china = (regionMasks & 0x10) != 0;
|
||||
const bool korea = (regionMasks & 0x20) != 0;
|
||||
const bool taiwan = (regionMasks & 0x40) != 0;
|
||||
|
||||
// Based on the allowed regions, set the autodetected 3DS region. We currently prefer English-speaking regions for practical purposes.
|
||||
// But this should be configurable later.
|
||||
if (northAmerica) {
|
||||
region = Regions::USA;
|
||||
} else if (europe) {
|
||||
region = Regions::Europe;
|
||||
} else if (australia) {
|
||||
region = Regions::Australia;
|
||||
} else if (japan) {
|
||||
region = Regions::Japan;
|
||||
} else if (korea) {
|
||||
region = Regions::Korea;
|
||||
} else if (china) {
|
||||
region = Regions::China;
|
||||
} else if (taiwan) {
|
||||
region = Regions::Taiwan;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<bool, Crypto::AESKey> NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) {
|
||||
Crypto::AESKey result;
|
||||
|
||||
|
|
|
@ -11,6 +11,12 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
|
|||
printf("Data address = %08X, size = %08X\n", cxi.data.address, cxi.data.size);
|
||||
printf("Stack size: %08X\n", cxi.stackSize);
|
||||
|
||||
static constexpr std::array<const char*, 7> regionNames = {"Japan", "North America", "Europe", "Australia", "China", "Korea", "Taiwan" };
|
||||
|
||||
// Set autodetected 3DS region to one of the values allowed by the CXI's SMDH
|
||||
region = cxi.region.value();
|
||||
printf("Console region autodetected to: %s\n", regionNames[static_cast<size_t>(region)]);
|
||||
|
||||
if (!isAligned(cxi.stackSize)) {
|
||||
Helpers::warn("CXI has a suspicious stack size of %08X which is not a multiple of 4KB", cxi.stackSize);
|
||||
}
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
|
||||
#include <cassert>
|
||||
#include <chrono> // For time since epoch
|
||||
#include <cmrc/cmrc.hpp>
|
||||
#include <ctime>
|
||||
|
||||
#include "config_mem.hpp"
|
||||
#include "resource_limits.hpp"
|
||||
#include "services/ptm.hpp"
|
||||
|
||||
CMRC_DECLARE(ConsoleFonts);
|
||||
|
||||
using namespace KernelMemoryTypes;
|
||||
|
||||
Memory::Memory(u64& cpuTicks) : cpuTicks(cpuTicks) {
|
||||
Memory::Memory(u64& cpuTicks, const EmulatorConfig& config) : cpuTicks(cpuTicks), config(config) {
|
||||
fcram = new uint8_t[FCRAM_SIZE]();
|
||||
dspRam = new uint8_t[DSP_RAM_SIZE]();
|
||||
|
||||
|
@ -45,6 +49,12 @@ void Memory::reset() {
|
|||
|
||||
// Initialize shared memory blocks and reserve memory for them
|
||||
for (auto& e : sharedMemBlocks) {
|
||||
if (e.handle == KernelHandles::FontSharedMemHandle) {
|
||||
// Read font size from the cmrc filesystem the font is stored in
|
||||
auto fonts = cmrc::ConsoleFonts::get_filesystem();
|
||||
e.size = fonts.open("CitraSharedFontUSRelocated.bin").size();
|
||||
}
|
||||
|
||||
e.mapped = false;
|
||||
e.paddr = allocateSysMemory(e.size);
|
||||
}
|
||||
|
@ -59,6 +69,9 @@ void Memory::reset() {
|
|||
readTable[i + initialPage] = pointer;
|
||||
writeTable[i + initialPage] = pointer;
|
||||
}
|
||||
|
||||
// Later adjusted based on ROM header when possible
|
||||
region = Regions::USA;
|
||||
}
|
||||
|
||||
bool Memory::allocateMainThreadStack(u32 size) {
|
||||
|
@ -82,7 +95,18 @@ u8 Memory::read8(u32 vaddr) {
|
|||
return *(u8*)(pointer + offset);
|
||||
} else {
|
||||
switch (vaddr) {
|
||||
case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars);
|
||||
case ConfigMem::BatteryState: {
|
||||
// Set by the PTM module
|
||||
// Charger plugged: Shows whether the charger is plugged
|
||||
// Charging: Shows whether the charger is plugged and the console is actually charging, ie the battery is not full
|
||||
// BatteryLevel: A battery level calculated via PTM::GetBatteryLevel
|
||||
// These are all assembled into a bitfield and returned via config memory
|
||||
const bool chargerPlugged = config.chargerPlugged;
|
||||
const bool charging = config.chargerPlugged && (config.batteryPercentage < 100);
|
||||
const auto batteryLevel = static_cast<BatteryLevel>(PTMService::batteryPercentToLevel(config.batteryPercentage));
|
||||
|
||||
return getBatteryState(chargerPlugged, charging, batteryLevel);
|
||||
}
|
||||
case ConfigMem::EnvInfo: return envInfo;
|
||||
case ConfigMem::HardwareType: return ConfigMem::HardwareCodes::Product;
|
||||
case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff);
|
||||
|
@ -149,8 +173,14 @@ u32 Memory::read32(u32 vaddr) {
|
|||
|
||||
default:
|
||||
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
|
||||
static int shutUpCounter = 0;
|
||||
if (shutUpCounter < 5) { // Stop spamming about VRAM reads after the first 5
|
||||
shutUpCounter++;
|
||||
Helpers::warn("VRAM read!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Properly handle framebuffer readbacks and the like
|
||||
return *(u32*)&vram[vaddr - VirtualAddrs::VramStart];
|
||||
}
|
||||
|
||||
Helpers::panic("Unimplemented 32-bit read, addr: %08X", vaddr);
|
||||
|
@ -472,3 +502,15 @@ u64 Memory::timeSince3DSEpoch() {
|
|||
milliseconds ms = duration_cast<milliseconds>(seconds(rawTime + timezoneDifference + offset));
|
||||
return ms.count();
|
||||
}
|
||||
|
||||
Regions Memory::getConsoleRegion() {
|
||||
// TODO: Let the user force the console region as they want
|
||||
// For now we pick one based on the ROM header
|
||||
return region;
|
||||
}
|
||||
|
||||
void Memory::copySharedFont(u8* pointer) {
|
||||
auto fonts = cmrc::ConsoleFonts::get_filesystem();
|
||||
auto font = fonts.open("CitraSharedFontUSRelocated.bin");
|
||||
std::memcpy(pointer, font.begin(), font.size());
|
||||
}
|
|
@ -10,6 +10,15 @@ void GLStateManager::resetBlend() {
|
|||
OpenGL::setLogicOp(GL_COPY);
|
||||
}
|
||||
|
||||
void GLStateManager::resetClearing() {
|
||||
clearRed = 0.f;
|
||||
clearBlue = 0.f;
|
||||
clearGreen = 0.f;
|
||||
clearAlpha = 1.f;
|
||||
|
||||
OpenGL::setClearColor(clearRed, clearBlue, clearGreen, clearAlpha);
|
||||
}
|
||||
|
||||
void GLStateManager::resetClipping() {
|
||||
// Disable all (supported) clip planes
|
||||
enabledClipPlanes = 0;
|
||||
|
@ -64,6 +73,7 @@ void GLStateManager::resetProgram() {
|
|||
|
||||
void GLStateManager::reset() {
|
||||
resetBlend();
|
||||
resetClearing();
|
||||
resetClipping();
|
||||
resetColourMask();
|
||||
resetDepth();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "PICA/float_types.hpp"
|
||||
#include "PICA/gpu.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "math_util.hpp"
|
||||
|
||||
CMRC_DECLARE(RendererGL);
|
||||
|
||||
|
@ -111,6 +112,7 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
|
|||
|
||||
dummyVBO.create();
|
||||
dummyVAO.create();
|
||||
gl.disableScissor();
|
||||
|
||||
// Create texture and framebuffer for the 3DS screen
|
||||
const u32 screenTextureWidth = 400; // Top screen is 400 pixels wide, bottom is 320
|
||||
|
@ -119,6 +121,24 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
|
|||
glGenTextures(1, &lightLUTTextureArray);
|
||||
|
||||
auto prevTexture = OpenGL::getTex2D();
|
||||
|
||||
// Create a plain black texture for when a game reads an invalid texture. It is common for games to configure the PICA to read texture info from NULL.
|
||||
// Some games that do this are Pokemon X, Cars 2, Tomodachi Life, and more. We bind the texture to an FBO, clear it, and free the FBO
|
||||
blankTexture.create(8, 8, GL_RGBA8);
|
||||
blankTexture.bind();
|
||||
blankTexture.setMinFilter(OpenGL::Linear);
|
||||
blankTexture.setMagFilter(OpenGL::Linear);
|
||||
|
||||
OpenGL::Framebuffer dummyFBO;
|
||||
dummyFBO.createWithDrawTexture(blankTexture); // Create FBO and bind our texture to it
|
||||
dummyFBO.bind(OpenGL::DrawFramebuffer);
|
||||
|
||||
// Clear the texture and then delete FBO
|
||||
OpenGL::setViewport(8, 8);
|
||||
gl.setClearColour(0.0, 0.0, 0.0, 1.0);
|
||||
OpenGL::clearColor();
|
||||
dummyFBO.free();
|
||||
|
||||
screenTexture.create(screenTextureWidth, screenTextureHeight, GL_RGBA8);
|
||||
screenTexture.bind();
|
||||
screenTexture.setMinFilter(OpenGL::Linear);
|
||||
|
@ -128,13 +148,14 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
|
|||
screenFramebuffer.createWithDrawTexture(screenTexture);
|
||||
screenFramebuffer.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Helpers::panic("Incomplete framebuffer");
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
Helpers::panic("Incomplete framebuffer");
|
||||
}
|
||||
|
||||
// TODO: This should not clear the framebuffer contents. It should load them from VRAM.
|
||||
GLint oldViewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, oldViewport);
|
||||
OpenGL::setViewport(screenTextureWidth, screenTextureHeight);
|
||||
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
OpenGL::clearColor();
|
||||
OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
|
||||
|
||||
|
@ -280,9 +301,17 @@ void RendererGL::bindTexturesToSlots() {
|
|||
u32 format = regs[ioBase + (i == 0 ? 13 : 5)] & 0xF;
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
|
||||
if (addr != 0) [[likely]] {
|
||||
Texture targetTex(addr, static_cast<PICA::TextureFmt>(format), width, height, config);
|
||||
OpenGL::Texture tex = getTexture(targetTex);
|
||||
tex.bind();
|
||||
} else {
|
||||
// Mapping a texture from NULL. PICA seems to read the last sampled colour, but for now we will display a black texture instead since it is far easier.
|
||||
// Games that do this don't really care what it does, they just expect the PICA to not crash, since it doesn't have a PU/MMU and can do all sorts of
|
||||
// Weird invalid memory accesses without crashing
|
||||
blankTexture.bind();
|
||||
}
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + 3);
|
||||
|
@ -330,8 +359,8 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
|||
}
|
||||
|
||||
setupBlending();
|
||||
OpenGL::Framebuffer poop = getColourFBO();
|
||||
poop.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
auto poop = getColourBuffer(colourBufferLoc, colourBufferFormat, fbSize[0], fbSize[1]);
|
||||
poop->fbo.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
|
||||
const u32 depthControl = regs[PICA::InternalRegs::DepthAndColorMask];
|
||||
const bool depthWrite = regs[PICA::InternalRegs::DepthBufferWrite];
|
||||
|
@ -373,10 +402,12 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
|||
updateLightingLUT();
|
||||
}
|
||||
|
||||
// TODO: Actually use this
|
||||
GLsizei viewportWidth = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0f);
|
||||
GLsizei viewportHeight = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0f);
|
||||
OpenGL::setViewport(viewportWidth, viewportHeight);
|
||||
const GLsizei viewportX = regs[PICA::InternalRegs::ViewportXY] & 0x3ff;
|
||||
const GLsizei viewportY = (regs[PICA::InternalRegs::ViewportXY] >> 16) & 0x3ff;
|
||||
const GLsizei viewportWidth = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0f);
|
||||
const GLsizei viewportHeight = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0f);
|
||||
const auto rect = poop->getSubRect(colourBufferLoc, fbSize[0], fbSize[1]);
|
||||
OpenGL::setViewport(rect.left + viewportX, rect.bottom + viewportY, viewportWidth, viewportHeight);
|
||||
|
||||
const u32 stencilConfig = regs[PICA::InternalRegs::StencilTest];
|
||||
const bool stencilEnable = getBit<0>(stencilConfig);
|
||||
|
@ -411,6 +442,42 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
|||
|
||||
void RendererGL::display() {
|
||||
gl.disableScissor();
|
||||
gl.disableBlend();
|
||||
gl.disableDepth();
|
||||
gl.disableScissor();
|
||||
// This will work fine whether or not logic ops are enabled. We set logic op to copy instead of disabling to avoid state changes
|
||||
gl.setLogicOp(GL_COPY);
|
||||
gl.setColourMask(true, true, true, true);
|
||||
gl.useProgram(displayProgram);
|
||||
gl.bindVAO(dummyVAO);
|
||||
|
||||
gl.disableClipPlane(0);
|
||||
gl.disableClipPlane(1);
|
||||
|
||||
screenFramebuffer.bind(OpenGL::DrawFramebuffer);
|
||||
gl.setClearColour(0.f, 0.f, 0.f, 1.f);
|
||||
OpenGL::clearColor();
|
||||
|
||||
using namespace PICA::ExternalRegs;
|
||||
const u32 topActiveFb = externalRegs[Framebuffer0Select] & 1;
|
||||
const u32 topScreenAddr = externalRegs[topActiveFb == 0 ? Framebuffer0AFirstAddr : Framebuffer0ASecondAddr];
|
||||
auto topScreen = colourBufferCache.findFromAddress(topScreenAddr);
|
||||
|
||||
if (topScreen) {
|
||||
topScreen->get().texture.bind();
|
||||
OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport
|
||||
OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen
|
||||
}
|
||||
|
||||
const u32 bottomActiveFb = externalRegs[Framebuffer1Select] & 1;
|
||||
const u32 bottomScreenAddr = externalRegs[bottomActiveFb == 0 ? Framebuffer1AFirstAddr : Framebuffer1ASecondAddr];
|
||||
auto bottomScreen = colourBufferCache.findFromAddress(bottomScreenAddr);
|
||||
|
||||
if (bottomScreen) {
|
||||
bottomScreen->get().texture.bind();
|
||||
OpenGL::setViewport(40, 0, 320, 240);
|
||||
OpenGL::draw(OpenGL::TriangleStrip, 4);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
screenFramebuffer.bind(OpenGL::ReadFramebuffer);
|
||||
|
@ -428,8 +495,9 @@ void RendererGL::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 co
|
|||
const float b = getBits<8, 8>(value) / 255.0f;
|
||||
const float a = (value & 0xff) / 255.0f;
|
||||
color->get().fbo.bind(OpenGL::DrawFramebuffer);
|
||||
|
||||
gl.setColourMask(true, true, true, true);
|
||||
OpenGL::setClearColor(r, g, b, a);
|
||||
gl.setClearColour(r, g, b, a);
|
||||
OpenGL::clearColor();
|
||||
return;
|
||||
}
|
||||
|
@ -511,42 +579,152 @@ OpenGL::Texture RendererGL::getTexture(Texture& tex) {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: The GPU format has RGB5551 and RGB655 swapped compared to internal regs format
|
||||
PICA::ColorFmt ToColorFmt(u32 format) {
|
||||
switch (format) {
|
||||
case 2: return PICA::ColorFmt::RGB565;
|
||||
case 3: return PICA::ColorFmt::RGBA5551;
|
||||
default: return static_cast<PICA::ColorFmt>(format);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererGL::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
const u32 inputWidth = inputSize & 0xffff;
|
||||
const u32 inputGap = inputSize >> 16;
|
||||
const u32 inputHeight = inputSize >> 16;
|
||||
const auto inputFormat = ToColorFmt(Helpers::getBits<8, 3>(flags));
|
||||
const auto outputFormat = ToColorFmt(Helpers::getBits<12, 3>(flags));
|
||||
const bool verticalFlip = flags & 1;
|
||||
const PICA::Scaling scaling = static_cast<PICA::Scaling>(Helpers::getBits<24, 2>(flags));
|
||||
|
||||
const u32 outputWidth = outputSize & 0xffff;
|
||||
const u32 outputGap = outputSize >> 16;
|
||||
u32 outputWidth = outputSize & 0xffff;
|
||||
u32 outputHeight = outputSize >> 16;
|
||||
|
||||
auto framebuffer = colourBufferCache.findFromAddress(inputAddr);
|
||||
// If there's a framebuffer at this address, use it. Otherwise go back to our old hack and display framebuffer 0
|
||||
// Displays are hard I really don't want to try implementing them because getting a fast solution is terrible
|
||||
OpenGL::Texture& tex = framebuffer.has_value() ? framebuffer.value().get().texture : colourBufferCache[0].texture;
|
||||
OpenGL::DebugScope scope("DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d",
|
||||
inputAddr, outputAddr, inputWidth, outputWidth, inputHeight, outputHeight);
|
||||
|
||||
tex.bind();
|
||||
screenFramebuffer.bind(OpenGL::DrawFramebuffer);
|
||||
auto srcFramebuffer = getColourBuffer(inputAddr, inputFormat, inputWidth, outputHeight);
|
||||
Math::Rect<u32> srcRect = srcFramebuffer->getSubRect(inputAddr, outputWidth, outputHeight);
|
||||
|
||||
gl.disableBlend();
|
||||
gl.disableLogicOp();
|
||||
gl.disableDepth();
|
||||
gl.disableScissor();
|
||||
gl.disableStencil();
|
||||
gl.setColourMask(true, true, true, true);
|
||||
gl.useProgram(displayProgram);
|
||||
gl.bindVAO(dummyVAO);
|
||||
|
||||
gl.disableClipPlane(0);
|
||||
gl.disableClipPlane(1);
|
||||
|
||||
// Hack: Detect whether we are writing to the top or bottom screen by checking output gap and drawing to the proper part of the output texture
|
||||
// We consider output gap == 320 to mean bottom, and anything else to mean top
|
||||
if (outputGap == 320) {
|
||||
OpenGL::setViewport(40, 0, 320, 240); // Bottom screen viewport
|
||||
} else {
|
||||
OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport
|
||||
if (verticalFlip) {
|
||||
std::swap(srcRect.bottom, srcRect.top);
|
||||
}
|
||||
|
||||
OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen
|
||||
// Apply scaling for the destination rectangle.
|
||||
if (scaling == PICA::Scaling::X || scaling == PICA::Scaling::XY) {
|
||||
outputWidth >>= 1;
|
||||
}
|
||||
|
||||
if (scaling == PICA::Scaling::XY) {
|
||||
outputHeight >>= 1;
|
||||
}
|
||||
|
||||
auto destFramebuffer = getColourBuffer(outputAddr, outputFormat, outputWidth, outputHeight);
|
||||
Math::Rect<u32> destRect = destFramebuffer->getSubRect(outputAddr, outputWidth, outputHeight);
|
||||
|
||||
if (inputWidth != outputWidth) {
|
||||
// Helpers::warn("Strided display transfer is not handled correctly!\n");
|
||||
}
|
||||
|
||||
// Blit the framebuffers
|
||||
srcFramebuffer->fbo.bind(OpenGL::ReadFramebuffer);
|
||||
destFramebuffer->fbo.bind(OpenGL::DrawFramebuffer);
|
||||
gl.disableScissor();
|
||||
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right, srcRect.top, destRect.left, destRect.bottom, destRect.right, destRect.top, GL_COLOR_BUFFER_BIT,
|
||||
GL_LINEAR
|
||||
);
|
||||
}
|
||||
|
||||
void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
// Texture copy size is aligned to 16 byte units
|
||||
const u32 copySize = totalBytes & ~0xf;
|
||||
if (copySize == 0) {
|
||||
printf("TextureCopy total bytes less than 16!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// The width and gap are provided in 16-byte units.
|
||||
const u32 inputWidth = (inputSize & 0xffff) << 4;
|
||||
const u32 inputGap = (inputSize >> 16) << 4;
|
||||
const u32 outputWidth = (outputSize & 0xffff) << 4;
|
||||
const u32 outputGap = (outputSize >> 16) << 4;
|
||||
|
||||
OpenGL::DebugScope scope("TextureCopy inputAddr 0x%08X outputAddr 0x%08X totalBytes %d inputWidth %d inputGap %d outputWidth %d outputGap %d",
|
||||
inputAddr, outputAddr, totalBytes, inputWidth, inputGap, outputWidth, outputGap);
|
||||
|
||||
if (inputGap != 0 || outputGap != 0) {
|
||||
// Helpers::warn("Strided texture copy\n");
|
||||
}
|
||||
if (inputWidth != outputWidth) {
|
||||
Helpers::warn("Input width does not match output width, cannot accelerate texture copy!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Texture copy is a raw data copy in PICA, which means no format or tiling information is provided to the engine.
|
||||
// Depending if the target surface is linear or tiled, games set inputWidth to either the width of the texture or
|
||||
// the width multiplied by eight (because tiles are stored linearly in memory).
|
||||
// To properly accelerate this we must examine each surface individually. For now we assume the most common case
|
||||
// of tiled surface with RGBA8 format. If our assumption does not hold true, we abort the texture copy as inserting
|
||||
// that surface is not correct.
|
||||
|
||||
// We assume the source surface is tiled and RGBA8. inputWidth is in bytes so divide it
|
||||
// by eight * sizePerPixel(RGBA8) to convert it to a useable width.
|
||||
const u32 bpp = sizePerPixel(PICA::ColorFmt::RGBA8);
|
||||
const u32 copyStride = (inputWidth + inputGap) / (8 * bpp);
|
||||
const u32 copyWidth = inputWidth / (8 * bpp);
|
||||
|
||||
// inputHeight/outputHeight are typically set to zero so they cannot be used to get the height of the copy region
|
||||
// in contrast to display transfer. Compute height manually by dividing the copy size with the copy width. The result
|
||||
// is the number of vertical tiles so multiply that by eight to get the actual copy height.
|
||||
const u32 copyHeight = (copySize / inputWidth) * 8;
|
||||
|
||||
// Find the source surface.
|
||||
auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
|
||||
if (!srcFramebuffer) {
|
||||
static int shutUpCounter = 0; // Don't want to spam the console too much, so shut up after 5 times
|
||||
|
||||
if (shutUpCounter < 5) {
|
||||
shutUpCounter++;
|
||||
printf("RendererGL::TextureCopy failed to locate src framebuffer!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Math::Rect<u32> srcRect = srcFramebuffer->getSubRect(inputAddr, copyWidth, copyHeight);
|
||||
|
||||
// Assume the destination surface has the same format. Unless the surfaces have the same block width,
|
||||
// texture copy does not make sense.
|
||||
auto destFramebuffer = getColourBuffer(outputAddr, srcFramebuffer->format, copyWidth, copyHeight);
|
||||
Math::Rect<u32> destRect = destFramebuffer->getSubRect(outputAddr, copyWidth, copyHeight);
|
||||
|
||||
// Blit the framebuffers
|
||||
srcFramebuffer->fbo.bind(OpenGL::ReadFramebuffer);
|
||||
destFramebuffer->fbo.bind(OpenGL::DrawFramebuffer);
|
||||
gl.disableScissor();
|
||||
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right, srcRect.top, destRect.left, destRect.bottom, destRect.right, destRect.top, GL_COLOR_BUFFER_BIT,
|
||||
GL_LINEAR
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<ColourBuffer> RendererGL::getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound) {
|
||||
// Try to find an already existing buffer that contains the provided address
|
||||
// This is a more relaxed check compared to getColourFBO as display transfer/texcopy may refer to
|
||||
// subrect of a surface and in case of texcopy we don't know the format of the surface.
|
||||
auto buffer = colourBufferCache.findFromAddress(addr);
|
||||
if (buffer.has_value()) {
|
||||
return buffer.value().get();
|
||||
}
|
||||
|
||||
if (!createIfnotFound) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Otherwise create and cache a new buffer.
|
||||
ColourBuffer sampleBuffer(addr, format, width, height);
|
||||
return colourBufferCache.add(sampleBuffer);
|
||||
}
|
||||
|
||||
void RendererGL::screenshot(const std::string& name) {
|
||||
|
|
|
@ -9,6 +9,11 @@ void Texture::allocate() {
|
|||
texture.create(size.u(), size.v(), GL_RGBA8);
|
||||
texture.bind();
|
||||
|
||||
#ifdef GPU_DEBUG_INFO
|
||||
const auto name = Helpers::format("Surface %dx%d %s from 0x%08X", size.x(), size.y(), PICA::textureFormatToString(format), location);
|
||||
OpenGL::setObjectLabel(GL_TEXTURE, texture.handle(), name.c_str());
|
||||
#endif
|
||||
|
||||
setNewConfig(config);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "renderer_null/renderer_null.hpp"
|
||||
|
||||
RendererNull::RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererNull::RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
RendererNull::~RendererNull() {}
|
||||
|
||||
void RendererNull::reset() {}
|
||||
|
@ -8,5 +9,6 @@ void RendererNull::display() {}
|
|||
void RendererNull::initGraphicsContext(SDL_Window* window) {}
|
||||
void RendererNull::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {}
|
||||
void RendererNull::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
void RendererNull::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
void RendererNull::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {}
|
||||
void RendererNull::screenshot(const std::string& name) {}
|
|
@ -1,6 +1,7 @@
|
|||
#include "renderer_sw/renderer_sw.hpp"
|
||||
|
||||
RendererSw::RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererSw::RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
RendererSw::~RendererSw() {}
|
||||
|
||||
void RendererSw::reset() { printf("RendererSW: Unimplemented reset call\n"); }
|
||||
|
@ -13,6 +14,10 @@ void RendererSw::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u
|
|||
printf("RendererSW: Unimplemented displayTransfer call\n");
|
||||
}
|
||||
|
||||
void RendererSw::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
printf("RendererSW: Unimplemented textureCopy call\n");
|
||||
}
|
||||
|
||||
void RendererSw::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {
|
||||
printf("RendererSW: Unimplemented drawVertices call\n");
|
||||
}
|
||||
|
|
|
@ -200,7 +200,8 @@ vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D sw
|
|||
return vk::Result::eSuccess;
|
||||
}
|
||||
|
||||
RendererVK::RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererVK::RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
|
||||
RendererVK::~RendererVK() {}
|
||||
|
||||
|
@ -541,6 +542,8 @@ void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 co
|
|||
|
||||
void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
|
||||
void RendererVK::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
|
||||
void RendererVK::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {}
|
||||
|
||||
void RendererVK::screenshot(const std::string& name) {}
|
|
@ -3,7 +3,11 @@
|
|||
|
||||
namespace ACCommands {
|
||||
enum : u32 {
|
||||
CreateDefaultConfig = 0x00010000,
|
||||
CancelConnectAsync = 0x00070002,
|
||||
CloseAsync = 0x00080004,
|
||||
GetLastErrorCode = 0x000A0000,
|
||||
RegisterDisconnectEvent = 0x00300004,
|
||||
SetClientVersion = 0x00400042,
|
||||
};
|
||||
}
|
||||
|
@ -13,12 +17,40 @@ void ACService::reset() {}
|
|||
void ACService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case ACCommands::CancelConnectAsync: cancelConnectAsync(messagePointer); break;
|
||||
case ACCommands::CloseAsync: closeAsync(messagePointer); break;
|
||||
case ACCommands::CreateDefaultConfig: createDefaultConfig(messagePointer); break;
|
||||
case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break;
|
||||
case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break;
|
||||
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
|
||||
default: Helpers::panic("AC service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void ACService::cancelConnectAsync(u32 messagePointer) {
|
||||
log("AC::CancelCommandAsync (stubbed)\n");
|
||||
|
||||
// TODO: Verify if this response header is correct on hardware
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ACService::closeAsync(u32 messagePointer) {
|
||||
log("AC::CloseAsync (stubbed)\n");
|
||||
|
||||
// TODO: Verify if this response header is correct on hardware
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ACService::createDefaultConfig(u32 messagePointer) {
|
||||
log("AC::CreateDefaultConfig (stubbed)\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
// TODO: Verify response buffer on hardware
|
||||
}
|
||||
|
||||
void ACService::getLastErrorCode(u32 messagePointer) {
|
||||
log("AC::GetLastErrorCode (stubbed)\n");
|
||||
|
||||
|
@ -34,3 +66,14 @@ void ACService::setClientVersion(u32 messagePointer) {
|
|||
mem.write32(messagePointer, IPC::responseHeader(0x40, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ACService::registerDisconnectEvent(u32 messagePointer) {
|
||||
log("AC::RegisterDisconnectEvent (stubbed)\n");
|
||||
const u32 pidHeader = mem.read32(messagePointer + 4);
|
||||
const u32 copyHandleHeader = mem.read32(messagePointer + 12);
|
||||
// Event signaled when disconnecting from AC
|
||||
const Handle eventHandle = mem.read32(messagePointer + 16);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x30, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
|
@ -4,7 +4,8 @@
|
|||
namespace AMCommands {
|
||||
enum : u32 {
|
||||
GetDLCTitleInfo = 0x10050084,
|
||||
ListTitleInfo = 0x10070102
|
||||
ListTitleInfo = 0x10070102,
|
||||
GetPatchTitleInfo = 0x100D0084,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -13,6 +14,7 @@ void AMService::reset() {}
|
|||
void AMService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case AMCommands::GetPatchTitleInfo: getPatchTitleInfo(messagePointer); break;
|
||||
case AMCommands::GetDLCTitleInfo: getDLCTitleInfo(messagePointer); break;
|
||||
case AMCommands::ListTitleInfo: listTitleInfo(messagePointer); break;
|
||||
default: Helpers::panic("AM service requested. Command: %08X\n", command);
|
||||
|
@ -42,7 +44,16 @@ void AMService::listTitleInfo(u32 messagePointer) {
|
|||
|
||||
void AMService::getDLCTitleInfo(u32 messagePointer) {
|
||||
log("AM::GetDLCTitleInfo (stubbed to fail)\n");
|
||||
Helpers::warn("Unimplemented AM::GetDLCTitleInfo. Will need to be implemented to support DLC\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1005, 1, 4));
|
||||
mem.write32(messagePointer + 4, -1);
|
||||
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
|
||||
}
|
||||
|
||||
void AMService::getPatchTitleInfo(u32 messagePointer) {
|
||||
log("AM::GetPatchTitleInfo (stubbed to fail)\n");
|
||||
Helpers::warn("Unimplemented AM::GetDLCTitleInfo. Will need to be implemented to support updates\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x100D, 1, 4));
|
||||
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
|
||||
}
|
|
@ -6,15 +6,24 @@ namespace BOSSCommands {
|
|||
InitializeSession = 0x00010082,
|
||||
UnregisterStorage = 0x00030000,
|
||||
GetTaskStorageInfo = 0x00040000,
|
||||
RegisterNewArrivalEvent = 0x00080002,
|
||||
GetOptoutFlag = 0x000A0000,
|
||||
RegisterTask = 0x000B00C2,
|
||||
UnregisterTask = 0x000C0082,
|
||||
GetTaskIdList = 0x000E0000,
|
||||
GetNsDataIdList = 0x00100102,
|
||||
GetNsDataIdList1 = 0x00110102,
|
||||
SendProperty = 0x00140082,
|
||||
ReceiveProperty = 0x00160082,
|
||||
GetTaskServiceStatus = 0x001B0042,
|
||||
StartTask = 0x001C0042,
|
||||
CancelTask = 0x001E0042,
|
||||
GetTaskState = 0x00200082,
|
||||
GetTaskStatus = 0x002300C2,
|
||||
GetTaskInfo = 0x00250082,
|
||||
GetErrorCode = 0x002E0040,
|
||||
RegisterStorageEntry = 0x002F0140,
|
||||
GetStorageEntryInfo = 0x00300000
|
||||
GetStorageEntryInfo = 0x00300000,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,15 +35,25 @@ void BOSSService::handleSyncRequest(u32 messagePointer) {
|
|||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case BOSSCommands::CancelTask: cancelTask(messagePointer); break;
|
||||
case BOSSCommands::GetNsDataIdList: getNsDataIdList(messagePointer); break;
|
||||
case BOSSCommands::GetErrorCode: getErrorCode(messagePointer); break;
|
||||
case BOSSCommands::GetNsDataIdList:
|
||||
case BOSSCommands::GetNsDataIdList1:
|
||||
getNsDataIdList(messagePointer, command); break;
|
||||
case BOSSCommands::GetOptoutFlag: getOptoutFlag(messagePointer); break;
|
||||
case BOSSCommands::GetStorageEntryInfo: getStorageEntryInfo(messagePointer); break;
|
||||
case BOSSCommands::GetTaskIdList: getTaskIdList(messagePointer); break;
|
||||
case BOSSCommands::GetTaskInfo: getTaskInfo(messagePointer); break;
|
||||
case BOSSCommands::GetTaskServiceStatus: getTaskServiceStatus(messagePointer); break;
|
||||
case BOSSCommands::GetTaskState: getTaskState(messagePointer); break;
|
||||
case BOSSCommands::GetTaskStatus: getTaskStatus(messagePointer); break;
|
||||
case BOSSCommands::GetTaskStorageInfo: getTaskStorageInfo(messagePointer); break;
|
||||
case BOSSCommands::InitializeSession: initializeSession(messagePointer); break;
|
||||
case BOSSCommands::ReceiveProperty: receiveProperty(messagePointer); break;
|
||||
case BOSSCommands::RegisterNewArrivalEvent: registerNewArrivalEvent(messagePointer); break;
|
||||
case BOSSCommands::RegisterStorageEntry: registerStorageEntry(messagePointer); break;
|
||||
case BOSSCommands::RegisterTask: registerTask(messagePointer); break;
|
||||
case BOSSCommands::SendProperty: sendProperty(messagePointer); break;
|
||||
case BOSSCommands::StartTask: startTask(messagePointer); break;
|
||||
case BOSSCommands::UnregisterStorage: unregisterStorage(messagePointer); break;
|
||||
case BOSSCommands::UnregisterTask: unregisterTask(messagePointer); break;
|
||||
default: Helpers::panic("BOSS service requested. Command: %08X\n", command);
|
||||
|
@ -48,17 +67,51 @@ void BOSSService::initializeSession(u32 messagePointer) {
|
|||
}
|
||||
|
||||
void BOSSService::getOptoutFlag(u32 messagePointer) {
|
||||
log("BOSS::getOptoutFlag\n");
|
||||
log("BOSS::GetOptoutFlag\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, optoutFlag);
|
||||
}
|
||||
|
||||
void BOSSService::getTaskState(u32 messagePointer) {
|
||||
const u32 taskIDBufferSize = mem.read32(messagePointer + 4);
|
||||
const u32 taskIDDataPointer = mem.read32(messagePointer + 16);
|
||||
log("BOSS::GetTaskStatus (task buffer size: %08X, task data pointer: %08X) (stubbed)\n", taskIDBufferSize, taskIDDataPointer);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x20, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, 0); // TaskStatus: Report the task finished successfully
|
||||
mem.write32(messagePointer + 12, 0); // Current state value for task PropertyID 0x4
|
||||
mem.write8(messagePointer + 16, 0); // TODO: Figure out what this should be
|
||||
}
|
||||
|
||||
void BOSSService::getTaskStatus(u32 messagePointer) {
|
||||
// TODO: 3DBrew does not mention what the parameters are, or what the return values are.
|
||||
log("BOSS::GetTaskStatus (Stubbed)\n");
|
||||
|
||||
// Response values stubbed based on Citra
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x23, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, 0);
|
||||
// TODO: Citra pushes a buffer here?
|
||||
}
|
||||
|
||||
void BOSSService::getTaskServiceStatus(u32 messagePointer) {
|
||||
// TODO: 3DBrew does not mention what the parameters are, or what the return values are... again
|
||||
log("BOSS::GetTaskServiceStatus (Stubbed)\n");
|
||||
|
||||
// Response values stubbed based on Citra
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1B, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, 0);
|
||||
// TODO: Citra pushes a buffer here too?
|
||||
}
|
||||
|
||||
void BOSSService::getTaskStorageInfo(u32 messagePointer) {
|
||||
log("BOSS::GetTaskStorageInfo (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, 0); // Seems to be unknown what this is?
|
||||
mem.write32(messagePointer + 8, 0);
|
||||
}
|
||||
|
||||
void BOSSService::getTaskIdList(u32 messagePointer) {
|
||||
|
@ -76,6 +129,13 @@ void BOSSService::getTaskInfo(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void BOSSService::getErrorCode(u32 messagePointer) {
|
||||
log("BOSS::GetErrorCode (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x2E, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, Result::Success); // No error code
|
||||
}
|
||||
|
||||
void BOSSService::getStorageEntryInfo(u32 messagePointer) {
|
||||
log("BOSS::GetStorageEntryInfo (undocumented)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x30, 3, 0));
|
||||
|
@ -84,33 +144,76 @@ void BOSSService::getStorageEntryInfo(u32 messagePointer) {
|
|||
mem.write16(messagePointer + 12, 0); // s16, unknown meaning
|
||||
}
|
||||
|
||||
void BOSSService::sendProperty(u32 messagePointer) {
|
||||
const u32 id = mem.read32(messagePointer + 4);
|
||||
const u32 size = mem.read32(messagePointer + 8);
|
||||
const u32 ptr = mem.read32(messagePointer + 16);
|
||||
|
||||
log("BOSS::SendProperty (id = %d, size = %08X, ptr = %08X) (stubbed)\n", id, size, ptr);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, 0); // Read size
|
||||
// TODO: Should this do anything else?
|
||||
}
|
||||
|
||||
|
||||
void BOSSService::receiveProperty(u32 messagePointer) {
|
||||
const u32 id = mem.read32(messagePointer + 4);
|
||||
const u32 size = mem.read32(messagePointer + 8);
|
||||
const u32 ptr = mem.read32(messagePointer + 16);
|
||||
|
||||
log("BOSS::ReceiveProperty(stubbed) (id = %d, size = %08X, ptr = %08X)\n", id, size, ptr);
|
||||
log("BOSS::ReceiveProperty (id = %d, size = %08X, ptr = %08X) (stubbed)\n", id, size, ptr);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x16, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, 0); // Read size
|
||||
}
|
||||
|
||||
// This seems to accept a KEvent as a parameter and register it for something Spotpass related
|
||||
// I need to update the 3DBrew page when it's known what it does properly
|
||||
void BOSSService::registerNewArrivalEvent(u32 messagePointer) {
|
||||
const Handle eventHandle = mem.read32(messagePointer + 4); // Kernel event handle to register
|
||||
log("BOSS::RegisterNewArrivalEvent (handle = %X)\n", eventHandle);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void BOSSService::startTask(u32 messagePointer) {
|
||||
log("BOSS::StartTask (stubbed)\n");
|
||||
const u32 bufferSize = mem.read32(messagePointer + 4);
|
||||
const u32 descriptor = mem.read32(messagePointer + 8);
|
||||
const u32 bufferData = mem.read32(messagePointer + 12);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1C, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void BOSSService::cancelTask(u32 messagePointer) {
|
||||
log("BOSS::CancelTask (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void BOSSService::registerTask(u32 messagePointer) {
|
||||
log("BOSS::RegisterTask (stubbed)\n");
|
||||
const u32 bufferSize = mem.read32(messagePointer + 4);
|
||||
const u32 dataPointr = mem.read32(messagePointer + 20);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x0B, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void BOSSService::unregisterTask(u32 messagePointer) {
|
||||
log("BOSS::UnregisterTask (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void BOSSService::getNsDataIdList(u32 messagePointer) {
|
||||
// There's multiple aliases for this command. commandWord is the first word in the IPC buffer with the command word, needed for the response header
|
||||
void BOSSService::getNsDataIdList(u32 messagePointer, u32 commandWord) {
|
||||
log("BOSS::GetNsDataIdList (stubbed)\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x10, 3, 2));
|
||||
mem.write32(messagePointer, IPC::responseHeader(commandWord >> 16, 3, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write16(messagePointer + 8, 0); // u16: Actual number of output entries.
|
||||
mem.write16(messagePointer + 12, 0); // u16: Last word-index copied to output in the internal NsDataId list.
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
#include "services/cam.hpp"
|
||||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
namespace CAMCommands {
|
||||
enum : u32 {
|
||||
GetBufferErrorInterruptEvent = 0x00060040,
|
||||
DriverInitialize = 0x00390000,
|
||||
GetMaxLines = 0x000A0080
|
||||
GetMaxLines = 0x000A0080,
|
||||
};
|
||||
}
|
||||
|
||||
void CAMService::reset() {}
|
||||
void CAMService::reset() { bufferErrorInterruptEvents.fill(std::nullopt); }
|
||||
|
||||
void CAMService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
|
||||
case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break;
|
||||
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
|
||||
default: Helpers::panic("CAM service requested. Command: %08X\n", command);
|
||||
}
|
||||
|
@ -56,3 +59,23 @@ void CAMService::getMaxLines(u32 messagePointer) {
|
|||
mem.write16(messagePointer + 8, lines);
|
||||
}
|
||||
}
|
||||
|
||||
void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
|
||||
const u32 port = mem.read32(messagePointer + 4);
|
||||
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", port);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2));
|
||||
|
||||
if (port >= portCount) {
|
||||
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
|
||||
} else {
|
||||
auto& event = bufferErrorInterruptEvents[port];
|
||||
if (!event.has_value()) {
|
||||
event = kernel.makeEvent(ResetType::OneShot);
|
||||
}
|
||||
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, 0);
|
||||
mem.write32(messagePointer + 12, event.value());
|
||||
}
|
||||
}
|
|
@ -1,21 +1,22 @@
|
|||
#include "services/cecd.hpp"
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
namespace CECDCommands {
|
||||
enum : u32 {
|
||||
GetInfoEventHandle = 0x000F0000
|
||||
GetInfoEventHandle = 0x000F0000,
|
||||
OpenAndRead = 0x00120104,
|
||||
};
|
||||
}
|
||||
|
||||
void CECDService::reset() {
|
||||
infoEvent = std::nullopt;
|
||||
}
|
||||
void CECDService::reset() { infoEvent = std::nullopt; }
|
||||
|
||||
void CECDService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case CECDCommands::GetInfoEventHandle: getInfoEventHandle(messagePointer); break;
|
||||
case CECDCommands::OpenAndRead: openAndRead(messagePointer); break;
|
||||
default:
|
||||
Helpers::panicDev("CECD service requested. Command: %08X\n", command);
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -35,3 +36,16 @@ void CECDService::getInfoEventHandle(u32 messagePointer) {
|
|||
// TODO: Translation descriptor here?
|
||||
mem.write32(messagePointer + 12, infoEvent.value());
|
||||
}
|
||||
|
||||
void CECDService::openAndRead(u32 messagePointer) {
|
||||
const u32 bufferSize = mem.read32(messagePointer + 4);
|
||||
const u32 programID = mem.read32(messagePointer + 8);
|
||||
const u32 pathType = mem.read32(messagePointer + 12);
|
||||
const u32 bufferAddress = mem.read32(messagePointer + 32);
|
||||
log("CECD::OpenAndRead (size = %08X, address = %08X, path type = %d)\n", bufferSize, bufferAddress, pathType);
|
||||
|
||||
// TODO: We should implement this properly the time comes
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x12, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, 0); // Bytes read
|
||||
}
|
|
@ -134,7 +134,7 @@ void CFGService::secureInfoGetRegion(u32 messagePointer) {
|
|||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x2, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(Regions::USA)); // TODO: Detect the game region and report it
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(mem.getConsoleRegion()));
|
||||
}
|
||||
|
||||
void CFGService::genUniqueConsoleHash(u32 messagePointer) {
|
||||
|
@ -153,7 +153,16 @@ void CFGService::genUniqueConsoleHash(u32 messagePointer) {
|
|||
// Used for market restriction-related stuff
|
||||
void CFGService::getRegionCanadaUSA(u32 messagePointer) {
|
||||
log("CFG::GetRegionCanadaUSA\n");
|
||||
const u8 ret = (country == CountryCodes::US || country == CountryCodes::CA) ? 1 : 0;
|
||||
bool regionUSA = mem.getConsoleRegion() == Regions::USA;
|
||||
u8 ret;
|
||||
|
||||
// First, this function checks that the console region is 1 (USA). If not then it instantly returns 0
|
||||
// Then it checks whether the country is US or Canda. If yes it returns 1, else it returns 0.
|
||||
if (!regionUSA) {
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = (country == CountryCodes::US || country == CountryCodes::CA) ? 1 : 0;
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
|
BIN
src/core/services/fonts/CitraSharedFontUSRelocated.bin
Normal file
BIN
src/core/services/fonts/CitraSharedFontUSRelocated.bin
Normal file
Binary file not shown.
|
@ -1,23 +1,28 @@
|
|||
#include <string>
|
||||
#include "services/frd.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
namespace FRDCommands {
|
||||
enum : u32 {
|
||||
HasLoggedIn = 0x00010000,
|
||||
AttachToEventNotification = 0x00200002,
|
||||
SetNotificationMask = 0x00210040,
|
||||
SetClientSdkVersion = 0x00320042,
|
||||
Logout = 0x00040000,
|
||||
GetMyFriendKey = 0x00050000,
|
||||
GetMyProfile = 0x00070000,
|
||||
GetMyPresence = 0x00080000,
|
||||
GetMyScreenName = 0x00090000,
|
||||
GetMyMii = 0x000A0000,
|
||||
GetFriendKeyList = 0x00110080
|
||||
GetFriendKeyList = 0x00110080,
|
||||
UpdateGameModeDescription = 0x001D0002,
|
||||
};
|
||||
}
|
||||
|
||||
void FRDService::reset() {}
|
||||
void FRDService::reset() { loggedIn = false; }
|
||||
|
||||
void FRDService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
|
@ -29,8 +34,11 @@ void FRDService::handleSyncRequest(u32 messagePointer) {
|
|||
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
|
||||
case FRDCommands::GetMyProfile: getMyProfile(messagePointer); break;
|
||||
case FRDCommands::GetMyScreenName: getMyScreenName(messagePointer); break;
|
||||
case FRDCommands::HasLoggedIn: hasLoggedIn(messagePointer); break;
|
||||
case FRDCommands::Logout: logout(messagePointer); break;
|
||||
case FRDCommands::SetClientSdkVersion: setClientSDKVersion(messagePointer); break;
|
||||
case FRDCommands::SetNotificationMask: setNotificationMask(messagePointer); break;
|
||||
case FRDCommands::UpdateGameModeDescription: updateGameModeDescription(messagePointer); break;
|
||||
default: Helpers::panic("FRD service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +48,14 @@ void FRDService::attachToEventNotification(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
// This is supposed to post stuff on your user profile so uhh can't really emulate it
|
||||
void FRDService::updateGameModeDescription(u32 messagePointer) {
|
||||
log("FRD::UpdateGameModeDescription\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1D, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void FRDService::getMyFriendKey(u32 messagePointer) {
|
||||
log("FRD::GetMyFriendKey\n");
|
||||
|
||||
|
@ -135,3 +151,19 @@ void FRDService::getMyMii(u32 messagePointer) {
|
|||
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void FRDService::hasLoggedIn(u32 messagePointer) {
|
||||
log("FRD::HasLoggedIn\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, loggedIn ? 1 : 0);
|
||||
}
|
||||
|
||||
void FRDService::logout(u32 messagePointer) {
|
||||
log("FRD::Logout\n");
|
||||
loggedIn = false;
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x4, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
|
@ -27,6 +27,8 @@ namespace FSCommands {
|
|||
IsSdmcWritable = 0x08180000,
|
||||
GetFormatInfo = 0x084500C2,
|
||||
FormatSaveData = 0x084C0242,
|
||||
CreateExtSaveData = 0x08510242,
|
||||
DeleteExtSaveData = 0x08520100,
|
||||
InitializeWithSdkVersion = 0x08610042,
|
||||
SetPriority = 0x08620040,
|
||||
GetPriority = 0x08630000
|
||||
|
@ -144,9 +146,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
|
|||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case FSCommands::CreateDirectory: createDirectory(messagePointer); break;
|
||||
case FSCommands::CreateExtSaveData: createExtSaveData(messagePointer); break;
|
||||
case FSCommands::CreateFile: createFile(messagePointer); break;
|
||||
case FSCommands::ControlArchive: controlArchive(messagePointer); break;
|
||||
case FSCommands::CloseArchive: closeArchive(messagePointer); break;
|
||||
case FSCommands::DeleteExtSaveData: deleteExtSaveData(messagePointer); break;
|
||||
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
|
||||
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
|
||||
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
|
||||
|
@ -455,6 +459,40 @@ void FSService::formatSaveData(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void FSService::deleteExtSaveData(u32 messagePointer) {
|
||||
Helpers::warn("Stubbed call to FS::DeleteExtSaveData!");
|
||||
// First 4 words of parameters are the ExtSaveData info
|
||||
// https://www.3dbrew.org/wiki/Filesystem_services#ExtSaveDataInfo
|
||||
const u8 mediaType = mem.read8(messagePointer + 4);
|
||||
const u64 saveID = mem.read64(messagePointer + 8);
|
||||
log("FS::DeleteExtSaveData (media type = %d, saveID = %llx) (stubbed)\n", mediaType, saveID);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x0852, 1, 0));
|
||||
// TODO: We can't properly implement this yet until we properly support title/save IDs. We will stub this and insert a warning for now. Required for Planet Robobot
|
||||
// When we properly implement it, it will just be a recursive directory deletion
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void FSService::createExtSaveData(u32 messagePointer) {
|
||||
Helpers::warn("Stubbed call to FS::CreateExtSaveData!");
|
||||
// First 4 words of parameters are the ExtSaveData info
|
||||
// https://www.3dbrew.org/wiki/Filesystem_services#ExtSaveDataInfo
|
||||
// This creates the ExtSaveData with the specified saveid in the specified media type. It stores the SMDH as "icon" in the root of the created directory.
|
||||
const u8 mediaType = mem.read8(messagePointer + 4);
|
||||
const u64 saveID = mem.read64(messagePointer + 8);
|
||||
const u32 numOfDirectories = mem.read32(messagePointer + 20);
|
||||
const u32 numOfFiles = mem.read32(messagePointer + 24);
|
||||
const u64 sizeLimit = mem.read64(messagePointer + 28);
|
||||
const u32 smdhSize = mem.read32(messagePointer + 36);
|
||||
const u32 smdhPointer = mem.read32(messagePointer + 44);
|
||||
|
||||
log("FS::CreateExtSaveData (stubbed)\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x0851, 1, 0));
|
||||
// TODO: Similar to DeleteExtSaveData, we need to refactor how our ExtSaveData stuff works before properly implementing this
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void FSService::formatThisUserSaveData(u32 messagePointer) {
|
||||
log("FS::FormatThisUserSaveData\n");
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "services/gsp_gpu.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
|
@ -10,6 +11,7 @@ namespace ServiceCommands {
|
|||
RegisterInterruptRelayQueue = 0x00130042,
|
||||
WriteHwRegs = 0x00010082,
|
||||
WriteHwRegsWithMask = 0x00020084,
|
||||
SetBufferSwap = 0x00050200,
|
||||
FlushDataCache = 0x00080082,
|
||||
SetLCDForceBlack = 0x000B0040,
|
||||
TriggerCmdReqQueue = 0x000C0000,
|
||||
|
@ -44,13 +46,14 @@ void GPUService::handleSyncRequest(u32 messagePointer) {
|
|||
case ServiceCommands::FlushDataCache: flushDataCache(messagePointer); break;
|
||||
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
|
||||
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
|
||||
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
|
||||
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
|
||||
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
|
||||
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
|
||||
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
|
||||
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
|
||||
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
|
||||
; default: Helpers::panic("GPU service requested. Command: %08X\n", command);
|
||||
default: Helpers::panic("GPU service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,15 +127,12 @@ void GPUService::requestInterrupt(GPUInterrupt type) {
|
|||
// Not emulating this causes Yoshi's Wooly World, Captain Toad, Metroid 2 et al to hang
|
||||
if (type == GPUInterrupt::VBlank0 || type == GPUInterrupt::VBlank1) {
|
||||
int screen = static_cast<u32>(type) - static_cast<u32>(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom
|
||||
|
||||
constexpr u32 FBInfoSize = 0x40;
|
||||
// TODO: Offset depends on GSP thread being triggered
|
||||
u8* info = &sharedMem[0x200 + screen * FBInfoSize];
|
||||
u8& dirtyFlag = info[1];
|
||||
FramebufferUpdate* update = reinterpret_cast<FramebufferUpdate*>(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]);
|
||||
|
||||
if (dirtyFlag & 1) {
|
||||
// TODO: Submit buffer info here
|
||||
dirtyFlag &= ~1;
|
||||
if (update->dirtyFlag & 1) {
|
||||
setBufferSwapImpl(screen, update->framebufferInfo[update->index]);
|
||||
update->dirtyFlag &= ~1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ void GPUService::flushDataCache(u32 messagePointer) {
|
|||
u32 address = mem.read32(messagePointer + 4);
|
||||
u32 size = mem.read32(messagePointer + 8);
|
||||
u32 processHandle = handle = mem.read32(messagePointer + 16);
|
||||
log("GSP::GPU::FlushDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle);
|
||||
log("GSP::GPU::FlushDataCache(address = %08X, size = %X, process = %X)\n", address, size, processHandle);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -230,7 +230,7 @@ void GPUService::storeDataCache(u32 messagePointer) {
|
|||
u32 address = mem.read32(messagePointer + 4);
|
||||
u32 size = mem.read32(messagePointer + 8);
|
||||
u32 processHandle = handle = mem.read32(messagePointer + 16);
|
||||
log("GSP::GPU::StoreDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle);
|
||||
log("GSP::GPU::StoreDataCache(address = %08X, size = %X, process = %X)\n", address, size, processHandle);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -261,6 +261,24 @@ void GPUService::setAxiConfigQoSMode(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void GPUService::setBufferSwap(u32 messagePointer) {
|
||||
FramebufferInfo info{};
|
||||
const u32 screenId = mem.read32(messagePointer + 4); // Selects either PDC0 or PDC1
|
||||
info.activeFb = mem.read32(messagePointer + 8);
|
||||
info.leftFramebufferVaddr = mem.read32(messagePointer + 12);
|
||||
info.rightFramebufferVaddr = mem.read32(messagePointer + 16);
|
||||
info.stride = mem.read32(messagePointer + 20);
|
||||
info.format = mem.read32(messagePointer + 24);
|
||||
info.displayFb = mem.read32(messagePointer + 28); // Selects either framebuffer A or B
|
||||
|
||||
log("GSP::GPU::SetBufferSwap\n");
|
||||
Helpers::panic("Untested GSP::GPU::SetBufferSwap call");
|
||||
|
||||
setBufferSwapImpl(screenId, info);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x05, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
// Seems to also be completely undocumented
|
||||
void GPUService::setInternalPriorities(u32 messagePointer) {
|
||||
log("GSP::GPU::SetInternalPriorities\n");
|
||||
|
@ -283,7 +301,7 @@ void GPUService::processCommandBuffer() {
|
|||
log("Processing %d GPU commands\n", commandsLeft);
|
||||
|
||||
while (commandsLeft != 0) {
|
||||
u32 cmdID = cmd[0] & 0xff;
|
||||
const u32 cmdID = cmd[0] & 0xff;
|
||||
switch (cmdID) {
|
||||
case GXCommands::ProcessCommandList: processCommandList(cmd); break;
|
||||
case GXCommands::MemoryFill: memoryFill(cmd); break;
|
||||
|
@ -375,12 +393,47 @@ void GPUService::flushCacheRegions(u32* cmd) {
|
|||
log("GSP::GPU::FlushCacheRegions (Stubbed)\n");
|
||||
}
|
||||
|
||||
void GPUService::setBufferSwapImpl(u32 screenId, const FramebufferInfo& info) {
|
||||
using namespace PICA::ExternalRegs;
|
||||
|
||||
static constexpr std::array<u32, 8> fbAddresses = {
|
||||
Framebuffer0AFirstAddr,
|
||||
Framebuffer0BFirstAddr,
|
||||
Framebuffer1AFirstAddr,
|
||||
Framebuffer1BFirstAddr,
|
||||
Framebuffer0ASecondAddr,
|
||||
Framebuffer0BSecondAddr,
|
||||
Framebuffer1ASecondAddr,
|
||||
Framebuffer1BSecondAddr,
|
||||
};
|
||||
|
||||
auto& regs = gpu.getExtRegisters();
|
||||
|
||||
const u32 fbIndex = info.activeFb * 4 + screenId * 2;
|
||||
regs[fbAddresses[fbIndex]] = VaddrToPaddr(info.leftFramebufferVaddr);
|
||||
regs[fbAddresses[fbIndex + 1]] = VaddrToPaddr(info.rightFramebufferVaddr);
|
||||
|
||||
static constexpr std::array<u32, 6> configAddresses = {
|
||||
Framebuffer0Config,
|
||||
Framebuffer0Select,
|
||||
Framebuffer0Stride,
|
||||
Framebuffer1Config,
|
||||
Framebuffer1Select,
|
||||
Framebuffer1Stride,
|
||||
};
|
||||
|
||||
const u32 configIndex = screenId * 3;
|
||||
regs[configAddresses[configIndex]] = info.format;
|
||||
regs[configAddresses[configIndex + 1]] = info.displayFb;
|
||||
regs[configAddresses[configIndex + 2]] = info.stride;
|
||||
}
|
||||
|
||||
// Actually send command list (aka display list) to GPU
|
||||
void GPUService::processCommandList(u32* cmd) {
|
||||
const u32 address = cmd[1] & ~7; // Buffer address
|
||||
const u32 size = cmd[2] & ~3; // Buffer size in bytes
|
||||
const bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
|
||||
const bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
|
||||
[[maybe_unused]] const bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
|
||||
[[maybe_unused]] const bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
|
||||
|
||||
log("GPU::GSP::processCommandList. Address: %08X, size in bytes: %08X\n", address, size);
|
||||
gpu.startCommandList(address, size);
|
||||
|
@ -390,7 +443,15 @@ void GPUService::processCommandList(u32* cmd) {
|
|||
// TODO: Emulate the transfer engine & its registers
|
||||
// Then this can be emulated by just writing the appropriate values there
|
||||
void GPUService::triggerTextureCopy(u32* cmd) {
|
||||
Helpers::warn("GSP::GPU::TriggerTextureCopy (unimplemented)\n");
|
||||
const u32 inputAddr = VaddrToPaddr(cmd[1]);
|
||||
const u32 outputAddr = VaddrToPaddr(cmd[2]);
|
||||
const u32 totalBytes = cmd[3];
|
||||
const u32 inputSize = cmd[4];
|
||||
const u32 outputSize = cmd[5];
|
||||
const u32 flags = cmd[6];
|
||||
|
||||
log("GSP::GPU::TriggerTextureCopy (Stubbed)\n");
|
||||
gpu.textureCopy(inputAddr, outputAddr, totalBytes, inputSize, outputSize, flags);
|
||||
// This uses the transfer engine and thus needs to fire a PPF interrupt.
|
||||
// NSMB2 relies on this
|
||||
requestInterrupt(GPUInterrupt::PPF);
|
||||
|
|
|
@ -11,7 +11,8 @@ namespace HIDCommands {
|
|||
EnableGyroscopeLow = 0x00130000,
|
||||
DisableGyroscopeLow = 0x00140000,
|
||||
GetGyroscopeLowRawToDpsCoefficient = 0x00150000,
|
||||
GetGyroscopeLowCalibrateParam = 0x00160000
|
||||
GetGyroscopeLowCalibrateParam = 0x00160000,
|
||||
GetSoundVolume = 0x00170000,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -46,6 +47,7 @@ void HIDService::handleSyncRequest(u32 messagePointer) {
|
|||
case HIDCommands::GetGyroscopeLowCalibrateParam: getGyroscopeLowCalibrateParam(messagePointer); break;
|
||||
case HIDCommands::GetGyroscopeLowRawToDpsCoefficient: getGyroscopeCoefficient(messagePointer); break;
|
||||
case HIDCommands::GetIPCHandles: getIPCHandles(messagePointer); break;
|
||||
case HIDCommands::GetSoundVolume: getSoundVolume(messagePointer); break;
|
||||
default: Helpers::panic("HID service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +109,18 @@ void HIDService::getGyroscopeCoefficient(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 8, Helpers::bit_cast<u32, float>(gyroscopeCoeff));
|
||||
}
|
||||
|
||||
// The volume here is in the range [0, 0x3F]
|
||||
// It is read directly from I2C Device 3 register 0x09
|
||||
// Since we currently do not have audio, set the volume a bit below max (0x30)
|
||||
void HIDService::getSoundVolume(u32 messagePointer) {
|
||||
log("HID::GetSoundVolume\n");
|
||||
constexpr u8 volume = 0x30;
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x17, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, volume);
|
||||
}
|
||||
|
||||
void HIDService::getIPCHandles(u32 messagePointer) {
|
||||
log("HID::GetIPCHandles\n");
|
||||
|
||||
|
|
27
src/core/services/mcu/mcu_hwc.cpp
Normal file
27
src/core/services/mcu/mcu_hwc.cpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#include "ipc.hpp"
|
||||
#include "result/result.hpp"
|
||||
#include "services/mcu/mcu_hwc.hpp"
|
||||
|
||||
namespace MCU::HWCCommands {
|
||||
enum : u32 {
|
||||
GetBatteryLevel = 0x00050000,
|
||||
};
|
||||
}
|
||||
|
||||
void MCU::HWCService::reset() {}
|
||||
|
||||
void MCU::HWCService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case HWCCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
||||
default: Helpers::panic("MCU::HWC service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void MCU::HWCService::getBatteryLevel(u32 messagePointer) {
|
||||
log("MCU::HWC::GetBatteryLevel\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x5, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, config.batteryPercentage);
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace MICCommands {
|
|||
enum : u32 {
|
||||
MapSharedMem = 0x00010042,
|
||||
StartSampling = 0x00030140,
|
||||
StopSampling = 0x00050000,
|
||||
SetGain = 0x00080040,
|
||||
GetGain = 0x00090000,
|
||||
SetPower = 0x000A0040,
|
||||
|
@ -17,6 +18,7 @@ namespace MICCommands {
|
|||
void MICService::reset() {
|
||||
micEnabled = false;
|
||||
shouldClamp = false;
|
||||
isSampling = false;
|
||||
gain = 0;
|
||||
}
|
||||
|
||||
|
@ -30,6 +32,7 @@ void MICService::handleSyncRequest(u32 messagePointer) {
|
|||
case MICCommands::SetIirFilter: setIirFilter(messagePointer); break;
|
||||
case MICCommands::SetPower: setPower(messagePointer); break;
|
||||
case MICCommands::StartSampling: startSampling(messagePointer); break;
|
||||
case MICCommands::StopSampling: stopSampling(messagePointer); break;
|
||||
case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break;
|
||||
default: Helpers::panic("MIC service requested. Command: %08X\n", command);
|
||||
}
|
||||
|
@ -88,10 +91,19 @@ void MICService::startSampling(u32 messagePointer) {
|
|||
encoding, sampleRate, offset, dataSize, loop ? "yes" : "no"
|
||||
);
|
||||
|
||||
isSampling = true;
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void MICService::stopSampling(u32 messagePointer) {
|
||||
log("MIC::StopSampling\n");
|
||||
isSampling = false;
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void MICService::setIirFilter(u32 messagePointer) {
|
||||
const u32 size = mem.read32(messagePointer + 4);
|
||||
const u32 pointer = mem.read32(messagePointer + 12);
|
||||
|
|
|
@ -7,7 +7,8 @@ namespace NDMCommands {
|
|||
SuspendDaemons = 0x00060040,
|
||||
ResumeDaemons = 0x00070040,
|
||||
SuspendScheduler = 0x00080040,
|
||||
ResumeScheduler = 0x00090000
|
||||
ResumeScheduler = 0x00090000,
|
||||
ClearHalfAwakeMacFilter = 0x00170000,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -16,6 +17,7 @@ void NDMService::reset() {}
|
|||
void NDMService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case NDMCommands::ClearHalfAwakeMacFilter: clearHalfAwakeMacFilter(messagePointer); break;
|
||||
case NDMCommands::OverrideDefaultDaemons: overrideDefaultDaemons(messagePointer); break;
|
||||
case NDMCommands::ResumeDaemons: resumeDaemons(messagePointer); break;
|
||||
case NDMCommands::ResumeScheduler: resumeScheduler(messagePointer); break;
|
||||
|
@ -26,31 +28,37 @@ void NDMService::handleSyncRequest(u32 messagePointer) {
|
|||
}
|
||||
|
||||
void NDMService::overrideDefaultDaemons(u32 messagePointer) {
|
||||
log("NDM::OverrideDefaultDaemons(stubbed)\n");
|
||||
log("NDM::OverrideDefaultDaemons (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void NDMService::resumeDaemons(u32 messagePointer) {
|
||||
log("NDM::resumeDaemons(stubbed)\n");
|
||||
log("NDM::resumeDaemons (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void NDMService::suspendDaemons(u32 messagePointer) {
|
||||
log("NDM::SuspendDaemons(stubbed)\n");
|
||||
log("NDM::SuspendDaemons (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void NDMService::resumeScheduler(u32 messagePointer) {
|
||||
log("NDM::ResumeScheduler(stubbed)\n");
|
||||
log("NDM::ResumeScheduler (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void NDMService::suspendScheduler(u32 messagePointer) {
|
||||
log("NDM::SuspendScheduler(stubbed)\n");
|
||||
log("NDM::SuspendScheduler (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void NDMService::clearHalfAwakeMacFilter(u32 messagePointer) {
|
||||
log("NDM::ClearHalfAwakeMacFilter (stubbed)\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
15
src/core/services/news_u.cpp
Normal file
15
src/core/services/news_u.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include "ipc.hpp"
|
||||
#include "services/news_u.hpp"
|
||||
|
||||
namespace NewsCommands {
|
||||
enum : u32 {};
|
||||
}
|
||||
|
||||
void NewsUService::reset() {}
|
||||
|
||||
void NewsUService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
default: Helpers::panic("news:u service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
|
@ -5,22 +5,35 @@
|
|||
namespace NFCCommands {
|
||||
enum : u32 {
|
||||
Initialize = 0x00010040,
|
||||
StartCommunication = 0x00030000,
|
||||
StopCommunication = 0x00040000,
|
||||
GetTagInRangeEvent = 0x000B0000,
|
||||
GetTagOutOfRangeEvent = 0x000C0000
|
||||
GetTagOutOfRangeEvent = 0x000C0000,
|
||||
GetTagState = 0x000D0000,
|
||||
CommunicationGetStatus = 0x000F0000,
|
||||
CommunicationGetResult = 0x00120000,
|
||||
};
|
||||
}
|
||||
|
||||
void NFCService::reset() {
|
||||
tagInRangeEvent = std::nullopt;
|
||||
tagOutOfRangeEvent = std::nullopt;
|
||||
|
||||
adapterStatus = Old3DSAdapterStatus::Idle;
|
||||
tagStatus = TagStatus::NotInitialized;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
void NFCService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case NFCCommands::CommunicationGetStatus: communicationGetStatus(messagePointer); break;
|
||||
case NFCCommands::Initialize: initialize(messagePointer); break;
|
||||
case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break;
|
||||
case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break;
|
||||
case NFCCommands::GetTagState: getTagState(messagePointer); break;
|
||||
case NFCCommands::StartCommunication: startCommunication(messagePointer); break;
|
||||
case NFCCommands::StopCommunication: stopCommunication(messagePointer); break;
|
||||
default: Helpers::panic("NFC service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +42,9 @@ void NFCService::initialize(u32 messagePointer) {
|
|||
const u8 type = mem.read8(messagePointer + 4);
|
||||
log("NFC::Initialize (type = %d)\n", type);
|
||||
|
||||
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
|
||||
tagStatus = TagStatus::Initialized;
|
||||
initialized = true;
|
||||
// TODO: This should error if already initialized. Also sanitize type.
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -68,3 +84,41 @@ void NFCService::getTagOutOfRangeEvent(u32 messagePointer) {
|
|||
// TODO: Translation descriptor here
|
||||
mem.write32(messagePointer + 12, tagOutOfRangeEvent.value());
|
||||
}
|
||||
|
||||
void NFCService::getTagState(u32 messagePointer) {
|
||||
log("NFC::GetTagState");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xD, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, static_cast<u8>(tagStatus));
|
||||
}
|
||||
|
||||
void NFCService::communicationGetStatus(u32 messagePointer) {
|
||||
log("NFC::CommunicationGetStatus");
|
||||
|
||||
if (!initialized) {
|
||||
Helpers::warn("NFC::CommunicationGetStatus: Old 3DS NFC Adapter not initialized\n");
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xF, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, static_cast<u32>(adapterStatus));
|
||||
}
|
||||
|
||||
void NFCService::startCommunication(u32 messagePointer) {
|
||||
log("NFC::StartCommunication\n");
|
||||
// adapterStatus = Old3DSAdapterStatus::Active;
|
||||
// TODO: Actually start communication when we emulate amiibo
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void NFCService::stopCommunication(u32 messagePointer) {
|
||||
log("NFC::StopCommunication\n");
|
||||
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
|
||||
// TODO: Actually stop communication when we emulate amiibo
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x4, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
namespace PTMCommands {
|
||||
enum : u32 {
|
||||
GetAdapterState = 0x00050000,
|
||||
GetBatteryLevel = 0x00070000,
|
||||
GetStepHistory = 0x000B00C2,
|
||||
GetTotalStepCount = 0x000C0000,
|
||||
ConfigureNew3DSCPU = 0x08180040
|
||||
ConfigureNew3DSCPU = 0x08180040,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -15,12 +17,30 @@ void PTMService::handleSyncRequest(u32 messagePointer) {
|
|||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case PTMCommands::ConfigureNew3DSCPU: configureNew3DSCPU(messagePointer); break;
|
||||
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
|
||||
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
||||
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
|
||||
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
|
||||
default: Helpers::panic("PTM service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void PTMService::getAdapterState(u32 messagePointer) {
|
||||
log("PTM::GetAdapterState\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x5, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, config.chargerPlugged ? 1 : 0);
|
||||
}
|
||||
|
||||
void PTMService::getBatteryLevel(u32 messagePointer) {
|
||||
log("PTM::GetBatteryLevel");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, batteryPercentToLevel(config.batteryPercentage));
|
||||
}
|
||||
|
||||
void PTMService::getStepHistory(u32 messagePointer) {
|
||||
log("PTM::GetStepHistory [stubbed]\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xB, 1, 2));
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel)
|
||||
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem), cecd(mem, kernel), cfg(mem),
|
||||
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config)
|
||||
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem),
|
||||
dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel),
|
||||
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem), ptm(mem), soc(mem),
|
||||
y2r(mem, kernel) {}
|
||||
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem),
|
||||
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
|
||||
|
||||
static constexpr int MAX_NOTIFICATION_COUNT = 16;
|
||||
|
||||
|
@ -33,11 +33,15 @@ void ServiceManager::reset() {
|
|||
gsp_gpu.reset();
|
||||
gsp_lcd.reset();
|
||||
ldr.reset();
|
||||
mcu_hwc.reset();
|
||||
mic.reset();
|
||||
nim.reset();
|
||||
ndm.reset();
|
||||
news_u.reset();
|
||||
nfc.reset();
|
||||
nim.reset();
|
||||
ptm.reset();
|
||||
soc.reset();
|
||||
ssl.reset();
|
||||
y2r.reset();
|
||||
|
||||
notificationSemaphore = std::nullopt;
|
||||
|
@ -109,13 +113,16 @@ static std::map<std::string, Handle> serviceMap = {
|
|||
{ "gsp::Gpu", KernelHandles::GPU },
|
||||
{ "gsp::Lcd", KernelHandles::LCD },
|
||||
{ "ldr:ro", KernelHandles::LDR_RO },
|
||||
{ "mcu::HWC", KernelHandles::MCU_HWC },
|
||||
{ "mic:u", KernelHandles::MIC },
|
||||
{ "ndm:u", KernelHandles::NDM },
|
||||
{ "news:u", KernelHandles::NEWS_U },
|
||||
{ "nfc:u", KernelHandles::NFC },
|
||||
{ "nim:aoc", KernelHandles::NIM },
|
||||
{ "ptm:u", KernelHandles::PTM }, // TODO: ptm:u and ptm:sysm have very different command sets
|
||||
{ "ptm:sysm", KernelHandles::PTM },
|
||||
{ "soc:U", KernelHandles::SOC },
|
||||
{ "ssl:C", KernelHandles::SSL },
|
||||
{ "y2r:u", KernelHandles::Y2R }
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -193,12 +200,15 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
|
|||
case KernelHandles::FRD: frd.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::LCD: gsp_lcd.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::LDR_RO: ldr.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::MCU_HWC: mcu_hwc.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::MIC: mic.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::NFC: nfc.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::NIM: nim.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::NDM: ndm.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::NEWS_U: news_u.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::PTM: ptm.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::SOC: soc.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::SSL: ssl.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::Y2R: y2r.handleSyncRequest(messagePointer); break;
|
||||
default: Helpers::panic("Sent IPC message to unknown service %08X\n Command: %08X", handle, mem.read32(messagePointer));
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
63
src/core/services/ssl.cpp
Normal file
63
src/core/services/ssl.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "ipc.hpp"
|
||||
#include "result/result.hpp"
|
||||
#include "services/ssl.hpp"
|
||||
|
||||
namespace SSLCommands {
|
||||
enum : u32 {
|
||||
Initialize = 0x00010002,
|
||||
GenerateRandomData = 0x00110042,
|
||||
};
|
||||
}
|
||||
|
||||
void SSLService::reset() {
|
||||
initialized = false;
|
||||
|
||||
// Use the default seed on reset to avoid funny bugs
|
||||
rng.seed();
|
||||
}
|
||||
|
||||
void SSLService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case SSLCommands::Initialize: initialize(messagePointer); break;
|
||||
case SSLCommands::GenerateRandomData: generateRandomData(messagePointer); break;
|
||||
default: Helpers::panic("SSL service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void SSLService::initialize(u32 messagePointer) {
|
||||
log("SSL::Initialize\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x01, 1, 0));
|
||||
|
||||
if (initialized) {
|
||||
Helpers::warn("SSL service initialized twice");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
rng.seed(std::random_device()()); // Seed rng via std::random_device
|
||||
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void SSLService::generateRandomData(u32 messagePointer) {
|
||||
const u32 size = mem.read32(messagePointer + 4);
|
||||
const u32 output = mem.read32(messagePointer + 12);
|
||||
log("SSL::GenerateRandomData (out = %08X, size = %08X)\n", output, size);
|
||||
|
||||
// TODO: This might be a biiit slow, might want to make it write in word quantities
|
||||
u32 data;
|
||||
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
// We don't have an available random value since we're on a multiple of 4 bytes and our Twister is 32-bit, generate a new one from the Mersenne Twister
|
||||
if ((i & 3) == 0) {
|
||||
data = rng();
|
||||
}
|
||||
|
||||
mem.write8(output + i, u8(data));
|
||||
// Shift data by 8 to get the next byte
|
||||
data >>= 8;
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x11, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
|
@ -23,6 +23,7 @@ namespace Y2RCommands {
|
|||
StartConversion = 0x00260000,
|
||||
StopConversion = 0x00270000,
|
||||
IsBusyConversion = 0x00280000,
|
||||
SetPackageParameter = 0x002901C0,
|
||||
PingProcess = 0x002A0000,
|
||||
DriverInitialize = 0x002B0000,
|
||||
DriverFinalize = 0x002C0000
|
||||
|
@ -60,6 +61,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
|
|||
case Y2RCommands::SetInputLineWidth: setInputLineWidth(messagePointer); break;
|
||||
case Y2RCommands::SetInputLines: setInputLines(messagePointer); break;
|
||||
case Y2RCommands::SetOutputFormat: setOutputFormat(messagePointer); break;
|
||||
case Y2RCommands::SetPackageParameter: setPackageParameter(messagePointer); break;
|
||||
case Y2RCommands::SetReceiving: setReceiving(messagePointer); break;
|
||||
case Y2RCommands::SetRotation: setRotation(messagePointer); break;
|
||||
case Y2RCommands::SetSendingY: setSendingY(messagePointer); break;
|
||||
|
@ -176,6 +178,17 @@ void Y2RService::setOutputFormat(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void Y2RService::setPackageParameter(u32 messagePointer) {
|
||||
// Package parameter is 3 words
|
||||
const u32 word1 = mem.read32(messagePointer + 4);
|
||||
const u32 word2 = mem.read32(messagePointer + 8);
|
||||
const u32 word3 = mem.read32(messagePointer + 12);
|
||||
Helpers::warn("Y2R::SetPackageParameter\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x29, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void Y2RService::setRotation(u32 messagePointer) {
|
||||
const u32 rot = mem.read32(messagePointer + 4);
|
||||
log("Y2R::SetRotation (format = %d)\n", rot);
|
||||
|
|
|
@ -12,8 +12,8 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
|
|||
#endif
|
||||
|
||||
Emulator::Emulator()
|
||||
: config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, config),
|
||||
memory(cpu.getTicksRef()), cheats(memory, kernel.getServiceManager().getHID()), running(false), programRunning(false)
|
||||
: config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config),
|
||||
memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), running(false), programRunning(false)
|
||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||
, httpServer(this)
|
||||
#endif
|
||||
|
@ -316,9 +316,25 @@ void Emulator::run() {
|
|||
|
||||
// Detect mouse motion events for gyroscope emulation
|
||||
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
|
||||
if (romType == ROMType::None || !holdingRightClick) break;
|
||||
if (romType == ROMType::None) break;
|
||||
|
||||
// Handle "dragging" across the touchscreen
|
||||
if (hid.isTouchScreenPressed()) {
|
||||
const s32 x = event.motion.x;
|
||||
const s32 y = event.motion.y;
|
||||
|
||||
// Check if touch falls in the touch screen area and register the new touch screen position
|
||||
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
||||
// Convert to 3DS coordinates
|
||||
u16 x_converted = static_cast<u16>(x) - 40;
|
||||
u16 y_converted = static_cast<u16>(y) - 240;
|
||||
|
||||
hid.setTouchScreenPress(x_converted, y_converted);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Relative motion since last mouse motion event
|
||||
const s32 motionX = event.motion.xrel;
|
||||
const s32 motionY = event.motion.yrel;
|
||||
|
@ -329,6 +345,7 @@ void Emulator::run() {
|
|||
const s32 pitch = motionY > 0 ? 0x7f : -0x7f;
|
||||
hid.setRoll(roll);
|
||||
hid.setPitch(pitch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -231,8 +231,8 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
|||
|
||||
// Positional Light
|
||||
if (bitfieldExtract(GPUREG_LIGHTi_CONFIG, 0, 1) == 0) {
|
||||
error_unimpl = true;
|
||||
// half_vector = normalize(normalize(light_vector + v_view) + view);
|
||||
// error_unimpl = true;
|
||||
half_vector = normalize(normalize(light_vector + v_view) + view);
|
||||
}
|
||||
|
||||
// Directional light
|
||||
|
@ -327,7 +327,7 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
|||
if (fresnel_output2 == 1u) secondary_color.a = d[FR_LUT];
|
||||
|
||||
if (error_unimpl) {
|
||||
secondary_color = primary_color = vec4(1.0, 0., 1.0, 1.0);
|
||||
// secondary_color = primary_color = vec4(1.0, 0., 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
Renderer::Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs) : gpu(gpu), regs(internalRegs) {}
|
||||
Renderer::Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: gpu(gpu), regs(internalRegs), externalRegs(externalRegs) {}
|
||||
Renderer::~Renderer() {}
|
||||
|
||||
std::optional<RendererType> Renderer::typeFromString(std::string inString) {
|
||||
|
|
2
third_party/discord-rpc
vendored
2
third_party/discord-rpc
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33
|
||||
Subproject commit e1caa6186a374758692fb764b811155483742a56
|
|
@ -687,7 +687,6 @@ namespace OpenGL {
|
|||
Rectangle(T x, T y, T width, T height) : x(x), y(y), width(width), height(height) {}
|
||||
|
||||
bool isEmpty() const { return width == 0 && height == 0; }
|
||||
bool isLine() const { return (width == 0 && height != 0) || (width != 0 && height == 0); }
|
||||
|
||||
void setEmpty() { x = y = width = height = 0; }
|
||||
};
|
Loading…
Add table
Reference in a new issue