Search code examples
iosqcar-sdk

Loading markers from outside the app bundle in iOS with Qualcomm AR (QCAR)


Does anyone know if it is possible to load the config.xml and qcar-resources.dat (The files that contain the marker info) from outside the app bundle?

According to the official forums and documentation, it is not. However, there is an app out there called Blippar which seems to be doing it. I extracted the app Documents folder from an iPhone backup and they seem to have a config.xml in there that has more tracking markers than the one that is in the app bundle.

How could they have done this if the QCAR SDK doesn't allow you to specify a location of the tracking files?

I tried being hacky and creating a category on NSBundle to override

  • (NSString *)pathForResource:(NSString *)name ofType:(NSString *)ext;

to return a path from the documents folder instead, but that didn't seem to work.


Solution

  • Ok, I've solved it.

    First I created a category on NSBundle, and created new implementations of pretty much every method related to loading files from the bundle, until I found the one Qualcomm are using, which is:

    - (NSString *)pathForResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)subpath;
    

    Once I had that, I changed my category method to be:

    #define XFILPATH4DOCUMENT(_value) [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:_value]
    
    - (NSString *)pathForResourceOverride:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)subpath
    {
        if (([name isEqualToString:@"config"] && [ext isEqualToString:@"xml"]) || ([name isEqualToString:@"qcar-resources"] && [ext isEqualToString:@"dat"]))
        {
            // OMG
            NSString *fileName = [NSString stringWithFormat:@"%@.%@", name, ext];
            NSString *path = XFILPATH4DOCUMENT(fileName);
            NSLog(@"%@", path);
            return path;
        }
        else
        {
            return [self pathForResourceOverride:name ofType:ext inDirectory:subpath];
        }
    }
    

    Then, through the magic of method swizzling:

    Swizzle([NSBundle class], @selector(pathForResource:ofType:inDirectory:), @selector(pathForResourceOverride:ofType:inDirectory:));
    

    Using this method:

    #import <objc/runtime.h> 
    #import <objc/message.h>
    
    void Swizzle(Class c, SEL orig, SEL replacement)
    {
        Method origMethod = class_getInstanceMethod(c, orig);
        Method newMethod = class_getInstanceMethod(c, replacement);
        if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
            class_replaceMethod(c, replacement, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        else
            method_exchangeImplementations(origMethod, newMethod);
    }
    

    Which I got from the answer here: Method Swizzle on iPhone device

    Scary? Yes.

    But it works.