Merge pull request #972 from hernan0078/ios/stadium-picker-kind

iOS picker: a "stadium" kind, and a way to ask which kinds exist
This commit is contained in:
bryanthaboi
2026-08-08 05:44:22 -04:00
committed by GitHub
2 changed files with 78 additions and 1 deletions
+34 -1
View File
@@ -85,11 +85,30 @@ public final class GRPickerBridge: NSObject {
types = [.zip]
case "sav":
destName = "picked_save.sav"
default:
// A Nintendo 64 cartridge, for mods that build assets out of one --
// the voxel mod's Pokemon Stadium battle models are the caller this
// was added for. Its own filename on purpose: an N64 ROM landing on
// picked_rom.gb is swept up by the Game Boy importer, deleted, and
// reported to the player as a broken cartridge.
case "stadium":
destName = "picked_stadium.z64"
for ext in ["z64", "n64", "v64"] {
if let t = UTType(filenameExtension: ext) { types.append(t) }
}
case "rom", "":
destName = "picked_rom.gb"
for ext in ["gb", "gbc"] {
if let t = UTType(filenameExtension: ext) { types.append(t) }
}
// An unknown kind is REFUSED rather than treated as a Game Boy ROM.
//
// It used to fall through to picked_rom.gb, so a caller asking for a
// kind this build had never heard of got its file deleted and
// reported as a broken cartridge -- the worst possible answer to
// "I do not know that one". Returning false lets the caller find out
// and offer its own fallback.
default:
return false
}
// .gb/.gbc/.sav resolve to dynamic UTTypes on most devices; offering
// .data as well keeps every real file selectable. The importer
@@ -107,6 +126,20 @@ public final class GRPickerBridge: NSObject {
return present(picker, with: delegate)
}
// Which kinds presentPicker understands, comma separated.
//
// So a CALLER can ask before it calls. A mod that wants a kind this build
// predates cannot otherwise tell "refused" from "the picker would not
// open", and guessing wrong used to cost the player their ROM (see the
// default case above). Asking first turns that into a fallback the caller
// chooses rather than a file it loses.
//
// Kept beside the switch it describes, because the two drifting apart is
// the only way this can lie.
@objc public static func supportedPickerKinds() -> NSString {
return "rom,mod,sav,stadium" as NSString
}
@objc(presentExportWithName:saveDir:)
public static func presentExport(name: UnsafePointer<CChar>?,
saveDir: UnsafePointer<CChar>?) -> Bool {
+44
View File
@@ -84,6 +84,49 @@ int w_pickFile(lua_State *L)
return gr_callBridge(L, "GRPickerBridge", "presentPickerWithKind:saveDir:", kind);
}
// love.system.pickFileKinds() -> "rom,mod,sav,stadium", or nil off iOS.
//
// So a caller can ask what this build's picker understands BEFORE opening it.
// An unknown kind is refused (GRPickerBridge), and a refusal looks exactly
// like a picker that would not open -- so a caller with a fallback worth
// showing needs to know which it is facing. A mod that guesses instead has
// no way back: before the refusal landed, an unrecognised kind wrote
// picked_rom.gb and the ROM importer deleted it.
//
// nil where there is no bridge at all, which reads the same as "no kinds".
int w_pickFileKinds(lua_State *L)
{
Class cls = objc_getClass("GRPickerBridge");
if (cls == nullptr)
{
lua_pushnil(L);
return 1;
}
// Fetched through the runtime: wrap_System.cpp is compiled as C++ rather
// than Objective-C++, so no Foundation type may be NAMED here -- writing
// `NSString` alone breaks the whole translation unit. objc_msgSend is a
// plain C entry point and `id` comes from objc/runtime.h, so the string
// is asked for its UTF8 bytes without ever being typed.
typedef id (*GRObj)(Class, SEL);
id kinds = ((GRObj)objc_msgSend)(cls,
sel_registerName("supportedPickerKinds"));
if (kinds == nullptr)
{
lua_pushnil(L);
return 1;
}
typedef const char *(*GRUTF8)(id, SEL);
const char *bytes = ((GRUTF8)objc_msgSend)(kinds,
sel_registerName("UTF8String"));
if (bytes == nullptr || bytes[0] == '\0')
{
lua_pushnil(L);
return 1;
}
lua_pushstring(L, bytes);
return 1;
}
int w_createFile(lua_State *L)
{
const char *name = luaL_optstring(L, 1, "export.sav");
@@ -101,6 +144,7 @@ int w_syncHealthSteps(lua_State *L)
WRAP_REGISTRATION = """#ifdef LOVE_IOS
{ "pickFile", w_pickFile },
{ "pickFileKinds", w_pickFileKinds },
{ "createFile", w_createFile },
{ "syncHealthSteps", w_syncHealthSteps },
{ "httpDownload", w_httpDownload },