CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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