Merge branch 'master' into TemporaryHackishBranch

# Conflicts:
#	Core/Config.cpp
#	Core/Config.h
#	Core/HW/SasAudio.cpp
#	Core/MIPS/MIPSVFPUUtils.cpp
#	GPU/D3D11/StateMappingD3D11.cpp
#	GPU/Directx9/StateMappingDX9.cpp
#	GPU/GLES/DrawEngineGLES.cpp
#	GPU/Vulkan/DrawEngineVulkan.cpp
#	README.md
#	UI/EmuScreen.cpp
#	UI/GameSettingsScreen.cpp
#	UI/GameSettingsScreen.h
#	Windows/resource.h
#	ext/native/thin3d/VulkanRenderManager.cpp
This commit is contained in:
LunaMoo committed 2018-07-11 22:37:00 +02:00
commit 9c655d8f89
379 files changed
+11257 -10640

No files matched your search

+61 -47
View File
@@ -19,6 +19,11 @@ if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
message("Clang enabled")
endif()
if(FORCED_CPU)
message("Detected CPU (${CMAKE_SYSTEM_PROCESSOR}) overridden as: ${FORCED_CPU}")
set(CMAKE_SYSTEM_PROCESSOR ${FORCED_CPU})
endif()
# Detect CPU from CMAKE configuration. Toolchains should set this up
if(CMAKE_SYSTEM_PROCESSOR)
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^arm")
@@ -109,6 +114,7 @@ option(USING_EGL "Set to ON if target environment uses EGL" ${USING_EGL})
option(USING_FBDEV "Set to ON if target environment uses fbdev (eg. Pandora)" ${USING_FBDEV})
option(USING_GLES2 "Set to ON if target device uses OpenGL ES 2.0" ${USING_GLES2})
option(RASPBIAN "Set to ON to workaround threading issues when building for ARMV7 on Raspbian" ${RASPBIAN})
option(USING_X11_VULKAN "Set to OFF if target environment doesn't use X11 for Vulkan" ON)
# :: Frontends
option(USING_QT_UI "Set to ON if you wish to use the Qt frontend wrapper" ${USING_QT_UI})
option(MOBILE_DEVICE "Set to ON when targeting a mobile device" ${MOBILE_DEVICE})
@@ -124,7 +130,12 @@ option(USE_WAYLAND_WSI "Set to ON to require Wayland support for Vulkan" ${USE_W
option(USE_ADDRESS_SANITIZER "Use Clang memory sanitizer" ${USE_ADDRESS_SANITIZER})
if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN)
add_definitions(-DVK_USE_PLATFORM_XLIB_KHR)
if(USING_X11_VULKAN)
message("Using X11 for Vulkan")
add_definitions(-DVK_USE_PLATFORM_XLIB_KHR)
else()
message("NOT using X11 for Vulkan")
endif()
# add_definitions(-DVK_USE_PLATFORM_XCB_KHR)
find_package(Wayland)
if (USE_WAYLAND_WSI AND NOT WAYLAND_FOUND)
@@ -273,8 +284,13 @@ if(NOT MSVC)
if(IOS)
elseif(APPLE AND NOT CMAKE_CROSSCOMPILING)
# We want C++11, so target 10.7+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.7")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++ -U__STRICT_ANSI__")
if(USING_QT_UI)
set(MACOSX_VERSION_MIN "10.8")
else()
set(MACOSX_VERSION_MIN "10.7")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${MACOSX_VERSION_MIN}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${MACOSX_VERSION_MIN} -stdlib=libc++ -U__STRICT_ANSI__")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
elseif(NOT ANDROID)
# TODO: See if we can get rid of no-psabi
@@ -458,11 +474,9 @@ if(USING_GLES2)
find_package(X11)
endif()
add_library(vjson STATIC
ext/native/ext/vjson/json.cpp
ext/native/ext/vjson/json.h
ext/native/ext/vjson/block_allocator.cpp
ext/native/ext/vjson/block_allocator.h
add_library(gason STATIC
ext/native/ext/gason/gason.cpp
ext/native/ext/gason/gason.h
)
add_library(rg_etc1 STATIC
@@ -751,43 +765,16 @@ elseif(USING_QT_UI)
if(NOT SDL2_FOUND)
find_package(Qt5 COMPONENTS Multimedia)
endif(NOT SDL2_FOUND)
set(Qt_UI
Qt/Debugger/debugger_disasm.ui
Qt/Debugger/debugger_displaylist.ui
Qt/Debugger/debugger_memory.ui
Qt/Debugger/debugger_memorytex.ui
Qt/Debugger/debugger_vfpu.ui
)
qt5_wrap_ui(QT_UI_GEN ${Qt_UI})
list(APPEND NativeAppSource
${QT_UI_GEN}
Qt/QtMain.cpp
Qt/QtMain.h
Qt/QtHost.cpp
Qt/QtHost.h
Qt/mainwindow.cpp
Qt/mainwindow.h
Qt/Debugger/ctrldisasmview.cpp
Qt/Debugger/ctrldisasmview.h
Qt/Debugger/ctrlmemview.cpp
Qt/Debugger/ctrlmemview.h
Qt/Debugger/ctrlregisterlist.cpp
Qt/Debugger/ctrlregisterlist.h
Qt/Debugger/ctrlvfpuview.cpp
Qt/Debugger/ctrlvfpuview.h
Qt/Debugger/debugger_disasm.cpp
Qt/Debugger/debugger_disasm.h
Qt/Debugger/debugger_displaylist.cpp
Qt/Debugger/debugger_displaylist.h
Qt/Debugger/debugger_memory.cpp
Qt/Debugger/debugger_memory.h
Qt/Debugger/debugger_memorytex.cpp
Qt/Debugger/debugger_memorytex.h
Qt/Debugger/debugger_vfpu.cpp
Qt/Debugger/debugger_vfpu.h
)
add_definitions(-DUSING_QT_UI)
include_directories(${CMAKE_CURRENT_BINARY_DIR} Qt Qt/Debugger)
include_directories(${CMAKE_CURRENT_BINARY_DIR} Qt)
set(nativeExtraLibs ${nativeExtraLibs} Qt5::OpenGL Qt5::Gui Qt5::Core)
set(TargetBin PPSSPPQt)
@@ -917,12 +904,10 @@ add_library(native STATIC
ext/native/base/stringutil.h
ext/native/base/timeutil.cpp
ext/native/base/timeutil.h
ext/native/data/base64.cpp
ext/native/data/base64.h
ext/native/data/compression.cpp
ext/native/data/compression.h
ext/native/ext/vjson/json.cpp
ext/native/ext/vjson/json.h
ext/native/ext/vjson/block_allocator.cpp
ext/native/ext/vjson/block_allocator.h
ext/native/file/chunk_file.cpp
ext/native/file/chunk_file.h
ext/native/file/fd_util.cpp
@@ -976,6 +961,10 @@ add_library(native STATIC
ext/native/input/keycodes.h
ext/native/input/input_state.h
ext/native/input/input_state.cpp
ext/native/json/json_reader.h
ext/native/json/json_reader.cpp
ext/native/json/json_writer.h
ext/native/json/json_writer.cpp
ext/native/math/fast/fast_math.c
ext/native/math/fast/fast_matrix.c
ext/native/math/fast/fast_matrix_neon.S
@@ -1009,6 +998,8 @@ add_library(native STATIC
ext/native/net/sinks.h
ext/native/net/url.cpp
ext/native/net/url.h
ext/native/net/websocket_server.cpp
ext/native/net/websocket_server.h
ext/native/profiler/profiler.cpp
ext/native/profiler/profiler.h
ext/native/thin3d/thin3d.cpp
@@ -1062,7 +1053,7 @@ if(ANDROID)
SET(ATOMIC_LIB atomic)
endif()
target_link_libraries(native ${LIBZIP_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} rg_etc1 vjson udis86 ${RT_LIB} ${nativeExtraLibs} ${ATOMIC_LIB})
target_link_libraries(native ${LIBZIP_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} rg_etc1 gason udis86 ${RT_LIB} ${nativeExtraLibs} ${ATOMIC_LIB})
if(TARGET Ext::GLEW)
target_link_libraries(native Ext::GLEW)
endif()
@@ -1111,10 +1102,6 @@ include_directories(ext/xxhash)
set(CoreExtra)
set(CoreExtraLibs)
if(WIN32 OR ANDROID)
set(CoreExtraLibs ${CoreExtraLibs} armips)
endif()
set(CoreExtra ${CoreExtra}
Core/MIPS/IR/IRCompALU.cpp
Core/MIPS/IR/IRCompBranch.cpp
@@ -1405,6 +1392,7 @@ add_library(${CoreLibName} ${CoreLinkType}
${CoreExtra}
Core/Config.cpp
Core/Config.h
Core/ConfigValues.h
Core/Core.cpp
Core/Core.h
Core/Compatibility.cpp
@@ -1417,6 +1405,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/HDRemaster.cpp
Core/HDRemaster.h
Core/ThreadEventQueue.h
Core/WebServer.cpp
Core/WebServer.h
Core/Debugger/Breakpoints.cpp
Core/Debugger/Breakpoints.h
Core/Debugger/DebugInterface.h
@@ -1424,6 +1414,30 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Debugger/SymbolMap.h
Core/Debugger/DisassemblyManager.cpp
Core/Debugger/DisassemblyManager.h
Core/Debugger/WebSocket.cpp
Core/Debugger/WebSocket.h
Core/Debugger/WebSocket/BreakpointSubscriber.cpp
Core/Debugger/WebSocket/BreakpointSubscriber.h
Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
Core/Debugger/WebSocket/CPUCoreSubscriber.h
Core/Debugger/WebSocket/DisasmSubscriber.cpp
Core/Debugger/WebSocket/DisasmSubscriber.h
Core/Debugger/WebSocket/GameBroadcaster.cpp
Core/Debugger/WebSocket/GameBroadcaster.h
Core/Debugger/WebSocket/GameSubscriber.cpp
Core/Debugger/WebSocket/GameSubscriber.h
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Core/Debugger/WebSocket/GPUBufferSubscriber.h
Core/Debugger/WebSocket/HLESubscriber.cpp
Core/Debugger/WebSocket/HLESubscriber.h
Core/Debugger/WebSocket/LogBroadcaster.cpp
Core/Debugger/WebSocket/LogBroadcaster.h
Core/Debugger/WebSocket/SteppingBroadcaster.cpp
Core/Debugger/WebSocket/SteppingBroadcaster.h
Core/Debugger/WebSocket/SteppingSubscriber.cpp
Core/Debugger/WebSocket/SteppingSubscriber.h
Core/Debugger/WebSocket/WebSocketUtils.cpp
Core/Debugger/WebSocket/WebSocketUtils.h
Core/Dialog/PSPDialog.cpp
Core/Dialog/PSPDialog.h
Core/Dialog/PSPGamedataInstallDialog.cpp
@@ -1701,7 +1715,6 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Util/ppge_atlas.h
${CORE_NEON}
${GPU_SOURCES}
git-version.cpp
ext/disarm.cpp
git-version.cpp)
@@ -1751,7 +1764,8 @@ add_custom_command(OUTPUT something_that_never_exists
-P ${CMAKE_CURRENT_SOURCE_DIR}/git-version.cmake)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/git-version.cpp
PROPERTIES GENERATED TRUE)
PROPERTIES GENERATED TRUE
SKIP_AUTOMOC ON)
add_dependencies(${CoreLibName} GitVersion)
set(WindowsFiles
+9 -3
View File
@@ -225,7 +225,7 @@ CChunkFileReader::Error CChunkFileReader::GetFileTitle(const std::string &filena
return LoadFileHeader(pFile, header, title);
}
CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, const char *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) {
CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, std::string *gitVersion, u8 *&_buffer, size_t &sz, std::string *failureReason) {
if (!File::Exists(filename)) {
*failureReason = "LoadStateDoesntExist";
ERROR_LOG(SAVESTATE, "ChunkReader: File doesn't exist");
@@ -264,6 +264,12 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename,
delete [] buffer;
}
if (header.GitVersion[31]) {
*gitVersion = std::string(header.GitVersion, 32);
} else {
*gitVersion = header.GitVersion;
}
return ERROR_NONE;
}
@@ -294,7 +300,7 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string &filename,
}
// Create header
SChunkHeader header;
SChunkHeader header{};
header.Compress = compressed_buffer ? 1 : 0;
header.Revision = REVISION_CURRENT;
header.ExpectedSize = (u32)write_len;
@@ -302,7 +308,7 @@ CChunkFileReader::Error CChunkFileReader::SaveFile(const std::string &filename,
truncate_cpy(header.GitVersion, gitVersion);
// Setup the fixed-length title.
char titleFixed[128];
char titleFixed[128]{};
truncate_cpy(titleFixed, title.c_str());
// Now let's start writing out the file...
+2 -2
View File
@@ -622,7 +622,7 @@ public:
// Load file template
template<class T>
static Error Load(const std::string &filename, const char *gitVersion, T& _class, std::string *failureReason)
static Error Load(const std::string &filename, std::string *gitVersion, T& _class, std::string *failureReason)
{
*failureReason = "LoadStateWrongVersion";
@@ -700,7 +700,7 @@ private:
REVISION_CURRENT = REVISION_TITLE,
};
static Error LoadFile(const std::string &filename, const char *gitVersion, u8 *&buffer, size_t &sz, std::string *failureReason);
static Error LoadFile(const std::string &filename, std::string *gitVersion, u8 *&buffer, size_t &sz, std::string *failureReason);
static Error SaveFile(const std::string &filename, const std::string &title, const char *gitVersion, u8 *buffer, size_t sz);
static Error LoadFileHeader(File::IOFile &pFile, SChunkHeader &header, std::string *title);
};
+1 -1
View File
@@ -600,7 +600,7 @@ void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool
void ConsoleListener::Log(const LogMessage &msg) {
char Text[2048];
snprintf(Text, sizeof(Text), "%s %s", msg.header, msg.msg.c_str());
snprintf(Text, sizeof(Text), "%s %s %s", msg.timestamp, msg.header, msg.msg.c_str());
Text[sizeof(Text) - 2] = '\n';
Text[sizeof(Text) - 1] = '\0';
+2
View File
@@ -46,6 +46,8 @@ public:
head_ = 0;
tail_ = 0;
count_ = 0;
// Not entirely necessary, but keeps things clean.
memset(storage_, 0, sizeof(T) * N);
}
void push(T t) {
+28 -5
View File
@@ -202,14 +202,21 @@ bool cInterfaceEGL::ChooseAndCreate(void *window_handle, bool core, bool use565)
EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE, 0
EGL_NONE, 0,
EGL_NONE, 0,
EGL_NONE, 0,
EGL_NONE, 0,
};
switch (s_opengl_mode) {
case MODE_OPENGL:
EGL_ILOG("Setting RENDERABLE_TYPE to EGL_OPENGL_BIT");
attribs[1] = EGL_OPENGL_BIT;
ctx_attribs[0] = EGL_NONE;
// 1 will be major version, and 3 the minor version.
ctx_attribs[2] = 0x30FB; /* EGL_CONTEXT_MINOR_VERSION_KHR */
// Let's always use a core profile here.
ctx_attribs[4] = 0x30FD; /* EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR */
ctx_attribs[5] = 1; /* EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR */
break;
case MODE_OPENGLES2:
EGL_ILOG("Setting RENDERABLE_TYPE to EGL_OPENGL_ES2_BIT");
@@ -230,7 +237,7 @@ bool cInterfaceEGL::ChooseAndCreate(void *window_handle, bool core, bool use565)
EGL_ILOG("Calling eglChooseConfig to get number of configs (use16bit=%d)...", (int)use565);
EGLConfig *configs;
EGLint num_configs;
EGLint num_configs = 0;
if (!eglChooseConfig(egl_dpy, attribs, NULL, 0, &num_configs) || num_configs == 0) {
EGL_ILOG("Error: couldn't get a number of configs. Trying with fallback config (no stencil, not specifying transparent:none)\n");
attribsFallback[1] = attribs[1];
@@ -298,7 +305,23 @@ bool cInterfaceEGL::ChooseAndCreate(void *window_handle, bool core, bool use565)
s = eglQueryString(egl_dpy, EGL_CLIENT_APIS);
EGL_ILOG("EGL_CLIENT_APIS = %s\n", s);
egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs);
if (s_opengl_mode == MODE_OPENGL) {
EGL_ILOG("Finding a good GL version");
egl_ctx = nullptr;
for (int minor = 6; minor >= 0 && !egl_ctx; --minor) {
ctx_attribs[1] = 4;
ctx_attribs[3] = minor;
egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs);
}
if (!egl_ctx) {
ctx_attribs[1] = 3;
ctx_attribs[3] = 3;
egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs);
}
} else {
egl_ctx = eglCreateContext(egl_dpy, configs[chosenConfig], EGL_NO_CONTEXT, ctx_attribs);
}
if (!egl_ctx) {
EGL_ILOG("Error: eglCreateContext failed: %s\n", EGLGetErrorString(eglGetError()));
delete[] configs;
@@ -339,7 +362,7 @@ bool cInterfaceEGL::Create(void *window_handle, bool core, bool use565) {
if (s_opengl_mode == MODE_DETECT || s_opengl_mode == MODE_DETECT_ES)
DetectMode();
if (!ChooseAndCreate(window_handle, core, use565) && s_opengl_mode == MODE_OPENGLES3) {
if (!ChooseAndCreate(window_handle, core, use565) && (s_opengl_mode == MODE_OPENGLES3 || s_opengl_mode == MODE_OPENGL)) {
// Fallback to ES 2.0 and try again.
s_opengl_mode = MODE_OPENGLES2;
if (!ChooseAndCreate(window_handle, core, use565)) {
+2
View File
@@ -649,6 +649,8 @@ const KeyMap_IntStrPair psp_button_names[] = {
{VIRTKEY_RAPID_FIRE, "RapidFire"},
{VIRTKEY_UNTHROTTLE, "Unthrottle"},
{VIRTKEY_SPEED_TOGGLE, "SpeedToggle"},
{VIRTKEY_SPEED_CUSTOM1, "Alt speed 1"},
{VIRTKEY_SPEED_CUSTOM2, "Alt speed 2"},
{VIRTKEY_PAUSE, "Pause"},
#ifndef MOBILE_DEVICE
{VIRTKEY_FRAME_ADVANCE, "Frame Advance"},
+2
View File
@@ -52,6 +52,8 @@ enum {
VIRTKEY_DEVMENU = 0x40000014,
VIRTKEY_FRAME_ADVANCE = 0x40000015,
VIRTKEY_RECORD = 0x40000016,
VIRTKEY_SPEED_CUSTOM1 = 0x40000017,
VIRTKEY_SPEED_CUSTOM2 = 0x40000018,
VIRTKEY_LAST,
VIRTKEY_COUNT = VIRTKEY_LAST - VIRTKEY_FIRST
};
+6 -12
View File
@@ -225,22 +225,17 @@ void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const
if (fileshort != file)
file = fileshort + 1;
}
char formattedTime[13];
std::lock_guard<std::mutex> lk(log_lock_);
Common::Timer::GetTimeFormatted(formattedTime);
Common::Timer::GetTimeFormatted(message.timestamp);
size_t prefixLen;
if (hleCurrentThreadName) {
prefixLen = snprintf(message.header, sizeof(message.header), "%s %-12.12s %c[%s]: %s:%d",
formattedTime,
snprintf(message.header, sizeof(message.header), "%-12.12s %c[%s]: %s:%d",
hleCurrentThreadName, level_to_char[(int)level],
log.m_shortName,
file, line);
} else {
prefixLen = snprintf(message.header, sizeof(message.header), "%s %s:%d %c[%s]:",
formattedTime,
snprintf(message.header, sizeof(message.header), "%s:%d %c[%s]:",
file, line, level_to_char[(int)level],
log.m_shortName);
}
@@ -250,15 +245,14 @@ void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const
va_copy(args_copy, args);
size_t neededBytes = vsnprintf(msgBuf, sizeof(msgBuf), format, args);
message.msg.resize(neededBytes + 1);
if (neededBytes > sizeof(msgBuf)) {
// Needed more space? Re-run vsnprintf.
message.msg.resize(neededBytes + 1);
vsnprintf(&message.msg[0], neededBytes + 1, format, args_copy);
} else {
message.msg.resize(neededBytes + 1);
memcpy(&message.msg[0], msgBuf, neededBytes);
}
message.msg[message.msg.size() - 1] = '\n';
message.msg[neededBytes] = '\n';
va_end(args_copy);
std::lock_guard<std::mutex> listeners_lock(listeners_lock_);
@@ -313,7 +307,7 @@ void FileLogListener::Log(const LogMessage &message) {
return;
std::lock_guard<std::mutex> lk(m_log_lock);
m_logfile << message.header << " " << message.msg << std::flush;
m_logfile << message.timestamp << " " << message.header << " " << message.msg << std::flush;
}
void OutputDebugStringLogListener::Log(const LogMessage &message) {
+2 -1
View File
@@ -34,7 +34,8 @@ extern const char *hleCurrentThreadName;
// Struct that listeners can output how they want. For example, on Android we don't want to add
// timestamp or write the level as a string, those already exist.
struct LogMessage {
char header[64]; // timestamp and the other stuff in front...
char timestamp[16];
char header[64]; // Filename/thread/etc. in front.
LogTypes::LOG_LEVELS level;
const char *log;
std::string msg; // The actual log message.
+17 -1
View File
@@ -213,6 +213,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
assert(gpu_count > 0);
physical_devices_.resize(gpu_count);
physicalDeviceProperties_.resize(gpu_count);
res = vkEnumeratePhysicalDevices(instance_, &gpu_count, physical_devices_.data());
if (res != VK_SUCCESS) {
init_error_ = "Failed to enumerate physical devices";
@@ -221,6 +222,9 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
return res;
}
for (uint32_t i = 0; i < gpu_count; i++) {
vkGetPhysicalDeviceProperties(physical_devices_[i], &physicalDeviceProperties_[i]);
}
return VK_SUCCESS;
}
@@ -417,6 +421,14 @@ bool VulkanContext::CheckLayers(const std::vector<LayerProperties> &layer_props,
return true;
}
int VulkanContext::GetPhysicalDeviceByName(std::string name) {
for (size_t i = 0; i < physical_devices_.size(); i++) {
if (physicalDeviceProperties_[i].deviceName == name)
return (int)i;
}
return -1;
}
int VulkanContext::GetBestPhysicalDevice() {
// Rules: Prefer discrete over embedded.
// Prefer nVidia over Intel.
@@ -432,12 +444,17 @@ int VulkanContext::GetBestPhysicalDevice() {
case VK_PHYSICAL_DEVICE_TYPE_CPU:
score += 1;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
score += 2;
break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
score += 20;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
score += 10;
break;
default:
break;
}
if (props.vendorID == VULKAN_VENDOR_AMD) {
score += 5;
@@ -490,7 +507,6 @@ void VulkanContext::ChooseDevice(int physical_device) {
// This is as good a place as any to do this
vkGetPhysicalDeviceMemoryProperties(physical_devices_[physical_device_], &memory_properties);
vkGetPhysicalDeviceProperties(physical_devices_[physical_device_], &gpu_props);
// Optional features
vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &featuresAvailable_);
+10 -3
View File
@@ -128,6 +128,7 @@ public:
void DestroyInstance();
int GetBestPhysicalDevice();
int GetPhysicalDeviceByName(std::string name);
void ChooseDevice(int physical_device);
bool EnableDeviceExtension(const char *extension);
VkResult CreateDevice();
@@ -173,6 +174,12 @@ public:
VkPhysicalDevice GetPhysicalDevice(int n = 0) const {
return physical_devices_[n];
}
int GetCurrentPhysicalDevice() const {
return physical_device_;
}
int GetNumPhysicalDevices() const {
return (int)physical_devices_.size();
}
VkQueue GetGraphicsQueue() const {
return gfx_queue_;
@@ -182,8 +189,8 @@ public:
return graphics_queue_family_index_;
}
const VkPhysicalDeviceProperties &GetPhysicalDeviceProperties() {
return gpu_props;
const VkPhysicalDeviceProperties &GetPhysicalDeviceProperties(int i) const {
return physicalDeviceProperties_[i];
}
VkResult GetInstanceLayerExtensionList(const char *layerName, std::vector<VkExtensionProperties> &extensions);
@@ -281,7 +288,7 @@ private:
int physical_device_ = -1;
uint32_t graphics_queue_family_index_ = -1;
VkPhysicalDeviceProperties gpu_props{};
std::vector<VkPhysicalDeviceProperties> physicalDeviceProperties_{};
std::vector<VkQueueFamilyProperties> queue_props;
VkPhysicalDeviceMemoryProperties memory_properties{};
+2
View File
@@ -313,6 +313,8 @@ bool VulkanMayBeAvailable() {
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
anyGood = true;
break;
default:
break;
}
// TODO: Should also check queuefamilyproperties for a GRAPHICS queue family? Oh well.
}
+105 -135
View File
@@ -22,9 +22,9 @@
#include "base/display.h"
#include "base/NativeApp.h"
#include "ext/vjson/json.h"
#include "file/ini_file.h"
#include "i18n/i18n.h"
#include "json/json_reader.h"
#include "gfx_es2/gpu_features.h"
#include "net/http_client.h"
#include "util/text/parsers.h"
@@ -38,6 +38,7 @@
#include "Common/StringUtils.h"
#include "Common/Vulkan/VulkanLoader.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/Loaders.h"
#include "Core/HLE/sceUtility.h"
#include "GPU/Common/FramebufferCommon.h"
@@ -63,6 +64,7 @@ struct ConfigSetting {
TYPE_UINT32,
TYPE_FLOAT,
TYPE_STRING,
TYPE_TOUCH_POS,
};
union Value {
bool b;
@@ -70,6 +72,7 @@ struct ConfigSetting {
uint32_t u;
float f;
const char *s;
ConfigTouchPos touchPos;
};
union SettingPtr {
bool *b;
@@ -77,6 +80,7 @@ struct ConfigSetting {
uint32_t *u;
float *f;
std::string *s;
ConfigTouchPos *touchPos;
};
typedef bool (*BoolDefaultCallback)();
@@ -84,6 +88,7 @@ struct ConfigSetting {
typedef uint32_t (*Uint32DefaultCallback)();
typedef float (*FloatDefaultCallback)();
typedef const char *(*StringDefaultCallback)();
typedef ConfigTouchPos (*TouchPosDefaultCallback)();
union Callback {
BoolDefaultCallback b;
@@ -91,6 +96,7 @@ struct ConfigSetting {
Uint32DefaultCallback u;
FloatDefaultCallback f;
StringDefaultCallback s;
TouchPosDefaultCallback touchPos;
};
ConfigSetting(bool v)
@@ -134,6 +140,13 @@ struct ConfigSetting {
default_.s = def;
}
ConfigSetting(const char *iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigTouchPos *v, ConfigTouchPos def, bool save = true, bool perGame = false)
: ini_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) {
ptr_.touchPos = v;
cb_.touchPos = nullptr;
default_.touchPos = def;
}
ConfigSetting(const char *ini, bool *v, BoolDefaultCallback def, bool save = true, bool perGame = false)
: ini_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) {
ptr_.b = v;
@@ -164,6 +177,12 @@ struct ConfigSetting {
cb_.s = def;
}
ConfigSetting(const char *iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigTouchPos *v, TouchPosDefaultCallback def, bool save = true, bool perGame = false)
: ini_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) {
ptr_.touchPos = v;
cb_.touchPos = def;
}
bool HasMore() const {
return type_ != TYPE_TERMINATOR;
}
@@ -195,6 +214,19 @@ struct ConfigSetting {
default_.s = cb_.s();
}
return section->Get(ini_, ptr_.s, default_.s);
case TYPE_TOUCH_POS:
if (cb_.touchPos) {
default_.touchPos = cb_.touchPos();
}
section->Get(ini_, &ptr_.touchPos->x, default_.touchPos.x);
section->Get(ini2_, &ptr_.touchPos->y, default_.touchPos.y);
section->Get(ini3_, &ptr_.touchPos->scale, default_.touchPos.scale);
if (ini4_) {
section->Get(ini4_, &ptr_.touchPos->show, default_.touchPos.show);
} else {
ptr_.touchPos->show = default_.touchPos.show;
}
return true;
default:
_dbg_assert_msg_(LOADER, false, "Unexpected ini setting type");
return false;
@@ -216,6 +248,14 @@ struct ConfigSetting {
return section->Set(ini_, *ptr_.f);
case TYPE_STRING:
return section->Set(ini_, *ptr_.s);
case TYPE_TOUCH_POS:
section->Set(ini_, ptr_.touchPos->x);
section->Set(ini2_, ptr_.touchPos->y);
section->Set(ini3_, ptr_.touchPos->scale);
if (ini4_) {
section->Set(ini4_, ptr_.touchPos->show);
}
return;
default:
_dbg_assert_msg_(LOADER, false, "Unexpected ini setting type");
return;
@@ -237,6 +277,9 @@ struct ConfigSetting {
return data.Add(prefix + ini_, *ptr_.f);
case TYPE_STRING:
return data.Add(prefix + ini_, *ptr_.s);
case TYPE_TOUCH_POS:
// Doesn't report.
return;
default:
_dbg_assert_msg_(LOADER, false, "Unexpected ini setting type");
return;
@@ -244,6 +287,9 @@ struct ConfigSetting {
}
const char *ini_;
const char *ini2_;
const char *ini3_;
const char *ini4_;
Type type_;
bool report_;
bool save_;
@@ -356,7 +402,7 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("ForceLagSync", &g_Config.bForceLagSync, false, true, true),
ReportedConfigSetting("NumWorkerThreads", &g_Config.iNumWorkerThreads, &DefaultNumWorkers, true, true),
ConfigSetting("EnableAutoLoad", &g_Config.bEnableAutoLoad, false, true, true),
ConfigSetting("AutoLoadSaveState", &g_Config.iAutoLoadSaveState, 0, true, true),
ReportedConfigSetting("EnableCheats", &g_Config.bEnableCheats, true, true, true),
ConfigSetting("CwCheatRefreshRate", &g_Config.iCwCheatRefreshRate, 77, true, true),
@@ -385,11 +431,12 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("RemoteISOManualConfig", &g_Config.bRemoteISOManual, false),
ConfigSetting("RemoteShareOnStartup", &g_Config.bRemoteShareOnStartup, false),
ConfigSetting("RemoteISOSubdir", &g_Config.sRemoteISOSubdir, "/"),
ConfigSetting("RemoteDebuggerOnStartup", &g_Config.bRemoteDebuggerOnStartup, false),
#ifdef __ANDROID__
ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, 1),
ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, ROTATION_LOCKED_HORIZONTAL),
#endif
ConfigSetting("InternalScreenRotation", &g_Config.iInternalScreenRotation, 1),
ConfigSetting("InternalScreenRotation", &g_Config.iInternalScreenRotation, ROTATION_LOCKED_HORIZONTAL),
#if defined(USING_WIN_UI)
ConfigSetting("TopMost", &g_Config.bTopMost, false),
@@ -422,6 +469,7 @@ static ConfigSetting cpuSettings[] = {
ConfigSetting("FastMemoryAccess", &g_Config.bFastMemory, false, true, true),
ReportedConfigSetting("FuncReplacements", &g_Config.bFuncReplacements, true, true, true),
ConfigSetting("HideSlowWarnings", &g_Config.bHideSlowWarnings, true, true, false),
ConfigSetting("HideStateWarnings", &g_Config.bHideStateWarnings, false, true, false),
ConfigSetting("PreloadFunctions", &g_Config.bPreloadFunctions, false, true, true),
ReportedConfigSetting("CPUSpeed", &g_Config.iLockedCPUSpeed, 0, true, true),
@@ -456,7 +504,7 @@ static bool DefaultFrameskipUnthrottle() {
}
static int DefaultZoomType() {
return 2;
return (int)SmallDisplayZoom::AUTO;
}
static bool DefaultTimerHack() {
@@ -512,20 +560,24 @@ static ConfigSetting graphicsSettings[] = {
ConfigSetting("CardboardYShift", &g_Config.iCardboardXShift, 0, true, true),
ConfigSetting("ShowFPSCounter", &g_Config.iShowFPSCounter, 0, true, true),
ReportedConfigSetting("GraphicsBackend", &g_Config.iGPUBackend, &DefaultGPUBackend),
ConfigSetting("VulkanDevice", &g_Config.sVulkanDevice, "", true, false),
#ifdef _WIN32
ConfigSetting("D3D11Device", &g_Config.sD3D11Device, "", true, false),
#endif
ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, &DefaultRenderingMode, true, true),
ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true),
ReportedConfigSetting("HardwareTransform", &g_Config.bHardwareTransform, true, true, true),
ReportedConfigSetting("SoftwareSkinning", &g_Config.bSoftwareSkinning, true, true, true),
ConfigSetting("ShaderDepal", &g_Config.bShaderDepal, false, true, true),
ReportedConfigSetting("TextureFiltering", &g_Config.iTexFiltering, 1, true, true),
ReportedConfigSetting("BufferFiltering", &g_Config.iBufFilter, 1, true, true),
ReportedConfigSetting("BufferFiltering", &g_Config.iBufFilter, SCALE_LINEAR, true, true),
ReportedConfigSetting("InternalResolution", &g_Config.iInternalResolution, &DefaultInternalResolution, true, true),
ReportedConfigSetting("AndroidHwScale", &g_Config.iAndroidHwScale, &DefaultAndroidHwScale),
ReportedConfigSetting("HighQualityDepth", &g_Config.bHighQualityDepth, true, true, true),
ReportedConfigSetting("FrameSkip", &g_Config.iFrameSkip, 0, true, true),
ReportedConfigSetting("AutoFrameSkip", &g_Config.bAutoFrameSkip, false, true, true),
ConfigSetting("FrameRate", &g_Config.iFpsLimit, 0, true, true),
ConfigSetting("UnToggleFpsLimit", &g_Config.bUnToggleFpsLimit, false, true, true),
ConfigSetting("FrameRate", &g_Config.iFpsLimit1, 0, true, true),
ConfigSetting("FrameRate2", &g_Config.iFpsLimit2, -1, true, true),
ConfigSetting("FrameSkipUnthrottle", &g_Config.bFrameSkipUnthrottle, &DefaultFrameskipUnthrottle, true, false),
#if defined(USING_WIN_UI)
ConfigSetting("RestartRequired", &g_Config.bRestartRequired, false, false),
@@ -617,6 +669,8 @@ static bool DefaultShowTouchControls() {
}
static const float defaultControlScale = 1.15f;
static const ConfigTouchPos defaultTouchPosShow = { -1.0f, -1.0f, defaultControlScale, true };
static const ConfigTouchPos defaultTouchPosHide = { -1.0f, -1.0f, defaultControlScale, false };
static ConfigSetting controlSettings[] = {
ConfigSetting("HapticFeedback", &g_Config.bHapticFeedback, false, true, true),
@@ -624,19 +678,7 @@ static ConfigSetting controlSettings[] = {
ConfigSetting("ShowTouchCircle", &g_Config.bShowTouchCircle, true, true, true),
ConfigSetting("ShowTouchSquare", &g_Config.bShowTouchSquare, true, true, true),
ConfigSetting("ShowTouchTriangle", &g_Config.bShowTouchTriangle, true, true, true),
ConfigSetting("ShowTouchStart", &g_Config.bShowTouchStart, true, true, true),
ConfigSetting("ShowTouchSelect", &g_Config.bShowTouchSelect, true, true, true),
ConfigSetting("ShowTouchLTrigger", &g_Config.bShowTouchLTrigger, true, true, true),
ConfigSetting("ShowTouchRTrigger", &g_Config.bShowTouchRTrigger, true, true, true),
ConfigSetting("ShowAnalogStick", &g_Config.bShowTouchAnalogStick, true, true, true),
ConfigSetting("ShowTouchDpad", &g_Config.bShowTouchDpad, true, true, true),
ConfigSetting("ShowTouchUnthrottle", &g_Config.bShowTouchUnthrottle, true, true, true),
ConfigSetting("ShowComboKey0", &g_Config.bShowComboKey0, false, true, true),
ConfigSetting("ShowComboKey1", &g_Config.bShowComboKey1, false, true, true),
ConfigSetting("ShowComboKey2", &g_Config.bShowComboKey2, false, true, true),
ConfigSetting("ShowComboKey3", &g_Config.bShowComboKey3, false, true, true),
ConfigSetting("ShowComboKey4", &g_Config.bShowComboKey4, false, true, true),
ConfigSetting("ComboKey0Mapping", &g_Config.iCombokey0, 0, true, true),
ConfigSetting("ComboKey1Mapping", &g_Config.iCombokey1, 0, true, true),
ConfigSetting("ComboKey2Mapping", &g_Config.iCombokey2, 0, true, true),
@@ -676,49 +718,26 @@ static ConfigSetting controlSettings[] = {
// -1.0f means uninitialized, set in GamepadEmu::CreatePadLayout().
ConfigSetting("ActionButtonSpacing2", &g_Config.fActionButtonSpacing, 1.0f, true, true),
ConfigSetting("ActionButtonCenterX", &g_Config.fActionButtonCenterX, -1.0f, true, true),
ConfigSetting("ActionButtonCenterY", &g_Config.fActionButtonCenterY, -1.0f, true, true),
ConfigSetting("ActionButtonScale", &g_Config.fActionButtonScale, defaultControlScale, true, true),
ConfigSetting("DPadX", &g_Config.fDpadX, -1.0f, true, true),
ConfigSetting("DPadY", &g_Config.fDpadY, -1.0f, true, true),
ConfigSetting("ActionButtonCenterX", "ActionButtonCenterY", "ActionButtonScale", nullptr, &g_Config.touchActionButtonCenter, defaultTouchPosShow, true, true),
ConfigSetting("DPadX", "DPadY", "DPadScale", "ShowTouchDpad", &g_Config.touchDpad, defaultTouchPosShow, true, true),
// Note: these will be overwritten if DPadRadius is set.
ConfigSetting("DPadScale", &g_Config.fDpadScale, defaultControlScale, true, true),
ConfigSetting("DPadSpacing", &g_Config.fDpadSpacing, 1.0f, true, true),
ConfigSetting("StartKeyX", &g_Config.fStartKeyX, -1.0f, true, true),
ConfigSetting("StartKeyY", &g_Config.fStartKeyY, -1.0f, true, true),
ConfigSetting("StartKeyScale", &g_Config.fStartKeyScale, defaultControlScale, true, true),
ConfigSetting("SelectKeyX", &g_Config.fSelectKeyX, -1.0f, true, true),
ConfigSetting("SelectKeyY", &g_Config.fSelectKeyY, -1.0f, true, true),
ConfigSetting("SelectKeyScale", &g_Config.fSelectKeyScale, defaultControlScale, true, true),
ConfigSetting("UnthrottleKeyX", &g_Config.fUnthrottleKeyX, -1.0f, true, true),
ConfigSetting("UnthrottleKeyY", &g_Config.fUnthrottleKeyY, -1.0f, true, true),
ConfigSetting("UnthrottleKeyScale", &g_Config.fUnthrottleKeyScale, defaultControlScale, true, true),
ConfigSetting("LKeyX", &g_Config.fLKeyX, -1.0f, true, true),
ConfigSetting("LKeyY", &g_Config.fLKeyY, -1.0f, true, true),
ConfigSetting("LKeyScale", &g_Config.fLKeyScale, defaultControlScale, true, true),
ConfigSetting("RKeyX", &g_Config.fRKeyX, -1.0f, true, true),
ConfigSetting("RKeyY", &g_Config.fRKeyY, -1.0f, true, true),
ConfigSetting("RKeyScale", &g_Config.fRKeyScale, defaultControlScale, true, true),
ConfigSetting("AnalogStickX", &g_Config.fAnalogStickX, -1.0f, true, true),
ConfigSetting("AnalogStickY", &g_Config.fAnalogStickY, -1.0f, true, true),
ConfigSetting("AnalogStickScale", &g_Config.fAnalogStickScale, defaultControlScale, true, true),
ConfigSetting("StartKeyX", "StartKeyY", "StartKeyScale", "ShowTouchStart", &g_Config.touchStartKey, defaultTouchPosShow, true, true),
ConfigSetting("SelectKeyX", "SelectKeyY", "SelectKeyScale", "ShowTouchSelect", &g_Config.touchSelectKey, defaultTouchPosShow, true, true),
ConfigSetting("UnthrottleKeyX", "UnthrottleKeyY", "UnthrottleKeyScale", "ShowTouchUnthrottle", &g_Config.touchUnthrottleKey, defaultTouchPosShow, true, true),
ConfigSetting("LKeyX", "LKeyY", "LKeyScale", "ShowTouchLTrigger", &g_Config.touchLKey, defaultTouchPosShow, true, true),
ConfigSetting("RKeyX", "RKeyY", "RKeyScale", "ShowTouchRTrigger", &g_Config.touchRKey, defaultTouchPosShow, true, true),
ConfigSetting("AnalogStickX", "AnalogStickY", "AnalogStickScale", "ShowAnalogStick", &g_Config.touchAnalogStick, defaultTouchPosShow, true, true),
ConfigSetting("fcombo0X", "fcombo0Y", "comboKeyScale0", "ShowComboKey0", &g_Config.touchCombo0, defaultTouchPosHide, true, true),
ConfigSetting("fcombo1X", "fcombo1Y", "comboKeyScale1", "ShowComboKey1", &g_Config.touchCombo1, defaultTouchPosHide, true, true),
ConfigSetting("fcombo2X", "fcombo2Y", "comboKeyScale2", "ShowComboKey2", &g_Config.touchCombo2, defaultTouchPosHide, true, true),
ConfigSetting("fcombo3X", "fcombo3Y", "comboKeyScale3", "ShowComboKey3", &g_Config.touchCombo3, defaultTouchPosHide, true, true),
ConfigSetting("fcombo4X", "fcombo4Y", "comboKeyScale4", "ShowComboKey4", &g_Config.touchCombo4, defaultTouchPosHide, true, true),
ConfigSetting("Speed1KeyX", "Speed1KeyY", "Speed1KeyScale", "ShowSpeed1Key", &g_Config.touchSpeed1Key, defaultTouchPosHide, true, true),
ConfigSetting("Speed2KeyX", "Speed2KeyY", "Speed2KeyScale", "ShowSpeed2Key", &g_Config.touchSpeed2Key, defaultTouchPosHide, true, true),
ConfigSetting("fcombo0X", &g_Config.fcombo0X, -1.0f, true, true),
ConfigSetting("fcombo0Y", &g_Config.fcombo0Y, -1.0f, true, true),
ConfigSetting("comboKeyScale0", &g_Config.fcomboScale0, defaultControlScale, true, true),
ConfigSetting("fcombo1X", &g_Config.fcombo1X, -1.0f, true, true),
ConfigSetting("fcombo1Y", &g_Config.fcombo1Y, -1.0f, true, true),
ConfigSetting("comboKeyScale1", &g_Config.fcomboScale1, defaultControlScale, true, true),
ConfigSetting("fcombo2X", &g_Config.fcombo2X, -1.0f, true, true),
ConfigSetting("fcombo2Y", &g_Config.fcombo2Y, -1.0f, true, true),
ConfigSetting("comboKeyScale2", &g_Config.fcomboScale2, defaultControlScale, true, true),
ConfigSetting("fcombo3X", &g_Config.fcombo3X, -1.0f, true, true),
ConfigSetting("fcombo3Y", &g_Config.fcombo3Y, -1.0f, true, true),
ConfigSetting("comboKeyScale3", &g_Config.fcomboScale3, defaultControlScale, true, true),
ConfigSetting("fcombo4X", &g_Config.fcombo4X, -1.0f, true, true),
ConfigSetting("fcombo4Y", &g_Config.fcombo4Y, -1.0f, true, true),
ConfigSetting("comboKeyScale4", &g_Config.fcomboScale4, defaultControlScale, true, true),
#ifdef _WIN32
ConfigSetting("DInputAnalogDeadzone", &g_Config.fDInputAnalogDeadzone, 0.1f, true, true),
ConfigSetting("DInputAnalogInverseMode", &g_Config.iDInputAnalogInverseMode, 0, true, true),
@@ -791,7 +810,7 @@ static ConfigSetting systemParamSettings[] = {
#endif
ConfigSetting("WlanPowerSave", &g_Config.bWlanPowerSave, (bool) PSP_SYSTEMPARAM_WLAN_POWERSAVE_OFF, true, true),
ReportedConfigSetting("EncryptSave", &g_Config.bEncryptSave, true, true, true),
ConfigSetting("SavedataUpgrade", &g_Config.bSavedataUpgrade, false, true, false),
ConfigSetting("SavedataUpgradeVersion", &g_Config.bSavedataUpgrade, true, true, false),
ConfigSetting(false),
};
@@ -1005,40 +1024,6 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
ResetControlLayout();
}
// MIGRATION: For users who had the old static touch layout, aren't I nice?
// We can probably kill this in 0.9.8 or something.
if (fDpadX > 1.0 || fDpadY > 1.0) { // Likely the rest are too!
float screen_width = dp_xres;
float screen_height = dp_yres;
fActionButtonCenterX /= screen_width;
fActionButtonCenterY /= screen_height;
fDpadX /= screen_width;
fDpadY /= screen_height;
fStartKeyX /= screen_width;
fStartKeyY /= screen_height;
fSelectKeyX /= screen_width;
fSelectKeyY /= screen_height;
fUnthrottleKeyX /= screen_width;
fUnthrottleKeyY /= screen_height;
fLKeyX /= screen_width;
fLKeyY /= screen_height;
fRKeyX /= screen_width;
fRKeyY /= screen_height;
fAnalogStickX /= screen_width;
fAnalogStickY /= screen_height;
fcombo0X /= screen_width;
fcombo0Y /= screen_height;
fcombo1X /= screen_width;
fcombo1Y /= screen_height;
fcombo2X /= screen_width;
fcombo2Y /= screen_height;
fcombo3X /= screen_width;
fcombo3Y /= screen_height;
fcombo4X /= screen_width;
fcombo4Y /= screen_height;
}
const char *gitVer = PPSSPP_GIT_VERSION;
Version installed(gitVer);
Version upgrade(upgradeVersion);
@@ -1189,13 +1174,13 @@ void Config::DownloadCompletedCallback(http::Download &download) {
}
JsonReader reader(data.c_str(), data.size());
const json_value *root = reader.root();
const JsonGet root = reader.root();
if (!root) {
ERROR_LOG(LOADER, "Failed to parse json");
return;
}
std::string version = root->getString("version", "");
std::string version = root.getString("version", "");
const char *gitVer = PPSSPP_GIT_VERSION;
Version installed(gitVer);
@@ -1436,47 +1421,28 @@ void Config::LoadStandardControllerIni() {
}
void Config::ResetControlLayout() {
g_Config.fActionButtonScale = defaultControlScale;
auto reset = [](ConfigTouchPos &pos) {
pos.x = defaultTouchPosShow.x;
pos.y = defaultTouchPosShow.y;
pos.scale = defaultTouchPosShow.scale;
};
reset(g_Config.touchActionButtonCenter);
g_Config.fActionButtonSpacing = 1.0f;
g_Config.fActionButtonCenterX = -1.0;
g_Config.fActionButtonCenterY = -1.0;
g_Config.fDpadScale = defaultControlScale;
reset(g_Config.touchDpad);
g_Config.fDpadSpacing = 1.0f;
g_Config.fDpadX = -1.0;
g_Config.fDpadY = -1.0;
g_Config.fStartKeyX = -1.0;
g_Config.fStartKeyY = -1.0;
g_Config.fStartKeyScale = defaultControlScale;
g_Config.fSelectKeyX = -1.0;
g_Config.fSelectKeyY = -1.0;
g_Config.fSelectKeyScale = defaultControlScale;
g_Config.fUnthrottleKeyX = -1.0;
g_Config.fUnthrottleKeyY = -1.0;
g_Config.fUnthrottleKeyScale = defaultControlScale;
g_Config.fLKeyX = -1.0;
g_Config.fLKeyY = -1.0;
g_Config.fLKeyScale = defaultControlScale;
g_Config.fRKeyX = -1.0;
g_Config.fRKeyY = -1.0;
g_Config.fRKeyScale = defaultControlScale;
g_Config.fAnalogStickX = -1.0;
g_Config.fAnalogStickY = -1.0;
g_Config.fAnalogStickScale = defaultControlScale;
g_Config.fcombo0X = -1.0;
g_Config.fcombo0Y = -1.0;
g_Config.fcomboScale0 = defaultControlScale;
g_Config.fcombo1X = -1.0f;
g_Config.fcombo1Y = -1.0f;
g_Config.fcomboScale1 = defaultControlScale;
g_Config.fcombo2X = -1.0f;
g_Config.fcombo2Y = -1.0f;
g_Config.fcomboScale2 = defaultControlScale;
g_Config.fcombo3X = -1.0f;
g_Config.fcombo3Y = -1.0f;
g_Config.fcomboScale3 = defaultControlScale;
g_Config.fcombo4X = -1.0f;
g_Config.fcombo4Y = -1.0f;
g_Config.fcomboScale4 = defaultControlScale;
reset(g_Config.touchStartKey);
reset(g_Config.touchSelectKey);
reset(g_Config.touchUnthrottleKey);
reset(g_Config.touchLKey);
reset(g_Config.touchRKey);
reset(g_Config.touchAnalogStick);
reset(g_Config.touchCombo0);
reset(g_Config.touchCombo1);
reset(g_Config.touchCombo2);
reset(g_Config.touchCombo3);
reset(g_Config.touchCombo4);
reset(g_Config.touchSpeed1Key);
reset(g_Config.touchSpeed2Key);
}
void Config::GetReportingInfo(UrlEncoder &data) {
@@ -1487,3 +1453,7 @@ void Config::GetReportingInfo(UrlEncoder &data) {
}
}
}
bool Config::IsPortrait() const {
return (iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180) && iRenderingMode != FB_NON_BUFFERED_MODE;
}
+39 -106
View File
@@ -25,53 +25,6 @@
extern const char *PPSSPP_GIT_VERSION;
const int MAX_CONFIG_VOLUME = 8;
const int PSP_MODEL_FAT = 0;
const int PSP_MODEL_SLIM = 1;
const int PSP_DEFAULT_FIRMWARE = 150;
static const s8 VOLUME_OFF = 0;
static const s8 VOLUME_MAX = 10;
enum class CPUCore {
INTERPRETER = 0,
JIT = 1,
IR_JIT = 2,
};
enum {
ROTATION_AUTO = 0,
ROTATION_LOCKED_HORIZONTAL = 1,
ROTATION_LOCKED_VERTICAL = 2,
ROTATION_LOCKED_HORIZONTAL180 = 3,
ROTATION_LOCKED_VERTICAL180 = 4,
};
enum BufferFilter {
SCALE_LINEAR = 1,
SCALE_NEAREST = 2,
};
// Software is not among these because it will have one of these perform the blit to display.
enum class GPUBackend {
OPENGL = 0,
DIRECT3D9 = 1,
DIRECT3D11 = 2,
VULKAN = 3,
};
enum AudioBackendType {
AUDIO_BACKEND_AUTO,
AUDIO_BACKEND_DSOUND,
AUDIO_BACKEND_WASAPI,
};
// For iIOTimingMethod.
enum IOTimingMethods {
IOTIMING_FAST = 0,
IOTIMING_HOST = 1,
IOTIMING_REALISTIC = 2,
};
namespace http {
class Download;
class Downloader;
@@ -79,6 +32,14 @@ namespace http {
struct UrlEncoder;
struct ConfigTouchPos {
float x;
float y;
float scale;
// Note: Show is not used for all settings.
bool show;
};
struct Config {
public:
Config();
@@ -108,10 +69,12 @@ public:
#if defined(USING_WIN_UI)
bool bPauseOnLostFocus;
bool bTopMost;
std::string sFont;
bool bIgnoreWindowsKey;
bool bRestartRequired;
#endif
#if defined(USING_WIN_UI) || defined(USING_QT_UI)
std::string sFont;
#endif
bool bPauseWhenMinimized;
@@ -131,6 +94,7 @@ public:
bool bForceLagSync;
bool bFuncReplacements;
bool bHideSlowWarnings;
bool bHideStateWarnings;
bool bPreloadFunctions;
bool bVulkanMultithreading;
@@ -147,6 +111,7 @@ public:
bool bRemoteISOManual;
bool bRemoteShareOnStartup;
std::string sRemoteISOSubdir;
bool bRemoteDebuggerOnStartup;
bool bMemStickInserted;
int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms.
@@ -159,6 +124,12 @@ public:
// GFX
int iGPUBackend;
// We have separate device parameters for each backend so it doesn't get erased if you switch backends.
// If not set, will use the "best" device.
std::string sVulkanDevice;
#ifdef _WIN32
std::string sD3D11Device;
#endif
bool bSoftwareRendering;
bool bHardwareTransform; // only used in the GLES backend
bool bSoftwareSkinning; // may speed up some games
@@ -201,19 +172,19 @@ public:
bool bTrueColor;
bool bReplaceTextures;
bool bSaveNewTextures;
int iTexScalingLevel; // 1 = off, 2 = 2x, ..., 5 = 5x
int iTexScalingLevel; // 0 = auto, 1 = off, 2 = 2x, ..., 5 = 5x
int iTexScalingType; // 0 = xBRZ, 1 = Hybrid
bool bRealtimeTexScaling;
bool bTexDeposterize;
int iFpsLimit;
int iFpsLimit1;
int iFpsLimit2;
bool bRefreshAt60Hz;
bool bUnToggleFpsLimit;
int iForceMaxEmulatedFPS;
int iMaxRecent;
int iCurrentStateSlot;
int iRewindFlipFrequency;
bool bEnableStateUndo;
bool bEnableAutoLoad;
int iAutoLoadSaveState; // 0 = off, 1 = oldest, 2 = newest, >2 = slot number + 3
bool bEnableCheats;
bool bReloadCheats;
int iCwCheatRefreshRate;
@@ -312,46 +283,27 @@ public:
//space between PSP buttons
//the PSP button's center (triangle, circle, square, cross)
float fActionButtonCenterX, fActionButtonCenterY;
float fActionButtonScale;
ConfigTouchPos touchActionButtonCenter;
float fActionButtonSpacing;
//radius of the D-pad (PSP cross)
// int iDpadRadius;
//the D-pad (PSP cross) position
float fDpadX, fDpadY;
float fDpadScale;
ConfigTouchPos touchDpad;
float fDpadSpacing;
//the start key position
float fStartKeyX, fStartKeyY;
float fStartKeyScale;
//the select key position;
float fSelectKeyX, fSelectKeyY;
float fSelectKeyScale;
ConfigTouchPos touchStartKey;
ConfigTouchPos touchSelectKey;
ConfigTouchPos touchUnthrottleKey;
ConfigTouchPos touchLKey;
ConfigTouchPos touchRKey;
ConfigTouchPos touchAnalogStick;
float fUnthrottleKeyX, fUnthrottleKeyY;
float fUnthrottleKeyScale;
float fLKeyX, fLKeyY;
float fLKeyScale;
float fRKeyX, fRKeyY;
float fRKeyScale;
//position of the analog stick
float fAnalogStickX, fAnalogStickY;
float fAnalogStickScale;
//the Combo Button position
float fcombo0X, fcombo0Y;
float fcomboScale0;
float fcombo1X, fcombo1Y;
float fcomboScale1;
float fcombo2X, fcombo2Y;
float fcomboScale2;
float fcombo3X, fcombo3Y;
float fcomboScale3;
float fcombo4X, fcombo4Y;
float fcomboScale4;
ConfigTouchPos touchCombo0;
ConfigTouchPos touchCombo1;
ConfigTouchPos touchCombo2;
ConfigTouchPos touchCombo3;
ConfigTouchPos touchCombo4;
ConfigTouchPos touchSpeed1Key;
ConfigTouchPos touchSpeed2Key;
// Controls Visibility
bool bShowTouchControls;
@@ -361,23 +313,6 @@ public:
bool bShowTouchTriangle;
bool bShowTouchSquare;
bool bShowTouchStart;
bool bShowTouchSelect;
bool bShowTouchUnthrottle;
bool bShowTouchLTrigger;
bool bShowTouchRTrigger;
bool bShowTouchAnalogStick;
bool bShowTouchDpad;
//Combo Button Visibility
bool bShowComboKey0;
bool bShowComboKey1;
bool bShowComboKey2;
bool bShowComboKey3;
bool bShowComboKey4;
// Combo_key mapping. These are bitfields.
int iCombokey0;
int iCombokey1;
@@ -512,9 +447,7 @@ public:
void GetReportingInfo(UrlEncoder &data);
bool IsPortrait() const {
return (iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180) && iRenderingMode != 0;
}
bool IsPortrait() const;
protected:
void LoadStandardControllerIni();
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <cstdint>
const int PSP_MODEL_FAT = 0;
const int PSP_MODEL_SLIM = 1;
const int PSP_DEFAULT_FIRMWARE = 660;
static const int8_t VOLUME_OFF = 0;
static const int8_t VOLUME_MAX = 10;
enum class CPUCore {
INTERPRETER = 0,
JIT = 1,
IR_JIT = 2,
};
enum {
ROTATION_AUTO = 0,
ROTATION_LOCKED_HORIZONTAL = 1,
ROTATION_LOCKED_VERTICAL = 2,
ROTATION_LOCKED_HORIZONTAL180 = 3,
ROTATION_LOCKED_VERTICAL180 = 4,
};
enum BufferFilter {
SCALE_LINEAR = 1,
SCALE_NEAREST = 2,
};
// Software is not among these because it will have one of these perform the blit to display.
enum class GPUBackend {
OPENGL = 0,
DIRECT3D9 = 1,
DIRECT3D11 = 2,
VULKAN = 3,
};
enum AudioBackendType {
AUDIO_BACKEND_AUTO,
AUDIO_BACKEND_DSOUND,
AUDIO_BACKEND_WASAPI,
};
// For iIOTimingMethod.
enum IOTimingMethods {
IOTIMING_FAST = 0,
IOTIMING_HOST = 1,
IOTIMING_REALISTIC = 2,
};
enum class SmallDisplayZoom {
STRETCH = 0,
PARTIAL_STRETCH = 1,
AUTO = 2,
MANUAL = 3,
};
enum class AutoLoadSaveState {
OFF = 0,
OLDEST = 1,
NEWEST = 2,
};
+86 -65
View File
@@ -28,22 +28,22 @@
#include "thread/threadutil.h"
#include "profiler/profiler.h"
#include "Common/GraphicsContext.h"
#include "Core/Core.h"
#include "Core/Config.h"
#include "Core/Host.h"
#include "Core/MemMap.h"
#include "Core/SaveState.h"
#include "Core/System.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/MIPS/MIPS.h"
#include "Common/GraphicsContext.h"
#include "GPU/Debugger/Stepping.h"
#ifdef _WIN32
#include "Common/CommonWindows.h"
#include "Windows/InputDevice.h"
#endif
#include "Host.h"
#include "Core/Debugger/Breakpoints.h"
// Time until we stop considering the core active without user input.
// Should this be configurable? 2 hours currently.
@@ -54,7 +54,9 @@ static std::mutex m_hStepMutex;
static std::condition_variable m_InactiveCond;
static std::mutex m_hInactiveMutex;
static bool singleStepPending = false;
static std::set<Core_ShutdownFunc> shutdownFuncs;
static int steppingCounter = 0;
static std::set<CoreLifecycleFunc> lifecycleFuncs;
static std::set<CoreStopRequestFunc> stopFuncs;
static bool windowHidden = false;
static double lastActivity = 0.0;
static double lastKeepAwake = 0.0;
@@ -75,30 +77,25 @@ void Core_NotifyActivity() {
lastActivity = time_now_d();
}
void Core_ListenShutdown(Core_ShutdownFunc func) {
shutdownFuncs.insert(func);
void Core_ListenLifecycle(CoreLifecycleFunc func) {
lifecycleFuncs.insert(func);
}
void Core_NotifyShutdown() {
for (auto it = shutdownFuncs.begin(); it != shutdownFuncs.end(); ++it) {
(*it)();
void Core_NotifyLifecycle(CoreLifecycle stage) {
for (auto func : lifecycleFuncs) {
func(stage);
}
}
void Core_ErrorPause() {
Core_UpdateState(CORE_ERROR);
}
void Core_Halt(const char *msg) {
Core_EnableStepping(true);
ERROR_LOG(CPU, "CPU HALTED : %s",msg);
_dbg_update_();
void Core_ListenStopRequest(CoreStopRequestFunc func) {
stopFuncs.insert(func);
}
void Core_Stop() {
Core_UpdateState(CORE_POWERDOWN);
Core_NotifyShutdown();
m_StepCond.notify_one();
for (auto func : stopFuncs) {
func();
}
}
bool Core_IsStepping() {
@@ -113,6 +110,14 @@ bool Core_IsInactive() {
return coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME && !coreStatePending;
}
static inline void Core_StateProcessed() {
if (coreStatePending) {
std::lock_guard<std::mutex> guard(m_hInactiveMutex);
coreStatePending = false;
m_InactiveCond.notify_all();
}
}
void Core_WaitInactive() {
while (Core_IsActive()) {
std::unique_lock<std::mutex> guard(m_hInactiveMutex);
@@ -184,6 +189,7 @@ bool UpdateScreenScale(int width, int height) {
return false;
}
// Note: not used on Android.
void UpdateRunLoop() {
if (windowHidden && g_Config.bPauseWhenMinimized) {
sleep_ms(16);
@@ -202,6 +208,8 @@ void KeepScreenAwake() {
void Core_RunLoop(GraphicsContext *ctx) {
graphicsContext = ctx;
while ((GetUIState() != UISTATE_INGAME || !PSP_IsInited()) && GetUIState() != UISTATE_EXIT) {
// In case it was pending, we're not in game anymore. We won't get to Core_Run().
Core_StateProcessed();
time_update();
double startTime = time_now_d();
UpdateRunLoop();
@@ -217,7 +225,7 @@ void Core_RunLoop(GraphicsContext *ctx) {
}
}
while (!coreState && GetUIState() == UISTATE_INGAME) {
while ((coreState == CORE_RUNNING || coreState == CORE_STEPPING) && GetUIState() == UISTATE_INGAME) {
time_update();
UpdateRunLoop();
if (!windowHidden && !Core_IsStepping()) {
@@ -238,22 +246,63 @@ void Core_RunLoop(GraphicsContext *ctx) {
}
void Core_DoSingleStep() {
std::lock_guard<std::mutex> guard(m_hStepMutex);
singleStepPending = true;
m_StepCond.notify_one();
m_StepCond.notify_all();
}
void Core_UpdateSingleStep() {
m_StepCond.notify_one();
std::lock_guard<std::mutex> guard(m_hStepMutex);
m_StepCond.notify_all();
}
void Core_SingleStep() {
currentMIPS->SingleStep();
if (coreState == CORE_STEPPING)
steppingCounter++;
}
static inline void CoreStateProcessed() {
if (coreStatePending) {
coreStatePending = false;
m_InactiveCond.notify_one();
static inline bool Core_WaitStepping() {
std::unique_lock<std::mutex> guard(m_hStepMutex);
// We only wait 16ms so that we can still draw UI or react to events.
if (!singleStepPending && coreState == CORE_STEPPING)
m_StepCond.wait_for(guard, std::chrono::milliseconds(16));
bool result = singleStepPending;
singleStepPending = false;
return result;
}
void Core_ProcessStepping() {
Core_StateProcessed();
// Check if there's any pending save state actions.
SaveState::Process();
if (coreState != CORE_STEPPING) {
return;
}
// Or any GPU actions.
GPUStepping::SingleStep();
// We're not inside jit now, so it's safe to clear the breakpoints.
static int lastSteppingCounter = -1;
if (lastSteppingCounter != steppingCounter) {
CBreakPoints::ClearTemporaryBreakPoints();
host->UpdateDisassembly();
host->UpdateMemView();
lastSteppingCounter = steppingCounter;
}
// Need to check inside the lock to avoid races.
bool doStep = Core_WaitStepping();
// We may still be stepping without singleStepPending to process a save state.
if (doStep && coreState == CORE_STEPPING) {
Core_SingleStep();
// Update disasm dialog.
host->UpdateDisassembly();
host->UpdateMemView();
}
}
@@ -262,9 +311,8 @@ static inline void CoreStateProcessed() {
void Core_Run(GraphicsContext *ctx) {
host->UpdateDisassembly();
while (true) {
reswitch:
if (GetUIState() != UISTATE_INGAME) {
CoreStateProcessed();
Core_StateProcessed();
if (GetUIState() == UISTATE_EXIT) {
UpdateRunLoop();
return;
@@ -275,51 +323,20 @@ reswitch:
switch (coreState) {
case CORE_RUNNING:
case CORE_STEPPING:
// enter a fast runloop
Core_RunLoop(ctx);
break;
// We should never get here on Android.
case CORE_STEPPING:
singleStepPending = false;
CoreStateProcessed();
// Check if there's any pending savestate actions.
SaveState::Process();
if (coreState == CORE_POWERDOWN) {
Core_StateProcessed();
return;
}
// wait for step command..
host->UpdateDisassembly();
host->UpdateMemView();
host->SendCoreWait(true);
{
std::unique_lock<std::mutex> guard(m_hStepMutex);
m_StepCond.wait(guard);
}
host->SendCoreWait(false);
// No step pending? Let's go back to the wait.
if (!singleStepPending || coreState != CORE_STEPPING) {
if (coreState == CORE_POWERDOWN) {
return;
}
goto reswitch;
}
Core_SingleStep();
// update disasm dialog
host->UpdateDisassembly();
host->UpdateMemView();
break;
case CORE_POWERUP:
case CORE_POWERDOWN:
case CORE_ERROR:
// Exit loop!!
CoreStateProcessed();
Core_StateProcessed();
return;
@@ -331,13 +348,17 @@ reswitch:
void Core_EnableStepping(bool step) {
if (step) {
sleep_ms(1);
host->SetDebugMode(true);
Core_UpdateState(CORE_STEPPING);
steppingCounter++;
} else {
host->SetDebugMode(false);
coreState = CORE_RUNNING;
coreStatePending = false;
m_StepCond.notify_one();
m_StepCond.notify_all();
}
}
int Core_GetSteppingCounter() {
return steppingCounter;
}
+25 -7
View File
@@ -27,26 +27,44 @@ void UpdateRunLoop();
void Core_Run(GraphicsContext *ctx);
void Core_Stop();
void Core_ErrorPause();
// For platforms that don't call Core_Run
void Core_SetGraphicsContext(GraphicsContext *ctx);
void Core_RunRenderThreadFrame();
// called from gui
void Core_EnableStepping(bool step);
void Core_DoSingleStep();
void Core_UpdateSingleStep();
void Core_ProcessStepping();
// Changes every time we enter stepping.
int Core_GetSteppingCounter();
typedef void (* Core_ShutdownFunc)();
void Core_ListenShutdown(Core_ShutdownFunc func);
void Core_NotifyShutdown();
void Core_Halt(const char *msg);
enum class CoreLifecycle {
STARTING,
// Note: includes failure cases. Guaranteed call after STARTING.
START_COMPLETE,
STOPPING,
// Guaranteed call after STOPPING.
STOPPED,
// Sometimes called for save states. Guaranteed sequence, and never during STARTING or STOPPING.
MEMORY_REINITING,
MEMORY_REINITED,
};
// Callback is called on the Emu thread.
typedef void (* CoreLifecycleFunc)(CoreLifecycle stage);
void Core_ListenLifecycle(CoreLifecycleFunc func);
void Core_NotifyLifecycle(CoreLifecycle stage);
// Callback is executed on requesting thread.
typedef void (* CoreStopRequestFunc)();
void Core_ListenStopRequest(CoreStopRequestFunc callback);
bool Core_IsStepping();
bool Core_IsActive();
bool Core_IsInactive();
// Warning: these currently work only on Windows.
void Core_WaitInactive();
void Core_WaitInactive(int milliseconds);
+28
View File
@@ -184,6 +184,18 @@
<ClCompile Include="..\ext\udis86\syn.c" />
<ClCompile Include="..\ext\udis86\udis86.c" />
<ClCompile Include="AVIDump.cpp" />
<ClCompile Include="Debugger\WebSocket.cpp" />
<ClCompile Include="Debugger\WebSocket\BreakpointSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\CPUCoreSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\GameBroadcaster.cpp" />
<ClCompile Include="Debugger\WebSocket\GameSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\LogBroadcaster.cpp" />
<ClCompile Include="Debugger\WebSocket\DisasmSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\SteppingBroadcaster.cpp" />
<ClCompile Include="Debugger\WebSocket\SteppingSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\WebSocketUtils.cpp" />
<ClCompile Include="FileSystems\BlobFileSystem.cpp" />
<ClCompile Include="HLE\KUBridge.cpp" />
<ClCompile Include="HLE\sceUsbCam.cpp" />
@@ -511,6 +523,7 @@
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AnySuitable</InlineFunctionExpansion>
</ClCompile>
<ClCompile Include="WaveFile.cpp" />
<ClCompile Include="WebServer.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ext\disarm.h" />
@@ -531,7 +544,21 @@
<ClInclude Include="..\ext\udis86\udint.h" />
<ClInclude Include="..\ext\udis86\udis86.h" />
<ClInclude Include="AVIDump.h" />
<ClInclude Include="ConfigValues.h" />
<ClInclude Include="Debugger\WebSocket.h" />
<ClInclude Include="Debugger\WebSocket\BreakpointSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\GameSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\DisasmSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h" />
<ClInclude Include="Debugger\WebSocket\SteppingSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\WebSocketUtils.h" />
<ClInclude Include="Debugger\WebSocket\CPUCoreSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\GameBroadcaster.h" />
<ClInclude Include="Debugger\WebSocket\LogBroadcaster.h" />
<ClInclude Include="Debugger\WebSocket\SteppingBroadcaster.h" />
<ClInclude Include="FileSystems\BlobFileSystem.h" />
<ClInclude Include="HLE\KernelThreadDebugInterface.h" />
<ClInclude Include="HLE\KUBridge.h" />
<ClInclude Include="HLE\sceUsbCam.h" />
<ClInclude Include="MIPS\IR\IRFrontend.h" />
@@ -746,6 +773,7 @@
<ClInclude Include="Util\ppge_atlas.h" />
<ClInclude Include="..\ext\xxhash.h" />
<ClInclude Include="WaveFile.h" />
<ClInclude Include="WebServer.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\android\jni\Android.mk" />
+87
View File
@@ -70,6 +70,9 @@
<Filter Include="MIPS\IR">
<UniqueIdentifier>{119ac973-e457-4025-9e1e-4fb34022ae23}</UniqueIdentifier>
</Filter>
<Filter Include="Debugger\WebSocket">
<UniqueIdentifier>{c21d9bb5-614d-451b-8c0b-3078b29122d8}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ELF\ElfReader.cpp">
@@ -689,6 +692,45 @@
<ClCompile Include="Replay.cpp">
<Filter>Core</Filter>
</ClCompile>
<ClCompile Include="WebServer.cpp">
<Filter>Core</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\LogBroadcaster.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\SteppingBroadcaster.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\GameBroadcaster.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\CPUCoreSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\WebSocketUtils.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\GameSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\DisasmSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\SteppingSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\BreakpointSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
@@ -1268,6 +1310,51 @@
<ClInclude Include="Replay.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="WebServer.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\LogBroadcaster.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\SteppingBroadcaster.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\GameBroadcaster.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\CPUCoreSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\WebSocketUtils.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\GameSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\DisasmSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\SteppingSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\BreakpointSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="HLE\KernelThreadDebugInterface.h">
<Filter>HLE\Kernel</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="ConfigValues.h">
<Filter>Core</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />
+17 -11
View File
@@ -31,6 +31,12 @@ enum GPUCore {
GPUCORE_VULKAN,
};
enum class FPSLimit {
NORMAL = 0,
CUSTOM1 = 1,
CUSTOM2 = 2,
};
class FileLoader;
class GraphicsContext;
@@ -42,13 +48,13 @@ enum class CPUCore;
// PSP_CoreParameter()
struct CoreParameter {
CoreParameter() : thin3d(nullptr), collectEmuLog(0), unthrottle(false), fpsLimit(0), updateRecent(true), freezeNext(false), frozen(false), mountIsoLoader(nullptr) {}
CoreParameter() {}
CPUCore cpuCore;
GPUCore gpuCore;
GraphicsContext *graphicsContext; // TODO: Find a better place.
Draw::DrawContext *thin3d;
GraphicsContext *graphicsContext = nullptr; // TODO: Find a better place.
Draw::DrawContext *thin3d = nullptr;
bool enableSound; // there aren't multiple sound cores.
std::string fileToStart;
@@ -56,9 +62,9 @@ struct CoreParameter {
std::string mountRoot; // If non-empty, and fileToStart is an ELF or PBP, mount this as host0: / umd0:.
std::string errorString;
bool startPaused;
bool startBreak;
bool printfEmuLog; // writes "emulator:" logging to stdout
std::string *collectEmuLog;
std::string *collectEmuLog = nullptr;
bool headLess; // Try to avoid messageboxes etc
// Internal PSP resolution
@@ -70,16 +76,16 @@ struct CoreParameter {
int pixelHeight;
// Can be modified at runtime.
bool unthrottle;
int fpsLimit;
bool unthrottle = false;
FPSLimit fpsLimit = FPSLimit::NORMAL;
bool updateRecent;
bool updateRecent = true;
// Freeze-frame. For nvidia perfhud profiling. Developers only.
bool freezeNext;
bool frozen;
bool freezeNext = false;
bool frozen = false;
FileLoader *mountIsoLoader;
FileLoader *mountIsoLoader = nullptr;
Compatibility compat;
};
+3 -1
View File
@@ -34,6 +34,7 @@
#include "Core/Reporting.h"
#include "Common/ChunkFile.h"
static const int initialHz = 222000000;
int CPU_HZ = 222000000;
// is this really necessary?
@@ -182,7 +183,7 @@ int RegisterEvent(const char *name, TimedCallback callback)
void AntiCrashCallback(u64 userdata, int cyclesLate)
{
ERROR_LOG(SAVESTATE, "Savestate broken: an unregistered event was called.");
Core_Halt("invalid timing events");
Core_EnableStepping(true);
}
void RestoreRegisterEvent(int event_type, const char *name, TimedCallback callback)
@@ -211,6 +212,7 @@ void Init()
lastGlobalTimeUs = 0;
hasTsEvents = 0;
mhzChangeCallbacks.clear();
CPU_HZ = initialHz;
}
void Shutdown()
+4 -4
View File
@@ -870,25 +870,25 @@ void CWCheatEngine::ExecuteOp(const CheatOperation &op, const CheatCode &cheat,
break;
case CheatOp::IfAddrEqual:
if (!TestIf(op, [](int a, int b) { return a == b; })) {
if (!TestIfAddr(op, [](int a, int b) { return a == b; })) {
i += (size_t)op.ifAddrTypes.skip;
}
break;
case CheatOp::IfAddrNotEqual:
if (!TestIf(op, [](int a, int b) { return a != b; })) {
if (!TestIfAddr(op, [](int a, int b) { return a != b; })) {
i += (size_t)op.ifAddrTypes.skip;
}
break;
case CheatOp::IfAddrLess:
if (!TestIf(op, [](int a, int b) { return a < b; })) {
if (!TestIfAddr(op, [](int a, int b) { return a < b; })) {
i += (size_t)op.ifAddrTypes.skip;
}
break;
case CheatOp::IfAddrGreater:
if (!TestIf(op, [](int a, int b) { return a > b; })) {
if (!TestIfAddr(op, [](int a, int b) { return a > b; })) {
i += (size_t)op.ifAddrTypes.skip;
}
break;
+153 -54
View File
@@ -16,6 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cstdio>
#include <mutex>
#include "Common/Log.h"
#include "Core/Core.h"
@@ -27,17 +28,14 @@
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/CoreTiming.h"
static std::mutex breakPointsMutex_;
std::vector<BreakPoint> CBreakPoints::breakPoints_;
u32 CBreakPoints::breakSkipFirstAt_ = 0;
u64 CBreakPoints::breakSkipFirstTicks_ = 0;
static std::mutex memCheckMutex_;
std::vector<MemCheck> CBreakPoints::memChecks_;
std::vector<MemCheck *> CBreakPoints::cleanupMemChecks_;
MemCheck::MemCheck()
{
numHits = 0;
}
void MemCheck::Log(u32 addr, bool write, int size, u32 pc) {
if (result & BREAK_ACTION_LOG) {
if (logFormat.empty()) {
@@ -50,16 +48,21 @@ void MemCheck::Log(u32 addr, bool write, int size, u32 pc) {
}
}
BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc)
{
BreakAction MemCheck::Apply(u32 addr, bool write, int size, u32 pc) {
int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ;
if (cond & mask)
{
if (cond & mask) {
++numHits;
return result;
}
return BREAK_ACTION_IGNORE;
}
BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) {
int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ;
if (cond & mask) {
Log(addr, write, size, pc);
if (result & BREAK_ACTION_PAUSE)
{
if (result & BREAK_ACTION_PAUSE) {
Core_EnableStepping(true);
host->SetDebugMode(true);
}
@@ -70,38 +73,46 @@ BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc)
return BREAK_ACTION_IGNORE;
}
void MemCheck::JitBefore(u32 addr, bool write, int size, u32 pc)
{
void MemCheck::JitBeforeApply(u32 addr, bool write, int size, u32 pc) {
int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE;
if (write && (cond & mask) == mask)
{
if (write && (cond & mask) == mask) {
lastAddr = addr;
lastPC = pc;
lastSize = size;
} else {
lastAddr = 0;
Apply(addr, write, size, pc);
}
}
void MemCheck::JitBeforeAction(u32 addr, bool write, int size, u32 pc) {
if (lastAddr) {
// We have to break to find out if it changed.
Core_EnableStepping(true);
}
else
{
lastAddr = 0;
} else {
Action(addr, write, size, pc);
}
}
void MemCheck::JitCleanup()
{
bool MemCheck::JitApplyChanged() {
if (lastAddr == 0 || lastPC == 0)
return;
return false;
// Here's the tricky part: would this have changed memory?
// Note that it did not actually get written.
bool changed = MIPSAnalyst::OpWouldChangeMemory(lastPC, lastAddr, lastSize);
if (changed)
{
++numHits;
return changed;
}
void MemCheck::JitCleanup(bool changed)
{
if (lastAddr == 0 || lastPC == 0)
return;
if (changed)
Log(lastAddr, true, lastSize, lastPC);
}
// Resume if it should not have gone to stepping, or if it did not change.
if ((!(result & BREAK_ACTION_PAUSE) || !changed) && coreState == CORE_STEPPING)
@@ -113,6 +124,7 @@ void MemCheck::JitCleanup()
host->SetDebugMode(true);
}
// Note: must lock while calling this.
size_t CBreakPoints::FindBreakpoint(u32 addr, bool matchTemp, bool temp)
{
size_t found = INVALID_BREAKPOINT;
@@ -145,12 +157,14 @@ size_t CBreakPoints::FindMemCheck(u32 start, u32 end)
bool CBreakPoints::IsAddressBreakPoint(u32 addr)
{
std::lock_guard<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
return bp != INVALID_BREAKPOINT && breakPoints_[bp].result != BREAK_ACTION_IGNORE;
}
bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled)
{
std::lock_guard<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp == INVALID_BREAKPOINT) return false;
if (enabled != nullptr)
@@ -160,12 +174,14 @@ bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled)
bool CBreakPoints::IsTempBreakPoint(u32 addr)
{
std::lock_guard<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr, true, true);
return bp != INVALID_BREAKPOINT;
}
bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size)
{
std::lock_guard<std::mutex> guard(breakPointsMutex_);
const u32 end = addr + size;
for (const auto &bp : breakPoints_)
{
@@ -178,6 +194,7 @@ bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size)
void CBreakPoints::AddBreakPoint(u32 addr, bool temp)
{
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr, true, temp);
if (bp == INVALID_BREAKPOINT)
{
@@ -187,18 +204,21 @@ void CBreakPoints::AddBreakPoint(u32 addr, bool temp)
pt.addr = addr;
breakPoints_.push_back(pt);
guard.unlock();
Update(addr);
}
else if (!breakPoints_[bp].IsEnabled())
{
breakPoints_[bp].result |= BREAK_ACTION_PAUSE;
breakPoints_[bp].hasCond = false;
guard.unlock();
Update(addr);
}
}
void CBreakPoints::RemoveBreakPoint(u32 addr)
{
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp != INVALID_BREAKPOINT)
{
@@ -209,12 +229,14 @@ void CBreakPoints::RemoveBreakPoint(u32 addr)
if (bp != INVALID_BREAKPOINT)
breakPoints_.erase(breakPoints_.begin() + bp);
guard.unlock();
Update(addr);
}
}
void CBreakPoints::ChangeBreakPoint(u32 addr, bool status)
{
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp != INVALID_BREAKPOINT)
{
@@ -222,31 +244,38 @@ void CBreakPoints::ChangeBreakPoint(u32 addr, bool status)
breakPoints_[bp].result |= BREAK_ACTION_PAUSE;
else
breakPoints_[bp].result = BreakAction(breakPoints_[bp].result & ~BREAK_ACTION_PAUSE);
guard.unlock();
Update(addr);
}
}
void CBreakPoints::ChangeBreakPoint(u32 addr, BreakAction result)
{
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp != INVALID_BREAKPOINT)
{
breakPoints_[bp].result = result;
guard.unlock();
Update(addr);
}
}
void CBreakPoints::ClearAllBreakPoints()
{
std::unique_lock<std::mutex> guard(breakPointsMutex_);
if (!breakPoints_.empty())
{
breakPoints_.clear();
guard.unlock();
Update();
}
}
void CBreakPoints::ClearTemporaryBreakPoints()
{
std::unique_lock<std::mutex> guard(breakPointsMutex_);
if (breakPoints_.empty())
return;
@@ -259,73 +288,85 @@ void CBreakPoints::ClearTemporaryBreakPoints()
update = true;
}
}
guard.unlock();
if (update)
Update();
}
void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond)
{
size_t bp = FindBreakpoint(addr, true, false);
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp != INVALID_BREAKPOINT)
{
breakPoints_[bp].hasCond = true;
breakPoints_[bp].cond = cond;
guard.unlock();
Update(addr);
}
}
void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr)
{
size_t bp = FindBreakpoint(addr, true, false);
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp != INVALID_BREAKPOINT)
{
breakPoints_[bp].hasCond = false;
guard.unlock();
Update(addr);
}
}
BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr)
{
size_t bp = FindBreakpoint(addr, true, false);
std::lock_guard<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr);
if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond)
return &breakPoints_[bp].cond;
return NULL;
}
void CBreakPoints::ChangeBreakPointLogFormat(u32 addr, const std::string &fmt) {
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr, true, false);
if (bp != INVALID_BREAKPOINT) {
breakPoints_[bp].logFormat = fmt;
guard.unlock();
Update(addr);
}
}
BreakAction CBreakPoints::ExecBreakPoint(u32 addr) {
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr, false);
if (bp != INVALID_BREAKPOINT) {
if (breakPoints_[bp].hasCond) {
BreakPoint info = breakPoints_[bp];
guard.unlock();
if (info.hasCond) {
// Evaluate the breakpoint and abort if necessary.
auto cond = CBreakPoints::GetBreakPointCondition(currentMIPS->pc);
if (cond && !cond->Evaluate())
return BREAK_ACTION_IGNORE;
}
if (breakPoints_[bp].result & BREAK_ACTION_LOG) {
if (breakPoints_[bp].logFormat.empty()) {
if (info.result & BREAK_ACTION_LOG) {
if (info.logFormat.empty()) {
NOTICE_LOG(JIT, "BKP PC=%08x (%s)", addr, g_symbolMap->GetDescription(addr).c_str());
} else {
std::string formatted;
CBreakPoints::EvaluateLogFormat(currentDebugMIPS, breakPoints_[bp].logFormat, formatted);
CBreakPoints::EvaluateLogFormat(currentDebugMIPS, info.logFormat, formatted);
NOTICE_LOG(JIT, "BKP PC=%08x: %s", addr, formatted.c_str());
}
}
if (breakPoints_[bp].result & BREAK_ACTION_PAUSE) {
if (info.result & BREAK_ACTION_PAUSE) {
Core_EnableStepping(true);
host->SetDebugMode(true);
}
return breakPoints_[bp].result;
return info.result;
}
return BREAK_ACTION_IGNORE;
@@ -333,6 +374,7 @@ BreakAction CBreakPoints::ExecBreakPoint(u32 addr) {
void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result)
{
std::unique_lock<std::mutex> guard(memCheckMutex_);
// This will ruin any pending memchecks.
cleanupMemChecks_.clear();
@@ -346,18 +388,21 @@ void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, Break
check.result = result;
memChecks_.push_back(check);
guard.unlock();
Update();
}
else
{
memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond);
memChecks_[mc].result = (BreakAction)(memChecks_[mc].result | result);
guard.unlock();
Update();
}
}
void CBreakPoints::RemoveMemCheck(u32 start, u32 end)
{
std::unique_lock<std::mutex> guard(memCheckMutex_);
// This will ruin any pending memchecks.
cleanupMemChecks_.clear();
@@ -365,49 +410,73 @@ void CBreakPoints::RemoveMemCheck(u32 start, u32 end)
if (mc != INVALID_MEMCHECK)
{
memChecks_.erase(memChecks_.begin() + mc);
guard.unlock();
Update();
}
}
void CBreakPoints::ChangeMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result)
{
std::unique_lock<std::mutex> guard(memCheckMutex_);
size_t mc = FindMemCheck(start, end);
if (mc != INVALID_MEMCHECK)
{
memChecks_[mc].cond = cond;
memChecks_[mc].result = result;
guard.unlock();
Update();
}
}
void CBreakPoints::ClearAllMemChecks()
{
std::unique_lock<std::mutex> guard(memCheckMutex_);
// This will ruin any pending memchecks.
cleanupMemChecks_.clear();
if (!memChecks_.empty())
{
memChecks_.clear();
guard.unlock();
Update();
}
}
void CBreakPoints::ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt) {
std::unique_lock<std::mutex> guard(memCheckMutex_);
size_t mc = FindMemCheck(start, end);
if (mc != INVALID_MEMCHECK) {
memChecks_[mc].logFormat = fmt;
guard.unlock();
Update();
}
}
bool CBreakPoints::GetMemCheck(u32 start, u32 end, MemCheck *check) {
std::lock_guard<std::mutex> guard(memCheckMutex_);
size_t mc = FindMemCheck(start, end);
if (mc != INVALID_MEMCHECK) {
*check = memChecks_[mc];
return true;
}
return false;
}
static inline u32 NotCached(u32 val)
{
// Remove the cached part of the address.
return val & ~0x40000000;
}
MemCheck *CBreakPoints::GetMemCheck(u32 address, int size)
{
bool CBreakPoints::GetMemCheckInRange(u32 address, int size, MemCheck *check) {
std::lock_guard<std::mutex> guard(memCheckMutex_);
auto result = GetMemCheckLocked(address, size);
if (result)
*check = *result;
return result != nullptr;
}
MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) {
std::vector<MemCheck>::iterator iter;
for (iter = memChecks_.begin(); iter != memChecks_.end(); ++iter)
{
@@ -430,9 +499,14 @@ MemCheck *CBreakPoints::GetMemCheck(u32 address, int size)
BreakAction CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc)
{
auto check = GetMemCheck(address, size);
if (check)
return check->Action(address, write, size, pc);
std::unique_lock<std::mutex> guard(memCheckMutex_);
auto check = GetMemCheckLocked(address, size);
if (check) {
check->Apply(address, write, size, pc);
auto copy = *check;
guard.unlock();
return copy.Action(address, write, size, pc);
}
return BREAK_ACTION_IGNORE;
}
@@ -448,15 +522,23 @@ BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc)
}
bool write = MIPSAnalyst::IsOpMemoryWrite(pc);
auto check = GetMemCheck(address, size);
std::unique_lock<std::mutex> guard(memCheckMutex_);
auto check = GetMemCheckLocked(address, size);
if (check) {
int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE;
bool apply = false;
if (write && (check->cond & mask) == mask) {
if (MIPSAnalyst::OpWouldChangeMemory(pc, address, size)) {
return check->Action(address, write, size, pc);
apply = true;
}
} else {
return check->Action(address, write, size, pc);
apply = true;
}
if (apply) {
check->Apply(address, write, size, pc);
auto copy = *check;
guard.unlock();
return copy.Action(address, write, size, pc);
}
}
return BREAK_ACTION_IGNORE;
@@ -464,18 +546,28 @@ BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc)
void CBreakPoints::ExecMemCheckJitBefore(u32 address, bool write, int size, u32 pc)
{
auto check = GetMemCheck(address, size);
std::unique_lock<std::mutex> guard(memCheckMutex_);
auto check = GetMemCheckLocked(address, size);
if (check) {
check->JitBefore(address, write, size, pc);
check->JitBeforeApply(address, write, size, pc);
auto copy = *check;
guard.unlock();
copy.JitBeforeAction(address, write, size, pc);
guard.lock();
cleanupMemChecks_.push_back(check);
}
}
void CBreakPoints::ExecMemCheckJitCleanup()
{
std::unique_lock<std::mutex> guard(memCheckMutex_);
for (auto it = cleanupMemChecks_.begin(), end = cleanupMemChecks_.end(); it != end; ++it) {
auto check = *it;
check->JitCleanup();
bool changed = check->JitApplyChanged();
auto copy = *check;
guard.unlock();
copy.JitCleanup(changed);
guard.lock();
}
cleanupMemChecks_.clear();
}
@@ -493,17 +585,21 @@ u32 CBreakPoints::CheckSkipFirst()
return 0;
}
const std::vector<MemCheck> CBreakPoints::GetMemCheckRanges()
{
const std::vector<MemCheck> CBreakPoints::GetMemCheckRanges(bool write) {
std::lock_guard<std::mutex> guard(memCheckMutex_);
std::vector<MemCheck> ranges = memChecks_;
for (auto it = memChecks_.begin(), end = memChecks_.end(); it != end; ++it)
{
MemCheck check = *it;
for (const auto &check : memChecks_) {
if (!(check.cond & MEMCHECK_READ) && !write)
continue;
if (!(check.cond & MEMCHECK_WRITE) && write)
continue;
MemCheck copy = check;
// Toggle the cached part of the address.
check.start ^= 0x40000000;
if (check.end != 0)
check.end ^= 0x40000000;
ranges.push_back(check);
copy.start ^= 0x40000000;
if (copy.end != 0)
copy.end ^= 0x40000000;
ranges.push_back(copy);
}
return ranges;
@@ -511,16 +607,19 @@ const std::vector<MemCheck> CBreakPoints::GetMemCheckRanges()
const std::vector<MemCheck> CBreakPoints::GetMemChecks()
{
std::lock_guard<std::mutex> guard(memCheckMutex_);
return memChecks_;
}
const std::vector<BreakPoint> CBreakPoints::GetBreakpoints()
{
std::lock_guard<std::mutex> guard(breakPointsMutex_);
return breakPoints_;
}
bool CBreakPoints::HasMemChecks()
{
std::lock_guard<std::mutex> guard(memCheckMutex_);
return !memChecks_.empty();
}
+29 -34
View File
@@ -21,8 +21,7 @@
#include "Core/Debugger/DebugInterface.h"
enum BreakAction
{
enum BreakAction {
BREAK_ACTION_IGNORE = 0x00,
BREAK_ACTION_LOG = 0x01,
BREAK_ACTION_PAUSE = 0x02,
@@ -37,35 +36,27 @@ static inline BreakAction operator | (const BreakAction &lhs, const BreakAction
return BreakAction((u32)lhs | (u32)rhs);
}
struct BreakPointCond
{
DebugInterface *debug;
struct BreakPointCond {
DebugInterface *debug = nullptr;
PostfixExpression expression;
std::string expressionString;
BreakPointCond() : debug(nullptr)
{
}
u32 Evaluate()
{
u32 Evaluate() {
u32 result;
if (debug->parseExpression(expression,result) == false) return 0;
if (debug->parseExpression(expression, result) == false)
return 0;
return result;
}
};
struct BreakPoint
{
BreakPoint() : hasCond(false) {}
struct BreakPoint {
u32 addr;
bool temporary;
BreakAction result;
BreakAction result = BREAK_ACTION_IGNORE;
std::string logFormat;
bool hasCond;
bool hasCond = false;
BreakPointCond cond;
bool IsEnabled() const {
@@ -80,8 +71,7 @@ struct BreakPoint
}
};
enum MemCheckCondition
{
enum MemCheckCondition {
MEMCHECK_READ = 0x01,
MEMCHECK_WRITE = 0x02,
MEMCHECK_WRITE_ONCHANGE = 0x04,
@@ -89,25 +79,28 @@ enum MemCheckCondition
MEMCHECK_READWRITE = 0x03,
};
struct MemCheck
{
MemCheck();
struct MemCheck {
u32 start;
u32 end;
MemCheckCondition cond;
BreakAction result;
MemCheckCondition cond = MEMCHECK_READ;
BreakAction result = BREAK_ACTION_IGNORE;
std::string logFormat;
u32 numHits;
u32 numHits = 0;
u32 lastPC;
u32 lastAddr;
int lastSize;
u32 lastPC = 0;
u32 lastAddr = 0;
int lastSize = 0;
// Called on the stored memcheck (affects numHits, etc.)
BreakAction Apply(u32 addr, bool write, int size, u32 pc);
// Called on a copy.
BreakAction Action(u32 addr, bool write, int size, u32 pc);
void JitBefore(u32 addr, bool write, int size, u32 pc);
void JitCleanup();
void JitBeforeApply(u32 addr, bool write, int size, u32 pc);
void JitBeforeAction(u32 addr, bool write, int size, u32 pc);
bool JitApplyChanged();
void JitCleanup(bool changed);
void Log(u32 addr, bool write, int size, u32 pc);
@@ -140,7 +133,7 @@ public:
static void ClearAllBreakPoints();
static void ClearTemporaryBreakPoints();
// Makes a copy. Temporary breakpoints can't have conditions.
// Makes a copy of the condition.
static void ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond);
static void ChangeBreakPointRemoveCond(u32 addr);
static BreakPointCond *GetBreakPointCondition(u32 addr);
@@ -156,7 +149,8 @@ public:
static void ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt);
static MemCheck *GetMemCheck(u32 address, int size);
static bool GetMemCheck(u32 start, u32 end, MemCheck *check);
static bool GetMemCheckInRange(u32 address, int size, MemCheck *check);
static BreakAction ExecMemCheck(u32 address, bool write, int size, u32 pc);
static BreakAction ExecOpMemCheck(u32 address, u32 pc);
@@ -168,7 +162,7 @@ public:
static u32 CheckSkipFirst();
// Includes uncached addresses.
static const std::vector<MemCheck> GetMemCheckRanges();
static const std::vector<MemCheck> GetMemCheckRanges(bool write);
static const std::vector<MemCheck> GetMemChecks();
static const std::vector<BreakPoint> GetBreakpoints();
@@ -184,6 +178,7 @@ private:
static size_t FindBreakpoint(u32 addr, bool matchTemp = false, bool temp = false);
// Finds exactly, not using a range check.
static size_t FindMemCheck(u32 start, u32 end);
static MemCheck *GetMemCheckLocked(u32 address, int size);
static std::vector<BreakPoint> breakPoints_;
static u32 breakSkipFirstAt_;
+94 -57
View File
@@ -31,6 +31,7 @@
#include "Core/Debugger/DisassemblyManager.h"
std::map<u32, DisassemblyEntry*> DisassemblyManager::entries;
std::recursive_mutex DisassemblyManager::entriesLock_;
DebugInterface* DisassemblyManager::cpu;
int DisassemblyManager::maxParamChars = 29;
@@ -149,7 +150,9 @@ void DisassemblyManager::analyze(u32 address, u32 size = 1024)
if (!PSP_IsInited())
return;
auto it = findDisassemblyEntry(entries,address,false);
auto memLock = Memory::Lock();
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
auto it = findDisassemblyEntry(entries, address, false);
if (it != entries.end())
{
DisassemblyEntry* entry = it->second;
@@ -219,7 +222,8 @@ void DisassemblyManager::analyze(u32 address, u32 size = 1024)
std::vector<BranchLine> DisassemblyManager::getBranchLines(u32 start, u32 size)
{
std::vector<BranchLine> result;
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
auto it = findDisassemblyEntry(entries,start,false);
if (it != entries.end())
{
@@ -233,40 +237,44 @@ std::vector<BranchLine> DisassemblyManager::getBranchLines(u32 start, u32 size)
return result;
}
void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest)
void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug)
{
// This is here really to avoid lock ordering issues.
auto memLock = Memory::Lock();
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
auto it = findDisassemblyEntry(entries,address,false);
if (it == entries.end())
{
analyze(address);
it = findDisassemblyEntry(entries,address,false);
if (it == entries.end())
{
if (address % 4)
dest.totalSize = ((address+3) & ~3)-address;
else
dest.totalSize = 4;
dest.name = "ERROR";
dest.params = "Disassembly failure";
return;
}
}
DisassemblyEntry* entry = it->second;
if (entry->disassemble(address,dest,insertSymbols))
return;
if (it != entries.end()) {
DisassemblyEntry *entry = it->second;
if (entry->disassemble(address, dest, insertSymbols, cpuDebug))
return;
}
dest.type = DISTYPE_OTHER;
memset(&dest.info, 0, sizeof(dest.info));
dest.info.opcodeAddress = address;
if (address % 4)
dest.totalSize = ((address+3) & ~3)-address;
else
dest.totalSize = 4;
dest.name = "ERROR";
dest.params = "Disassembly failure";
if (Memory::IsValidRange(address, 4)) {
dest.name = "ERROR";
dest.params = "Disassembly failure";
} else {
dest.name = "-";
dest.params = "";
}
}
u32 DisassemblyManager::getStartAddress(u32 address)
{
auto memLock = Memory::Lock();
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
auto it = findDisassemblyEntry(entries,address,false);
if (it == entries.end())
{
@@ -283,6 +291,8 @@ u32 DisassemblyManager::getStartAddress(u32 address)
u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n)
{
auto memLock = Memory::Lock();
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
while (Memory::IsValidAddress(address))
{
auto it = findDisassemblyEntry(entries,address,false);
@@ -311,6 +321,8 @@ u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n)
u32 DisassemblyManager::getNthNextAddress(u32 address, int n)
{
auto memLock = Memory::Lock();
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
while (Memory::IsValidAddress(address))
{
auto it = findDisassemblyEntry(entries,address,false);
@@ -340,11 +352,12 @@ u32 DisassemblyManager::getNthNextAddress(u32 address, int n)
}
DisassemblyManager::~DisassemblyManager() {
clear();
}
void DisassemblyManager::clear()
{
auto memLock = Memory::Lock();
std::lock_guard<std::recursive_mutex> guard(entriesLock_);
for (auto it = entries.begin(); it != entries.end(); it++)
{
delete it->second;
@@ -383,11 +396,13 @@ void DisassemblyFunction::recheck()
int DisassemblyFunction::getNumLines()
{
std::lock_guard<std::recursive_mutex> guard(lock_);
return (int) lineAddresses.size();
}
int DisassemblyFunction::getLineNum(u32 address, bool findStart)
{
std::lock_guard<std::recursive_mutex> guard(lock_);
if (findStart)
{
int last = (int)lineAddresses.size() - 1;
@@ -418,22 +433,25 @@ int DisassemblyFunction::getLineNum(u32 address, bool findStart)
u32 DisassemblyFunction::getLineAddress(int line)
{
std::lock_guard<std::recursive_mutex> guard(lock_);
return lineAddresses[line];
}
bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols)
bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
std::lock_guard<std::recursive_mutex> guard(lock_);
auto it = findDisassemblyEntry(entries,address,false);
if (it == entries.end())
return false;
return it->second->disassemble(address,dest,insertSymbols);
return it->second->disassemble(address, dest, insertSymbols, cpuDebug);
}
void DisassemblyFunction::getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest)
{
u32 end = start+size;
std::lock_guard<std::recursive_mutex> guard(lock_);
for (size_t i = 0; i < lines.size(); i++)
{
BranchLine& line = lines[i];
@@ -466,17 +484,19 @@ void DisassemblyFunction::generateBranchLines()
u32 end = address+size;
std::lock_guard<std::recursive_mutex> guard(lock_);
DebugInterface* cpu = DisassemblyManager::getCpu();
for (u32 funcPos = address; funcPos < end; funcPos += 4)
{
MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos);
bool inFunction = (opInfo.branchTarget >= address && opInfo.branchTarget < end);
if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction)
{
if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) {
if (!Memory::IsValidAddress(opInfo.branchTarget))
continue;
BranchLine line;
if (opInfo.branchTarget < funcPos)
{
if (opInfo.branchTarget < funcPos) {
line.first = opInfo.branchTarget;
line.second = funcPos;
line.type = LINE_UP;
@@ -511,7 +531,8 @@ void DisassemblyFunction::generateBranchLines()
if (lane == -1)
{
// error
// Let's just pile on.
lines[i].laneIndex = 15;
continue;
}
@@ -524,6 +545,7 @@ void DisassemblyFunction::generateBranchLines()
void DisassemblyFunction::addOpcodeSequence(u32 start, u32 end)
{
DisassemblyOpcode* opcode = new DisassemblyOpcode(start,(end-start)/4);
std::lock_guard<std::recursive_mutex> guard(lock_);
entries[start] = opcode;
for (u32 pos = start; pos < end; pos += 4)
{
@@ -537,18 +559,21 @@ void DisassemblyFunction::load()
// gather all branch targets
std::set<u32> branchTargets;
for (size_t i = 0; i < lines.size(); i++)
{
switch (lines[i].type)
std::lock_guard<std::recursive_mutex> guard(lock_);
for (size_t i = 0; i < lines.size(); i++)
{
case LINE_DOWN:
branchTargets.insert(lines[i].second);
break;
case LINE_UP:
branchTargets.insert(lines[i].first);
break;
default:
break;
switch (lines[i].type)
{
case LINE_DOWN:
branchTargets.insert(lines[i].second);
break;
case LINE_UP:
branchTargets.insert(lines[i].first);
break;
default:
break;
}
}
}
@@ -566,6 +591,7 @@ void DisassemblyFunction::load()
addOpcodeSequence(opcodeSequenceStart,funcPos);
DisassemblyData* data = new DisassemblyData(funcPos,g_symbolMap->GetDataSize(funcPos),g_symbolMap->GetDataType(funcPos));
std::lock_guard<std::recursive_mutex> guard(lock_);
entries[funcPos] = data;
lineAddresses.push_back(funcPos);
funcPos += data->getTotalSize();
@@ -581,6 +607,7 @@ void DisassemblyFunction::load()
u32 nextPos = (funcPos+3) & ~3;
DisassemblyComment* comment = new DisassemblyComment(funcPos,nextPos-funcPos,".align","4");
std::lock_guard<std::recursive_mutex> guard(lock_);
entries[funcPos] = comment;
lineAddresses.push_back(funcPos);
@@ -665,6 +692,7 @@ void DisassemblyFunction::load()
if (opcodeSequenceStart != opAddress)
addOpcodeSequence(opcodeSequenceStart,opAddress);
std::lock_guard<std::recursive_mutex> guard(lock_);
entries[opAddress] = macro;
for (int i = 0; i < macro->getNumLines(); i++)
{
@@ -686,6 +714,7 @@ void DisassemblyFunction::load()
void DisassemblyFunction::clear()
{
std::lock_guard<std::recursive_mutex> guard(lock_);
for (auto it = entries.begin(); it != entries.end(); it++)
{
delete it->second;
@@ -697,16 +726,19 @@ void DisassemblyFunction::clear()
hash = 0;
}
bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols)
bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
if (!cpuDebug)
cpuDebug = DisassemblyManager::getCpu();
char opcode[64],arguments[256];
const char *dizz = DisassemblyManager::getCpu()->disasm(address,4);
const char *dizz = cpuDebug->disasm(address, 4);
parseDisasm(dizz,opcode,arguments,insertSymbols);
dest.type = DISTYPE_OPCODE;
dest.name = opcode;
dest.params = arguments;
dest.totalSize = 4;
dest.info = MIPSAnalyst::GetOpcodeInfo(DisassemblyManager::getCpu(),address);
dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address);
return true;
}
@@ -725,13 +757,14 @@ void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vector<BranchLi
for (u32 pos = start; pos < start+size; pos += 4)
{
MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(DisassemblyManager::getCpu(),pos);
if (info.isBranch && !info.isBranchToRegister && !info.isLinkedBranch)
{
if (info.isBranch && !info.isBranchToRegister && !info.isLinkedBranch) {
if (!Memory::IsValidAddress(info.branchTarget))
continue;
BranchLine line;
line.laneIndex = lane++;
if (info.branchTarget < pos)
{
if (info.branchTarget < pos) {
line.first = info.branchTarget;
line.second = pos;
line.type = LINE_UP;
@@ -766,11 +799,14 @@ void DisassemblyMacro::setMacroMemory(std::string _name, u32 _immediate, u8 _rt,
numOpcodes = 2;
}
bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols)
bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
if (!cpuDebug)
cpuDebug = DisassemblyManager::getCpu();
char buffer[64];
dest.type = DISTYPE_MACRO;
dest.info = MIPSAnalyst::GetOpcodeInfo(DisassemblyManager::getCpu(),address);
dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address);
std::string addressSymbol;
switch (type)
@@ -779,11 +815,10 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool
dest.name = name;
addressSymbol = g_symbolMap->GetLabelString(immediate);
if (!addressSymbol.empty() && insertSymbols)
{
sprintf(buffer,"%s,%s",DisassemblyManager::getCpu()->GetRegName(0,rt),addressSymbol.c_str());
if (!addressSymbol.empty() && insertSymbols) {
sprintf(buffer, "%s,%s", cpuDebug->GetRegName(0, rt), addressSymbol.c_str());
} else {
sprintf(buffer,"%s,0x%08X",DisassemblyManager::getCpu()->GetRegName(0,rt),immediate);
sprintf(buffer, "%s,0x%08X", cpuDebug->GetRegName(0, rt), immediate);
}
dest.params = buffer;
@@ -795,11 +830,10 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool
dest.name = name;
addressSymbol = g_symbolMap->GetLabelString(immediate);
if (!addressSymbol.empty() && insertSymbols)
{
sprintf(buffer,"%s,%s",DisassemblyManager::getCpu()->GetRegName(0,rt),addressSymbol.c_str());
if (!addressSymbol.empty() && insertSymbols) {
sprintf(buffer, "%s,%s", cpuDebug->GetRegName(0, rt), addressSymbol.c_str());
} else {
sprintf(buffer,"%s,0x%08X",DisassemblyManager::getCpu()->GetRegName(0,rt),immediate);
sprintf(buffer, "%s,0x%08X", cpuDebug->GetRegName(0, rt), immediate);
}
dest.params = buffer;
@@ -844,7 +878,7 @@ void DisassemblyData::recheck()
}
}
bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols)
bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
dest.type = DISTYPE_DATA;
@@ -866,6 +900,7 @@ bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool i
return false;
}
std::lock_guard<std::recursive_mutex> guard(lock_);
auto it = lines.find(address);
if (it == lines.end())
return false;
@@ -877,6 +912,7 @@ bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool i
int DisassemblyData::getLineNum(u32 address, bool findStart)
{
std::lock_guard<std::recursive_mutex> guard(lock_);
auto it = lines.upper_bound(address);
if (it != lines.end())
{
@@ -891,6 +927,7 @@ int DisassemblyData::getLineNum(u32 address, bool findStart)
void DisassemblyData::createLines()
{
std::lock_guard<std::recursive_mutex> guard(lock_);
lines.clear();
lineAddresses.clear();
@@ -1039,7 +1076,7 @@ DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string _nam
}
bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols)
bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug)
{
dest.type = DISTYPE_OTHER;
dest.name = name;
+11 -7
View File
@@ -17,6 +17,7 @@
#pragma once
#include <mutex>
#include "Common/CommonTypes.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MIPS/MIPSAnalyst.h"
@@ -62,7 +63,7 @@ public:
virtual int getLineNum(u32 address, bool findStart) = 0;
virtual u32 getLineAddress(int line) = 0;
virtual u32 getTotalSize() = 0;
virtual bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) = 0;
virtual bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) = 0;
virtual void getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest) { };
};
@@ -76,7 +77,7 @@ public:
int getLineNum(u32 address, bool findStart) override;
u32 getLineAddress(int line) override;
u32 getTotalSize() override { return size; };
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override;
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override;
void getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest) override;
private:
@@ -91,6 +92,7 @@ private:
std::vector<BranchLine> lines;
std::map<u32,DisassemblyEntry*> entries;
std::vector<u32> lineAddresses;
std::recursive_mutex lock_;
};
class DisassemblyOpcode: public DisassemblyEntry
@@ -103,7 +105,7 @@ public:
int getLineNum(u32 address, bool findStart) override { return (address - this->address) / 4; };
u32 getLineAddress(int line) override { return address + line * 4; };
u32 getTotalSize() override { return num * 4; };
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override;
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override;
void getBranchLines(u32 start, u32 size, std::vector<BranchLine>& dest) override;
private:
@@ -126,7 +128,7 @@ public:
int getLineNum(u32 address, bool findStart) override { return 0; };
u32 getLineAddress(int line) override { return address; };
u32 getTotalSize() override { return numOpcodes * 4; };
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override;
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override;
private:
enum MacroType { MACRO_LI, MACRO_MEMORYIMM };
@@ -151,7 +153,7 @@ public:
int getLineNum(u32 address, bool findStart) override;
u32 getLineAddress(int line) override { return lineAddresses[line]; };
u32 getTotalSize() override { return size; };
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override;
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override;
private:
void createLines();
@@ -169,6 +171,7 @@ private:
DataType type;
std::map<u32,DataEntry> lines;
std::vector<u32> lineAddresses;
std::recursive_mutex lock_;
};
class DisassemblyComment: public DisassemblyEntry
@@ -182,7 +185,7 @@ public:
int getLineNum(u32 address, bool findStart) override { return 0; };
u32 getLineAddress(int line) override { return address; };
u32 getTotalSize() override { return size; };
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override;
bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override;
private:
u32 address;
@@ -202,7 +205,7 @@ public:
void setCpu(DebugInterface* _cpu) { cpu = _cpu; };
void setMaxParamChars(int num) { maxParamChars = num; clear(); };
void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest);
void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug = nullptr);
void analyze(u32 address, u32 size);
std::vector<BranchLine> getBranchLines(u32 start, u32 size);
@@ -214,6 +217,7 @@ public:
static int getMaxParamChars() { return maxParamChars; };
private:
static std::map<u32,DisassemblyEntry*> entries;
static std::recursive_mutex entriesLock_;
static DebugInterface* cpu;
static int maxParamChars;
};
+1
View File
@@ -29,6 +29,7 @@
#endif
#include <algorithm>
#include <memory>
#include "util/text/utf8.h"
#include "zlib.h"
+211
View File
@@ -0,0 +1,211 @@
// Copyright (c) 2017- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <mutex>
#include <condition_variable>
#include "thread/threadutil.h"
#include "Core/Debugger/WebSocket.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/MemMap.h"
// This WebSocket (connected through the same port as disc sharing) allows API/debugger access to PPSSPP.
// Currently, the only subprotocol "debugger.ppsspp.org" uses a simple JSON based interface.
//
// Messages to and from PPSSPP follow the same basic format:
// { "event": "NAME", ... }
//
// And are primarily of these types:
// * Events from the debugger/client (you) to PPSSPP
// If there's a response, it will generally use the same name. It may not be immedate - it's an event.
// * Spontaneous events from PPSSPP
// Things like logs, breakpoint hits, etc. not directly requested.
//
// Otherwise you may see error events which indicate PPSSPP couldn't understand or failed internally:
// - "event": "error"
// - "message": A string describing what happened.
// - "level": Integer severity level. (1 = NOTICE, 2 = ERROR, 3 = WARN, 4 = INFO, 5 = DEBUG, 6 = VERBOSE)
// - "ticket": Optional, present if in response to an event with a "ticket" field, simply repeats that value.
//
// At start, please send a "version" event. See WebSocket/GameSubscriber.cpp for more details.
//
// For other events, look inside Core/Debugger/WebSocket/ for details on each event.
#include "Core/Debugger/WebSocket/GameBroadcaster.h"
#include "Core/Debugger/WebSocket/LogBroadcaster.h"
#include "Core/Debugger/WebSocket/SteppingBroadcaster.h"
#include "Core/Debugger/WebSocket/BreakpointSubscriber.h"
#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
#include "Core/Debugger/WebSocket/GameSubscriber.h"
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
#include "Core/Debugger/WebSocket/HLESubscriber.h"
#include "Core/Debugger/WebSocket/SteppingSubscriber.h"
typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map);
typedef void (*Subscribershutdown)(void *p);
struct SubscriberInfo {
SubscriberInit init;
Subscribershutdown shutdown;
};
static const std::vector<SubscriberInfo> subscribers({
{ &WebSocketBreakpointInit, nullptr },
{ &WebSocketCPUCoreInit, nullptr },
{ &WebSocketDisasmInit, &WebSocketDisasmShutdown },
{ &WebSocketGameInit, nullptr },
{ &WebSocketGPUBufferInit, nullptr },
{ &WebSocketHLEInit, nullptr },
{ &WebSocketSteppingInit, &WebSocketSteppingShutdown },
});
// To handle webserver restart, keep track of how many running.
static volatile int debuggersConnected = 0;
static volatile bool stopRequested = false;
static std::mutex stopLock;
static std::condition_variable stopCond;
// Prevent threading surprises and obscure crashes by locking startup/shutdown.
static bool lifecycleLockSetup = false;
static std::mutex lifecycleLock;
static void UpdateConnected(int delta) {
std::lock_guard<std::mutex> guard(stopLock);
debuggersConnected += delta;
stopCond.notify_all();
}
static void WebSocketNotifyLifecycle(CoreLifecycle stage) {
switch (stage) {
case CoreLifecycle::STARTING:
case CoreLifecycle::STOPPING:
case CoreLifecycle::MEMORY_REINITING:
if (debuggersConnected > 0) {
DEBUG_LOG(SYSTEM, "Waiting for debugger to complete on shutdown");
}
lifecycleLock.lock();
break;
case CoreLifecycle::START_COMPLETE:
case CoreLifecycle::STOPPED:
case CoreLifecycle::MEMORY_REINITED:
lifecycleLock.unlock();
if (debuggersConnected > 0) {
DEBUG_LOG(SYSTEM, "Debugger ready for shutdown");
}
break;
}
}
static void SetupDebuggerLock() {
if (!lifecycleLockSetup) {
Core_ListenLifecycle(&WebSocketNotifyLifecycle);
lifecycleLockSetup = true;
}
}
void HandleDebuggerRequest(const http::Request &request) {
net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org");
if (!ws)
return;
setCurrentThreadName("Debugger");
UpdateConnected(1);
SetupDebuggerLock();
LogBroadcaster logger;
GameBroadcaster game;
SteppingBroadcaster stepping;
std::unordered_map<std::string, DebuggerEventHandler> eventHandlers;
std::vector<void *> subscriberData;
for (auto info : subscribers) {
std::lock_guard<std::mutex> guard(lifecycleLock);
subscriberData.push_back(info.init(eventHandlers));
}
// There's a tradeoff between responsiveness to incoming events, and polling for changes.
int highActivity = 0;
ws->SetTextHandler([&](const std::string &t) {
JsonReader reader(t.c_str(), t.size());
if (!reader.ok()) {
ws->Send(DebuggerErrorEvent("Bad message: invalid JSON", LogTypes::LERROR));
return;
}
const JsonGet root = reader.root();
const char *event = root ? root.getString("event", nullptr) : nullptr;
if (!event) {
ws->Send(DebuggerErrorEvent("Bad message: no event property", LogTypes::LERROR, root));
return;
}
DebuggerRequest req(event, ws, root);
auto eventFunc = eventHandlers.find(event);
if (eventFunc != eventHandlers.end()) {
std::lock_guard<std::mutex> guard(lifecycleLock);
eventFunc->second(req);
if (!req.Finish()) {
// Poll more frequently for a second in case this triggers something.
highActivity = 1000;
}
} else {
req.Fail("Bad message: unknown event");
}
});
ws->SetBinaryHandler([&](const std::vector<uint8_t> &d) {
ws->Send(DebuggerErrorEvent("Bad message", LogTypes::LERROR));
});
while (ws->Process(highActivity ? 1.0f / 1000.0f : 1.0f / 60.0f)) {
std::lock_guard<std::mutex> guard(lifecycleLock);
// These send events that aren't just responses to requests.
logger.Broadcast(ws);
game.Broadcast(ws);
stepping.Broadcast(ws);
if (stopRequested) {
ws->Close(net::WebSocketClose::GOING_AWAY);
}
if (highActivity > 0) {
highActivity--;
}
}
std::lock_guard<std::mutex> guard(lifecycleLock);
for (size_t i = 0; i < subscribers.size(); ++i) {
if (subscribers[i].shutdown) {
subscribers[i].shutdown(subscriberData[i]);
} else {
assert(!subscriberData[i]);
}
}
delete ws;
UpdateConnected(-1);
}
void StopAllDebuggers() {
std::unique_lock<std::mutex> guard(stopLock);
while (debuggersConnected != 0) {
stopRequested = true;
stopCond.wait(guard);
}
// Reset it back for next time.
stopRequested = false;
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright (c) 2017- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
namespace http {
class Request;
}
void HandleDebuggerRequest(const http::Request &request);
// Note: blocks.
void StopAllDebuggers();
@@ -0,0 +1,446 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/StringUtils.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/Debugger/WebSocket/BreakpointSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/MIPS/MIPSDebugInterface.h"
void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map) {
// No need to bind or alloc state, these are all global.
map["cpu.breakpoint.add"] = &WebSocketCPUBreakpointAdd;
map["cpu.breakpoint.update"] = &WebSocketCPUBreakpointUpdate;
map["cpu.breakpoint.remove"] = &WebSocketCPUBreakpointRemove;
map["cpu.breakpoint.list"] = &WebSocketCPUBreakpointList;
map["memory.breakpoint.add"] = &WebSocketMemoryBreakpointAdd;
map["memory.breakpoint.update"] = &WebSocketMemoryBreakpointUpdate;
map["memory.breakpoint.remove"] = &WebSocketMemoryBreakpointRemove;
map["memory.breakpoint.list"] = &WebSocketMemoryBreakpointList;
return nullptr;
}
struct WebSocketCPUBreakpointParams {
uint32_t address = 0;
bool hasEnabled = false;
bool hasLog = false;
bool hasCondition = false;
bool hasLogFormat = false;
bool enabled;
bool log;
std::string condition;
PostfixExpression compiledCondition;
std::string logFormat;
bool Parse(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
req.Fail("CPU not started");
return false;
}
if (!req.ParamU32("address", &address))
return false;
hasEnabled = req.HasParam("enabled");
if (hasEnabled) {
if (!req.ParamBool("enabled", &enabled))
return false;
}
hasLog = req.HasParam("log");
if (hasLog) {
if (!req.ParamBool("log", &log))
return false;
}
hasCondition = req.HasParam("condition");
if (hasCondition) {
if (!req.ParamString("condition", &condition))
return false;
if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) {
req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
return false;
}
}
hasLogFormat = req.HasParam("logFormat");
if (hasLogFormat) {
if (!req.ParamString("logFormat", &logFormat))
return false;
}
return true;
}
void Apply() {
if (hasCondition && !condition.empty()) {
BreakPointCond cond;
cond.debug = currentDebugMIPS;
cond.expressionString = condition;
cond.expression = compiledCondition;
CBreakPoints::ChangeBreakPointAddCond(address, cond);
} else if (hasCondition && condition.empty()) {
CBreakPoints::ChangeBreakPointRemoveCond(address);
}
if (hasLogFormat) {
CBreakPoints::ChangeBreakPointLogFormat(address, logFormat);
}
// TODO: Fix this interface.
if (hasLog && !hasEnabled) {
CBreakPoints::IsAddressBreakPoint(address, &enabled);
hasEnabled = true;
}
if (hasLog && hasEnabled) {
BreakAction result = BREAK_ACTION_IGNORE;
if (log)
result |= BREAK_ACTION_LOG;
if (enabled)
result |= BREAK_ACTION_PAUSE;
CBreakPoints::ChangeBreakPoint(address, result);
} else if (hasEnabled) {
CBreakPoints::ChangeBreakPoint(address, enabled);
}
}
};
// Add a new CPU instruction breakpoint (cpu.breakpoint.add)
//
// Parameters:
// - address: unsigned integer address of instruction to break at.
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
// - log: optional boolean, whether to log when this breakpoint trips.
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
//
// Response (same event name) with no extra data.
//
// Note: will replace any breakpoint at the same address.
void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
WebSocketCPUBreakpointParams params;
if (!params.Parse(req))
return;
CBreakPoints::AddBreakPoint(params.address);
params.Apply();
req.Respond();
}
// Update a CPU instruction breakpoint (cpu.breakpoint.update)
//
// Parameters:
// - address: unsigned integer address of instruction to break at.
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
// - log: optional boolean, whether to log when this breakpoint trips.
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
//
// Response (same event name) with no extra data.
void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
WebSocketCPUBreakpointParams params;
if (!params.Parse(req))
return;
bool enabled;
if (!CBreakPoints::IsAddressBreakPoint(params.address, &enabled))
return req.Fail("Breakpoint not found");
params.Apply();
req.Respond();
}
// Remove a CPU instruction breakpoint (cpu.breakpoint.remove)
//
// Parameters:
// - address: unsigned integer address of instruction to break at.
//
// Response (same event name) with no extra data.
void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
uint32_t address;
if (!req.ParamU32("address", &address))
return;
CBreakPoints::RemoveBreakPoint(address);
req.Respond();
}
// List all CPU instruction breakpoints (cpu.breakpoint.list)
//
// No parameters.
//
// Response (same event name):
// - breakpoints: array of objects, each with properties:
// - address: unsigned integer address of instruction to break at.
// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.
// - log: optional boolean, whether to log when this breakpoint trips.
// - condition: null, or string expression to evaluate - breakpoint does not trip if false.
// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.
// - symbol: null, or string label or symbol at breakpoint address.
// - code: string disassembly of breakpoint address.
void WebSocketCPUBreakpointList(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
JsonWriter &json = req.Respond();
json.pushArray("breakpoints");
auto bps = CBreakPoints::GetBreakpoints();
for (const auto &bp : bps) {
if (bp.temporary)
continue;
json.pushDict();
json.writeUint("address", bp.addr);
json.writeBool("enabled", bp.IsEnabled());
json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);
if (bp.hasCond)
json.writeString("condition", bp.cond.expressionString);
else
json.writeNull("condition");
if (!bp.logFormat.empty())
json.writeString("logFormat", bp.logFormat);
else
json.writeNull("logFormat");
std::string symbol = g_symbolMap->GetLabelString(bp.addr);
if (symbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", symbol);
DisassemblyManager manager;
DisassemblyLineInfo line;
manager.getLine(manager.getStartAddress(bp.addr), true, line);
json.writeString("code", line.name + " " + line.params);
json.pop();
}
json.pop();
}
struct WebSocketMemoryBreakpointParams {
uint32_t address = 0;
uint32_t end = 0;
bool hasEnabled = false;
bool hasLog = false;
bool hasCond = false;
bool hasLogFormat = false;
bool enabled = true;
bool log = true;
MemCheckCondition cond = MEMCHECK_READWRITE;
std::string logFormat;
bool Parse(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
req.Fail("CPU not started");
return false;
}
if (!req.ParamU32("address", &address))
return false;
uint32_t size;
if (!req.ParamU32("size", &size))
return false;
if (address + size < address) {
req.Fail("Size is too large");
return false;
}
end = size == 0 ? 0 : address + size;
hasEnabled = req.HasParam("enabled");
if (hasEnabled) {
if (!req.ParamBool("enabled", &enabled))
return false;
}
hasLog = req.HasParam("log");
if (hasLog) {
if (!req.ParamBool("log", &log))
return false;
}
hasCond = req.HasParam("read") || req.HasParam("write") || req.HasParam("change");
if (hasCond) {
bool read, write, change;
if (!req.ParamBool("read", &read) || !req.ParamBool("write", &write) || !req.ParamBool("change", &change))
return false;
int bits = (read ? MEMCHECK_READ : 0) | (write ? MEMCHECK_WRITE : 0) | (change ? MEMCHECK_WRITE_ONCHANGE : 0);
cond = MemCheckCondition(bits);
}
hasLogFormat = req.HasParam("logFormat");
if (hasLogFormat) {
if (!req.ParamString("logFormat", &logFormat))
return false;
}
return true;
}
BreakAction Result(bool adding) {
int bits = MEMCHECK_READWRITE;
if (adding || (hasLog && hasEnabled)) {
bits = (enabled ? BREAK_ACTION_PAUSE : 0) | (log ? BREAK_ACTION_LOG : 0);
} else {
MemCheck prev;
if (CBreakPoints::GetMemCheck(address, end, &prev))
bits = prev.result;
if (hasEnabled)
bits = (bits & ~BREAK_ACTION_PAUSE) | (enabled ? BREAK_ACTION_PAUSE : 0);
if (hasLog)
bits = (bits & ~BREAK_ACTION_LOG) | (log ? BREAK_ACTION_LOG : 0);
}
return BreakAction(bits);
}
void Apply() {
if (hasLogFormat) {
CBreakPoints::ChangeMemCheckLogFormat(address, end, logFormat);
}
}
};
// Add a new memory breakpoint (memory.breakpoint.add)
//
// Parameters:
// - address: unsigned integer address for the start of the memory range.
// - size: unsigned integer specifying size of memory range.
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
// - log: optional boolean, whether to log when this breakpoint trips.
// - read: optional boolean, whether to trip on any read to this address.
// - write: optional boolean, whether to trip on any write to this address.
// - change: optional boolean, whether to trip on a write to this address which modifies data
// (or any write that may modify data.)
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
//
// Response (same event name) with no extra data.
//
// Note: will replace any breakpoint that has the same start address and size.
void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
WebSocketMemoryBreakpointParams params;
if (!params.Parse(req))
return;
CBreakPoints::AddMemCheck(params.address, params.end, params.cond, params.Result(true));
params.Apply();
req.Respond();
}
// Update a memory breakpoint (memory.breakpoint.update)
//
// Parameters:
// - address: unsigned integer address for the start of the memory range.
// - size: unsigned integer specifying size of memory range.
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
// - log: optional boolean, whether to log when this breakpoint trips.
// - read: optional boolean, whether to trip on any read to this address.
// - write: optional boolean, whether to trip on any write to this address.
// - change: optional boolean, whether to trip on a write to this address which modifies data
// (or any write that may modify data.)
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
//
// Response (same event name) with no extra data.
void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
WebSocketMemoryBreakpointParams params;
if (!params.Parse(req))
return;
MemCheck mc;
if (!CBreakPoints::GetMemCheck(params.address, params.end, &mc))
return req.Fail("Breakpoint not found");
CBreakPoints::ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));
params.Apply();
req.Respond();
}
// Remove a memory breakpoint (memory.breakpoint.remove)
//
// Parameters:
// - address: unsigned integer address for the start of the memory range.
// - size: unsigned integer specifying size of memory range.
//
// Response (same event name) with no extra data.
void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
uint32_t address;
if (!req.ParamU32("address", &address))
return;
uint32_t size;
if (!req.ParamU32("size", &size))
return;
CBreakPoints::RemoveMemCheck(address, size == 0 ? 0 : address + size);
req.Respond();
}
// List all memory breakpoints (memory.breakpoint.list)
//
// No parameters.
//
// Response (same event name):
// - breakpoints: array of objects, each with properties:
// - address: unsigned integer address for the start of the memory range.
// - size: unsigned integer specifying size of memory range.
// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.
// - log: optional boolean, whether to log when this breakpoint trips.
// - read: optional boolean, whether to trip on any read to this address.
// - write: optional boolean, whether to trip on any write to this address.
// - change: optional boolean, whether to trip on a write to this address which modifies data
// (or any write that may modify data.)
// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.
// - symbol: null, or string label or symbol at breakpoint address.
void WebSocketMemoryBreakpointList(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
JsonWriter &json = req.Respond();
json.pushArray("breakpoints");
auto mcs = CBreakPoints::GetMemChecks();
for (const auto &mc : mcs) {
json.pushDict();
json.writeUint("address", mc.start);
json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);
json.writeBool("enabled", mc.IsEnabled());
json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);
json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);
json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);
json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);
json.writeUint("hits", mc.numHits);
if (!mc.logFormat.empty())
json.writeString("logFormat", mc.logFormat);
else
json.writeNull("logFormat");
std::string symbol = g_symbolMap->GetLabelString(mc.start);
if (symbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", symbol);
json.pop();
}
json.pop();
}
@@ -0,0 +1,32 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map);
void WebSocketCPUBreakpointAdd(DebuggerRequest &req);
void WebSocketCPUBreakpointUpdate(DebuggerRequest &req);
void WebSocketCPUBreakpointRemove(DebuggerRequest &req);
void WebSocketCPUBreakpointList(DebuggerRequest &req);
void WebSocketMemoryBreakpointAdd(DebuggerRequest &req);
void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req);
void WebSocketMemoryBreakpointRemove(DebuggerRequest &req);
void WebSocketMemoryBreakpointList(DebuggerRequest &req);
@@ -0,0 +1,412 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/StringUtils.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSDebugInterface.h"
void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) {
// No need to bind or alloc state, these are all global.
map["cpu.stepping"] = &WebSocketCPUStepping;
map["cpu.resume"] = &WebSocketCPUResume;
map["cpu.status"] = &WebSocketCPUStatus;
map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs;
map["cpu.getReg"] = &WebSocketCPUGetReg;
map["cpu.setReg"] = &WebSocketCPUSetReg;
map["cpu.evaluate"] = &WebSocketCPUEvaluate;
return nullptr;
}
static std::string RegValueAsFloat(uint32_t u) {
union {
uint32_t u;
float f;
} bits = { u };
return StringFromFormat("%f", bits.f);
}
static DebugInterface *CPUFromRequest(DebuggerRequest &req) {
if (!req.HasParam("thread"))
return currentDebugMIPS;
u32 uid;
if (!req.ParamU32("thread", &uid))
return nullptr;
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
if (!cpuDebug)
req.Fail("Thread could not be found");
return cpuDebug;
}
// Begin stepping and pause the CPU (cpu.stepping)
//
// No parameters.
//
// No immediate response. Once CPU is stepping, a "cpu.stepping" event will be sent.
void WebSocketCPUStepping(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
if (!Core_IsStepping() && Core_IsActive()) {
Core_EnableStepping(true);
}
}
// Stop stepping and resume the CPU (cpu.resume)
//
// No parameters.
//
// No immediate response. Once CPU is stepping, a "cpu.resume" event will be sent.
void WebSocketCPUResume(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
if (!Core_IsStepping() || coreState == CORE_POWERDOWN) {
return req.Fail("CPU not stepping");
}
CBreakPoints::SetSkipFirst(currentMIPS->pc);
if (currentMIPS->inDelaySlot) {
Core_DoSingleStep();
}
Core_EnableStepping(false);
}
// Request the current CPU status (cpu.status)
//
// No parameters.
//
// Response (same event name):
// - stepping: boolean, CPU currently stepping.
// - paused: boolean, CPU paused or not started yet.
// - pc: number value of PC register (inaccurate unless stepping.)
// - ticks: number of CPU cycles into emulation.
void WebSocketCPUStatus(DebuggerRequest &req) {
JsonWriter &json = req.Respond();
json.writeBool("stepping", PSP_IsInited() && Core_IsStepping() && coreState != CORE_POWERDOWN);
json.writeBool("paused", GetUIState() != UISTATE_INGAME);
// Avoid NULL deference.
json.writeUint("pc", PSP_IsInited() ? currentMIPS->pc : 0);
// A double ought to be good enough for a 156 day debug session.
json.writeFloat("ticks", PSP_IsInited() ? CoreTiming::GetTicks() : 0);
}
// Retrieve all regs and their values (cpu.getAllRegs)
//
// Parameters:
// - thread: optional number indicating the thread id to get regs for.
//
// Response (same event name):
// - categories: array of objects:
// - id: "category" property to use for other events.
// - name: a category name, such as "GPR".
// - registerNames: array of string names of the registers (size varies per category.)
// - uintValues: array of unsigned integer values for the registers.
// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf".
void WebSocketCPUGetAllRegs(DebuggerRequest &req) {
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
JsonWriter &json = req.Respond();
json.pushArray("categories");
for (int c = 0; c < cpuDebug->GetNumCategories(); ++c) {
json.pushDict();
json.writeInt("id", c);
json.writeString("name", cpuDebug->GetCategoryName(c));
int total = cpuDebug->GetNumRegsInCategory(c);
json.pushArray("registerNames");
for (int r = 0; r < total; ++r)
json.writeString(cpuDebug->GetRegName(c, r));
if (c == 0) {
json.writeString("pc");
json.writeString("hi");
json.writeString("lo");
}
json.pop();
json.pushArray("uintValues");
// Writing as floating point to avoid negatives. Actually double, so safe.
for (int r = 0; r < total; ++r)
json.writeUint(cpuDebug->GetRegValue(c, r));
if (c == 0) {
json.writeUint(cpuDebug->GetPC());
json.writeUint(cpuDebug->GetHi());
json.writeUint(cpuDebug->GetLo());
}
json.pop();
json.pushArray("floatValues");
// Note: String so it can have Infinity and NaN.
for (int r = 0; r < total; ++r)
json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r)));
if (c == 0) {
json.writeString(RegValueAsFloat(cpuDebug->GetPC()));
json.writeString(RegValueAsFloat(cpuDebug->GetHi()));
json.writeString(RegValueAsFloat(cpuDebug->GetLo()));
}
json.pop();
json.pop();
}
json.pop();
}
enum class DebuggerRegType {
INVALID,
NORMAL,
PC,
HI,
LO,
};
static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) {
if (name == "pc") {
*cat = 0;
*reg = 32;
return DebuggerRegType::PC;
}
if (name == "hi") {
*cat = 0;
*reg = 33;
return DebuggerRegType::HI;
}
if (name == "lo") {
*cat = 0;
*reg = 34;
return DebuggerRegType::LO;
}
for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {
int total = currentDebugMIPS->GetNumRegsInCategory(c);
for (int r = 0; r < total; ++r) {
if (name == currentDebugMIPS->GetRegName(c, r)) {
*cat = c;
*reg = r;
return DebuggerRegType::NORMAL;
}
}
}
req.Fail("Invalid 'name' parameter");
return DebuggerRegType::INVALID;
}
static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) {
const char *name = req.data.getString("name", nullptr);
if (name)
return ValidateRegName(req, name, cat, reg);
*cat = req.data.getInt("category", -1);
*reg = req.data.getInt("register", -1);
if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) {
req.Fail("Invalid 'category' parameter");
return DebuggerRegType::INVALID;
}
// TODO: We fake it for GPR... not sure yet if this is a good thing.
if (*cat == 0) {
// Intentionally retains the reg value.
if (*reg == 32)
return DebuggerRegType::PC;
if (*reg == 33)
return DebuggerRegType::HI;
if (*reg == 34)
return DebuggerRegType::LO;
}
if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) {
req.Fail("Invalid 'register' parameter");
return DebuggerRegType::INVALID;
}
return DebuggerRegType::NORMAL;
}
// Retrieve the value of a single register (cpu.getReg)
//
// Parameters (by name):
// - thread: optional number indicating the thread id to get from.
// - name: string name of register to lookup.
//
// Parameters (by category id and index, ignored if name specified):
// - thread: optional number indicating the thread id to get from.
// - category: id of category for the register.
// - register: index into array of registers.
//
// Response (same event name):
// - category: id of category for the register.
// - register: index into array of registers.
// - uintValue: value in register.
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
void WebSocketCPUGetReg(DebuggerRequest &req) {
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
int cat, reg;
uint32_t val;
switch (ValidateCatReg(req, &cat, &reg)) {
case DebuggerRegType::NORMAL:
val = cpuDebug->GetRegValue(cat, reg);
break;
case DebuggerRegType::PC:
val = cpuDebug->GetPC();
break;
case DebuggerRegType::HI:
val = cpuDebug->GetHi();
break;
case DebuggerRegType::LO:
val = cpuDebug->GetLo();
break;
case DebuggerRegType::INVALID:
// Error response already sent.
return;
}
JsonWriter &json = req.Respond();
json.writeInt("category", cat);
json.writeInt("register", reg);
json.writeUint("uintValue", val);
json.writeString("floatValue", RegValueAsFloat(val));
}
// Update the value of a single register (cpu.setReg)
//
// Parameters (by name):
// - thread: optional number indicating the thread id to update.
// - name: string name of register to lookup.
// - value: number (uint values only) or string to set to. Values may include
// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".
//
// Parameters (by category id and index, ignored if name specified):
// - thread: optional number indicating the thread id to update.
// - category: id of category for the register.
// - register: index into array of registers.
// - value: number (uint values only) or string to set to. Values may include
// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".
//
// Response (same event name):
// - category: id of category for the register.
// - register: index into array of registers.
// - uintValue: new value in register.
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
//
// NOTE: Cannot be called unless the CPU is currently stepping.
void WebSocketCPUSetReg(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
if (!Core_IsStepping()) {
return req.Fail("CPU currently running (cpu.stepping first)");
}
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
uint32_t val;
if (!req.ParamU32("value", &val, true)) {
// Already sent error.
return;
}
int cat, reg;
switch (ValidateCatReg(req, &cat, &reg)) {
case DebuggerRegType::NORMAL:
if (cat == 0 && reg == 0 && val != 0) {
return req.Fail("Cannot change reg zero");
}
cpuDebug->SetRegValue(cat, reg, val);
// In case part of it was ignored (e.g. flags reg.)
val = cpuDebug->GetRegValue(cat, reg);
break;
case DebuggerRegType::PC:
cpuDebug->SetPC(val);
break;
case DebuggerRegType::HI:
cpuDebug->SetHi(val);
break;
case DebuggerRegType::LO:
cpuDebug->SetLo(val);
break;
case DebuggerRegType::INVALID:
// Error response already sent.
return;
}
JsonWriter &json = req.Respond();
// Repeat it back just to avoid confusion on how it parsed.
json.writeInt("category", cat);
json.writeInt("register", reg);
json.writeUint("uintValue", val);
json.writeString("floatValue", RegValueAsFloat(val));
}
// Evaluate an expression (cpu.evaluate)
//
// Parameters:
// - thread: optional number indicating the thread id to update.
// - expression: string containing labels, operators, regs, etc.
//
// Response (same event name):
// - uintValue: value in register.
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
void WebSocketCPUEvaluate(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
std::string exp;
if (!req.ParamString("expression", &exp)) {
// Already sent error.
return;
}
u32 val;
PostfixExpression postfix;
if (!cpuDebug->initExpression(exp.c_str(), postfix)) {
return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
}
if (!cpuDebug->parseExpression(postfix, val)) {
return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError()));
}
JsonWriter &json = req.Respond();
json.writeUint("uintValue", val);
json.writeString("floatValue", RegValueAsFloat(val));
}
@@ -0,0 +1,30 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map);
void WebSocketCPUStepping(DebuggerRequest &req);
void WebSocketCPUResume(DebuggerRequest &req);
void WebSocketCPUStatus(DebuggerRequest &req);
void WebSocketCPUGetAllRegs(DebuggerRequest &req);
void WebSocketCPUGetReg(DebuggerRequest &req);
void WebSocketCPUSetReg(DebuggerRequest &req);
void WebSocketCPUEvaluate(DebuggerRequest &req);
@@ -0,0 +1,481 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <cctype>
#include "base/stringutil.h"
#include "util/text/utf8.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPSAsm.h"
#include "Core/MIPS/MIPSDebugInterface.h"
struct WebSocketDisasmState {
WebSocketDisasmState() {
disasm_.setCpu(currentDebugMIPS);
}
~WebSocketDisasmState() {
disasm_.clear();
}
void Base(DebuggerRequest &req);
void Disasm(DebuggerRequest &req);
void SearchDisasm(DebuggerRequest &req);
void Assemble(DebuggerRequest &req);
protected:
void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l);
void WriteBranchGuide(JsonWriter &json, const BranchLine &l);
uint32_t RoundAddressUp(uint32_t addr);
DisassemblyManager disasm_;
};
void *WebSocketDisasmInit(DebuggerEventHandlerMap &map) {
auto p = new WebSocketDisasmState();
map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1);
map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1);
map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1);
map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1);
return p;
}
void WebSocketDisasmShutdown(void *p) {
delete static_cast<WebSocketDisasmState *>(p);
}
static DebugInterface *CPUFromRequest(DebuggerRequest &req) {
if (!req.HasParam("thread"))
return currentDebugMIPS;
u32 uid;
if (!req.ParamU32("thread", &uid))
return nullptr;
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
if (!cpuDebug)
req.Fail("Thread could not be found");
return cpuDebug;
}
void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) {
u32 addr = l.info.opcodeAddress;
json.pushDict();
if (l.type == DISTYPE_OPCODE)
json.writeString("type", "opcode");
else if (l.type == DISTYPE_MACRO)
json.writeString("type", "macro");
else if (l.type == DISTYPE_DATA)
json.writeString("type", "data");
else if (l.type == DISTYPE_OTHER)
json.writeString("type", "other");
json.writeUint("address", addr);
json.writeInt("addressSize", l.totalSize);
json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0);
if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) {
json.pushArray("macroEncoding");
for (u32 off = 0; off < l.totalSize; off += 4) {
json.writeUint(Memory::Read_Instruction(addr + off).encoding);
}
json.pop();
} else {
json.writeNull("macroEncoding");
}
int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF;
json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16));
json.writeString("name", l.name);
json.writeString("params", l.params);
const std::string addressSymbol = g_symbolMap->GetLabelString(addr);
if (addressSymbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", addressSymbol);
const u32 funcAddress = g_symbolMap->GetFunctionStart(addr);
const std::string funcName = g_symbolMap->GetLabelString(funcAddress);
if (funcName.empty())
json.writeNull("function");
else
json.writeString("function", funcName);
if (l.type == DISTYPE_DATA) {
u32 dataStart = g_symbolMap->GetDataStart(addr);
if (dataStart == -1)
dataStart = addr;
const std::string dataLabel = g_symbolMap->GetLabelString(dataStart);
json.pushDict("dataSymbol");
json.writeUint("start", dataStart);
if (dataLabel.empty())
json.writeNull("label");
else
json.writeString("label", dataLabel);
json.pop();
} else {
json.writeNull("dataSymbol");
}
bool enabled;
// TODO: Account for bp inside macro?
if (CBreakPoints::IsAddressBreakPoint(addr, &enabled)) {
json.pushDict("breakpoint");
json.writeBool("enabled", enabled);
auto cond = CBreakPoints::GetBreakPointCondition(addr);
if (cond)
json.writeString("condition", cond->expressionString);
else
json.writeNull("condition");
json.pop();
} else {
json.writeNull("breakpoint");
}
// This is always the current execution's PC.
json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr);
if (l.info.isBranch) {
json.pushDict("branch");
std::string targetSymbol;
if (!l.info.isBranchToRegister) {
targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget);
json.writeUint("targetAddress", l.info.branchTarget);
json.writeNull("register");
} else {
json.writeNull("targetAddress");
json.writeInt("register", l.info.branchRegisterNum);
}
json.writeBool("isLinked", l.info.isLinkedBranch);
json.writeBool("isLikely", l.info.isLikelyBranch);
if (targetSymbol.empty())
json.writeNull("symbol");
else
json.writeString("symbol", targetSymbol);
json.pop();
} else {
json.writeNull("branch");
}
if (l.info.hasRelevantAddress) {
json.pushDict("relevantData");
json.writeUint("address", l.info.relevantAddress);
if (Memory::IsValidRange(l.info.relevantAddress, 4))
json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress));
else
json.writeNull("uintValue");
json.pop();
} else {
json.writeNull("relevantData");
}
if (l.info.isConditional)
json.writeBool("conditionMet", l.info.conditionMet);
else
json.writeNull("conditionMet");
if (l.info.isDataAccess) {
json.pushDict("dataAccess");
json.writeUint("address", l.info.dataAddress);
json.writeInt("size", l.info.dataSize);
std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress);
std::string valueSymbol;
if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize))
json.writeNull("uintValue");
else if (l.info.dataSize == 1)
json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress));
else if (l.info.dataSize == 2)
json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress));
else if (l.info.dataSize >= 4) {
u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress);
valueSymbol = g_symbolMap->GetLabelString(data);
json.writeUint("uintValue", data);
}
if (!dataSymbol.empty())
json.writeString("symbol", dataSymbol);
else
json.writeNull("symbol");
if (!valueSymbol.empty())
json.writeString("valueSymbol", valueSymbol);
else
json.writeNull("valueSymbol");
json.pop();
} else {
json.writeNull("dataAccess");
}
json.pop();
}
void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) {
json.pushDict();
json.writeUint("top", l.first);
json.writeUint("bottom", l.second);
if (l.type == LINE_UP)
json.writeString("direction", "up");
else if (l.type == LINE_DOWN)
json.writeString("direction", "down");
else if (l.type == LINE_RIGHT)
json.writeString("direction", "right");
json.writeInt("lane", l.laneIndex);
json.pop();
}
uint32_t WebSocketDisasmState::RoundAddressUp(uint32_t addr) {
if (addr < PSP_GetScratchpadMemoryBase())
return PSP_GetScratchpadMemoryBase();
else if (addr >= PSP_GetScratchpadMemoryEnd() && addr < PSP_GetVidMemBase())
return PSP_GetVidMemBase();
else if (addr >= PSP_GetVidMemEnd() && addr < PSP_GetKernelMemoryBase())
return PSP_GetKernelMemoryBase();
else if (addr >= PSP_GetUserMemoryEnd())
return PSP_GetScratchpadMemoryBase();
return addr;
}
// Request the current PSP memory base address (memory.base)
//
// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space.
//
// No parameters.
//
// Response (same event name):
// - addressHex: string indicating base address in hexadecimal (may be 64 bit.)
void WebSocketDisasmState::Base(DebuggerRequest &req) {
JsonWriter &json = req.Respond();
json.writeString("addressHex", StringFromFormat("%016llx", Memory::base));
}
// Disassemble a range of memory as CPU instructions (memory.disasm)
//
// Parameters (by count):
// - thread: optional number indicating the thread id for branch info.
// - address: number specifying the start address.
// - count: number of lines to return (may be clamped to an internal limit.)
// - displaySymbols: boolean true to show symbol names in instruction params.
//
// Parameters (by end address):
// - thread: optional number indicating the thread id for branch info.
// - address: number specifying the start address.
// - end: number which must be after the start address (may be clamped to an internal limit.)
// - displaySymbols: boolean true to show symbol names in instruction params.
//
// Response (same event name):
// - range: object with result "start" and "end" properties, the addresses actually used.
// (disassembly may have snapped to a nearby instruction.)
// - branchGuides: array of objects:
// - top: the earlier address as a number.
// - bottom: the later address as a number.
// - direction: "up", "down", or "right" depending on the flow of the branch.
// - lane: number index to avoid overlapping guides.
// - lines: array of objects:
// - type: "opcode", "macro", "data", or "other".
// - address: address of first actual instruction.
// - addressSize: bytes used by this line (might be more than 4.)
// - encoding: uint value of actual instruction (may differ from memory read when using jit.)
// - macroEncoding: null, or an array of encodings if this line represents multiple instructions.
// - name: string name of the instruction.
// - params: formatted parameters for the instruction.
// - (other info about the disassembled line.)
void WebSocketDisasmState::Disasm(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
return req.Fail("CPU not started");
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
// In case of client errors, we limit the range to something that won't make us crash.
static const uint32_t MAX_RANGE = 10000;
uint32_t start, end;
if (!req.ParamU32("address", &start))
return;
uint32_t count = 0;
if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL))
return;
if (count != 0) {
count = std::min(count, MAX_RANGE);
// Let's assume everything is two instructions.
disasm_.analyze(start - 4, count * 8 + 8);
start = disasm_.getStartAddress(start);
if (start == -1)
req.ParamU32("address", &start);
end = disasm_.getNthNextAddress(start, count);
} else if (req.ParamU32("end", &end)) {
end = std::min(std::max(start, end), start + MAX_RANGE * 4);
// Let's assume everything is two instructions.
disasm_.analyze(start - 4, end - start + 8);
start = disasm_.getStartAddress(start);
if (start == -1)
req.ParamU32("address", &start);
// Correct end and calculate count based on it.
// This accounts for macros as one line, although two instructions.
u32 stop = end;
count = 0;
for (end = start; end < stop; end = disasm_.getNthNextAddress(end, 1)) {
count++;
}
} else {
// Error message already sent.
return;
}
bool displaySymbols = true;
if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))
return;
JsonWriter &json = req.Respond();
json.pushDict("range");
json.writeUint("start", start);
json.writeUint("end", end);
json.pop();
json.pushArray("lines");
DisassemblyLineInfo line;
uint32_t addr = start;
for (uint32_t i = 0; i < count; ++i) {
disasm_.getLine(addr, displaySymbols, line, cpuDebug);
WriteDisasmLine(json, line);
addr += line.totalSize;
// These are pretty long, so let's grease the wheels a bit.
if (i % 50 == 0)
req.Flush();
}
json.pop();
json.pushArray("branchGuides");
auto branchGuides = disasm_.getBranchLines(start, end - start);
for (auto bl : branchGuides)
WriteBranchGuide(json, bl);
json.pop();
}
// Search disassembly for some text (cpu.searchDisasm)
//
// Parameters:
// - thread: optional number indicating the thread id (may not affect search much.)
// - address: starting address as a number.
// - end: optional end address as a number (otherwise uses start.)
// - match: string to search for.
// - displaySymbols: optional, specify false to hide symbols in the searched parameters.
//
// Response (same event name):
// - address: number address of match or null if none was found.
void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
return req.Fail("CPU not started");
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
uint32_t start;
if (!req.ParamU32("address", &start))
return;
uint32_t end = start;
if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))
return;
std::string match;
if (!req.ParamString("match", &match))
return;
bool displaySymbols = true;
if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))
return;
bool loopSearch = end <= start;
start = RoundAddressUp(start);
if ((end <= start) != loopSearch) {
// We must've passed end by rounding up.
JsonWriter &json = req.Respond();
json.writeNull("address");
return;
}
// We do this after the check in case both were in unused memory.
end = RoundAddressUp(end);
std::transform(match.begin(), match.end(), match.begin(), ::tolower);
DisassemblyLineInfo line;
bool found = false;
uint32_t addr = start;
do {
disasm_.getLine(addr, displaySymbols, line, cpuDebug);
const std::string addressSymbol = g_symbolMap->GetLabelString(addr);
std::string mergeForSearch;
// Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case.
mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size());
sprintf(&mergeForSearch[0], "%08x ", addr);
auto inserter = mergeForSearch.begin() + 9;
if (!addressSymbol.empty()) {
inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower);
*inserter++ = ':';
*inserter++ = ' ';
}
inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower);
*inserter++ = ' ';
inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower);
if (mergeForSearch.find(match) != mergeForSearch.npos) {
found = true;
break;
}
addr = RoundAddressUp(addr + line.totalSize);
} while (addr != end);
JsonWriter &json = req.Respond();
if (found)
json.writeUint("address", addr);
else
json.writeNull("address");
}
// Assemble an instruction (cpu.assemble)
//
// Parameters:
// - address: number indicating the address to write to.
// - code: string containing the instruction to assemble.
//
// Response (same event name):
// - encoding: resulting encoding at this address. Always returns one value, even for macros.
void WebSocketDisasmState::Assemble(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) {
return req.Fail("CPU not started");
}
uint32_t address;
if (!req.ParamU32("address", &address))
return;
std::string code;
if (!req.ParamString("code", &code))
return;
if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address))
return req.Fail(StringFromFormat("Could not assemble: %s", ConvertWStringToUTF8(MIPSAsm::GetAssembleError()).c_str()));
JsonWriter &json = req.Respond();
json.writeUint("encoding", Memory::Read_Instruction(address).encoding);
}
@@ -0,0 +1,23 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketDisasmInit(DebuggerEventHandlerMap &map);
void WebSocketDisasmShutdown(void *p);
@@ -0,0 +1,332 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#ifndef USING_QT_UI
#include <libpng17/png.h>
#include <zlib.h>
#endif
#include "data/base64.h"
#include "Common/StringUtils.h"
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/Screenshot.h"
#include "GPU/Debugger/Stepping.h"
void *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map) {
// No need to bind or alloc state, these are all global.
map["gpu.buffer.screenshot"] = &WebSocketGPUBufferScreenshot;
map["gpu.buffer.renderColor"] = &WebSocketGPUBufferRenderColor;
map["gpu.buffer.renderDepth"] = &WebSocketGPUBufferRenderDepth;
map["gpu.buffer.renderStencil"] = &WebSocketGPUBufferRenderStencil;
return nullptr;
}
// Note: Calls req.Respond(). Other data can be added afterward.
static bool StreamBufferToDataURI(DebuggerRequest &req, const GPUDebugBuffer &buf, bool includeAlpha) {
#ifdef USING_QT_UI
req.Fail("Not supported on Qt yet, pull requests accepted");
return false;
#else
u8 *flipbuffer = nullptr;
u32 w = (u32)-1;
u32 h = (u32)-1;
const u8 *buffer = ConvertBufferToScreenshot(buf, includeAlpha, flipbuffer, w, h);
if (!buffer) {
req.Fail("Internal error converting buffer for PNG encode");
return false;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) {
req.Fail("Internal error setting up PNG encoder (png_ptr)");
return false;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, nullptr);
req.Fail("Internal error setting up PNG encoder (info_ptr)");
return false;
}
// Speed. Wireless N should give 35 KB/ms. For most devices, zlib/filters will cost more.
png_set_compression_strategy(png_ptr, Z_RLE);
png_set_compression_level(png_ptr, 1);
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE);
auto &json = req.Respond();
json.writeInt("width", w);
json.writeInt("height", h);
// Start a value...
json.writeRaw("uri", "");
req.Flush();
// Now we'll write it directly to the stream.
req.ws->AddFragment(false, "\"data:image/png;base64,");
struct Context {
DebuggerRequest *req;
uint8_t buf[3];
size_t bufSize;
};
Context ctx = { &req, {}, 0 };
auto write = [](png_structp png_ptr, png_bytep data, png_size_t length) {
auto ctx = (Context *)png_get_io_ptr(png_ptr);
auto &req = *ctx->req;
// If we buffered some bytes, fill to 3 bytes for a clean base64 encode.
// This way we don't have padding.
while (length > 0 && ctx->bufSize > 0 && ctx->bufSize != 3) {
ctx->buf[ctx->bufSize++] = data[0];
data++;
length--;
}
if (ctx->bufSize == 3) {
req.ws->AddFragment(false, Base64Encode(ctx->buf, ctx->bufSize));
ctx->bufSize = 0;
}
assert(ctx->bufSize == 0 || length == 0);
// Save bytes that would result in padding for next time.
size_t toBuffer = length % 3;
for (size_t i = 0; i < toBuffer; ++i) {
ctx->buf[i] = data[length - toBuffer + i];
ctx->bufSize++;
}
if (length > toBuffer) {
req.ws->AddFragment(false, Base64Encode(data, length - toBuffer));
}
};
auto flush = [](png_structp png_ptr) {
// Nothing, just here to prevent stdio flush.
};
png_bytep *row_pointers = new png_bytep[h];
u32 stride = includeAlpha ? w * 4 : w * 3;
for (u32 i = 0; i < h; ++i) {
row_pointers[i] = (u8 *)buffer + stride * i;
}
png_set_write_fn(png_ptr, &ctx, write, flush);
int colorType = includeAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB;
png_set_IHDR(png_ptr, info_ptr, w, h, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_rows(png_ptr, info_ptr, row_pointers);
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] row_pointers;
delete [] flipbuffer;
if (ctx.bufSize > 0) {
req.ws->AddFragment(false, Base64Encode(ctx.buf, ctx.bufSize));
ctx.bufSize = 0;
}
// End the string.
req.ws->AddFragment(false, "\"");
return true;
#endif
}
static std::string DescribeFormat(GPUDebugBufferFormat fmt) {
switch (fmt) {
case GPU_DBG_FORMAT_565: return "B5G6R5_UNORM_PACK16";
case GPU_DBG_FORMAT_5551: return "A1B5G5R5_UNORM_PACK16";
case GPU_DBG_FORMAT_4444: return "A4B4G4R4_UNORM_PACK16";
case GPU_DBG_FORMAT_8888: return "R8G8B8A8_UNORM";
case GPU_DBG_FORMAT_565_REV: return "R5G6B5_UNORM_PACK16";
case GPU_DBG_FORMAT_5551_REV: return "R5G5B5A1_UNORM_PACK16";
case GPU_DBG_FORMAT_4444_REV: return "R4G4B4A4_UNORM_PACK16";
case GPU_DBG_FORMAT_5551_BGRA: return "A1R5G5B5_UNORM_PACK16";
case GPU_DBG_FORMAT_4444_BGRA: return "A4R4G4B4_UNORM_PACK16";
case GPU_DBG_FORMAT_8888_BGRA: return "B8G8R8A8_UNORM";
case GPU_DBG_FORMAT_FLOAT: return "D32F";
case GPU_DBG_FORMAT_16BIT: return "D16";
case GPU_DBG_FORMAT_8BIT: return "S8";
case GPU_DBG_FORMAT_24BIT_8X: return "D24_X8";
case GPU_DBG_FORMAT_24X_8BIT: return "X24_S8";
case GPU_DBG_FORMAT_FLOAT_DIV_256: return "D32F_DIV_256";
case GPU_DBG_FORMAT_24BIT_8X_DIV_256: return "D32F_X8_DIV_256";
case GPU_DBG_FORMAT_888_RGB: return "R8G8B8_UNORM";
case GPU_DBG_FORMAT_INVALID:
case GPU_DBG_FORMAT_BRSWAP_FLAG:
default:
return "UNDEFINED";
}
}
// Note: Calls req.Respond(). Other data can be added afterward.
static bool StreamBufferToBase64(DebuggerRequest &req, const GPUDebugBuffer &buf) {
size_t length = buf.GetStride() * buf.GetHeight();
auto &json = req.Respond();
json.writeInt("width", buf.GetStride());
json.writeInt("height", buf.GetHeight());
json.writeBool("flipped", buf.GetFlipped());
json.writeString("format", DescribeFormat(buf.GetFormat()));
// Start a value without any actual data yet...
json.writeRaw("base64", "");
req.Flush();
// Now we'll write it directly to the stream.
req.ws->AddFragment(false, "\"");
// 65535 is an "even" number of base64 characters.
static const size_t CHUNK_SIZE = 65535;
for (size_t i = 0; i < length; i += CHUNK_SIZE) {
size_t left = std::min(length - i, CHUNK_SIZE);
req.ws->AddFragment(false, Base64Encode(buf.GetData() + i, left));
}
req.ws->AddFragment(false, "\"");
return true;
}
static void GenericStreamBuffer(DebuggerRequest &req, std::function<bool(const GPUDebugBuffer *&)> func) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
if (coreState != CORE_STEPPING && !GPUStepping::IsStepping()) {
return req.Fail("Neither CPU or GPU is stepping");
}
bool includeAlpha = false;
if (!req.ParamBool("alpha", &includeAlpha, DebuggerParamType::OPTIONAL))
return;
std::string type = "uri";
if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))
return;
if (type != "uri" && type != "base64")
return req.Fail("Parameter 'type' must be either 'uri' or 'base64'");
const GPUDebugBuffer *buf = nullptr;
if (!func(buf)) {
return req.Fail("Could not download output");
}
assert(buf != nullptr);
if (type == "base64") {
StreamBufferToBase64(req, *buf);
} else if (type == "uri") {
StreamBufferToDataURI(req, *buf, includeAlpha);
} else {
_assert_(false);
}
}
// Retrieve a screenshot (gpu.buffer.screenshot)
//
// Parameters:
// - type: either 'uri' or 'base64'.
// - alpha: boolean to include the alpha channel for 'uri' type (not normally useful for screenshots.)
//
// Response (same event name) for 'uri' type:
// - width: numeric width of screenshot.
// - height: numeric height of screenshot.
// - uri: data: URI of PNG image for display.
//
// Response (same event name) for 'base64' type:
// - width: numeric width of screenshot (also stride, in pixels, of binary data.)
// - height: numeric height of screenshot.
// - flipped: boolean to indicate whether buffer is vertically flipped.
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
// - base64: base64 encode of binary data.
void WebSocketGPUBufferScreenshot(DebuggerRequest &req) {
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
return GPUStepping::GPU_GetOutputFramebuffer(buf);
});
}
// Retrieve current color render buffer (gpu.buffer.renderColor)
//
// Parameters:
// - type: either 'uri' or 'base64'.
// - alpha: boolean to include the alpha channel for 'uri' type.
//
// Response (same event name) for 'uri' type:
// - width: numeric width of render buffer (may include stride.)
// - height: numeric height of render buffer.
// - uri: data: URI of PNG image for display.
//
// Response (same event name) for 'base64' type:
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
// - height: numeric height of render buffer.
// - flipped: boolean to indicate whether buffer is vertically flipped.
// - format: string indicating format, such as 'R8G8B8A8_UNORM' or 'B8G8R8A8_UNORM'.
// - base64: base64 encode of binary data.
void WebSocketGPUBufferRenderColor(DebuggerRequest &req) {
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
return GPUStepping::GPU_GetCurrentFramebuffer(buf, GPU_DBG_FRAMEBUF_RENDER);
});
}
// Retrieve current depth render buffer (gpu.buffer.renderDepth)
//
// Parameters:
// - type: either 'uri' or 'base64'.
// - alpha: true to use alpha to encode depth, otherwise red for 'uri' type.
//
// Response (same event name) for 'uri' type:
// - width: numeric width of render buffer (may include stride.)
// - height: numeric height of render buffer.
// - uri: data: URI of PNG image for display.
//
// Response (same event name) for 'base64' type:
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
// - height: numeric height of render buffer.
// - flipped: boolean to indicate whether buffer is vertically flipped.
// - format: string indicating format, such as 'D16', 'D24_X8' or 'D32F'.
// - base64: base64 encode of binary data.
void WebSocketGPUBufferRenderDepth(DebuggerRequest &req) {
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
return GPUStepping::GPU_GetCurrentDepthbuffer(buf);
});
}
// Retrieve current stencil render buffer (gpu.buffer.renderStencil)
//
// Parameters:
// - type: either 'uri' or 'base64'.
// - alpha: true to use alpha to encode stencil, otherwise red for 'uri' type.
//
// Response (same event name) for 'uri' type:
// - width: numeric width of render buffer (may include stride.)
// - height: numeric height of render buffer.
// - uri: data: URI of PNG image for display.
//
// Response (same event name) for 'base64' type:
// - width: numeric width of render buffer (also stride, in pixels, of binary data.)
// - height: numeric height of render buffer.
// - flipped: boolean to indicate whether buffer is vertically flipped.
// - format: string indicating format, such as 'X24_S8' or 'S8'.
// - base64: base64 encode of binary data.
void WebSocketGPUBufferRenderStencil(DebuggerRequest &req) {
GenericStreamBuffer(req, [](const GPUDebugBuffer *&buf) {
return GPUStepping::GPU_GetCurrentStencilbuffer(buf);
});
}
@@ -0,0 +1,27 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketGPUBufferInit(DebuggerEventHandlerMap &map);
void WebSocketGPUBufferScreenshot(DebuggerRequest &req);
void WebSocketGPUBufferRenderColor(DebuggerRequest &req);
void WebSocketGPUBufferRenderDepth(DebuggerRequest &req);
void WebSocketGPUBufferRenderStencil(DebuggerRequest &req);
@@ -0,0 +1,94 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Debugger/WebSocket/GameBroadcaster.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/System.h"
struct GameStatusEvent {
const char *ev;
operator std::string() {
JsonWriter j;
j.begin();
j.writeString("event", ev);
if (PSP_IsInited()) {
j.pushDict("game");
j.writeString("id", g_paramSFO.GetDiscID());
j.writeString("version", g_paramSFO.GetValueString("DISC_VERSION"));
j.writeString("title", g_paramSFO.GetValueString("TITLE"));
j.pop();
} else {
j.writeNull("game");
}
j.end();
return j.str();
}
};
// Game started (game.start)
//
// Sent unexpectedly with these properties:
// - game: null or an object with properties:
// - id: string disc ID (such as ULUS12345.)
// - version: string disc version.
// - title: string game title.
// Game quit / ended (game.quit)
//
// Sent unexpectedly with these properties:
// - game: null
// Game paused (game.pause)
//
// Note: this is not the same as stepping. This means the user went to the pause menu.
//
// Sent unexpectedly with these properties:
// - game: null or an object with properties:
// - id: string disc ID (such as ULUS12345.)
// - version: string disc version.
// - title: string game title.
// Game resumed (game.pause)
//
// Note: this is not the same as stepping. This means the user resumed from the pause menu.
//
// Sent unexpectedly with these properties:
// - game: null or an object with properties:
// - id: string disc ID (such as ULUS12345.)
// - version: string disc version.
// - title: string game title.
void GameBroadcaster::Broadcast(net::WebSocketServer *ws) {
// TODO: This is ugly. Callbacks instead?
GlobalUIState state = GetUIState();
if (prevState_ != state) {
if (state == UISTATE_PAUSEMENU) {
ws->Send(GameStatusEvent{"game.pause"});
prevState_ = state;
} else if (state == UISTATE_INGAME && prevState_ == UISTATE_PAUSEMENU) {
ws->Send(GameStatusEvent{"game.resume"});
prevState_ = state;
} else if (state == UISTATE_INGAME && PSP_IsInited()) {
ws->Send(GameStatusEvent{"game.start"});
prevState_ = state;
} else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting()) {
ws->Send(GameStatusEvent{"game.quit"});
prevState_ = state;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/System.h"
namespace net {
class WebSocketServer;
}
struct GameBroadcaster {
public:
GameBroadcaster() {
prevState_ = GetUIState();
}
void Broadcast(net::WebSocketServer *ws);
private:
GlobalUIState prevState_;
};
@@ -0,0 +1,67 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Debugger/WebSocket/GameSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/System.h"
void *WebSocketGameInit(DebuggerEventHandlerMap &map) {
map["game.status"] = &WebSocketGameStatus;
map["version"] = &WebSocketVersion;
return nullptr;
}
// Check game status (game.status)
//
// No parameters.
//
// Response (same event name):
// - game: null or an object with properties:
// - id: string disc ID (such as ULUS12345.)
// - version: string disc version.
// - title: string game title.
// - paused: boolean, true when gameplay is paused (not the same as stepping.)
void WebSocketGameStatus(DebuggerRequest &req) {
JsonWriter &json = req.Respond();
if (PSP_IsInited()) {
json.pushDict("game");
json.writeString("id", g_paramSFO.GetDiscID());
json.writeString("version", g_paramSFO.GetValueString("DISC_VERSION"));
json.writeString("title", g_paramSFO.GetValueString("TITLE"));
json.pop();
} else {
json.writeNull("game");
}
json.writeBool("paused", GetUIState() == UISTATE_PAUSEMENU);
}
// Notify debugger version info (version)
//
// Parameters:
// - name: string indicating name of app or tool.
// - version: string version.
//
// Response (same event name):
// - name: string, "PPSSPP" unless some special build.
// - version: string, typically starts with "v" and may have git build info.
void WebSocketVersion(DebuggerRequest &req) {
JsonWriter &json = req.Respond();
json.writeString("name", "PPSSPP");
json.writeString("version", PPSSPP_GIT_VERSION);
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketGameInit(DebuggerEventHandlerMap &map);
void WebSocketGameStatus(DebuggerRequest &req);
void WebSocketVersion(DebuggerRequest &req);
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Debugger/SymbolMap.h"
#include "Core/Debugger/WebSocket/HLESubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/HLE/sceKernelThread.h"
void *WebSocketHLEInit(DebuggerEventHandlerMap &map) {
map["hle.thread.list"] = &WebSocketHLEThreadList;
map["hle.func.list"] = &WebSocketHLEFuncList;
map["hle.module.list"] = &WebSocketHLEModuleList;
return nullptr;
}
void WebSocketHLEThreadList(DebuggerRequest &req) {
// Will just return none of the CPU isn't ready yet.
auto threads = GetThreadsInfo();
JsonWriter &json = req.Respond();
json.pushArray("threads");
for (auto th : threads) {
json.pushDict();
json.writeUint("id", th.id);
json.writeString("name", th.name);
json.writeInt("status", th.status);
json.pushArray("statuses");
if (th.status & THREADSTATUS_RUNNING)
json.writeString("running");
if (th.status & THREADSTATUS_READY)
json.writeString("ready");
if (th.status & THREADSTATUS_WAIT)
json.writeString("wait");
if (th.status & THREADSTATUS_SUSPEND)
json.writeString("suspend");
if (th.status & THREADSTATUS_DORMANT)
json.writeString("dormant");
if (th.status & THREADSTATUS_DEAD)
json.writeString("dead");
json.pop();
json.writeUint("pc", th.curPC);
json.writeUint("entry", th.entrypoint);
json.writeUint("initialStackSize", th.initialStack);
json.writeUint("currentStackSize", th.stackSize);
json.writeInt("priority", th.priority);
json.writeInt("priority", (int)th.waitType);
json.writeBool("isCurrent", th.isCurrent);
json.pop();
}
json.pop();
}
void WebSocketHLEFuncList(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
auto functions = g_symbolMap->GetAllSymbols(ST_FUNCTION);
JsonWriter &json = req.Respond();
json.pushArray("functions");
for (auto f : functions) {
json.pushDict();
json.writeString("name", f.name);
json.writeUint("address", f.address);
json.writeUint("size", f.size);
json.pop();
}
json.pop();
}
void WebSocketHLEModuleList(DebuggerRequest &req) {
if (!g_symbolMap)
return req.Fail("CPU not active");
auto modules = g_symbolMap->getAllModules();
JsonWriter &json = req.Respond();
json.pushArray("functions");
for (auto m : modules) {
json.pushDict();
json.writeString("name", m.name);
json.writeUint("address", m.address);
json.writeUint("size", m.size);
json.writeBool("isActive", m.active);
json.pop();
}
json.pop();
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketHLEInit(DebuggerEventHandlerMap &map);
void WebSocketHLEThreadList(DebuggerRequest &req);
void WebSocketHLEFuncList(DebuggerRequest &req);
void WebSocketHLEModuleList(DebuggerRequest &req);
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <mutex>
#include "Common/LogManager.h"
#include "Core/Debugger/WebSocket/LogBroadcaster.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
class DebuggerLogListener : public LogListener {
public:
void Log(const LogMessage &msg) override {
std::lock_guard<std::mutex> guard(lock_);
messages_[nextMessage_] = msg;
nextMessage_++;
if (nextMessage_ >= BUFFER_SIZE)
nextMessage_ -= BUFFER_SIZE;
count_++;
}
std::vector<LogMessage> GetMessages() {
std::lock_guard<std::mutex> guard(lock_);
int splitPoint;
int readCount;
if (read_ + BUFFER_SIZE < count_) {
// We'll start with our oldest then.
splitPoint = nextMessage_;
readCount = Count();
} else {
splitPoint = read_;
readCount = count_ - read_;
}
read_ = count_;
std::vector<LogMessage> results;
int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE);
for (int i = splitPoint; i < splitEnd; ++i) {
results.push_back(messages_[i]);
readCount--;
}
for (int i = 0; i < readCount; ++i) {
results.push_back(messages_[i]);
}
return results;
}
int Count() const {
return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE;
}
private:
enum { BUFFER_SIZE = 1024 };
LogMessage messages_[BUFFER_SIZE];
std::mutex lock_;
int nextMessage_ = 0;
int count_ = 0;
int read_ = 0;
};
LogBroadcaster::LogBroadcaster() {
listener_ = new DebuggerLogListener();
if (LogManager::GetInstance())
LogManager::GetInstance()->AddListener(listener_);
}
LogBroadcaster::~LogBroadcaster() {
if (LogManager::GetInstance())
LogManager::GetInstance()->RemoveListener(listener_);
delete listener_;
}
struct DebuggerLogEvent {
const LogMessage &l;
operator std::string() {
JsonWriter j;
j.begin();
j.writeString("event", "log");
j.writeString("timestamp", l.timestamp);
j.writeString("header", l.header);
j.writeString("message", l.msg);
j.writeInt("level", l.level);
j.writeString("channel", l.log);
j.end();
return j.str();
}
};
// Log message (log)
//
// Sent unexpectedly with these properties:
// - timestamp: string timestamp of event.
// - header: string header information about the event (including file/line.)
// - message: actual log message as a string.
// - level: number severity level (1 = highest.)
// - channel: string describing log channel / grouping.
void LogBroadcaster::Broadcast(net::WebSocketServer *ws) {
auto messages = listener_->GetMessages();
for (auto msg : messages) {
ws->Send(DebuggerLogEvent{msg});
}
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
namespace net {
class WebSocketServer;
}
class DebuggerLogListener;
struct LogBroadcaster {
public:
LogBroadcaster();
~LogBroadcaster();
void Broadcast(net::WebSocketServer *ws);
private:
DebuggerLogListener *listener_;
};
@@ -0,0 +1,61 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/WebSocket/SteppingBroadcaster.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/MIPS/MIPS.h"
#include "Core/System.h"
struct CPUSteppingEvent {
operator std::string() {
JsonWriter j;
j.begin();
j.writeString("event", "cpu.stepping");
j.writeUint("pc", currentMIPS->pc);
// A double ought to be good enough for a 156 day debug session.
j.writeFloat("ticks", CoreTiming::GetTicks());
j.end();
return j.str();
}
};
// CPU has begun stepping (cpu.stepping)
//
// Sent unexpectedly with these properties:
// - pc: number value of PC register (inaccurate unless stepping.)
// - ticks: number of CPU cycles into emulation.
// CPU has resumed from stepping (cpu.resume)
//
// Sent unexpectedly with no other properties.
void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) {
if (PSP_IsInited()) {
int steppingCounter = Core_GetSteppingCounter();
// We ignore CORE_POWERDOWN as a stepping state.
if (coreState == CORE_STEPPING && steppingCounter != lastCounter_) {
ws->Send(CPUSteppingEvent());
} else if (prevState_ == CORE_STEPPING && coreState != CORE_STEPPING && Core_IsActive()) {
ws->Send(R"({"event":"cpu.resume"})");
}
lastCounter_ = steppingCounter;
prevState_ = coreState;
} else {
prevState_ = CORE_POWERDOWN;
}
}
@@ -0,0 +1,37 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Core.h"
namespace net {
class WebSocketServer;
}
struct SteppingBroadcaster {
public:
SteppingBroadcaster() {
prevState_ = coreState;
}
void Broadcast(net::WebSocketServer *ws);
private:
CoreState prevState_;
int lastCounter_ = 0;
};
@@ -0,0 +1,298 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "base/stringutil.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/WebSocket/SteppingSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/Core.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/MIPS/MIPSStackWalk.h"
using namespace MIPSAnalyst;
struct WebSocketSteppingState {
WebSocketSteppingState() {
disasm_.setCpu(currentDebugMIPS);
}
~WebSocketSteppingState() {
disasm_.clear();
}
void Into(DebuggerRequest &req);
void Over(DebuggerRequest &req);
void Out(DebuggerRequest &req);
void RunUntil(DebuggerRequest &req);
void HLE(DebuggerRequest &req);
protected:
uint32_t GetNextAddress(DebugInterface *cpuDebug);
int GetNextInstructionCount(DebugInterface *cpuDebug);
void PrepareResume();
void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID);
DisassemblyManager disasm_;
};
void *WebSocketSteppingInit(DebuggerEventHandlerMap &map) {
auto p = new WebSocketSteppingState();
map["cpu.stepInto"] = std::bind(&WebSocketSteppingState::Into, p, std::placeholders::_1);
map["cpu.stepOver"] = std::bind(&WebSocketSteppingState::Over, p, std::placeholders::_1);
map["cpu.stepOut"] = std::bind(&WebSocketSteppingState::Out, p, std::placeholders::_1);
map["cpu.runUntil"] = std::bind(&WebSocketSteppingState::RunUntil, p, std::placeholders::_1);
map["cpu.nextHLE"] = std::bind(&WebSocketSteppingState::HLE, p, std::placeholders::_1);
return p;
}
void WebSocketSteppingShutdown(void *p) {
delete static_cast<WebSocketSteppingState *>(p);
}
static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) {
if (!req.HasParam("thread")) {
if (threadID)
*threadID = -1;
return currentDebugMIPS;
}
uint32_t uid;
if (!req.ParamU32("thread", &uid))
return nullptr;
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
if (!cpuDebug)
req.Fail("Thread could not be found");
if (threadID)
*threadID = uid;
return cpuDebug;
}
// Single step into the next instruction (cpu.stepInto)
//
// Parameters:
// - thread: optional number indicating the thread id to plan stepping on.
//
// No immediate response. A cpu.stepping event will be sent once complete.
//
// Note: any thread can wake the cpu when it hits the next instruction currently.
void WebSocketSteppingState::Into(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive())
return req.Fail("CPU not started");
if (!Core_IsStepping()) {
Core_EnableStepping(true);
return;
}
uint32_t threadID;
auto cpuDebug = CPUFromRequest(req, &threadID);
if (!cpuDebug)
return;
if (cpuDebug == currentDebugMIPS) {
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
CBreakPoints::SetSkipFirst(currentMIPS->pc);
int c = GetNextInstructionCount(cpuDebug);
for (int i = 0; i < c; ++i) {
Core_DoSingleStep();
}
} else {
uint32_t breakpointAddress = cpuDebug->GetPC();
PrepareResume();
// Could have advanced to the breakpoint already in PrepareResume().
// Note: we need to get cpuDebug again anyway (in case we ran some HLE above.)
cpuDebug = CPUFromRequest(req);
if (cpuDebug != currentDebugMIPS) {
CBreakPoints::AddBreakPoint(breakpointAddress, true);
AddThreadCondition(breakpointAddress, threadID);
Core_EnableStepping(false);
}
}
}
// Step over the next instruction (cpu.stepOver)
//
// Note: this jumps over function calls, but also delay slots.
//
// Parameters:
// - thread: optional number indicating the thread id to plan stepping on.
//
// No immediate response. A cpu.stepping event will be sent once complete.
//
// Note: any thread can wake the cpu when it hits the next instruction currently.
void WebSocketSteppingState::Over(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive())
return req.Fail("CPU not started");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
uint32_t threadID;
auto cpuDebug = CPUFromRequest(req, &threadID);
if (!cpuDebug)
return;
MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC());
uint32_t breakpointAddress = GetNextAddress(cpuDebug);
if (info.isBranch) {
if (info.isConditional && !info.isLinkedBranch) {
if (info.conditionMet) {
breakpointAddress = info.branchTarget;
} else {
// Skip over the delay slot.
breakpointAddress += 4;
}
} else {
if (info.isLinkedBranch) {
// jal or jalr - a function call. Skip the delay slot.
breakpointAddress += 4;
} else {
// j - for absolute branches, set the breakpoint at the branch target.
breakpointAddress = info.branchTarget;
}
}
}
PrepareResume();
// Could have advanced to the breakpoint already in PrepareResume().
cpuDebug = CPUFromRequest(req);
if (cpuDebug->GetPC() != breakpointAddress) {
CBreakPoints::AddBreakPoint(breakpointAddress, true);
if (cpuDebug != currentDebugMIPS)
AddThreadCondition(breakpointAddress, threadID);
Core_EnableStepping(false);
}
}
// Step out of a function based on a stack walk (cpu.stepOut)
//
// Parameters:
// - thread: optional number indicating the thread id to plan stepping on.
//
// No immediate response. A cpu.stepping event will be sent once complete.
//
// Note: any thread can wake the cpu when it hits the next instruction currently.
void WebSocketSteppingState::Out(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive())
return req.Fail("CPU not started");
if (!Core_IsStepping())
return req.Fail("CPU currently running (cpu.stepping first)");
uint32_t threadID;
auto cpuDebug = CPUFromRequest(req, &threadID);
if (!cpuDebug)
return;
auto threads = GetThreadsInfo();
uint32_t entry = cpuDebug->GetPC();
uint32_t stackTop = 0;
for (const DebugThreadInfo &th : threads) {
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
entry = th.entrypoint;
stackTop = th.initialStack;
break;
}
}
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
if (frames.size() < 2) {
return req.Fail("Could not find function call to step out into");
}
uint32_t breakpointAddress = frames[1].pc;
PrepareResume();
// Could have advanced to the breakpoint already in PrepareResume().
cpuDebug = CPUFromRequest(req);
if (cpuDebug->GetPC() != breakpointAddress) {
CBreakPoints::AddBreakPoint(breakpointAddress, true);
if (cpuDebug != currentDebugMIPS)
AddThreadCondition(breakpointAddress, threadID);
Core_EnableStepping(false);
}
}
// Run until a certain address (cpu.runUntil)
//
// Parameters:
// - address: number parameter for destination.
//
// No immediate response. A cpu.stepping event will be sent once complete.
void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
uint32_t address = 0;
if (!req.ParamU32("address", &address)) {
// Error already sent.
return;
}
bool wasAtAddress = currentMIPS->pc == address;
PrepareResume();
// We may have arrived already if PauseResume() stepped out of a delay slot.
if (currentMIPS->pc != address || wasAtAddress) {
CBreakPoints::AddBreakPoint(address, true);
Core_EnableStepping(false);
}
}
// Jump after the next HLE call (cpu.nextHLE)
//
// No parameters.
//
// No immediate response. A cpu.stepping event will be sent once complete.
void WebSocketSteppingState::HLE(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
PrepareResume();
hleDebugBreak();
Core_EnableStepping(false);
}
uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) {
uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC());
return disasm_.getNthNextAddress(current, 1);
}
int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {
return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4;
}
void WebSocketSteppingState::PrepareResume() {
if (currentMIPS->inDelaySlot) {
Core_DoSingleStep();
} else {
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
CBreakPoints::SetSkipFirst(currentMIPS->pc);
}
}
void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) {
BreakPointCond cond;
cond.debug = currentDebugMIPS;
cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID);
if (currentDebugMIPS->initExpression(cond.expressionString.c_str(), cond.expression))
CBreakPoints::ChangeBreakPointAddCond(breakpointAddress, cond);
}
@@ -0,0 +1,23 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
void *WebSocketSteppingInit(DebuggerEventHandlerMap &map);
void WebSocketSteppingShutdown(void *p);
+267
View File
@@ -0,0 +1,267 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cmath>
#include <limits>
#include "Common/StringUtils.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
JsonWriter &DebuggerRequest::Respond() {
writer_.begin();
writer_.writeString("event", name);
DebuggerJsonAddTicket(writer_, data);
responseBegun_ = true;
return writer_;
}
bool DebuggerRequest::Finish() {
if (responseBegun_ && !responseSent_) {
writer_.end();
if (responsePartial_)
ws->AddFragment(true, writer_.str());
else
ws->Send(writer_.str());
responseBegun_ = false;
responseSent_ = true;
responsePartial_ = false;
}
return responseSent_;
}
void DebuggerRequest::Flush() {
ws->AddFragment(false, writer_.flush());
responsePartial_ = true;
}
static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) {
if (TryParse(str, out))
return true;
// Now let's try signed (the above parses only positive.)
if (str[0] == '-' && TryParse(&str[1], out)) {
*out = static_cast<uint32_t>(-static_cast<int>(*out));
return true;
}
// We have to try float last because we use float bits, so 1.0 != 1.
if (allowFloat) {
union {
uint32_t u;
float f;
} bits;
if (TryParse(str, &bits.f)) {
*out = bits.u;
return true;
}
if (!strcasecmp(str, "nan")) {
*out = 0x7FC00000;
return true;
} else if (!strcasecmp(str, "infinity") || !strcasecmp(str, "inf")) {
*out = 0x7F800000;
return true;
} else if (!strcasecmp(str, "-infinity") || !strcasecmp(str, "-inf")) {
*out = 0xFF800000;
return true;
}
}
return false;
}
bool DebuggerRequest::HasParam(const char *name, bool ignoreNull) {
const JsonNode *node = data.get(name);
if (!node) {
return false;
}
if (node->value.getTag() == JSON_NULL) {
return !ignoreNull;
}
return true;
}
bool DebuggerRequest::ParamU32(const char *name, uint32_t *out, bool allowFloatBits, DebuggerParamType type) {
bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE;
bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE;
const JsonNode *node = data.get(name);
if (!node) {
if (required)
Fail(StringFromFormat("Missing '%s' parameter", name));
return !required;
}
auto tag = node->value.getTag();
if (tag == JSON_NUMBER) {
double val = node->value.toNumber();
bool isInteger = trunc(val) == val;
if (!isInteger && !allowLoose) {
// JSON doesn't give a great way to differentiate ints and floats.
// Let's play it safe and require a string.
if (allowFloatBits)
Fail(StringFromFormat("Could not parse '%s' parameter: use a string for non integer values", name));
else
Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name));
return false;
} else if (!isInteger && allowFloatBits) {
union {
float f;
uint32_t u;
} bits = { (float)val };
*out = bits.u;
return true;
}
if (val < 0 && val >= std::numeric_limits<int32_t>::min()) {
// Convert to unsigned representation.
*out = (uint32_t)(int32_t)val;
return true;
} else if (val >= 0 && val <= std::numeric_limits<uint32_t>::max()) {
*out = (uint32_t)val;
return true;
} else if (allowLoose) {
*out = val >= 0 ? std::numeric_limits<uint32_t>::max() : std::numeric_limits<uint32_t>::min();
return true;
}
if (allowFloatBits)
Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name));
else
Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range", name));
return false;
}
if (tag != JSON_STRING) {
if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) {
Fail(StringFromFormat("Invalid '%s' parameter type", name));
return false;
}
return true;
}
if (U32FromString(node->value.toString(), out, allowFloatBits))
return true;
if (allowFloatBits)
Fail(StringFromFormat("Could not parse '%s' parameter: number expected", name));
else
Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name));
return false;
}
bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType type) {
bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE;
bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE;
const JsonNode *node = data.get(name);
if (!node) {
if (required)
Fail(StringFromFormat("Missing '%s' parameter", name));
return !required;
}
auto tag = node->value.getTag();
if (tag == JSON_NUMBER) {
double val = node->value.toNumber();
if (val == 1.0 || val == 0.0 || allowLoose) {
*out = val != 0.0;
return true;
}
Fail(StringFromFormat("Could not parse '%s' parameter: should be true/1 or false/0", name));
return false;
}
if (tag == JSON_TRUE) {
*out = true;
return true;
}
if (tag == JSON_FALSE) {
*out = false;
return true;
}
if (tag != JSON_STRING) {
if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) {
Fail(StringFromFormat("Invalid '%s' parameter type", name));
return false;
}
return true;
}
const std::string s = node->value.toString();
if (s == "1" || s == "true") {
*out = true;
return true;
}
if (s == "0" || s == "false" || (s == "" && allowLoose)) {
*out = false;
return true;
}
if (allowLoose) {
*out = true;
return true;
}
Fail(StringFromFormat("Could not parse '%s' parameter: boolean required", name));
return false;
}
bool DebuggerRequest::ParamString(const char *name, std::string *out, DebuggerParamType type) {
bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE;
bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE;
const JsonNode *node = data.get(name);
if (!node) {
if (required)
Fail(StringFromFormat("Missing '%s' parameter", name));
return !required;
}
auto tag = node->value.getTag();
if (tag == JSON_STRING) {
*out = node->value.toString();
return true;
} else if (!allowLoose) {
if (required || tag != JSON_NULL) {
Fail(StringFromFormat("Invalid '%s' parameter type", name));
return false;
}
return true;
}
// For loose, let's allow a few things.
if (tag == JSON_TRUE) {
*out = "true";
return true;
} else if (tag == JSON_FALSE) {
*out = "false";
return true;
} else if (tag == JSON_NULL) {
if (required) {
*out = "";
}
return true;
} else if (tag == JSON_NUMBER) {
// Will have a decimal place, though.
*out = StringFromFormat("%f", node->value.toNumber());
return true;
}
Fail(StringFromFormat("Invalid '%s' parameter type", name));
return false;
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <cassert>
#include <string>
#include "json/json_reader.h"
#include "json/json_writer.h"
#include "net/websocket_server.h"
#include "Common/Log.h"
static inline void DebuggerJsonAddTicket(JsonWriter &writer, const JsonGet &data) {
const JsonNode *value = data.get("ticket");
if (value)
writer.writeRaw("ticket", json_stringify(value));
}
struct DebuggerErrorEvent {
DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l, const JsonGet data = JsonValue(JSON_NULL))
: message(m), level(l) {
// Need to format right away, before it's out of scope.
if (data) {
const JsonNode *value = data.get("ticket");
if (value)
ticketRaw = json_stringify(value);
}
}
std::string message;
LogTypes::LOG_LEVELS level;
std::string ticketRaw;
operator std::string() {
JsonWriter j;
j.begin();
j.writeString("event", "error");
j.writeString("message", message);
j.writeInt("level", level);
if (!ticketRaw.empty()) {
j.writeRaw("ticket", ticketRaw);
}
j.end();
return j.str();
}
};
enum class DebuggerParamType {
REQUIRED,
OPTIONAL,
REQUIRED_LOOSE,
OPTIONAL_LOOSE,
};
struct DebuggerRequest {
DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d)
: name(n), ws(w), data(d) {
}
const char *name;
net::WebSocketServer *ws;
const JsonGet data;
void Fail(const std::string &message) {
ws->Send(DebuggerErrorEvent(message, LogTypes::LERROR, data));
responseSent_ = true;
}
bool HasParam(const char *name, bool ignoreNull = false);
bool ParamU32(const char *name, uint32_t *out, bool allowFloatBits = false, DebuggerParamType type = DebuggerParamType::REQUIRED);
bool ParamBool(const char *name, bool *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
JsonWriter &Respond();
void Flush();
bool Finish();
private:
JsonWriter writer_;
bool responseBegun_ = false;
bool responseSent_ = false;
bool responsePartial_ = false;
};
typedef std::function<void(DebuggerRequest &req)> DebuggerEventHandler;
typedef std::unordered_map<std::string, DebuggerEventHandler> DebuggerEventHandlerMap;
+29 -35
View File
@@ -1019,14 +1019,14 @@ void PSPSaveDialog::ExecuteIOAction() {
std::lock_guard<std::mutex> guard(paramLock);
switch (display) {
case DS_LOAD_LOADING:
if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave)) {
if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave) == 0) {
display = DS_LOAD_DONE;
} else {
display = DS_LOAD_FAILED;
}
break;
case DS_SAVE_SAVING:
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName())) {
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName()) == 0) {
display = DS_SAVE_DONE;
} else {
display = DS_SAVE_FAILED;
@@ -1052,87 +1052,81 @@ void PSPSaveDialog::ExecuteIOAction() {
}
void PSPSaveDialog::ExecuteNotVisibleIOAction() {
auto &result = param.GetPspParam()->common.result;
switch ((SceUtilitySavedataType)(u32)param.GetPspParam()->mode) {
case SCE_UTILITY_SAVEDATA_TYPE_LOAD: // Only load and exit
case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:
if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave)) {
param.GetPspParam()->common.result = 0;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
}
result = param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave);
break;
case SCE_UTILITY_SAVEDATA_TYPE_SAVE: // Only save and exit
case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName())) {
param.GetPspParam()->common.result = 0;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;
}
result = param.Save(param.GetPspParam(), GetSelectedSaveDirName());
break;
case SCE_UTILITY_SAVEDATA_TYPE_SIZES:
param.GetPspParam()->common.result = param.GetSizes(param.GetPspParam());
result = param.GetSizes(param.GetPspParam());
break;
case SCE_UTILITY_SAVEDATA_TYPE_LIST:
param.GetList(param.GetPspParam());
param.GetPspParam()->common.result = 0;
result = 0;
break;
case SCE_UTILITY_SAVEDATA_TYPE_FILES:
param.GetPspParam()->common.result = param.GetFilesList(param.GetPspParam());
result = param.GetFilesList(param.GetPspParam());
break;
case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE:
{
bool result = param.GetSize(param.GetPspParam());
bool sizeResult = param.GetSize(param.GetPspParam());
// TODO: According to JPCSP, should test/verify this part but seems edge casey.
if (MemoryStick_State() != PSP_MEMORYSTICK_STATE_INSERTED) {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK;
} else if (result) {
param.GetPspParam()->common.result = 0;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK;
} else if (sizeResult) {
result = 0;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
}
}
break;
case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:
DEBUG_LOG(SCEUTILITY, "sceUtilitySavedata DELETEDATA: %s", param.GetPspParam()->saveName);
param.GetPspParam()->common.result = param.DeleteData(param.GetPspParam());
result = param.DeleteData(param.GetPspParam());
break;
//case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:
case SCE_UTILITY_SAVEDATA_TYPE_SINGLEDELETE:
if (param.Delete(param.GetPspParam(), param.GetSelectedSave())) {
param.GetPspParam()->common.result = 0;
result = 0;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA;
result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA;
}
break;
// TODO: Should reset the directory's other files.
case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:
case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE)) {
param.GetPspParam()->common.result = 0;
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE) == 0) {
result = 0;
} else if (MemoryStick_FreeSpace() == 0) {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
}
break;
case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:
case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE)) {
param.GetPspParam()->common.result = 0;
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE) == 0) {
result = 0;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
}
break;
case SCE_UTILITY_SAVEDATA_TYPE_READDATA:
case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:
if (!param.IsSaveDirectoryExist(param.GetPspParam())){
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
} else if (!param.IsSfoFileExist(param.GetPspParam())) {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
} else if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave, param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE)) {
param.GetPspParam()->common.result = 0;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
} else if (param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave, param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE) == 0) {
result = 0;
} else {
param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;
result = SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;
}
break;
default:
+142 -84
View File
@@ -358,9 +358,19 @@ int SavedataParam::DeleteData(SceUtilitySavedataParam* param) {
return 0;
}
bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode) {
int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode) {
if (!param) {
return false;
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;
}
if (param->secureVersion > 3) {
ERROR_LOG_REPORT(SCEUTILITY, "Savedata version requested on save: %d", param->secureVersion);
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;
} else if (param->secureVersion != 0) {
if (param->secureVersion != 1 && !HasKey(param)) {
ERROR_LOG_REPORT(SCEUTILITY, "Savedata version with missing key on save: %d", param->secureVersion);
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;
}
WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested on save: %d", param->secureVersion);
}
std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
@@ -390,8 +400,13 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save
memcpy(cryptedData, data_, cryptedSize);
int decryptMode = DetermineCryptMode(param);
if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (HasKey(param) ? param->key : 0)) != 0)
{
bool hasKey = decryptMode > 1;
if (hasKey && !HasKey(param)) {
delete[] cryptedData;
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_PARAM;
}
if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (hasKey ? param->key : 0)) != 0) {
I18NCategory *err = GetI18NCategory("Error");
host->NotifyUserMessage(err->T("Save encryption failed. This save won't work on real PSP"), 6.0f);
ERROR_LOG(SCEUTILITY,"Save encryption failed. This save won't work on real PSP");
@@ -420,39 +435,31 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save
sfoFile.SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64);
// For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding
if (secureMode)
{
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
if (secureMode) {
const int FILE_LIST_COUNT_MAX = 99;
const u32 FILE_LIST_TOTAL_SIZE = FILE_LIST_ITEM_SIZE * FILE_LIST_COUNT_MAX;
const u32 FILE_LIST_TOTAL_SIZE = sizeof(SaveSFOFileListEntry) * FILE_LIST_COUNT_MAX;
u32 tmpDataSize = 0;
u8 *tmpDataOrig = sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
u8 *tmpData = new u8[FILE_LIST_TOTAL_SIZE];
SaveSFOFileListEntry *tmpDataOrig = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
SaveSFOFileListEntry *updatedList = new SaveSFOFileListEntry[FILE_LIST_COUNT_MAX];
if (tmpDataSize != 0)
memcpy(updatedList, tmpDataOrig, std::min(tmpDataSize, FILE_LIST_TOTAL_SIZE));
if (tmpDataSize < FILE_LIST_TOTAL_SIZE)
memset(updatedList + tmpDataSize, 0, FILE_LIST_TOTAL_SIZE - tmpDataSize);
if (tmpDataOrig != NULL)
memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize);
else
memset(tmpData, 0, FILE_LIST_TOTAL_SIZE);
if (param->dataBuf.IsValid()) {
const std::string saveFilename = GetFileName(param);
for (auto entry = updatedList; entry < updatedList + FILE_LIST_COUNT_MAX; ++entry) {
if (entry->filename[0] != '\0') {
if (strncmp(entry->filename, saveFilename.c_str(), sizeof(entry->filename)) != 0)
continue;
}
if (param->dataBuf.IsValid())
{
char *fName = (char*)tmpData;
for(int i = 0; i < FILE_LIST_COUNT_MAX; i++)
{
if(fName[0] == 0)
break; // End of list
if(strncmp(fName,GetFileName(param).c_str(),20) == 0)
break;
fName += FILE_LIST_ITEM_SIZE;
snprintf(entry->filename, sizeof(entry->filename), "%s", saveFilename.c_str());
memcpy(entry->hash, cryptedHash, 16);
}
if (fName + 13 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
snprintf(fName, 13, "%s",GetFileName(param).c_str());
if (fName + 13 + 16 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
memcpy(fName+13, cryptedHash, 16);
}
sfoFile.SetValue("SAVEDATA_FILE_LIST", tmpData, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
delete[] tmpData;
sfoFile.SetValue("SAVEDATA_FILE_LIST", (u8 *)updatedList, FILE_LIST_TOTAL_SIZE, (int)FILE_LIST_TOTAL_SIZE);
delete[] updatedList;
}
// Init param with 0. This will be used to detect crypted save or not on loading
@@ -502,11 +509,8 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save
if (!WritePSPFile(filePath, data_, saveSize))
{
ERROR_LOG(SCEUTILITY,"Error writing file %s",filePath.c_str());
if(cryptedData != 0)
{
delete[] cryptedData;
}
return false;
delete[] cryptedData;
return SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE;
}
delete[] cryptedData;
}
@@ -538,23 +542,23 @@ bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &save
WritePSPFile(snd0path, param->snd0FileData.buf, param->snd0FileData.bufSize);
}
return true;
return 0;
}
bool SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode)
{
int SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode) {
if (!param) {
return false;
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
}
std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
std::string filePath = dirPath + "/" + GetFileName(param);
if (!pspFileSystem.GetFileInfo(filePath).exists) {
return false;
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
}
if(!LoadSaveData(param, saveDirName, dirPath, secureMode)) // Load main savedata
return false;
int result = LoadSaveData(param, saveDirName, dirPath, secureMode);
if (result != 0)
return result;
LoadSFO(param, dirPath); // Load sfo
@@ -572,19 +576,30 @@ bool SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &save
// Load SND0.AT3
LoadFile(dirPath, SND0_FILENAME, &param->snd0FileData);
return true;
return 0;
}
bool SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode) {
int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string &dirPath, bool secureMode) {
if (param->secureVersion > 3) {
ERROR_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion);
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
} else if (param->secureVersion != 0) {
if (param->secureVersion != 1 && !HasKey(param)) {
ERROR_LOG_REPORT(SCEUTILITY, "Savedata version with missing key: %d", param->secureVersion);
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
}
WARN_LOG_REPORT(SCEUTILITY, "Savedata version requested: %d", param->secureVersion);
}
u8 *data_ = param->dataBuf;
std::string filePath = dirPath+"/"+GetFileName(param);
std::string filename = GetFileName(param);
std::string filePath = dirPath + "/" + filename;
s64 readSize;
INFO_LOG(SCEUTILITY,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str());
u8* saveData = 0;
u8 *saveData = nullptr;
int saveSize = -1;
if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize)) {
ERROR_LOG(SCEUTILITY,"Error reading file %s",filePath.c_str());
return false;
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
}
saveSize = (int)readSize;
@@ -595,7 +610,13 @@ bool SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::stri
bool isCrypted = prevCryptMode != 0 && secureMode;
bool saveDone = false;
if (isCrypted) {
LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, saveDone);
if (DetermineCryptMode(param) > 1 && !HasKey(param))
return SCE_UTILITY_SAVEDATA_ERROR_LOAD_PARAM;
u8 hash[16];
bool hasExpectedHash = GetExpectedHash(dirPath, filename, hash);
LoadCryptedSave(param, data_, saveData, saveSize, prevCryptMode, hasExpectedHash ? hash : nullptr, saveDone);
// TODO: Should return SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN here if !saveDone.
}
if (!saveDone) {
LoadNotCryptedSave(param, data_, saveData, saveSize);
@@ -603,36 +624,43 @@ bool SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::stri
param->dataSize = (SceSize)saveSize;
delete[] saveData;
return true;
return 0;
}
int SavedataParam::DetermineCryptMode(const SceUtilitySavedataParam *param) const {
int decryptMode = 1;
if (HasKey(param)) {
if (param->secureVersion == 1) {
decryptMode = 1;
} else if (param->secureVersion == 2) {
decryptMode = 3;
} else if (param->secureVersion == 3) {
decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 1;
} else if (HasKey(param)) {
// TODO: This should ignore HasKey(), which would trigger errors. Not doing that yet to play it safe.
decryptMode = GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 3;
}
return decryptMode;
}
void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone) {
void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone) {
int align_len = align16(saveSize);
u8 *data_base = new u8[align_len];
u8 *cryptKey = new u8[0x10];
memset(cryptKey, 0, 0x10);
bool hasKey = HasKey(param);
int decryptMode = DetermineCryptMode(param);
const int detectedMode = decryptMode;
bool hasKey = decryptMode > 1;
if (hasKey) {
memcpy(cryptKey, param->key, 0x10);
}
memset(data_base + saveSize, 0, align_len - saveSize);
memcpy(data_base, saveData, saveSize);
int decryptMode = DetermineCryptMode(param);
if (decryptMode != prevCryptMode) {
if (prevCryptMode == 1 && param->key[0] == 0) {
// Backwards compat for a bug we used to have.
WARN_LOG(SCEUTILITY, "Savedata loading with hashmode %d instead of detected %d", prevCryptMode, decryptMode);
hasKey = false;
decryptMode = prevCryptMode;
// Don't notify the user if we're not going to upgrade the save.
@@ -654,9 +682,22 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8
host->NotifyUserMessage(di->T("Old savedata detected"), 6.0f);
}
}
hasKey = decryptMode > 1;
}
if (DecryptSave(decryptMode, data_base, &saveSize, &align_len, (hasKey?cryptKey:0)) == 0) {
int err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
// Perhaps the file had the wrong mode....
if (err != 0 && detectedMode != decryptMode) {
hasKey = detectedMode > 1;
err = DecryptSave(detectedMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, expectedHash);
}
// TODO: Should return an error, but let's just try with a bad hash.
if (err != 0 && expectedHash != nullptr) {
WARN_LOG(SCEUTILITY, "Incorrect hash on save data, likely corrupt");
err = DecryptSave(decryptMode, data_base, &saveSize, &align_len, hasKey ? cryptKey : nullptr, nullptr);
}
if (err == 0) {
if (param->dataBuf.IsValid())
memcpy(data, data_base, std::min((u32)saveSize, (u32)param->dataBufSize));
saveDone = true;
@@ -689,39 +730,54 @@ void SavedataParam::LoadSFO(SceUtilitySavedataParam *param, const std::string& d
}
}
std::set<std::string> SavedataParam::getSecureFileNames(std::string dirPath) {
PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME);
std::set<std::string> secureFileNames;
if (!sfoFileInfo.exists)
return secureFileNames;
std::vector<SaveSFOFileListEntry> SavedataParam::GetSFOEntries(const std::string &dirPath) {
std::vector<SaveSFOFileListEntry> result;
const std::string sfoPath = dirPath + "/" + SFO_FILENAME;
if (!pspFileSystem.GetFileInfo(sfoPath).exists)
return result;
ParamSFOData sfoFile;
std::vector<u8> sfoData;
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0) {
if (pspFileSystem.ReadEntireFile(dirPath + "/" + SFO_FILENAME, sfoData) >= 0)
sfoFile.ReadSFO(sfoData);
const int FILE_LIST_COUNT_MAX = 99;
u32 sfoFileListSize = 0;
SaveSFOFileListEntry *sfoFileList = (SaveSFOFileListEntry *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const u32 count = std::min((u32)FILE_LIST_COUNT_MAX, sfoFileListSize / (u32)sizeof(SaveSFOFileListEntry));
for (u32 i = 0; i < count; ++i) {
if (sfoFileList[i].filename[0] != '\0')
result.push_back(sfoFileList[i]);
}
u32 sfoFileListSize = 0;
char *sfoFileList = (char *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
const u32 FILE_LIST_COUNT_MAX = 99;
return result;
}
// Filenames are 13 bytes long at most. Add a NULL so there's no surprises.
char temp[14];
temp[13] = '\0';
std::set<std::string> SavedataParam::GetSecureFileNames(const std::string &dirPath) {
auto entries = GetSFOEntries(dirPath);
for (u32 i = 0; i < FILE_LIST_COUNT_MAX; ++i) {
// Ends at a NULL filename.
if (i * FILE_LIST_ITEM_SIZE >= sfoFileListSize || sfoFileList[i * FILE_LIST_ITEM_SIZE] == '\0') {
break;
}
strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13);
std::set<std::string> secureFileNames;
for (auto entry : entries) {
char temp[14];
truncate_cpy(temp, entry.filename);
secureFileNames.insert(temp);
}
return secureFileNames;
}
bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]) {
auto entries = GetSFOEntries(dirPath);
for (auto entry : entries) {
if (strncmp(entry.filename, filename.c_str(), sizeof(entry.filename)) == 0) {
memcpy(hash, entry.hash, sizeof(entry.hash));
return true;
}
}
return false;
}
void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) {
std::string filePath = dirPath + "/" + filename;
s64 readSize = -1;
@@ -784,13 +840,7 @@ int SavedataParam::EncryptData(unsigned int mode,
return 0;
}
int SavedataParam::DecryptSave(unsigned int mode,
unsigned char *data,
int *dataLen,
int *alignedLen,
unsigned char *cryptkey)
{
int SavedataParam::DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash) {
pspChnnlsvContext1 ctx1;
pspChnnlsvContext2 ctx2;
@@ -820,6 +870,14 @@ int SavedataParam::DecryptSave(unsigned int mode,
if (sceChnnlsv_21BE78B4_(ctx2) < 0)
return -7;
if (expectedHash) {
u8 hash[16];
if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0)
return -7;
if (memcmp(hash, expectedHash, sizeof(hash)) != 0)
return -8;
}
/* The decrypted data starts at data + 0x10, so shift it back. */
memmove(data, data + 0x10, *dataLen);
return 0;
@@ -1122,7 +1180,7 @@ int SavedataParam::GetFilesList(SceUtilitySavedataParam *param)
std::set<std::string> secureFilenames;
if (sfoFileInfo.exists) {
secureFilenames = getSecureFileNames(dirPath);
secureFilenames = GetSecureFileNames(dirPath);
} else {
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
}
+15 -7
View File
@@ -253,7 +253,7 @@ struct SceUtilitySavedataParam
u8 key[16];
s32_le secureVersion;
u32_le secureVersion;
s32_le multiStatus;
// Function 11 LIST
@@ -293,6 +293,12 @@ struct SaveFileInfo
void DoState(PointerWrap &p);
};
struct SaveSFOFileListEntry {
char filename[13];
u8 hash[16];
u8 pad[3];
};
class SavedataParam
{
public:
@@ -306,8 +312,8 @@ public:
std::string GetSaveDir(const SceUtilitySavedataParam *param, const std::string &saveDirName) const;
bool Delete(SceUtilitySavedataParam* param, int saveId = -1);
int DeleteData(SceUtilitySavedataParam* param);
bool Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode = true);
bool Load(SceUtilitySavedataParam* param, const std::string &saveDirName, int saveId = -1, bool secureMode = true);
int Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode = true);
int Load(SceUtilitySavedataParam* param, const std::string &saveDirName, int saveId = -1, bool secureMode = true);
int GetSizes(SceUtilitySavedataParam* param);
bool GetList(SceUtilitySavedataParam* param);
int GetFilesList(SceUtilitySavedataParam* param);
@@ -354,19 +360,21 @@ private:
void SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, std::string saveName);
void ClearFileInfo(SaveFileInfo &saveInfo, const std::string &saveName);
bool LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode);
void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, bool &saveDone);
int LoadSaveData(SceUtilitySavedataParam *param, const std::string &saveDirName, const std::string& dirPath, bool secureMode);
void LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize, int prevCryptMode, const u8 *expectedHash, bool &saveDone);
void LoadNotCryptedSave(SceUtilitySavedataParam *param, u8 *data, u8 *saveData, int &saveSize);
void LoadSFO(SceUtilitySavedataParam *param, const std::string& dirPath);
void LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData);
int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey);
int DecryptSave(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *cryptkey, const u8 *expectedHash);
int EncryptData(unsigned int mode, unsigned char *data, int *dataLen, int *alignedLen, unsigned char *hash, unsigned char *cryptkey);
int UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode);
int BuildHash(unsigned char *output, unsigned char *data, unsigned int len, unsigned int alignedLen, int mode, unsigned char *cryptkey);
int DetermineCryptMode(const SceUtilitySavedataParam *param) const;
std::set<std::string> getSecureFileNames(std::string dirPath);
std::vector<SaveSFOFileListEntry> GetSFOEntries(const std::string &dirPath);
std::set<std::string> GetSecureFileNames(const std::string &dirPath);
bool GetExpectedHash(const std::string &dirPath, const std::string &filename, u8 hash[16]);
SceUtilitySavedataParam* pspParam;
int selectedSave;
+1 -1
View File
@@ -18,7 +18,7 @@
#include "Core/MemMap.h"
#include "Core/Reporting.h"
#include "Core/MIPS/MIPSTables.h"
#include "ElfReader.h"
#include "Core/ELF/ElfReader.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/HLE/sceKernelMemory.h"
+8 -2
View File
@@ -181,8 +181,10 @@ bool DirectoryFileHandle::Open(std::string &basePath, std::string &fileName, Fil
#if HOST_IS_CASE_SENSITIVE
if (access & (FILEACCESS_APPEND|FILEACCESS_CREATE|FILEACCESS_WRITE)) {
DEBUG_LOG(FILESYS, "Checking case for path %s", fileName.c_str());
if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST) )
if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
return false; // or go on and attempt (for a better error code than just 0?)
}
}
// else we try fopen first (in case we're lucky) before simulating case insensitivity
#endif
@@ -250,6 +252,8 @@ bool DirectoryFileHandle::Open(std::string &basePath, std::string &fileName, Fil
I18NCategory *err = GetI18NCategory("Error");
host->NotifyUserMessage(err->T("Disk full while writing data"));
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;
} else if (!success) {
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
}
}
#else
@@ -310,6 +314,8 @@ bool DirectoryFileHandle::Open(std::string &basePath, std::string &fileName, Fil
I18NCategory *err = GetI18NCategory("Error");
host->NotifyUserMessage(err->T("Disk full while writing data"));
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;
} else {
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
}
#endif
@@ -597,7 +603,7 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const
u32 err = 0;
bool success = entry.hFile.Open(basePath, filename, access, err);
if (err == 0 && !success) {
err = -1;
err = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
}
err = ReplayApplyDisk(ReplayAction::FILE_OPEN, err, CoreTiming::GetGlobalTimeUs());
+1 -1
View File
@@ -265,7 +265,7 @@ void hleCheckCurrentCallbacks()
void hleReSchedule(const char *reason)
{
#ifdef _DEBUG
_dbg_assert_msg_(HLE, reason != 0 && strlen(reason) < 256, "hleReSchedule: Invalid or too long reason.");
_dbg_assert_msg_(HLE, reason != nullptr && strlen(reason) < 256, "hleReSchedule: Invalid or too long reason.");
#endif
hleAfterSyscall |= HLE_AFTER_RESCHED;
+90
View File
@@ -0,0 +1,90 @@
// Copyright (c) 2018- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cstdio>
#include "Core/HLE/sceKernelThread.h"
#include "Core/MIPS/MIPSDebugInterface.h"
class KernelThreadDebugInterface : public MIPSDebugInterface {
public:
KernelThreadDebugInterface(MIPSState *c, ThreadContext &t) : MIPSDebugInterface(c), ctx(t) {
}
unsigned int getPC() override { return ctx.pc; }
void setPC(unsigned int address) override { ctx.pc = address; }
u32 GetGPR32Value(int reg) override { return ctx.r[reg]; }
u32 GetPC() override { return ctx.pc; }
u32 GetLR() override { return ctx.r[MIPS_REG_RA]; }
void SetPC(u32 _pc) override { ctx.pc = _pc; }
void PrintRegValue(int cat, int index, char *out) override {
switch (cat) {
case 0: sprintf(out, "%08X", ctx.r[index]); break;
case 1: sprintf(out, "%f", ctx.f[index]); break;
case 2: sprintf(out, "N/A"); break;
}
}
u32 GetHi() override {
return ctx.hi;
}
u32 GetLo() override {
return ctx.lo;
}
void SetHi(u32 val) override {
ctx.hi = val;
}
void SetLo(u32 val) override {
ctx.lo = val;
}
u32 GetRegValue(int cat, int index) override {
switch (cat) {
case 0: return ctx.r[index];
case 1: return ctx.fi[index];
case 2: return ctx.vi[voffset[index]];
default: return 0;
}
}
void SetRegValue(int cat, int index, u32 value) override {
switch (cat) {
case 0:
if (index != 0)
ctx.r[index] = value;
break;
case 1:
ctx.fi[index] = value;
break;
case 2:
ctx.vi[voffset[index]] = value;
break;
default:
break;
}
}
protected:
ThreadContext &ctx;
};
+13 -16
View File
@@ -152,10 +152,10 @@ static int Replace_memcpy() {
}
}
RETURN(destPtr);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc);
CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc);
#endif
return 10 + bytes / 4; // approximation
}
@@ -194,10 +194,10 @@ static int Replace_memcpy_jak() {
currentMIPS->r[MIPS_REG_A2] = 0;
currentMIPS->r[MIPS_REG_A3] = destPtr + bytes;
RETURN(destPtr);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc);
CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc);
#endif
return 5 + bytes * 8 + 2; // approximation. This is a slow memcpy - a byte copy loop..
}
@@ -222,10 +222,10 @@ static int Replace_memcpy16() {
}
}
RETURN(destPtr);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc);
CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc);
#endif
return 10 + bytes / 4; // approximation
}
@@ -260,10 +260,10 @@ static int Replace_memcpy_swizzled() {
}
RETURN(0);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(srcPtr, false, pitch * h, currentMIPS->pc);
CBreakPoints::ExecMemCheck(destPtr, true, pitch * h, currentMIPS->pc);
#endif
return 10 + (pitch * h) / 4; // approximation
}
@@ -288,10 +288,10 @@ static int Replace_memmove() {
}
}
RETURN(destPtr);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(srcPtr, false, bytes, currentMIPS->pc);
CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc);
#endif
return 10 + bytes / 4; // approximation
}
@@ -310,9 +310,9 @@ static int Replace_memset() {
}
}
RETURN(destPtr);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc);
#endif
return 10 + bytes / 4; // approximation
}
@@ -342,9 +342,8 @@ static int Replace_memset_jak() {
currentMIPS->r[MIPS_REG_A3] = -1;
RETURN(destPtr);
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(destPtr, true, bytes, currentMIPS->pc);
#endif
return 5 + bytes * 6 + 2; // approximation (hm, inspecting the disasm this should be 5 + 6 * bytes + 2, but this is what works..)
}
@@ -591,11 +590,9 @@ static int Replace_dl_write_matrix() {
#endif
}
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(PARAM(2), false, count * sizeof(float), currentMIPS->pc);
CBreakPoints::ExecMemCheck(PARAM(0) + 2 * sizeof(u32), true, sizeof(u32), currentMIPS->pc);
CBreakPoints::ExecMemCheck(dlStruct[2], true, (count + 1) * sizeof(u32), currentMIPS->pc);
#endif
dlStruct[2] += (1 + count) * 4;
RETURN(dlStruct[2]);
+1
View File
@@ -160,6 +160,7 @@ void __AudioDoState(PointerWrap &p) {
p.Do(mixFrequency);
// TODO: This never happens because maxVer=1.
if (s >= 2) {
resampler.DoState(p);
} else {
+6 -4
View File
@@ -503,18 +503,20 @@ static int sceCtrlPeekBufferNegative(u32 ctrlDataPtr, u32 nBufs)
return done;
}
void __CtrlWriteUserLatch(CtrlLatch *userLatch) {
static void __CtrlWriteUserLatch(CtrlLatch *userLatch, int bufs) {
*userLatch = latch;
userLatch->btnBreak &= CTRL_MASK_USER;
userLatch->btnMake &= CTRL_MASK_USER;
userLatch->btnPress &= CTRL_MASK_USER;
userLatch->btnRelease &= CTRL_MASK_USER;
if (bufs > 0) {
userLatch->btnRelease |= CTRL_MASK_USER;
}
}
static u32 sceCtrlPeekLatch(u32 latchDataPtr) {
auto userLatch = PSPPointer<CtrlLatch>::Create(latchDataPtr);
if (userLatch.IsValid()) {
__CtrlWriteUserLatch(userLatch);
__CtrlWriteUserLatch(userLatch, ctrlLatchBufs);
}
return hleLogSuccessI(SCECTRL, ctrlLatchBufs);
}
@@ -522,7 +524,7 @@ static u32 sceCtrlPeekLatch(u32 latchDataPtr) {
static u32 sceCtrlReadLatch(u32 latchDataPtr) {
auto userLatch = PSPPointer<CtrlLatch>::Create(latchDataPtr);
if (userLatch.IsValid()) {
__CtrlWriteUserLatch(userLatch);
__CtrlWriteUserLatch(userLatch, ctrlLatchBufs);
}
return hleLogSuccessI(SCECTRL, __CtrlResetLatch());
}
+23 -15
View File
@@ -476,17 +476,17 @@ void __DisplayGetDebugStats(char *stats, size_t bufsize) {
statbuf);
}
enum {
FPS_LIMIT_NORMAL = 0,
FPS_LIMIT_CUSTOM = 1,
};
void __DisplaySetWasPaused() {
wasPaused = true;
}
static bool FrameTimingThrottled() {
if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit == 0) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 == 0) {
return false;
}
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 == 0) {
return false;
}
return !PSP_CoreParameter().unthrottle;
@@ -505,7 +505,7 @@ static void DoFrameDropLogging(float scaledTimestep) {
// Let's collect all the throttling and frameskipping logic here.
static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
PROFILE_THIS_SCOPE("timing");
int fpsLimiter = PSP_CoreParameter().fpsLimit;
FPSLimit fpsLimiter = PSP_CoreParameter().fpsLimit;
throttle = FrameTimingThrottled();
skipFrame = false;
@@ -528,8 +528,10 @@ static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
time_update();
float scaledTimestep = timestep;
if (fpsLimiter == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit != 0) {
scaledTimestep *= 60.0f / g_Config.iFpsLimit;
if (fpsLimiter == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 0) {
scaledTimestep *= 60.0f / g_Config.iFpsLimit1;
} else if (fpsLimiter == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 0) {
scaledTimestep *= 60.0f / g_Config.iFpsLimit2;
}
if (lastFrameTime == 0.0 || wasPaused) {
@@ -549,7 +551,8 @@ static void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
// Auto-frameskip automatically if speed limit is set differently than the default.
bool useAutoFrameskip = g_Config.bAutoFrameSkip && g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
if (g_Config.bAutoFrameSkip || (g_Config.iFrameSkip == 0 && fpsLimiter == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit > 60)) {
bool forceFrameskip = (fpsLimiter == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 60) || (fpsLimiter == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 60);
if (g_Config.bAutoFrameSkip || forceFrameskip) {
// autoframeskip
// Argh, we are falling behind! Let's skip a frame and see if we catch up.
if (curFrameTime > nextFrameTime && doFrameSkip) {
@@ -601,9 +604,11 @@ static void DoFrameIdleTiming() {
}
float scaledVblank = timePerVblank;
if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 0) {
// 0 is handled in FrameTimingThrottled().
scaledVblank *= 60.0f / g_Config.iFpsLimit;
scaledVblank *= 60.0f / g_Config.iFpsLimit1;
} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 0) {
scaledVblank *= 60.0f / g_Config.iFpsLimit2;
}
// If we have over at least a vblank of spare time, maintain at least 30fps in delay.
@@ -695,7 +700,7 @@ void __DisplayFlip(int cyclesLate) {
// Let the user know if we're running slow, so they know to adjust settings.
// Sometimes users just think the sound emulation is broken.
static bool hasNotifiedSlow = false;
if (!g_Config.bHideSlowWarnings && !hasNotifiedSlow && PSP_CoreParameter().fpsLimit == FPS_LIMIT_NORMAL && IsRunningSlow()) {
if (!g_Config.bHideSlowWarnings && !hasNotifiedSlow && PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && IsRunningSlow()) {
#ifndef _DEBUG
I18NCategory *err = GetI18NCategory("Error");
if (g_Config.bSoftwareRendering) {
@@ -787,9 +792,11 @@ void hleLagSync(u64 userdata, int cyclesLate) {
}
float scale = 1.0f;
if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM) {
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit1 > 0) {
// 0 is handled in FrameTimingThrottled().
scale = 60.0f / g_Config.iFpsLimit;
scale = 60.0f / g_Config.iFpsLimit1;
} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 > 0) {
scale = 60.0f / g_Config.iFpsLimit2;
}
const double goal = lastLagSync + (scale / 1000.0f);
@@ -937,7 +944,8 @@ u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync)
__DisplaySetFramebuf(topaddr, linesize, pixelformat, sync);
if (delayCycles > 0) {
// No delaying while inside an interrupt. It'll cause idle threads to starve.
if (delayCycles > 0 && !__IsInInterrupt()) {
// Okay, the game is going at too high a frame rate. God of War and Fat Princess both do this.
// Simply eating the cycles works and is fast, but breaks other games (like Jeanne d'Arc.)
// So, instead, we delay this HLE thread only (a small deviation from correct behavior.)
+16 -15
View File
@@ -24,6 +24,7 @@
#include "Core/Core.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/MemMapHelpers.h"
@@ -62,7 +63,6 @@ extern "C" {
// For headless screenshots.
#include "Core/HLE/sceDisplay.h"
static const int ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002;
static const int ERROR_ERRNO_IO_ERROR = 0x80010005;
static const int ERROR_MEMSTICK_DEVCTL_BAD_PARAMS = 0x80220081;
static const int ERROR_MEMSTICK_DEVCTL_TOO_MANY_CALLBACKS = 0x80220082;
@@ -497,10 +497,12 @@ static void __IoManagerThread() {
}
}
static void __IoWakeManager() {
static void __IoWakeManager(CoreLifecycle stage) {
// Ping the thread so that it knows to check coreState.
ioManagerThreadEnabled = false;
ioManager.FinishEventLoop();
if (stage == CoreLifecycle::STOPPING) {
ioManagerThreadEnabled = false;
ioManager.FinishEventLoop();
}
}
static void __IoVblank() {
@@ -587,7 +589,7 @@ void __IoInit() {
ioManagerThreadEnabled = g_Config.bSeparateIOThread;
ioManager.SetThreadEnabled(ioManagerThreadEnabled);
if (ioManagerThreadEnabled) {
Core_ListenShutdown(&__IoWakeManager);
Core_ListenLifecycle(&__IoWakeManager);
ioManagerThread = new std::thread(&__IoManagerThread);
ioManagerThread->detach();
}
@@ -805,7 +807,7 @@ static u32 sceIoGetstat(const char *filename, u32 addr) {
}
} else {
DEBUG_LOG(SCEIO, "sceIoGetstat(%s, %08x) : FILE NOT FOUND", filename, addr);
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "io getstat", usec);
return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "io getstat", usec);
}
}
@@ -1383,7 +1385,7 @@ static u32 sceIoOpen(const char *filename, int flags, int mode) {
else
{
ERROR_LOG(SCEIO, "ERROR_ERRNO_FILE_NOT_FOUND=sceIoOpen(%s, %08x, %08x) - file not found", filename, flags, mode);
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file opened", 10000);
return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file opened", 10000);
}
}
@@ -1412,7 +1414,7 @@ static u32 sceIoRemove(const char *filename) {
// TODO: This timing isn't necessarily accurate, low end for now.
if(!pspFileSystem.GetFileInfo(filename).exists)
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file removed", 100);
return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file removed", 100);
pspFileSystem.RemoveFile(filename);
return hleDelayResult(0, "file removed", 100);
@@ -1433,7 +1435,7 @@ static u32 sceIoRmdir(const char *dirname) {
if (pspFileSystem.RmDir(dirname))
return hleDelayResult(0, "rmdir", 1000);
else
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "rmdir", 1000);
return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "rmdir", 1000);
}
static u32 sceIoSync(const char *devicename, int flag) {
@@ -1855,7 +1857,7 @@ static u32 sceIoRename(const char *from, const char *to) {
// TODO: Timing isn't terribly accurate.
if (!pspFileSystem.GetFileInfo(from).exists)
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file renamed", 1000);
return hleDelayResult(SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file renamed", 1000);
int result = pspFileSystem.RenameFile(from, to);
if (result < 0)
@@ -1933,7 +1935,7 @@ static u32 sceIoOpenAsync(const char *filename, int flags, int mode)
f = new FileNode();
f->handle = kernelObjects.Create(f);
f->fullpath = filename;
f->asyncResult = error == 0 ? ERROR_ERRNO_FILE_NOT_FOUND : error;
f->asyncResult = error == 0 ? SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND : error;
f->closePending = true;
fd = __IoAllocFd(f);
@@ -2087,7 +2089,7 @@ static u32 sceIoPollAsync(int id, u32 address) {
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->pendingAsyncResult) {
DEBUG_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address);
VERBOSE_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address);
return 1;
} else if (f->hasAsyncResult) {
DEBUG_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x)", f->asyncResult, id, address);
@@ -2142,9 +2144,8 @@ public:
static u32 sceIoDopen(const char *path) {
DEBUG_LOG(SCEIO, "sceIoDopen(\"%s\")", path);
if(!pspFileSystem.GetFileInfo(path).exists)
{
return ERROR_ERRNO_FILE_NOT_FOUND;
if (!pspFileSystem.GetFileInfo(path).exists) {
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
}
DirListing *dir = new DirListing();
+1 -1
View File
@@ -313,7 +313,7 @@ u32 sceKernelDevkitVersion()
int revision = firmwareVersion % 10;
int devkitVersion = (major << 24) | (minor << 16) | (revision << 8) | 0x10;
DEBUG_LOG_REPORT_ONCE(devkitVer, SCEKERNEL, "%08x=sceKernelDevkitVersion()", devkitVersion);
DEBUG_LOG(SCEKERNEL, "%08x=sceKernelDevkitVersion()", devkitVersion);
return devkitVersion;
}
+1
View File
@@ -44,6 +44,7 @@ enum {
SCE_KERNEL_ERROR_BAD_FILE = 0x80000209,
SCE_KERNEL_ERROR_ACCESS_ERROR = 0x8000020D,
SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002,
SCE_KERNEL_ERROR_ERRNO_ARG_LIST_TOO_LONG = 0x80010007,
SCE_KERNEL_ERROR_ERRNO_INVALID_FILE_DESCRIPTOR = 0x80010009,
SCE_KERNEL_ERROR_ERRNO_RESOURCE_UNAVAILABLE = 0x8001000B,
+3 -2
View File
@@ -317,6 +317,7 @@ void InterruptState::restore()
void InterruptState::clear()
{
savedCpu.reset();
}
// http://forums.ps2dev.org/viewtopic.php?t=5687
@@ -654,10 +655,10 @@ static u32 sceKernelMemcpy(u32 dst, u32 src, u32 size)
*dstp++ = *srcp++;
}
}
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(src, false, size, currentMIPS->pc);
CBreakPoints::ExecMemCheck(dst, true, size, currentMIPS->pc);
#endif
return dst;
}
+1 -2
View File
@@ -1811,8 +1811,7 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
s64 size = (s64)info.size;
if (!info.exists) {
const int ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002;
const u32 error = hleLogError(LOADER, ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist");
const u32 error = hleLogError(LOADER, SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist");
return hleDelayResult(error, "module loaded", 500);
}
+43 -19
View File
@@ -15,10 +15,11 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <set>
#include <map>
#include <queue>
#include <algorithm>
#include <map>
#include <mutex>
#include <set>
#include <queue>
#include "base/logging.h"
@@ -29,6 +30,7 @@
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/CoreTiming.h"
#include "Core/MemMapHelpers.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
@@ -42,6 +44,7 @@
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/KernelThreadDebugInterface.h"
#include "Core/HLE/KernelWaitHelpers.h"
#include "Core/HLE/ThreadQueueList.h"
@@ -494,7 +497,7 @@ public:
return true;
}
Thread()
Thread() : debug(currentMIPS, context)
{
currentStack.start = 0;
}
@@ -542,7 +545,7 @@ public:
p.Do(currentMipscallId);
p.Do(currentCallbackId);
// TODO: How do I "version" adding a DoState method to ThreadContext?
// TODO: If we want to "version" a DoState method here, we can just use minVer = 0.
p.Do(context);
if (s <= 3)
@@ -587,6 +590,7 @@ public:
SceUID currentCallbackId;
ThreadContext context;
KernelThreadDebugInterface debug;
std::vector<SceUID> callbacks;
@@ -645,6 +649,8 @@ static std::vector<SceUID> pendingDeleteThreads;
// Lists all thread ids that aren't deleted/etc.
std::vector<SceUID> threadqueue;
// Only for debugger, so not needed to read, just write.
std::mutex threadqueueLock;
// Lists only ready thread ids.
ThreadQueueList threadReadyQueue;
@@ -1152,8 +1158,9 @@ void __KernelIdle()
__KernelReSchedule("idle");
}
void __KernelThreadingShutdown()
{
void __KernelThreadingShutdown() {
std::lock_guard<std::mutex> guard(threadqueueLock);
kernelMemory.Free(threadReturnHackAddr);
threadqueue.clear();
threadReadyQueue.clear();
@@ -1588,8 +1595,9 @@ void __KernelCancelThreadEndTimeout(SceUID threadID)
CoreTiming::UnscheduleEvent(eventThreadEndTimeout, threadID);
}
static void __KernelRemoveFromThreadQueue(SceUID threadID)
{
static void __KernelRemoveFromThreadQueue(SceUID threadID) {
std::lock_guard<std::mutex> guard(threadqueueLock);
int prio = __KernelGetThreadPrio(threadID);
if (prio != 0)
threadReadyQueue.remove(prio, threadID);
@@ -1817,6 +1825,8 @@ void ThreadContext::reset()
fcr31 = 0x00000e00;
hi = 0xDEADBEEF;
lo = 0xDEADBEEF;
// Just for a clean state.
other[5] = 0;
}
void __KernelResetThread(Thread *t, int lowestPriority)
@@ -1848,8 +1858,9 @@ void __KernelResetThread(Thread *t, int lowestPriority)
ERROR_LOG_REPORT(SCEKERNEL, "Resetting thread with threads waiting on end?");
}
Thread *__KernelCreateThread(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr)
{
Thread *__KernelCreateThread(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr) {
std::lock_guard<std::mutex> guard(threadqueueLock);
Thread *t = new Thread;
id = kernelObjects.Create(t);
@@ -3511,19 +3522,18 @@ void __KernelRegisterWaitTypeFuncs(WaitType type, WaitBeginCallbackFunc beginFun
waitTypeFuncs[type].endFunc = endFunc;
}
std::vector<DebugThreadInfo> GetThreadsInfo()
{
std::vector<DebugThreadInfo> GetThreadsInfo() {
std::lock_guard<std::mutex> guard(threadqueueLock);
std::vector<DebugThreadInfo> threadList;
u32 error;
for (auto iter = threadqueue.begin(); iter != threadqueue.end(); ++iter)
{
Thread *t = kernelObjects.Get<Thread>(*iter, error);
for (const auto uid : threadqueue) {
Thread *t = kernelObjects.Get<Thread>(uid, error);
if (!t)
continue;
DebugThreadInfo info;
info.id = *iter;
info.id = uid;
strncpy(info.name,t->GetName(),KERNELOBJECT_MAX_NAME_LENGTH);
info.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0;
info.status = t->nt.status;
@@ -3532,17 +3542,31 @@ std::vector<DebugThreadInfo> GetThreadsInfo()
info.stackSize = (u32)t->nt.stackSize;
info.priority = t->nt.currentPriority;
info.waitType = (WaitType)(u32)t->nt.waitType;
if(*iter == currentThread)
info.isCurrent = uid == currentThread;
if (info.isCurrent)
info.curPC = currentMIPS->pc;
else
info.curPC = t->context.pc;
info.isCurrent = (*iter == currentThread);
threadList.push_back(info);
}
return threadList;
}
DebugInterface *KernelDebugThread(SceUID threadID) {
if (threadID == currentThread) {
return currentDebugMIPS;
}
u32 error;
Thread *t = kernelObjects.Get<Thread>(threadID, error);
if (t) {
return &t->debug;
}
return nullptr;
}
void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus)
{
u32 error;
+2
View File
@@ -27,6 +27,7 @@
// http://code.google.com/p/jpcsp/source/browse/trunk/src/jpcsp/HLE/modules150/ThreadManForUser.java
class Thread;
class DebugInterface;
int sceKernelChangeThreadPriority(SceUID threadID, int priority);
SceUID __KernelCreateThreadInternal(const char *threadName, SceUID moduleID, u32 entry, u32 prio, int stacksize, u32 attr);
@@ -316,6 +317,7 @@ struct DebugThreadInfo
};
std::vector<DebugThreadInfo> GetThreadsInfo();
DebugInterface *KernelDebugThread(SceUID threadID);
void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus);
int LoadExecForUser_362A956B();
+2 -1
View File
@@ -342,7 +342,8 @@ static u32 sceUmdGetDriveStat()
}
//u32 retVal = PSP_UMD_INITED | PSP_UMD_READY | PSP_UMD_PRESENT;
u32 retVal = __KernelUmdGetState();
DEBUG_LOG(SCEIO,"0x%02x=sceUmdGetDriveStat()", retVal);
// This one can be very spammy.
VERBOSE_LOG(SCEIO,"0x%02x=sceUmdGetDriveStat()", retVal);
return retVal;
}
+10 -12
View File
@@ -477,6 +477,14 @@ bool MediaEngine::setVideoStream(int streamNum, bool force) {
return false;
}
AVCodecContext *m_pCodecCtx = m_pFormatCtx->streams[streamNum]->codec;
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57,33,100)
AVCodecParameters *m_pCodecPar = m_pFormatCtx->streams[streamNum]->codecpar;
// Update from deprecated public codec context
if (avcodec_parameters_from_context(m_pCodecPar, m_pCodecCtx) < 0) {
return false;
}
#endif
// Find the decoder for the video stream
AVCodec *pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id);
@@ -788,9 +796,8 @@ int MediaEngine::writeVideoImage(u32 bufferPtr, int frameWidth, int videoPixelMo
delete [] imgbuf;
}
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(bufferPtr, true, videoImageSize, currentMIPS->pc);
#endif
return videoImageSize;
#endif // USE_FFMPEG
return 0;
@@ -844,9 +851,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid
writeVideoLineRGBA(imgbuf, data, width);
data += m_desWidth * sizeof(u32);
imgbuf += videoLineSize;
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u32), true, width * sizeof(u32), currentMIPS->pc);
#endif
}
break;
@@ -856,9 +861,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid
writeVideoLineABGR5650(imgbuf, data, width);
data += m_desWidth * sizeof(u16);
imgbuf += videoLineSize;
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc);
#endif
}
break;
@@ -868,9 +871,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid
writeVideoLineABGR5551(imgbuf, data, width);
data += m_desWidth * sizeof(u16);
imgbuf += videoLineSize;
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc);
#endif
}
break;
@@ -880,9 +881,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid
writeVideoLineABGR4444(imgbuf, data, width);
data += m_desWidth * sizeof(u16);
imgbuf += videoLineSize;
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(bufferPtr + y * frameWidth * sizeof(u16), true, width * sizeof(u16), currentMIPS->pc);
#endif
}
break;
@@ -976,9 +975,8 @@ int MediaEngine::getAudioSamples(u32 bufferPtr) {
if (!m_audioContext->Decode(audioFrame, frameSize, buffer, &outbytes)) {
ERROR_LOG(ME, "Audio (%s) decode failed during video playback", GetCodecName(m_audioType));
}
#ifndef MOBILE_DEVICE
CBreakPoints::ExecMemCheck(bufferPtr, true, outbytes, currentMIPS->pc);
#endif
}
return 0x2000;
+25 -8
View File
@@ -65,6 +65,11 @@ void VagDecoder::Start(u32 data, u32 vagSize, bool loopEnabled) {
}
void VagDecoder::DecodeBlock(u8 *&read_pointer) {
if (curBlock_ == numBlocks_ - 1) {
end_ = true;
return;
}
u8 *readp = read_pointer;
int predict_nr = *readp++;
int shift_factor = predict_nr & 0xf;
@@ -106,9 +111,6 @@ void VagDecoder::DecodeBlock(u8 *&read_pointer) {
s_2 = s2;
curSample = 0;
curBlock_++;
if (curBlock_ == numBlocks_) {
end_ = true;
}
read_pointer = readp;
}
@@ -508,16 +510,31 @@ void SasInstance::MixVoice(SasVoice &voice) {
ERROR_LOG(SCESAS, "Too many samples to read (%d)! This shouldn't happen.", samplesToRead);
samplesToRead = ARRAY_SIZE(mixTemp_) - 2;
}
voice.ReadSamples(&mixTemp_[2], samplesToRead);
int tempPos = 2 + samplesToRead;
int readPos = 2;
if (voice.envelope.NeedsKeyOn()) {
readPos = 0;
samplesToRead += 2;
}
voice.ReadSamples(&mixTemp_[readPos], samplesToRead);
int tempPos = readPos + samplesToRead;
int volumeShift = (12 + MAX_CONFIG_VOLUME - g_Config.iSASVolume);
if (volumeShift < 0) volumeShift = 0;
for (int i = 0; i < delay; ++i) {
// Walk the curve. This means we'll reach ATTACK already, likely.
// This matches the results of tests (but maybe we can just remove the STATE_KEYON_STEP hack.)
voice.envelope.Step();
}
const bool needsInterp = voicePitch != PSP_SAS_PITCH_BASE || (sampleFrac & PSP_SAS_PITCH_MASK) != 0;
for (int i = delay; i < grainSize; i++) {
const int16_t *s = mixTemp_ + (sampleFrac >> PSP_SAS_PITCH_BASE_SHIFT);
// Linear interpolation. Good enough. Need to make resampleHist bigger if we want more.
int f = sampleFrac & PSP_SAS_PITCH_MASK;
int sample = (s[0] * (PSP_SAS_PITCH_MASK - f) + s[1] * f) >> PSP_SAS_PITCH_BASE_SHIFT;
int sample = s[0];
if (needsInterp) {
int f = sampleFrac & PSP_SAS_PITCH_MASK;
sample = (s[0] * (PSP_SAS_PITCH_MASK - f) + s[1] * f) >> PSP_SAS_PITCH_BASE_SHIFT;
}
sampleFrac += voicePitch;
// The maximum envelope height (PSP_SAS_ENVELOPE_HEIGHT_MAX) is (1 << 30) - 1.
@@ -542,7 +559,7 @@ void SasInstance::MixVoice(SasVoice &voice) {
voice.resampleHist[0] = mixTemp_[tempPos - 2];
voice.resampleHist[1] = mixTemp_[tempPos - 1];
voice.sampleFrac = sampleFrac - (tempPos - 2) * PSP_SAS_PITCH_BASE;;
voice.sampleFrac = sampleFrac - (tempPos - 2) * PSP_SAS_PITCH_BASE;
if (voice.HaveSamplesEnded())
voice.envelope.End();
+11 -11
View File
@@ -111,21 +111,21 @@ public:
private:
s16 samples[28];
int curSample;
int curSample = 0;
u32 data_;
u32 read_;
int curBlock_;
int loopStartBlock_;
int numBlocks_;
u32 data_ = 0;
u32 read_ = 0;
int curBlock_ = -1;
int loopStartBlock_ = -1;
int numBlocks_ = 0;
// rolling state. start at 0, should probably reset to 0 on loops?
int s_1;
int s_2;
int s_1 = 0;
int s_2 = 0;
bool loopEnabled_;
bool loopAtNextBlock_;
bool end_;
bool loopEnabled_ = false;
bool loopAtNextBlock_ = false;
bool end_ = false;
};
class SasAtrac3 {
+1
View File
@@ -37,6 +37,7 @@
#include "Common/MathUtil.h"
#include "Common/Atomics.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/HW/StereoResampler.h"
#include "Core/HLE/__sceAudio.h"
#include "Core/Util/AudioFormat.h" // for clamp_u8
-2
View File
@@ -50,8 +50,6 @@ public:
virtual void SaveSymbolMap() {}
virtual void SetWindowTitle(const char *message) {}
virtual void SendCoreWait(bool) {}
// While debugging is active, it's perfectly fine for these to block.
virtual bool GPUDebuggingActive() { return false; }
virtual void GPUNotifyCommand(u32 pc) {}
+1
View File
@@ -110,6 +110,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader);
class FileLoaderFactory {
public:
virtual ~FileLoaderFactory() {}
virtual FileLoader *ConstructFileLoader(const std::string &filename) = 0;
};
void RegisterFileLoaderFactory(std::string name, std::unique_ptr<FileLoaderFactory> factory);
-1
View File
@@ -238,7 +238,6 @@ void ArmJit::GenerateFixedCode() {
B_CC(CC_EQ, outerLoop);
SetJumpTarget(badCoreState);
breakpointBailout = GetCodePtr();
SaveDowncount();
RestoreRoundingMode(true);
+1
View File
@@ -93,6 +93,7 @@ extern int logBlocks;
void ArmJit::Comp_FPULS(MIPSOpcode op)
{
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
s32 offset = (s16)(op & 0xFFFF);
int ft = _FT;
+3
View File
@@ -112,6 +112,7 @@ namespace MIPSComp
void ArmJit::Comp_ITypeMemLR(MIPSOpcode op, bool load) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int offset = (signed short)(op & 0xFFFF);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
@@ -120,6 +121,7 @@ namespace MIPSComp
if (!js.inDelaySlot) {
// Optimisation: Combine to single unaligned load/store
bool isLeft = (o == 34 || o == 42);
CheckMemoryBreakpoint(1);
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Find a matching shift in opposite direction with opposite offset.
if (nextOp == (isLeft ? (op.encoding + (4<<26) - 3)
@@ -259,6 +261,7 @@ namespace MIPSComp
void ArmJit::Comp_ITypeMem(MIPSOpcode op)
{
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int offset = (signed short)(op&0xFFFF);
bool load = false;
MIPSGPReg rt = _RT;
+2
View File
@@ -226,6 +226,7 @@ namespace MIPSComp
void ArmJit::Comp_SV(MIPSOpcode op) {
NEON_IF_AVAILABLE(CompNEON_SV);
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
s32 offset = (signed short)(op & 0xFFFC);
int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5);
@@ -332,6 +333,7 @@ namespace MIPSComp
{
NEON_IF_AVAILABLE(CompNEON_SVQ);
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int imm = (signed short)(op&0xFFFC);
int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5);
+2
View File
@@ -159,6 +159,7 @@ void ArmJit::CompNEON_VecDo3(MIPSOpcode op) {
void ArmJit::CompNEON_SV(MIPSOpcode op) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
// Remember to use single lane stores here and not VLDR/VSTR - switching usage
// between NEON and VFPU can be expensive on some chips.
@@ -276,6 +277,7 @@ inline int MIPS_GET_VQVT(u32 op) {
void ArmJit::CompNEON_SVQ(MIPSOpcode op) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int offset = (signed short)(op & 0xFFFC);
int vt = MIPS_GET_VQVT(op.encoding);
+111 -4
View File
@@ -26,6 +26,7 @@
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
@@ -45,7 +46,7 @@
using namespace ArmJitConstants;
void DisassembleArm(const u8 *data, int size) {
static void DisassembleArm(const u8 *data, int size) {
char temp[256];
for (int i = 0; i < size; i += 4) {
const u32 *codePtr = (const u32 *)(data + i);
@@ -69,6 +70,33 @@ void DisassembleArm(const u8 *data, int size) {
}
}
static u32 JitBreakpoint() {
// Should we skip this breakpoint?
if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc)
return 0;
BreakAction result = CBreakPoints::ExecBreakPoint(currentMIPS->pc);
if ((result & BREAK_ACTION_PAUSE) == 0)
return 0;
return 1;
}
static u32 JitMemCheck(u32 pc) {
if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc)
return 0;
// Note: pc may be the delay slot.
const auto op = Memory::Read_Instruction(pc, true);
s32 offset = (s16)(op & 0xFFFF);
if (MIPSGetInfo(op) & IS_VFPU)
offset &= 0xFFFC;
u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset;
CBreakPoints::ExecOpMemCheck(addr, pc);
return coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME ? 0 : 1;
}
namespace MIPSComp
{
using namespace ArmGen;
@@ -86,6 +114,10 @@ ArmJit::ArmJit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), fpr(m
INFO_LOG(JIT, "ARM JIT initialized: %d MB of code space", GetSpaceLeft() / (1024 * 1024));
js.startDefaultPrefix = mips_->HasDefaultPrefix();
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
CBreakPoints::SetSkipFirst(0);
}
ArmJit::~ArmJit() {
@@ -104,6 +136,10 @@ void ArmJit::DoState(PointerWrap &p)
} else {
js.hasSetRounding = 1;
}
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
CBreakPoints::SetSkipFirst(0);
}
void ArmJit::UpdateFCR31() {
@@ -158,13 +194,16 @@ void ArmJit::EatInstruction(MIPSOpcode op) {
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, JIT, "Ate an instruction inside a delay slot.");
}
CheckJitBreakpoint(GetCompilerPC() + 4, 0);
js.numInstructions++;
js.compilerPC += 4;
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
}
void ArmJit::CompileDelaySlot(int flags)
{
void ArmJit::CompileDelaySlot(int flags) {
// Need to offset the downcount which was already incremented for the branch + delay slot.
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
// preserve flag around the delay slot! Maybe this is not always necessary on ARM where
// we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the
// delay slot, we're screwed.
@@ -295,6 +334,9 @@ const u8 *ArmJit::DoJit(u32 em_address, JitBlock *b)
while (js.compiling)
{
gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages
// Jit breakpoints are quite fast, so let's do them in release too.
CheckJitBreakpoint(GetCompilerPC(), 0);
MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());
//MIPSInfo info = MIPSGetInfo(inst);
//if (info & IS_VFPU) {
@@ -492,7 +534,18 @@ void ArmJit::Comp_ReplacementFunc(MIPSOpcode op)
return;
}
if (entry->flags & REPFLAG_DISABLED) {
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
// We don't need to disable hooks, the code will still run.
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
// Any breakpoint at the func entry was already tripped, so we can still run the replacement.
// That's a common case - just to see how often the replacement hits.
disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
}
}
if (disabled) {
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
} else if (entry->jitReplaceFunc) {
MIPSReplaceFunc repl = entry->jitReplaceFunc;
@@ -686,6 +739,60 @@ void ArmJit::WriteSyscallExit()
B((const void *)dispatcherCheckCoreState);
}
bool ArmJit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
if (CBreakPoints::IsAddressBreakPoint(addr)) {
MRS(R8);
FlushAll();
MOVI2R(SCRATCHREG1, GetCompilerPC());
MovToPC(SCRATCHREG1);
RestoreRoundingMode();
QuickCallFunction(SCRATCHREG1, &JitBreakpoint);
// If 0, the conditional breakpoint wasn't taken.
CMPI2R(R0, 0, SCRATCHREG2);
FixupBranch skip = B_CC(CC_EQ);
WriteDownCount(downcountOffset);
ApplyRoundingMode();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(true, false, R8);
return true;
}
return false;
}
bool ArmJit::CheckMemoryBreakpoint(int instructionOffset) {
if (CBreakPoints::HasMemChecks()) {
int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
MRS(R8);
FlushAll();
RestoreRoundingMode();
MOVI2R(R0, GetCompilerPC());
MovToPC(R0);
if (off != 0)
ADDI2R(R0, R0, off, SCRATCHREG2);
QuickCallFunction(SCRATCHREG2, &JitMemCheck);
// If 0, the breakpoint wasn't tripped.
CMPI2R(R0, 0, SCRATCHREG2);
FixupBranch skip = B_CC(CC_EQ);
WriteDownCount(-1 - off);
ApplyRoundingMode();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(true, false, R8);
return true;
}
return false;
}
void ArmJit::Comp_DoNothing(MIPSOpcode op) { }
MIPSOpcode ArmJit::GetOriginalOp(MIPSOpcode op) {
+2 -2
View File
@@ -215,6 +215,8 @@ private:
void WriteExit(u32 destination, int exit_num);
void WriteExitDestInR(ArmGen::ARMReg Reg);
void WriteSyscallExit();
bool CheckJitBreakpoint(u32 addr, int downcountOffset);
bool CheckMemoryBreakpoint(int instructionOffset = 0);
// Utility compilation functions
void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely);
@@ -311,8 +313,6 @@ public:
const u8 *restoreRoundingMode;
const u8 *applyRoundingMode;
const u8 *breakpointBailout;
};
} // namespace MIPSComp
-1
View File
@@ -275,7 +275,6 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) {
B(CC_EQ, outerLoop);
SetJumpTarget(badCoreState);
breakpointBailout = GetCodePtr();
SaveStaticRegisters();
RestoreRoundingMode(true);
+1 -1
View File
@@ -81,6 +81,7 @@ void Arm64Jit::Comp_FPU3op(MIPSOpcode op) {
void Arm64Jit::Comp_FPULS(MIPSOpcode op)
{
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
// Surprisingly, these work fine alraedy.
@@ -88,7 +89,6 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op)
int ft = _FT;
MIPSGPReg rs = _RS;
// u32 addr = R(rs) + offset;
// logBlocks = 1;
std::vector<FixupBranch> skips;
switch (op >> 26) {
case 49: //FI(ft) = Memory::Read_U32(addr); break; //lwc1
+3
View File
@@ -111,6 +111,7 @@ namespace MIPSComp {
void Arm64Jit::Comp_ITypeMemLR(MIPSOpcode op, bool load) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int offset = (signed short)(op & 0xFFFF);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
@@ -119,6 +120,7 @@ namespace MIPSComp {
if (!js.inDelaySlot) {
// Optimisation: Combine to single unaligned load/store
bool isLeft = (o == 34 || o == 42);
CheckMemoryBreakpoint(1);
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Find a matching shift in opposite direction with opposite offset.
if (nextOp == (isLeft ? (op.encoding + (4 << 26) - 3) : (op.encoding - (4 << 26) + 3))) {
@@ -255,6 +257,7 @@ namespace MIPSComp {
void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int offset = (signed short)(op & 0xFFFF);
bool load = false;
+2
View File
@@ -202,6 +202,7 @@ namespace MIPSComp {
void Arm64Jit::Comp_SV(MIPSOpcode op) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
s32 offset = (signed short)(op & 0xFFFC);
int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5);
@@ -275,6 +276,7 @@ namespace MIPSComp {
void Arm64Jit::Comp_SVQ(MIPSOpcode op) {
CONDITIONAL_DISABLE;
CheckMemoryBreakpoint();
int imm = (signed short)(op&0xFFFC);
int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5);
+110 -3
View File
@@ -28,6 +28,7 @@
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
@@ -44,7 +45,7 @@
using namespace Arm64JitConstants;
void DisassembleArm64Print(const u8 *data, int size) {
static void DisassembleArm64Print(const u8 *data, int size) {
std::vector<std::string> lines = DisassembleArm64(data, size);
for (auto s : lines) {
ILOG("%s", s.c_str());
@@ -60,6 +61,33 @@ void DisassembleArm64Print(const u8 *data, int size) {
ILOG("===");*/
}
static u32 JitBreakpoint() {
// Should we skip this breakpoint?
if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc)
return 0;
BreakAction result = CBreakPoints::ExecBreakPoint(currentMIPS->pc);
if ((result & BREAK_ACTION_PAUSE) == 0)
return 0;
return 1;
}
static u32 JitMemCheck(u32 pc) {
if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc)
return 0;
// Note: pc may be the delay slot.
const auto op = Memory::Read_Instruction(pc, true);
s32 offset = (s16)(op & 0xFFFF);
if (MIPSGetInfo(op) & IS_VFPU)
offset &= 0xFFFC;
u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset;
CBreakPoints::ExecOpMemCheck(addr, pc);
return coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME ? 0 : 1;
}
namespace MIPSComp
{
using namespace Arm64Gen;
@@ -80,6 +108,10 @@ Arm64Jit::Arm64Jit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), f
GenerateFixedCode(jo);
js.startDefaultPrefix = mips_->HasDefaultPrefix();
js.currentRoundingFunc = convertS0ToSCRATCH1[mips_->fcr31 & 3];
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
CBreakPoints::SetSkipFirst(0);
}
Arm64Jit::~Arm64Jit() {
@@ -99,6 +131,10 @@ void Arm64Jit::DoState(PointerWrap &p) {
}
// Note: we can't update the currentRoundingFunc here because fcr31 wasn't loaded yet.
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
CBreakPoints::SetSkipFirst(0);
}
void Arm64Jit::UpdateFCR31() {
@@ -151,12 +187,16 @@ void Arm64Jit::EatInstruction(MIPSOpcode op) {
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, JIT, "Ate an instruction inside a delay slot.");
}
CheckJitBreakpoint(GetCompilerPC() + 4, 0);
js.numInstructions++;
js.compilerPC += 4;
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
}
void Arm64Jit::CompileDelaySlot(int flags) {
// Need to offset the downcount which was already incremented for the branch + delay slot.
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
// preserve flag around the delay slot! Maybe this is not always necessary on ARM where
// we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the
// delay slot, we're screwed.
@@ -285,8 +325,10 @@ const u8 *Arm64Jit::DoJit(u32 em_address, JitBlock *b) {
js.numInstructions = 0;
while (js.compiling) {
gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages
// Jit breakpoints are quite fast, so let's do them in release too.
CheckJitBreakpoint(GetCompilerPC(), 0);
MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
MIPSCompileOp(inst, this);
@@ -473,7 +515,18 @@ void Arm64Jit::Comp_ReplacementFunc(MIPSOpcode op)
return;
}
if (entry->flags & REPFLAG_DISABLED) {
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
// We don't need to disable hooks, the code will still run.
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
// Any breakpoint at the func entry was already tripped, so we can still run the replacement.
// That's a common case - just to see how often the replacement hits.
disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
}
}
if (disabled) {
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
} else if (entry->jitReplaceFunc) {
MIPSReplaceFunc repl = entry->jitReplaceFunc;
@@ -646,6 +699,60 @@ void Arm64Jit::WriteSyscallExit() {
B((const void *)dispatcherCheckCoreState);
}
bool Arm64Jit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
if (CBreakPoints::IsAddressBreakPoint(addr)) {
MRS(FLAGTEMPREG, FIELD_NZCV);
FlushAll();
MOVI2R(SCRATCH1, GetCompilerPC());
MovToPC(SCRATCH1);
RestoreRoundingMode();
QuickCallFunction(SCRATCH1_64, &JitBreakpoint);
// If 0, the conditional breakpoint wasn't taken.
CMPI2R(W0, 0);
FixupBranch skip = B(CC_EQ);
WriteDownCount(downcountOffset);
ApplyRoundingMode();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(FIELD_NZCV, FLAGTEMPREG);
return true;
}
return false;
}
bool Arm64Jit::CheckMemoryBreakpoint(int instructionOffset) {
if (CBreakPoints::HasMemChecks()) {
int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
MRS(FLAGTEMPREG, FIELD_NZCV);
FlushAll();
RestoreRoundingMode();
MOVI2R(W0, GetCompilerPC());
MovToPC(W0);
if (off != 0)
ADDI2R(W0, W0, off * 4);
QuickCallFunction(SCRATCH2_64, &JitMemCheck);
// If 0, the breakpoint wasn't tripped.
CMPI2R(W0, 0);
FixupBranch skip = B(CC_EQ);
WriteDownCount(-1 - off);
ApplyRoundingMode();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(FIELD_NZCV, FLAGTEMPREG);
return true;
}
return false;
}
void Arm64Jit::Comp_DoNothing(MIPSOpcode op) { }
MIPSOpcode Arm64Jit::GetOriginalOp(MIPSOpcode op) {
+2 -2
View File
@@ -214,6 +214,8 @@ private:
void WriteExit(u32 destination, int exit_num);
void WriteExitDestInR(Arm64Gen::ARM64Reg Reg);
void WriteSyscallExit();
bool CheckJitBreakpoint(u32 addr, int downcountOffset);
bool CheckMemoryBreakpoint(int instructionOffset = 0);
// Utility compilation functions
void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely);
@@ -272,8 +274,6 @@ public:
const u8 *dispatcher;
const u8 *dispatcherNoCheck;
const u8 *breakpointBailout;
const u8 *saveStaticRegisters;
const u8 *loadStaticRegisters;
+1
View File
@@ -22,6 +22,7 @@
#include "Common.h"
#include "Common/ChunkFile.h"
#include "Core/ConfigValues.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSInt.h"
#include "Core/MIPS/MIPSTables.h"
-2
View File
@@ -181,8 +181,6 @@ public:
const u8 *dispatcherPCInR0;
const u8 *dispatcher;
const u8 *dispatcherNoCheck;
const u8 *breakpointBailout;
};
typedef void (MipsJit::*MIPSCompileFunc)(MIPSOpcode opcode);
+1
View File
@@ -1415,6 +1415,7 @@ skip:
memset(&info, 0, sizeof(info));
if (!Memory::IsValidAddress(address)) {
info.opcodeAddress = address;
return info;
}
+2 -17
View File
@@ -3,20 +3,13 @@
#endif
#include <cstdarg>
#include <cstring>
#include <memory>
#include <vector>
#include "Common/CommonTypes.h"
#if defined(_WIN32) || defined(__ANDROID__)
// Temporarily turned off on Android
#define USE_ARMIPS
#endif
#ifdef USE_ARMIPS
// This has to be before basictypes to avoid a define conflict.
#include "ext/armips/Core/Assembler.h"
#endif
#include "util/text/utf8.h"
#include "Core/Debugger/SymbolMap.h"
@@ -33,7 +26,6 @@ std::wstring GetAssembleError()
return errorText;
}
#ifdef USE_ARMIPS
class PspAssemblerFile: public AssemblerFile
{
public:
@@ -72,12 +64,9 @@ private:
u64 address;
std::wstring dummyWFilename_;
};
#endif
bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address)
{
#ifdef USE_ARMIPS
PspAssemblerFile file;
StringList errors;
wchar_t str[64];
@@ -87,7 +76,7 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address)
args.mode = ArmipsMode::MEMORY;
args.content = str + ConvertUTF8ToWString(line);
args.silent = true;
args.memoryFile = &file;
args.memoryFile.reset(new PspAssemblerFile());
args.errorsResult = &errors;
if (g_symbolMap) {
@@ -108,10 +97,6 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address)
}
return true;
#else
errorText = L"Unsupported platform";
return false;
#endif
}
} // namespace
+18 -1
View File
@@ -23,6 +23,7 @@
#include "Core/Debugger/DebugInterface.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/MIPS.h"
@@ -37,13 +38,16 @@ enum ReferenceIndexType {
REF_INDEX_VFPU = 0x4000,
REF_INDEX_VFPU_INT = 0x8000,
REF_INDEX_IS_FLOAT = REF_INDEX_FPU | REF_INDEX_VFPU,
REF_INDEX_HLE = 0x10000,
REF_INDEX_THREAD = REF_INDEX_HLE | 0,
REF_INDEX_MODULE = REF_INDEX_HLE | 1,
};
class MipsExpressionFunctions: public IExpressionFunctions
{
public:
MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { };
MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { }
bool parseReference(char* str, uint32_t& referenceIndex) override
{
@@ -106,6 +110,15 @@ public:
return true;
}
if (strcasecmp(str, "threadid") == 0) {
referenceIndex = REF_INDEX_THREAD;
return true;
}
if (strcasecmp(str, "moduleid") == 0) {
referenceIndex = REF_INDEX_MODULE;
return true;
}
return false;
}
@@ -124,6 +137,10 @@ public:
return cpu->GetHi();
if (referenceIndex == REF_INDEX_LO)
return cpu->GetLo();
if (referenceIndex == REF_INDEX_THREAD)
return __KernelGetCurThread();
if (referenceIndex == REF_INDEX_MODULE)
return __KernelGetCurThreadModuleId();
if ((referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)) < 32)
return cpu->GetRegValue(1, referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT));
if ((referenceIndex & ~(REF_INDEX_VFPU | REF_INDEX_VFPU_INT)) < 128)
+18 -33
View File
@@ -54,85 +54,70 @@ public:
u32 GetLR() override { return cpu->r[MIPS_REG_RA]; }
void SetPC(u32 _pc) override { cpu->pc = _pc; }
const char *GetCategoryName(int cat) override
{
const char *names[3] = {("GPR"),("FPU"),("VFPU")};
const char *GetCategoryName(int cat) override {
static const char *const names[3] = { "GPR", "FPU", "VFPU" };
return names[cat];
}
int GetNumCategories() override { return 3; }
int GetNumRegsInCategory(int cat) override
{
int r[3] = {32,32,32};
int GetNumRegsInCategory(int cat) override {
static int r[3] = { 32, 32, 128 };
return r[cat];
}
const char *GetRegName(int cat, int index) override;
void PrintRegValue(int cat, int index, char *out) override
{
switch (cat)
{
void PrintRegValue(int cat, int index, char *out) override {
switch (cat) {
case 0: sprintf(out, "%08X", cpu->r[index]); break;
case 1: sprintf(out, "%f", cpu->f[index]); break;
case 2: sprintf(out, "N/A"); break;
}
}
u32 GetHi() override
{
u32 GetHi() override {
return cpu->hi;
}
u32 GetLo() override
{
u32 GetLo() override {
return cpu->lo;
}
void SetHi(u32 val) override
{
void SetHi(u32 val) override {
cpu->hi = val;
}
void SetLo(u32 val) override
{
void SetLo(u32 val) override {
cpu->lo = val;
}
u32 GetRegValue(int cat, int index) override
{
u32 temp;
switch (cat)
{
u32 GetRegValue(int cat, int index) override {
switch (cat) {
case 0:
return cpu->r[index];
case 1:
memcpy(&temp, &cpu->f[index], 4);
return temp;
return cpu->fi[index];
case 2:
memcpy(&temp, &cpu->v[voffset[index]], 4);
return temp;
return cpu->vi[voffset[index]];
default:
return 0;
}
}
void SetRegValue(int cat, int index, u32 value) override
{
switch (cat)
{
void SetRegValue(int cat, int index, u32 value) override {
switch (cat) {
case 0:
if (index != 0)
cpu->r[index] = value;
break;
case 1:
memcpy(&cpu->f[index], &value, 4);
cpu->fi[index] = value;
break;
case 2:
memcpy(&cpu->v[voffset[index]], &value, 4);
cpu->vi[voffset[index]] = value;
break;
default:
+31 -31
View File
@@ -258,7 +258,7 @@ namespace MIPSDis
{
const char *name = MIPSGetName(op);
int vd = _VD;
MatrixSize sz = GetMtxSize(op);
MatrixSize sz = GetMtxSizeSafe(op);
sprintf(out, "%s%s\t%s",name,VSuff(op),MN(vd, sz));
}
void Dis_MatrixSet2(MIPSOpcode op, char *out)
@@ -266,7 +266,7 @@ namespace MIPSDis
const char *name = MIPSGetName(op);
int vd = _VD;
int vs = _VS;
MatrixSize sz = GetMtxSize(op);
MatrixSize sz = GetMtxSizeSafe(op);
sprintf(out, "%s%s\t%s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz));
}
void Dis_MatrixSet3(MIPSOpcode op, char *out)
@@ -275,7 +275,7 @@ namespace MIPSDis
int vd = _VD;
int vs = _VS;
int vt = _VT;
MatrixSize sz = GetMtxSize(op);
MatrixSize sz = GetMtxSizeSafe(op);
sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz),MN(vt,sz));
}
@@ -285,7 +285,7 @@ namespace MIPSDis
int vd = _VD;
int vs = _VS;
int vt = _VT;
MatrixSize sz = GetMtxSize(op);
MatrixSize sz = GetMtxSizeSafe(op);
// TODO: Xpose?
sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(Xpose(vs),sz),MN(vt,sz));
}
@@ -296,7 +296,7 @@ namespace MIPSDis
int vd = _VD;
int vs = _VS;
int vt = _VT;
MatrixSize sz = GetMtxSize(op);
MatrixSize sz = GetMtxSizeSafe(op);
sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), MN(vd, sz), MN(vs, sz), VN(vt, V_Single));
}
@@ -306,7 +306,7 @@ namespace MIPSDis
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz), VN(vt, sz));
}
@@ -316,8 +316,8 @@ namespace MIPSDis
int vs = _VS;
int vt = _VT;
int ins = (op>>23) & 7;
VectorSize sz = GetVecSize(op);
MatrixSize msz = GetMtxSize(op);
VectorSize sz = GetVecSizeSafe(op);
MatrixSize msz = GetMtxSizeSafe(op);
int n = GetNumVectorElements(sz);
if (n == ins)
@@ -346,7 +346,7 @@ namespace MIPSDis
int vt = _VT;
int vs = _VS;
int vd = _VS;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
if (sz != V_Triple)
{
sprintf(out, "vcrs\tERROR");
@@ -362,7 +362,7 @@ namespace MIPSDis
int vt = _VT;
int vs = _VS;
int cond = op&15;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
const char *condNames[16] = {"FL","EQ","LT","LE","TR","NE","GE","GT","EZ","EN","EI","ES","NZ","NN","NI","NS"};
sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), condNames[cond], VN(vs, sz), VN(vt,sz));
}
@@ -370,7 +370,7 @@ namespace MIPSDis
void Dis_Vcmov(MIPSOpcode op, char *out)
{
const char *name = MIPSGetName(op);
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int vd = _VD;
int vs = _VS;
int tf = (op >> 19)&3;
@@ -391,7 +391,7 @@ namespace MIPSDis
const char *name = MIPSGetName(op);
int vd = _VD;
int vs = _VS;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz));
}
@@ -401,7 +401,7 @@ namespace MIPSDis
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, V_Single));
}
@@ -409,7 +409,7 @@ namespace MIPSDis
{
const char *name = MIPSGetName(op);
int vd = _VD;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
sprintf(out, "%s%s\t%s",name,VSuff(op),VN(vd, sz));
}
void Dis_VectorSet2(MIPSOpcode op, char *out)
@@ -417,7 +417,7 @@ namespace MIPSDis
const char *name = MIPSGetName(op);
int vd = _VD;
int vs = _VS;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, sz),VN(vs, sz));
}
void Dis_VectorSet3(MIPSOpcode op, char *out)
@@ -426,7 +426,7 @@ namespace MIPSDis
int vd = _VD;
int vs = _VS;
int vt = _VT;
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, sz));
}
@@ -445,7 +445,7 @@ namespace MIPSDis
}
c[(imm>>2) & 3] = 'S';
c[imm&3] = 'C';
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int numElems = GetNumVectorElements(sz);
int pos = 0;
temp[pos++] = '[';
@@ -465,7 +465,7 @@ namespace MIPSDis
void Dis_CrossQuat(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
const char *name;
switch (sz)
{
@@ -490,7 +490,7 @@ namespace MIPSDis
void Dis_Vbfy(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int vd = _VD;
int vs = _VS;
const char *name = MIPSGetName(op);
@@ -499,7 +499,7 @@ namespace MIPSDis
void Dis_Vf2i(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int vd = _VD;
int vs = _VS;
int imm = (op>>16)&0x1f;
@@ -509,7 +509,7 @@ namespace MIPSDis
void Dis_Vs2i(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int vd = _VD;
int vs = _VS;
const char *name = MIPSGetName(op);
@@ -518,8 +518,8 @@ namespace MIPSDis
void Dis_Vi2x(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize dsz = GetHalfVectorSize(sz);
VectorSize sz = GetVecSizeSafe(op);
VectorSize dsz = GetHalfVectorSizeSafe(sz);
if (((op>>16)&3)==0)
dsz = V_Single;
@@ -531,7 +531,7 @@ namespace MIPSDis
void Dis_Vwbn(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int vd = _VD;
int vs = _VS;
@@ -542,8 +542,8 @@ namespace MIPSDis
void Dis_Vf2h(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize dsz = GetHalfVectorSize(sz);
VectorSize sz = GetVecSizeSafe(op);
VectorSize dsz = GetHalfVectorSizeSafe(sz);
if (((op>>16)&3)==0)
dsz = V_Single;
@@ -555,8 +555,8 @@ namespace MIPSDis
void Dis_Vh2f(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize dsz = GetDoubleVectorSize(sz);
VectorSize sz = GetVecSizeSafe(op);
VectorSize dsz = GetDoubleVectorSizeSafe(sz);
int vd = _VD;
int vs = _VS;
@@ -566,8 +566,8 @@ namespace MIPSDis
void Dis_ColorConv(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize dsz = GetHalfVectorSize(sz);
VectorSize sz = GetVecSizeSafe(op);
VectorSize dsz = GetHalfVectorSizeSafe(sz);
int vd = _VD;
int vs = _VS;
@@ -584,7 +584,7 @@ namespace MIPSDis
void Dis_VrndX(MIPSOpcode op, char *out)
{
VectorSize sz = GetVecSize(op);
VectorSize sz = GetVecSizeSafe(op);
int vd = _VD;
const char *name = MIPSGetName(op);
+76 -32
View File
@@ -303,89 +303,133 @@ int GetNumVectorElements(VectorSize sz) {
}
}
VectorSize GetHalfVectorSize(VectorSize sz) {
VectorSize GetHalfVectorSizeSafe(VectorSize sz) {
switch (sz) {
case V_Pair: return V_Single;
case V_Quad: return V_Pair;
default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid;
default: return V_Invalid;
}
}
VectorSize GetDoubleVectorSize(VectorSize sz)
{
switch (sz)
{
VectorSize GetHalfVectorSize(VectorSize sz) {
VectorSize res = GetHalfVectorSizeSafe(sz);
_assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__);
return res;
}
VectorSize GetDoubleVectorSizeSafe(VectorSize sz) {
switch (sz) {
case V_Single: return V_Pair;
case V_Pair: return V_Quad;
default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid;
default: return V_Invalid;
}
}
VectorSize GetVecSize(MIPSOpcode op)
{
int a = (op>>7)&1;
int b = (op>>15)&1;
a += (b<<1);
switch (a)
{
case 0: return V_Single;
case 1: return V_Pair;
case 2: return V_Triple;
case 3: return V_Quad;
default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid;
VectorSize GetDoubleVectorSize(VectorSize sz) {
VectorSize res = GetDoubleVectorSizeSafe(sz);
_assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__);
return res;
}
VectorSize GetVecSizeSafe(MIPSOpcode op) {
int a = (op >> 7) & 1;
int b = (op >> 15) & 1;
a += (b << 1);
switch (a) {
case 0: return V_Single;
case 1: return V_Pair;
case 2: return V_Triple;
case 3: return V_Quad;
default: return V_Invalid;
}
}
VectorSize GetVectorSize(MatrixSize sz) {
VectorSize GetVecSize(MIPSOpcode op) {
VectorSize res = GetVecSizeSafe(op);
_assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__);
return res;
}
VectorSize GetVectorSizeSafe(MatrixSize sz) {
switch (sz) {
case M_2x2: return V_Pair;
case M_3x3: return V_Triple;
case M_4x4: return V_Quad;
default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return V_Invalid;
default: return V_Invalid;
}
}
MatrixSize GetMatrixSize(VectorSize sz) {
VectorSize GetVectorSize(MatrixSize sz) {
VectorSize res = GetVectorSizeSafe(sz);
_assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__);
return res;
}
MatrixSize GetMatrixSizeSafe(VectorSize sz) {
switch (sz) {
case V_Single: return M_Invalid;
case V_Pair: return M_2x2;
case V_Triple: return M_3x3;
case V_Quad: return M_4x4;
default: /*_assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__);*/ return M_Invalid;
default: return M_Invalid;
}
}
MatrixSize GetMtxSize(MIPSOpcode op) {
int a = (op>>7)&1;
int b = (op>>15)&1;
a += (b<<1);
MatrixSize GetMatrixSize(VectorSize sz) {
MatrixSize res = GetMatrixSizeSafe(sz);
_assert_msg_(JIT, res != M_Invalid, "%s: Bad vector size", __FUNCTION__);
return res;
}
MatrixSize GetMtxSizeSafe(MIPSOpcode op) {
int a = (op >> 7) & 1;
int b = (op >> 15) & 1;
a += (b << 1);
switch (a) {
case 0: return M_4x4; // This happens in disassembly of junk
case 1: return M_2x2;
case 2: return M_3x3;
case 3: return M_4x4;
default: /*_assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__);*/ return M_Invalid;
default: return M_Invalid;
}
}
VectorSize MatrixVectorSize(MatrixSize sz) {
MatrixSize GetMtxSize(MIPSOpcode op) {
MatrixSize res = GetMtxSizeSafe(op);
_assert_msg_(JIT, res != M_Invalid, "%s: Bad matrix size", __FUNCTION__);
return res;
}
VectorSize MatrixVectorSizeSafe(MatrixSize sz) {
switch (sz) {
case M_2x2: return V_Pair;
case M_3x3: return V_Triple;
case M_4x4: return V_Quad;
default: /*_assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__);*/ return V_Invalid;
default: return V_Invalid;
}
}
int GetMatrixSide(MatrixSize sz) {
VectorSize MatrixVectorSize(MatrixSize sz) {
VectorSize res = MatrixVectorSizeSafe(sz);
_assert_msg_(JIT, res != V_Invalid, "%s: Bad matrix size", __FUNCTION__);
return res;
}
int GetMatrixSideSafe(MatrixSize sz) {
switch (sz) {
case M_2x2: return 2;
case M_3x3: return 3;
case M_4x4: return 4;
default: /*_assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__);*/ return 0;
default: return 0;
}
}
int GetMatrixSide(MatrixSize sz) {
int res = MatrixVectorSizeSafe(sz);
_assert_msg_(JIT, res != 0, "%s: Bad matrix size", __FUNCTION__);
return res;
}
// TODO: Optimize
MatrixOverlapType GetMatrixOverlap(int mtx1, int mtx2, MatrixSize msize) {
int n = GetMatrixSide(msize);
+6
View File
@@ -158,12 +158,18 @@ inline int GetMtx(int matrixReg) {
return (matrixReg >> 2) & 7;
}
VectorSize GetVecSizeSafe(MIPSOpcode op);
VectorSize GetVecSize(MIPSOpcode op);
MatrixSize GetMtxSizeSafe(MIPSOpcode op);
MatrixSize GetMtxSize(MIPSOpcode op);
VectorSize GetHalfVectorSizeSafe(VectorSize sz);
VectorSize GetHalfVectorSize(VectorSize sz);
VectorSize GetDoubleVectorSizeSafe(VectorSize sz);
VectorSize GetDoubleVectorSize(VectorSize sz);
VectorSize MatrixVectorSizeSafe(MatrixSize sz);
VectorSize MatrixVectorSize(MatrixSize sz);
int GetNumVectorElements(VectorSize sz);
int GetMatrixSideSafe(MatrixSize sz);
int GetMatrixSide(MatrixSize sz);
const char *GetVectorNotation(int reg, VectorSize size);
const char *GetMatrixNotation(int reg, MatrixSize size);
-5
View File
@@ -211,11 +211,6 @@ void Jit::GenerateFixedCode(JitOptions &jo) {
ABI_PopAllCalleeSavedRegsAndAdjustStack();
RET();
breakpointBailout = GetCodePtr();
RestoreRoundingMode(true);
ABI_PopAllCalleeSavedRegsAndAdjustStack();
RET();
// Let's spare the pre-generated code from unprotect-reprotect.
endOfPregeneratedCode = AlignCodePage();
EndWrite();
Loaded 100 of 379 files, more files were not shown because too many files have changed in this diff. Show more