From cc21923f58aafe4fb17f0315aed204e137b3a22a Mon Sep 17 00:00:00 2001 From: Andrea <1890041+Zer0one@users.noreply.github.com> Date: Wed, 19 Aug 2026 04:24:34 +0200 Subject: [PATCH] Add normalized lightgun calibration data --- Scripts/generate_initial_nvram_templates.py | 9 ++- Src/OSD/libretro/InitialNvramTemplates.h | 40 ++++++++++ Src/OSD/libretro/LibretroNvramSettings.h | 87 +++++++++++++++++++-- Src/OSD/libretro/libretro.cpp | 5 ++ 4 files changed, 132 insertions(+), 9 deletions(-) diff --git a/Scripts/generate_initial_nvram_templates.py b/Scripts/generate_initial_nvram_templates.py index b82b270..dde0063 100644 --- a/Scripts/generate_initial_nvram_templates.py +++ b/Scripts/generate_initial_nvram_templates.py @@ -9,13 +9,17 @@ from pathlib import Path # Every released set for which Automatic Initial NVRAM Setup changes Link Mode, -# plus the two released Star Wars Trilogy Arcade sets whose cabinet is forced -# to Upright. Prototypes and location tests are intentionally excluded. +# the two released Star Wars Trilogy Arcade sets whose cabinet is forced to +# Upright, and gun games requiring normalized Service-menu calibration. +# Prototypes and location tests are intentionally excluded. GAMES = ( "daytona2", "dayto2pe", "dirtdvls", "dirtdvlsu", "dirtdvlsau", "dirtdvlsj", "dirtdvlsg", "harley", "harleya", + "lamachin", "lemans24", + "lostwsga", + "oceanhun", "oceanhuna", "scud", "scudau", "scuddx", "scuddxo", "scudplus", "scudplusa", "skichamp", "spikeofe", "spikeout", @@ -24,7 +28,6 @@ GAMES = ( "von2", "von254g", "von2a", "von2o", ) - def block_data(blob: bytes, wanted: bytes) -> bytes: offset = 0 while offset + 12 <= len(blob): diff --git a/Src/OSD/libretro/InitialNvramTemplates.h b/Src/OSD/libretro/InitialNvramTemplates.h index 8f9c872..5a631b7 100644 --- a/Src/OSD/libretro/InitialNvramTemplates.h +++ b/Src/OSD/libretro/InitialNvramTemplates.h @@ -107,6 +107,16 @@ static constexpr Template kTemplates[] = { 0x0400, 0x0101, 0x6101, 0x0101, 0x6101, 0x0180, 0xc040, 0xc040, 0xc040, 0xc040, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, } }, + { "lamachin", { + 0x4d33, 0x5345, 0x4741, 0x40dc, 0xa310, 0x0470, 0x0001, 0x0401, + 0x0101, 0x0101, 0x0101, 0x0000, 0x0101, 0x0100, 0x0005, 0x0480, + 0x0103, 0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0401, 0x0101, 0x0101, 0x0101, + 0x0000, 0x0101, 0x0100, 0x0005, 0x0480, 0x0103, 0x0100, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + } }, { "lemans24", { 0x4d33, 0x5345, 0x4741, 0x7870, 0xa319, 0x015f, 0x0001, 0x0501, 0x0101, 0x0101, 0x0101, 0x0000, 0x0002, 0x0cc0, 0x0005, 0x0070, @@ -117,6 +127,36 @@ static constexpr Template kTemplates[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x001f, 0xded8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x001f, 0xded8, 0x0000, 0x0022, } }, + { "lostwsga", { + 0x4d33, 0x5345, 0x4741, 0xeda6, 0xa319, 0x0111, 0x0002, 0x0501, + 0x0101, 0x0101, 0x0101, 0x0000, 0x0003, 0x0100, 0x0205, 0x0470, + 0x0100, 0x0100, 0x0000, 0x9001, 0x1001, 0x9001, 0x1001, 0x766e, + 0xd518, 0xcf0c, 0x7b6c, 0xde1d, 0xc705, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0002, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + } }, + { "oceanhun", { + 0x4d33, 0x5345, 0x4741, 0x8d3b, 0xa110, 0x0360, 0x0001, 0x0002, + 0x0101, 0x0101, 0x0202, 0x0001, 0x0100, 0x0000, 0x0000, 0x0101, + 0x0000, 0x0000, 0x0100, 0x80c0, 0x4080, 0xc040, 0x80c0, 0x4080, + 0xc040, 0x80c0, 0x4080, 0xc040, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0101, 0x0101, 0x0202, + 0x0001, 0x0100, 0x0000, 0x0000, 0x0101, 0x0000, 0x0000, 0x0100, + 0x80c0, 0x4080, 0xc040, 0x80c0, 0x4080, 0xc040, 0x80c0, 0x4080, + 0xc040, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + } }, + { "oceanhuna", { + 0x4d33, 0x5345, 0x4741, 0x8d3b, 0xa110, 0x0360, 0x0001, 0x0002, + 0x0101, 0x0101, 0x0202, 0x0001, 0x0100, 0x0000, 0x0000, 0x0101, + 0x0000, 0x0000, 0x0100, 0x80c0, 0x4080, 0xc040, 0x80c0, 0x4080, + 0xc040, 0x80c0, 0x4080, 0xc040, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0101, 0x0101, 0x0202, + 0x0001, 0x0100, 0x0000, 0x0000, 0x0101, 0x0000, 0x0000, 0x0100, + 0x80c0, 0x4080, 0xc040, 0x80c0, 0x4080, 0xc040, 0x80c0, 0x4080, + 0xc040, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + } }, { "scud", { 0x4d33, 0x5345, 0x4741, 0x18e8, 0xa218, 0x0103, 0x0001, 0x0001, 0xffff, 0xff01, 0x0101, 0x0001, 0x0000, 0x0000, 0x0000, 0x0100, diff --git a/Src/OSD/libretro/LibretroNvramSettings.h b/Src/OSD/libretro/LibretroNvramSettings.h index 0524b69..ed3e278 100644 --- a/Src/OSD/libretro/LibretroNvramSettings.h +++ b/Src/OSD/libretro/LibretroNvramSettings.h @@ -21,6 +21,7 @@ enum class Setting : unsigned { Vocal, LeverFeedback, DefaultView, + ProvideCalibrationData, Count, }; @@ -37,6 +38,8 @@ enum Capability : unsigned { Vocal = 1u << static_cast(Setting::Vocal), LeverFeedback = 1u << static_cast(Setting::LeverFeedback), DefaultView = 1u << static_cast(Setting::DefaultView), + ProvideCalibrationData = + 1u << static_cast(Setting::ProvideCalibrationData), }; constexpr unsigned CapabilityFor(Setting setting) @@ -110,17 +113,21 @@ inline const SupportedGame *GetSupportedGames(size_t &count) { "harleya", "harley", { "export", "normal", "stand_alone", "1", "deluxe" } }, { "lamachin", nullptr, - { "japan", "level_5", nullptr, nullptr, "standard" } }, + { "japan", "level_5", nullptr, nullptr, "standard", nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, "disabled" } }, { "lemans24", nullptr, { "japan", "level_8", "no_link", "1", "twin_lemans", "none" } }, { "lostwsga", nullptr, - { "japan", "level_8" } }, + { "japan", "level_8", nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "disabled" } }, { "magtruck", nullptr, { "export", "level_8", nullptr, nullptr, nullptr, nullptr, nullptr, "off" } }, { "oceanhun", nullptr, - { "japan", "normal", nullptr, nullptr, "deluxe" } }, + { "japan", "normal", nullptr, nullptr, "deluxe", nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "disabled" } }, { "oceanhuna", "oceanhun", - { "japan", "normal", nullptr, nullptr, "deluxe" } }, + { "japan", "normal", nullptr, nullptr, "deluxe", nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "disabled" } }, { "scud", nullptr, { "japan", "normal", "single", "1", "twin" } }, { "scudau", "scud", @@ -147,10 +154,10 @@ inline const SupportedGame *GetSupportedGames(size_t &count) nullptr, nullptr, nullptr, nullptr, "driver" } }, { "swtrilgy", nullptr, { "export", "normal", nullptr, nullptr, "upright", nullptr, nullptr, - nullptr, nullptr, "enable" } }, + nullptr, nullptr, "enable", nullptr, "disabled" } }, { "swtrilgya", "swtrilgy", { "export", "normal", nullptr, nullptr, "upright", nullptr, nullptr, - nullptr, nullptr, "enable" } }, + nullptr, nullptr, "enable", nullptr, "disabled" } }, { "vf3", nullptr, { "japan", "normal" } }, { "vf3a", "vf3", @@ -210,6 +217,7 @@ inline const char *GetDefaultValue(const Game *game, Setting setting) struct Selection { const char *values[static_cast(Setting::Count)] = {}; + bool normalizeAnalogInputs = false; const char *Get(Setting setting) const { @@ -218,6 +226,8 @@ struct Selection { bool HasAny() const { + if (normalizeAnalogInputs) + return true; for (const char *value : values) if (value) return true; @@ -476,6 +486,8 @@ NVRAM_VALUES(kLeverFeedback, { "enable", "ON", 1 }, { "disable", "Disable", 0 }); NVRAM_VALUES(kDefaultView, { "driver", "Driver", 0 }, { "behind", "Behind", 0x0004 }); +NVRAM_VALUES(kProvideCalibrationData, + { "disabled", "OFF", 0 }, { "enabled", "ON", 1 }); NVRAM_INFO(kCountryInfoJuea0, Setting::Country, "Country", "Set the machine country stored in NVRAM.", kCountryJuea0); NVRAM_INFO(kCountryInfoJuea1, Setting::Country, "Country", "Set the machine country stored in NVRAM.", kCountryJuea1); @@ -527,6 +539,10 @@ NVRAM_INFO(kChristmasInfo, Setting::ChristmasMode, "Christmas Mode", "Set Magica NVRAM_INFO(kVocalInfo, Setting::Vocal, "Vocal", "Select the Daytona USA 2 vocal version stored in NVRAM.", kVocal); NVRAM_INFO(kLeverFeedbackInfo, Setting::LeverFeedback, "Lever Feedback", "Enable or disable Star Wars Trilogy Arcade's Deluxe cabinet lever feedback stored in NVRAM.", kLeverFeedback); NVRAM_INFO(kDefaultViewInfo, Setting::DefaultView, "Default View", "Select Sega Rally 2's default driving view stored in NVRAM.", kDefaultView); +NVRAM_INFO(kProvideCalibrationDataInfo, Setting::ProvideCalibrationData, + "Provide Calibration Data", + "Apply validated title-specific calibration data at startup, overwriting calibration stored by the Service Menu.", + kProvideCalibrationData); #undef NVRAM_INFO #undef NVRAM_VALUES @@ -641,6 +657,15 @@ inline const SettingInfo *GetSettingInfo(const Game *game, Setting setting) return family == Family::StarWars ? &kLeverFeedbackInfo : nullptr; case Setting::DefaultView: return family == Family::SegaRally2 ? &kDefaultViewInfo : nullptr; + case Setting::ProvideCalibrationData: + switch (family) + { + case Family::Lamachin: + case Family::LostWorld: + case Family::OceanHunter: + case Family::StarWars: return &kProvideCalibrationDataInfo; + default: return nullptr; + } default: return nullptr; } @@ -755,6 +780,7 @@ inline const char *OptionSuffix(Setting setting) "vocal", "lever_feedback", "default_view", + "provide_calibration_data", }; return suffixes[static_cast(setting)]; } @@ -843,6 +869,9 @@ inline ApplyResult Apply(const Game &game, uint16_t *words, }; uint16_t value = 0; + const bool provideCalibrationData = + SelectedValue(game, Setting::ProvideCalibrationData, selection, value) && + value != 0; switch (family) { case Family::Bass: @@ -906,6 +935,15 @@ inline ApplyResult Apply(const Game &game, uint16_t *words, if (SelectedValue(game, Setting::Country, selection, value)) replaceHigh(words[12], value); if (SelectedValue(game, Setting::Difficulty, selection, value)) replaceMasked(words[15], 0x00e0, value); if (SelectedValue(game, Setting::Cabinet, selection, value)) replaceHigh(words[14], value); + if (selection.normalizeAnalogInputs || provideCalibrationData) + { + // Service-menu P1/P2 X/Y calibration: complete 00-FF range in the + // primary settings block and its redundant copy. + for (unsigned word = 19; word <= 22; ++word) + replaceMasked(words[word], 0xffff, 0xff00); + for (unsigned word = 48; word <= 51; ++word) + replaceMasked(words[word], 0xffff, 0xff00); + } break; case Family::LeMans24: if (SelectedValue(game, Setting::Country, selection, value)) replaceHigh(words[12], value); @@ -918,6 +956,19 @@ inline ApplyResult Apply(const Game &game, uint16_t *words, case Family::LostWorld: if (SelectedValue(game, Setting::Country, selection, value)) replaceHigh(words[12], value); if (SelectedValue(game, Setting::Difficulty, selection, value)) replaceMasked(words[15], 0x00f0, value); + if (selection.normalizeAnalogInputs || provideCalibrationData) + { + // Five-point Service-menu calibration reduced by the game to center and + // endpoint values for each axis. Apply the same normalized F4/80/0B + // calibration to both players; counters and transient records are not + // copied from the captured samples. + replaceMasked(words[23], 0xffff, 0x8080); // P1 X/Y center + replaceMasked(words[24], 0xffff, 0xf40b); // P1 X endpoints + replaceMasked(words[25], 0xffff, 0xf40b); // P1 Y endpoints + replaceMasked(words[26], 0xffff, 0x8080); // P2 X/Y center + replaceMasked(words[27], 0xffff, 0xf40b); // P2 X endpoints + replaceMasked(words[28], 0xffff, 0xf40b); // P2 Y endpoints + } break; case Family::MagTruck: if (SelectedValue(game, Setting::Country, selection, value)) replaceHigh(words[12], value); @@ -935,6 +986,23 @@ inline ApplyResult Apply(const Game &game, uint16_t *words, replaceHigh(words[15], value), replaceHigh(words[44], value); if (SelectedValue(game, Setting::Cabinet, selection, value)) replaceHigh(words[16], value), replaceHigh(words[45], value); + if (selection.normalizeAnalogInputs || provideCalibrationData) + { + // Service-menu calibration for P1/P2 X/Y: center 80, complete 00-FF + // range. Preserve the unused third analog channel in each player group. + replaceMasked(words[19], 0xffff, 0x80ff); + replaceMasked(words[20], 0xffff, 0x0080); + replaceMasked(words[21], 0xffff, 0xff00); + replaceMasked(words[24], 0xffff, 0xff00); + replaceMasked(words[25], 0xffff, 0x80ff); + replaceMasked(words[26], 0xffff, 0x0080); + replaceMasked(words[48], 0xffff, 0x80ff); + replaceMasked(words[49], 0xffff, 0x0080); + replaceMasked(words[50], 0xffff, 0xff00); + replaceMasked(words[53], 0xffff, 0xff00); + replaceMasked(words[54], 0xffff, 0x80ff); + replaceMasked(words[55], 0xffff, 0x0080); + } break; case Family::Scud: if (SelectedValue(game, Setting::Country, selection, value)) @@ -994,6 +1062,13 @@ inline ApplyResult Apply(const Game &game, uint16_t *words, if (SelectedValue(game, Setting::Difficulty, selection, value)) replaceMasked(words[12], 0x0038, value); if (SelectedValue(game, Setting::Cabinet, selection, value)) replaceMasked(words[12], 0x0400, value); if (SelectedValue(game, Setting::LeverFeedback, selection, value)) replaceMasked(words[13], 0x0001, value); + if (selection.normalizeAnalogInputs || provideCalibrationData) + { + // X/Y calibration is stored as minimum, maximum and center values. + replaceMasked(words[24], 0xffff, 0x00ff); + replaceMasked(words[25], 0xffff, 0x8000); + replaceMasked(words[26], 0xffff, 0xff80); + } break; case Family::VirtuaFighter3: if (SelectedValue(game, Setting::Difficulty, selection, value)) replaceBackup(122886, value); diff --git a/Src/OSD/libretro/libretro.cpp b/Src/OSD/libretro/libretro.cpp index a5b855c..8727b21 100644 --- a/Src/OSD/libretro/libretro.cpp +++ b/Src/OSD/libretro/libretro.cpp @@ -633,6 +633,11 @@ static NvramPatchResult apply_initial_nvram_settings(void) link_mode); const std::string &name = g_active_input_game.name; + if (name == "lamachin" || name == "lostwsga" || + name == "oceanhun" || name == "oceanhuna" || + name == "swtrilgy" || name == "swtrilgya") + selection.normalizeAnalogInputs = true; + if (name == "swtrilgy" || name == "swtrilgya") { const auto cabinet = LibretroNvramSettings::Setting::Cabinet;