mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Answer NVN getters asked of no object with 0, and map a buffer with no pool to memory of its own.
The game's scene-loading thread makes a few NVN calls with a null object while an area loads or Kakariko's time of day changes: a memory pool's flags (and the other pool getters), a texture builder's storage size or alignment, and the mapping of a buffer that has no pool, into which it then writes 0x3d0 bytes. Each read the object at address 0 plus a field's offset, an access violation. The pool getters, nvnMemoryPoolInvalidateMappedRange and the two builder queries now answer 0 for a null object (nvn_no_object), and nvnBufferMap of a buffer with no pool returns a block of scratch memory (64 KiB, or the buffer's size when it records a larger one) instead of 0. The first such calls are logged with their caller. These fit the crash reports in Castle Town and in Kakariko before Death Mountain (#605, #602), which have not been confirmed to be this. Checked: --probe-nvn gains three checks (pool getters of no pool, builder queries of no builder, a 0x3d0-byte write through the mapping of a buffer with no pool) and they pass; its four other failures are the same on the baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,11 @@ void nvn_called(uint8_t *seen, const char *name); // the counting prolo
|
||||
// A wait for the GPU, the display or another thread gives the virtual core up until the end of the enclosing scope.
|
||||
#define NVN_BLOCKING() __attribute__((cleanup(kern_core_back))) int nvn_away_ = kern_core_away()
|
||||
static inline int nvn_arg_bool(const Cpu *c, int i) { return (int)(arg_x(c, i) & 0xff); } // NVNboolean: low 8 bits only
|
||||
// 1 when argument 0, the object of a getter, is NULL: the call is answered with 0 instead of reading the object at
|
||||
// address 0 plus a field's offset. The game's scene-loading thread makes a few such calls while an area or Kakariko's
|
||||
// time of day changes (a memory pool's flags, a texture builder's storage size). The first calls are logged with
|
||||
// their caller (nvn_api_core.c).
|
||||
int nvn_no_object(Cpu *c, const char *what);
|
||||
|
||||
#define NVN_POOL_MAGIC MAGIC('P', 'O', 'O', 'L') // pool_t.magic of an initialized pool
|
||||
#define NVN_VPOOL_PAGE 0x10000ull // NVN virtual mapping granularity (64 KiB)
|
||||
|
||||
@@ -355,10 +355,23 @@ NVN_API(nvnMemoryPoolFlushMappedRange) {
|
||||
if (rt_trace & RT_TRACE_PERF) flush_note(0, p, size);
|
||||
if (p && off >= 0 && (uint64_t)off < p->size) nvn_pool_flushed(p, (uint64_t)off, size < p->size - (uint64_t)off ? size : p->size - (uint64_t)off);
|
||||
}
|
||||
int nvn_no_object(Cpu *c, const char *what) {
|
||||
if (arg_x(c, 0)) return 0;
|
||||
static int told;
|
||||
if (__atomic_fetch_add(&told, 1, __ATOMIC_RELAXED) < 8)
|
||||
rt_log("nvn: %s of no object (caller %#llx): 0", what, (unsigned long long)c->x[30]);
|
||||
ret_x(c, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// NVNmemoryPoolFlags nvnMemoryPoolGetFlags(const NVNmemoryPool *p)
|
||||
NVN_API(nvnMemoryPoolGetFlags) { ret_x(c, ((const pool_t *)arg_p(c, 0))->flags); }
|
||||
NVN_API(nvnMemoryPoolGetFlags) {
|
||||
if (nvn_no_object(c, "nvnMemoryPoolGetFlags")) return;
|
||||
ret_x(c, ((const pool_t *)arg_p(c, 0))->flags);
|
||||
}
|
||||
// void *nvnMemoryPoolMap(const NVNmemoryPool *p): NULL for CPU_NO_ACCESS
|
||||
NVN_API(nvnMemoryPoolMap) {
|
||||
if (nvn_no_object(c, "nvnMemoryPoolMap")) return;
|
||||
const pool_t *o = arg_p(c, 0);
|
||||
if (o->magic != NVN_POOL_MAGIC || (o->flags & NVN_MEMORY_POOL_FLAGS_CPU_NO_ACCESS)) { ret_x(c, 0); return; }
|
||||
ret_x(c, (uintptr_t)o->mem);
|
||||
@@ -414,7 +427,10 @@ NVN_API(nvnMemoryPoolMapVirtual) {
|
||||
ret_x(c, 1);
|
||||
}
|
||||
// NVNbufferAddress nvnMemoryPoolGetBufferAddress(const NVNmemoryPool *p)
|
||||
NVN_API(nvnMemoryPoolGetBufferAddress) { ret_x(c, (uintptr_t)((const pool_t *)arg_p(c, 0))->mem); }
|
||||
NVN_API(nvnMemoryPoolGetBufferAddress) {
|
||||
if (nvn_no_object(c, "nvnMemoryPoolGetBufferAddress")) return;
|
||||
ret_x(c, (uintptr_t)((const pool_t *)arg_p(c, 0))->mem);
|
||||
}
|
||||
|
||||
// ---------- Buffers: they reference their pool and own no storage
|
||||
// void nvnBufferBuilderSetDevice(NVNbufferBuilder *b, NVNdevice *d)
|
||||
@@ -448,9 +464,35 @@ NVN_API(nvnBufferInitialize) {
|
||||
// void nvnBufferFinalize(NVNbuffer *b)
|
||||
NVN_API(nvnBufferFinalize) { memset(arg_p(c, 0), 0, sizeof(buf_t)); }
|
||||
static uint64_t buf_address(const buf_t *o) { return o->pool && o->pool->mem ? (uintptr_t)o->pool->mem + (uint64_t)o->off : 0; }
|
||||
// The mapping of a buffer with no pool behind it (no buffer, or one whose nvnBufferInitialize failed): memory of its
|
||||
// own, zero at first, instead of address 0, since the game's scene-loading thread has been seen filling such a mapping
|
||||
// (0x3d0 bytes) without looking at it. 64 KiB, or the buffer's size when it records a larger one; a larger block
|
||||
// replaces the smaller for later calls, and none is freed (the game may still hold one). Guest addresses are host
|
||||
// addresses.
|
||||
static uint64_t no_pool_mapping(const buf_t *o, uint64_t lr) {
|
||||
static uint8_t first[64 << 10] __attribute__((aligned(256)));
|
||||
static void *block = first;
|
||||
static uint64_t block_size = sizeof first;
|
||||
static int told;
|
||||
static PlatMutex mu = PLAT_MUTEX_INIT;
|
||||
uint64_t want = o && o->magic == BUF_MAGIC && o->size <= (256ull << 20) ? o->size : 0;
|
||||
plat_mutex_lock(&mu);
|
||||
if (want > block_size) {
|
||||
void *b = calloc(1, want);
|
||||
if (b) block = b, block_size = want;
|
||||
}
|
||||
void *r = block;
|
||||
if (told++ < 4)
|
||||
rt_log("nvn: nvnBufferMap of a buffer with no memory pool (caller %#llx): %llu bytes of scratch memory",
|
||||
(unsigned long long)lr, (unsigned long long)block_size);
|
||||
plat_mutex_unlock(&mu);
|
||||
return (uintptr_t)r;
|
||||
}
|
||||
// void *nvnBufferMap(const NVNbuffer *b): NULL in a GPU-window pool, which has no CPU view
|
||||
NVN_API(nvnBufferMap) {
|
||||
uint64_t a = buf_address(arg_p(c, 0));
|
||||
const buf_t *o = arg_p(c, 0);
|
||||
if (!o || !o->pool) { ret_x(c, no_pool_mapping(o, c->x[30])); return; }
|
||||
uint64_t a = buf_address(o);
|
||||
ret_x(c, nvn_gpu_only(a) ? 0 : a);
|
||||
}
|
||||
// NVNbufferAddress nvnBufferGetAddress(const NVNbuffer *b)
|
||||
|
||||
@@ -135,6 +135,7 @@ NVN_API(nvnTextureBuilderGetStorageClass) { ret_x(c, storage_class(&TB(c)->d));
|
||||
NVN_API(nvnTextureBuilderGetFlags) { ret_x(c, (uint32_t)TB(c)->d.flags); }
|
||||
// size_t nvnTextureBuilderGetStorageSize(const NVNtextureBuilder *b)
|
||||
NVN_API(nvnTextureBuilderGetStorageSize) {
|
||||
if (nvn_no_object(c, "nvnTextureBuilderGetStorageSize")) return;
|
||||
nvn_texlayout L;
|
||||
nvn_tex_layout(&TB(c)->d, &L);
|
||||
if (!L.bpb) rt_log("nvn: TextureBuilderGetStorageSize: unknown format %#x", TB(c)->d.format);
|
||||
@@ -142,6 +143,7 @@ NVN_API(nvnTextureBuilderGetStorageSize) {
|
||||
}
|
||||
// size_t nvnTextureBuilderGetStorageAlignment(const NVNtextureBuilder *b)
|
||||
NVN_API(nvnTextureBuilderGetStorageAlignment) {
|
||||
if (nvn_no_object(c, "nvnTextureBuilderGetStorageAlignment")) return;
|
||||
nvn_texlayout L;
|
||||
nvn_tex_layout(&TB(c)->d, &L);
|
||||
ret_x(c, L.align);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// back end's images) back first. NX2navi invalidates its 260x260 R16 grass maps and 1x1 pick targets this way. A
|
||||
// CPU-invisible pool has no storage to read.
|
||||
void nvn_pool_invalidate(const pool_t *p, uint64_t off, uint64_t size) {
|
||||
if (!p->mem || nvn_gpu_only((uintptr_t)p->mem) || off >= p->size || !size) return;
|
||||
if (!p || !p->mem || nvn_gpu_only((uintptr_t)p->mem) || off >= p->size || !size) return;
|
||||
if (size > p->size - off) size = p->size - off;
|
||||
gfx_backend()->guest_read((uintptr_t)p->mem + off, size);
|
||||
}
|
||||
@@ -63,7 +63,10 @@ NVN_API(nvnDeviceGetTimestampInNanoseconds) { ret_x(c, ticks_to_ns(((const NVNco
|
||||
|
||||
// ---------- memory pools and buffers
|
||||
// size_t nvnMemoryPoolGetSize(const NVNmemoryPool *p)
|
||||
NVN_API(nvnMemoryPoolGetSize) { ret_x(c, ((const pool_t *)arg_p(c, 0))->size); }
|
||||
NVN_API(nvnMemoryPoolGetSize) {
|
||||
if (nvn_no_object(c, "nvnMemoryPoolGetSize")) return;
|
||||
ret_x(c, ((const pool_t *)arg_p(c, 0))->size);
|
||||
}
|
||||
// void nvnMemoryPoolInvalidateMappedRange(const NVNmemoryPool *p, ptrdiff_t offset, size_t size)
|
||||
NVN_API(nvnMemoryPoolInvalidateMappedRange) {
|
||||
int64_t off = (int64_t)arg_x(c, 1);
|
||||
|
||||
@@ -106,6 +106,21 @@ static void probe_device_and_pools(void) {
|
||||
CHECK(nvn_pool_find(U(pmem) + 0x100, 0x200) == P() && !nvn_pool_find(U(pmem) + PROBE_POOL_SIZE - 8, 16), "pool index");
|
||||
n_objects += 6;
|
||||
|
||||
// getters asked of no object answer 0 instead of reading address 0 (nvn_no_object); a buffer with no pool maps to
|
||||
// writable memory of its own, not to 0
|
||||
CHECK(CALL(nvnMemoryPoolGetFlags, 0) == 0 && CALL(nvnMemoryPoolGetSize, 0) == 0 && CALL(nvnMemoryPoolMap, 0) == 0 &&
|
||||
CALL(nvnMemoryPoolGetBufferAddress, 0) == 0, "pool getters of no pool");
|
||||
CALL(nvnMemoryPoolInvalidateMappedRange, 0, 0, 0x100);
|
||||
CHECK(CALL(nvnTextureBuilderGetStorageSize, 0) == 0 && CALL(nvnTextureBuilderGetStorageAlignment, 0) == 0,
|
||||
"storage size and alignment of no texture builder");
|
||||
{
|
||||
static NVNbuffer nopool; // zeroed, as a buffer whose initialization failed
|
||||
uint8_t *m = (uint8_t *)(uintptr_t)CALL(nvnBufferMap, U(&nopool));
|
||||
int ok = m != NULL;
|
||||
if (ok) memset(m, 0xab, 0x3d0), ok = m[0x3cf] == 0xab;
|
||||
CHECK(ok, "a buffer with no pool maps to writable memory");
|
||||
}
|
||||
|
||||
// refused: no storage, PHYSICAL | VIRTUAL, a size that is not a 64 KiB multiple
|
||||
static NVNmemoryPool bad;
|
||||
CHECK(make_pool(&bad, NULL, 0x10000, NVN_MEMORY_POOL_FLAGS_CPU_CACHED) == 0, "a pool without storage is refused");
|
||||
|
||||
Reference in New Issue
Block a user