// android.system.Os has no public chdir() (confirmed against the actual // API 34 stub jar - Java has no way to change a process's working // directory at all). This is the one bit of native code QuestShockActivity // needs, compiled into the same "main" library as engine/ (see // android/engine-patches/01-android-shared-lib.patch's ANDROID_EXTRA_SOURCES) // - engine/ itself is never modified. #include #include #include #include #include "qs_config.h" #include "qs_splash.h" #include "xr_input.h" #include "xr_overlay.h" #include "xr_session.h" JNIEXPORT void JNICALL Java_de_ladkau_questshock_QuestShockActivity_nativeChdir(JNIEnv *env, jclass clazz, jstring path) { const char *cpath = (*env)->GetStringUTFChars(env, path, NULL); chdir(cpath); (*env)->ReleaseStringUTFChars(env, path, cpath); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_QuestShockActivity_nativeSetSplashImage( JNIEnv *env, jclass clazz, jintArray pixels, jint width, jint height) { jint *data = (*env)->GetIntArrayElements(env, pixels, NULL); if (data == NULL) return; qs_splash_set_image((const uint32_t *)data, width, height); (*env)->ReleaseIntArrayElements(env, pixels, data, JNI_ABORT); } // KeyboardOverlay's hand-built on-screen keyboard (see // KeyboardOverlay.handleCharKeyDown()/handleCharKeyUp()) has no real // Android IME session behind it, so printable characters can't reach the // game via Android's usual IME path - instead this builds and pushes an // SDL_TEXTINPUT event directly, the same event type/shape SDL's own // Android backend pushes for typed text, using only the public // SDL_Event/SDL_PushEvent API. The engine's pump_events() (sdl_events.c) // only picks up printable characters from this event, not from // SDL_KEYDOWN. // // pump_events()'s SDL_TEXTINPUT handling sets sshockKeyStates[code] for // this character and only ever clears it again on a matching SDL_KEYUP. // Any character bound to a continuously-polled motion key (space -> jump, // w -> forward, etc., see MacSrc/Prefs.c's default keybinds.txt) is // polled every physics tick for as long as that state stays set, exactly // like a real held key - so nativeSendPrintableCharDown()/Up() are called // from the key button's own ACTION_DOWN/ACTION_UP (see // KeyboardOverlay.charKeyTouchListener()), not both at once: pushing the // release in the same call as the press would set and clear // sshockKeyStates[code] within a single pump_events() call, before the // game loop's poll (which runs once per frame, before pump_events - see // GameSrc/mainloop.c) ever has a chance to observe the press. JNIEXPORT void JNICALL Java_de_ladkau_questshock_KeyboardOverlay_nativeSendPrintableCharDown(JNIEnv *env, jclass clazz, jchar c) { SDL_Event event; SDL_zero(event); event.type = SDL_TEXTINPUT; event.text.timestamp = SDL_GetTicks(); event.text.windowID = 0; event.text.text[0] = (char)c; event.text.text[1] = '\0'; SDL_PushEvent(&event); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_KeyboardOverlay_nativeSendPrintableCharUp(JNIEnv *env, jclass clazz, jchar c) { SDL_Event releaseEvent; SDL_zero(releaseEvent); releaseEvent.type = SDL_KEYUP; releaseEvent.key.timestamp = SDL_GetTicks(); releaseEvent.key.windowID = 0; releaseEvent.key.state = SDL_RELEASED; releaseEvent.key.repeat = 0; // SDL keycodes for printable characters are the character's own // (lowercased) byte value - see sdl_events.c's own comment on this. releaseEvent.key.keysym.sym = (SDL_Keycode)tolower((unsigned char)c); releaseEvent.key.keysym.mod = KMOD_NONE; SDL_PushEvent(&releaseEvent); } // KeyboardOverlay's title-bar Close button - the keyboard is a fully // independent overlay quad from the menu launcher (see xr_overlay.h), so // this is the only way to hide it once it's open; the controller's menu // button only ever affects the menu launcher. JNIEXPORT void JNICALL Java_de_ladkau_questshock_KeyboardOverlay_nativeRequestClose(JNIEnv *env, jclass clazz) { xr_overlay_set_visible(xr_session_get_keyboard_overlay(), false); } // MenuOverlay's "Keyboard" button - only opens the keyboard overlay, never // touches the menu launcher's own visibility, so both can be shown // together. JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeShowKeyboard(JNIEnv *env, jclass clazz) { xr_overlay_set_visible(xr_session_get_keyboard_overlay(), true); } // MenuOverlay's "Save Configuration"/"Load Configuration" buttons - see // qs_config.h for why these only enqueue an event rather than doing the // actual file/engine-state work here directly (this runs on Android's UI // thread, not the engine's own). JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSaveConfig(JNIEnv *env, jclass clazz) { qs_config_request_save(); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestLoadConfig(JNIEnv *env, jclass clazz) { qs_config_request_load(); } // MenuOverlay's Video Options tab - live-appliable controls, see // qs_config.h. Same enqueue-only reasoning as the Save/Load buttons above. JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetRenderer(JNIEnv *env, jclass clazz, jboolean opengl) { qs_config_request_set_renderer(opengl); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetResolution(JNIEnv *env, jclass clazz, jint resolutionIndex) { qs_config_request_set_resolution(resolutionIndex); } // MenuOverlay's Main Menu tab's Language row - same enqueue-only reasoning as above. JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetLanguage(JNIEnv *env, jclass clazz, jint languageIndex) { qs_config_request_set_language(languageIndex); } // MenuOverlay's Main Menu tab's "Reset Position" button - same enqueue-only // reasoning as above. JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestResetPosition(JNIEnv *env, jclass clazz) { qs_config_request_reset_position(); } // MenuOverlay's Video Options tab - "Initial Screen"/"Game Screen" width/ // distance preset rows, same enqueue-only reasoning as above. JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetInitialScreenWidth(JNIEnv *env, jclass clazz, jint presetIndex) { qs_config_request_set_initial_screen_width(presetIndex); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetGameScreenWidth(JNIEnv *env, jclass clazz, jint presetIndex) { qs_config_request_set_game_screen_width(presetIndex); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetInitialScreenDistance(JNIEnv *env, jclass clazz, jint presetIndex) { qs_config_request_set_initial_screen_distance(presetIndex); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestSetGameScreenDistance(JNIEnv *env, jclass clazz, jint presetIndex) { qs_config_request_set_game_screen_distance(presetIndex); } // Read-only queries - safe to call directly (no enqueue needed, see // qs_config.h's own comment on each of these). JNIEXPORT jboolean JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetRendererIsOpenGl(JNIEnv *env, jclass clazz) { return qs_config_current_renderer_is_opengl(); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetResolutionIndex(JNIEnv *env, jclass clazz) { return qs_config_current_resolution_index(); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetInitialScreenWidthIndex(JNIEnv *env, jclass clazz) { return qs_config_current_initial_screen_width_index(); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetGameScreenWidthIndex(JNIEnv *env, jclass clazz) { return qs_config_current_game_screen_width_index(); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetInitialScreenDistanceIndex(JNIEnv *env, jclass clazz) { return qs_config_current_initial_screen_distance_index(); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetGameScreenDistanceIndex(JNIEnv *env, jclass clazz) { return qs_config_current_game_screen_distance_index(); } // Preset button *labels* (not the current value) - shared by both // "Initial Screen" and "Game Screen" columns, since their offset/step is // config-file-only, not per-profile (see qs_config.h's own comment). JNIEXPORT jfloat JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetScreenWidthPresetValue(JNIEnv *env, jclass clazz, jint index) { return qs_config_screen_width_preset_value(index); } JNIEXPORT jfloat JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetScreenDistancePresetValue(JNIEnv *env, jclass clazz, jint index) { return qs_config_screen_distance_preset_value(index); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetLanguage(JNIEnv *env, jclass clazz) { return qs_config_current_language(); } // MenuOverlay's header - polled on a periodic timer (see its own comment), // not event-driven, so these are plain synchronous reads too. JNIEXPORT jfloat JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetFps(JNIEnv *env, jclass clazz) { return xr_session_get_fps(); } JNIEXPORT jfloat JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetFrameTimeMs(JNIEnv *env, jclass clazz) { return xr_session_get_frame_ms(); } JNIEXPORT jfloat JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetCpuUtilization(JNIEnv *env, jclass clazz) { return xr_session_get_cpu_utilization(); } JNIEXPORT jfloat JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetGpuUtilization(JNIEnv *env, jclass clazz) { return xr_session_get_gpu_utilization(); } // Controller Options tab: actionIndex follows xr_input.c's action order // (MenuOverlay.java and Strings.java hold the matching labels). JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestBeginControlCapture(JNIEnv *env, jclass clazz, jint actionIndex) { qs_config_request_begin_control_capture(actionIndex); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestCancelControlCapture(JNIEnv *env, jclass clazz) { qs_config_request_cancel_control_capture(); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestResetPlayerControls(JNIEnv *env, jclass clazz) { qs_config_request_reset_player_controls(); } JNIEXPORT jstring JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetControlBindingKey(JNIEnv *env, jclass clazz, jint actionIndex) { return (*env)->NewStringUTF(env, xr_input_get_action_binding_name(actionIndex)); } JNIEXPORT jstring JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetActiveInteractionProfilePath(JNIEnv *env, jclass clazz) { return (*env)->NewStringUTF(env, xr_input_get_active_interaction_profile_path()); } JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeSetControllerOptionsTabOpen(JNIEnv *env, jclass clazz, jboolean open) { xr_input_set_controller_options_tab_open(open == JNI_TRUE); } // Cheats tab: same enqueue-only pattern as the other requests. JNIEXPORT void JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeRequestCheat(JNIEnv *env, jclass clazz, jint cheatId) { qs_config_request_cheat(cheatId); } JNIEXPORT jint JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetCheatUnlimitedMask(JNIEnv *env, jclass clazz) { return qs_config_cheat_unlimited_mask(); } JNIEXPORT jboolean JNICALL Java_de_ladkau_questshock_MenuOverlay_nativeGetCheatPhysicsRunning(JNIEnv *env, jclass clazz) { return qs_config_cheat_physics_running(); }