mirror of
https://github.com/PodeCaradox/GalaxyQuest-SteamFrame.git
synced 2026-10-17 23:01:12 +02:00
SteamOS gives a game RLIMIT_NICE 28 (nice down to -8); the launcher takes it before any thread starts, so every thread inherits it. On the Frame a chat client in the same cgroup took two cores, and the frame loop missed up to 67 refreshes in 10 s with its own work late; with the game's threads at -8 (tried on the running game) one or none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.4 KiB
C
85 lines
3.4 KiB
C
// Linux launcher (Steam Frame): the game code assumes a 32-bit address space,
|
|
// so the executable is linked at 0x98000000, the slot libgame.so takes on
|
|
// Android, and the rest of the window 0x80000000-0xE0010000 is reserved around
|
|
// it before the game maps MEM1/MEM2 there.
|
|
//
|
|
// galaxyquest the VR app
|
|
// galaxyquest --headless <data root> <save root> [s] the bring-up runner
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/resource.h>
|
|
|
|
#ifndef MAP_FIXED_NOREPLACE
|
|
#define MAP_FIXED_NOREPLACE 0x100000
|
|
#endif
|
|
|
|
static const uintptr_t kWindowBase = 0x80000000u;
|
|
static const size_t kWindowSize = 0x60010000u; // up to 0xE0010000 (locked cache)
|
|
static const uintptr_t kImageBase = 0x98000000u;
|
|
static const size_t kImageSlot = 0x08000000u; // the executable and the start of its heap
|
|
|
|
extern char _end[]; // end of the executable's image (lld)
|
|
|
|
void port_linux_main(uintptr_t windowBase, size_t windowSize);
|
|
int port_headless_main(int argc, char** argv);
|
|
|
|
static void reserve(uintptr_t lo, uintptr_t hi) {
|
|
void* p = mmap((void*)lo, hi - lo, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED_NOREPLACE, -1, 0);
|
|
if (p == (void*)lo) {
|
|
return;
|
|
}
|
|
fprintf(stderr, "cannot reserve the game address window %p-%p (got %p, errno %d)\n", (void*)lo, (void*)hi, p, errno);
|
|
FILE* f = fopen("/proc/self/maps", "r");
|
|
char line[512];
|
|
while (f && fgets(line, sizeof(line), f)) {
|
|
fputs(line, stderr);
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
// The kernel starts the brk heap up to 1 GB past a non-PIE executable, which
|
|
// can be inside the window. Reserved from .preinit_array, before any
|
|
// library's constructor makes the heap, the window wins and malloc uses mmap.
|
|
static void reserveWindow(int argc, char** argv, char** envp) {
|
|
(void)argc;
|
|
(void)argv;
|
|
(void)envp;
|
|
if ((uintptr_t)_end > kImageBase + kImageSlot) {
|
|
fprintf(stderr, "the executable ends at %p, past its slot (%p)\n", (void*)_end, (void*)(kImageBase + kImageSlot));
|
|
exit(1);
|
|
}
|
|
reserve(kWindowBase, kImageBase);
|
|
reserve(kImageBase + kImageSlot, kWindowBase + kWindowSize);
|
|
}
|
|
__attribute__((section(".preinit_array"), used)) static void (*sReserveWindow)(int, char**, char**) = reserveWindow;
|
|
|
|
int main(int argc, char** argv) {
|
|
// The disc layer keeps each game file open once read (2,400 files); a
|
|
// shell's soft limit is 1024.
|
|
struct rlimit files;
|
|
if (getrlimit(RLIMIT_NOFILE, &files) == 0 && files.rlim_cur < files.rlim_max) {
|
|
files.rlim_cur = files.rlim_max;
|
|
setrlimit(RLIMIT_NOFILE, &files);
|
|
}
|
|
// SteamOS lets a game raise its priority (RLIMIT_NICE's hard limit is 28
|
|
// there: down to nice -8), so the frame loop keeps its refreshes while
|
|
// other apps want the same cores (a chat client took two of them). Every
|
|
// thread started from here on inherits it.
|
|
struct rlimit nice;
|
|
if (getrlimit(RLIMIT_NICE, &nice) == 0 && nice.rlim_max > 20) {
|
|
nice.rlim_cur = nice.rlim_max;
|
|
if (setrlimit(RLIMIT_NICE, &nice) == 0) {
|
|
setpriority(PRIO_PROCESS, 0, 20 - (int)(nice.rlim_max > 40 ? 40 : nice.rlim_max));
|
|
}
|
|
}
|
|
if (argc > 1 && strcmp(argv[1], "--headless") == 0) {
|
|
return port_headless_main(argc - 1, argv + 1);
|
|
}
|
|
port_linux_main(kWindowBase, kWindowSize);
|
|
return 0;
|
|
}
|