mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-15 02:29:51 +01:00
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:
commit
9c655d8f89
379 files changed
+11257
-10640
No files matched your search
+61
-47
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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{};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "util/text/utf8.h"
|
||||
#include "zlib.h"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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, ®)) {
|
||||
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, ®)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
@@ -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, ¶m->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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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]);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -238,7 +238,6 @@ void ArmJit::GenerateFixedCode() {
|
||||
B_CC(CC_EQ, outerLoop);
|
||||
|
||||
SetJumpTarget(badCoreState);
|
||||
breakpointBailout = GetCodePtr();
|
||||
|
||||
SaveDowncount();
|
||||
RestoreRoundingMode(true);
|
||||
|
||||
@@ -93,6 +93,7 @@ extern int logBlocks;
|
||||
void ArmJit::Comp_FPULS(MIPSOpcode op)
|
||||
{
|
||||
CONDITIONAL_DISABLE;
|
||||
CheckMemoryBreakpoint();
|
||||
|
||||
s32 offset = (s16)(op & 0xFFFF);
|
||||
int ft = _FT;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -275,7 +275,6 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) {
|
||||
B(CC_EQ, outerLoop);
|
||||
|
||||
SetJumpTarget(badCoreState);
|
||||
breakpointBailout = GetCodePtr();
|
||||
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -181,8 +181,6 @@ public:
|
||||
const u8 *dispatcherPCInR0;
|
||||
const u8 *dispatcher;
|
||||
const u8 *dispatcherNoCheck;
|
||||
|
||||
const u8 *breakpointBailout;
|
||||
};
|
||||
|
||||
typedef void (MipsJit::*MIPSCompileFunc)(MIPSOpcode opcode);
|
||||
|
||||
@@ -1415,6 +1415,7 @@ skip:
|
||||
memset(&info, 0, sizeof(info));
|
||||
|
||||
if (!Memory::IsValidAddress(address)) {
|
||||
info.opcodeAddress = address;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
+2
-17
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in new issue
Block a user