Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/HLE/ReplaceTables.cpp
3187 views
1
// Copyright (c) 2013- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#include <algorithm>
21
#include <map>
22
#include <unordered_map>
23
24
#include "Common/CommonTypes.h"
25
#include "Common/Log.h"
26
#include "Common/Swap.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"
38
39
#include "GPU/Math3D.h"
40
#include "GPU/GPU.h"
41
#include "GPU/GPUCommon.h"
42
#include "Common/Math/SIMDHeaders.h"
43
44
enum class GPUReplacementSkip {
45
MEMSET = 1,
46
MEMCPY = 2,
47
MEMMOVE = 4,
48
};
49
50
static int skipGPUReplacements = 0;
51
52
// I think these have to be pretty accurate as these are libc replacements,
53
// but we can probably get away with approximating the VFPU vsin/vcos and vrot
54
// pretty roughly.
55
static int Replace_sinf() {
56
float f = PARAMF(0);
57
RETURNF(sinf(f));
58
return 80; // guess number of cycles
59
}
60
61
static int Replace_cosf() {
62
float f = PARAMF(0);
63
RETURNF(cosf(f));
64
return 80; // guess number of cycles
65
}
66
67
static int Replace_tanf() {
68
float f = PARAMF(0);
69
RETURNF(tanf(f));
70
return 80; // guess number of cycles
71
}
72
73
static int Replace_acosf() {
74
float f = PARAMF(0);
75
RETURNF(acosf(f));
76
return 80; // guess number of cycles
77
}
78
79
static int Replace_asinf() {
80
float f = PARAMF(0);
81
RETURNF(asinf(f));
82
return 80; // guess number of cycles
83
}
84
85
static int Replace_atanf() {
86
float f = PARAMF(0);
87
RETURNF(atanf(f));
88
return 80; // guess number of cycles
89
}
90
91
static int Replace_sqrtf() {
92
float f = PARAMF(0);
93
RETURNF(sqrtf(f));
94
return 80; // guess number of cycles
95
}
96
97
static int Replace_atan2f() {
98
float f1 = PARAMF(0);
99
float f2 = PARAMF(1);
100
RETURNF(atan2f(f1, f2));
101
return 120; // guess number of cycles
102
}
103
104
static int Replace_floorf() {
105
float f1 = PARAMF(0);
106
RETURNF(floorf(f1));
107
return 30; // guess number of cycles
108
}
109
110
static int Replace_ceilf() {
111
float f1 = PARAMF(0);
112
RETURNF(ceilf(f1));
113
return 30; // guess number of cycles
114
}
115
116
// Should probably do JIT versions of this, possibly ones that only delegate
117
// large copies to a C function.
118
static int Replace_memcpy() {
119
u32 destPtr = PARAM(0);
120
u32 srcPtr = PARAM(1);
121
u32 bytes = PARAM(2);
122
bool skip = false;
123
if (!bytes) {
124
RETURN(destPtr);
125
return 10;
126
}
127
128
// Some games use memcpy on executable code. We need to flush emuhack ops.
129
currentMIPS->InvalidateICache(srcPtr, bytes);
130
if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) {
131
if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {
132
skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);
133
}
134
}
135
if (!skip && bytes != 0) {
136
u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);
137
const u8 *src = Memory::GetPointerRange(srcPtr, bytes);
138
139
if (!dst || !src) {
140
// Already logged.
141
} else if (std::min(destPtr, srcPtr) + bytes > std::max(destPtr, srcPtr)) {
142
// Overlap. Star Ocean breaks if it's not handled in 16 bytes blocks.
143
const u32 blocks = bytes & ~0x0f;
144
for (u32 offset = 0; offset < blocks; offset += 0x10) {
145
memcpy(dst + offset, src + offset, 0x10);
146
}
147
for (u32 offset = blocks; offset < bytes; ++offset) {
148
dst[offset] = src[offset];
149
}
150
} else {
151
memmove(dst, src, bytes);
152
}
153
}
154
RETURN(destPtr);
155
156
if (MemBlockInfoDetailed(bytes)) {
157
// It's pretty common that games will copy video data.
158
// Detect that by manually reading the tag when the size looks right.
159
if (bytes == 512 * 272 * 4) {
160
char tagData[128];
161
size_t tagSize = FormatMemWriteTagAt(tagData, sizeof(tagData), "ReplaceMemcpy/", srcPtr, bytes);
162
NotifyMemInfo(MemBlockFlags::READ, srcPtr, bytes, tagData, tagSize);
163
NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, tagData, tagSize);
164
165
if (!strcmp(tagData, "ReplaceMemcpy/VideoDecode") || !strcmp(tagData, "ReplaceMemcpy/VideoDecodeRange")) {
166
gpu->PerformWriteFormattedFromMemory(destPtr, bytes, 512, GE_FORMAT_8888);
167
}
168
} else {
169
NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemcpy/");
170
}
171
}
172
173
return 10 + bytes / 4; // approximation
174
}
175
176
static int Replace_memcpy_jak() {
177
u32 destPtr = PARAM(0);
178
u32 srcPtr = PARAM(1);
179
u32 bytes = PARAM(2);
180
181
if (bytes == 0) {
182
RETURN(destPtr);
183
return 5;
184
}
185
186
bool skip = false;
187
bool sliced = false;
188
static constexpr uint32_t SLICE_SIZE = 32768;
189
190
currentMIPS->InvalidateICache(srcPtr, bytes);
191
if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) {
192
if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {
193
skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);
194
}
195
}
196
if (!skip && bytes > SLICE_SIZE && bytes != 512 * 272 * 4 && !PSP_CoreParameter().compat.flags().DisableMemcpySlicing) {
197
// This is a very slow func. To avoid thread blocking, do a slice at a time.
198
// Avoiding exactly 512 * 272 * 4 to detect videos, though.
199
bytes = SLICE_SIZE;
200
sliced = true;
201
}
202
if (!skip && bytes != 0) {
203
u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);
204
const u8 *src = Memory::GetPointerRange(srcPtr, bytes);
205
206
if (dst && src) {
207
// Jak style overlap.
208
for (u32 i = 0; i < bytes; i++) {
209
dst[i] = src[i];
210
}
211
}
212
}
213
214
if (sliced) {
215
currentMIPS->r[MIPS_REG_A0] += SLICE_SIZE;
216
currentMIPS->r[MIPS_REG_A1] += SLICE_SIZE;
217
currentMIPS->r[MIPS_REG_A2] -= SLICE_SIZE;
218
} else {
219
// Jak relies on more registers coming out right than the ABI specifies.
220
// See the disassembly of the function for the explanations for these...
221
currentMIPS->r[MIPS_REG_T0] = 0;
222
currentMIPS->r[MIPS_REG_A0] = -1;
223
currentMIPS->r[MIPS_REG_A2] = 0;
224
// Even after slicing, this ends up correct.
225
currentMIPS->r[MIPS_REG_A3] = destPtr + bytes;
226
RETURN(destPtr);
227
}
228
229
if (MemBlockInfoDetailed(bytes)) {
230
// It's pretty common that games will copy video data.
231
// Detect that by manually reading the tag when the size looks right.
232
if (bytes == 512 * 272 * 4) {
233
char tagData[128];
234
size_t tagSize = FormatMemWriteTagAt(tagData, sizeof(tagData), "ReplaceMemcpy/", srcPtr, bytes);
235
NotifyMemInfo(MemBlockFlags::READ, srcPtr, bytes, tagData, tagSize);
236
NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, tagData, tagSize);
237
238
if (!strcmp(tagData, "ReplaceMemcpy/VideoDecode") || !strcmp(tagData, "ReplaceMemcpy/VideoDecodeRange")) {
239
gpu->PerformWriteFormattedFromMemory(destPtr, bytes, 512, GE_FORMAT_8888);
240
}
241
} else {
242
NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemcpy/");
243
}
244
}
245
246
if (sliced) {
247
// Negative causes the function to be run again for the next slice.
248
return 5 + bytes * -8 + 2;
249
}
250
return 5 + bytes * 8 + 2; // approximation. This is a slow memcpy - a byte copy loop..
251
}
252
253
static int Replace_memcpy16() {
254
u32 destPtr = PARAM(0);
255
u32 srcPtr = PARAM(1);
256
u32 bytes = PARAM(2) * 16;
257
bool skip = false;
258
259
// Some games use memcpy on executable code. We need to flush emuhack ops.
260
if (bytes != 0)
261
currentMIPS->InvalidateICache(srcPtr, bytes);
262
if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0 && bytes != 0) {
263
if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {
264
skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);
265
}
266
}
267
if (!skip && bytes != 0) {
268
u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);
269
const u8 *src = Memory::GetPointerRange(srcPtr, bytes);
270
if (dst && src) {
271
memmove(dst, src, bytes);
272
}
273
}
274
RETURN(destPtr);
275
276
if (MemBlockInfoDetailed(bytes)) {
277
NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemcpy16/");
278
}
279
280
return 10 + bytes / 4; // approximation
281
}
282
283
static int Replace_memcpy_swizzled() {
284
u32 destPtr = PARAM(0);
285
u32 srcPtr = PARAM(1);
286
u32 pitch = PARAM(2);
287
u32 h = PARAM(4);
288
if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) {
289
if (Memory::IsVRAMAddress(srcPtr)) {
290
gpu->PerformReadbackToMemory(srcPtr, pitch * h);
291
}
292
}
293
u8 *dstp = Memory::GetPointerWriteRange(destPtr, pitch * h);
294
const u8 *srcp = Memory::GetPointerRange(srcPtr, pitch * h);
295
296
if (dstp && srcp) {
297
const u8 *ysrcp = srcp;
298
for (u32 y = 0; y < h; y += 8) {
299
const u8 *xsrcp = ysrcp;
300
for (u32 x = 0; x < pitch; x += 16) {
301
const u8 *src = xsrcp;
302
for (int n = 0; n < 8; ++n) {
303
memcpy(dstp, src, 16);
304
src += pitch;
305
dstp += 16;
306
}
307
xsrcp += 16;
308
}
309
ysrcp += 8 * pitch;
310
}
311
}
312
313
RETURN(0);
314
315
if (MemBlockInfoDetailed(pitch * h)) {
316
NotifyMemInfoCopy(destPtr, srcPtr, pitch * h, "ReplaceMemcpySwizzle/");
317
}
318
319
return 10 + (pitch * h) / 4; // approximation
320
}
321
322
static int Replace_memmove() {
323
u32 destPtr = PARAM(0);
324
u32 srcPtr = PARAM(1);
325
u32 bytes = PARAM(2);
326
bool skip = false;
327
328
// Some games use memcpy on executable code. We need to flush emuhack ops.
329
if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMMOVE) == 0 && bytes != 0) {
330
currentMIPS->InvalidateICache(srcPtr, bytes);
331
if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) {
332
skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes);
333
}
334
}
335
if (!skip && bytes != 0) {
336
u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);
337
const u8 *src = Memory::GetPointerRange(srcPtr, bytes);
338
if (dst && src) {
339
memmove(dst, src, bytes);
340
}
341
}
342
RETURN(destPtr);
343
344
if (MemBlockInfoDetailed(bytes)) {
345
NotifyMemInfoCopy(destPtr, srcPtr, bytes, "ReplaceMemmove/");
346
}
347
348
return 10 + bytes / 4; // approximation
349
}
350
351
static int Replace_memset() {
352
u32 destPtr = PARAM(0);
353
u8 value = PARAM(1);
354
u32 bytes = PARAM(2);
355
bool skip = false;
356
if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) {
357
skip = gpu->PerformMemorySet(destPtr, value, bytes);
358
}
359
if (!skip && bytes != 0) {
360
u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);
361
if (dst) {
362
memset(dst, value, bytes);
363
}
364
}
365
RETURN(destPtr);
366
367
NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, "ReplaceMemset");
368
369
return 10 + bytes / 4; // approximation
370
}
371
372
static int Replace_memset_jak() {
373
u32 destPtr = PARAM(0);
374
u8 value = PARAM(1);
375
u32 bytes = PARAM(2);
376
377
if (bytes == 0) {
378
RETURN(destPtr);
379
return 5;
380
}
381
382
bool skip = false;
383
bool sliced = false;
384
static constexpr uint32_t SLICE_SIZE = 32768;
385
if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) {
386
skip = gpu->PerformMemorySet(destPtr, value, bytes);
387
}
388
if (!skip && bytes > SLICE_SIZE && !PSP_CoreParameter().compat.flags().DisableMemcpySlicing) {
389
// This is a very slow func. To avoid thread blocking, do a slice at a time.
390
bytes = SLICE_SIZE;
391
sliced = true;
392
}
393
if (!skip && bytes != 0) {
394
u8 *dst = Memory::GetPointerWriteRange(destPtr, bytes);
395
if (dst) {
396
memset(dst, value, bytes);
397
}
398
}
399
400
NotifyMemInfo(MemBlockFlags::WRITE, destPtr, bytes, "ReplaceMemset");
401
402
if (sliced) {
403
currentMIPS->r[MIPS_REG_A0] += SLICE_SIZE;
404
currentMIPS->r[MIPS_REG_A2] -= SLICE_SIZE;
405
406
// This is approximate, and must be a negative value.
407
// Negative causes the function to be run again for the next slice.
408
return 5 + (int)SLICE_SIZE * -6 + 2;
409
}
410
411
// Even after slicing, this ends up correct.
412
currentMIPS->r[MIPS_REG_T0] = destPtr + bytes;
413
currentMIPS->r[MIPS_REG_A2] = -1;
414
currentMIPS->r[MIPS_REG_A3] = -1;
415
RETURN(destPtr);
416
417
return 5 + bytes * 6 + 2; // approximation
418
}
419
420
static uint32_t SafeStringLen(const uint32_t ptr, uint32_t maxLen = 0x07FFFFFF) {
421
maxLen = Memory::ValidSize(ptr, 0x07FFFFFF);
422
const uint8_t *p = Memory::GetPointerRange(ptr, maxLen);
423
if (!p)
424
return 0;
425
const uint8_t *end = (const uint8_t *)memchr(p, '\0', maxLen);
426
if (!end)
427
return 0;
428
return (uint32_t)(end - p);
429
}
430
431
static int Replace_strlen() {
432
u32 srcPtr = PARAM(0);
433
u32 len = SafeStringLen(srcPtr);
434
RETURN(len);
435
return 7 + len * 4; // approximation
436
}
437
438
static int Replace_strcpy() {
439
u32 destPtr = PARAM(0);
440
u32 srcPtr = PARAM(1);
441
u32 len = SafeStringLen(srcPtr);
442
char *dst = (char *)Memory::GetPointerWriteRange(destPtr, len);
443
const char *src = (const char *)Memory::GetPointerRange(srcPtr, len);
444
if (dst && src && len != 0) {
445
strcpy(dst, src);
446
}
447
RETURN(destPtr);
448
return 10; // approximation
449
}
450
451
static int Replace_strncpy() {
452
u32 destPtr = PARAM(0);
453
u32 srcPtr = PARAM(1);
454
u32 bytes = PARAM(2);
455
char *dst = (char *)Memory::GetPointerRange(destPtr, bytes);
456
u32 srcLen = SafeStringLen(srcPtr, bytes);
457
const char *src = (const char *)Memory::GetPointerRange(srcPtr, srcLen == 0 ? bytes : srcLen);
458
if (dst && src && bytes != 0) {
459
strncpy(dst, src, bytes);
460
}
461
RETURN(destPtr);
462
return 10; // approximation
463
}
464
465
static int Replace_strcmp() {
466
u32 aLen = SafeStringLen(PARAM(0));
467
const char *a = (const char *)Memory::GetPointerRange(PARAM(0), aLen);
468
u32 bLen = SafeStringLen(PARAM(1));
469
const char *b = (const char *)Memory::GetPointerRange(PARAM(1), bLen);
470
if (a && b && aLen != 0 && bLen != 0) {
471
RETURN(strcmp(a, b));
472
} else {
473
RETURN(0);
474
}
475
return 10; // approximation
476
}
477
478
static int Replace_strncmp() {
479
u32 bytes = PARAM(2);
480
u32 aLen = SafeStringLen(PARAM(0), bytes);
481
const char *a = (const char *)Memory::GetPointerRange(PARAM(0), aLen == 0 ? bytes : aLen);
482
u32 bLen = SafeStringLen(PARAM(1), bytes);
483
const char *b = (const char *)Memory::GetPointerRange(PARAM(1), bLen == 0 ? bytes : bLen);
484
if (a && b && bytes != 0) {
485
RETURN(strncmp(a, b, bytes));
486
} else {
487
RETURN(0);
488
}
489
return 10 + bytes / 4; // approximation
490
}
491
492
static int Replace_fabsf() {
493
RETURNF(fabsf(PARAMF(0)));
494
return 4;
495
}
496
497
static int Replace_vmmul_q_transp() {
498
float_le *out = (float_le *)Memory::GetPointerRange(PARAM(0), 16 * 4);
499
const float_le *a = (const float_le *)Memory::GetPointerRange(PARAM(1), 16 * 4);
500
const float_le *b = (const float_le *)Memory::GetPointerRange(PARAM(2), 16 * 4);
501
502
// TODO: Actually use an optimized matrix multiply here...
503
if (out && b && a) {
504
#ifdef COMMON_BIG_ENDIAN
505
float outn[16], an[16], bn[16];
506
for (int i = 0; i < 16; ++i) {
507
an[i] = a[i];
508
bn[i] = b[i];
509
}
510
Matrix4ByMatrix4(outn, bn, an);
511
for (int i = 0; i < 16; ++i) {
512
out[i] = outn[i];
513
}
514
#else
515
Matrix4ByMatrix4(out, b, a);
516
#endif
517
}
518
return 16;
519
}
520
521
// a0 = pointer to destination address
522
// a1 = matrix
523
// a2 = source address
524
static int Replace_gta_dl_write_matrix() {
525
u32_le *ptr = (u32_le *)Memory::GetPointerWriteRange(PARAM(0), 4);
526
const u32_le *src = (const u32_le *)Memory::GetPointerRange(PARAM(2), 16);
527
u32 matrix = PARAM(1) << 24;
528
529
if (!ptr || !src) {
530
RETURN(0);
531
return 38;
532
}
533
534
u32_le *dest = (u32_le *)Memory::GetPointerWriteRange(ptr[0], 12 * 4);
535
if (!dest) {
536
RETURN(0);
537
return 38;
538
}
539
540
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
541
__m128i topBytes = _mm_set1_epi32(matrix);
542
__m128i m0 = _mm_loadu_si128((const __m128i *)src);
543
__m128i m1 = _mm_loadu_si128((const __m128i *)(src + 4));
544
__m128i m2 = _mm_loadu_si128((const __m128i *)(src + 8));
545
__m128i m3 = _mm_loadu_si128((const __m128i *)(src + 12));
546
m0 = _mm_or_si128(_mm_srli_epi32(m0, 8), topBytes);
547
m1 = _mm_or_si128(_mm_srli_epi32(m1, 8), topBytes);
548
m2 = _mm_or_si128(_mm_srli_epi32(m2, 8), topBytes);
549
m3 = _mm_or_si128(_mm_srli_epi32(m3, 8), topBytes);
550
// These three stores overlap by a word, due to the offsets.
551
_mm_storeu_si128((__m128i *)dest, m0);
552
_mm_storeu_si128((__m128i *)(dest + 3), m1);
553
_mm_storeu_si128((__m128i *)(dest + 6), m2);
554
// Store the last one in parts to not overwrite forwards (probably mostly risk free though)
555
_mm_storel_epi64((__m128i *)(dest + 9), m3);
556
m3 = _mm_srli_si128(m3, 8);
557
_mm_store_ss((float *)(dest + 11), _mm_castsi128_ps(m3));
558
#else
559
// Bit tricky to SIMD (note the offsets) but should be doable if not perfect
560
dest[0] = matrix | (src[0] >> 8);
561
dest[1] = matrix | (src[1] >> 8);
562
dest[2] = matrix | (src[2] >> 8);
563
dest[3] = matrix | (src[4] >> 8);
564
dest[4] = matrix | (src[5] >> 8);
565
dest[5] = matrix | (src[6] >> 8);
566
dest[6] = matrix | (src[8] >> 8);
567
dest[7] = matrix | (src[9] >> 8);
568
dest[8] = matrix | (src[10] >> 8);
569
dest[9] = matrix | (src[12] >> 8);
570
dest[10] = matrix | (src[13] >> 8);
571
dest[11] = matrix | (src[14] >> 8);
572
#endif
573
574
(*ptr) += 0x30;
575
576
RETURN(0);
577
return 38;
578
}
579
580
581
// TODO: Inline into a few NEON or SSE instructions - especially if a1 is a known immediate!
582
// Anyway, not sure if worth it. There's not that many matrices written per frame normally.
583
static int Replace_dl_write_matrix() {
584
u32_le *dlStruct = (u32_le *)Memory::GetPointerWriteRange(PARAM(0), 3 * 4);
585
const u32_le *src = (const u32_le *)Memory::GetPointerRange(PARAM(2), 16 * 4);
586
587
if (!dlStruct || !src) {
588
RETURN(0);
589
return 60;
590
}
591
592
u32 matrix = 0;
593
int count = 12;
594
switch (PARAM(1)) {
595
case 3:
596
matrix = 0x40000000; // tex mtx
597
break;
598
case 2:
599
matrix = 0x3A000000;
600
break;
601
case 1:
602
matrix = 0x3C000000;
603
break;
604
case 0:
605
matrix = 0x3E000000;
606
count = 16;
607
break;
608
}
609
610
u32_le *dest = (u32_le *)Memory::GetPointerWriteRange(dlStruct[2], 4 + count * 4);
611
if (!dest) {
612
RETURN(0);
613
return 60;
614
}
615
616
*dest++ = matrix;
617
matrix += 0x01000000;
618
619
if (count == 16) {
620
// Ultra SIMD friendly! These intrinsics generate pretty much perfect code,
621
// no point in hand rolling.
622
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
623
__m128i topBytes = _mm_set1_epi32(matrix);
624
__m128i m0 = _mm_loadu_si128((const __m128i *)src);
625
__m128i m1 = _mm_loadu_si128((const __m128i *)(src + 4));
626
__m128i m2 = _mm_loadu_si128((const __m128i *)(src + 8));
627
__m128i m3 = _mm_loadu_si128((const __m128i *)(src + 12));
628
m0 = _mm_or_si128(_mm_srli_epi32(m0, 8), topBytes);
629
m1 = _mm_or_si128(_mm_srli_epi32(m1, 8), topBytes);
630
m2 = _mm_or_si128(_mm_srli_epi32(m2, 8), topBytes);
631
m3 = _mm_or_si128(_mm_srli_epi32(m3, 8), topBytes);
632
_mm_storeu_si128((__m128i *)dest, m0);
633
_mm_storeu_si128((__m128i *)(dest + 4), m1);
634
_mm_storeu_si128((__m128i *)(dest + 8), m2);
635
_mm_storeu_si128((__m128i *)(dest + 12), m3);
636
#else
637
#if 0
638
//TODO: Finish NEON, make conditional somehow
639
uint32x4_t topBytes = vdupq_n_u32(matrix);
640
uint32x4_t m0 = vld1q_u32(dataPtr);
641
uint32x4_t m1 = vld1q_u32(dataPtr + 4);
642
uint32x4_t m2 = vld1q_u32(dataPtr + 8);
643
uint32x4_t m3 = vld1q_u32(dataPtr + 12);
644
m0 = vorr_u32(vsri_n_u32(m0, 8), topBytes); // TODO: look into VSRI
645
m1 = vorr_u32(vshr_n_u32(m1, 8), topBytes);
646
m2 = vorr_u32(vshr_n_u32(m2, 8), topBytes);
647
m3 = vorr_u32(vshr_n_u32(m3, 8), topBytes);
648
vst1q_u32(dlPtr, m0);
649
vst1q_u32(dlPtr + 4, m1);
650
vst1q_u32(dlPtr + 8, m2);
651
vst1q_u32(dlPtr + 12, m3);
652
#endif
653
for (int i = 0; i < count; i++) {
654
dest[i] = matrix | (src[i] >> 8);
655
}
656
#endif
657
} else {
658
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
659
__m128i topBytes = _mm_set1_epi32(matrix);
660
__m128i m0 = _mm_loadu_si128((const __m128i *)src);
661
__m128i m1 = _mm_loadu_si128((const __m128i *)(src + 4));
662
__m128i m2 = _mm_loadu_si128((const __m128i *)(src + 8));
663
__m128i m3 = _mm_loadu_si128((const __m128i *)(src + 12));
664
m0 = _mm_or_si128(_mm_srli_epi32(m0, 8), topBytes);
665
m1 = _mm_or_si128(_mm_srli_epi32(m1, 8), topBytes);
666
m2 = _mm_or_si128(_mm_srli_epi32(m2, 8), topBytes);
667
m3 = _mm_or_si128(_mm_srli_epi32(m3, 8), topBytes);
668
// These three stores overlap by a word, due to the offsets.
669
_mm_storeu_si128((__m128i *)dest, m0);
670
_mm_storeu_si128((__m128i *)(dest + 3), m1);
671
_mm_storeu_si128((__m128i *)(dest + 6), m2);
672
// Store the last one in parts to not overwrite forwards (probably mostly risk free though)
673
_mm_storel_epi64((__m128i *)(dest + 9), m3);
674
m3 = _mm_srli_si128(m3, 8);
675
_mm_store_ss((float *)(dest + 11), _mm_castsi128_ps(m3));
676
#else
677
// Bit tricky to SIMD (note the offsets) but should be doable if not perfect
678
dest[0] = matrix | (src[0] >> 8);
679
dest[1] = matrix | (src[1] >> 8);
680
dest[2] = matrix | (src[2] >> 8);
681
dest[3] = matrix | (src[4] >> 8);
682
dest[4] = matrix | (src[5] >> 8);
683
dest[5] = matrix | (src[6] >> 8);
684
dest[6] = matrix | (src[8] >> 8);
685
dest[7] = matrix | (src[9] >> 8);
686
dest[8] = matrix | (src[10] >> 8);
687
dest[9] = matrix | (src[12] >> 8);
688
dest[10] = matrix | (src[13] >> 8);
689
dest[11] = matrix | (src[14] >> 8);
690
#endif
691
}
692
693
NotifyMemInfo(MemBlockFlags::READ, PARAM(2), 16 * sizeof(float), "ReplaceDLWriteMatrix");
694
NotifyMemInfo(MemBlockFlags::WRITE, PARAM(0) + 2 * sizeof(u32), sizeof(u32), "ReplaceDLWriteMatrix");
695
NotifyMemInfo(MemBlockFlags::WRITE, dlStruct[2], (count + 1) * sizeof(u32), "ReplaceDLWriteMatrix");
696
697
dlStruct[2] += (1 + count) * 4;
698
RETURN(dlStruct[2]);
699
return 60;
700
}
701
702
static bool GetMIPSStaticAddress(u32 &addr, s32 lui_offset, s32 lw_offset) {
703
const MIPSOpcode upper = Memory::Read_Instruction(currentMIPS->pc + lui_offset, true);
704
if (upper != MIPS_MAKE_LUI(MIPS_GET_RT(upper), upper & 0xffff)) {
705
return false;
706
}
707
const MIPSOpcode lower = Memory::Read_Instruction(currentMIPS->pc + lw_offset, true);
708
if (lower != MIPS_MAKE_LW(MIPS_GET_RT(lower), MIPS_GET_RS(lower), lower & 0xffff)) {
709
if (lower != MIPS_MAKE_ORI(MIPS_GET_RT(lower), MIPS_GET_RS(lower), lower & 0xffff)) {
710
return false;
711
}
712
}
713
addr = ((upper & 0xffff) << 16) + (s16)(lower & 0xffff);
714
return true;
715
}
716
717
static bool GetMIPSGPAddress(u32 &addr, s32 offset) {
718
const MIPSOpcode loadOp = Memory::Read_Instruction(currentMIPS->pc + offset, true);
719
if (MIPS_GET_RS(loadOp) == MIPS_REG_GP) {
720
s16 gpoff = (s16)(u16)(loadOp & 0x0000FFFF);
721
addr = currentMIPS->r[MIPS_REG_GP] + gpoff;
722
return true;
723
}
724
725
return false;
726
}
727
728
static int Hook_godseaterburst_blit_texture() {
729
u32 texaddr;
730
// Only if there's no texture.
731
if (!GetMIPSStaticAddress(texaddr, 0x000c, 0x0030)) {
732
return 0;
733
}
734
u32 fb_infoaddr;
735
if (Memory::Read_U32(texaddr) != 0 || !GetMIPSStaticAddress(fb_infoaddr, 0x01d0, 0x01d4)) {
736
return 0;
737
}
738
739
const u32 fb_info = Memory::Read_U32(fb_infoaddr);
740
const u32 fb_address = Memory::Read_U32(fb_info);
741
if (Memory::IsVRAMAddress(fb_address)) {
742
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
743
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "godseaterburst_blit_texture");
744
}
745
return 0;
746
}
747
748
static int Hook_godseaterburst_depthmask_5551() {
749
// This function copies the 5551 framebuffer to a temporary, generating alpha based on depth.
750
// Depth is optional, in which case all pixels get full alpha.
751
// Called when your avatar changes to screenshot for save data.
752
uint32_t colorBuffer = currentMIPS->r[MIPS_REG_A1];
753
uint32_t depthBuffer = currentMIPS->r[MIPS_REG_T2];
754
uint32_t byteStride = currentMIPS->r[MIPS_REG_A2];
755
uint32_t height = currentMIPS->r[MIPS_REG_T1];
756
uint32_t size = byteStride * height;
757
758
if (!Memory::IsVRAMAddress(colorBuffer) || !Memory::IsValidRange(colorBuffer, size))
759
return 0;
760
if (depthBuffer != 0) {
761
if (!Memory::IsVRAMAddress(colorBuffer) || !Memory::IsValidRange(depthBuffer, size))
762
return 0;
763
764
// This is added to read from the linearized mirror.
765
uint32_t depthMirror = depthBuffer + 0x00200000;
766
// Depth download required, or it won't work and will be transparent.
767
gpu->PerformMemoryCopy(depthMirror, depthMirror, size, GPUCopyFlag::FORCE_DST_MATCH_MEM | GPUCopyFlag::DEPTH_REQUESTED);
768
NotifyMemInfo(MemBlockFlags::WRITE, depthMirror, size, "godseaterburst_depthmask_5551");
769
}
770
771
gpu->PerformReadbackToMemory(colorBuffer, size);
772
NotifyMemInfo(MemBlockFlags::WRITE, colorBuffer, size, "godseaterburst_depthmask_5551");
773
774
return 0;
775
}
776
777
static int Hook_hexyzforce_monoclome_thread() {
778
u32 fb_info;
779
if (!GetMIPSStaticAddress(fb_info, -4, 0)) {
780
return 0;
781
}
782
783
const u32 fb_address = Memory::Read_U32(fb_info);
784
if (Memory::IsVRAMAddress(fb_address)) {
785
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
786
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "hexyzforce_monoclome_thread");
787
}
788
return 0;
789
}
790
791
static int Hook_starocean_write_stencil() {
792
const u32 fb_address = currentMIPS->r[MIPS_REG_T7];
793
if (Memory::IsVRAMAddress(fb_address)) {
794
gpu->PerformWriteStencilFromMemory(fb_address, 0x00088000, WriteStencil::IGNORE_ALPHA);
795
}
796
return 0;
797
}
798
799
static int Hook_topx_create_saveicon() {
800
const u32 fb_address = currentMIPS->r[MIPS_REG_V0];
801
if (Memory::IsVRAMAddress(fb_address)) {
802
gpu->PerformMemoryCopy(fb_address, fb_address, 0x00044000, GPUCopyFlag::FORCE_DST_MATCH_MEM | GPUCopyFlag::DISALLOW_CREATE_VFB);
803
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "topx_create_saveicon");
804
}
805
return 0;
806
}
807
808
static int Hook_ff1_battle_effect() {
809
const u32 fb_address = currentMIPS->r[MIPS_REG_A1];
810
if (Memory::IsVRAMAddress(fb_address)) {
811
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
812
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "ff1_battle_effect");
813
}
814
return 0;
815
}
816
817
static int Hook_dissidia_recordframe_avi() {
818
// This is called once per frame, and records that frame's data to avi.
819
const u32 fb_address = currentMIPS->r[MIPS_REG_A1];
820
if (Memory::IsVRAMAddress(fb_address)) {
821
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
822
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "dissidia_recordframe_avi");
823
}
824
return 0;
825
}
826
827
static int Hook_brandish_download_frame() {
828
u32 fb_infoaddr;
829
if (!GetMIPSStaticAddress(fb_infoaddr, 0x2c, 0x30)) {
830
return 0;
831
}
832
const u32 fb_info = Memory::Read_U32(fb_infoaddr);
833
const MIPSOpcode fb_index_load = Memory::Read_Instruction(currentMIPS->pc + 0x38, true);
834
if (fb_index_load != MIPS_MAKE_LW(MIPS_GET_RT(fb_index_load), MIPS_GET_RS(fb_index_load), fb_index_load & 0xffff)) {
835
return 0;
836
}
837
const int fb_index_offset = (s16)(fb_index_load & 0xffff);
838
const u32 fb_index = (Memory::Read_U32(fb_info + fb_index_offset) + 1) & 1;
839
const u32 fb_address = 0x4000000 + (0x44000 * fb_index);
840
const u32 dest_address = currentMIPS->r[MIPS_REG_A1];
841
if (Memory::IsRAMAddress(dest_address)) {
842
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
843
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "brandish_download_frame");
844
}
845
return 0;
846
}
847
848
static int Hook_growlanser_create_saveicon() {
849
const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 4);
850
const u32 fmt = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP]);
851
const u32 sz = fmt == GE_FORMAT_8888 ? 0x00088000 : 0x00044000;
852
if (Memory::IsVRAMAddress(fb_address) && fmt <= 3) {
853
gpu->PerformMemoryCopy(fb_address, fb_address, sz, GPUCopyFlag::FORCE_DST_MATCH_MEM | GPUCopyFlag::DISALLOW_CREATE_VFB);
854
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, sz, "growlanser_create_saveicon");
855
}
856
return 0;
857
}
858
859
static int Hook_sd_gundam_g_generation_download_frame() {
860
const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 8);
861
const u32 fmt = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 4);
862
const u32 sz = fmt == GE_FORMAT_8888 ? 0x00088000 : 0x00044000;
863
if (Memory::IsVRAMAddress(fb_address) && fmt <= 3) {
864
gpu->PerformReadbackToMemory(fb_address, sz);
865
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, sz, "sd_gundam_g_generation_download_frame");
866
}
867
return 0;
868
}
869
870
static int Hook_narisokonai_download_frame() {
871
const u32 fb_address = currentMIPS->r[MIPS_REG_V0];
872
if (Memory::IsVRAMAddress(fb_address)) {
873
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
874
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "narisokonai_download_frame");
875
}
876
return 0;
877
}
878
879
static int Hook_kirameki_school_life_download_frame() {
880
const u32 fb_address = currentMIPS->r[MIPS_REG_A2];
881
if (Memory::IsVRAMAddress(fb_address)) {
882
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
883
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kirameki_school_life_download_frame");
884
}
885
return 0;
886
}
887
888
static int Hook_orenoimouto_download_frame() {
889
const u32 fb_address = currentMIPS->r[MIPS_REG_A4];
890
if (Memory::IsVRAMAddress(fb_address)) {
891
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
892
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "orenoimouto_download_frame");
893
}
894
return 0;
895
}
896
897
static int Hook_sakurasou_download_frame() {
898
const u32 fb_address = currentMIPS->r[MIPS_REG_V0];
899
if (Memory::IsVRAMAddress(fb_address)) {
900
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
901
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "sakurasou_download_frame");
902
}
903
return 0;
904
}
905
906
static int Hook_suikoden1_and_2_download_frame_1() {
907
const u32 fb_address = currentMIPS->r[MIPS_REG_S4];
908
if (Memory::IsVRAMAddress(fb_address)) {
909
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
910
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "suikoden1_and_2_download_frame_1");
911
}
912
return 0;
913
}
914
915
static int Hook_suikoden1_and_2_download_frame_2() {
916
const u32 fb_address = currentMIPS->r[MIPS_REG_S2];
917
if (Memory::IsVRAMAddress(fb_address)) {
918
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
919
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "suikoden1_and_2_download_frame_2");
920
}
921
return 0;
922
}
923
924
static int Hook_rezel_cross_download_frame() {
925
const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 0x1C);
926
const u32 fmt = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 0x14);
927
const u32 sz = fmt == GE_FORMAT_8888 ? 0x00088000 : 0x00044000;
928
if (Memory::IsVRAMAddress(fb_address) && fmt <= 3) {
929
gpu->PerformReadbackToMemory(fb_address, sz);
930
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, sz, "rezel_cross_download_frame");
931
}
932
return 0;
933
}
934
935
static int Hook_kagaku_no_ensemble_download_frame() {
936
const u32 fb_address = currentMIPS->r[MIPS_REG_V0];
937
if (Memory::IsVRAMAddress(fb_address)) {
938
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
939
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kagaku_no_ensemble_download_frame");
940
}
941
return 0;
942
}
943
944
static int Hook_soranokiseki_fc_download_frame() {
945
const u32 fb_address = currentMIPS->r[MIPS_REG_A2];
946
if (Memory::IsVRAMAddress(fb_address)) {
947
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
948
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "soranokiseki_fc_download_frame");
949
}
950
return 0;
951
}
952
953
static int Hook_soranokiseki_sc_download_frame() {
954
u32 fb_infoaddr;
955
if (!GetMIPSStaticAddress(fb_infoaddr, 0x28, 0x2C)) {
956
return 0;
957
}
958
const u32 fb_info = Memory::Read_U32(fb_infoaddr);
959
const MIPSOpcode fb_index_load = Memory::Read_Instruction(currentMIPS->pc + 0x34, true);
960
if (fb_index_load != MIPS_MAKE_LW(MIPS_GET_RT(fb_index_load), MIPS_GET_RS(fb_index_load), fb_index_load & 0xffff)) {
961
return 0;
962
}
963
const int fb_index_offset = (s16)(fb_index_load & 0xffff);
964
const u32 fb_index = (Memory::Read_U32(fb_info + fb_index_offset) + 1) & 1;
965
const u32 fb_address = 0x4000000 + (0x44000 * fb_index);
966
const u32 dest_address = currentMIPS->r[MIPS_REG_A1];
967
if (Memory::IsRAMAddress(dest_address)) {
968
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
969
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "soranokiseki_sc_download_frame");
970
}
971
return 0;
972
}
973
974
static int Hook_bokunonatsuyasumi4_download_frame() {
975
const u32 fb_address = currentMIPS->r[MIPS_REG_A3];
976
if (Memory::IsVRAMAddress(fb_address)) {
977
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
978
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "bokunonatsuyasumi4_download_frame");
979
}
980
return 0;
981
}
982
983
static int Hook_danganronpa2_1_download_frame() {
984
const u32 fb_base = currentMIPS->r[MIPS_REG_V0];
985
const u32 fb_offset = currentMIPS->r[MIPS_REG_V1];
986
const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;
987
const u32 fb_address = fb_base + fb_offset_fix;
988
if (Memory::IsVRAMAddress(fb_address)) {
989
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
990
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa2_1_download_frame");
991
}
992
return 0;
993
}
994
995
static int Hook_danganronpa2_2_download_frame() {
996
const u32 fb_base = currentMIPS->r[MIPS_REG_V0];
997
const u32 fb_offset = currentMIPS->r[MIPS_REG_V1];
998
const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;
999
const u32 fb_address = fb_base + fb_offset_fix;
1000
if (Memory::IsVRAMAddress(fb_address)) {
1001
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1002
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa2_2_download_frame");
1003
}
1004
return 0;
1005
}
1006
1007
static int Hook_danganronpa1_1_download_frame() {
1008
const u32 fb_base = currentMIPS->r[MIPS_REG_A5];
1009
const u32 fb_offset = currentMIPS->r[MIPS_REG_V0];
1010
const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;
1011
const u32 fb_address = fb_base + fb_offset_fix;
1012
if (Memory::IsVRAMAddress(fb_address)) {
1013
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1014
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa1_1_download_frame");
1015
}
1016
return 0;
1017
}
1018
1019
static int Hook_danganronpa1_2_download_frame() {
1020
const MIPSOpcode instruction = Memory::Read_Instruction(currentMIPS->pc + 0x8, true);
1021
const int reg_num = instruction >> 11 & 31;
1022
const u32 fb_base = currentMIPS->r[reg_num];
1023
const u32 fb_offset = currentMIPS->r[MIPS_REG_V0];
1024
const u32 fb_offset_fix = fb_offset & 0xFFFFFFFC;
1025
const u32 fb_address = fb_base + fb_offset_fix;
1026
if (Memory::IsVRAMAddress(fb_address)) {
1027
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1028
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "danganronpa1_2_download_frame");
1029
}
1030
return 0;
1031
}
1032
1033
static int Hook_kankabanchoutbr_download_frame() {
1034
const u32 fb_address = currentMIPS->r[MIPS_REG_A1];
1035
if (Memory::IsVRAMAddress(fb_address)) {
1036
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
1037
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "kankabanchoutbr_download_frame");
1038
}
1039
return 0;
1040
}
1041
1042
static int Hook_orenoimouto_download_frame_2() {
1043
const u32 fb_address = currentMIPS->r[MIPS_REG_A4];
1044
if (Memory::IsVRAMAddress(fb_address)) {
1045
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1046
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "orenoimouto_download_frame_2");
1047
}
1048
return 0;
1049
}
1050
1051
static int Hook_rewrite_download_frame() {
1052
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1053
if (Memory::IsVRAMAddress(fb_address)) {
1054
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1055
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "rewrite_download_frame");
1056
}
1057
return 0;
1058
}
1059
1060
static int Hook_kudwafter_download_frame() {
1061
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1062
if (Memory::IsVRAMAddress(fb_address)) {
1063
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1064
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kudwafter_download_frame");
1065
}
1066
return 0;
1067
}
1068
1069
static int Hook_kumonohatateni_download_frame() {
1070
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1071
if (Memory::IsVRAMAddress(fb_address)) {
1072
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1073
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kumonohatateni_download_frame");
1074
}
1075
return 0;
1076
}
1077
1078
static int Hook_otomenoheihou_download_frame() {
1079
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1080
if (Memory::IsVRAMAddress(fb_address)) {
1081
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1082
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "otomenoheihou_download_frame");
1083
}
1084
return 0;
1085
}
1086
1087
static int Hook_grisaianokajitsu_download_frame() {
1088
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1089
if (Memory::IsVRAMAddress(fb_address)) {
1090
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1091
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "grisaianokajitsu_download_frame");
1092
}
1093
return 0;
1094
}
1095
1096
static int Hook_kokoroconnect_download_frame() {
1097
const u32 fb_address = currentMIPS->r[MIPS_REG_A3];
1098
if (Memory::IsVRAMAddress(fb_address)) {
1099
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1100
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "kokoroconnect_download_frame");
1101
}
1102
return 0;
1103
}
1104
1105
static int Hook_toheart2_download_frame() {
1106
const u32 fb_address = currentMIPS->r[MIPS_REG_A1];
1107
if (Memory::IsVRAMAddress(fb_address)) {
1108
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
1109
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "toheart2_download_frame");
1110
}
1111
return 0;
1112
}
1113
1114
static int Hook_toheart2_download_frame_2() {
1115
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1116
if (Memory::IsVRAMAddress(fb_address)) {
1117
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1118
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "toheart2_download_frame_2");
1119
}
1120
return 0;
1121
}
1122
1123
static int Hook_flowers_download_frame() {
1124
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1125
if (Memory::IsVRAMAddress(fb_address)) {
1126
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1127
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "flowers_download_frame");
1128
}
1129
return 0;
1130
}
1131
1132
static int Hook_motorstorm_download_frame() {
1133
const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A1] + 0x18);
1134
if (Memory::IsVRAMAddress(fb_address)) {
1135
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1136
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "motorstorm_download_frame");
1137
}
1138
return 0;
1139
}
1140
1141
static int Hook_utawarerumono_download_frame() {
1142
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1143
if (Memory::IsVRAMAddress(fb_address)) {
1144
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1145
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "utawarerumono_download_frame");
1146
}
1147
return 0;
1148
}
1149
1150
static int Hook_photokano_download_frame() {
1151
const u32 fb_address = currentMIPS->r[MIPS_REG_A1];
1152
if (Memory::IsVRAMAddress(fb_address)) {
1153
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1154
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "photokano_download_frame");
1155
}
1156
return 0;
1157
}
1158
1159
static int Hook_photokano_download_frame_2() {
1160
const u32 fb_address = currentMIPS->r[MIPS_REG_A1];
1161
if (Memory::IsVRAMAddress(fb_address)) {
1162
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1163
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "photokano_download_frame_2");
1164
}
1165
return 0;
1166
}
1167
1168
static int Hook_gakuenheaven_download_frame() {
1169
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1170
if (Memory::IsVRAMAddress(fb_address)) {
1171
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1172
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "gakuenheaven_download_frame");
1173
}
1174
return 0;
1175
}
1176
1177
static int Hook_youkosohitsujimura_download_frame() {
1178
const u32 fb_address = currentMIPS->r[MIPS_REG_V0];
1179
if (Memory::IsVRAMAddress(fb_address)) {
1180
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1181
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "youkosohitsujimura_download_frame");
1182
}
1183
return 0;
1184
}
1185
1186
static int Hook_zettai_hero_update_minimap_tex() {
1187
const MIPSOpcode storeOffset = Memory::Read_Instruction(currentMIPS->pc + 4, true);
1188
const uint32_t texAddr = currentMIPS->r[MIPS_REG_A0] + SignExtend16ToS32(storeOffset);
1189
const uint32_t texSize = 64 * 64 * 1;
1190
const uint32_t writeAddr = currentMIPS->r[MIPS_REG_V1] + SignExtend16ToS32(storeOffset);
1191
if (Memory::IsValidRange(texAddr, texSize) && writeAddr >= texAddr && writeAddr < texAddr + texSize) {
1192
const uint8_t currentValue = Memory::Read_U8(writeAddr);
1193
if (currentValue != currentMIPS->r[MIPS_REG_A3]) {
1194
gpu->InvalidateCache(texAddr, texSize, GPU_INVALIDATE_FORCE);
1195
}
1196
}
1197
return 0;
1198
}
1199
1200
static int Hook_tonyhawkp8_upload_tutorial_frame() {
1201
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1202
if (Memory::IsVRAMAddress(fb_address)) {
1203
gpu->PerformWriteColorFromMemory(fb_address, 0x00088000);
1204
}
1205
return 0;
1206
}
1207
1208
static int Hook_sdgundamggenerationportable_download_frame() {
1209
const u32 fb_address = currentMIPS->r[MIPS_REG_A3];
1210
if (Memory::IsVRAMAddress(fb_address)) {
1211
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1212
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "sdgundamggenerationportable_download_frame");
1213
}
1214
return 0;
1215
}
1216
1217
static int Hook_atvoffroadfurypro_download_frame() {
1218
const u32 fb_address = currentMIPS->r[MIPS_REG_S2];
1219
const u32 fb_size = (currentMIPS->r[MIPS_REG_S4] >> 3) * currentMIPS->r[MIPS_REG_S3];
1220
if (Memory::IsVRAMAddress(fb_address)) {
1221
gpu->PerformReadbackToMemory(fb_address, fb_size);
1222
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "atvoffroadfurypro_download_frame");
1223
}
1224
return 0;
1225
}
1226
1227
static int Hook_atvoffroadfuryblazintrails_download_frame() {
1228
const u32 fb_address = currentMIPS->r[MIPS_REG_S5];
1229
const u32 fb_size = (currentMIPS->r[MIPS_REG_S3] >> 3) * currentMIPS->r[MIPS_REG_S2];
1230
if (Memory::IsVRAMAddress(fb_address)) {
1231
gpu->PerformReadbackToMemory(fb_address, fb_size);
1232
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "atvoffroadfuryblazintrails_download_frame");
1233
}
1234
return 0;
1235
}
1236
1237
static int Hook_littlebustersce_download_frame() {
1238
const u32 fb_address = currentMIPS->r[MIPS_REG_A0];
1239
if (Memory::IsVRAMAddress(fb_address)) {
1240
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1241
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "littlebustersce_download_frame");
1242
}
1243
return 0;
1244
}
1245
1246
static int Hook_shinigamitoshoujo_download_frame() {
1247
const u32 fb_address = currentMIPS->r[MIPS_REG_S2];
1248
if (Memory::IsVRAMAddress(fb_address)) {
1249
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1250
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "shinigamitoshoujo_download_frame");
1251
}
1252
return 0;
1253
}
1254
1255
static int Hook_atvoffroadfuryprodemo_download_frame() {
1256
const u32 fb_address = currentMIPS->r[MIPS_REG_S5];
1257
const u32 fb_size = ((currentMIPS->r[MIPS_REG_A0] + currentMIPS->r[MIPS_REG_A1]) >> 3) * currentMIPS->r[MIPS_REG_S2];
1258
if (Memory::IsVRAMAddress(fb_address)) {
1259
gpu->PerformReadbackToMemory(fb_address, fb_size);
1260
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "atvoffroadfuryprodemo_download_frame");
1261
}
1262
return 0;
1263
}
1264
1265
static int Hook_unendingbloodycall_download_frame() {
1266
const u32 fb_address = currentMIPS->r[MIPS_REG_T3];
1267
if (Memory::IsVRAMAddress(fb_address)) {
1268
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1269
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "unendingbloodycall_download_frame");
1270
}
1271
return 0;
1272
}
1273
1274
static int Hook_omertachinmokunookitethelegacy_download_frame() {
1275
const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_SP] + 4);
1276
if (Memory::IsVRAMAddress(fb_address)) {
1277
gpu->PerformReadbackToMemory(fb_address, 0x00044000);
1278
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00044000, "omertachinmokunookitethelegacy_download_frame");
1279
}
1280
return 0;
1281
}
1282
1283
// Function at 0886665C in US version (Persona 1)
1284
// Function at 08807DC4 in EU version (Persona 2)
1285
static int Hook_persona_download_frame() {
1286
// Depending on a global (curframe kind of thing), this either reads from
1287
// 0x04088000 or 0x04000000 (the two addresses are hardcoded).
1288
// We'd have to do some gnarly stuff to get this address, so let's just download both.
1289
for (int i = 0; i < 2; i++) {
1290
const u32 fb_address = i == 0 ? 0x04000000 : 0x04088000;
1291
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1292
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "persona1_download_frame");
1293
}
1294
return 0;
1295
}
1296
1297
static int Hook_steinsgate_download_frame() {
1298
u32 fb_offset_addr;
1299
if (!GetMIPSStaticAddress(fb_offset_addr, 0x1C, 0x20)) {
1300
return 0;
1301
}
1302
const u32 fb_address = 0x04000000 + Memory::Read_U32(fb_offset_addr);
1303
if (Memory::IsVRAMAddress(fb_address)) {
1304
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1305
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "steinsgate_download_frame");
1306
}
1307
return 0;
1308
}
1309
1310
static int Hook_infinity_download_frame() {
1311
// There are a few games that share this same function.
1312
// The hash matches, but due to relocations, the addresses it references differ.
1313
// Because of this, the address, even though hardcoded, has to be fetched from the function.
1314
u32 magic_value_addr;
1315
if (!GetMIPSStaticAddress(magic_value_addr, 0x08, 0x1C)) {
1316
return 0;
1317
}
1318
1319
// Not sure why it was done like this, but that's what the actual function does.
1320
const u32 fb_address = (Memory::Read_U32(magic_value_addr) & 1) ? 0x04000000 : 0x04088000;
1321
1322
gpu->PerformReadbackToMemory(fb_address, 0x00088000);
1323
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, 0x00088000, "infinity_download_frame");
1324
return 0;
1325
}
1326
1327
static int Hook_takuyo_download_frame() {
1328
gpu->PerformReadbackToMemory(0x04088000, 0x00088000); // The offset is hardcoded.
1329
NotifyMemInfo(MemBlockFlags::WRITE, 0x04088000, 0x00088000, "takuyo_download_frame");
1330
return 0;
1331
}
1332
1333
// Offsets in comments are valid for the US version of "KINGDOM HEARTS Birth by Sleep".
1334
// Function at 0x0881EF68
1335
static int Hook_kingdomhearts_download_frame() {
1336
const u32 fb_base = 0x04000000; // Set in 0x0880C458, doesn't seem like it's ever overwriten.
1337
1338
const u32 get_fb_offset = MIPSCodeUtils::GetJumpTarget(currentMIPS->pc + 0x5C); // Jump to function at 0x08821EB0. Said function returns the framebuffer offset.
1339
if (get_fb_offset == INVALIDTARGET) {
1340
return 0;
1341
}
1342
u32 fb_offset_index_addr;
1343
if (!GetMIPSStaticAddress(fb_offset_index_addr, get_fb_offset - currentMIPS->pc, get_fb_offset + 0x04 - currentMIPS->pc)) {
1344
return 0;
1345
}
1346
if (!Memory::IsValidRange(fb_offset_index_addr, 4)) {
1347
return 0;
1348
}
1349
1350
const u32 fb_offset_index = Memory::Read_U32(fb_offset_index_addr); // 0x08821E90-0x08821E98
1351
if (fb_offset_index > 2) {
1352
return 0;
1353
}
1354
1355
const MIPSOpcode fb_offset_table_lui = Memory::Read_Instruction(get_fb_offset + 0x08, true); // 0x08821EB8
1356
if (fb_offset_table_lui != MIPS_MAKE_LUI(MIPS_REG_A1, fb_offset_table_lui & 0xFFFF)) {
1357
return 0;
1358
}
1359
const MIPSOpcode fb_offset_table_addiu = Memory::Read_Instruction(get_fb_offset + 0x10, true); // 0x08821EC0
1360
if (fb_offset_table_addiu != MIPS_MAKE_ADDIU(MIPS_REG_A1, MIPS_REG_A1, fb_offset_table_addiu & 0xFFFF)) {
1361
return 0;
1362
}
1363
const u32 fb_offset_table = ((fb_offset_table_lui & 0xFFFF) << 16) + (s16)(fb_offset_table_addiu & 0xFFFF);
1364
if (!Memory::IsValidRange(fb_offset_table, 12)) {
1365
return 0;
1366
}
1367
const u32 fb_offset = Memory::Read_U32(fb_offset_table + fb_offset_index*4); // 0x08821E98-0x08821EB0
1368
1369
u32 magic_ptr_addr;
1370
if (!GetMIPSStaticAddress(magic_ptr_addr, 0x08, 0x10)) {
1371
return 0;
1372
}
1373
const u32 magic_ptr = Memory::Read_U32(magic_ptr_addr); // 0x0881EF70, 0x0881EF78
1374
1375
// Function of the variable guessed.
1376
const u8 bytes_per_pixel = Memory::Read_U8(magic_ptr+0x50); // 0x0881EFE0
1377
1378
const u32 fb_address = fb_base + fb_offset;
1379
const u32 fb_size = (bytes_per_pixel == 2) ? 0x044000 : 0x088000; // Branch at 0x0881EFE8, s3 set at 0x0881EFB8
1380
if (Memory::IsVRAMAddress(fb_address)) {
1381
gpu->PerformReadbackToMemory(fb_address, fb_size);
1382
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "kingdomhearts_download_frame");
1383
}
1384
return 0;
1385
}
1386
1387
static int Hook_katamari_render_check() {
1388
const u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x3C);
1389
const u32 fbInfoPtr = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x40);
1390
if (Memory::IsVRAMAddress(fb_address) && fbInfoPtr != 0) {
1391
const u32 sizeInfoPtr = Memory::Read_U32(fbInfoPtr + 0x0C);
1392
// These are the values it uses to control the loop.
1393
// Width in memory appears to be stride / 8.
1394
const u32 width = Memory::Read_U16(sizeInfoPtr + 0x08) * 8;
1395
// Height in memory is also divided by 8 (but this one isn't hardcoded.)
1396
const u32 heightBlocks = Memory::Read_U16(sizeInfoPtr + 0x0A);
1397
// For some reason this is the number of heightBlocks less 1.
1398
const u32 heightBlockCount = Memory::Read_U8(fbInfoPtr + 0x08) + 1;
1399
1400
const u32 totalBytes = width * heightBlocks * heightBlockCount;
1401
gpu->PerformReadbackToMemory(fb_address, totalBytes);
1402
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, totalBytes, "katamari_render_check");
1403
}
1404
return 0;
1405
}
1406
1407
static int Hook_katamari_screenshot_to_565() {
1408
u32 fb_address;
1409
if (GetMIPSStaticAddress(fb_address, 0x0040, 0x0044)) {
1410
gpu->PerformReadbackToMemory(0x04000000 | fb_address, 0x00088000);
1411
NotifyMemInfo(MemBlockFlags::WRITE, 0x04000000 | fb_address, 0x00088000, "katamari_screenshot_to_565");
1412
}
1413
return 0;
1414
}
1415
1416
static int Hook_mytranwars_upload_frame() {
1417
u32 fb_address = currentMIPS->r[MIPS_REG_S0];
1418
if (Memory::IsVRAMAddress(fb_address)) {
1419
gpu->PerformWriteColorFromMemory(fb_address, 0x00088000);
1420
}
1421
return 0;
1422
}
1423
1424
static u32 marvelalliance1_copy_src = 0;
1425
static u32 marvelalliance1_copy_dst = 0;
1426
static u32 marvelalliance1_copy_size = 0;
1427
1428
static int Hook_marvelalliance1_copy_a1_before() {
1429
marvelalliance1_copy_src = currentMIPS->r[MIPS_REG_A1];
1430
marvelalliance1_copy_dst = currentMIPS->r[MIPS_REG_V1];
1431
marvelalliance1_copy_size = currentMIPS->r[MIPS_REG_V0] - currentMIPS->r[MIPS_REG_A1];
1432
1433
if (Memory::IsValidRange(marvelalliance1_copy_src, marvelalliance1_copy_size)) {
1434
gpu->PerformReadbackToMemory(marvelalliance1_copy_src, marvelalliance1_copy_size);
1435
NotifyMemInfo(MemBlockFlags::WRITE, marvelalliance1_copy_src, marvelalliance1_copy_size, "marvelalliance1_copy_a1_before");
1436
}
1437
1438
return 0;
1439
}
1440
1441
static int Hook_marvelalliance1_copy_a2_before() {
1442
marvelalliance1_copy_src = currentMIPS->r[MIPS_REG_A2];
1443
marvelalliance1_copy_dst = currentMIPS->r[MIPS_REG_V0];
1444
marvelalliance1_copy_size = currentMIPS->r[MIPS_REG_A1] - currentMIPS->r[MIPS_REG_A2];
1445
1446
if (Memory::IsValidRange(marvelalliance1_copy_src, marvelalliance1_copy_size)) {
1447
gpu->PerformReadbackToMemory(marvelalliance1_copy_src, marvelalliance1_copy_size);
1448
NotifyMemInfo(MemBlockFlags::WRITE, marvelalliance1_copy_src, marvelalliance1_copy_size, "marvelalliance1_copy_a2_before");
1449
}
1450
1451
return 0;
1452
}
1453
1454
static int Hook_marvelalliance1_copy_after() {
1455
if (Memory::IsValidRange(marvelalliance1_copy_dst, marvelalliance1_copy_size)) {
1456
gpu->PerformWriteColorFromMemory(marvelalliance1_copy_dst, marvelalliance1_copy_size);
1457
NotifyMemInfo(MemBlockFlags::READ, marvelalliance1_copy_dst, marvelalliance1_copy_size, "marvelalliance1_copy_after");
1458
}
1459
1460
return 0;
1461
}
1462
1463
static int Hook_starocean_clear_framebuf_before() {
1464
skipGPUReplacements |= (int)GPUReplacementSkip::MEMSET;
1465
return 0;
1466
}
1467
1468
static int Hook_starocean_clear_framebuf_after() {
1469
skipGPUReplacements &= ~(int)GPUReplacementSkip::MEMSET;
1470
1471
// This hook runs after the copy, this is the final memcpy destination.
1472
u32 framebuf = currentMIPS->r[MIPS_REG_V0] - 512 * 4 * 271;
1473
u32 y_address, h_address;
1474
1475
if (GetMIPSGPAddress(y_address, -204) && GetMIPSGPAddress(h_address, -200)) {
1476
int y = (s16)Memory::Read_U16(y_address);
1477
int h = (s16)Memory::Read_U16(h_address);
1478
1479
DEBUG_LOG(Log::HLE, "starocean_clear_framebuf() - %08x y=%d-%d", framebuf, y, h);
1480
// TODO: This is always clearing to 0, actually, which could be faster than an upload.
1481
gpu->PerformWriteColorFromMemory(framebuf + 512 * y * 4, 512 * h * 4);
1482
}
1483
return 0;
1484
}
1485
1486
static int Hook_motorstorm_pixel_read() {
1487
u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x18);
1488
u32 fb_height = Memory::Read_U16(currentMIPS->r[MIPS_REG_A0] + 0x26);
1489
u32 fb_stride = Memory::Read_U16(currentMIPS->r[MIPS_REG_A0] + 0x28);
1490
gpu->PerformReadbackToMemory(fb_address, fb_height * fb_stride);
1491
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_height * fb_stride, "motorstorm_pixel_read");
1492
return 0;
1493
}
1494
1495
static int Hook_worms_copy_normalize_alpha() {
1496
// At this point in the function (0x0CC), s1 is the framebuf and a2 is the size.
1497
u32 fb_address = currentMIPS->r[MIPS_REG_S1];
1498
u32 fb_size = currentMIPS->r[MIPS_REG_A2];
1499
if (Memory::IsVRAMAddress(fb_address) && Memory::IsValidRange(fb_address, fb_size)) {
1500
gpu->PerformReadbackToMemory(fb_address, fb_size);
1501
NotifyMemInfo(MemBlockFlags::WRITE, fb_address, fb_size, "worms_copy_normalize_alpha");
1502
}
1503
return 0;
1504
}
1505
1506
static int Hook_openseason_data_decode() {
1507
static u32 firstWritePtr = 0;
1508
1509
u32 curWritePtr = currentMIPS->r[MIPS_REG_A0];
1510
u32 endPtr = currentMIPS->r[MIPS_REG_A1];
1511
u32 writeBytes = currentMIPS->r[MIPS_REG_V0];
1512
u32 startPtr = curWritePtr - writeBytes;
1513
if (Memory::IsVRAMAddress(startPtr) && (firstWritePtr == 0 || startPtr < firstWritePtr)) {
1514
firstWritePtr = startPtr;
1515
}
1516
if (Memory::IsVRAMAddress(endPtr) && curWritePtr == endPtr) {
1517
gpu->PerformWriteColorFromMemory(firstWritePtr, endPtr - firstWritePtr);
1518
firstWritePtr = 0;
1519
}
1520
return 0;
1521
}
1522
1523
static int Hook_soltrigger_render_ucschar() {
1524
u32 targetInfoPtrPtr = currentMIPS->r[MIPS_REG_A2];
1525
u32 targetInfoPtr = Memory::IsValidRange(targetInfoPtrPtr, 4) ? Memory::ReadUnchecked_U32(targetInfoPtrPtr) : 0;
1526
if (Memory::IsValidRange(targetInfoPtr, 32)) {
1527
u32 targetPtr = Memory::Read_U32(targetInfoPtr + 8);
1528
u32 targetByteStride = Memory::Read_U32(targetInfoPtr + 16);
1529
1530
// We don't know the height specifically.
1531
gpu->InvalidateCache(targetPtr, targetByteStride * 512, GPU_INVALIDATE_HINT);
1532
}
1533
return 0;
1534
}
1535
1536
static int Hook_gow_fps_hack() {
1537
if (PSP_CoreParameter().compat.flags().GoWFramerateHack60 || PSP_CoreParameter().compat.flags().FramerateHack30) {
1538
if (PSP_CoreParameter().compat.flags().FramerateHack30) {
1539
__DisplayWaitForVblanks("vblank start waited", 2);
1540
} else {
1541
__DisplayWaitForVblanks("vblank start waited", 1);
1542
}
1543
}
1544
return 0;
1545
}
1546
1547
static int Hook_blitz_fps_hack() {
1548
if (PSP_CoreParameter().compat.flags().FramerateHack30) {
1549
__DisplayWaitForVblanks("vblank start waited", 1);
1550
}
1551
return 0;
1552
}
1553
1554
static int Hook_brian_lara_fps_hack() {
1555
if (PSP_CoreParameter().compat.flags().FramerateHack30) {
1556
__DisplayWaitForVblanks("vblank start waited", 1);
1557
}
1558
return 0;
1559
}
1560
1561
static int Hook_gow_vortex_hack() {
1562
if (PSP_CoreParameter().compat.flags().GoWFramerateHack60) {
1563
// from my tests both ==0x3F800000 and !=0x3F800000 takes around 1:40-1:50, that seems to match correct behaviour
1564
if (currentMIPS->r[MIPS_REG_S1] == 0 && currentMIPS->r[MIPS_REG_A0] == 0xC0 && currentMIPS->r[MIPS_REG_T4] != 0x3F800000) {
1565
currentMIPS->r[MIPS_REG_S1] = 1;
1566
}
1567
}
1568
return 0;
1569
}
1570
1571
static int Hook_ZZT3_select_hack() {
1572
if (PSP_CoreParameter().compat.flags().ZZT3SelectHack) {
1573
if (currentMIPS->r[MIPS_REG_V0] == 0) {
1574
currentMIPS->r[MIPS_REG_V0] = 1;
1575
}
1576
}
1577
return 0;
1578
}
1579
1580
#define JITFUNC(f) (&MIPSComp::MIPSFrontendInterface::f)
1581
1582
// Can either replace with C functions or functions emitted in Asm/ArmAsm.
1583
static const ReplacementTableEntry entries[] = {
1584
// TODO: I think some games can be helped quite a bit by implementing the
1585
// double-precision soft-float routines: __adddf3, __subdf3 and so on. These
1586
// should of course be implemented JIT style, inline.
1587
1588
/* These two collide (same hash) and thus can't be replaced :/
1589
{ "asinf", &Replace_asinf, 0, REPFLAG_DISABLED },
1590
{ "acosf", &Replace_acosf, 0, REPFLAG_DISABLED },
1591
*/
1592
1593
{ "sinf", &Replace_sinf, 0, REPFLAG_DISABLED },
1594
{ "cosf", &Replace_cosf, 0, REPFLAG_DISABLED },
1595
{ "tanf", &Replace_tanf, 0, REPFLAG_DISABLED },
1596
{ "atanf", &Replace_atanf, 0, REPFLAG_DISABLED },
1597
{ "sqrtf", &Replace_sqrtf, 0, REPFLAG_DISABLED },
1598
{ "atan2f", &Replace_atan2f, 0, REPFLAG_DISABLED },
1599
{ "floorf", &Replace_floorf, 0, REPFLAG_DISABLED },
1600
{ "ceilf", &Replace_ceilf, 0, REPFLAG_DISABLED },
1601
1602
{ "memcpy", &Replace_memcpy, 0, 0 },
1603
{ "memcpy_jak", &Replace_memcpy_jak, 0, REPFLAG_SLICED },
1604
{ "memcpy16", &Replace_memcpy16, 0, 0 },
1605
{ "memcpy_swizzled", &Replace_memcpy_swizzled, 0, 0 },
1606
{ "memmove", &Replace_memmove, 0, 0 },
1607
{ "memset", &Replace_memset, 0, 0 },
1608
{ "memset_jak", &Replace_memset_jak, 0, REPFLAG_SLICED },
1609
{ "strlen", &Replace_strlen, 0, REPFLAG_DISABLED },
1610
{ "strcpy", &Replace_strcpy, 0, REPFLAG_DISABLED },
1611
{ "strncpy", &Replace_strncpy, 0, REPFLAG_DISABLED },
1612
{ "strcmp", &Replace_strcmp, 0, REPFLAG_DISABLED },
1613
{ "strncmp", &Replace_strncmp, 0, REPFLAG_DISABLED },
1614
{ "fabsf", &Replace_fabsf, JITFUNC(Replace_fabsf), REPFLAG_ALLOWINLINE | REPFLAG_DISABLED },
1615
{ "dl_write_matrix", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED }, // &MIPSComp::Jit::Replace_dl_write_matrix, REPFLAG_DISABLED },
1616
{ "dl_write_matrix_2", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED },
1617
{ "gta_dl_write_matrix", &Replace_gta_dl_write_matrix, 0, REPFLAG_DISABLED },
1618
// 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...
1619
// Haven't investigated write_matrix_4 and 5 but I think they are similar to 1 and 2.
1620
1621
// { "vmmul_q_transp", &Replace_vmmul_q_transp, 0, REPFLAG_DISABLED },
1622
1623
{ "godseaterburst_blit_texture", &Hook_godseaterburst_blit_texture, 0, REPFLAG_HOOKENTER },
1624
{ "godseaterburst_depthmask_5551", &Hook_godseaterburst_depthmask_5551, 0, REPFLAG_HOOKENTER },
1625
{ "hexyzforce_monoclome_thread", &Hook_hexyzforce_monoclome_thread, 0, REPFLAG_HOOKENTER, 0x58 },
1626
{ "starocean_write_stencil", &Hook_starocean_write_stencil, 0, REPFLAG_HOOKENTER, 0x260 },
1627
{ "topx_create_saveicon", &Hook_topx_create_saveicon, 0, REPFLAG_HOOKENTER, 0x34 },
1628
{ "ff1_battle_effect", &Hook_ff1_battle_effect, 0, REPFLAG_HOOKENTER },
1629
// This is actually used in other games, not just Dissidia.
1630
{ "dissidia_recordframe_avi", &Hook_dissidia_recordframe_avi, 0, REPFLAG_HOOKENTER },
1631
{ "brandish_download_frame", &Hook_brandish_download_frame, 0, REPFLAG_HOOKENTER },
1632
{ "growlanser_create_saveicon", &Hook_growlanser_create_saveicon, 0, REPFLAG_HOOKENTER, 0x7C },
1633
{ "sd_gundam_g_generation_download_frame", &Hook_sd_gundam_g_generation_download_frame, 0, REPFLAG_HOOKENTER, 0x48},
1634
{ "narisokonai_download_frame", &Hook_narisokonai_download_frame, 0, REPFLAG_HOOKENTER, 0x14 },
1635
{ "kirameki_school_life_download_frame", &Hook_kirameki_school_life_download_frame, 0, REPFLAG_HOOKENTER },
1636
{ "orenoimouto_download_frame", &Hook_orenoimouto_download_frame, 0, REPFLAG_HOOKENTER },
1637
{ "sakurasou_download_frame", &Hook_sakurasou_download_frame, 0, REPFLAG_HOOKENTER, 0xF8 },
1638
{ "suikoden1_and_2_download_frame_1", &Hook_suikoden1_and_2_download_frame_1, 0, REPFLAG_HOOKENTER, 0x9C },
1639
{ "suikoden1_and_2_download_frame_2", &Hook_suikoden1_and_2_download_frame_2, 0, REPFLAG_HOOKENTER, 0x48 },
1640
{ "rezel_cross_download_frame", &Hook_rezel_cross_download_frame, 0, REPFLAG_HOOKENTER, 0x54 },
1641
{ "kagaku_no_ensemble_download_frame", &Hook_kagaku_no_ensemble_download_frame, 0, REPFLAG_HOOKENTER, 0x38 },
1642
{ "soranokiseki_fc_download_frame", &Hook_soranokiseki_fc_download_frame, 0, REPFLAG_HOOKENTER, 0x180 },
1643
{ "soranokiseki_sc_download_frame", &Hook_soranokiseki_sc_download_frame, 0, REPFLAG_HOOKENTER, },
1644
{ "bokunonatsuyasumi4_download_frame", &Hook_bokunonatsuyasumi4_download_frame, 0, REPFLAG_HOOKENTER, 0x8C },
1645
{ "danganronpa2_1_download_frame", &Hook_danganronpa2_1_download_frame, 0, REPFLAG_HOOKENTER, 0x68 },
1646
{ "danganronpa2_2_download_frame", &Hook_danganronpa2_2_download_frame, 0, REPFLAG_HOOKENTER, 0x94 },
1647
{ "danganronpa1_1_download_frame", &Hook_danganronpa1_1_download_frame, 0, REPFLAG_HOOKENTER, 0x78 },
1648
{ "danganronpa1_2_download_frame", &Hook_danganronpa1_2_download_frame, 0, REPFLAG_HOOKENTER, 0xA8 },
1649
{ "kankabanchoutbr_download_frame", &Hook_kankabanchoutbr_download_frame, 0, REPFLAG_HOOKENTER, },
1650
{ "orenoimouto_download_frame_2", &Hook_orenoimouto_download_frame_2, 0, REPFLAG_HOOKENTER, },
1651
{ "rewrite_download_frame", &Hook_rewrite_download_frame, 0, REPFLAG_HOOKENTER, 0x5C },
1652
{ "kudwafter_download_frame", &Hook_kudwafter_download_frame, 0, REPFLAG_HOOKENTER, 0x58 },
1653
{ "kumonohatateni_download_frame", &Hook_kumonohatateni_download_frame, 0, REPFLAG_HOOKENTER, },
1654
{ "otomenoheihou_download_frame", &Hook_otomenoheihou_download_frame, 0, REPFLAG_HOOKENTER, 0x14 },
1655
{ "grisaianokajitsu_download_frame", &Hook_grisaianokajitsu_download_frame, 0, REPFLAG_HOOKENTER, 0x14 },
1656
{ "kokoroconnect_download_frame", &Hook_kokoroconnect_download_frame, 0, REPFLAG_HOOKENTER, 0x60 },
1657
{ "toheart2_download_frame", &Hook_toheart2_download_frame, 0, REPFLAG_HOOKENTER, },
1658
{ "toheart2_download_frame_2", &Hook_toheart2_download_frame_2, 0, REPFLAG_HOOKENTER, 0x18 },
1659
{ "flowers_download_frame", &Hook_flowers_download_frame, 0, REPFLAG_HOOKENTER, 0x44 },
1660
{ "motorstorm_download_frame", &Hook_motorstorm_download_frame, 0, REPFLAG_HOOKENTER, },
1661
{ "utawarerumono_download_frame", &Hook_utawarerumono_download_frame, 0, REPFLAG_HOOKENTER, },
1662
{ "photokano_download_frame", &Hook_photokano_download_frame, 0, REPFLAG_HOOKENTER, 0x2C },
1663
{ "photokano_download_frame_2", &Hook_photokano_download_frame_2, 0, REPFLAG_HOOKENTER, },
1664
{ "gakuenheaven_download_frame", &Hook_gakuenheaven_download_frame, 0, REPFLAG_HOOKENTER, },
1665
{ "youkosohitsujimura_download_frame", &Hook_youkosohitsujimura_download_frame, 0, REPFLAG_HOOKENTER, 0x94 },
1666
{ "zettai_hero_update_minimap_tex", &Hook_zettai_hero_update_minimap_tex, 0, REPFLAG_HOOKEXIT, },
1667
{ "tonyhawkp8_upload_tutorial_frame", &Hook_tonyhawkp8_upload_tutorial_frame, 0, REPFLAG_HOOKENTER, },
1668
{ "sdgundamggenerationportable_download_frame", &Hook_sdgundamggenerationportable_download_frame, 0, REPFLAG_HOOKENTER, 0x34 },
1669
{ "atvoffroadfurypro_download_frame", &Hook_atvoffroadfurypro_download_frame, 0, REPFLAG_HOOKENTER, 0xA0 },
1670
{ "atvoffroadfuryblazintrails_download_frame", &Hook_atvoffroadfuryblazintrails_download_frame, 0, REPFLAG_HOOKENTER, 0x80 },
1671
{ "littlebustersce_download_frame", &Hook_littlebustersce_download_frame, 0, REPFLAG_HOOKENTER, },
1672
{ "shinigamitoshoujo_download_frame", &Hook_shinigamitoshoujo_download_frame, 0, REPFLAG_HOOKENTER, 0xBC },
1673
{ "atvoffroadfuryprodemo_download_frame", &Hook_atvoffroadfuryprodemo_download_frame, 0, REPFLAG_HOOKENTER, 0x80 },
1674
{ "unendingbloodycall_download_frame", &Hook_unendingbloodycall_download_frame, 0, REPFLAG_HOOKENTER, 0x54 },
1675
{ "omertachinmokunookitethelegacy_download_frame", &Hook_omertachinmokunookitethelegacy_download_frame, 0, REPFLAG_HOOKENTER, 0x88 },
1676
{ "katamari_render_check", &Hook_katamari_render_check, 0, REPFLAG_HOOKENTER, 0, },
1677
{ "katamari_screenshot_to_565", &Hook_katamari_screenshot_to_565, 0, REPFLAG_HOOKENTER, 0 },
1678
{ "mytranwars_upload_frame", &Hook_mytranwars_upload_frame, 0, REPFLAG_HOOKENTER, 0x128 },
1679
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x284 },
1680
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x2bc },
1681
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x2e8 },
1682
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x320 },
1683
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a2_before, 0, REPFLAG_HOOKENTER, 0x3b0 },
1684
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x3e8 },
1685
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a2_before, 0, REPFLAG_HOOKENTER, 0x410 },
1686
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x448 },
1687
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x600 },
1688
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x638 },
1689
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x664 },
1690
{ "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x69c },
1691
{ "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_before, 0, REPFLAG_HOOKENTER, 0 },
1692
{ "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_after, 0, REPFLAG_HOOKEXIT, 0 },
1693
{ "motorstorm_pixel_read", &Hook_motorstorm_pixel_read, 0, REPFLAG_HOOKENTER, 0 },
1694
{ "worms_copy_normalize_alpha", &Hook_worms_copy_normalize_alpha, 0, REPFLAG_HOOKENTER, 0x0CC },
1695
{ "openseason_data_decode", &Hook_openseason_data_decode, 0, REPFLAG_HOOKENTER, 0x2F0 },
1696
{ "soltrigger_render_ucschar", &Hook_soltrigger_render_ucschar, 0, REPFLAG_HOOKENTER, 0 },
1697
{ "gow_fps_hack", &Hook_gow_fps_hack, 0, REPFLAG_HOOKEXIT , 0 },
1698
{ "gow_vortex_hack", &Hook_gow_vortex_hack, 0, REPFLAG_HOOKENTER, 0x60 },
1699
{ "ZZT3_select_hack", &Hook_ZZT3_select_hack, 0, REPFLAG_HOOKENTER, 0xC4 },
1700
{ "blitz_fps_hack", &Hook_blitz_fps_hack, 0, REPFLAG_HOOKEXIT , 0 },
1701
{ "brian_lara_fps_hack", &Hook_brian_lara_fps_hack, 0, REPFLAG_HOOKEXIT , 0 },
1702
{ "persona1_download_frame", &Hook_persona_download_frame, 0, REPFLAG_HOOKENTER, 0 },
1703
{ "persona2_download_frame", &Hook_persona_download_frame, 0, REPFLAG_HOOKENTER, 0 },
1704
{ "steinsgate_download_frame", &Hook_steinsgate_download_frame, 0, REPFLAG_HOOKENTER, 0 },
1705
{ "infinity_download_frame", &Hook_infinity_download_frame, 0, REPFLAG_HOOKENTER, 0 },
1706
{ "takuyo_1_download_frame", &Hook_takuyo_download_frame, 0, REPFLAG_HOOKENTER, 0},
1707
{ "takuyo_2_download_frame", &Hook_takuyo_download_frame, 0, REPFLAG_HOOKENTER, 0},
1708
{ "takuyo_3_download_frame", &Hook_takuyo_download_frame, 0, REPFLAG_HOOKENTER, 0},
1709
{ "kingdomhearts_download_frame", &Hook_kingdomhearts_download_frame, 0, REPFLAG_HOOKENTER, 0},
1710
{}
1711
};
1712
1713
1714
static std::map<u32, u32> replacedInstructions;
1715
static std::unordered_map<std::string, std::vector<int> > replacementNameLookup;
1716
1717
void Replacement_Init() {
1718
for (int i = 0; i < (int)ARRAY_SIZE(entries); i++) {
1719
const auto entry = &entries[i];
1720
if (!entry->name || (entry->flags & REPFLAG_DISABLED) != 0)
1721
continue;
1722
replacementNameLookup[entry->name].push_back(i);
1723
}
1724
1725
skipGPUReplacements = 0;
1726
}
1727
1728
void Replacement_Shutdown() {
1729
replacedInstructions.clear();
1730
replacementNameLookup.clear();
1731
}
1732
1733
int GetNumReplacementFuncs() {
1734
return ARRAY_SIZE(entries);
1735
}
1736
1737
std::vector<int> GetReplacementFuncIndexes(u64 hash, int funcSize) {
1738
const char *name = MIPSAnalyst::LookupHash(hash, funcSize);
1739
std::vector<int> emptyResult;
1740
if (!name) {
1741
return emptyResult;
1742
}
1743
1744
auto index = replacementNameLookup.find(name);
1745
if (index != replacementNameLookup.end()) {
1746
return index->second;
1747
}
1748
return emptyResult;
1749
}
1750
1751
const ReplacementTableEntry *GetReplacementFunc(size_t i) {
1752
if (i >= ARRAY_SIZE(entries)) {
1753
return nullptr;
1754
}
1755
return &entries[i];
1756
}
1757
1758
static bool WriteReplaceInstruction(u32 address, int index) {
1759
u32 prevInstr = Memory::Read_Instruction(address, false).encoding;
1760
if (MIPS_IS_REPLACEMENT(prevInstr)) {
1761
int prevIndex = prevInstr & MIPS_EMUHACK_VALUE_MASK;
1762
if (prevIndex == index) {
1763
return false;
1764
}
1765
WARN_LOG(Log::HLE, "Replacement func changed at %08x (%d -> %d)", address, prevIndex, index);
1766
// Make sure we don't save the old replacement.
1767
prevInstr = replacedInstructions[address];
1768
}
1769
1770
if (MIPS_IS_RUNBLOCK(Memory::Read_U32(address))) {
1771
WARN_LOG(Log::HLE, "Replacing jitted func address %08x", address);
1772
}
1773
replacedInstructions[address] = prevInstr;
1774
Memory::Write_U32(MIPS_EMUHACK_CALL_REPLACEMENT | index, address);
1775
return true;
1776
}
1777
1778
void WriteReplaceInstructions(u32 address, u64 hash, int size) {
1779
std::vector<int> indexes = GetReplacementFuncIndexes(hash, size);
1780
for (int index : indexes) {
1781
bool didReplace = false;
1782
const ReplacementTableEntry *entry = GetReplacementFunc(index);
1783
if (entry->flags & REPFLAG_HOOKEXIT) {
1784
// When hooking func exit, we search for jr ra, and replace those.
1785
for (u32 offset = 0; offset < (u32)size; offset += 4) {
1786
const u32 op = Memory::Read_Instruction(address + offset, false).encoding;
1787
if (op == MIPS_MAKE_JR_RA()) {
1788
if (WriteReplaceInstruction(address + offset, index)) {
1789
didReplace = true;
1790
}
1791
}
1792
}
1793
} else if (entry->flags & REPFLAG_HOOKENTER) {
1794
if (WriteReplaceInstruction(address + entry->hookOffset, index)) {
1795
didReplace = true;
1796
}
1797
} else {
1798
if (WriteReplaceInstruction(address, index)) {
1799
didReplace = true;
1800
}
1801
}
1802
1803
if (didReplace) {
1804
INFO_LOG(Log::HLE, "Replaced %s at %08x with hash %016llx", entries[index].name, address, hash);
1805
}
1806
}
1807
}
1808
1809
void RestoreReplacedInstruction(u32 address) {
1810
const u32 curInstr = Memory::Read_U32(address);
1811
if (MIPS_IS_REPLACEMENT(curInstr)) {
1812
Memory::Write_U32(replacedInstructions[address], address);
1813
NOTICE_LOG(Log::HLE, "Restored replaced func at %08x", address);
1814
} else {
1815
NOTICE_LOG(Log::HLE, "Replaced func changed at %08x", address);
1816
}
1817
replacedInstructions.erase(address);
1818
}
1819
1820
void RestoreReplacedInstructions(u32 startAddr, u32 endAddr) {
1821
if (endAddr == startAddr)
1822
return;
1823
// Need to be in order, or we'll hang.
1824
if (endAddr < startAddr)
1825
std::swap(endAddr, startAddr);
1826
const auto start = replacedInstructions.lower_bound(startAddr);
1827
const auto end = replacedInstructions.upper_bound(endAddr);
1828
int restored = 0;
1829
for (auto it = start; it != end; ++it) {
1830
const u32 addr = it->first;
1831
const u32 curInstr = Memory::Read_U32(addr);
1832
if (MIPS_IS_REPLACEMENT(curInstr)) {
1833
Memory::Write_U32(it->second, addr);
1834
++restored;
1835
}
1836
}
1837
INFO_LOG(Log::HLE, "Restored %d replaced funcs between %08x-%08x", restored, startAddr, endAddr);
1838
replacedInstructions.erase(start, end);
1839
}
1840
1841
std::map<u32, u32> SaveAndClearReplacements() {
1842
std::map<u32, u32> saved;
1843
for (const auto &[addr, instr] : replacedInstructions) {
1844
// This will not retain jit blocks.
1845
const u32 curInstr = Memory::Read_Opcode_JIT(addr).encoding;
1846
if (MIPS_IS_REPLACEMENT(curInstr)) {
1847
saved[addr] = curInstr;
1848
Memory::Write_U32(instr, addr);
1849
}
1850
}
1851
return saved;
1852
}
1853
1854
void RestoreSavedReplacements(const std::map<u32, u32> &saved) {
1855
for (const auto &[addr, instr] : saved) {
1856
// Just put the replacements back.
1857
Memory::Write_U32(instr, addr);
1858
}
1859
}
1860
1861
bool GetReplacedOpAt(u32 address, u32 *op) {
1862
u32 instr = Memory::Read_Opcode_JIT(address).encoding;
1863
if (MIPS_IS_REPLACEMENT(instr)) {
1864
auto iter = replacedInstructions.find(address);
1865
if (iter != replacedInstructions.end()) {
1866
*op = iter->second;
1867
return true;
1868
} else {
1869
return false;
1870
}
1871
}
1872
return false;
1873
}
1874
1875
bool CanReplaceJalTo(u32 dest, const ReplacementTableEntry **entry, u32 *funcSize) {
1876
MIPSOpcode op(Memory::Read_Opcode_JIT(dest));
1877
if (!MIPS_IS_REPLACEMENT(op.encoding))
1878
return false;
1879
1880
// Make sure we don't replace if there are any breakpoints inside.
1881
*funcSize = g_symbolMap->GetFunctionSize(dest);
1882
if (*funcSize == SymbolMap::INVALID_ADDRESS) {
1883
if (g_breakpoints.IsAddressBreakPoint(dest)) {
1884
return false;
1885
}
1886
*funcSize = (u32)sizeof(u32);
1887
} else {
1888
if (g_breakpoints.RangeContainsBreakPoint(dest, *funcSize)) {
1889
return false;
1890
}
1891
}
1892
1893
int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;
1894
*entry = GetReplacementFunc(index);
1895
if (!*entry) {
1896
ERROR_LOG(Log::HLE, "ReplaceJalTo: Invalid replacement op %08x at %08x", op.encoding, dest);
1897
return false;
1898
}
1899
1900
if ((*entry)->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT | REPFLAG_DISABLED | REPFLAG_SLICED)) {
1901
// If it's a hook, we can't replace the jal, we have to go inside the func.
1902
return false;
1903
}
1904
return true;
1905
}
1906
1907