├── .gitignore ├── CMakeLists.txt └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(speedy-portal-bridge C) 3 | 4 | set(CMAKE_C_STANDARD 17) 5 | 6 | add_executable(speedy-portal-bridge main.c) -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static const char* g_map_names[] = { 13 | "testchmb_a_00", 14 | "testchmb_a_01", 15 | "testchmb_a_02", 16 | "testchmb_a_03", 17 | "testchmb_a_04", 18 | "testchmb_a_05", 19 | "testchmb_a_06", 20 | "testchmb_a_07", 21 | "testchmb_a_08", 22 | "testchmb_a_09", 23 | "testchmb_a_10", 24 | "testchmb_a_11", 25 | "testchmb_a_13", 26 | "testchmb_a_14", 27 | "testchmb_a_15", 28 | "escape_00", 29 | "escape_01", 30 | "escape_02" 31 | }; 32 | 33 | static const size_t g_map_names_len = sizeof(g_map_names) / sizeof(g_map_names[0]); 34 | 35 | char* read_from_proccess(pid_t pid, void* addr) { 36 | size_t cap = 0; 37 | char* res = NULL; 38 | bool done = false; 39 | 40 | while (!done) { 41 | uint32_t word = ptrace(PTRACE_PEEKDATA, pid, addr + cap * 4, NULL); 42 | res = realloc(res, (cap + 1) * 4); 43 | 44 | if (res == NULL) { 45 | puts("Out of memory!"); 46 | abort(); 47 | } 48 | 49 | memcpy(res + cap * 4, &word, 4); 50 | 51 | for (int i = 0; i < 4; ++i) { 52 | done |= res[cap * 4 + i] == '\0'; 53 | } 54 | 55 | cap += 1; 56 | } 57 | 58 | return res; 59 | } 60 | 61 | char* wait_for_openat(pid_t pid) { 62 | static const int sys_open = 5; 63 | static const int sys_openat = 295; 64 | 65 | if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) goto error; 66 | if (waitpid(pid, NULL, WUNTRACED | WCONTINUED) < 0) goto error; 67 | 68 | char* result = NULL; 69 | 70 | while (result == NULL) { 71 | if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) goto error; 72 | if (waitpid(pid, NULL, WUNTRACED | WCONTINUED) < 0) goto error; 73 | 74 | struct user_regs_struct regs; 75 | 76 | if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) < 0) goto error; 77 | 78 | void* fileptr = NULL; 79 | 80 | if (regs.orig_rax == sys_open) { 81 | fileptr = (void*) (regs.rbx & 0xFFFFFFFF); /* read ebx only */ 82 | } else if (regs.orig_rax == sys_openat) { 83 | fileptr = (void*) (regs.rcx & 0xFFFFFFFF); /* read ecx only */ 84 | } 85 | 86 | if (fileptr != NULL) { 87 | result = read_from_proccess(pid, fileptr); 88 | } 89 | 90 | if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) goto error; 91 | if (waitpid(pid, NULL, WUNTRACED | WCONTINUED) < 0) goto error; 92 | } 93 | 94 | ptrace(PTRACE_DETACH, pid, NULL, NULL); 95 | waitpid(pid, NULL, WUNTRACED | WCONTINUED); 96 | return result; 97 | 98 | error: 99 | perror("Error waiting for openat"); 100 | return NULL; 101 | } 102 | 103 | char* extract_map_name(const char* filename) { 104 | size_t len = strlen(filename); 105 | 106 | if (len < 4) { 107 | return NULL; 108 | } 109 | 110 | if (memcmp(filename + len - 4, ".bsp", 4) != 0) { 111 | return NULL; 112 | } 113 | 114 | size_t start = len - 1; 115 | 116 | while (start > 0 && filename[start - 1] != '/') { 117 | start -= 1; 118 | } 119 | 120 | size_t result_len = len - start - 4; 121 | char* result = calloc(result_len, sizeof(char)); 122 | 123 | if (result == NULL) { 124 | puts("Out of memory!"); 125 | abort(); 126 | } 127 | 128 | memcpy(result, filename + start, result_len); 129 | return result; 130 | } 131 | 132 | bool is_valid_number(const char* s) { 133 | for (; *s; s++) { 134 | if (*s < '0' || *s > '9') { 135 | return false; 136 | } 137 | } 138 | 139 | return true; 140 | } 141 | 142 | int main(int argc, char** argv) { 143 | const char* invoc_name = argc ? argv[0] : "speedy-portal-bridge"; 144 | 145 | if (argc < 3) { 146 | printf("Usage: %s \n", invoc_name); 147 | return EXIT_FAILURE; 148 | } 149 | 150 | if (!is_valid_number(argv[1]) || !is_valid_number(argv[2])) { 151 | printf("%s: invalid process id\n", invoc_name); 152 | return EXIT_FAILURE; 153 | } 154 | 155 | pid_t speedy_pid = strtol(argv[2], NULL, 10); 156 | pid_t portal_pid = strtol(argv[1], NULL, 10); 157 | 158 | char* res = NULL; 159 | const char* current_map = NULL; 160 | 161 | 162 | while ((res = wait_for_openat(portal_pid))) { 163 | char* name; 164 | bool sendsig; 165 | 166 | sendsig = false; 167 | 168 | if ((name = extract_map_name(res))) { 169 | for (size_t i = 0; i < g_map_names_len; ++i) { 170 | if (strcmp(name, g_map_names[i]) == 0 && current_map != g_map_names[i]) { 171 | current_map = g_map_names[i]; 172 | sendsig = true; 173 | } 174 | } 175 | } 176 | 177 | if (sendsig) { 178 | printf("loading %s\n", current_map); 179 | kill(speedy_pid, SIGUSR1); 180 | } 181 | 182 | free(name); 183 | free(res); 184 | } 185 | 186 | return EXIT_SUCCESS; 187 | } 188 | --------------------------------------------------------------------------------