From 6484382092ac778a831e4d860f49d43232049dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 1 Jan 2026 22:29:39 +0100 Subject: [PATCH 1/6] Reformat crashcleaner, improve output slightly --- Tools/crashcleaner.html | 310 ++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 152 deletions(-) diff --git a/Tools/crashcleaner.html b/Tools/crashcleaner.html index 6136e41c97..5b1b639500 100644 --- a/Tools/crashcleaner.html +++ b/Tools/crashcleaner.html @@ -1,180 +1,186 @@  + - - Android Crash Dump Cleaner - - + .top-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + -

Android crash dump cleaner

-
-
- - -
-
- - - -
-
Automatically removes BuildId and shortens paths to the arch + filename.
-
-
+

Android crash dump cleaner

+
+
+ + +
+
+ + + +
+
Automatically removes BuildId and shortens paths to the arch + + filename.
+
+
-
- - -
- - -
-
-
-
+
+ + +
+ + +
+
+
+
- + exampleBtn.addEventListener('click', () => { + inputEl.value = `backtrace: + #00 pc 0x0000000001310974 /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (ff_seek_frame_binary+360) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #01 pc 0x0000000001311700 /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (av_seek_frame+572) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #02 pc 0x0000000000ab4e9c /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (MediaEngine::openContext(bool)+760) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #03 pc 0x0000000000ab5be4 /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (MediaEngine::addStreamData(unsigned char const*, int)+388) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #04 pc 0x0000000000a1b048 /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (PostPutAction::run(MipsCall&)+1072) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #05 pc 0x000000000096b7cc /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (HLEReturnFromMipsCall()+652) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #06 pc 0x000000000096bfd8 /data/app/~~Hyfi4AvhSN3X30k-enAkTA==/org.ppsspp.ppsspp-ICO6UNv4-Hyh2OmM_FmvZA==/lib/arm64/libppsspp_jni.so (CallSyscallWithoutFlags(HLEFunction const*)) (BuildId: bf1ef0a0943c53c1f57b19b0e3c71c5d7135877e) + #07 pc 0x00000000000c2068`; + }); + + // Optional: convert on paste/drop for convenience + inputEl.addEventListener('paste', (e) => { + // small convenience: automatically convert after paste + setTimeout(() => { + outputEl.value = cleanCrashDump(inputEl.value); + }, 50); + }); + + - + + \ No newline at end of file From 9d620c195ead5eaa3ec7c3ca0bcd6a32a4707022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 1 Jan 2026 22:41:21 +0100 Subject: [PATCH 2/6] Android: Catch a possible exception while assigning shortcut icons --- .../org/ppsspp/ppsspp/ShortcutActivity.java | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java index 8f73c786bc..65f00cc34a 100644 --- a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java +++ b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java @@ -116,16 +116,16 @@ public class ShortcutActivity extends Activity { PpssppActivity.CheckABIAndLoadLibrary(); String gameName = name; - byte[] iconData = null; + byte[] iconData; Object[] result = queryGameInfo(this, path); if (result != null && result.length >= 2) { if (result[0] != null) { gameName = (String) result[0]; // index 0 = name } iconData = (byte[]) result[1]; // index 1 = raw PNG/JPEG bytes, or null - Log.i(TAG, "Game name: " + gameName); } else { + iconData = null; Log.e(TAG, "Bad return value from queryGameInfo"); } @@ -138,29 +138,37 @@ public class ShortcutActivity extends Activity { responseIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); if (iconData != null) { - // Try to create a PNG from the iconData. - Bitmap bmp = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); - if (bmp != null) { - // Pad the bitmap into a square, to keep it a nice 2:1 aspect ratio. - Bitmap paddedBitmap = Bitmap.createBitmap( - bmp.getWidth(), bmp.getWidth(), - Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(paddedBitmap); - canvas.drawARGB(0, 0, 0, 0); - int y = (bmp.getWidth() - bmp.getHeight()) / 2; - if (y < 0) { - // To be safe from wacky-aspect-ratio bitmaps. - y = 0; + try { + // Try to create a PNG from the iconData. + Bitmap bmp = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); + if (bmp != null) { + // Pad the bitmap into a square, to keep it a nice 2:1 aspect ratio. + Bitmap paddedBitmap = Bitmap.createBitmap( + bmp.getWidth(), bmp.getWidth(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(paddedBitmap); + canvas.drawARGB(0, 0, 0, 0); + int y = (bmp.getWidth() - bmp.getHeight()) / 2; + if (y < 0) { + // To be safe from wacky-aspect-ratio bitmaps. + y = 0; + } + canvas.drawBitmap(bmp, 0, y, new Paint(Paint.FILTER_BITMAP_FLAG)); + // Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, 144, 72, true); + responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, paddedBitmap); } - canvas.drawBitmap(bmp, 0, y, new Paint(Paint.FILTER_BITMAP_FLAG)); - // Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, 144, 72, true); - responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, paddedBitmap); + } catch (Exception e) { + NativeApp.reportException(e, "Error assigning generated icon"); } } else { Log.i(TAG, "No icon available, falling back to PPSSPP icon"); - // Fall back to the PPSSPP icon. - ShortcutIconResource iconResource = ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher); - responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + try { + // Fall back to the PPSSPP icon. + ShortcutIconResource iconResource = ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher); + responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + } catch (Exception e) { + NativeApp.reportException(e, "Error assigning default icon"); + } } setResult(RESULT_OK, responseIntent); From 02d621c0f6310488bcc727f929b6d20504cab35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 1 Jan 2026 22:41:52 +0100 Subject: [PATCH 3/6] OpenGL ES: Correct check for 3D texture support. --- Common/GPU/OpenGL/thin3d_gl.cpp | 8 +++++++- GPU/GLES/TextureCacheGLES.cpp | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index fd54444ff1..54cc2059ce 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -567,7 +567,7 @@ OpenGLContext::OpenGLContext(bool canChangeSwapInterval) : renderManager_(frameT caps_.fragmentShaderInt32Supported = true; } } - caps_.texture3DSupported = gl_extensions.OES_texture_3D; + caps_.texture3DSupported = gl_extensions.GLES3 || gl_extensions.OES_texture_3D; caps_.textureDepthSupported = gl_extensions.GLES3 || gl_extensions.OES_depth_texture; } else { if (gl_extensions.VersionGEThan(3, 3, 0)) { @@ -1310,6 +1310,12 @@ bool OpenGLPipeline::LinkShaders(const PipelineDesc &desc) { } } + if (linkShaders.empty()) { + // Can't proceed. + ERROR_LOG(Log::G3D, "LinkShaders: No valid shaders to link"); + return false; + } + std::vector semantics; semantics.reserve(8); // Bind all the common vertex data points. Mismatching ones will be ignored. diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 8b68acdff8..8ca5ca602f 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -248,6 +248,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { if (plan.depth == 1) { entry->textureName = render_->CreateTexture(GL_TEXTURE_2D, tw, th, 1, plan.levelsToCreate); } else { + _dbg_assert_(draw_->GetDeviceCaps().texture3DSupported); entry->textureName = render_->CreateTexture(GL_TEXTURE_3D, tw, th, plan.depth, 1); } From cadbe3a2e06907d5392e4629dcaa9ea83342c98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 1 Jan 2026 23:03:58 +0100 Subject: [PATCH 4/6] Speedup text rendering on Android slightly, eliminate possible assert --- Common/Render/Text/draw_text_android.cpp | 11 ++++++++--- android/src/org/ppsspp/ppsspp/TextRenderer.java | 7 +------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Common/Render/Text/draw_text_android.cpp b/Common/Render/Text/draw_text_android.cpp index 3e43e8b691..ab95022634 100644 --- a/Common/Render/Text/draw_text_android.cpp +++ b/Common/Render/Text/draw_text_android.cpp @@ -25,7 +25,7 @@ TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw) method_allocFont = env->GetStaticMethodID(cls_textRenderer, "allocFont", "(Ljava/lang/String;)I"); method_freeAllFonts = env->GetStaticMethodID(cls_textRenderer, "freeAllFonts", "()V"); method_measureText = env->GetStaticMethodID(cls_textRenderer, "measureText", "(Ljava/lang/String;ID)I"); - method_renderText = env->GetStaticMethodID(cls_textRenderer, "renderText", "(Ljava/lang/String;ID)[I"); + method_renderText = env->GetStaticMethodID(cls_textRenderer, "renderText", "(Ljava/lang/String;IDII)[I"); } else { ERROR_LOG(Log::G3D, "Failed to find class: '%s'", textRendererClassName); } @@ -126,7 +126,7 @@ bool TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextS // WARN_LOG(Log::G3D, "Text: '%.*s' (%02x)", (int)str.length(), str.data(), str[0]); // TODO: Handle bold/italic - jintArray imageData = (jintArray)env->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, iter->second.font, iter->second.size); + jintArray imageData = (jintArray)env->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, iter->second.font, iter->second.size, imageWidth, imageHeight); env->DeleteLocalRef(jstr); entry.texture = nullptr; @@ -137,7 +137,12 @@ bool TextDrawerAndroid::DrawStringBitmap(std::vector &bitmapData, TextS entry.lastUsedFrame = frameCount_; jint *jimage = env->GetIntArrayElements(imageData, nullptr); - _assert_(env->GetArrayLength(imageData) == imageWidth * imageHeight); + if (env->GetArrayLength(imageData) != imageWidth * imageHeight) { + ERROR_LOG(Log::G3D, "TextRenderer returned bad image size"); + env->ReleaseIntArrayElements(imageData, jimage, JNI_ABORT); + env->DeleteLocalRef(imageData); + return false; + } if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; diff --git a/android/src/org/ppsspp/ppsspp/TextRenderer.java b/android/src/org/ppsspp/ppsspp/TextRenderer.java index 8af8e207c8..09e63b0d22 100644 --- a/android/src/org/ppsspp/ppsspp/TextRenderer.java +++ b/android/src/org/ppsspp/ppsspp/TextRenderer.java @@ -109,14 +109,9 @@ public class TextRenderer { Point s = measure(string, font, textSize); return (s.x << 16) | s.y; } - - public static int[] renderText(String string, int font, double textSize) { + public static int[] renderText(String string, int font, double textSize, int w, int h) { textPaint.setTypeface(fontMap.get(font)); textPaint.setTextSize((float) textSize); - Point s = measure(string, font, textSize); - - int w = s.x; - int h = s.y; Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); From 64461329e7083f48267982f44e716e0809c5ee3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 1 Jan 2026 23:20:34 +0100 Subject: [PATCH 5/6] More minor fixes and cleanups --- Common/GPU/OpenGL/GLRenderManager.h | 7 ++++--- Common/GPU/Vulkan/thin3d_vulkan.cpp | 5 +++++ Core/HLE/sceDisplay.cpp | 7 ++++++- GPU/Common/PresentationCommon.cpp | 3 +-- GPU/GPUCommonHW.cpp | 1 + GPU/GPUState.h | 9 ++++----- UI/MiscViews.cpp | 2 ++ UI/Store.h | 1 - 8 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 442b74bb2c..ca3d038581 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +84,7 @@ private: class GLRShader { public: + explicit GLRShader(std::string_view _desc) : desc(_desc) {} ~GLRShader() { if (shader) { glDeleteShader(shader); @@ -275,11 +277,10 @@ public: return step.create_buffer.buffer; } - GLRShader *CreateShader(GLuint stage, const std::string &code, const std::string &desc) { + GLRShader *CreateShader(GLuint stage, const std::string &code, std::string_view desc) { GLRInitStep &step = initSteps_.push_uninitialized(); step.stepType = GLRInitStepType::CREATE_SHADER; - step.create_shader.shader = new GLRShader(); - step.create_shader.shader->desc = desc; + step.create_shader.shader = new GLRShader(desc); step.create_shader.stage = stage; step.create_shader.code = new char[code.size() + 1]; memcpy(step.create_shader.code, code.data(), code.size() + 1); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index ced87f32c3..17ac1d8eec 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1225,6 +1225,11 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char for (auto &iter : desc.shaders) { VKShaderModule *vkshader = (VKShaderModule *)iter; + if (!iter) { + ERROR_LOG(Log::G3D, "Null shader in pipeline creation"); + delete pipeline; + return nullptr; + } vkshader->AddRef(); pipeline->deps.push_back(vkshader); if (vkshader->GetStage() == ShaderStage::Vertex) { diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 31aa24b4ac..6aecc36bb5 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -571,7 +571,12 @@ void __DisplaySetDisplayLayoutConfig(const DisplayLayoutConfig &config) { } void __DisplayFlip(int cyclesLate) { - _dbg_assert_(gpu); + if (!gpu) { + _dbg_assert_(gpu); + // Something has gone wrong. + flippedThisFrame = true; + return; + } __DisplaySetFramerate(); diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index cdfdd4a64e..49f60db226 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -596,8 +596,7 @@ Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, S return nullptr; } } - Draw::ShaderModule *shader = draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader"); - return shader; + return draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader"); } void PresentationCommon::SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight) { diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index f240f6fa22..b07273fc59 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1252,6 +1252,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) { break; } + // Keep going if these commands don't change state. case GE_CMD_TEXBUFWIDTH0: case GE_CMD_TEXADDR0: if (data != gstate.cmdmem[data >> 24]) diff --git a/GPU/GPUState.h b/GPU/GPUState.h index b7427b7d1f..1eb67262a3 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -682,7 +682,10 @@ public: ShaderDepalMode shaderDepalMode; GEBufferFormat depalFramebufferFormat; - u32 getRelativeAddress(u32 data) const; + u32 getRelativeAddress(u32 data) const { + u32 baseExtended = ((gstate.base & 0x000F0000) << 8) | data; + return (offsetAddr + baseExtended) & 0x0FFFFFFF; + } static void Reset(); void DoState(PointerWrap &p); }; @@ -692,7 +695,3 @@ class GPUDebugInterface; extern GPUStateCache gstate_c; -inline u32 GPUStateCache::getRelativeAddress(u32 data) const { - u32 baseExtended = ((gstate.base & 0x000F0000) << 8) | data; - return (gstate_c.offsetAddr + baseExtended) & 0x0FFFFFFF; -} diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index 5b10ab1ebc..42273de999 100644 --- a/UI/MiscViews.cpp +++ b/UI/MiscViews.cpp @@ -185,6 +185,8 @@ GameInfoTex *GameImageView::GetTex(std::shared_ptr info) const { case GameInfoFlags::PIC1: tex = &info->pic1; break; + default: + break; } return tex; } diff --git a/UI/Store.h b/UI/Store.h index ffb785191f..9d31f03560 100644 --- a/UI/Store.h +++ b/UI/Store.h @@ -104,6 +104,5 @@ private: UI::ViewGroup *scrollItemView_ = nullptr; UI::ViewGroup *productPanel_ = nullptr; - UI::TextView *titleText_ = nullptr; }; From 1d16d6be6adb9461b973b192cfd5c86e72ba77df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 2 Jan 2026 14:17:56 +0100 Subject: [PATCH 6/6] Workaround for a shutdown destruction order problem. Part of #21055 --- UI/GameInfoCache.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 187a435d3c..dc9739aab5 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -935,6 +935,14 @@ void GameInfoCache::Clear() { CancelAll(); std::lock_guard lock(mapLock_); + // NOTE: Some shared_pointers might have other owners. We still need to wipe their textures here. + for (auto &[key, value] : info_) { + std::lock_guard lock(value->lock); + value->pic0.Clear(); + value->pic1.Clear(); + value->icon.Clear(); + value->hasFlags &= ~(GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::ICON); + } info_.clear(); }