Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

JoeMatt
Copy link
Member

@JoeMatt JoeMatt commented Sep 6, 2025

PR Type

Enhancement


Description

  • Add Skylander Portal emulation support for iOS

  • Implement Skylander file loading and management

  • Add portal settings in Wii configuration

  • Support .sky file format for Skylander dumps


Diagram Walkthrough

flowchart LR
  A["Wii Settings"] --> B["Skylander Portal Toggle"]
  B --> C["Emulation Menu"]
  C --> D["Skylander Manager"]
  D --> E["Load .sky Files"]
  D --> F["Clear Skylanders"]
Loading

File Walkthrough

Relevant files
Enhancement
ConfigWiiViewController.mm
Add Skylander Portal settings toggle                                         

Source/iOS/App/Common/UI/Settings/Config/Wii/ConfigWiiViewController.mm

  • Add Skylander Portal switch configuration
  • Implement switch change handler
  • Connect switch to config setting
+25/-18 
EmulationiOSViewController.mm
Implement Skylander Portal emulation interface                     

Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.mm

  • Add Skylander Portal menu in emulation
  • Implement file picker for .sky files
  • Add portal management (load, clear, clear all)
  • Handle Skylander file loading and validation
+179/-86
ConfigWiiViewController.h
Add Skylander Portal switch outlet                                             

Source/iOS/App/Common/UI/Settings/Config/Wii/ConfigWiiViewController.h

  • Add skylanderPortalSwitch outlet property
+1/-0     
EmulationiOSViewController.h
Add document picker delegate and slot tracking                     

Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.h

  • Add UIDocumentPickerDelegate protocol
  • Add skylanderSlot property for tracking
+3/-1     
ConfigSettings.storyboard
Add Skylander Portal UI in settings                                           

Source/iOS/App/DolphiniOS/UI/Settings/Config/Base.lproj/ConfigSettings.storyboard

  • Add Skylander Portal switch UI element
  • Update table view cell layout and constraints
+41/-13 
ConfigSettings.strings
Add English localization strings                                                 

Source/iOS/App/DolphiniOS/UI/Settings/Config/en.lproj/ConfigSettings.strings

  • Add English localization for Skylander Portal setting
+3/-0     
ConfigSettings.strings
Add Japanese localization strings                                               

Source/iOS/App/DolphiniOS/UI/Settings/Config/ja.lproj/ConfigSettings.strings

  • Add Japanese localization for Skylander Portal setting
+3/-0     
Configuration changes
Info.plist
Register Skylander dump file type                                               

Source/iOS/App/DolphiniOS/Info.plist

  • Register .sky file type for Skylander dumps
  • Add UTType definition for skylander files
+33/-0   

Copy link

qodo-merge-pro bot commented Sep 6, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Crash

In the document picker handler, the code indexes urls[0] without validating that the array is non-empty or the URL is a file URL. Add guards and handle cancellation gracefully to avoid crashes.

NSString* sourcePath = [urls[0] path];
std::string path = std::string([sourcePath UTF8String]);
File::IOFile sky_file(path, "r+b");
if (!sky_file)
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Open Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
File Handling

The File::IOFile is opened with "r+b" and then moved into SkylanderFigure after reading; ensure ownership and lifetime are correct and the file descriptor is not leaked if early returns occur. Consider closing before early returns or using RAII consistently.

std::string path = std::string([sourcePath UTF8String]);
File::IOFile sky_file(path, "r+b");
if (!sky_file)
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Open Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
    return;
}
std::array<u8, 0x40 * 0x10> file_data;
if (!sky_file.ReadBytes(file_data.data(), file_data.size()))
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Read Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
    return;
}
auto& system = Core::System::GetInstance();
std::pair<u16, u16> id_var = system.GetSkylanderPortal().CalculateIDs(file_data);
u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(std::make_unique<IOS::HLE::USB::SkylanderFigure>(std::move(sky_file)));
if (portal_slot == 0xFF)
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Load Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
    return;
}
self.skylanderSlot = portal_slot + 1;
UX/Localization

Newly added alert titles and button texts are hardcoded English strings; these should use DOLCoreLocalizedString or localized string tables for consistency with the rest of the UI.

    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Open Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
    return;
}
std::array<u8, 0x40 * 0x10> file_data;
if (!sky_file.ReadBytes(file_data.data(), file_data.size()))
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Read Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
    return;
}
auto& system = Core::System::GetInstance();
std::pair<u16, u16> id_var = system.GetSkylanderPortal().CalculateIDs(file_data);
u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(std::make_unique<IOS::HLE::USB::SkylanderFigure>(std::move(sky_file)));
if (portal_slot == 0xFF)
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Load Skylander File!"
                                   message:nil
                                   preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];
    return;

Copy link

qodo-merge-pro bot commented Sep 6, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Move portal ops off UI thread

Skylander portal operations (LoadSkylander/RemoveSkylander) are invoked directly
from the main/UI thread, which risks race conditions with the emulation core and
can stall the UI. Mirror the save-state pattern by dispatching these calls
through the core/host queue (e.g., DOLHostQueueRunAsync) and perform file I/O
off the main thread, then post UI updates back to the main queue. This ensures
thread safety with Core::System and keeps the app responsive.

Examples:

Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.mm [249-268]
        UIAlertAction* clearAction = [UIAlertAction actionWithTitle:@"Clear" style:UIAlertActionStyleDefault
                                                       handler:^(UIAlertAction* action) {
            auto& system = Core::System::GetInstance();
            if (self.skylanderSlot) {
              bool removed = system.GetSkylanderPortal().RemoveSkylander(self.skylanderSlot - 1);
              if (removed && self.skylanderSlot != 0) {
                  self.skylanderSlot--;
              }
            }
        }];

 ... (clipped 10 lines)
Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.mm [457-490]
- (void)documentPicker:(UIDocumentPickerViewController*)controller didPickDocumentsAtURLs:(NSArray<NSURL*>*)urls {
    NSString* sourcePath = [urls[0] path];
    std::string path = std::string([sourcePath UTF8String]);
    File::IOFile sky_file(path, "r+b");
    if (!sky_file)
    {
        UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Open Skylander File!"
                                       message:nil
                                       preferredStyle:UIAlertControllerStyleAlert];
        [self presentViewController:alert animated:YES completion:nil];

 ... (clipped 24 lines)

Solution Walkthrough:

Before:

// In EmulationiOSViewController.mm

// "Clear" action handler
- (void)clearSkylander {
    auto& system = Core::System::GetInstance();
    if (self.skylanderSlot) {
        system.GetSkylanderPortal().RemoveSkylander(self.skylanderSlot - 1);
        self.skylanderSlot--;
    }
}

// File picker delegate
- (void)documentPicker:(...)didPickDocumentsAtURLs:(...)urls {
    NSString* sourcePath = [urls[0] path];
    File::IOFile sky_file(std::string([sourcePath UTF8String]), "r+b"); // File I/O on main thread
    // ... read file data on main thread ...
    auto& system = Core::System::GetInstance();
    u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(...); // Core interaction on main thread
    self.skylanderSlot = portal_slot + 1;
}

After:

// In EmulationiOSViewController.mm

// "Clear" action handler
- (void)clearSkylander {
    DOLHostQueueRunAsync(^{
        auto& system = Core::System::GetInstance();
        if (self.skylanderSlot) {
            bool removed = system.GetSkylanderPortal().RemoveSkylander(self.skylanderSlot - 1);
            if (removed) {
                dispatch_async(dispatch_get_main_queue(), ^{ self.skylanderSlot--; });
            }
        }
    });
}

// File picker delegate
- (void)documentPicker:(...)didPickDocumentsAtURLs:(...)urls {
    DOLHostQueueRunAsync(^{
        NSString* sourcePath = [urls[0] path];
        File::IOFile sky_file(std::string([sourcePath UTF8String]), "r+b");
        // ... read file data ...
        auto& system = Core::System::GetInstance();
        u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(...);
        dispatch_async(dispatch_get_main_queue(), ^{
            if (portal_slot != 0xFF) self.skylanderSlot = portal_slot + 1;
            else { /* show error alert */ }
        });
    });
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that file I/O and core emulation operations are performed on the main UI thread, which can cause UI freezes and race conditions, and proposes a robust solution using the existing DOLHostQueueRunAsync pattern.

High
Possible issue
Fix UTType construction for picker

Replace the UTType construction to use a valid Objective-C API.
exportedTypeWithIdentifier: is not available in Objective-C and will fail to
build. Use typeWithIdentifier: and optionally fall back to the file extension.

Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.mm [238-241]

-NSArray<UTType*>* types = @[
-    [UTType exportedTypeWithIdentifier:@"me.oatmealdome.dolphinios.skylander-dumps"]
-  ];
+UTType* skyType = nil;
+if (@available(iOS 14.0, *)) {
+  skyType = [UTType typeWithIdentifier:@"me.oatmealdome.dolphinios.skylander-dumps"];
+  if (!skyType) {
+    skyType = [UTType typeWithFilenameExtension:@"sky"];
+  }
+}
+NSArray<UTType*>* types = skyType ? @[ skyType ] : @[];
 UIDocumentPickerViewController* pickerController = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:types];
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that exportedTypeWithIdentifier: is a Swift-only API, and using it in Objective-C++ would cause a compilation error.

High
Use security-scoped URL access

Access the picked URL using security-scoped resource APIs to avoid sandbox
permission failures. Call startAccessingSecurityScopedResource before opening
the file and ensure stopAccessingSecurityScopedResource is called in a @finally
block.

Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.mm [457-490]

 - (void)documentPicker:(UIDocumentPickerViewController*)controller didPickDocumentsAtURLs:(NSArray<NSURL*>*)urls {
-    NSString* sourcePath = [urls[0] path];
-    std::string path = std::string([sourcePath UTF8String]);
-    File::IOFile sky_file(path, "r+b");
-    if (!sky_file)
-    {
-        UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Open Skylander File!"
-                                       message:nil
-                                       preferredStyle:UIAlertControllerStyleAlert];
-        [self presentViewController:alert animated:YES completion:nil];
-        return;
+    NSURL* url = urls.firstObject;
+    BOOL started = [url startAccessingSecurityScopedResource];
+    @try {
+        NSString* sourcePath = [url path];
+        std::string path = std::string([sourcePath UTF8String]);
+        File::IOFile sky_file(path, "r+b");
+        if (!sky_file)
+        {
+            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to Open Skylander File!"
+                                           message:nil
+                                           preferredStyle:UIAlertControllerStyleAlert];
+            [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
+            [self presentViewController:alert animated:YES completion:nil];
+            return;
+        }
+        ...
+        self.skylanderSlot = portal_slot + 1;
+    } @finally {
+        if (started) {
+            [url stopAccessingSecurityScopedResource];
+        }
     }
-    ...
-    self.skylanderSlot = portal_slot + 1;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: This suggestion correctly points out the need for security-scoped resource access to handle files outside the app's sandbox, preventing potential runtime permission errors.

High
Reset file offset before loading

Reset the file read position before passing the file to the Skylander loader.
After reading bytes for validation, the file pointer is advanced and may cause
incorrect parsing.

Source/iOS/App/DolphiniOS/UI/Emulation/EmulationiOSViewController.mm [478-480]

 auto& system = Core::System::GetInstance();
 std::pair<u16, u16> id_var = system.GetSkylanderPortal().CalculateIDs(file_data);
+sky_file.Seek(0, SEEK_SET);
 u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(std::make_unique<IOS::HLE::USB::SkylanderFigure>(std::move(sky_file)));
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the file pointer is not reset after reading, which would cause subsequent operations on the file to fail or use incorrect data.

Medium
  • More

@JoeMatt JoeMatt force-pushed the brand175/master+patches branch from 2475505 to 749b2f2 Compare September 9, 2025 01:39
@JoeMatt
Copy link
Member Author

JoeMatt commented Sep 21, 2025

already merged

@JoeMatt JoeMatt closed this Sep 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants