From 47eb4998931aeaf95c0964487731e8c4b9b397bf Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Wed, 5 Nov 2025 01:32:17 +0000 Subject: [PATCH] GS/HW: Add CRC hack to cover IRem games to deal with depth deswizzle shuffle --- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 85 ++++++++++++++++++++++++++++++ pcsx2/GS/Renderers/HW/GSHwHack.h | 1 + 2 files changed, 86 insertions(+) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index f6ad905162..495e9c4f88 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -37,6 +37,90 @@ static bool s_nativeres; // Partial level, broken on all renderers. //////////////////////////////////////////////////////////////////////////////// +bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip) +{ + if (skip == 0) + { + const int get_next_ctx = r.m_env.PRIM.CTXT; + const GSDrawingContext& next_ctx = r.m_env.CTXT[get_next_ctx]; + + // Game does alternate line channel shuffles with blending, we can't handle this and the first one does it, so skip the second. + if (RTME && RTPSM == PSMT8 && (RTBP0 + 0x20) == next_ctx.TEX0.TBP0 && RFBP == next_ctx.FRAME.Block()) + { + skip = 1; + return false; + } + // Detect the deswizzling shuffle from depth, copying the RG and BA separately on each half of the page (ignore the split). + if (RTME && RFBP != RTBP0 && RFPSM == PSMCT16S && RTPSM == PSMCT16S) + { + if (r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 64 && r.m_index.tail == 128) + { + const GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y/2, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y/2); + const GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y/2, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y/2); + r.m_cached_ctx.TEX0.PSM = PSMCT32; + r.m_cached_ctx.FRAME.PSM = PSMCT32; + r.ReplaceVerticesWithSprite(draw_size, read_size, GSVector2i(read_size.width(), read_size.height()), draw_size); + } + } + + // Following the previous draw, it tries to copy everything read from depth and offset it by 2, for the alternate line channel shuffle (skipped above). + if (RTBP0 == (RFBP - 0x20) && r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 34 && r.m_index.tail == 2) + { + GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y - 2.0f, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y - 2.0f); + GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y); + r.ReplaceVerticesWithSprite(draw_size, read_size, GSVector2i(read_size.width(), read_size.height()), draw_size); + + + // Fix up the shuffle from last draw. + { + GIFRegTEX0 RTLookup = GIFRegTEX0::Create(RTBP0, RFBW, RFPSM); + GSTextureCache::Source* src = g_texture_cache->LookupSource(true, RTLookup, r.m_cached_ctx.TEXA, r.m_cached_ctx.CLAMP, GSVector4i(0,0,1,1), nullptr, true, false, r.m_cached_ctx.FRAME, true, true); + + GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM), + GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i(0,0,1,1), true, false, true, src); + + if (!rt) + return false; + + GSLocalMemory::psm_t rt_psm = GSLocalMemory::m_psm[RFPSM]; + int page_offset = (RTBP0 - rt->m_TEX0.TBP0) >> 5; + int vertical_offset = page_offset / std::max(rt->m_TEX0.TBW, 1U) * rt_psm.pgs.y; + int horizontal_offset = page_offset % std::max(rt->m_TEX0.TBW, 1U) * rt_psm.pgs.x; + + draw_size = draw_size + GSVector4i(horizontal_offset, vertical_offset, horizontal_offset, vertical_offset); + rt->UnscaleRTAlpha(); + + // Shuffle the blue channel in to red, leave green as-is. + GSHWDrawConfig& config = r.BeginHLEHardwareDraw( + rt->GetTexture(), nullptr, rt->GetScale(), rt->GetTexture(), rt->GetScale(), draw_size); + config.ps.shuffle = 1; + config.ps.dst_fmt = GSLocalMemory::PSM_FMT_32; + config.ps.write_rg = 0; + config.ps.shuffle_same = 0; + config.ps.real16src = 0; + config.ps.shuffle_across = 1; + config.ps.process_rg = r.SHUFFLE_WRITE; + config.ps.process_ba = r.SHUFFLE_READ; + config.colormask.wrgba = 0; + config.colormask.wr = 1; + config.ps.rta_correction = 0; + config.ps.rta_source_correction = 0; + config.ps.tfx = TFX_DECAL; + config.ps.tcc = true; + r.EndHLEHardwareDraw(true); + + rt->m_alpha_min = 0; + rt->m_alpha_max = 255; + + rt = nullptr; + src = nullptr; + } + } + } + + return true; +} + // Channel effect not properly supported yet bool GSHwHack::GSC_Manhunt2(GSRendererHW& r, int& skip) { @@ -1382,6 +1466,7 @@ bool GSHwHack::MV_Ico(GSRendererHW& r) #define CRC_F(name) { #name, &GSHwHack::name } const GSHwHack::Entry GSHwHack::s_get_skip_count_functions[] = { + CRC_F(GSC_IRem), CRC_F(GSC_Manhunt2), CRC_F(GSC_MidnightClub3), CRC_F(GSC_SacredBlaze), diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index 12621b607e..53b486500a 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -6,6 +6,7 @@ class GSHwHack { public: + static bool GSC_IRem(GSRendererHW& r, int& skip); static bool GSC_Manhunt2(GSRendererHW& r, int& skip); static bool GSC_SacredBlaze(GSRendererHW& r, int& skip); static bool GSC_GuitarHero(GSRendererHW& r, int& skip);