start of ui game list config etc

This commit is contained in:
izzy2lost
2025-12-17 04:44:30 -05:00
parent 097ddb08c8
commit ae0365ae21
25 changed files with 6468 additions and 70 deletions
+2025
View File
File diff suppressed because it is too large Load Diff
-5
View File
@@ -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);
+1
View File
@@ -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")
}
+8 -1
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
Recommended (but not mandatory) location for ROM sets.
@@ -0,0 +1 @@
Save states go here.
+114 -31
View File
@@ -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;
};
+10 -3
View File
@@ -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;
+142 -25
View File
@@ -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