CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/HLE/ReplaceTables.cpp
Views: 1401
// Copyright (c) 2013- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"18#include <algorithm>19#include <map>20#include <unordered_map>2122#include "Common/CommonTypes.h"23#include "Common/Data/Convert/SmallDataConvert.h"24#include "Common/Log.h"25#include "Common/Swap.h"26#include "Core/Config.h"27#include "Core/System.h"28#include "Core/Debugger/Breakpoints.h"29#include "Core/Debugger/MemBlockInfo.h"30#include "Core/Debugger/SymbolMap.h"31#include "Core/MemMap.h"32#include "Core/MIPS/JitCommon/JitCommon.h"33#include "Core/MIPS/MIPSCodeUtils.h"34#include "Core/MIPS/MIPSAnalyst.h"35#include "Core/HLE/ReplaceTables.h"36#include "Core/HLE/FunctionWrappers.h"37#include "Core/HLE/sceDisplay.h"3839#include "GPU/Math3D.h"40#include "GPU/GPU.h"41#include "GPU/GPUInterface.h"42#include "GPU/GPUState.h"4344#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)45#include <emmintrin.h>46#endif4748enum class GPUReplacementSkip {49MEMSET = 1,50MEMCPY = 2,51MEMMOVE = 4,52};5354static int skipGPUReplacements = 0;5556// I think these have to be pretty accurate as these are libc replacements,57// but we can probably get away with approximating the VFPU vsin/vcos and vrot58// pretty roughly.59static int Replace_sinf() {60float f = PARAMF(0);61RETURNF(sinf(f));62return 80; // guess number of cycles63}6465static int Replace_cosf() {66float f = PARAMF(0);67RETURNF(cosf(f));68return 80; // guess number of cycles69}7071static int Replace_tanf() {72float f = PARAMF(0);73RETURNF(tanf(f));74return 80; // guess number of cycles75}7677static int Replace_acosf() {78float f = PARAMF(0);79RETURNF(acosf(f));80return 80; // guess number of cycles81}8283static int Replace_asinf() {84float f = PARAMF(0);85RETURNF(asinf(f));86return 80; // guess number of cycles87}8889static int Replace_atanf() {90float f = PARAMF(0);91RETURNF(atanf(f));92return 80; // guess number of cycles93}9495static int Replace_sqrtf() {96float f = PARAMF(0);97RETURNF(sqrtf(f));98return 80; // guess number of cycles99}100101static int Replace_atan2f() {102float f1 = PARAMF(0);103float f2 = PARAMF(1);104RETURNF(atan2f(f1, f2));105return 120; // guess number of cycles106}107108static int Replace_floorf() {109float f1 = PARAMF(0);110RETURNF(floorf(f1));111return 30; // guess number of cycles112}113114static int Replace_ceilf() {115float f1 = PARAMF(0);116RETURNF(ceilf(f1));117return 30; // guess number of cycles118}119120// Should probably do JIT versions of this, possibly ones that only delegate121// large copies to a C function.122static int Replace_memcpy() {123u32 destPtr = PARAM(0);124u32 srcPtr = PARAM(1);125u32 bytes = PARAM(2);126bool skip = false;127if (!bytes) {128RETURN(destPtr);129return 10;130}131132// Some games use memcpy on executable code. We need to flush emuhack ops.133currentMIPS->InvalidateICache(srcPtr, bytes);134if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) {135if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {136skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);137}138}139if (!skip && bytes != 0) {140u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);141const u8 *src = Memory::GetPointerRange(srcPtr, bytes);142143if (!dst || !src) {144// Already logged.145} else if (std::min(destPtr, srcPtr) + bytes > std::max(destPtr, srcPtr)) {146// Overlap. Star Ocean breaks if it's not handled in 16 bytes blocks.147const u32 blocks = bytes & ~0x0f;148for (u32 offset = 0; offset < blocks; offset += 0x10) {149memcpy(dst + offset, src + offset, 0x10);150}151for (u32 offset = blocks; offset < bytes; ++offset) {152dst[offset] = src[offset];153}154} else {155memmove(dst, src, bytes);156}157}158RETURN(destPtr);159160if (MemBlockInfoDetailed(bytes)) {161// It's pretty common that games will copy video data.162// Detect that by manually reading the tag when the size looks right.163if (bytes == 512 * 272 * 4) {164char tagData[128];165size_t tagSize = FormatMemWriteTagAt(tagData, sizeof(tagData), "ReplaceMemcpy/", srcPtr, bytes);166NotifyMemInfo(MemBlockFlags::READ, srcPtr, bytes, tagData, tagSize);167NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, tagData, tagSize);168169if (!strcmp(tagData, "ReplaceMemcpy/VideoDecode") || !strcmp(tagData, "ReplaceMemcpy/VideoDecodeRange")) {170gpu->PerformWriteFormattedFromMemory(destPtr, bytes, 512, GE_FORMAT_8888);171}172} else {173NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemcpy/");174}175}176177return 10 + bytes / 4; // approximation178}179180static int Replace_memcpy_jak() {181u32 destPtr = PARAM(0);182u32 srcPtr = PARAM(1);183u32 bytes = PARAM(2);184185if (bytes == 0) {186RETURN(destPtr);187return 5;188}189190bool skip = false;191bool sliced = false;192static constexpr uint32_t SLICE_SIZE = 32768;193194currentMIPS->InvalidateICache(srcPtr, bytes);195if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) {196if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {197skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);198}199}200if (!skip && bytes > SLICE_SIZE && bytes != 512 * 272 * 4 && !PSP_CoreParameter().compat.flags().DisableMemcpySlicing) {201// This is a very slow func. To avoid thread blocking, do a slice at a time.202// Avoiding exactly 512 * 272 * 4 to detect videos, though.203bytes = SLICE_SIZE;204sliced = true;205}206if (!skip && bytes != 0) {207u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);208const u8 *src = Memory::GetPointerRange(srcPtr, bytes);209210if (dst && src) {211// Jak style overlap.212for (u32 i = 0; i < bytes; i++) {213dst[i] = src[i];214}215}216}217218if (sliced) {219currentMIPS->r[MIPS_REG_A0] += SLICE_SIZE;220currentMIPS->r[MIPS_REG_A1] += SLICE_SIZE;221currentMIPS->r[MIPS_REG_A2] -= SLICE_SIZE;222} else {223// Jak relies on more registers coming out right than the ABI specifies.224// See the disassembly of the function for the explanations for these...225currentMIPS->r[MIPS_REG_T0] = 0;226currentMIPS->r[MIPS_REG_A0] = -1;227currentMIPS->r[MIPS_REG_A2] = 0;228// Even after slicing, this ends up correct.229currentMIPS->r[MIPS_REG_A3] = destPtr + bytes;230RETURN(destPtr);231}232233if (MemBlockInfoDetailed(bytes)) {234// It's pretty common that games will copy video data.235// Detect that by manually reading the tag when the size looks right.236if (bytes == 512 * 272 * 4) {237char tagData[128];238size_t tagSize = FormatMemWriteTagAt(tagData, sizeof(tagData), "ReplaceMemcpy/", srcPtr, bytes);239NotifyMemInfo(MemBlockFlags::READ, srcPtr, bytes, tagData, tagSize);240NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, tagData, tagSize);241242if (!strcmp(tagData, "ReplaceMemcpy/VideoDecode") || !strcmp(tagData, "ReplaceMemcpy/VideoDecodeRange")) {243gpu->PerformWriteFormattedFromMemory(destPtr, bytes, 512, GE_FORMAT_8888);244}245} else {246NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemcpy/");247}248}249250if (sliced) {251// Negative causes the function to be run again for the next slice.252return 5 + bytes * -8 + 2;253}254return 5 + bytes * 8 + 2; // approximation. This is a slow memcpy - a byte copy loop..255}256257static int Replace_memcpy16() {258u32 destPtr = PARAM(0);259u32 srcPtr = PARAM(1);260u32 bytes = PARAM(2) * 16;261bool skip = false;262263// Some games use memcpy on executable code. We need to flush emuhack ops.264if (bytes != 0)265currentMIPS->InvalidateICache(srcPtr, bytes);266if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0 && bytes != 0) {267if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {268skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);269}270}271if (!skip && bytes != 0) {272u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);273const u8 *src = Memory::GetPointerRange(srcPtr, bytes);274if (dst && src) {275memmove(dst, src, bytes);276}277}278RETURN(destPtr);279280if (MemBlockInfoDetailed(bytes)) {281NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemcpy16/");282}283284return 10 + bytes / 4; // approximation285}286287static int Replace_memcpy_swizzled() {288u32 destPtr = PARAM(0);289u32 srcPtr = PARAM(1);290u32 pitch = PARAM(2);291u32 h = PARAM(4);292if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) {293if (Memory::IsVRAMAddress(srcPtr)) {294gpu->PerformReadbackToMemory(srcPtr, pitch * h);295}296}297u8 *dstp = Memory::GetPointerWriteRange(destPtr, pitch * h);298const u8 *srcp = Memory::GetPointerRange(srcPtr, pitch * h);299300if (dstp && srcp) {301const u8 *ysrcp = srcp;302for (u32 y = 0; y < h; y += 8) {303const u8 *xsrcp = ysrcp;304for (u32 x = 0; x < pitch; x += 16) {305const u8 *src = xsrcp;306for (int n = 0; n < 8; ++n) {307memcpy(dstp, src, 16);308src += pitch;309dstp += 16;310}311xsrcp += 16;312}313ysrcp += 8 * pitch;314}315}316317RETURN(0);318319if (MemBlockInfoDetailed(pitch * h)) {320NotifyMemInfoCopy(destPtr, srcPtr, pitch * h, "ReplaceMemcpySwizzle/");321}322323return 10 + (pitch * h) / 4; // approximation324}325326static int Replace_memmove() {327u32 destPtr = PARAM(0);328u32 srcPtr = PARAM(1);329u32 bytes = PARAM(2);330bool skip = false;331332// Some games use memcpy on executable code. We need to flush emuhack ops.333if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMMOVE) == 0 && bytes != 0) {334currentMIPS->InvalidateICache(srcPtr, bytes);335if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {336skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);337}338}339if (!skip && bytes != 0) {340u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);341const u8 *src = Memory::GetPointerRange(srcPtr, bytes);342if (dst && src) {343memmove(dst, src, bytes);344}345}346RETURN(destPtr);347348if (MemBlockInfoDetailed(bytes)) {349NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemmove/");350}351352return 10 + bytes / 4; // approximation353}354355static int Replace_memset() {356u32 destPtr = PARAM(0);357u8 value = PARAM(1);358u32 bytes = PARAM(2);359bool skip = false;360if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) {361skip = gpu->PerformMemorySet(destPtr, value, bytes);362}363if (!skip && bytes != 0) {364u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);365if (dst) {366memset(dst, value, bytes);367}368}369RETURN(destPtr);370371NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, "ReplaceMemset");372373return 10 + bytes / 4; // approximation374}375376static int Replace_memset_jak() {377u32 destPtr = PARAM(0);378u8 value = PARAM(1);379u32 bytes = PARAM(2);380381if (bytes == 0) {382RETURN(destPtr);383return 5;384}385386bool skip = false;387bool sliced = false;388static constexpr uint32_t SLICE_SIZE = 32768;389if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) {390skip = gpu->PerformMemorySet(destPtr, value, bytes);391}392if (!skip && bytes > SLICE_SIZE && !PSP_CoreParameter().compat.flags().DisableMemcpySlicing) {393// This is a very slow func. To avoid thread blocking, do a slice at a time.394bytes = SLICE_SIZE;395sliced = true;396}397if (!skip && bytes != 0) {398u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);399if (dst) {400memset(dst, value, bytes);401}402}403404NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, "ReplaceMemset");405406if (sliced) {407currentMIPS->r[MIPS_REG_A0] += SLICE_SIZE;408currentMIPS->r[MIPS_REG_A2] -= SLICE_SIZE;409410// This is approximate, and must be a negative value.411// Negative causes the function to be run again for the next slice.412return 5 + (int)SLICE_SIZE * -6 + 2;413}414415// Even after slicing, this ends up correct.416currentMIPS->r[MIPS_REG_T0] = destPtr + bytes;417currentMIPS->r[MIPS_REG_A2] = -1;418currentMIPS->r[MIPS_REG_A3] = -1;419RETURN(destPtr);420421return 5 + bytes * 6 + 2; // approximation422}423424static uint32_t SafeStringLen(const uint32_t ptr, uint32_t maxLen = 0x07FFFFFF) {425maxLen = Memory::ValidSize(ptr, 0x07FFFFFF);426const uint8_t *p = Memory::GetPointerRange(ptr, maxLen);427if (!p)428return 0;429const uint8_t *end = (const uint8_t *)memchr(p, '\0', maxLen);430if (!end)431return 0;432return (uint32_t)(end - p);433}434435static int Replace_strlen() {436u32 srcPtr = PARAM(0);437u32 len = SafeStringLen(srcPtr);438RETURN(len);439return 7 + len * 4; // approximation440}441442static int Replace_strcpy() {443u32 destPtr = PARAM(0);444u32 srcPtr = PARAM(1);445u32 len = SafeStringLen(srcPtr);446char *dst = (char *)Memory::GetPointerWriteRange(destPtr, len);447const char *src = (const char *)Memory::GetPointerRange(srcPtr, len);448if (dst && src && len != 0) {449strcpy(dst, src);450}451RETURN(destPtr);452return 10; // approximation453}454455static int Replace_strncpy() {456u32 destPtr = PARAM(0);457u32 srcPtr = PARAM(1);458u32 bytes = PARAM(2);459char *dst = (char *)Memory::GetPointerRange(destPtr, bytes);460u32 srcLen = SafeStringLen(srcPtr, bytes);461const char *src = (const char *)Memory::GetPointerRange(srcPtr, srcLen == 0 ? bytes : srcLen);462if (dst && src && bytes != 0) {463strncpy(dst, src, bytes);464}465RETURN(destPtr);466return 10; // approximation467}468469static int Replace_strcmp() {470u32 aLen = SafeStringLen(PARAM(0));471const char *a = (const char *)Memory::GetPointerRange(PARAM(0), aLen);472u32 bLen = SafeStringLen(PARAM(1));473const char *b = (const char *)Memory::GetPointerRange(PARAM(1), bLen);474if (a && b && aLen != 0 && bLen != 0) {475RETURN(strcmp(a, b));476} else {477RETURN(0);478}479return 10; // approximation480}481482static int Replace_strncmp() {483u32 bytes = PARAM(2);484u32 aLen = SafeStringLen(PARAM(0), bytes);485const char *a = (const char *)Memory::GetPointerRange(PARAM(0), aLen == 0 ? bytes : aLen);486u32 bLen = SafeStringLen(PARAM(1), bytes);487const char *b = (const char *)Memory::GetPointerRange(PARAM(1), bLen == 0 ? bytes : bLen);488if (a && b && bytes != 0) {489RETURN(strncmp(a, b, bytes));490} else {491RETURN(0);492}493return 10 + bytes / 4; // approximation494}495496static int Replace_fabsf() {497RETURNF(fabsf(PARAMF(0)));498return 4;499}500501static int Replace_vmmul_q_transp() {502float_le *out = (float_le *)Memory::GetPointerRange(PARAM(0), 16 * 4);503const float_le *a = (const float_le *)Memory::GetPointerRange(PARAM(1), 16 * 4);504const float_le *b = (const float_le *)Memory::GetPointerRange(PARAM(2), 16 * 4);505506// TODO: Actually use an optimized matrix multiply here...507if (out && b && a) {508#ifdef COMMON_BIG_ENDIAN509float outn[16], an[16], bn[16];510for (int i = 0; i < 16; ++i) {511an[i] = a[i];512bn[i] = b[i];513}514Matrix4ByMatrix4(outn, bn, an);515for (int i = 0; i < 16; ++i) {516out[i] = outn[i];517}518#else519Matrix4ByMatrix4(out, b, a);520#endif521}522return 16;523}524525// a0 = pointer to destination address526// a1 = matrix527// a2 = source address528static int Replace_gta_dl_write_matrix() {529u32_le *ptr = (u32_le *)Memory::GetPointerWriteRange(PARAM(0), 4);530const u32_le *src = (const u32_le *)Memory::GetPointerRange(PARAM(2), 16);531u32 matrix = PARAM(1) << 24;532533if (!ptr || !src) {534RETURN(0);535return 38;536}537538u32_le *dest = (u32_le *)Memory::GetPointerWriteRange(ptr[0], 12 * 4);539if (!dest) {540RETURN(0);541return 38;542}543544#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)545__m128i topBytes = _mm_set1_epi32(matrix);546__m128i m0 = _mm_loadu_si128((const __m128i *)src);547__m128i m1 = _mm_loadu_si128((const __m128i *)(src + 4));548__m128i m2 = _mm_loadu_si128((const __m128i *)(src + 8));549__m128i m3 = _mm_loadu_si128((const __m128i *)(src + 12));550m0 = _mm_or_si128(_mm_srli_epi32(m0, 8), topBytes);551m1 = _mm_or_si128(_mm_srli_epi32(m1, 8), topBytes);552m2 = _mm_or_si128(_mm_srli_epi32(m2, 8), topBytes);553m3 = _mm_or_si128(_mm_srli_epi32(m3, 8), topBytes);554// These three stores overlap by a word, due to the offsets.555_mm_storeu_si128((__m128i *)dest, m0);556_mm_storeu_si128((__m128i *)(dest + 3), m1);557_mm_storeu_si128((__m128i *)(dest + 6), m2);558// Store the last one in parts to not overwrite forwards (probably mostly risk free though)559_mm_storel_epi64((__m128i *)(dest + 9), m3);560m3 = _mm_srli_si128(m3, 8);561_mm_store_ss((float *)(dest + 11), _mm_castsi128_ps(m3));562#else563// Bit tricky to SIMD (note the offsets) but should be doable if not perfect564dest[0] = matrix | (src[0] >> 8);565dest[1] = matrix | (src[1] >> 8);566dest[2] = matrix | (src[2] >> 8);567dest[3] = matrix | (src[4] >> 8);568dest[4] = matrix | (src[5] >> 8);569dest[5] = matrix | (src[6] >> 8);570dest[6] = matrix | (src[8] >> 8);571dest[7] = matrix | (src[9] >> 8);572dest[8] = matrix | (src[10] >> 8);573dest[9] = matrix | (src[12] >> 8);574dest[10] = matrix | (src[13] >> 8);575dest[11] = matrix | (src[14] >> 8);576#endif577578(*ptr) += 0x30;579580RETURN(0);581return 38;582}583584585// TODO: Inline into a few NEON or SSE instructions - especially if a1 is a known immediate!586// Anyway, not sure if worth it. There's not that many matrices written per frame normally.587static int Replace_dl_write_matrix() {588u32_le *dlStruct = (u32_le *)Memory::GetPointerWriteRange(PARAM(0), 3 * 4);589const u32_le *src = (const u32_le *)Memory::GetPointerRange(PARAM(2), 16 * 4);590591if (!dlStruct || !src) {592RETURN(0);593return 60;594}595596u32 matrix = 0;597int count = 12;598switch (PARAM(1)) {599case 3:600matrix = 0x40000000; // tex mtx601break;602case 2:603matrix = 0x3A000000;604break;605case 1:606matrix = 0x3C000000;607break;608case 0:609matrix = 0x3E000000;610count = 16;611break;612}613614u32_le *dest = (u32_le *)Memory::GetPointerWriteRange(dlStruct[2], 4 + count * 4);615if (!dest) {616RETURN(0);617return 60;618}619620*dest++ = matrix;621matrix += 0x01000000;622623if (count == 16) {624// Ultra SIMD friendly! These intrinsics generate pretty much perfect code,625// no point in hand rolling.626#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)627__m128i topBytes = _mm_set1_epi32(matrix);628__m128i m0 = _mm_loadu_si128((const __m128i *)src);629__m128i m1 = _mm_loadu_si128((const __m128i *)(src + 4));630__m128i m2 = _mm_loadu_si128((const __m128i *)(src + 8));631__m128i m3 = _mm_loadu_si128((const __m128i *)(src + 12));632m0 = _mm_or_si128(_mm_srli_epi32(m0, 8), topBytes);633m1 = _mm_or_si128(_mm_srli_epi32(m1, 8), topBytes);634m2 = _mm_or_si128(_mm_srli_epi32(m2, 8), topBytes);635m3 = _mm_or_si128(_mm_srli_epi32(m3, 8), topBytes);636_mm_storeu_si128((__m128i *)dest, m0);637_mm_storeu_si128((__m128i *)(dest + 4), m1);638_mm_storeu_si128((__m128i *)(dest + 8), m2);639_mm_storeu_si128((__m128i *)(dest + 12), m3);640#else641#if 0642//TODO: Finish NEON, make conditional somehow643uint32x4_t topBytes = vdupq_n_u32(matrix);644uint32x4_t m0 = vld1q_u32(dataPtr);645uint32x4_t m1 = vld1q_u32(dataPtr + 4);646uint32x4_t m2 = vld1q_u32(dataPtr + 8);647uint32x4_t m3 = vld1q_u32(dataPtr + 12);648m0 = vorr_u32(vsri_n_u32(m0, 8), topBytes); // TODO: look into VSRI649m1 = vorr_u32(vshr_n_u32(m1, 8), topBytes);650m2 = vorr_u32(vshr_n_u32(m2, 8), topBytes);651m3 = vorr_u32(vshr_n_u32(m3, 8), topBytes);652vst1q_u32(dlPtr, m0);653vst1q_u32(dlPtr + 4, m1);654vst1q_u32(dlPtr + 8, m2);655vst1q_u32(dlPtr + 12, m3);656#endif657for (int i = 0; i < count; i++) {658dest[i] = matrix | (src[i] >> 8);659}660#endif661} else {662#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)663__m128i topBytes = _mm_set1_epi32(matrix);664__m128i m0 = _mm_loadu_si128((const __m128i *)src);665__m128i m1 = _mm_loadu_si128((const __m128i *)(src + 4));666__m128i m2 = _mm_loadu_si128((const __m128i *)(src + 8));667__m128i m3 = _mm_loadu_si128((const __m128i *)(src + 12));668m0 = _mm_or_si128(_mm_srli_epi32(m0, 8), topBytes);669m1 = _mm_or_si128(_mm_srli_epi32(m1, 8), topBytes);670m2 = _mm_or_si128(_mm_srli_epi32(m2, 8), topBytes);671m3 = _mm_or_si128(_mm_srli_epi32(m3, 8), topBytes);672// These three stores overlap by a word, due to the offsets.673_mm_storeu_si128((__m128i *)dest, m0);674_mm_storeu_si128((__m128i *)(dest + 3), m1);675_mm_storeu_si128((__m128i *)(dest + 6), m2);676// Store the last one in parts to not overwrite forwards (probably mostly risk free though)677_mm_storel_epi64((__m128i *)(dest + 9), m3);678m3 = _mm_srli_si128(m3, 8);679_mm_store_ss((float *)(dest + 11), _mm_castsi128_ps(m3));680#else681// Bit tricky to SIMD (note the offsets) but should be doable if not perfect682dest[0] = matrix | (src[0] >> 8);683dest[1] = matrix | (src[1] >> 8);684dest[2] = matrix | (src[2] >> 8);685dest[3] = matrix | (src[4] >> 8);686dest[4] = matrix | (src[5] >> 8);687dest[5] = matrix | (src[6] >> 8);688dest[6] = matrix | (src[8] >> 8);689dest[7] = matrix | (src[9] >> 8);690dest[8] = matrix | (src[10] >> 8);691dest[9] = matrix | (src[12] >> 8);692dest[10] = matrix | (src[13] >> 8);693dest[11] = matrix | (src[14] >> 8);694#endif695}696697NotifyMemInfo(MemBlockFlags::READ, PARAM(2), 16 * sizeof(float), "ReplaceDLWriteMatrix");698NotifyMemInfo(MemBlockFlags::WRITE, PARAM(0) + 2 * sizeof(u32), sizeof(u32), "ReplaceDLWriteMatrix");699NotifyMemInfo(MemBlockFlags::WRITE, dlStruct[2], (count + 1) * sizeof(u32), "ReplaceDLWriteMatrix");700701dlStruct[2] += (1 + count) * 4;702RETURN(dlStruct[2]);703return 60;704}705706static bool GetMIPSStaticAddress(u32 &addr, s32 lui_offset, s32 lw_offset) {707const MIPSOpcode upper = Memory::Read_Instruction(currentMIPS->pc + lui_offset, true);708if (upper != MIPS_MAKE_LUI(MIPS_GET_RT(upper), upper & 0xffff)) {709return false;710}711const MIPSOpcode lower = Memory::Read_Instruction(currentMIPS->pc + lw_offset, true);712if (lower != MIPS_MAKE_LW(MIPS_GET_RT(lower), MIPS_GET_RS(lower), lower & 0xffff)) {713if (lower != MIPS_MAKE_ORI(MIPS_GET_RT(lower), MIPS_GET_RS(lower), lower & 0xffff)) {714return false;715}716}717addr = ((upper & 0xffff) << 16) + (s16)(lower & 0xffff);718return true;719}720721static bool GetMIPSGPAddress(u32 &addr, s32 offset) {722const MIPSOpcode loadOp = Memory::Read_Instruction(currentMIPS->pc + offset, true);723if (MIPS_GET_RS(loadOp) == MIPS_REG_GP) {724s16 gpoff = (s16)(u16)(loadOp & 0x0000FFFF);725addr = currentMIPS->r[MIPS_REG_GP] + gpoff;726return true;727}728729return false;730}731732static int Hook_godseaterburst_blit_texture() {733u32 texaddr;734// Only if there's no texture.735if (!GetMIPSStaticAddress(texaddr, 0x000c, 0x0030)) {736return 0;737}738u32 fb_infoaddr;739if (Memory::Read_U32(texaddr) != 0 || !GetMIPSStaticAddress(fb_infoaddr, 0x01d0, 0x01d4)) {740return 0;741}742743const u32 fb_info = Memory::Read_U32(fb_infoaddr);744const u32 fb_address = Memory::Read_U32(fb_info);745if (Memory::IsVRAMAddress(fb_address)) {746gpu->PerformReadbackToMemory(fb_address, 0x00044000);747NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "godseaterburst_blit_texture");748}749return 0;750}751752static int Hook_godseaterburst_depthmask_5551() {753// This function copies the 5551 framebuffer to a temporary, generating alpha based on depth.754// Depth is optional, in which case all pixels get full alpha.755// Called when your avatar changes to screenshot for save data.756uint32_t colorBuffer = currentMIPS->r[MIPS_REG_A1];757uint32_t depthBuffer = currentMIPS->r[MIPS_REG_T2];758uint32_t byteStride = currentMIPS->r[MIPS_REG_A2];759uint32_t height = currentMIPS->r[MIPS_REG_T1];760uint32_t size = byteStride * height;761762if (!Memory::IsVRAMAddress(colorBuffer) || !Memory::IsValidRange(colorBuffer, size))763return 0;764if (depthBuffer != 0) {765if (!Memory::IsVRAMAddress(colorBuffer) || !Memory::IsValidRange(depthBuffer, size))766return 0;767768// This is added to read from the linearized mirror.769uint32_t depthMirror = depthBuffer + 0x00200000;770// Depth download required, or it won't work and will be transparent.771gpu->PerformMemoryCopy(depthMirror, depthMirror, size, GPUCopyFlag::FORCE_DST_MATCH_MEM | GPUCopyFlag::DEPTH_REQUESTED);772NotifyMemInfo(MemBlockFlags::WRITE, depthMirror, size, "godseaterburst_depthmask_5551");773}774775gpu->PerformReadbackToMemory(colorBuffer, size);776NotifyMemInfo(MemBlockFlags::WRITE, colorBuffer, size, "godseaterburst_depthmask_5551");777778return 0;779}780781static int Hook_hexyzforce_monoclome_thread() {782u32 fb_info;783if (!GetMIPSStaticAddress(fb_info, -4, 0)) {784return 0;785}786787const u32 fb_address = Memory::Read_U32(fb_info);788if (Memory::IsVRAMAddress(fb_address)) {789gpu->PerformReadbackToMemory(fb_address, 0x00088000);790NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "hexyzforce_monoclome_thread");791}792return 0;793}794795static int Hook_starocean_write_stencil() {796const u32 fb_address = currentMIPS->r[MIPS_REG_T7];797if (Memory::IsVRAMAddress(fb_address)) {798gpu->PerformWriteStencilFromMemory(fb_address, 0x00088000, WriteStencil::IGNORE_ALPHA);799}800return 0;801}802803static int Hook_topx_create_saveicon() {804const u32 fb_address = currentMIPS->r[MIPS_REG_V0];805if (Memory::IsVRAMAddress(fb_address)) {806gpu->PerformMemoryCopy(fb_address, fb_address, 0x00044000, GPUCopyFlag::FORCE_DST_MATCH_MEM | GPUCopyFlag::DISALLOW_CREATE_VFB);807NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "topx_create_saveicon");808}809return 0;810}811812static int Hook_ff1_battle_effect() {813const u32 fb_address = currentMIPS->r[MIPS_REG_A1];814if (Memory::IsVRAMAddress(fb_address)) {815gpu->PerformReadbackToMemory(fb_address, 0x00088000);816NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "ff1_battle_effect");817}818return 0;819}820821static int Hook_dissidia_recordframe_avi() {822// This is called once per frame, and records that frame's data to avi.823const u32 fb_address = currentMIPS->r[MIPS_REG_A1];824if (Memory::IsVRAMAddress(fb_address)) {825gpu->PerformReadbackToMemory(fb_address, 0x00044000);826NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "dissidia_recordframe_avi");827}828return 0;829}830831static int Hook_brandish_download_frame() {832u32 fb_infoaddr;833if (!GetMIPSStaticAddress(fb_infoaddr, 0x2c, 0x30)) {834return 0;835}836const u32 fb_info = Memory::Read_U32(fb_infoaddr);837const MIPSOpcode fb_index_load = Memory::Read_Instruction(currentMIPS->pc + 0x38, true);838if (fb_index_load != MIPS_MAKE_LW(MIPS_GET_RT(fb_index_load), MIPS_GET_RS(fb_index_load), fb_index_load & 0xffff)) {839return 0;840}841const int fb_index_offset = (s16)(fb_index_load & 0xffff);842const u32 fb_index = (Memory::Read_U32(fb_info + fb_index_offset) + 1) & 1;843const u32 fb_address = 0x4000000 + (0x44000 * fb_index);844const u32 dest_address = currentMIPS->r[MIPS_REG_A1];845if (Memory::IsRAMAddress(dest_address)) {846gpu->PerformReadbackToMemory(fb_address, 0x00044000);847NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "brandish_download_frame");848}849return 0;850}851852static int Hook_growlanser_create_saveicon() {853const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 4);854const u32 fmt = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP]);855const u32 sz = fmt == GE_FORMAT_8888 ? 0x00088000 : 0x00044000;856if (Memory::IsVRAMAddress(fb_address) && fmt <= 3) {857gpu->PerformMemoryCopy(fb_address, fb_address, sz, GPUCopyFlag::FORCE_DST_MATCH_MEM | GPUCopyFlag::DISALLOW_CREATE_VFB);858NotifyMemInfo(MemBlockFlags::WRITE, fb_address, sz, "growlanser_create_saveicon");859}860return 0;861}862863static int Hook_sd_gundam_g_generation_download_frame() {864const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 8);865const u32 fmt = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 4);866const u32 sz = fmt == GE_FORMAT_8888 ? 0x00088000 : 0x00044000;867if (Memory::IsVRAMAddress(fb_address) && fmt <= 3) {868gpu->PerformReadbackToMemory(fb_address, sz);869NotifyMemInfo(MemBlockFlags::WRITE, fb_address, sz, "sd_gundam_g_generation_download_frame");870}871return 0;872}873874static int Hook_narisokonai_download_frame() {875const u32 fb_address = currentMIPS->r[MIPS_REG_V0];876if (Memory::IsVRAMAddress(fb_address)) {877gpu->PerformReadbackToMemory(fb_address, 0x00044000);878NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "narisokonai_download_frame");879}880return 0;881}882883static int Hook_kirameki_school_life_download_frame() {884const u32 fb_address = currentMIPS->r[MIPS_REG_A2];885if (Memory::IsVRAMAddress(fb_address)) {886gpu->PerformReadbackToMemory(fb_address, 0x00088000);887NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kirameki_school_life_download_frame");888}889return 0;890}891892static int Hook_orenoimouto_download_frame() {893const u32 fb_address = currentMIPS->r[MIPS_REG_A4];894if (Memory::IsVRAMAddress(fb_address)) {895gpu->PerformReadbackToMemory(fb_address, 0x00088000);896NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "orenoimouto_download_frame");897}898return 0;899}900901static int Hook_sakurasou_download_frame() {902const u32 fb_address = currentMIPS->r[MIPS_REG_V0];903if (Memory::IsVRAMAddress(fb_address)) {904gpu->PerformReadbackToMemory(fb_address, 0x00088000);905NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "sakurasou_download_frame");906}907return 0;908}909910static int Hook_suikoden1_and_2_download_frame_1() {911const u32 fb_address = currentMIPS->r[MIPS_REG_S4];912if (Memory::IsVRAMAddress(fb_address)) {913gpu->PerformReadbackToMemory(fb_address, 0x00088000);914NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "suikoden1_and_2_download_frame_1");915}916return 0;917}918919static int Hook_suikoden1_and_2_download_frame_2() {920const u32 fb_address = currentMIPS->r[MIPS_REG_S2];921if (Memory::IsVRAMAddress(fb_address)) {922gpu->PerformReadbackToMemory(fb_address, 0x00088000);923NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "suikoden1_and_2_download_frame_2");924}925return 0;926}927928static int Hook_rezel_cross_download_frame() {929const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 0x1C);930const u32 fmt = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 0x14);931const u32 sz = fmt == GE_FORMAT_8888 ? 0x00088000 : 0x00044000;932if (Memory::IsVRAMAddress(fb_address) && fmt <= 3) {933gpu->PerformReadbackToMemory(fb_address, sz);934NotifyMemInfo(MemBlockFlags::WRITE, fb_address, sz, "rezel_cross_download_frame");935}936return 0;937}938939static int Hook_kagaku_no_ensemble_download_frame() {940const u32 fb_address = currentMIPS->r[MIPS_REG_V0];941if (Memory::IsVRAMAddress(fb_address)) {942gpu->PerformReadbackToMemory(fb_address, 0x00088000);943NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kagaku_no_ensemble_download_frame");944}945return 0;946}947948static int Hook_soranokiseki_fc_download_frame() {949const u32 fb_address = currentMIPS->r[MIPS_REG_A2];950if (Memory::IsVRAMAddress(fb_address)) {951gpu->PerformReadbackToMemory(fb_address, 0x00044000);952NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "soranokiseki_fc_download_frame");953}954return 0;955}956957static int Hook_soranokiseki_sc_download_frame() {958u32 fb_infoaddr;959if (!GetMIPSStaticAddress(fb_infoaddr, 0x28, 0x2C)) {960return 0;961}962const u32 fb_info = Memory::Read_U32(fb_infoaddr);963const MIPSOpcode fb_index_load = Memory::Read_Instruction(currentMIPS->pc + 0x34, true);964if (fb_index_load != MIPS_MAKE_LW(MIPS_GET_RT(fb_index_load), MIPS_GET_RS(fb_index_load), fb_index_load & 0xffff)) {965return 0;966}967const int fb_index_offset = (s16)(fb_index_load & 0xffff);968const u32 fb_index = (Memory::Read_U32(fb_info + fb_index_offset) + 1) & 1;969const u32 fb_address = 0x4000000 + (0x44000 * fb_index);970const u32 dest_address = currentMIPS->r[MIPS_REG_A1];971if (Memory::IsRAMAddress(dest_address)) {972gpu->PerformReadbackToMemory(fb_address, 0x00044000);973NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "soranokiseki_sc_download_frame");974}975return 0;976}977978static int Hook_bokunonatsuyasumi4_download_frame() {979const u32 fb_address = currentMIPS->r[MIPS_REG_A3];980if (Memory::IsVRAMAddress(fb_address)) {981gpu->PerformReadbackToMemory(fb_address, 0x00044000);982NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "bokunonatsuyasumi4_download_frame");983}984return 0;985}986987static int Hook_danganronpa2_1_download_frame() {988const u32 fb_base = currentMIPS->r[MIPS_REG_V0];989const u32 fb_offset = currentMIPS->r[MIPS_REG_V1];990const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;991const u32 fb_address = fb_base + fb_offset_fix;992if (Memory::IsVRAMAddress(fb_address)) {993gpu->PerformReadbackToMemory(fb_address, 0x00088000);994NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa2_1_download_frame");995}996return 0;997}998999static int Hook_danganronpa2_2_download_frame() {1000const u32 fb_base = currentMIPS->r[MIPS_REG_V0];1001const u32 fb_offset = currentMIPS->r[MIPS_REG_V1];1002const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;1003const u32 fb_address = fb_base + fb_offset_fix;1004if (Memory::IsVRAMAddress(fb_address)) {1005gpu->PerformReadbackToMemory(fb_address, 0x00088000);1006NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa2_2_download_frame");1007}1008return 0;1009}10101011static int Hook_danganronpa1_1_download_frame() {1012const u32 fb_base = currentMIPS->r[MIPS_REG_A5];1013const u32 fb_offset = currentMIPS->r[MIPS_REG_V0];1014const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;1015const u32 fb_address = fb_base + fb_offset_fix;1016if (Memory::IsVRAMAddress(fb_address)) {1017gpu->PerformReadbackToMemory(fb_address, 0x00088000);1018NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa1_1_download_frame");1019}1020return 0;1021}10221023static int Hook_danganronpa1_2_download_frame() {1024const MIPSOpcode instruction = Memory::Read_Instruction(currentMIPS->pc + 0x8, true);1025const int reg_num = instruction >> 11 & 31;1026const u32 fb_base = currentMIPS->r[reg_num];1027const u32 fb_offset = currentMIPS->r[MIPS_REG_V0];1028const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;1029const u32 fb_address = fb_base + fb_offset_fix;1030if (Memory::IsVRAMAddress(fb_address)) {1031gpu->PerformReadbackToMemory(fb_address, 0x00088000);1032NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa1_2_download_frame");1033}1034return 0;1035}10361037static int Hook_kankabanchoutbr_download_frame() {1038const u32 fb_address = currentMIPS->r[MIPS_REG_A1];1039if (Memory::IsVRAMAddress(fb_address)) {1040gpu->PerformReadbackToMemory(fb_address, 0x00044000);1041NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "kankabanchoutbr_download_frame");1042}1043return 0;1044}10451046static int Hook_orenoimouto_download_frame_2() {1047const u32 fb_address = currentMIPS->r[MIPS_REG_A4];1048if (Memory::IsVRAMAddress(fb_address)) {1049gpu->PerformReadbackToMemory(fb_address, 0x00088000);1050NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "orenoimouto_download_frame_2");1051}1052return 0;1053}10541055static int Hook_rewrite_download_frame() {1056const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1057if (Memory::IsVRAMAddress(fb_address)) {1058gpu->PerformReadbackToMemory(fb_address, 0x00088000);1059NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "rewrite_download_frame");1060}1061return 0;1062}10631064static int Hook_kudwafter_download_frame() {1065const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1066if (Memory::IsVRAMAddress(fb_address)) {1067gpu->PerformReadbackToMemory(fb_address, 0x00088000);1068NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kudwafter_download_frame");1069}1070return 0;1071}10721073static int Hook_kumonohatateni_download_frame() {1074const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1075if (Memory::IsVRAMAddress(fb_address)) {1076gpu->PerformReadbackToMemory(fb_address, 0x00088000);1077NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kumonohatateni_download_frame");1078}1079return 0;1080}10811082static int Hook_otomenoheihou_download_frame() {1083const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1084if (Memory::IsVRAMAddress(fb_address)) {1085gpu->PerformReadbackToMemory(fb_address, 0x00088000);1086NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "otomenoheihou_download_frame");1087}1088return 0;1089}10901091static int Hook_grisaianokajitsu_download_frame() {1092const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1093if (Memory::IsVRAMAddress(fb_address)) {1094gpu->PerformReadbackToMemory(fb_address, 0x00088000);1095NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "grisaianokajitsu_download_frame");1096}1097return 0;1098}10991100static int Hook_kokoroconnect_download_frame() {1101const u32 fb_address = currentMIPS->r[MIPS_REG_A3];1102if (Memory::IsVRAMAddress(fb_address)) {1103gpu->PerformReadbackToMemory(fb_address, 0x00088000);1104NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kokoroconnect_download_frame");1105}1106return 0;1107}11081109static int Hook_toheart2_download_frame() {1110const u32 fb_address = currentMIPS->r[MIPS_REG_A1];1111if (Memory::IsVRAMAddress(fb_address)) {1112gpu->PerformReadbackToMemory(fb_address, 0x00044000);1113NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "toheart2_download_frame");1114}1115return 0;1116}11171118static int Hook_toheart2_download_frame_2() {1119const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1120if (Memory::IsVRAMAddress(fb_address)) {1121gpu->PerformReadbackToMemory(fb_address, 0x00088000);1122NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "toheart2_download_frame_2");1123}1124return 0;1125}11261127static int Hook_flowers_download_frame() {1128const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1129if (Memory::IsVRAMAddress(fb_address)) {1130gpu->PerformReadbackToMemory(fb_address, 0x00088000);1131NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "flowers_download_frame");1132}1133return 0;1134}11351136static int Hook_motorstorm_download_frame() {1137const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A1] + 0x18);1138if (Memory::IsVRAMAddress(fb_address)) {1139gpu->PerformReadbackToMemory(fb_address, 0x00088000);1140NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "motorstorm_download_frame");1141}1142return 0;1143}11441145static int Hook_utawarerumono_download_frame() {1146const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1147if (Memory::IsVRAMAddress(fb_address)) {1148gpu->PerformReadbackToMemory(fb_address, 0x00088000);1149NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "utawarerumono_download_frame");1150}1151return 0;1152}11531154static int Hook_photokano_download_frame() {1155const u32 fb_address = currentMIPS->r[MIPS_REG_A1];1156if (Memory::IsVRAMAddress(fb_address)) {1157gpu->PerformReadbackToMemory(fb_address, 0x00088000);1158NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "photokano_download_frame");1159}1160return 0;1161}11621163static int Hook_photokano_download_frame_2() {1164const u32 fb_address = currentMIPS->r[MIPS_REG_A1];1165if (Memory::IsVRAMAddress(fb_address)) {1166gpu->PerformReadbackToMemory(fb_address, 0x00088000);1167NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "photokano_download_frame_2");1168}1169return 0;1170}11711172static int Hook_gakuenheaven_download_frame() {1173const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1174if (Memory::IsVRAMAddress(fb_address)) {1175gpu->PerformReadbackToMemory(fb_address, 0x00088000);1176NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "gakuenheaven_download_frame");1177}1178return 0;1179}11801181static int Hook_youkosohitsujimura_download_frame() {1182const u32 fb_address = currentMIPS->r[MIPS_REG_V0];1183if (Memory::IsVRAMAddress(fb_address)) {1184gpu->PerformReadbackToMemory(fb_address, 0x00088000);1185NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "youkosohitsujimura_download_frame");1186}1187return 0;1188}11891190static int Hook_zettai_hero_update_minimap_tex() {1191const MIPSOpcode storeOffset = Memory::Read_Instruction(currentMIPS->pc + 4, true);1192const uint32_t texAddr = currentMIPS->r[MIPS_REG_A0] + SignExtend16ToS32(storeOffset);1193const uint32_t texSize = 64 * 64 * 1;1194const uint32_t writeAddr = currentMIPS->r[MIPS_REG_V1] + SignExtend16ToS32(storeOffset);1195if (Memory::IsValidRange(texAddr, texSize) && writeAddr >= texAddr && writeAddr < texAddr + texSize) {1196const uint8_t currentValue = Memory::Read_U8(writeAddr);1197if (currentValue != currentMIPS->r[MIPS_REG_A3]) {1198gpu->InvalidateCache(texAddr, texSize, GPU_INVALIDATE_FORCE);1199}1200}1201return 0;1202}12031204static int Hook_tonyhawkp8_upload_tutorial_frame() {1205const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1206if (Memory::IsVRAMAddress(fb_address)) {1207gpu->PerformWriteColorFromMemory(fb_address, 0x00088000);1208}1209return 0;1210}12111212static int Hook_sdgundamggenerationportable_download_frame() {1213const u32 fb_address = currentMIPS->r[MIPS_REG_A3];1214if (Memory::IsVRAMAddress(fb_address)) {1215gpu->PerformReadbackToMemory(fb_address, 0x00088000);1216NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "sdgundamggenerationportable_download_frame");1217}1218return 0;1219}12201221static int Hook_atvoffroadfurypro_download_frame() {1222const u32 fb_address = currentMIPS->r[MIPS_REG_S2];1223const u32 fb_size = (currentMIPS->r[MIPS_REG_S4] >> 3) * currentMIPS->r[MIPS_REG_S3];1224if (Memory::IsVRAMAddress(fb_address)) {1225gpu->PerformReadbackToMemory(fb_address, fb_size);1226NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "atvoffroadfurypro_download_frame");1227}1228return 0;1229}12301231static int Hook_atvoffroadfuryblazintrails_download_frame() {1232const u32 fb_address = currentMIPS->r[MIPS_REG_S5];1233const u32 fb_size = (currentMIPS->r[MIPS_REG_S3] >> 3) * currentMIPS->r[MIPS_REG_S2];1234if (Memory::IsVRAMAddress(fb_address)) {1235gpu->PerformReadbackToMemory(fb_address, fb_size);1236NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "atvoffroadfuryblazintrails_download_frame");1237}1238return 0;1239}12401241static int Hook_littlebustersce_download_frame() {1242const u32 fb_address = currentMIPS->r[MIPS_REG_A0];1243if (Memory::IsVRAMAddress(fb_address)) {1244gpu->PerformReadbackToMemory(fb_address, 0x00088000);1245NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "littlebustersce_download_frame");1246}1247return 0;1248}12491250static int Hook_shinigamitoshoujo_download_frame() {1251const u32 fb_address = currentMIPS->r[MIPS_REG_S2];1252if (Memory::IsVRAMAddress(fb_address)) {1253gpu->PerformReadbackToMemory(fb_address, 0x00088000);1254NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "shinigamitoshoujo_download_frame");1255}1256return 0;1257}12581259static int Hook_atvoffroadfuryprodemo_download_frame() {1260const u32 fb_address = currentMIPS->r[MIPS_REG_S5];1261const u32 fb_size = ((currentMIPS->r[MIPS_REG_A0] + currentMIPS->r[MIPS_REG_A1]) >> 3) * currentMIPS->r[MIPS_REG_S2];1262if (Memory::IsVRAMAddress(fb_address)) {1263gpu->PerformReadbackToMemory(fb_address, fb_size);1264NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "atvoffroadfuryprodemo_download_frame");1265}1266return 0;1267}12681269static int Hook_unendingbloodycall_download_frame() {1270const u32 fb_address = currentMIPS->r[MIPS_REG_T3];1271if (Memory::IsVRAMAddress(fb_address)) {1272gpu->PerformReadbackToMemory(fb_address, 0x00088000);1273NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "unendingbloodycall_download_frame");1274}1275return 0;1276}12771278static int Hook_omertachinmokunookitethelegacy_download_frame() {1279const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 4);1280if (Memory::IsVRAMAddress(fb_address)) {1281gpu->PerformReadbackToMemory(fb_address, 0x00044000);1282NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "omertachinmokunookitethelegacy_download_frame");1283}1284return 0;1285}12861287static int Hook_katamari_render_check() {1288const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x3C);1289const u32 fbInfoPtr = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x40);1290if (Memory::IsVRAMAddress(fb_address) && fbInfoPtr != 0) {1291const u32 sizeInfoPtr = Memory::Read_U32(fbInfoPtr + 0x0C);1292// These are the values it uses to control the loop.1293// Width in memory appears to be stride / 8.1294const u32 width = Memory::Read_U16(sizeInfoPtr + 0x08) * 8;1295// Height in memory is also divided by 8 (but this one isn't hardcoded.)1296const u32 heightBlocks = Memory::Read_U16(sizeInfoPtr + 0x0A);1297// For some reason this is the number of heightBlocks less 1.1298const u32 heightBlockCount = Memory::Read_U8(fbInfoPtr + 0x08) + 1;12991300const u32 totalBytes = width * heightBlocks * heightBlockCount;1301gpu->PerformReadbackToMemory(fb_address, totalBytes);1302NotifyMemInfo(MemBlockFlags::WRITE, fb_address, totalBytes, "katamari_render_check");1303}1304return 0;1305}13061307static int Hook_katamari_screenshot_to_565() {1308u32 fb_address;1309if (GetMIPSStaticAddress(fb_address, 0x0040, 0x0044)) {1310gpu->PerformReadbackToMemory(0x04000000 | fb_address, 0x00088000);1311NotifyMemInfo(MemBlockFlags::WRITE, 0x04000000 | fb_address, 0x00088000, "katamari_screenshot_to_565");1312}1313return 0;1314}13151316static int Hook_mytranwars_upload_frame() {1317u32 fb_address = currentMIPS->r[MIPS_REG_S0];1318if (Memory::IsVRAMAddress(fb_address)) {1319gpu->PerformWriteColorFromMemory(fb_address, 0x00088000);1320}1321return 0;1322}13231324static u32 marvelalliance1_copy_src = 0;1325static u32 marvelalliance1_copy_dst = 0;1326static u32 marvelalliance1_copy_size = 0;13271328static int Hook_marvelalliance1_copy_a1_before() {1329marvelalliance1_copy_src = currentMIPS->r[MIPS_REG_A1];1330marvelalliance1_copy_dst = currentMIPS->r[MIPS_REG_V1];1331marvelalliance1_copy_size = currentMIPS->r[MIPS_REG_V0] - currentMIPS->r[MIPS_REG_A1];13321333if (Memory::IsValidRange(marvelalliance1_copy_src, marvelalliance1_copy_size)) {1334gpu->PerformReadbackToMemory(marvelalliance1_copy_src, marvelalliance1_copy_size);1335NotifyMemInfo(MemBlockFlags::WRITE, marvelalliance1_copy_src, marvelalliance1_copy_size, "marvelalliance1_copy_a1_before");1336}13371338return 0;1339}13401341static int Hook_marvelalliance1_copy_a2_before() {1342marvelalliance1_copy_src = currentMIPS->r[MIPS_REG_A2];1343marvelalliance1_copy_dst = currentMIPS->r[MIPS_REG_V0];1344marvelalliance1_copy_size = currentMIPS->r[MIPS_REG_A1] - currentMIPS->r[MIPS_REG_A2];13451346if (Memory::IsValidRange(marvelalliance1_copy_src, marvelalliance1_copy_size)) {1347gpu->PerformReadbackToMemory(marvelalliance1_copy_src, marvelalliance1_copy_size);1348NotifyMemInfo(MemBlockFlags::WRITE, marvelalliance1_copy_src, marvelalliance1_copy_size, "marvelalliance1_copy_a2_before");1349}13501351return 0;1352}13531354static int Hook_marvelalliance1_copy_after() {1355if (Memory::IsValidRange(marvelalliance1_copy_dst, marvelalliance1_copy_size)) {1356gpu->PerformWriteColorFromMemory(marvelalliance1_copy_dst, marvelalliance1_copy_size);1357NotifyMemInfo(MemBlockFlags::READ, marvelalliance1_copy_dst, marvelalliance1_copy_size, "marvelalliance1_copy_after");1358}13591360return 0;1361}13621363static int Hook_starocean_clear_framebuf_before() {1364skipGPUReplacements |= (int)GPUReplacementSkip::MEMSET;1365return 0;1366}13671368static int Hook_starocean_clear_framebuf_after() {1369skipGPUReplacements &= ~(int)GPUReplacementSkip::MEMSET;13701371// This hook runs after the copy, this is the final memcpy destination.1372u32 framebuf = currentMIPS->r[MIPS_REG_V0] - 512 * 4 * 271;1373u32 y_address, h_address;13741375if (GetMIPSGPAddress(y_address, -204) && GetMIPSGPAddress(h_address, -200)) {1376int y = (s16)Memory::Read_U16(y_address);1377int h = (s16)Memory::Read_U16(h_address);13781379DEBUG_LOG(Log::HLE, "starocean_clear_framebuf() - %08x y=%d-%d", framebuf, y, h);1380// TODO: This is always clearing to 0, actually, which could be faster than an upload.1381gpu->PerformWriteColorFromMemory(framebuf + 512 * y * 4, 512 * h * 4);1382}1383return 0;1384}13851386static int Hook_motorstorm_pixel_read() {1387u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x18);1388u32 fb_height = Memory::Read_U16(currentMIPS->r[MIPS_REG_A0] + 0x26);1389u32 fb_stride = Memory::Read_U16(currentMIPS->r[MIPS_REG_A0] + 0x28);1390gpu->PerformReadbackToMemory(fb_address, fb_height * fb_stride);1391NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_height * fb_stride, "motorstorm_pixel_read");1392return 0;1393}13941395static int Hook_worms_copy_normalize_alpha() {1396// At this point in the function (0x0CC), s1 is the framebuf and a2 is the size.1397u32 fb_address = currentMIPS->r[MIPS_REG_S1];1398u32 fb_size = currentMIPS->r[MIPS_REG_A2];1399if (Memory::IsVRAMAddress(fb_address) && Memory::IsValidRange(fb_address, fb_size)) {1400gpu->PerformReadbackToMemory(fb_address, fb_size);1401NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "worms_copy_normalize_alpha");1402}1403return 0;1404}14051406static int Hook_openseason_data_decode() {1407static u32 firstWritePtr = 0;14081409u32 curWritePtr = currentMIPS->r[MIPS_REG_A0];1410u32 endPtr = currentMIPS->r[MIPS_REG_A1];1411u32 writeBytes = currentMIPS->r[MIPS_REG_V0];1412u32 startPtr = curWritePtr - writeBytes;1413if (Memory::IsVRAMAddress(startPtr) && (firstWritePtr == 0 || startPtr < firstWritePtr)) {1414firstWritePtr = startPtr;1415}1416if (Memory::IsVRAMAddress(endPtr) && curWritePtr == endPtr) {1417gpu->PerformWriteColorFromMemory(firstWritePtr, endPtr - firstWritePtr);1418firstWritePtr = 0;1419}1420return 0;1421}14221423static int Hook_soltrigger_render_ucschar() {1424u32 targetInfoPtrPtr = currentMIPS->r[MIPS_REG_A2];1425u32 targetInfoPtr = Memory::IsValidRange(targetInfoPtrPtr, 4) ? Memory::ReadUnchecked_U32(targetInfoPtrPtr) : 0;1426if (Memory::IsValidRange(targetInfoPtr, 32)) {1427u32 targetPtr = Memory::Read_U32(targetInfoPtr + 8);1428u32 targetByteStride = Memory::Read_U32(targetInfoPtr + 16);14291430// We don't know the height specifically.1431gpu->InvalidateCache(targetPtr, targetByteStride * 512, GPU_INVALIDATE_HINT);1432}1433return 0;1434}14351436static int Hook_gow_fps_hack() {1437if (PSP_CoreParameter().compat.flags().GoWFramerateHack60 || PSP_CoreParameter().compat.flags().FramerateHack30) {1438if (PSP_CoreParameter().compat.flags().FramerateHack30) {1439__DisplayWaitForVblanks("vblank start waited", 2);1440} else {1441__DisplayWaitForVblanks("vblank start waited", 1);1442}1443}1444return 0;1445}14461447static int Hook_blitz_fps_hack() {1448if (PSP_CoreParameter().compat.flags().FramerateHack30) {1449__DisplayWaitForVblanks("vblank start waited", 1);1450}1451return 0;1452}14531454static int Hook_brian_lara_fps_hack() {1455if (PSP_CoreParameter().compat.flags().FramerateHack30) {1456__DisplayWaitForVblanks("vblank start waited", 1);1457}1458return 0;1459}14601461static int Hook_gow_vortex_hack() {1462if (PSP_CoreParameter().compat.flags().GoWFramerateHack60) {1463// from my tests both ==0x3F800000 and !=0x3F800000 takes around 1:40-1:50, that seems to match correct behaviour1464if (currentMIPS->r[MIPS_REG_S1] == 0 && currentMIPS->r[MIPS_REG_A0] == 0xC0 && currentMIPS->r[MIPS_REG_T4] != 0x3F800000) {1465currentMIPS->r[MIPS_REG_S1] = 1;1466}1467}1468return 0;1469}14701471static int Hook_ZZT3_select_hack() {1472if (PSP_CoreParameter().compat.flags().ZZT3SelectHack) {1473if (currentMIPS->r[MIPS_REG_V0] == 0) {1474currentMIPS->r[MIPS_REG_V0] = 1;1475}1476}1477return 0;1478}14791480#define JITFUNC(f) (&MIPSComp::MIPSFrontendInterface::f)14811482// Can either replace with C functions or functions emitted in Asm/ArmAsm.1483static const ReplacementTableEntry entries[] = {1484// TODO: I think some games can be helped quite a bit by implementing the1485// double-precision soft-float routines: __adddf3, __subdf3 and so on. These1486// should of course be implemented JIT style, inline.14871488/* These two collide (same hash) and thus can't be replaced :/1489{ "asinf", &Replace_asinf, 0, REPFLAG_DISABLED },1490{ "acosf", &Replace_acosf, 0, REPFLAG_DISABLED },1491*/14921493{ "sinf", &Replace_sinf, 0, REPFLAG_DISABLED },1494{ "cosf", &Replace_cosf, 0, REPFLAG_DISABLED },1495{ "tanf", &Replace_tanf, 0, REPFLAG_DISABLED },1496{ "atanf", &Replace_atanf, 0, REPFLAG_DISABLED },1497{ "sqrtf", &Replace_sqrtf, 0, REPFLAG_DISABLED },1498{ "atan2f", &Replace_atan2f, 0, REPFLAG_DISABLED },1499{ "floorf", &Replace_floorf, 0, REPFLAG_DISABLED },1500{ "ceilf", &Replace_ceilf, 0, REPFLAG_DISABLED },15011502{ "memcpy", &Replace_memcpy, 0, 0 },1503{ "memcpy_jak", &Replace_memcpy_jak, 0, REPFLAG_SLICED },1504{ "memcpy16", &Replace_memcpy16, 0, 0 },1505{ "memcpy_swizzled", &Replace_memcpy_swizzled, 0, 0 },1506{ "memmove", &Replace_memmove, 0, 0 },1507{ "memset", &Replace_memset, 0, 0 },1508{ "memset_jak", &Replace_memset_jak, 0, REPFLAG_SLICED },1509{ "strlen", &Replace_strlen, 0, REPFLAG_DISABLED },1510{ "strcpy", &Replace_strcpy, 0, REPFLAG_DISABLED },1511{ "strncpy", &Replace_strncpy, 0, REPFLAG_DISABLED },1512{ "strcmp", &Replace_strcmp, 0, REPFLAG_DISABLED },1513{ "strncmp", &Replace_strncmp, 0, REPFLAG_DISABLED },1514{ "fabsf", &Replace_fabsf, JITFUNC(Replace_fabsf), REPFLAG_ALLOWINLINE | REPFLAG_DISABLED },1515{ "dl_write_matrix", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED }, // &MIPSComp::Jit::Replace_dl_write_matrix, REPFLAG_DISABLED },1516{ "dl_write_matrix_2", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED },1517{ "gta_dl_write_matrix", &Replace_gta_dl_write_matrix, 0, REPFLAG_DISABLED },1518// dl_write_matrix_3 doesn't take the dl as a parameter, it accesses a global instead. Need to extract the address of the global from the code when replacing...1519// Haven't investigated write_matrix_4 and 5 but I think they are similar to 1 and 2.15201521// { "vmmul_q_transp", &Replace_vmmul_q_transp, 0, REPFLAG_DISABLED },15221523{ "godseaterburst_blit_texture", &Hook_godseaterburst_blit_texture, 0, REPFLAG_HOOKENTER },1524{ "godseaterburst_depthmask_5551", &Hook_godseaterburst_depthmask_5551, 0, REPFLAG_HOOKENTER },1525{ "hexyzforce_monoclome_thread", &Hook_hexyzforce_monoclome_thread, 0, REPFLAG_HOOKENTER, 0x58 },1526{ "starocean_write_stencil", &Hook_starocean_write_stencil, 0, REPFLAG_HOOKENTER, 0x260 },1527{ "topx_create_saveicon", &Hook_topx_create_saveicon, 0, REPFLAG_HOOKENTER, 0x34 },1528{ "ff1_battle_effect", &Hook_ff1_battle_effect, 0, REPFLAG_HOOKENTER },1529// This is actually used in other games, not just Dissidia.1530{ "dissidia_recordframe_avi", &Hook_dissidia_recordframe_avi, 0, REPFLAG_HOOKENTER },1531{ "brandish_download_frame", &Hook_brandish_download_frame, 0, REPFLAG_HOOKENTER },1532{ "growlanser_create_saveicon", &Hook_growlanser_create_saveicon, 0, REPFLAG_HOOKENTER, 0x7C },1533{ "sd_gundam_g_generation_download_frame", &Hook_sd_gundam_g_generation_download_frame, 0, REPFLAG_HOOKENTER, 0x48},1534{ "narisokonai_download_frame", &Hook_narisokonai_download_frame, 0, REPFLAG_HOOKENTER, 0x14 },1535{ "kirameki_school_life_download_frame", &Hook_kirameki_school_life_download_frame, 0, REPFLAG_HOOKENTER },1536{ "orenoimouto_download_frame", &Hook_orenoimouto_download_frame, 0, REPFLAG_HOOKENTER },1537{ "sakurasou_download_frame", &Hook_sakurasou_download_frame, 0, REPFLAG_HOOKENTER, 0xF8 },1538{ "suikoden1_and_2_download_frame_1", &Hook_suikoden1_and_2_download_frame_1, 0, REPFLAG_HOOKENTER, 0x9C },1539{ "suikoden1_and_2_download_frame_2", &Hook_suikoden1_and_2_download_frame_2, 0, REPFLAG_HOOKENTER, 0x48 },1540{ "rezel_cross_download_frame", &Hook_rezel_cross_download_frame, 0, REPFLAG_HOOKENTER, 0x54 },1541{ "kagaku_no_ensemble_download_frame", &Hook_kagaku_no_ensemble_download_frame, 0, REPFLAG_HOOKENTER, 0x38 },1542{ "soranokiseki_fc_download_frame", &Hook_soranokiseki_fc_download_frame, 0, REPFLAG_HOOKENTER, 0x180 },1543{ "soranokiseki_sc_download_frame", &Hook_soranokiseki_sc_download_frame, 0, REPFLAG_HOOKENTER, },1544{ "bokunonatsuyasumi4_download_frame", &Hook_bokunonatsuyasumi4_download_frame, 0, REPFLAG_HOOKENTER, 0x8C },1545{ "danganronpa2_1_download_frame", &Hook_danganronpa2_1_download_frame, 0, REPFLAG_HOOKENTER, 0x68 },1546{ "danganronpa2_2_download_frame", &Hook_danganronpa2_2_download_frame, 0, REPFLAG_HOOKENTER, 0x94 },1547{ "danganronpa1_1_download_frame", &Hook_danganronpa1_1_download_frame, 0, REPFLAG_HOOKENTER, 0x78 },1548{ "danganronpa1_2_download_frame", &Hook_danganronpa1_2_download_frame, 0, REPFLAG_HOOKENTER, 0xA8 },1549{ "kankabanchoutbr_download_frame", &Hook_kankabanchoutbr_download_frame, 0, REPFLAG_HOOKENTER, },1550{ "orenoimouto_download_frame_2", &Hook_orenoimouto_download_frame_2, 0, REPFLAG_HOOKENTER, },1551{ "rewrite_download_frame", &Hook_rewrite_download_frame, 0, REPFLAG_HOOKENTER, 0x5C },1552{ "kudwafter_download_frame", &Hook_kudwafter_download_frame, 0, REPFLAG_HOOKENTER, 0x58 },1553{ "kumonohatateni_download_frame", &Hook_kumonohatateni_download_frame, 0, REPFLAG_HOOKENTER, },1554{ "otomenoheihou_download_frame", &Hook_otomenoheihou_download_frame, 0, REPFLAG_HOOKENTER, 0x14 },1555{ "grisaianokajitsu_download_frame", &Hook_grisaianokajitsu_download_frame, 0, REPFLAG_HOOKENTER, 0x14 },1556{ "kokoroconnect_download_frame", &Hook_kokoroconnect_download_frame, 0, REPFLAG_HOOKENTER, 0x60 },1557{ "toheart2_download_frame", &Hook_toheart2_download_frame, 0, REPFLAG_HOOKENTER, },1558{ "toheart2_download_frame_2", &Hook_toheart2_download_frame_2, 0, REPFLAG_HOOKENTER, 0x18 },1559{ "flowers_download_frame", &Hook_flowers_download_frame, 0, REPFLAG_HOOKENTER, 0x44 },1560{ "motorstorm_download_frame", &Hook_motorstorm_download_frame, 0, REPFLAG_HOOKENTER, },1561{ "utawarerumono_download_frame", &Hook_utawarerumono_download_frame, 0, REPFLAG_HOOKENTER, },1562{ "photokano_download_frame", &Hook_photokano_download_frame, 0, REPFLAG_HOOKENTER, 0x2C },1563{ "photokano_download_frame_2", &Hook_photokano_download_frame_2, 0, REPFLAG_HOOKENTER, },1564{ "gakuenheaven_download_frame", &Hook_gakuenheaven_download_frame, 0, REPFLAG_HOOKENTER, },1565{ "youkosohitsujimura_download_frame", &Hook_youkosohitsujimura_download_frame, 0, REPFLAG_HOOKENTER, 0x94 },1566{ "zettai_hero_update_minimap_tex", &Hook_zettai_hero_update_minimap_tex, 0, REPFLAG_HOOKEXIT, },1567{ "tonyhawkp8_upload_tutorial_frame", &Hook_tonyhawkp8_upload_tutorial_frame, 0, REPFLAG_HOOKENTER, },1568{ "sdgundamggenerationportable_download_frame", &Hook_sdgundamggenerationportable_download_frame, 0, REPFLAG_HOOKENTER, 0x34 },1569{ "atvoffroadfurypro_download_frame", &Hook_atvoffroadfurypro_download_frame, 0, REPFLAG_HOOKENTER, 0xA0 },1570{ "atvoffroadfuryblazintrails_download_frame", &Hook_atvoffroadfuryblazintrails_download_frame, 0, REPFLAG_HOOKENTER, 0x80 },1571{ "littlebustersce_download_frame", &Hook_littlebustersce_download_frame, 0, REPFLAG_HOOKENTER, },1572{ "shinigamitoshoujo_download_frame", &Hook_shinigamitoshoujo_download_frame, 0, REPFLAG_HOOKENTER, 0xBC },1573{ "atvoffroadfuryprodemo_download_frame", &Hook_atvoffroadfuryprodemo_download_frame, 0, REPFLAG_HOOKENTER, 0x80 },1574{ "unendingbloodycall_download_frame", &Hook_unendingbloodycall_download_frame, 0, REPFLAG_HOOKENTER, 0x54 },1575{ "omertachinmokunookitethelegacy_download_frame", &Hook_omertachinmokunookitethelegacy_download_frame, 0, REPFLAG_HOOKENTER, 0x88 },1576{ "katamari_render_check", &Hook_katamari_render_check, 0, REPFLAG_HOOKENTER, 0, },1577{ "katamari_screenshot_to_565", &Hook_katamari_screenshot_to_565, 0, REPFLAG_HOOKENTER, 0 },1578{ "mytranwars_upload_frame", &Hook_mytranwars_upload_frame, 0, REPFLAG_HOOKENTER, 0x128 },1579{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x284 },1580{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x2bc },1581{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x2e8 },1582{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x320 },1583{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a2_before, 0, REPFLAG_HOOKENTER, 0x3b0 },1584{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x3e8 },1585{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a2_before, 0, REPFLAG_HOOKENTER, 0x410 },1586{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x448 },1587{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x600 },1588{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x638 },1589{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x664 },1590{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x69c },1591{ "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_before, 0, REPFLAG_HOOKENTER, 0 },1592{ "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_after, 0, REPFLAG_HOOKEXIT, 0 },1593{ "motorstorm_pixel_read", &Hook_motorstorm_pixel_read, 0, REPFLAG_HOOKENTER, 0 },1594{ "worms_copy_normalize_alpha", &Hook_worms_copy_normalize_alpha, 0, REPFLAG_HOOKENTER, 0x0CC },1595{ "openseason_data_decode", &Hook_openseason_data_decode, 0, REPFLAG_HOOKENTER, 0x2F0 },1596{ "soltrigger_render_ucschar", &Hook_soltrigger_render_ucschar, 0, REPFLAG_HOOKENTER, 0 },1597{ "gow_fps_hack", &Hook_gow_fps_hack, 0, REPFLAG_HOOKEXIT , 0 },1598{ "gow_vortex_hack", &Hook_gow_vortex_hack, 0, REPFLAG_HOOKENTER, 0x60 },1599{ "ZZT3_select_hack", &Hook_ZZT3_select_hack, 0, REPFLAG_HOOKENTER, 0xC4 },1600{ "blitz_fps_hack", &Hook_blitz_fps_hack, 0, REPFLAG_HOOKEXIT , 0 },1601{ "brian_lara_fps_hack", &Hook_brian_lara_fps_hack, 0, REPFLAG_HOOKEXIT , 0 },1602{}1603};160416051606static std::map<u32, u32> replacedInstructions;1607static std::unordered_map<std::string, std::vector<int> > replacementNameLookup;16081609void Replacement_Init() {1610for (int i = 0; i < (int)ARRAY_SIZE(entries); i++) {1611const auto entry = &entries[i];1612if (!entry->name || (entry->flags & REPFLAG_DISABLED) != 0)1613continue;1614replacementNameLookup[entry->name].push_back(i);1615}16161617skipGPUReplacements = 0;1618}16191620void Replacement_Shutdown() {1621replacedInstructions.clear();1622replacementNameLookup.clear();1623}16241625int GetNumReplacementFuncs() {1626return ARRAY_SIZE(entries);1627}16281629std::vector<int> GetReplacementFuncIndexes(u64 hash, int funcSize) {1630const char *name = MIPSAnalyst::LookupHash(hash, funcSize);1631std::vector<int> emptyResult;1632if (!name) {1633return emptyResult;1634}16351636auto index = replacementNameLookup.find(name);1637if (index != replacementNameLookup.end()) {1638return index->second;1639}1640return emptyResult;1641}16421643const ReplacementTableEntry *GetReplacementFunc(size_t i) {1644if (i >= ARRAY_SIZE(entries)) {1645return nullptr;1646}1647return &entries[i];1648}16491650static bool WriteReplaceInstruction(u32 address, int index) {1651u32 prevInstr = Memory::Read_Instruction(address, false).encoding;1652if (MIPS_IS_REPLACEMENT(prevInstr)) {1653int prevIndex = prevInstr & MIPS_EMUHACK_VALUE_MASK;1654if (prevIndex == index) {1655return false;1656}1657WARN_LOG(Log::HLE, "Replacement func changed at %08x (%d -> %d)", address, prevIndex, index);1658// Make sure we don't save the old replacement.1659prevInstr = replacedInstructions[address];1660}16611662if (MIPS_IS_RUNBLOCK(Memory::Read_U32(address))) {1663WARN_LOG(Log::HLE, "Replacing jitted func address %08x", address);1664}1665replacedInstructions[address] = prevInstr;1666Memory::Write_U32(MIPS_EMUHACK_CALL_REPLACEMENT | index, address);1667return true;1668}16691670void WriteReplaceInstructions(u32 address, u64 hash, int size) {1671std::vector<int> indexes = GetReplacementFuncIndexes(hash, size);1672for (int index : indexes) {1673bool didReplace = false;1674const ReplacementTableEntry *entry = GetReplacementFunc(index);1675if (entry->flags & REPFLAG_HOOKEXIT) {1676// When hooking func exit, we search for jr ra, and replace those.1677for (u32 offset = 0; offset < (u32)size; offset += 4) {1678const u32 op = Memory::Read_Instruction(address + offset, false).encoding;1679if (op == MIPS_MAKE_JR_RA()) {1680if (WriteReplaceInstruction(address + offset, index)) {1681didReplace = true;1682}1683}1684}1685} else if (entry->flags & REPFLAG_HOOKENTER) {1686if (WriteReplaceInstruction(address + entry->hookOffset, index)) {1687didReplace = true;1688}1689} else {1690if (WriteReplaceInstruction(address, index)) {1691didReplace = true;1692}1693}16941695if (didReplace) {1696INFO_LOG(Log::HLE, "Replaced %s at %08x with hash %016llx", entries[index].name, address, hash);1697}1698}1699}17001701void RestoreReplacedInstruction(u32 address) {1702const u32 curInstr = Memory::Read_U32(address);1703if (MIPS_IS_REPLACEMENT(curInstr)) {1704Memory::Write_U32(replacedInstructions[address], address);1705NOTICE_LOG(Log::HLE, "Restored replaced func at %08x", address);1706} else {1707NOTICE_LOG(Log::HLE, "Replaced func changed at %08x", address);1708}1709replacedInstructions.erase(address);1710}17111712void RestoreReplacedInstructions(u32 startAddr, u32 endAddr) {1713if (endAddr == startAddr)1714return;1715// Need to be in order, or we'll hang.1716if (endAddr < startAddr)1717std::swap(endAddr, startAddr);1718const auto start = replacedInstructions.lower_bound(startAddr);1719const auto end = replacedInstructions.upper_bound(endAddr);1720int restored = 0;1721for (auto it = start; it != end; ++it) {1722const u32 addr = it->first;1723const u32 curInstr = Memory::Read_U32(addr);1724if (MIPS_IS_REPLACEMENT(curInstr)) {1725Memory::Write_U32(it->second, addr);1726++restored;1727}1728}1729INFO_LOG(Log::HLE, "Restored %d replaced funcs between %08x-%08x", restored, startAddr, endAddr);1730replacedInstructions.erase(start, end);1731}17321733std::map<u32, u32> SaveAndClearReplacements() {1734std::map<u32, u32> saved;1735for (const auto &[addr, instr] : replacedInstructions) {1736// This will not retain jit blocks.1737const u32 curInstr = Memory::Read_Opcode_JIT(addr).encoding;1738if (MIPS_IS_REPLACEMENT(curInstr)) {1739saved[addr] = curInstr;1740Memory::Write_U32(instr, addr);1741}1742}1743return saved;1744}17451746void RestoreSavedReplacements(const std::map<u32, u32> &saved) {1747for (const auto &[addr, instr] : saved) {1748// Just put the replacements back.1749Memory::Write_U32(instr, addr);1750}1751}17521753bool GetReplacedOpAt(u32 address, u32 *op) {1754u32 instr = Memory::Read_Opcode_JIT(address).encoding;1755if (MIPS_IS_REPLACEMENT(instr)) {1756auto iter = replacedInstructions.find(address);1757if (iter != replacedInstructions.end()) {1758*op = iter->second;1759return true;1760} else {1761return false;1762}1763}1764return false;1765}17661767bool CanReplaceJalTo(u32 dest, const ReplacementTableEntry **entry, u32 *funcSize) {1768MIPSOpcode op(Memory::Read_Opcode_JIT(dest));1769if (!MIPS_IS_REPLACEMENT(op.encoding))1770return false;17711772// Make sure we don't replace if there are any breakpoints inside.1773*funcSize = g_symbolMap->GetFunctionSize(dest);1774if (*funcSize == SymbolMap::INVALID_ADDRESS) {1775if (CBreakPoints::IsAddressBreakPoint(dest)) {1776return false;1777}1778*funcSize = (u32)sizeof(u32);1779} else {1780if (CBreakPoints::RangeContainsBreakPoint(dest, *funcSize)) {1781return false;1782}1783}17841785int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;1786*entry = GetReplacementFunc(index);1787if (!*entry) {1788ERROR_LOG(Log::HLE, "ReplaceJalTo: Invalid replacement op %08x at %08x", op.encoding, dest);1789return false;1790}17911792if ((*entry)->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT | REPFLAG_DISABLED | REPFLAG_SLICED)) {1793// If it's a hook, we can't replace the jal, we have to go inside the func.1794return false;1795}1796return true;1797}179817991800