mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
start of ui game list config etc
This commit is contained in:
+2025
File diff suppressed because it is too large
Load Diff
@@ -1716,12 +1716,7 @@ void CNew3D::CalcViewport(Viewport* vp, float near, float far)
|
||||
// and only their scissor box differs)
|
||||
float correction = windowAR / viewableAreaAR;
|
||||
|
||||
// Android: use x offset for letterboxed "viewable area" within the physical window.
|
||||
#ifdef __ANDROID__
|
||||
vp->x = m_xOffs;
|
||||
#else
|
||||
vp->x = 0;
|
||||
#endif
|
||||
vp->y = m_yOffs + (int)((float)(384 - (vp->vpY + vp->vpHeight))*m_yRatio);
|
||||
vp->width = m_totalXRes;
|
||||
vp->height = (int)((float)vp->vpHeight*m_yRatio);
|
||||
|
||||
@@ -62,6 +62,7 @@ android {
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.15.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:theme="@style/Theme.Super3">
|
||||
|
||||
<activity
|
||||
android:name=".Super3Activity"
|
||||
android:name=".MainActivity"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:exported="true">
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".Super3Activity"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:exported="false">
|
||||
|
||||
<!-- Tell SDL which native library to load -->
|
||||
<meta-data
|
||||
android:name="SDL_MAIN_LIBRARY"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Crosshairs images needs to be placed in here.
|
||||
32bits bmp format + alpha
|
||||
size multiple of 2
|
||||
p1crosshair.bmp
|
||||
p2crosshair.bmp
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
<!--
|
||||
Supermodel
|
||||
A Sega Model 3 Arcade Emulator.
|
||||
Copyright 2003-2025 The Supermodel Team
|
||||
|
||||
Music.xml
|
||||
|
||||
This file defines custom MPEG music tracks to be used in DSB1 and DSB2 games.
|
||||
Make sure to use MP3 files sampled at 32 KHz. Normal sampling rates (e.g. 44.1
|
||||
or 48 KHz) will sound slow.
|
||||
|
||||
The examples below are wrapped in dummy <comment></comment> blocks to render
|
||||
them inactive. Remove these and add your own MP2 or MP3 files.
|
||||
-->
|
||||
<games>
|
||||
<!-- Scud Race (Australian version)-->
|
||||
<!-- Works for all versions of Scud Race. Copy this and change game name
|
||||
accordingly (e.g., "scudplus", "scuddx", etc.) -->
|
||||
<comment> <!-- Remove this tag -->
|
||||
<game name="scudau">
|
||||
<track mpeg_rom_start_offset="0x001d4b42" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Beginner Day -->
|
||||
<track mpeg_rom_start_offset="0x000e7e41" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Beginner Night (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x00103981" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Beginner Night (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00000000" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Medium (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x0001caff" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Medium (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x0037a4c4" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Expert (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x0037fc84" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Expert (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x002afa83" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Extra (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x002b4943" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Extra (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00463745" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Selector -->
|
||||
</game>
|
||||
</comment> <!-- Remove this tag -->
|
||||
|
||||
<!-- Daytona USA 2: Battle on the Edge (daytona2) -->
|
||||
<!-- For Power Edition, copy this and change game name to "dayto2pe" -->
|
||||
<comment> <!-- Remove this tag -->
|
||||
<game name="daytona2">
|
||||
<track mpeg_rom_start_offset="0x000109e8" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Advertise, Dennis -->
|
||||
<track mpeg_rom_start_offset="0x001214aa" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Beginner, Dennis (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x00124f2a" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Beginner, Dennis (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x002bc7ac" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Advanced, Dennis (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x002edd6c" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Advanced, Dennis (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x003cddae" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Expert, Dennis (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x003fe3ae" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Expert, Dennis (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00d80e02" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Advertise, Mitsuyoshi -->
|
||||
<track mpeg_rom_start_offset="0x005177b0" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Beginner, Mitsuyoshi (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x0051aff0" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Beginner, Mitsuyoshi (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x007cd0b4" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Advanced, Mitsuyoshi (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x007fe674" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Advanced, Mitsuyoshi (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x008df436" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Expert, Mitsuyoshi (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x008df676" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Expert, Mitsuyoshi (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00a29978" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- How To (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x00a50438" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- How To (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00b1427a" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Selector (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x00b2f6fa" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Selector (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00c4213e" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Name Entry -->
|
||||
<track mpeg_rom_start_offset="0x00cc9380" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Ending (Beginner) -->
|
||||
<track mpeg_rom_start_offset="0x006b2ab2" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Ending (Advanced) -->
|
||||
<track mpeg_rom_start_offset="0x00e8de44" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Ending (Expert) -->
|
||||
<track mpeg_rom_start_offset="0x00f458c6" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Ending (Expert 2) -->
|
||||
<track mpeg_rom_start_offset="0x0076ff34" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Goal (Beginner) -->
|
||||
<track mpeg_rom_start_offset="0x0078c136" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Goal (Advanced) -->
|
||||
<track mpeg_rom_start_offset="0x007a7c78" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Goal (Expert) -->
|
||||
</game>
|
||||
</comment> <!-- Remove this tag -->
|
||||
|
||||
<!-- Sega Rally 2 -->
|
||||
<comment> <!-- Remove this tag -->
|
||||
<game name="srally2">
|
||||
<!--
|
||||
Note: Some tracks are played as mono, sourced from a single audio channel.
|
||||
For these, ensure that your audio file contains the appropriate song
|
||||
rendered in mono in the correct stereo channel.
|
||||
-->
|
||||
<track mpeg_rom_start_offset="0x000109e8" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Left channel: BM_ADV1 (initial start), Right channel: BM_RESERVE2 -->
|
||||
<track mpeg_rom_start_offset="0x00010c28" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Left channel: BM_ADV1 (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x001e576a" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_ADV2 (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x001e59aa" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_ADV2 (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x003452ac" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_MOUNTAIN (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x003accec" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_MOUNTAIN (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00a60ff6" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_RESULT -->
|
||||
<track mpeg_rom_start_offset="0x005d1ab0" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_SNOWY (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x006343f0" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_SNOWY (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x007b22f2" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Left channel: BM_N_MOUNTAIN (initial start), Right channel: BM_RESERVE1 (initial start) -->
|
||||
<track mpeg_rom_start_offset="0x008162b2" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Left channel: BM_N_MOUNTAIN -->
|
||||
<track mpeg_rom_start_offset="0x007fe672" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Right channel: BM_RESERVE1 (loop point) -->
|
||||
<track mpeg_rom_start_offset="0x00976934" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_ENDING -->
|
||||
<track mpeg_rom_start_offset="0x00ade038" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_DESERT -->
|
||||
<track mpeg_rom_start_offset="0x00cf517a" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_SELECT -->
|
||||
<track mpeg_rom_start_offset="0x00cf53ba" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_SELECT -->
|
||||
<track mpeg_rom_start_offset="0x00d9f87c" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_IGNITION -->
|
||||
<track mpeg_rom_start_offset="0x00f95fbe" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- BM_GAMEOVER -->
|
||||
</game>
|
||||
</comment> <!-- Remove this tag -->
|
||||
|
||||
</games>
|
||||
@@ -0,0 +1,165 @@
|
||||
[ Global ]
|
||||
;
|
||||
; Super3 Android default configuration.
|
||||
; Notes:
|
||||
; - Touch controls synthesize keyboard inputs. Keep a KEY_* mapping for these inputs.
|
||||
; - Some desktop options are ignored/overridden on Android for stability.
|
||||
;
|
||||
|
||||
GameXMLFile = Config/Games.xml
|
||||
PowerPCFrequency = 50
|
||||
MultiThreaded = 1
|
||||
GPUMultiThreaded = 0
|
||||
EmulateSound = 1
|
||||
EmulateDSB = 1
|
||||
NbSoundChannels = 4
|
||||
SoundFreq = 57.6
|
||||
; Android audio uses "200" as a sane default (matches typical Supermodel configs).
|
||||
SoundVolume = 200
|
||||
MusicVolume = 200
|
||||
LegacySoundDSP = 0
|
||||
ForceFeedback = 0
|
||||
Network = 0
|
||||
SimulateNet = 0
|
||||
Outputs = none
|
||||
|
||||
; Rendering (Android GLES path)
|
||||
New3DEngine = 0
|
||||
QuadRendering = 0
|
||||
XResolution = 496
|
||||
YResolution = 384
|
||||
VSync = 1
|
||||
WideScreen = 0
|
||||
WideBackground = 0
|
||||
|
||||
; Input system (Android uses SDL input backend)
|
||||
InputSystem = sdl
|
||||
|
||||
; Touch defaults:
|
||||
; - bottom-left: Coin1
|
||||
; - bottom-right: Start1
|
||||
; - top-left: ServiceA
|
||||
; - top-right: TestA
|
||||
; - left-middle: D-pad / steer
|
||||
; - right-middle: throttle (top) / brake (bottom)
|
||||
InputCoin1 = KEY_5
|
||||
InputStart1 = KEY_1
|
||||
InputServiceA = KEY_F1
|
||||
InputTestA = KEY_F2
|
||||
InputJoyUp = KEY_UP
|
||||
InputJoyDown = KEY_DOWN
|
||||
InputJoyLeft = KEY_LEFT
|
||||
InputJoyRight = KEY_RIGHT
|
||||
InputSteeringLeft = KEY_LEFT
|
||||
InputSteeringRight = KEY_RIGHT
|
||||
InputAccelerator = KEY_W
|
||||
InputBrake = KEY_S
|
||||
InputVR1 = KEY_A,JOY1_BUTTON1
|
||||
InputVR2 = KEY_S,JOY1_BUTTON2
|
||||
InputVR3 = KEY_D,JOY1_BUTTON3
|
||||
InputVR4 = KEY_F,JOY1_BUTTON4
|
||||
InputViewChange = KEY_A,JOY1_BUTTON1
|
||||
InputHandBrake = KEY_S,JOY1_BUTTON2
|
||||
InputRearBrake = KEY_S,JOY1_BUTTON2
|
||||
InputMusicSelect = KEY_D,JOY1_BUTTON3
|
||||
InputTwinJoyTurnLeft = KEY_Q,JOY1_RXAXIS_NEG
|
||||
InputTwinJoyTurnRight = KEY_W,JOY1_RXAXIS_POS
|
||||
InputTwinJoyForward = KEY_UP,JOY1_YAXIS_NEG
|
||||
InputTwinJoyReverse = KEY_DOWN,JOY1_YAXIS_POS
|
||||
InputTwinJoyStrafeLeft = KEY_LEFT,JOY1_XAXIS_NEG
|
||||
InputTwinJoyStrafeRight = KEY_RIGHT,JOY1_XAXIS_POS
|
||||
InputTwinJoyJump = KEY_E,JOY1_BUTTON1
|
||||
InputTwinJoyCrouch = KEY_R,JOY1_BUTTON2
|
||||
InputTwinJoyLeft1 = NONE
|
||||
InputTwinJoyLeft2 = NONE
|
||||
InputTwinJoyRight1 = NONE
|
||||
InputTwinJoyRight2 = NONE
|
||||
InputTwinJoyUp1 = NONE
|
||||
InputTwinJoyUp2 = NONE
|
||||
InputTwinJoyDown1 = NONE
|
||||
InputTwinJoyDown2 = NONE
|
||||
InputTwinJoyShot1 = KEY_A,JOY1_BUTTON5
|
||||
InputTwinJoyShot2 = KEY_S,JOY1_BUTTON6
|
||||
InputTwinJoyTurbo1 = KEY_Z,JOY1_BUTTON7
|
||||
InputTwinJoyTurbo2 = KEY_X,JOY1_BUTTON8
|
||||
InputAnalogJoyLeft = KEY_LEFT
|
||||
InputAnalogJoyRight = KEY_RIGHT
|
||||
InputAnalogJoyUp = KEY_UP
|
||||
InputAnalogJoyDown = KEY_DOWN
|
||||
InputAnalogJoyX = JOY_XAXIS,MOUSE_XAXIS
|
||||
InputAnalogJoyY = JOY_YAXIS,MOUSE_YAXIS
|
||||
InputAnalogJoyTrigger = KEY_A,JOY_BUTTON1,MOUSE_LEFT_BUTTON
|
||||
InputAnalogJoyEvent = KEY_S,JOY_BUTTON2,MOUSE_RIGHT_BUTTON
|
||||
InputAnalogJoyTrigger2 = KEY_D,JOY_BUTTON2
|
||||
InputAnalogJoyEvent2 = NONE
|
||||
InputGunLeft = KEY_LEFT
|
||||
InputGunRight = KEY_RIGHT
|
||||
InputGunUp = KEY_UP
|
||||
InputGunDown = KEY_DOWN
|
||||
InputGunX = MOUSE_XAXIS,JOY1_XAXIS
|
||||
InputGunY = MOUSE_YAXIS,JOY1_YAXIS
|
||||
InputTrigger = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON
|
||||
InputOffscreen = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON
|
||||
InputAutoTrigger = 0
|
||||
InputGunLeft2 = NONE
|
||||
InputGunRight2 = NONE
|
||||
InputGunUp2 = NONE
|
||||
InputGunDown2 = NONE
|
||||
InputGunX2 = JOY2_XAXIS
|
||||
InputGunY2 = JOY2_YAXIS
|
||||
InputTrigger2 = JOY2_BUTTON1
|
||||
InputOffscreen2 = JOY2_BUTTON2
|
||||
InputAutoTrigger2 = 0
|
||||
InputAnalogGunLeft = KEY_LEFT
|
||||
InputAnalogGunRight = KEY_RIGHT
|
||||
InputAnalogGunUp = KEY_UP
|
||||
InputAnalogGunDown = KEY_DOWN
|
||||
InputAnalogGunX = MOUSE_XAXIS,JOY1_XAXIS
|
||||
InputAnalogGunY = MOUSE_YAXIS,JOY1_YAXIS
|
||||
InputAnalogTriggerLeft = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON
|
||||
InputAnalogTriggerRight = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON
|
||||
InputAnalogGunLeft2 = NONE
|
||||
InputAnalogGunRight2 = NONE
|
||||
InputAnalogGunUp2 = NONE
|
||||
InputAnalogGunDown2 = NONE
|
||||
InputAnalogGunX2 = NONE
|
||||
InputAnalogGunY2 = NONE
|
||||
InputAnalogTriggerLeft2 = NONE
|
||||
InputAnalogTriggerRight2 = NONE
|
||||
InputSkiLeft = KEY_LEFT
|
||||
InputSkiRight = KEY_RIGHT
|
||||
InputSkiUp = KEY_UP
|
||||
InputSkiDown = KEY_DOWN
|
||||
InputSkiX = JOY1_XAXIS
|
||||
InputSkiY = JOY1_YAXIS
|
||||
InputSkiPollLeft = KEY_A,JOY1_BUTTON1
|
||||
InputSkiPollRight = KEY_S,JOY1_BUTTON2
|
||||
InputSkiSelect1 = KEY_Q,JOY1_BUTTON3
|
||||
InputSkiSelect2 = KEY_W,JOY1_BUTTON4
|
||||
InputSkiSelect3 = KEY_E,JOY1_BUTTON5
|
||||
InputMagicalLeverUp1 = KEY_UP
|
||||
InputMagicalLeverDown1 = KEY_DOWN
|
||||
InputMagicalLeverUp2 = NONE
|
||||
InputMagicalLeverDown2 = NONE
|
||||
InputMagicalLever1 = JOY1_YAXIS
|
||||
InputMagicalLever2 = JOY2_YAXIS
|
||||
InputMagicalPedal1 = KEY_A,JOY1_BUTTON1
|
||||
InputMagicalPedal2 = KEY_S,JOY2_BUTTON1
|
||||
InputFishingRodLeft = KEY_LEFT
|
||||
InputFishingRodRight = KEY_RIGHT
|
||||
InputFishingRodUp = KEY_UP
|
||||
InputFishingRodDown = KEY_DOWN
|
||||
InputFishingStickLeft = KEY_A
|
||||
InputFishingStickRight = KEY_D
|
||||
InputFishingStickUp = KEY_W
|
||||
InputFishingStickDown = KEY_S
|
||||
InputFishingRodX = JOY1_XAXIS
|
||||
InputFishingRodY = JOY1_YAXIS
|
||||
InputFishingStickX = JOY1_RXAXIS
|
||||
InputFishingStickY = JOY1_RYAXIS
|
||||
InputFishingReel = KEY_SPACE,JOY1_ZAXIS_POS
|
||||
InputFishingCast = KEY_Z,JOY1_BUTTON1
|
||||
InputFishingSelect = KEY_X,JOY1_BUTTON2
|
||||
InputFishingTension = KEY_T,JOY1_ZAXIS_NEG
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Graphics Analysis go here.
|
||||
@@ -0,0 +1 @@
|
||||
NVRAM files go here.
|
||||
@@ -0,0 +1 @@
|
||||
Recommended (but not mandatory) location for ROM sets.
|
||||
@@ -0,0 +1 @@
|
||||
Save states go here.
|
||||
@@ -9,6 +9,41 @@ AndroidInputSystem::AndroidInputSystem()
|
||||
{
|
||||
}
|
||||
|
||||
void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config)
|
||||
{
|
||||
auto get = [&](const char* key, const char* fallback) -> std::string {
|
||||
return config[key].ValueAsDefault<std::string>(fallback);
|
||||
};
|
||||
|
||||
auto keySc = [&](const std::string& mapping, SDL_Scancode fallback) -> SDL_Scancode {
|
||||
if (mapping.empty()) return fallback;
|
||||
SDL_Scancode sc = ParseFirstKeyboardScancode(mapping, *this);
|
||||
return (sc == SDL_SCANCODE_UNKNOWN) ? fallback : sc;
|
||||
};
|
||||
|
||||
m_touchCoin.a = keySc(get("InputCoin1", "KEY_5"), SDL_SCANCODE_5);
|
||||
m_touchStart.a = keySc(get("InputStart1", "KEY_1"), SDL_SCANCODE_1);
|
||||
m_touchService.a = keySc(get("InputServiceA", "KEY_F1"), SDL_SCANCODE_F1);
|
||||
m_touchTest.a = keySc(get("InputTestA", "KEY_F2"), SDL_SCANCODE_F2);
|
||||
|
||||
// For left/right, we press both joy and steering if they differ, to keep menus and driving working.
|
||||
const SDL_Scancode joyLeft = keySc(get("InputJoyLeft", "KEY_LEFT"), SDL_SCANCODE_LEFT);
|
||||
const SDL_Scancode joyRight = keySc(get("InputJoyRight", "KEY_RIGHT"), SDL_SCANCODE_RIGHT);
|
||||
const SDL_Scancode steerLeft = keySc(get("InputSteeringLeft", "KEY_LEFT"), SDL_SCANCODE_LEFT);
|
||||
const SDL_Scancode steerRight = keySc(get("InputSteeringRight", "KEY_RIGHT"), SDL_SCANCODE_RIGHT);
|
||||
|
||||
m_touchJoyUp.a = keySc(get("InputJoyUp", "KEY_UP"), SDL_SCANCODE_UP);
|
||||
m_touchJoyDown.a = keySc(get("InputJoyDown", "KEY_DOWN"), SDL_SCANCODE_DOWN);
|
||||
|
||||
m_touchJoyLeft = {joyLeft, (joyLeft != steerLeft) ? steerLeft : SDL_SCANCODE_UNKNOWN};
|
||||
m_touchJoyRight = {joyRight, (joyRight != steerRight) ? steerRight : SDL_SCANCODE_UNKNOWN};
|
||||
m_touchSteerLeft = {steerLeft, (steerLeft != joyLeft) ? joyLeft : SDL_SCANCODE_UNKNOWN};
|
||||
m_touchSteerRight = {steerRight, (steerRight != joyRight) ? joyRight : SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
m_touchThrottle.a = keySc(get("InputAccelerator", "KEY_W"), SDL_SCANCODE_W);
|
||||
m_touchBrake.a = keySc(get("InputBrake", "KEY_S"), SDL_SCANCODE_S);
|
||||
}
|
||||
|
||||
bool AndroidInputSystem::InitializeSystem()
|
||||
{
|
||||
std::fill(m_keys.begin(), m_keys.end(), 0);
|
||||
@@ -33,6 +68,18 @@ void AndroidInputSystem::PulseKey(SDL_Scancode sc, uint32_t durationMs)
|
||||
m_pulseUntilMs[sc] = SDL_GetTicks() + durationMs;
|
||||
}
|
||||
|
||||
void AndroidInputSystem::SetKeys(const DualScancode& sc, bool down)
|
||||
{
|
||||
if (sc.a != SDL_SCANCODE_UNKNOWN) SetKey(sc.a, down);
|
||||
if (sc.b != SDL_SCANCODE_UNKNOWN) SetKey(sc.b, down);
|
||||
}
|
||||
|
||||
void AndroidInputSystem::PulseKeys(const DualScancode& sc, uint32_t durationMs)
|
||||
{
|
||||
if (sc.a != SDL_SCANCODE_UNKNOWN) PulseKey(sc.a, durationMs);
|
||||
if (sc.b != SDL_SCANCODE_UNKNOWN) PulseKey(sc.b, durationMs);
|
||||
}
|
||||
|
||||
bool AndroidInputSystem::Poll()
|
||||
{
|
||||
const uint32_t now = SDL_GetTicks();
|
||||
@@ -94,31 +141,31 @@ void AndroidInputSystem::HandleControllerButtonEvent(const SDL_ControllerButtonE
|
||||
// - Back: TestA
|
||||
// - D-pad: menu navigation
|
||||
// - A/B/X/Y: map to common keys for future bindings
|
||||
SDL_Scancode sc = SDL_SCANCODE_UNKNOWN;
|
||||
DualScancode sc;
|
||||
switch (btn.button)
|
||||
{
|
||||
case SDL_CONTROLLER_BUTTON_START: sc = SDL_SCANCODE_1; break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: sc = SDL_SCANCODE_5; break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: sc = SDL_SCANCODE_F2; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: sc = SDL_SCANCODE_UP; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: sc = SDL_SCANCODE_DOWN; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: sc = SDL_SCANCODE_LEFT; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: sc = SDL_SCANCODE_RIGHT; break;
|
||||
case SDL_CONTROLLER_BUTTON_A: sc = SDL_SCANCODE_Z; break;
|
||||
case SDL_CONTROLLER_BUTTON_B: sc = SDL_SCANCODE_X; break;
|
||||
case SDL_CONTROLLER_BUTTON_X: sc = SDL_SCANCODE_C; break;
|
||||
case SDL_CONTROLLER_BUTTON_Y: sc = SDL_SCANCODE_V; break;
|
||||
case SDL_CONTROLLER_BUTTON_START: sc = m_touchStart; break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: sc = m_touchCoin; break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: sc = m_touchTest; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: sc = m_touchJoyUp; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: sc = m_touchJoyDown; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: sc = m_touchJoyLeft; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: sc = m_touchJoyRight; break;
|
||||
case SDL_CONTROLLER_BUTTON_A: sc = {SDL_SCANCODE_Z, SDL_SCANCODE_UNKNOWN}; break;
|
||||
case SDL_CONTROLLER_BUTTON_B: sc = {SDL_SCANCODE_X, SDL_SCANCODE_UNKNOWN}; break;
|
||||
case SDL_CONTROLLER_BUTTON_X: sc = {SDL_SCANCODE_C, SDL_SCANCODE_UNKNOWN}; break;
|
||||
case SDL_CONTROLLER_BUTTON_Y: sc = {SDL_SCANCODE_V, SDL_SCANCODE_UNKNOWN}; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (sc == SDL_SCANCODE_UNKNOWN)
|
||||
if (sc.a == SDL_SCANCODE_UNKNOWN && sc.b == SDL_SCANCODE_UNKNOWN)
|
||||
return;
|
||||
|
||||
// For coin/start/test, prefer a pulse to avoid repeating while held.
|
||||
if (down && (sc == SDL_SCANCODE_1 || sc == SDL_SCANCODE_5 || sc == SDL_SCANCODE_F1 || sc == SDL_SCANCODE_F2))
|
||||
PulseKey(sc, 120);
|
||||
if (down && (sc.a == m_touchStart.a || sc.a == m_touchCoin.a || sc.a == m_touchService.a || sc.a == m_touchTest.a))
|
||||
PulseKeys(sc, 120);
|
||||
else
|
||||
SetKey(sc, down);
|
||||
SetKeys(sc, down);
|
||||
}
|
||||
|
||||
void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
@@ -133,10 +180,10 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
// - Top-right: Test (KEY_F2)
|
||||
if (down)
|
||||
{
|
||||
if (x < 0.25f && y > 0.75f) { PulseKey(SDL_SCANCODE_5, 120); return; }
|
||||
if (x > 0.75f && y > 0.75f) { PulseKey(SDL_SCANCODE_1, 120); return; }
|
||||
if (x < 0.25f && y < 0.25f) { PulseKey(SDL_SCANCODE_F1, 120); return; }
|
||||
if (x > 0.75f && y < 0.25f) { PulseKey(SDL_SCANCODE_F2, 120); return; }
|
||||
if (x < 0.25f && y > 0.75f) { PulseKeys(m_touchCoin, 120); return; }
|
||||
if (x > 0.75f && y > 0.75f) { PulseKeys(m_touchStart, 120); return; }
|
||||
if (x < 0.25f && y < 0.25f) { PulseKeys(m_touchService, 120); return; }
|
||||
if (x > 0.75f && y < 0.25f) { PulseKeys(m_touchTest, 120); return; }
|
||||
}
|
||||
|
||||
// Held throttle/brake zone (right-middle): hold to accelerate/brake.
|
||||
@@ -150,15 +197,15 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
auto it = m_fingerHeldKey.find(tf.fingerId);
|
||||
if (it != m_fingerHeldKey.end())
|
||||
{
|
||||
SetKey(it->second, false);
|
||||
SetKeys(it->second, false);
|
||||
m_fingerHeldKey.erase(it);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Scancode pedal = (y < 0.575f) ? SDL_SCANCODE_W : SDL_SCANCODE_S;
|
||||
DualScancode pedal = (y < 0.575f) ? m_touchThrottle : m_touchBrake;
|
||||
m_fingerHeldKey[tf.fingerId] = pedal;
|
||||
SetKey(pedal, true);
|
||||
SetKeys(pedal, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,7 +219,7 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
auto it = m_fingerHeldDir.find(tf.fingerId);
|
||||
if (it != m_fingerHeldDir.end())
|
||||
{
|
||||
SetKey(it->second, false);
|
||||
SetKeys(it->second, false);
|
||||
m_fingerHeldDir.erase(it);
|
||||
}
|
||||
return;
|
||||
@@ -184,16 +231,16 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
const float dx = x - cx;
|
||||
const float dy = y - cy;
|
||||
|
||||
SDL_Scancode dir = SDL_SCANCODE_UNKNOWN;
|
||||
DualScancode dir;
|
||||
if (std::abs(dx) > std::abs(dy))
|
||||
dir = (dx < 0.0f) ? SDL_SCANCODE_LEFT : SDL_SCANCODE_RIGHT;
|
||||
dir = (dx < 0.0f) ? m_touchJoyLeft : m_touchJoyRight;
|
||||
else
|
||||
dir = (dy < 0.0f) ? SDL_SCANCODE_UP : SDL_SCANCODE_DOWN;
|
||||
dir = (dy < 0.0f) ? m_touchJoyUp : m_touchJoyDown;
|
||||
|
||||
if (dir != SDL_SCANCODE_UNKNOWN)
|
||||
if (dir.a != SDL_SCANCODE_UNKNOWN || dir.b != SDL_SCANCODE_UNKNOWN)
|
||||
{
|
||||
m_fingerHeldDir[tf.fingerId] = dir;
|
||||
SetKey(dir, true);
|
||||
SetKeys(dir, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,14 +256,14 @@ void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf)
|
||||
return;
|
||||
|
||||
// Release previous pedal and re-evaluate based on new Y position.
|
||||
SetKey(itKey->second, false);
|
||||
SetKeys(itKey->second, false);
|
||||
m_fingerHeldKey.erase(itKey);
|
||||
HandleTouch(tf, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Release previous direction and compute a new one.
|
||||
SetKey(it->second, false);
|
||||
SetKeys(it->second, false);
|
||||
m_fingerHeldDir.erase(it);
|
||||
HandleTouch(tf, true);
|
||||
}
|
||||
@@ -249,6 +296,42 @@ SDL_Scancode AndroidInputSystem::ScancodeFromSupermodelKeyName(const char* keyNa
|
||||
return SDL_GetScancodeFromName(keyName);
|
||||
}
|
||||
|
||||
SDL_Scancode AndroidInputSystem::ParseFirstKeyboardScancode(const std::string& mapping, const AndroidInputSystem& self)
|
||||
{
|
||||
// mapping examples:
|
||||
// - "KEY_F2"
|
||||
// - "KEY_RIGHT,JOY1_XAXIS_POS"
|
||||
// - "KEY_ALT+KEY_R"
|
||||
// - "!KEY_ALT+KEY_P"
|
||||
std::string s = mapping;
|
||||
size_t start = 0;
|
||||
while (start < s.size())
|
||||
{
|
||||
// Find next token boundary.
|
||||
while (start < s.size() && (s[start] == ' ' || s[start] == '\t' || s[start] == ',' || s[start] == '+'))
|
||||
start++;
|
||||
if (start >= s.size()) break;
|
||||
|
||||
size_t end = start;
|
||||
while (end < s.size() && s[end] != ' ' && s[end] != '\t' && s[end] != ',' && s[end] != '+')
|
||||
end++;
|
||||
|
||||
std::string tok = s.substr(start, end - start);
|
||||
if (!tok.empty() && tok[0] == '!')
|
||||
tok.erase(tok.begin());
|
||||
|
||||
if (tok.rfind("KEY_", 0) == 0 && tok.size() > 4)
|
||||
{
|
||||
const std::string keyName = tok.substr(4);
|
||||
return self.ScancodeFromSupermodelKeyName(keyName.c_str());
|
||||
}
|
||||
|
||||
start = end;
|
||||
}
|
||||
|
||||
return SDL_SCANCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
int AndroidInputSystem::GetKeyIndex(const char* keyName)
|
||||
{
|
||||
const SDL_Scancode sc = ScancodeFromSupermodelKeyName(keyName);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Inputs/InputSystem.h"
|
||||
#include "Inputs/Input.h"
|
||||
#include "Util/NewConfig.h"
|
||||
|
||||
// Minimal SDL-backed input system for Android.
|
||||
// - Uses SDL scancodes as key indices.
|
||||
@@ -21,6 +22,9 @@ public:
|
||||
// Called from the SDL event loop (main thread).
|
||||
void HandleEvent(const SDL_Event& ev);
|
||||
|
||||
// Update touch/controller bindings from the current config (e.g. Supermodel.ini overrides).
|
||||
void ApplyConfig(const Util::Config::Node& config);
|
||||
|
||||
bool InitializeSystem() override;
|
||||
int GetKeyIndex(const char* keyName) override;
|
||||
const char* GetKeyName(int keyIndex) override;
|
||||
@@ -50,8 +54,15 @@ public:
|
||||
void UngrabMouse() override {}
|
||||
|
||||
private:
|
||||
struct DualScancode {
|
||||
SDL_Scancode a = SDL_SCANCODE_UNKNOWN;
|
||||
SDL_Scancode b = SDL_SCANCODE_UNKNOWN;
|
||||
};
|
||||
|
||||
void SetKey(SDL_Scancode sc, bool down);
|
||||
void PulseKey(SDL_Scancode sc, uint32_t durationMs);
|
||||
void SetKeys(const DualScancode& sc, bool down);
|
||||
void PulseKeys(const DualScancode& sc, uint32_t durationMs);
|
||||
|
||||
void HandleKeyEvent(const SDL_KeyboardEvent& key, bool down);
|
||||
void HandleControllerButtonEvent(const SDL_ControllerButtonEvent& btn, bool down);
|
||||
@@ -59,9 +70,26 @@ private:
|
||||
void HandleTouchMotion(const SDL_TouchFingerEvent& tf);
|
||||
|
||||
SDL_Scancode ScancodeFromSupermodelKeyName(const char* keyName) const;
|
||||
static SDL_Scancode ParseFirstKeyboardScancode(const std::string& mapping, const AndroidInputSystem& self);
|
||||
|
||||
// Configurable bindings (default to our original hardcoded mapping).
|
||||
DualScancode m_touchCoin{SDL_SCANCODE_5, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchStart{SDL_SCANCODE_1, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchService{SDL_SCANCODE_F1, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchTest{SDL_SCANCODE_F2, SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
DualScancode m_touchJoyUp{SDL_SCANCODE_UP, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchJoyDown{SDL_SCANCODE_DOWN, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchJoyLeft{SDL_SCANCODE_LEFT, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchJoyRight{SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchSteerLeft{SDL_SCANCODE_LEFT, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchSteerRight{SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
DualScancode m_touchThrottle{SDL_SCANCODE_W, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchBrake{SDL_SCANCODE_S, SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
std::vector<uint8_t> m_keys; // indexed by SDL_Scancode
|
||||
std::unordered_map<SDL_FingerID, SDL_Scancode> m_fingerHeldDir;
|
||||
std::unordered_map<SDL_FingerID, SDL_Scancode> m_fingerHeldKey;
|
||||
std::unordered_map<SDL_FingerID, DualScancode> m_fingerHeldDir;
|
||||
std::unordered_map<SDL_FingerID, DualScancode> m_fingerHeldKey;
|
||||
std::unordered_map<SDL_Scancode, uint32_t> m_pulseUntilMs;
|
||||
};
|
||||
|
||||
@@ -179,9 +179,16 @@ void GlesPresenter::UpdateQuadVerts()
|
||||
const float srcW = (float)m_srcW;
|
||||
const float srcH = (float)m_srcH;
|
||||
|
||||
const float scale = std::min(outW / srcW, outH / srcH);
|
||||
const float drawW = srcW * scale;
|
||||
const float drawH = srcH * scale;
|
||||
float drawW;
|
||||
float drawH;
|
||||
if (m_stretch) {
|
||||
drawW = outW;
|
||||
drawH = outH;
|
||||
} else {
|
||||
const float scale = std::min(outW / srcW, outH / srcH);
|
||||
drawW = srcW * scale;
|
||||
drawH = srcH * scale;
|
||||
}
|
||||
|
||||
const float ndcW = (drawW / outW) * 2.0f;
|
||||
const float ndcH = (drawH / outH) * 2.0f;
|
||||
|
||||
@@ -13,6 +13,7 @@ public:
|
||||
void Shutdown();
|
||||
|
||||
void Resize(int outputW, int outputH);
|
||||
void SetStretch(bool stretch) { m_stretch = stretch; }
|
||||
|
||||
// pixelsARGB: 0xAARRGGBB packed.
|
||||
void UpdateFrameARGB(const uint32_t* pixelsARGB, int width, int height);
|
||||
@@ -27,6 +28,7 @@ private:
|
||||
int m_outputH = 0;
|
||||
int m_srcW = 0;
|
||||
int m_srcH = 0;
|
||||
bool m_stretch = false;
|
||||
|
||||
unsigned m_program = 0;
|
||||
unsigned m_vao = 0;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "OSD/Audio.h"
|
||||
#include "OSD/Logger.h"
|
||||
#include "Util/NewConfig.h"
|
||||
#include "Util/ConfigBuilders.h"
|
||||
#include "BlockFile.h"
|
||||
|
||||
#include "android_input_system.h"
|
||||
@@ -176,10 +177,99 @@ struct Super3Host {
|
||||
config.Set("InputSteeringRight", "KEY_RIGHT");
|
||||
config.Set("InputAccelerator", "KEY_W");
|
||||
config.Set("InputBrake", "KEY_S");
|
||||
inputSystem.ApplyConfig(config);
|
||||
}
|
||||
|
||||
void ApplyAndroidHardOverrides()
|
||||
{
|
||||
auto ensureKeyboardFallback = [&](const char* cfgKey, const char* defaultKeyToken) {
|
||||
std::string v = config[cfgKey].ValueAsDefault<std::string>("");
|
||||
if (v.empty()) {
|
||||
config.Set(cfgKey, defaultKeyToken);
|
||||
return;
|
||||
}
|
||||
std::string upper = v;
|
||||
std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return (char)std::toupper(c); });
|
||||
if (upper.find("KEY_") == std::string::npos) {
|
||||
config.Set(cfgKey, std::string(defaultKeyToken) + "," + v);
|
||||
}
|
||||
};
|
||||
|
||||
// Settings that must remain stable on Android (unsupported or not yet wired).
|
||||
config.Set("InputSystem", "sdl");
|
||||
config.Set("Outputs", "none");
|
||||
config.Set("ForceFeedback", false);
|
||||
config.Set("Network", false);
|
||||
config.Set("SimulateNet", false);
|
||||
|
||||
// Ensure required nodes exist / sane.
|
||||
config.Set("PowerPCFrequency", "50");
|
||||
|
||||
// Keep the current Android renderer path stable for now.
|
||||
config.Set("New3DEngine", false);
|
||||
config.Set("QuadRendering", false);
|
||||
|
||||
// Android build currently targets the native 496x384 framebuffer.
|
||||
config.Set("XResolution", "496");
|
||||
config.Set("YResolution", "384");
|
||||
|
||||
// Ensure touch zones always have a working keyboard mapping even if the user remaps to joystick-only.
|
||||
ensureKeyboardFallback("InputCoin1", "KEY_5");
|
||||
ensureKeyboardFallback("InputStart1", "KEY_1");
|
||||
ensureKeyboardFallback("InputServiceA", "KEY_F1");
|
||||
ensureKeyboardFallback("InputTestA", "KEY_F2");
|
||||
ensureKeyboardFallback("InputJoyUp", "KEY_UP");
|
||||
ensureKeyboardFallback("InputJoyDown", "KEY_DOWN");
|
||||
ensureKeyboardFallback("InputJoyLeft", "KEY_LEFT");
|
||||
ensureKeyboardFallback("InputJoyRight", "KEY_RIGHT");
|
||||
ensureKeyboardFallback("InputSteeringLeft", "KEY_LEFT");
|
||||
ensureKeyboardFallback("InputSteeringRight", "KEY_RIGHT");
|
||||
ensureKeyboardFallback("InputAccelerator", "KEY_W");
|
||||
ensureKeyboardFallback("InputBrake", "KEY_S");
|
||||
|
||||
inputSystem.ApplyConfig(config);
|
||||
}
|
||||
|
||||
void ApplyIniOverrides(const std::string& gameSectionName)
|
||||
{
|
||||
const std::string base = userDataRoot.empty() ? std::string("super3") : userDataRoot;
|
||||
const std::string iniPath = JoinPath(JoinPath(base, "Config"), "Supermodel.ini");
|
||||
if (!std::filesystem::exists(iniPath)) {
|
||||
SDL_Log("Supermodel.ini not found at %s (using built-in defaults)", iniPath.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Util::Config::Node iniConfig{"Global"};
|
||||
if (Util::Config::FromINIFile(&iniConfig, iniPath)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to parse %s (using built-in defaults)", iniPath.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge global settings: INI overrides built-in defaults.
|
||||
{
|
||||
Util::Config::Node merged{"Global"};
|
||||
Util::Config::MergeINISections(&merged, config, iniConfig);
|
||||
config = merged;
|
||||
}
|
||||
|
||||
// Merge game-specific settings if present.
|
||||
if (!gameSectionName.empty()) {
|
||||
const Util::Config::Node* section = iniConfig.TryGet(gameSectionName);
|
||||
if (section != nullptr) {
|
||||
Util::Config::Node merged{"Global"};
|
||||
Util::Config::MergeINISections(&merged, config, *section);
|
||||
config = merged;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyAndroidHardOverrides();
|
||||
inputSystem.ApplyConfig(config);
|
||||
}
|
||||
|
||||
bool InitLoader(const std::string& gamesXml) {
|
||||
ApplyDefaults();
|
||||
ApplyIniOverrides(std::string());
|
||||
ApplyAndroidHardOverrides();
|
||||
if (!std::filesystem::exists(gamesXml)) {
|
||||
ErrorLog("Games XML not found at %s", gamesXml.c_str());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Games XML not found at %s", gamesXml.c_str());
|
||||
@@ -209,6 +299,10 @@ struct Super3Host {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply Supermodel.ini overrides (Global + [ game ]) after the loader has determined game->name.
|
||||
ApplyIniOverrides(game.name);
|
||||
ApplyAndroidHardOverrides();
|
||||
|
||||
// Initialize inputs before attaching to model
|
||||
if (!inputs.Initialize()) {
|
||||
ErrorLog("Inputs initialization failed");
|
||||
@@ -274,30 +368,14 @@ struct Super3Host {
|
||||
}
|
||||
}
|
||||
|
||||
bool InstallNew3D(unsigned totalXRes, unsigned totalYRes)
|
||||
bool InstallNew3D(unsigned xOff, unsigned yOff, unsigned xRes, unsigned yRes, unsigned totalXRes, unsigned totalYRes)
|
||||
{
|
||||
if (!model3 || new3d)
|
||||
return !!new3d;
|
||||
|
||||
// Letterbox the Model 3 496x384 output into the physical drawable area.
|
||||
// We pass the "viewable area" size to New3D so it doesn't wide-screen expand the frustum.
|
||||
const float targetAR = 496.0f / 384.0f;
|
||||
unsigned viewW = totalXRes;
|
||||
unsigned viewH = totalYRes;
|
||||
const float windowAR = (float)totalXRes / (float)totalYRes;
|
||||
if (windowAR > targetAR) {
|
||||
viewH = totalYRes;
|
||||
viewW = (unsigned)std::lround((double)totalYRes * targetAR);
|
||||
} else {
|
||||
viewW = totalXRes;
|
||||
viewH = (unsigned)std::lround((double)totalXRes / targetAR);
|
||||
}
|
||||
const unsigned xOff = (totalXRes - viewW) / 2;
|
||||
const unsigned yOff = (totalYRes - viewH) / 2;
|
||||
|
||||
SDL_Log("Initializing New3D (GLES) ...");
|
||||
new3d = std::make_unique<New3D::CNew3D>(config, game.name);
|
||||
if (new3d->Init(xOff, yOff, viewW, viewH, viewW, viewH) != 0)
|
||||
if (new3d->Init(xOff, yOff, xRes, yRes, totalXRes, totalYRes) != 0)
|
||||
{
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "New3D Init failed");
|
||||
new3d.reset();
|
||||
@@ -426,6 +504,12 @@ extern "C" int SDL_main(int argc, char* argv[]) {
|
||||
if (argc > 2 && argv[2]) {
|
||||
gameName = argv[2];
|
||||
}
|
||||
if (argc > 3 && argv[3]) {
|
||||
gamesXml = argv[3];
|
||||
}
|
||||
if (argc > 4 && argv[4] && argv[4][0] != '\0') {
|
||||
host.SetUserDataRoot(argv[4]);
|
||||
}
|
||||
|
||||
SDL_Log("Super3 paths: Games.xml=%s ROM=%s", gamesXml.c_str(), romZip.c_str());
|
||||
|
||||
@@ -501,6 +585,42 @@ extern "C" int SDL_main(int argc, char* argv[]) {
|
||||
if (winW <= 0) winW = 1;
|
||||
if (winH <= 0) winH = 1;
|
||||
|
||||
// Compute aspect-correct viewable area (xRes/yRes) centered within the drawable (totalXRes/totalYRes).
|
||||
const unsigned totalXRes = (unsigned)winW;
|
||||
const unsigned totalYRes = (unsigned)winH;
|
||||
unsigned xRes = totalXRes;
|
||||
unsigned yRes = totalYRes;
|
||||
const float model3AR = 496.0f / 384.0f;
|
||||
const float outAR = (float)totalXRes / (float)totalYRes;
|
||||
if (outAR > model3AR) {
|
||||
// output is wider => reduce width
|
||||
xRes = (unsigned)std::lround((double)totalYRes * model3AR);
|
||||
yRes = totalYRes;
|
||||
} else if (outAR < model3AR) {
|
||||
// output is taller => reduce height
|
||||
xRes = totalXRes;
|
||||
yRes = (unsigned)std::lround((double)totalXRes / model3AR);
|
||||
}
|
||||
const unsigned xOff = (totalXRes - xRes) / 2;
|
||||
const unsigned yOff = (totalYRes - yRes) / 2;
|
||||
|
||||
const bool wideScreen = host.config["WideScreen"].ValueAsDefault<bool>(false);
|
||||
const bool wideBackground = host.config["WideBackground"].ValueAsDefault<bool>(false);
|
||||
|
||||
// Clear full drawable (scissor off), then set scissor like desktop.
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glViewport(0, 0, winW, winH);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
const unsigned correction = (unsigned)(((double)yRes / 384.0) * 2.0 + 0.5);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
if (wideScreen) {
|
||||
glScissor(0, (int)correction, (int)totalXRes, (int)(totalYRes - (correction * 2)));
|
||||
} else {
|
||||
glScissor((int)(xOff + correction), (int)(yOff + correction), (int)(xRes - (correction * 2)), (int)(yRes - (correction * 2)));
|
||||
}
|
||||
|
||||
const int state = loadState.load(std::memory_order_acquire);
|
||||
if (state == 1) {
|
||||
if (!audioOpened) {
|
||||
@@ -516,18 +636,16 @@ extern "C" int SDL_main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (!new3dAttached) {
|
||||
new3dAttached = host.InstallNew3D((unsigned)winW, (unsigned)winH);
|
||||
new3dAttached = host.InstallNew3D(xOff, yOff, xRes, yRes, totalXRes, totalYRes);
|
||||
}
|
||||
|
||||
if (new3dAttached) {
|
||||
// 3D path: let New3D draw into the default framebuffer from inside the core.
|
||||
glViewport(0, 0, winW, winH);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
// 3D path: let New3D draw into the default framebuffer from inside the core (scissored).
|
||||
host.RunFrame();
|
||||
|
||||
// Overlay TileGen top layers (HUD/menus) on top of 3D.
|
||||
presenter.Resize(winW, winH);
|
||||
presenter.SetStretch(false);
|
||||
if (host.render2d.HasTopSurface()) {
|
||||
const uint32_t* pixels = host.render2d.GetTopSurfaceARGB();
|
||||
presenter.UpdateFrameARGB(pixels, (int)host.render2d.GetFrameWidth(), (int)host.render2d.GetFrameHeight());
|
||||
@@ -537,8 +655,7 @@ extern "C" int SDL_main(int argc, char* argv[]) {
|
||||
// 2D-only path: keep showing TileGen software output.
|
||||
host.RunFrame();
|
||||
presenter.Resize(winW, winH);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
presenter.SetStretch(wideBackground);
|
||||
if (host.render2d.HasFrame()) {
|
||||
const uint32_t* pixels = host.render2d.GetFrameBufferRGBA();
|
||||
presenter.UpdateFrameARGB(pixels, (int)host.render2d.GetFrameWidth(), (int)host.render2d.GetFrameHeight());
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.izzy2lost.super3
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
object AssetInstaller {
|
||||
fun ensureInstalled(context: Context, internalUserRoot: File) {
|
||||
internalUserRoot.mkdirs()
|
||||
|
||||
// Copy the standard Supermodel folder layout from APK assets into the internal user root.
|
||||
// We never overwrite existing files so user edits persist.
|
||||
val topLevel = listOf("Assets", "Config", "GraphicsAnalysis", "NVRAM", "Saves")
|
||||
for (dir in topLevel) {
|
||||
copyAssetTree(context, dir, File(internalUserRoot, dir))
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyAssetTree(context: Context, assetPath: String, dest: File) {
|
||||
val assets = context.assets
|
||||
val children = assets.list(assetPath) ?: return
|
||||
|
||||
// If list() returns empty, it's a file.
|
||||
if (children.isEmpty()) {
|
||||
if (!dest.exists()) {
|
||||
dest.parentFile?.mkdirs()
|
||||
assets.open(assetPath).use { input ->
|
||||
dest.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!dest.exists()) dest.mkdirs()
|
||||
for (child in children) {
|
||||
copyAssetTree(context, "$assetPath/$child", File(dest, child))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user