otoca: add cam hook, fix printer rotation (#573)

## Link to GitHub Issue or related Pull Request, if one exists
related to #154 though this PR doesn't fix it

## Description of change

1. Add an option for cam hook so that the game can be launched without
any camera
1. Rotate image printed out by print-to-file so that it's right side up
(portrait orientation)

## Testing
Able to boot the game without any cameras, and tested printing in test
menu.

"Holo" printing remains broken.
This commit is contained in:
bicarus
2026-03-09 02:54:04 -07:00
committed by GitHub
parent 34ef034345
commit be388f7b49
9 changed files with 168 additions and 1 deletions
+1
View File
@@ -256,6 +256,7 @@ namespace games {
const std::string otoca("Otoca D'or"); const std::string otoca("Otoca D'or");
games.push_back(otoca); games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() }); buttons.insert({ otoca, otoca::get_buttons() });
buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].emplace_back("arkkep.dll"); file_hints[otoca].emplace_back("arkkep.dll");
// winning eleven // winning eleven
+5
View File
@@ -23,6 +23,11 @@ std::vector<Button> &games::otoca::get_buttons() {
return buttons; return buttons;
} }
std::string games::otoca::get_buttons_help() {
// keep to max 100 characters wide
return " BT_Left Lever BT_Right";
}
std::vector<Light> &games::otoca::get_lights() { std::vector<Light> &games::otoca::get_lights() {
static std::vector<Light> lights; static std::vector<Light> lights;
+1
View File
@@ -30,5 +30,6 @@ namespace games::otoca {
// getters // getters
std::vector<Button> &get_buttons(); std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::vector<Light> &get_lights(); std::vector<Light> &get_lights();
} }
+142
View File
@@ -1,16 +1,121 @@
#include "otoca.h" #include "otoca.h"
#include "p4io.h" #include "p4io.h"
#include "games/shared/printer.h" #include "games/shared/printer.h"
#include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h"
#define OTOCA_DEBUG_VERBOSE 0
#if OTOCA_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace games::otoca { namespace games::otoca {
bool BYPASS_CAMERA = false;
// for dev only
static bool CAMLIB_CALL_ORIGINAL = false;
OtocaGame::OtocaGame() : Game("Otoca D'or") { OtocaGame::OtocaGame() : Game("Otoca D'or") {
} }
OtocaGame::~OtocaGame() { OtocaGame::~OtocaGame() {
} }
typedef void (__cdecl *LibCameraInit_t)();
static LibCameraInit_t LibCameraInit_orig = nullptr;
static void __cdecl LibCameraInit_hook() {
if (CAMLIB_CALL_ORIGINAL) {
LibCameraInit_orig();
log_debug("otoca::cam", "LibCameraInit returned");
}
}
typedef int (__cdecl *LibCameraOpen_t)(double, int, int);
static LibCameraOpen_t LibCameraOpen_orig = nullptr;
static int __cdecl LibCameraOpen_hook(double a1, int a2, int a3) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (15, 2, 0) returned 0
ret = LibCameraOpen_orig(a1, a2, a3);
log_debug("otoca::cam", "LibCameraOpen({}, {}, {}) returned {}", a1, a2, a3, ret);
}
return ret;
}
typedef int (__cdecl *LibCameraStop_t)(int);
static LibCameraStop_t LibCameraStop_orig = nullptr;
static int __cdecl LibCameraStop_hook(int a1) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam,
ret = LibCameraStop_orig(a1);
log_debug("otoca::cam", "LibCameraStop({}) returned {}", a1, ret);
}
return ret;
}
typedef void (__cdecl *LibCameraGetCameraNr_t)();
static LibCameraGetCameraNr_t LibCameraGetCameraNr_orig = nullptr;
static void __cdecl LibCameraGetCameraNr_hook() {
if (CAMLIB_CALL_ORIGINAL) {
LibCameraGetCameraNr_orig();
log_debug("otoca::cam", "LibCameraGetCameraNr returned");
}
}
typedef int (__cdecl *LibCameraRun_t)(int);
static LibCameraRun_t LibCameraRun_orig = nullptr;
static int __cdecl LibCameraRun_hook(int a1) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (0) returned 0
ret = LibCameraRun_orig(a1);
log_debug("otoca::cam", "LibCameraRun({}) returned {}", a1, ret);
}
return ret;
}
typedef int (__cdecl *LibCameraGetImage_t)(int, void *);
static LibCameraGetImage_t LibCameraGetImage_orig = nullptr;
static int __cdecl LibCameraGetImage_hook(int a1, void *a2) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// not sure if this is called
ret = LibCameraGetImage_orig(a1, a2);
log_debug("otoca::cam", "LibCameraGetImage({}, {}) returned {}", a1, fmt::ptr(a2), ret);
}
return ret;
}
typedef int (__cdecl *LibCameraGetBrightness_t)(int *, int);
static LibCameraGetBrightness_t LibCameraGetBrightness_orig = nullptr;
static int __cdecl LibCameraGetBrightness_hook(int *a1, int a2) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (ptr,) returned
ret = LibCameraGetBrightness_orig(a1, a2);
}
log_debug("otoca::cam", "LibCameraGetBrightness({}, {}) returned {}", fmt::ptr(a1), a2, ret);
return ret;
}
typedef int (__cdecl *LibCameraGetBrightnessRange_t)(int *, int *, int);
static LibCameraGetBrightnessRange_t LibCameraGetBrightnessRange_orig = nullptr;
static int __cdecl LibCameraGetBrightnessRange_hook(int *a1, int *a2, int a3) {
int ret = 0;
if (CAMLIB_CALL_ORIGINAL) {
// with webcam, (ptr, ptr, 0) returned 0
ret = LibCameraGetBrightnessRange_orig(a1, a2, a3);
log_debug("otoca::cam", "LibCameraGetBrightnessRange({}, {}, {}) returned {}", fmt::ptr(a1), fmt::ptr(a2), a3, ret);
}
return ret;
}
void OtocaGame::attach() { void OtocaGame::attach() {
Game::attach(); Game::attach();
@@ -23,5 +128,42 @@ namespace games::otoca {
// initialize IO hooks // initialize IO hooks
p4io_hook(); p4io_hook();
games::shared::printer_attach(); games::shared::printer_attach();
if (BYPASS_CAMERA) {
libutils::try_library("libcamera.dll");
const auto libcamera = "libcamera.dll";
detour::trampoline_try(
libcamera,
"?LibCameraInit@@YAXXZ",
LibCameraInit_hook, &LibCameraInit_orig);
detour::trampoline_try(
libcamera,
"?LibCameraOpen@@YA?AW4LIBCAMERA_STATUS@@NHPAUt_libcamera_open_param@@@Z",
LibCameraOpen_hook, &LibCameraOpen_orig);
detour::trampoline_try(
libcamera,
"?LibCameraStop@@YA?AW4LIBCAMERA_STATUS@@H@Z",
LibCameraStop_hook, &LibCameraStop_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetCameraNr@@YAHXZ",
LibCameraGetCameraNr_hook, &LibCameraGetCameraNr_orig);
detour::trampoline_try(
libcamera,
"?LibCameraRun@@YA?AW4LIBCAMERA_STATUS@@H@Z",
LibCameraRun_hook, &LibCameraRun_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetImage@@YA?AW4LIBCAMERA_STATUS@@HPAX@Z",
LibCameraGetImage_hook, &LibCameraGetImage_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetBrightness@@YA?AW4LIBCAMERA_STATUS@@PAJH@Z",
LibCameraGetBrightness_hook, &LibCameraGetBrightness_orig);
detour::trampoline_try(
libcamera,
"?LibCameraGetBrightnessRange@@YA?AW4LIBCAMERA_STATUS@@PAJ0H@Z",
LibCameraGetBrightnessRange_hook, &LibCameraGetBrightnessRange_orig);
}
} }
} }
+2
View File
@@ -4,6 +4,8 @@
namespace games::otoca { namespace games::otoca {
extern bool BYPASS_CAMERA;
class OtocaGame : public games::Game { class OtocaGame : public games::Game {
public: public:
OtocaGame(); OtocaGame();
+1 -1
View File
@@ -334,7 +334,7 @@ namespace games::shared {
image_data[index + 2] = tmp; image_data[index + 2] = tmp;
} }
if (avs::game::is_model({"KLP", "KFC"})) { if (avs::game::is_model({"KLP", "KFC", "NCG"})) {
// rotate image clockwise // rotate image clockwise
log_misc("printer", "rotate clockwise..."); log_misc("printer", "rotate clockwise...");
auto rotated = new uint8_t[image_width * image_height * 3]; auto rotated = new uint8_t[image_width * image_height * 3];
+4
View File
@@ -559,6 +559,10 @@ int main_implementation(int argc, char *argv[]) {
nvenc_hook::FORCE_DISABLE = true; nvenc_hook::FORCE_DISABLE = true;
} }
#endif #endif
if (options[launcher::Options::OtocaCamHook].value_bool()) {
games::otoca::BYPASS_CAMERA = true;
}
if (options[launcher::Options::LoadJubeatModule].value_bool()) { if (options[launcher::Options::LoadJubeatModule].value_bool()) {
attach_jb = true; attach_jb = true;
} }
+11
View File
@@ -2678,6 +2678,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Development", .category = "Development",
.disabled = true, .disabled = true,
},
{
// OtocaCamHook
.title = "Otoca Camera Check Bypass",
.name = "otocacamhook",
.desc =
"This game REQUIRES a (compatible) webcam to save progress. If you don't have any, check this "
"option to bypass camera error during boot, allowing you to try out the game.",
.type = OptionType::Bool,
.game_name = "Otoca D'or",
.category = "Game Options",
} }
}; };
+1
View File
@@ -271,6 +271,7 @@ namespace launcher {
LovePlusPrinterOutputFormat, LovePlusPrinterOutputFormat,
LovePlusPrinterJPGQuality, LovePlusPrinterJPGQuality,
OptionConflictResolution, OptionConflictResolution,
OtocaCamHook,
}; };
enum class OptionsCategory { enum class OptionsCategory {