Hunter's Atlas: the game-state readers and lamp travel

First part of Hunter's Atlas by MikeTat1988, a fullscreen in-game map
(GPL-2.0-or-later, github.com/MikeTat1988/HuntersAtlas), as the Linux fork
has it: the runtime pieces that read the game's state.

- src/runtime_map.c: the player's position, facing and area, the quick
  menu's state and which button confirms. Reads only; every guarded site
  is checked byte for byte first.
- src/runtime_atlas_state.c: event flags (read only) and the opt-in lamp
  travel (BB_MAP_TRAVEL_ENABLED=1), which hooks the game's frame pacer
  entry (0x2034770, not a site 0.7's frame rate hooks touch) and calls the
  game's own return-point and warp functions on the game thread.
- bb-probe binds them right after bbgpu_patch_image, and only with
  BB_ATLAS_ENABLED=1: with the map off, nothing of the game is touched.

Both test programs run in build.sh --test and pass. The map screen comes
next.

Co-Authored-By: MikeTat1988 <[email protected]>
This commit is contained in:
ayozetrandMikeTat1988 committed 2026-11-01 15:04:28 +00:00
1 parent e2ac43b725
commit 6621a1f0a7
13 files changed
+567

No files matched your search

+5
View File
@@ -126,4 +126,9 @@ if [[ ${1:-} == --test ]]; then
out/content-test
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -Isrc tests/test_trophy.c src/runtime_trophy.c -o out/trophy-test
out/trophy-test out/trophy_test_tmp
# Hunter's Atlas: its guarded game-memory readers on synthetic images.
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -Isrc tests/test_map_location.c src/runtime_map.c -lm -o out/map-location-test
out/map-location-test
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -Isrc tests/test_atlas_state.c src/runtime_atlas_state.c -lm -o out/atlas-state-test
out/atlas-state-test
fi
+1
View File
@@ -416,6 +416,7 @@ BB_ENV(BB_COPY_FILTER_GATE, TUNING, "=1 with BB_COPY_SKIP_SAME: the copy filter
BB_ENV(BB_COPY_WHERE, DEBUG, "=1: the copy shader's sources and destinations per frame, VRAM or in place (log lines)")
BB_ENV(BB_VERTEX_IN_PLACE, DEBUG, "=1: vertex data bound in place per frame, by why it has no VRAM copy, and what draws read of it (log lines)")
BB_ENV(BB_VERTEX_READ_COPY, TUNING, "=1 (experimental): vertex data bound in place copied into VRAM for the vertices each draw reads (once per guest submission)")
BB_ENV(BB_ATLAS_DEBUG, DEBUG, "=1: Hunter's Atlas logs what its game-state readers find")
BB_ENV(BB_EXPERIMENT_CYCLE, DEBUG, "=bits,bits,...:seconds: the experiment bits step through the list, over and over (test builds)")
BB_ENV(BB_EXP_EXTRA_BREAKS, DEBUG, "=N (experiment): the render pass ended before every Nth draw, to measure breaks")
BB_ENV(BB_EXP_FINISH_BEFORE_TARGET, DEBUG, "=hex address (experiment): the GPU finishes everything before a draw into that target")
+2
View File
@@ -0,0 +1,2 @@
#define BB_MAP_GUARD_OFFSET 0x18db3c0
static const unsigned char bb_map_guard[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x89,0x55,0xd4,0x41,0x89,0xf7,0x48,0x89,0xfb,0x4c,0x8d,0x35,0x97,0x34,0xc6,0x03,0x49,0x8b,0x0e,0x48,0x85,0xc9,0x75,0x24,0x48,0x8d,0x3d,0x79,0xff,0x05,0x03,0x48,0x8d,0x15,0xc6,0xff,0x05,0x03,0x48,0x8d,0x0d,0x6d,0xfe,0x05,0x03,0xbe,0xb1};
+9
View File
@@ -0,0 +1,9 @@
/* CUSA00207 1.09 original region predicate: native Cross-confirm layout.
* Verified read-only against the current mapped executable and native menu.
* Check beyond the function prologue; other predicate patches fail closed. */
static const unsigned char bb_map_cross_region_guard[]={
0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,
0x41,0x54,0x53,0x48,0x83,0xec,0x40,0x4c,
0x8b,0x25,0x9a,0xb4,0x42,0x03,0x49,0x8b,
0x04,0x24,0x48,0x89,0x45,0xd8,0x8b,0x0d
};
+11
View File
@@ -12,6 +12,8 @@
#include <cpuid.h>
#include "runtime.h"
#include "runtime_trophy.h"
#include "runtime_map.h"
#include "runtime_atlas_state.h"
#include "gpu/bbgpu.h"
#if !defined(__x86_64__) || !defined(__GNUC__)
#error This prototype requires x86-64 GCC or Clang (including MinGW).
@@ -799,6 +801,15 @@ int main(int argc, char **argv) {
runtime_net_image_mapped(image, size); /* online modules that read the original code */
if (patch_file) apply_patches(patch_file, segments, ns, relocs, nr);
if (!cpu_only) bbgpu_patch_image(image, size);
/* Hunter's Atlas (BB_ATLAS_ENABLED=1): its guarded readers, and the opt-in lamp travel hook,
* written here while the image is still writable. Off, nothing of the game is touched. */
if (!cpu_only) {
const char *atlas = bb_env("BB_ATLAS_ENABLED");
if (atlas && atlas[0] && strcmp(atlas, "0")) {
runtime_map_bind(image, size);
runtime_atlas_state_bind(image, size);
}
}
#ifdef _WIN32
patch_guest_tls(segments, ns);
#endif
+3
View File
@@ -0,0 +1,3 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#define BB_ATLAS_BUILD_JSON "{\"adapter_protocol\":1,\"upstream_release\":\"windows-v1.8.1\",\"upstream_commit\":\"bee7ec16213022f15bf34084b5587f1ff09e0e65\",\"feature_schema\":1}"
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef BB_ATLAS_RELEASE_H
#define BB_ATLAS_RELEASE_H
#include <stdint.h>
typedef struct {uint32_t buttons;unsigned axes,triggers;} BbAtlasRelease;
static inline int bb_atlas_axis_held(uint8_t value){return value<110 || value>146;}
static inline void bb_atlas_remember(BbAtlasRelease *s,uint32_t buttons,const uint8_t axes[4],const uint8_t triggers[2]){
s->buttons|=buttons;
for(unsigned i=0;i<4;++i)if(bb_atlas_axis_held(axes[i]))s->axes|=1u<<i;
for(unsigned i=0;i<2;++i)if(triggers[i]>32)s->triggers|=1u<<i;
}
static inline void bb_atlas_release_filter(BbAtlasRelease *s,uint32_t raw_buttons,uint32_t *buttons,uint8_t axes[4],uint8_t triggers[2]){
s->buttons&=raw_buttons;*buttons&=~s->buttons;
for(unsigned i=0;i<4;++i)if(s->axes&(1u<<i)){if(!bb_atlas_axis_held(axes[i]))s->axes&=~(1u<<i);else axes[i]=128;}
for(unsigned i=0;i<2;++i)if(s->triggers&(1u<<i)){if(triggers[i]<=32)s->triggers&=~(1u<<i);else triggers[i]=0;}
}
#endif
+201
View File
@@ -0,0 +1,201 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Atlas's guarded event-flag observation and explicitly enabled lamp travel.
* Layouts and entry bytes verified against the supported local 1.09 image.
* Flags are only read. Travel uses the game's normal return-point/loading calls.
* From Hunter's Atlas by MikeTat1988 (github.com/MikeTat1988/HuntersAtlas). bbport (Linux): the
* reads go through runtime_map_read, the lock is a pthread mutex, and the frame hook is written
* while the loader still has the image writable (runtime_atlas_state_bind is called from
* bb-probe right after bbgpu_patch_image), so no page protection changes at run time.
*/
#define _GNU_SOURCE
#include "runtime_atlas_state.h"
#include "runtime_map.h"
#include "../gpu/shim/bb_env.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <stdatomic.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <sys/mman.h>
#include <time.h>
#endif
#define ATLAS_ABI __attribute__((sysv_abi))
static uintptr_t base;
static int state_ready, travel_ready;
static atomic_int travel_status;
static BbAtlasTravelDestination pending;
static uintptr_t pending_player;
static uint64_t request_time;
static uint32_t source_block;
static int observed_loading;
#ifdef _WIN32
static SRWLOCK request_lock = SRWLOCK_INIT;
#define LOCK() AcquireSRWLockExclusive(&request_lock)
#define UNLOCK() ReleaseSRWLockExclusive(&request_lock)
#else
/* bbport: the Win32 calls of the travel code, on Linux. */
static pthread_mutex_t request_lock = PTHREAD_MUTEX_INITIALIZER;
#define LOCK() pthread_mutex_lock(&request_lock)
#define UNLOCK() pthread_mutex_unlock(&request_lock)
static uint64_t GetTickCount64(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC,&t);
return (uint64_t)t.tv_sec*1000u+(uint64_t)t.tv_nsec/1000000u;
}
#endif
static int64_t (ATLAS_ABI *original_frame)(void *);
static int read_mem(uintptr_t at, void *out, size_t size) {
return runtime_map_read(at, out, size); /* bbport: one reader for both platforms */
}
static uintptr_t ptr(uintptr_t at) { uintptr_t p=0; return read_mem(at,&p,sizeof(p))?p:0; }
uintptr_t runtime_atlas_player_token(void) {
if(!state_ready) return 0;
uintptr_t world=ptr(base+0x553e878);
return world?ptr(world+0x60):0;
}
int runtime_atlas_flag(uint32_t id) {
if(!state_ready || id>99999999u) return -1;
uintptr_t man=ptr(base+0x553b100),head,node,found;
uint32_t bits=0;
if(!man || !read_mem(man+0x1c,&bits,4) || bits!=1000) return -1;
head=ptr(man+0x38); node=head?ptr(head+8):0; found=head;
const uint32_t group=id/bits, bit=id%bits;
int stopped=0;
for(int depth=0;depth<64;++depth) {
unsigned char nil=1; uint32_t key=0;
if(!node || !read_mem(node+0x19,&nil,1))return -1;
if(nil){stopped=1;break;}
if(!read_mem(node+0x20,&key,4))return -1;
if(key<group)node=ptr(node+0x10);
else {found=node;node=ptr(node);}
}
uint32_t key=0,kind=0;uintptr_t data=0;
if(!stopped || found==head || !read_mem(found+0x20,&key,4) || key!=group || !read_mem(found+0x28,&kind,4))return -1;
if(kind==2)data=ptr(found+0x30);
else if(kind==1) {
uint32_t stride=0,index=0;uintptr_t pool=ptr(man+0x28);
if(!pool || !read_mem(man+0x20,&stride,4) || stride<125 || stride>4096 || !read_mem(found+0x30,&index,4))return -1;
uint64_t offset=(uint64_t)stride*index;if(offset>UINT32_MAX)return -1;
data=pool+(uintptr_t)offset;
} else return -1;
unsigned char value=0;
if(!data || !read_mem(data+(bit>>3),&value,1) || man!=ptr(base+0x553b100))return -1;
return (value&(0x80u>>(bit&7)))?1:0;
}
static int eligible(const BbAtlasTravelDestination *d) {
if(d->return_point<=0 || d->on_count>8 || d->off_count>8)return 0;
if(d->unlock_flag && runtime_atlas_flag(d->unlock_flag)!=1)return 0;
for(unsigned i=0;i<d->on_count;++i)if(runtime_atlas_flag(d->on[i])!=1)return 0;
for(unsigned i=0;i<d->off_count;++i)if(runtime_atlas_flag(d->off[i])!=0)return 0;
return 1;
}
int runtime_atlas_travel_supported(void){return travel_ready;}
int runtime_atlas_travel_status(void){return atomic_load_explicit(&travel_status,memory_order_acquire);}
int runtime_atlas_travel_request(const BbAtlasTravelDestination *d) {
BbMapLocation loc={0};
BbMapMenu menu={0};
if(!travel_ready || !d || !runtime_map_location(&loc) || !runtime_map_menu_state(&menu) || !menu.top_level || !eligible(d))return 0;
for(int i=0;i<3;++i)if(!isfinite(d->arrival[i])||fabsf(d->arrival[i])>20000)return 0;
LOCK();
int status=atomic_load(&travel_status);
if(status==1 || status==2){UNLOCK();return 0;}
pending=*d;pending_player=runtime_atlas_player_token();request_time=GetTickCount64();source_block=loc.block;observed_loading=0;
atomic_store_explicit(&travel_status,1,memory_order_release);
UNLOCK();return 1;
}
static void game_tick(void) {
int status=atomic_load_explicit(&travel_status,memory_order_acquire);
if(status!=1 && status!=2)return;
BbMapLocation loc={0};const int valid=runtime_map_location(&loc);
if(status==2) {
if(!valid)observed_loading=1;
/* Placement may keep the same block; require loading/player replacement
* or enough time for the native stage change, then its target block. */
const uint32_t block=((uint32_t)(pending.return_point/100000)<<24)|((uint32_t)((pending.return_point/10000)%10)<<16);
if(valid && loc.block==block && GetTickCount64()-request_time>1500 &&
(observed_loading || loc.block!=source_block || runtime_atlas_player_token()!=pending_player ||
(fabsf(loc.x-pending.arrival[0])<20&&fabsf(loc.y-pending.arrival[1])<20&&fabsf(loc.z-pending.arrival[2])<20)))atomic_store(&travel_status,3);
else if(GetTickCount64()-request_time>60000)atomic_store(&travel_status,-2);
return;
}
LOCK();
if(atomic_load(&travel_status)!=1){UNLOCK();return;}
BbMapMenu menu={0};
uintptr_t data=ptr(base+0x553b130),record=data?ptr(data+8):0;int32_t hp=0;
if(!valid || !record || !read_mem(record+0x14,&hp,4) || hp<=0 ||
!runtime_map_menu_state(&menu) || !menu.top_level || runtime_atlas_player_token()!=pending_player ||
GetTickCount64()-request_time>3000 || !eligible(&pending)) {
atomic_store(&travel_status,-1);UNLOCK();return;
}
/* These three exact SysV entry points are fingerprinted before installation.
* First choose the return point for arrival and the following respawn; then
* request the game's normal stage change. No flag setter is called. */
typedef int64_t (ATLAS_ABI *SetReturn)(int32_t);
typedef int64_t (ATLAS_ABI *UseReturn)(int32_t);
typedef int64_t (ATLAS_ABI *Warp)(uintptr_t,int32_t);
const int32_t destination=pending.return_point;
request_time=GetTickCount64();atomic_store_explicit(&travel_status,2,memory_order_release);
UNLOCK();
((SetReturn)(base+0x156d110))(destination);
((UseReturn)(base+0x156d0e0))(1);
((Warp)(base+0x132e050))(0,destination);
printf("Atlas: native lamp travel queued to %d\n",destination);
}
static int64_t ATLAS_ABI frame_bridge(void *self) {
game_tick();return original_frame(self);
}
static int guard(uintptr_t rva,const unsigned char *bytes,size_t n,uint64_t size) {
return rva<=size && n<=size-rva && !memcmp((const void*)(base+rva),bytes,n);
}
void runtime_atlas_state_bind(void *image,uint64_t size) {
state_ready=travel_ready=0;base=(uintptr_t)image;
if(!image || size<0x553e880)return;
static const unsigned char flag_guard[]={0x53,0x49,0x89,0xd0,0x8b,0x4f,0x1c,0x31,0xd2,0x89,0xf0,0xf7,0xf1,0x41,0x89,0xc2,0x44,0x0f,0xaf,0xd1,0x48,0x8b,0x5f,0x38};
#include "map_build_guard.inc"
if(!guard(BB_MAP_GUARD_OFFSET,bb_map_guard,sizeof(bb_map_guard),size))return;
if(!guard(0x13cfc00,flag_guard,sizeof(flag_guard),size))return;
state_ready=1;puts("Atlas: guarded read-only event flags ready");
const char *opt=bb_env("BB_MAP_TRAVEL_ENABLED");if(!opt || strcmp(opt,"1"))return;
static const unsigned char frame[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38};
static const unsigned char warp[]={0x55,0x48,0x89,0xe5,0x48,0x63,0xc6,0x48,0x69,0xc8,0x89,0xb5,0xf8,0x14,0x48,0x89,0xca,0x48,0xc1,0xea,0x3f,0x48,0xc1,0xe9,0x2d,0x01,0xd1};
static const unsigned char ret[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xec,0x28,0x89,0xfb,0x4c,0x8b,0x35,0xba,0x6a,0xe7,0x03};
static const unsigned char use[]={0x48,0x8b,0x05,0x91,0x95,0xfe,0x03,0x40,0x88,0xb8,0x24,0x15,0,0,0xc3};
if(!guard(0x2034770,frame,sizeof(frame),size)||!guard(0x132e050,warp,sizeof(warp),size)||
!guard(0x156d110,ret,sizeof(ret),size)||!guard(0x156d0e0,use,sizeof(use),size)){
puts("Atlas: travel unavailable; native entry fingerprint mismatch");return;
}
#ifdef _WIN32
unsigned char *trampoline=VirtualAlloc(NULL,4096,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE);
if(!trampoline)return;
memcpy(trampoline,frame,sizeof(frame));trampoline[17]=0xff;trampoline[18]=0x25;memset(trampoline+19,0,4);
uintptr_t resume=base+0x2034770+17;memcpy(trampoline+23,&resume,8);
DWORD old=0;
if(!VirtualProtect(trampoline,4096,PAGE_EXECUTE_READ,&old)){VirtualFree(trampoline,0,MEM_RELEASE);return;}
original_frame=(int64_t (ATLAS_ABI *)(void*))trampoline;
unsigned char jump[17]={0xff,0x25,0,0,0,0};uintptr_t target=(uintptr_t)frame_bridge;
memcpy(jump+6,&target,8);memset(jump+14,0x90,3);
void *site=(void*)(base+0x2034770);
if(!VirtualProtect(site,17,PAGE_EXECUTE_READWRITE,&old)){VirtualFree(trampoline,0,MEM_RELEASE);original_frame=NULL;return;}
memcpy(site,jump,17);FlushInstructionCache(GetCurrentProcess(),site,17);
DWORD ignored=0;VirtualProtect(site,17,old,&ignored);
#else
/* bbport: the trampoline (the 17 bytes the jump covers, then a jump back) in a page of its
* own; the jump over the game's per-frame function is written as plain bytes, the image being
* still writable here (see the header). Absolute jumps: no page near the image is needed. */
unsigned char *trampoline=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
if(trampoline==MAP_FAILED)return;
memcpy(trampoline,frame,sizeof(frame));trampoline[17]=0xff;trampoline[18]=0x25;memset(trampoline+19,0,4);
uintptr_t resume=base+0x2034770+17;memcpy(trampoline+23,&resume,8);
if(mprotect(trampoline,4096,PROT_READ|PROT_EXEC)){munmap(trampoline,4096);return;}
original_frame=(int64_t (ATLAS_ABI *)(void*))trampoline;
unsigned char jump[17]={0xff,0x25,0,0,0,0};uintptr_t target=(uintptr_t)frame_bridge;
memcpy(jump+6,&target,8);memset(jump+14,0x90,3);
memcpy((void*)(base+0x2034770),jump,17);
#endif
travel_ready=1;puts("Atlas: opt-in native lamp travel ready on the main game thread");
}
+27
View File
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef BB_RUNTIME_ATLAS_STATE_H
#define BB_RUNTIME_ATLAS_STATE_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void runtime_atlas_state_bind(void *image, uint64_t size);
/* -1 means unreadable/unsupported, 0 means off, 1 means on. */
int runtime_atlas_flag(uint32_t id);
uintptr_t runtime_atlas_player_token(void);
/* Travel requests contain only a verified native destination and its eligibility. */
typedef struct {
int32_t return_point;
uint32_t unlock_flag;
uint32_t on[8], off[8];
uint8_t on_count, off_count;
float arrival[3];
} BbAtlasTravelDestination;
int runtime_atlas_travel_request(const BbAtlasTravelDestination *destination);
/* 0 idle, 1 queued, 2 loading, 3 arrived, -1 rejected, -2 timed out. */
int runtime_atlas_travel_status(void);
int runtime_atlas_travel_supported(void);
#ifdef __cplusplus
}
#endif
#endif
+152
View File
@@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Local observation only: no writes, game calls, hooks, or save access.
* 1.09 layout facts cross-checked against bbhost's public engine documentation.
* From Hunter's Atlas by MikeTat1988 (github.com/MikeTat1988/HuntersAtlas); bbport: the reads
* use process_vm_readv on Linux (ReadProcessMemory on Windows only).
*/
#define _GNU_SOURCE
#include "runtime_map.h"
#include "../gpu/shim/bb_env.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <fcntl.h>
#include <sys/uio.h>
#include <unistd.h>
#endif
static uintptr_t world_slot;
static uintptr_t menu_slot;
static uintptr_t menu_main_slot,image_base;
void runtime_map_bind(void *image, uint64_t size) {
world_slot=0;
menu_slot=0;
menu_main_slot=0;image_base=0;
/* Anchors from the installed CUSA00207 1.09 executable. Full anchors rather
* than prologues prevent a future executable silently using stale offsets. */
if (!image || size<0x553e880) return;
/* MAP_BUILD_GUARD is an image byte fingerprint generated during packaging. */
#include "map_build_guard.inc"
if (memcmp((unsigned char *)image+BB_MAP_GUARD_OFFSET,bb_map_guard,sizeof(bb_map_guard))) {
puts("Map: executable layout mismatch; position tracking disabled");return;
}
world_slot=(uintptr_t)image+0x553e878;
/* Game IsShowMenu reads this manager; +0x54 contributes the player-menu bit.
* Fingerprint the exact predicate, and observe without calling or patching it. */
static const unsigned char menu_guard[]={0x48,0x8b,0x05,0xd6,0x6f,0xdf,0x03,0x83,0x78,0x3c,0x00};
/* The native grid's selected-index read, independently correlated with
* actual 0/1/2 top-row selection and 8 below System in isolated runtime.
* No writes or game function calls are used by this bridge. */
static const unsigned char focus_guard[]={0x8b,0x8b,0xe8,0x0a,0,0,0x85,0xc9,0x79,0x06,0x8b,0x8b,0xe4,0x0a,0,0};
if(size>0x5562880 && !memcmp((unsigned char*)image+0x176b89b,menu_guard,sizeof(menu_guard)) &&
!memcmp((unsigned char*)image+0x1bdf835,focus_guard,sizeof(focus_guard))) {
image_base=(uintptr_t)image;menu_slot=image_base+0x5562878;menu_main_slot=image_base+0x5562870;
}
puts("Map: read-only local position reader ready");
}
int runtime_map_read(uintptr_t at,void *out,uint64_t bytes) {
if (!at || at<0x10000 || !out) return 0;
#ifdef _WIN32
SIZE_T done=0;
return ReadProcessMemory(GetCurrentProcess(),(const void *)at,out,bytes,&done) && done==bytes;
#else
/* bbport: a read of an unmapped or guarded address fails instead of faulting. On AMD the
* game's memory is a GPU dma-buf, which process_vm_readv cannot read: /proc/self/mem then. */
struct iovec local={out,(size_t)bytes},remote={(void *)at,(size_t)bytes};
if (process_vm_readv(getpid(),&local,1,&remote,1,0)==(ssize_t)bytes) return 1;
static int mem=-2;
if (mem==-2) mem=open("/proc/self/mem",O_RDONLY|O_CLOEXEC);
return mem>=0 && pread(mem,out,(size_t)bytes,(off_t)at)==(ssize_t)bytes;
#endif
}
static int read_at(uintptr_t at,void *out,size_t bytes) {
return runtime_map_read(at,out,bytes);
}
static uintptr_t pointer(uintptr_t at) {
uintptr_t p=0;return read_at(at,&p,sizeof(p)) ? p : 0;
}
int runtime_map_menu_open(void) {
uintptr_t menu=pointer(menu_slot);int value=0;
if(!menu || !read_at(menu+0x54,&value,sizeof(value)))return 0;
if(value==2)return 1; /* Stable shown state, verified against the actual quick menu. */
/* bbport: 0 back from the System page while the quick menu is shown (runtime_map_menu_state). */
BbMapMenu state;
return value==0 && runtime_map_menu_state(&state) && state.top_level;
}
uint32_t runtime_map_confirm_button(void) {
/* The game uses its region predicate, not the platform enter-button setting.
* Observe the exact applied 1.09 region/confirm patch; never execute or alter it.
* Unlock Game Region returns false (Cross), Circle Confirm returns true.
* An unknown predicate cannot safely establish menu input ownership. */
static const unsigned char cross[]={0x31,0xc0,0xc3,0x90,0x90,0x90};
static const unsigned char circle[]={0xb8,0x01,0x00,0x00,0x00,0xc3};
unsigned char code[6];
/* The manifest-supported US build's original predicate uses Cross. */
#include "map_confirm_guard.inc"
unsigned char original[sizeof(bb_map_cross_region_guard)];
if(!image_base || !read_at(image_base+0x1fb8730,code,sizeof(code)))return 0;
if(!memcmp(code,cross,sizeof(cross)))return 0x4000u;
if(!memcmp(code,circle,sizeof(circle)))return 0x2000u;
if(read_at(image_base+0x1fb8730,original,sizeof(original)) &&
!memcmp(original,bb_map_cross_region_guard,sizeof(original)))return 0x4000u;
return 0;
}
int runtime_map_menu_state(BbMapMenu *out) {
if(!out)return 0;
memset(out,0,sizeof(*out));out->selected=-1;
if(!runtime_map_confirm_button())return 0;
uintptr_t menu=pointer(menu_slot),main=pointer(menu_main_slot);
uintptr_t list=pointer(main?main+0x80:0);
uint32_t status=0,shown=0;int shape[6]={0};
/* bbport: BB_ATLAS_DEBUG=1 logs what the menu reader sees whenever it changes. */
static int debug=-1;
if(debug<0){const char *d=bb_env("BB_ATLAS_DEBUG");debug=d&&d[0]=='1';}
if(debug){
static char last[256];char now[256];uint32_t s=0,st=0;int sh[6]={0};
const uintptr_t vt=list?pointer(list):0;
if(menu)read_at(menu+0x54,&s,sizeof(s));
if(list){read_at(list+0x98,&st,sizeof(st));read_at(list+0xae0,sh,sizeof(sh));}
snprintf(now,sizeof(now),"menu=%d main=%#lx list=%#lx vt=%s shown=%u status=%u shape=%d,%d,%d,%d,%d,%d",
menu!=0,(unsigned long)main,(unsigned long)list,vt==image_base+0x539c680?"ok":"other",s,st,
sh[0],sh[1],sh[2],sh[3],sh[4],sh[5]);
if(strcmp(now,last)){strcpy(last,now);printf("Atlas debug: %s\n",now);fflush(stdout);}
}
if(!menu || !main || !list || pointer(list)!=image_base+0x539c680 ||
!read_at(menu+0x54,&shown,sizeof(shown)) ||
!read_at(list+0x98,&status,sizeof(status)) ||
!read_at(list+0xae0,shape,sizeof(shape)))return 0;
/* This native list is a four-row six-slot grid, with only three enabled
* items on its first row. +0x98 is 1 while the list is interactive and 2
* while hidden behind nested dialogs; manager+0x54 stays 2 in both. */
if(shape[0]!=24 || shape[3]!=6 || shape[4]!=4 || shape[1]<0 || shape[1]>=24 || shape[2]!=-1)return 0;
if(main!=pointer(menu_main_slot) || list!=pointer(main+0x80))return 0;
/* bbport: back from the System page the manager's +0x54 stays 0 while the list is shown and
* interactive again (until the menu is closed and opened): 0 counts as shown then. The list
* itself is freed when the menu closes (its vtable check above fails), so a closed menu is
* never taken for a shown one (BB_ATLAS_DEBUG=1 log, 2026-10-19). */
out->supported=1;out->top_level=(shown==2 || shown==0) && status==1;out->selected=shape[1];return 1;
}
int runtime_map_location(BbMapLocation *out) {
if (!out) return 0;
memset(out,0,sizeof(*out));
if (!world_slot) return 0;
uintptr_t world=pointer(world_slot),chr=pointer(world ? world+0x60 : 0);
uintptr_t modules=pointer(chr ? chr+0x3b0 : 0);
uintptr_t transform=pointer(modules ? modules+0x68 : 0);
float v[3],yaw;uint32_t block;
if (!transform || !pointer(chr+0x288) ||
!read_at(transform+0x1e0,v,sizeof(v)) || !read_at(transform+0x1d4,&yaw,sizeof(yaw)) ||
!read_at(chr+0x3f8,&block,sizeof(block))) return 0;
if (chr!=pointer(world+0x60) || transform!=pointer(modules+0x68)) return 0;
unsigned area=block>>24;
if (area<21 || area>36 || block==0xffffffffu || !isfinite(yaw) ||
!isfinite(v[0]) || !isfinite(v[1]) || !isfinite(v[2]) ||
fabsf(v[0])>20000 || fabsf(v[1])>20000 || fabsf(v[2])>20000) return 0;
out->x=v[0];out->y=v[1];out->z=v[2];out->yaw=yaw;out->block=block;out->valid=1;
return 1;
}
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef BB_RUNTIME_MAP_H
#define BB_RUNTIME_MAP_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct { float x, y, z, yaw; uint32_t block; int valid; } BbMapLocation;
typedef struct { int supported, top_level, selected; } BbMapMenu;
void runtime_map_bind(void *image, uint64_t size);
/* bbport: reads `bytes` of the game's memory at `at` without faulting; 1 on success. */
int runtime_map_read(uintptr_t at, void *out, uint64_t bytes);
int runtime_map_location(BbMapLocation *out);
int runtime_map_menu_open(void);
int runtime_map_menu_state(BbMapMenu *out);
uint32_t runtime_map_confirm_button(void);
#ifdef __cplusplus
}
#endif
#endif
+62
View File
@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* From Hunter's Atlas by MikeTat1988 (github.com/MikeTat1988/HuntersAtlas); bbport: setenv for
* _putenv_s. */
#define _GNU_SOURCE /* bbport: setenv */
#include "../src/runtime_atlas_state.h"
#include "../src/runtime_map.h"
#include "../src/map_build_guard.inc"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* This fixture exercises guarded flag memory and disabled travel. Live map/menu
* discovery and successful travel are covered by copied-save gameplay checks. */
int runtime_map_location(BbMapLocation *out){memset(out,0,sizeof(*out));return 0;}
int runtime_map_menu_state(BbMapMenu *out){memset(out,0,sizeof(*out));return 0;}
/* bbport: the fixture's memory is all readable (runtime_map.c's reader is tested there). */
int runtime_map_read(uintptr_t at,void *out,uint64_t bytes){if(at<0x10000||!out)return 0;memcpy(out,(const void*)at,bytes);return 1;}
static void put_ptr(unsigned char *p,size_t off,void *value){memcpy(p+off,&value,sizeof(value));}
static void put_u32(unsigned char *p,size_t off,uint32_t value){memcpy(p+off,&value,4);}
int main(void) {
unsigned char *image=calloc(1,0x5570000),*manager=calloc(1,0x100),*head=calloc(1,0x38),*node=calloc(1,0x38),*data=calloc(1,125);
assert(image&&manager&&head&&node&&data);
setenv("BB_MAP_TRAVEL_ENABLED","0",1);
runtime_atlas_state_bind(image,0x5570000);assert(runtime_atlas_flag(12417830)==-1);
const unsigned char guard[]={0x53,0x49,0x89,0xd0,0x8b,0x4f,0x1c,0x31,0xd2,0x89,0xf0,0xf7,0xf1,0x41,0x89,0xc2,0x44,0x0f,0xaf,0xd1,0x48,0x8b,0x5f,0x38};
memcpy(image+0x13cfc00,guard,sizeof(guard));memcpy(image+BB_MAP_GUARD_OFFSET,bb_map_guard,sizeof(bb_map_guard));
put_ptr(image,0x553b100,manager);put_u32(manager,0x1c,1000);put_ptr(manager,0x38,head);
head[0x19]=1;put_ptr(head,8,node);put_ptr(node,0,head);put_ptr(node,0x10,head);put_u32(node,0x20,12417);put_u32(node,0x28,2);put_ptr(node,0x30,data);
runtime_atlas_state_bind(image,0x5570000);
assert(runtime_atlas_flag(12417830)==0);
data[830/8]|=(unsigned char)(0x80u>>(830&7));assert(runtime_atlas_flag(12417830)==1);
assert(runtime_atlas_flag(12417831)==0);assert(runtime_atlas_flag(12418830)==-1);
put_u32(node,0x28,1);put_u32(node,0x30,0);put_ptr(manager,0x28,data);put_u32(manager,0x20,125);
assert(runtime_atlas_flag(12417830)==1);put_u32(manager,0x20,0);assert(runtime_atlas_flag(12417830)==-1);
put_u32(manager,0x20,125);put_ptr(node,0,node);assert(runtime_atlas_flag(12417830)==-1);
assert(!runtime_atlas_travel_supported());BbAtlasTravelDestination d={0};d.return_point=2412951;d.unlock_flag=12417830;assert(!runtime_atlas_travel_request(&d));
/* bbport: travel on, its four entry points in place: the jump over the game's per-frame
* function is written into the (still writable) image, an absolute jmp [rip] to the bridge. */
{
static const unsigned char frame[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38};
static const unsigned char warp[]={0x55,0x48,0x89,0xe5,0x48,0x63,0xc6,0x48,0x69,0xc8,0x89,0xb5,0xf8,0x14,0x48,0x89,0xca,0x48,0xc1,0xea,0x3f,0x48,0xc1,0xe9,0x2d,0x01,0xd1};
static const unsigned char ret[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xec,0x28,0x89,0xfb,0x4c,0x8b,0x35,0xba,0x6a,0xe7,0x03};
static const unsigned char use[]={0x48,0x8b,0x05,0x91,0x95,0xfe,0x03,0x40,0x88,0xb8,0x24,0x15,0,0,0xc3};
memcpy(image+0x2034770,frame,sizeof(frame));memcpy(image+0x132e050,warp,sizeof(warp));
memcpy(image+0x156d110,ret,sizeof(ret));memcpy(image+0x156d0e0,use,sizeof(use));
setenv("BB_MAP_TRAVEL_ENABLED","1",1);
runtime_atlas_state_bind(image,0x5570000);
assert(runtime_atlas_travel_supported());
const unsigned char jmp[]={0xff,0x25,0,0,0,0};
uint64_t target=0;memcpy(&target,image+0x2034770+6,8);
assert(!memcmp(image+0x2034770,jmp,sizeof(jmp)) && target!=0);
assert(image[0x2034770+14]==0x90 && image[0x2034770+16]==0x90);
/* A mismatching entry leaves the image alone and travel off. */
memcpy(image+0x2034770,frame,sizeof(frame));image[0x132e050]^=1;
runtime_atlas_state_bind(image,0x5570000);
assert(!runtime_atlas_travel_supported() && !memcmp(image+0x2034770,frame,sizeof(frame)));
setenv("BB_MAP_TRAVEL_ENABLED","0",1);
}
runtime_atlas_state_bind(NULL,0);assert(runtime_atlas_flag(12417830)==-1);
free(data);free(node);free(head);free(manager);free(image);puts("ATLAS_STATE_PASS");return 0;
}
+57
View File
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* From Hunter's Atlas by MikeTat1988 (github.com/MikeTat1988/HuntersAtlas); bbport: setenv for
* _putenv_s. */
#define _GNU_SOURCE /* bbport: setenv */
#include "../src/runtime_map.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include "../src/map_build_guard.inc"
static void ptr(unsigned char* memory,size_t off,void* value){memcpy(memory+off,&value,sizeof(value));}
int main(void) {
BbMapLocation l;
unsigned char *image=calloc(1,0x5570000),*world=calloc(1,0x100),*chr=calloc(1,0x600),*modules=calloc(1,0x100),*transform=calloc(1,0x300),*menu=calloc(1,0x100);
assert(image && world && chr && modules && transform);
runtime_map_bind(image,0x5570000);assert(!runtime_map_location(&l) && !l.valid && !runtime_map_menu_open());
memcpy(image+BB_MAP_GUARD_OFFSET,bb_map_guard,sizeof(bb_map_guard));
const unsigned char menu_guard[]={0x48,0x8b,0x05,0xd6,0x6f,0xdf,0x03,0x83,0x78,0x3c,0x00};
const unsigned char focus_guard[]={0x8b,0x8b,0xe8,0x0a,0,0,0x85,0xc9,0x79,0x06,0x8b,0x8b,0xe4,0x0a,0,0};
memcpy(image+0x1bdf835,focus_guard,sizeof(focus_guard));
memcpy(image+0x176b89b,menu_guard,sizeof(menu_guard));ptr(image,0x5562878,menu);
runtime_map_bind(image,0x5570000);assert(!runtime_map_menu_open());
assert(runtime_map_confirm_button()==0); /* Missing native predicate: fail closed. */
const unsigned char cross[]={0x31,0xc0,0xc3,0x90,0x90,0x90};
const unsigned char circle[]={0xb8,0x01,0x00,0x00,0x00,0xc3};
memcpy(image+0x1fb8730,cross,sizeof(cross));
setenv("BB_ENTER_BUTTON","0",1);
assert(runtime_map_confirm_button()==0x4000u); /* Conflicting environment cannot override game. */
memcpy(image+0x1fb8730,circle,sizeof(circle));
setenv("BB_ENTER_BUTTON","1",1);
assert(runtime_map_confirm_button()==0x2000u);
image[0x1fb8730+1]=2;assert(runtime_map_confirm_button()==0);
BbMapMenu native_menu;assert(!runtime_map_menu_state(&native_menu)&&!native_menu.supported);
memcpy(image+0x1fb8730,cross,sizeof(cross));
setenv("BB_ENTER_BUTTON","",1);
#include "../src/map_confirm_guard.inc"
memcpy(image+0x1fb8730,bb_map_cross_region_guard,sizeof(bb_map_cross_region_guard));
assert(runtime_map_confirm_button()==0x4000u);
image[0x1fb8730+24]^=1;assert(runtime_map_confirm_button()==0);
memcpy(image+0x1fb8730,cross,sizeof(cross));
int state=2;memcpy(menu+0x54,&state,4);assert(runtime_map_menu_open());
state=1;memcpy(menu+0x54,&state,4);assert(!runtime_map_menu_open());
state=0;memcpy(menu+0x54,&state,4);assert(!runtime_map_menu_open());
ptr(image,0x5562878,(void*)0x12345678);assert(!runtime_map_menu_open());
ptr(image,0x5562878,menu);
ptr(image,0x553e878,world);ptr(world,0x60,chr);ptr(chr,0x3b0,modules);ptr(modules,0x68,transform);
ptr(chr,0x288,world);uint32_t block=0x18010000;memcpy(chr+0x3f8,&block,4);
float pos[3]={-196.75f,-28.65f,65.39f},yaw=1.0f;memcpy(transform+0x1e0,pos,12);memcpy(transform+0x1d4,&yaw,4);
assert(runtime_map_location(&l) && l.valid && l.x==pos[0] && l.block==block);
pos[0]=NAN;memcpy(transform+0x1e0,pos,12);assert(!runtime_map_location(&l) && !l.valid);
ptr(modules,0x68,(void*)0x12345);assert(!runtime_map_location(&l));
ptr(world,0x60,NULL);assert(!runtime_map_location(&l));
runtime_map_bind(NULL,0);assert(runtime_map_confirm_button()==0);
free(menu);free(transform);free(modules);free(chr);free(world);free(image);
puts("MAP_LOCATION_READ_ONLY_PASS");return 0;
}