mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 12:15:31 +02:00
@@ -32,12 +32,17 @@ FILESYSTEM_CPP = LOVE_SRC / "src" / "modules" / "filesystem" / "physfs" / "Files
|
||||
IOS_MM = LOVE_SRC / "src" / "common" / "ios.mm"
|
||||
IOS_H = LOVE_SRC / "src" / "common" / "ios.h"
|
||||
SYSTEM_CPP = LOVE_SRC / "src" / "modules" / "system" / "System.cpp"
|
||||
AUDIO_H = LOVE_SRC / "src" / "modules" / "audio" / "openal" / "Audio.h"
|
||||
AUDIO_CPP = LOVE_SRC / "src" / "modules" / "audio" / "openal" / "Audio.cpp"
|
||||
ENTITLEMENTS_SRC = IOS_DIR / "overlays" / "love-ios.entitlements"
|
||||
|
||||
NATIVE_FILES = ("GRPickerBridge.swift", "GRHealthBridge.swift", "GRBootstrap.m")
|
||||
|
||||
MARKER = "gen1recomp iOS picker bridge"
|
||||
|
||||
POOL_PAUSE_MARKER = "poolPaused"
|
||||
AUDIO_EVENT_MARKER = "gr_pushAudioEvent"
|
||||
|
||||
# Headers must land outside `namespace love { namespace system {`.
|
||||
WRAP_INCLUDES = """
|
||||
// %s: headers for the native-bridge functions below.
|
||||
@@ -406,6 +411,277 @@ def patch_ios_haptics():
|
||||
print("patch_love_src: iOS haptics use Taptic Engine impact presets")
|
||||
|
||||
|
||||
def patch_audio_pool_gate():
|
||||
header = pristine(AUDIO_H, (POOL_PAUSE_MARKER,))
|
||||
header_include_original = "#include <stack>\n#include <cmath>\n"
|
||||
header_include_replacement = "#include <stack>\n#include <cmath>\n#include <atomic>\n"
|
||||
if header_include_original not in header:
|
||||
fail(f"audio header include anchor not found in {AUDIO_H}")
|
||||
header = header.replace(header_include_original, header_include_replacement, 1)
|
||||
|
||||
header_original = (
|
||||
"\tpublic:\n"
|
||||
"\t\tPoolThread(Pool *pool);\n"
|
||||
"\t\tvirtual ~PoolThread();\n"
|
||||
"\t\tvoid setFinish();\n"
|
||||
"\t\tvoid threadFunction();\n"
|
||||
"\t};\n"
|
||||
)
|
||||
header_replacement = (
|
||||
"\tpublic:\n"
|
||||
"\t\tPoolThread(Pool *pool);\n"
|
||||
"\t\tvirtual ~PoolThread();\n"
|
||||
"\t\tvoid setFinish();\n"
|
||||
"\t\tvoid setPoolPaused(bool paused);\n"
|
||||
"\t\tvoid threadFunction();\n"
|
||||
"\n"
|
||||
"\tprivate:\n"
|
||||
"\t\tstd::atomic<bool> poolPaused;\n"
|
||||
"\t};\n"
|
||||
)
|
||||
if header_original not in header:
|
||||
fail(f"PoolThread declaration anchor not found in {AUDIO_H}")
|
||||
AUDIO_H.write_text(header.replace(header_original, header_replacement, 1))
|
||||
|
||||
text = pristine(AUDIO_CPP, (POOL_PAUSE_MARKER,))
|
||||
|
||||
ctor_original = (
|
||||
"Audio::PoolThread::PoolThread(Pool *pool)\n"
|
||||
"\t: pool(pool)\n"
|
||||
"\t, finish(false)\n"
|
||||
"{\n"
|
||||
)
|
||||
ctor_replacement = (
|
||||
"Audio::PoolThread::PoolThread(Pool *pool)\n"
|
||||
"\t: pool(pool)\n"
|
||||
"\t, finish(false)\n"
|
||||
"\t, poolPaused(false)\n"
|
||||
"{\n"
|
||||
)
|
||||
if ctor_original not in text:
|
||||
fail(f"PoolThread constructor anchor not found in {AUDIO_CPP}")
|
||||
text = text.replace(ctor_original, ctor_replacement, 1)
|
||||
|
||||
loop_original = "\t\tpool->update();\n\t\tsleep(5);\n"
|
||||
loop_replacement = (
|
||||
"\t\tif (!poolPaused.load(std::memory_order_acquire))\n"
|
||||
"\t\t\tpool->update();\n"
|
||||
"\n"
|
||||
"\t\tsleep(5);\n"
|
||||
)
|
||||
if loop_original not in text:
|
||||
fail(f"pool update loop anchor not found in {AUDIO_CPP}")
|
||||
text = text.replace(loop_original, loop_replacement, 1)
|
||||
|
||||
finish_original = (
|
||||
"void Audio::PoolThread::setFinish()\n"
|
||||
"{\n"
|
||||
"\tthread::Lock lock(mutex);\n"
|
||||
"\tfinish = true;\n"
|
||||
"}\n"
|
||||
)
|
||||
finish_replacement = (
|
||||
"void Audio::PoolThread::setFinish()\n"
|
||||
"{\n"
|
||||
"\tthread::Lock lock(mutex);\n"
|
||||
"\tfinish = true;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"void Audio::PoolThread::setPoolPaused(bool paused)\n"
|
||||
"{\n"
|
||||
"\tpoolPaused.store(paused, std::memory_order_release);\n"
|
||||
"}\n"
|
||||
)
|
||||
if finish_original not in text:
|
||||
fail(f"setFinish anchor not found in {AUDIO_CPP}")
|
||||
text = text.replace(finish_original, finish_replacement, 1)
|
||||
|
||||
pause_original = (
|
||||
"#else\n"
|
||||
"\talcMakeContextCurrent(nullptr);\n"
|
||||
"#endif\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"void Audio::resumeContext()\n"
|
||||
)
|
||||
pause_replacement = (
|
||||
"#else\n"
|
||||
"\tif (poolThread)\n"
|
||||
"\t\tpoolThread->setPoolPaused(true);\n"
|
||||
"\talcMakeContextCurrent(nullptr);\n"
|
||||
"#endif\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"void Audio::resumeContext()\n"
|
||||
)
|
||||
if pause_original not in text:
|
||||
fail(f"pauseContext anchor not found in {AUDIO_CPP}")
|
||||
text = text.replace(pause_original, pause_replacement, 1)
|
||||
|
||||
resume_original = (
|
||||
"#else\n"
|
||||
"\tif (context && alcGetCurrentContext() != context)\n"
|
||||
"\t\talcMakeContextCurrent(context);\n"
|
||||
"#endif\n"
|
||||
)
|
||||
resume_replacement = (
|
||||
"#else\n"
|
||||
"\tif (context && alcGetCurrentContext() != context)\n"
|
||||
"\t\talcMakeContextCurrent(context);\n"
|
||||
"\tif (poolThread)\n"
|
||||
"\t\tpoolThread->setPoolPaused(false);\n"
|
||||
"#endif\n"
|
||||
)
|
||||
if resume_original not in text:
|
||||
fail(f"resumeContext anchor not found in {AUDIO_CPP}")
|
||||
text = text.replace(resume_original, resume_replacement, 1)
|
||||
|
||||
AUDIO_CPP.write_text(text)
|
||||
print("patch_love_src: audio pool thread gated while the context is paused")
|
||||
|
||||
|
||||
IOS_AUDIO_EVENT_HELPER = """static void gr_pushAudioEvent(const char *name)
|
||||
{
|
||||
auto ev = love::Module::getInstance<love::event::Event>(love::Module::M_EVENT);
|
||||
if (ev == nullptr)
|
||||
return;
|
||||
|
||||
love::event::Message *msg = new love::event::Message(name);
|
||||
ev->push(msg);
|
||||
msg->release();
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
IOS_ROUTE_CHANGE_METHOD = """
|
||||
- (void)audioSessionRouteChange:(NSNotification *)note
|
||||
{
|
||||
@synchronized (self)
|
||||
{
|
||||
NSNumber *reason = note.userInfo[AVAudioSessionRouteChangeReasonKey];
|
||||
NSUInteger value = reason.unsignedIntegerValue;
|
||||
if (value != AVAudioSessionRouteChangeReasonOldDeviceUnavailable
|
||||
&& value != AVAudioSessionRouteChangeReasonNewDeviceAvailable)
|
||||
return;
|
||||
|
||||
auto audio = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
|
||||
if (!audio)
|
||||
{
|
||||
NSLog(@"LoveAudioInterruptionListener could not get love audio module");
|
||||
return;
|
||||
}
|
||||
|
||||
gr_pushAudioEvent("audiosuspend");
|
||||
audio->pauseContext();
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@synchronized (self)
|
||||
{
|
||||
NSError *err = nil;
|
||||
if (![[AVAudioSession sharedInstance] setActive:YES error:&err])
|
||||
NSLog(@"Error reactivating AVAudioSession: %@", [err localizedDescription]);
|
||||
|
||||
auto resumed = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
|
||||
if (resumed)
|
||||
resumed->resumeContext();
|
||||
|
||||
gr_pushAudioEvent("audioreset");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
IOS_ROUTE_OBSERVER = """
|
||||
[center addObserver:[LoveAudioInterruptionListener shared]
|
||||
selector:@selector(audioSessionRouteChange:)
|
||||
name:AVAudioSessionRouteChangeNotification
|
||||
object:session];
|
||||
"""
|
||||
|
||||
|
||||
def patch_ios_audio_route():
|
||||
text = IOS_MM.read_text()
|
||||
if AUDIO_EVENT_MARKER in text:
|
||||
print("patch_love_src: iOS audio route-change handling already present")
|
||||
return
|
||||
|
||||
include_anchor = '#include "modules/audio/Audio.h"\n'
|
||||
if include_anchor not in text:
|
||||
fail(f"audio module include not found in {IOS_MM}")
|
||||
text = text.replace(
|
||||
include_anchor, include_anchor + '#include "modules/event/Event.h"\n', 1)
|
||||
|
||||
listener_anchor = "@interface LoveAudioInterruptionListener : NSObject\n"
|
||||
if listener_anchor not in text:
|
||||
fail(f"audio listener interface not found in {IOS_MM}")
|
||||
text = text.replace(listener_anchor,
|
||||
IOS_AUDIO_EVENT_HELPER + listener_anchor, 1)
|
||||
|
||||
interruption_original = (
|
||||
"\t\tNSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];\n"
|
||||
"\t\tif (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)\n"
|
||||
"\t\t\taudio->pauseContext();\n"
|
||||
"\t\telse\n"
|
||||
"\t\t\taudio->resumeContext();\n"
|
||||
"\t}\n"
|
||||
"}\n"
|
||||
)
|
||||
interruption_replacement = (
|
||||
"\t\tNSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];\n"
|
||||
"\t\tif (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)\n"
|
||||
"\t\t{\n"
|
||||
"\t\t\tgr_pushAudioEvent(\"audiosuspend\");\n"
|
||||
"\t\t\taudio->pauseContext();\n"
|
||||
"\t\t}\n"
|
||||
"\t\telse\n"
|
||||
"\t\t{\n"
|
||||
"\t\t\taudio->resumeContext();\n"
|
||||
"\t\t\tgr_pushAudioEvent(\"audioreset\");\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"}\n"
|
||||
+ IOS_ROUTE_CHANGE_METHOD
|
||||
)
|
||||
if interruption_original not in text:
|
||||
fail(f"audio interruption handler not found in {IOS_MM}")
|
||||
text = text.replace(interruption_original, interruption_replacement, 1)
|
||||
|
||||
active_original = (
|
||||
"\t\t\tNSLog(@\"ERROR:could not get love audio module\");\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\t\taudio->resumeContext();\n"
|
||||
"\t}\n"
|
||||
"}\n"
|
||||
)
|
||||
active_replacement = (
|
||||
"\t\t\tNSLog(@\"ERROR:could not get love audio module\");\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\t\taudio->resumeContext();\n"
|
||||
"\t\tgr_pushAudioEvent(\"audioreset\");\n"
|
||||
"\t}\n"
|
||||
"}\n"
|
||||
)
|
||||
if active_original not in text:
|
||||
fail(f"applicationBecameActive handler not found in {IOS_MM}")
|
||||
text = text.replace(active_original, active_replacement, 1)
|
||||
|
||||
observer_anchor = (
|
||||
"\t\t[center addObserver:[LoveAudioInterruptionListener shared]\n"
|
||||
"\t\t\t selector:@selector(audioSessionInterruption:)\n"
|
||||
"\t\t\t name:AVAudioSessionInterruptionNotification\n"
|
||||
"\t\t\t object:session];\n"
|
||||
)
|
||||
if observer_anchor not in text:
|
||||
fail(f"audio interruption observer not found in {IOS_MM}")
|
||||
text = text.replace(observer_anchor, observer_anchor + IOS_ROUTE_OBSERVER, 1)
|
||||
|
||||
IOS_MM.write_text(text)
|
||||
print("patch_love_src: iOS audio route changes suspend and rebuild playback")
|
||||
|
||||
|
||||
def patch_pbxproj():
|
||||
text = pristine(PBXPROJ)
|
||||
|
||||
@@ -473,6 +749,8 @@ def main():
|
||||
copy_native_files()
|
||||
patch_public_documents()
|
||||
patch_ios_haptics()
|
||||
patch_ios_audio_route()
|
||||
patch_audio_pool_gate()
|
||||
patch_wrap_system()
|
||||
patch_pbxproj()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user