QCRenderer and NSData

cwright's picture

Anyone familiar with QCRenderer will know of its weakness when it comes to initialization. Its only init method uses an NSString which is interpreted as a file name, which is loaded. If you want to provide data from some other route, you have no choice but to save your compositions in plain text on the harddrive.

Normally, I'm all for open data, and I suppose the above enforces that. But there are times where you just want your compositions to be private.

A few days ago, a friend "dared" me to overcome this limitation. And now, after quite a bit of reverse engineering, here I am, with a (very beta) solution.

[Note: this only works for 10.4 versions of Quartz Composer. 10.5 changed the location of objects inside, so using this will cause cool errors when the QC runtime tries to invoke methods on the wrong objects]

@class QCPatch;
 
typedef struct
{
   QCPatch *patch;
        QCOpenGLContext *context;
        NSDictionary    *args;
        void *reserved[3];
} KinemePrivateQCRendererStruct;
 
extern NSDictionary* QCCompositionFromData(NSData*data);
extern id QCPatchFromComposition(id composition, int something);
extern id QCInfoFromComposition(id composition);
 
@interface QCRenderer (KinemeData)
- (QCRenderer*) initWithOpenGLContext: (NSOpenGLContext*)context
                pixelFormat: (NSOpenGLPixelFormat*)format
      data: (NSData*)data;
@end
 
@implementation QCRenderer (KinemeData)
// This is intended to behave exactly like 
// initWith... file:, but using NSData instead
- (QCRenderer*) initWithOpenGLContext: (NSOpenGLContext*)context
                pixelFormat: (NSOpenGLPixelFormat*)format
            data: (NSData*)data
{   
   KinemePrivateQCRendererStruct *kpqcr;
 
   if(context == nil)
   {
      // GFThrowException(?,?,?,?,?);
      return nil;
   }
   if(format == nil)
   {
      // GFThrowException(?,?,?,?,?);
      return nil;
   }
   if(data == nil || [data length] == 0)
   {
      // GFThrowException(?,?,?,?,?);
      return nil;
   }
   self = [super init];
 
   if(self)
   {
      kpqcr = (KinemePrivateQCRendererStruct*) 
         NSAllocateCollectable(sizeof(KinemePrivateQCRendererStruct), 0);
      NSDictionary *composition = QCCompositionFromData(data);
      kpqcr->patch = QCPatchFromComposition(composition,0);
      kpqcr->args = QCInfoFromComposition(composition);
 
      QCOpenGLContext* ctxt = 
         [[QCOpenGLContext alloc] initWithNSOpenGLContext:context
         format:format options:nil];
      [ctxt retain];
      [kpqcr->patch retain];
      kpqcr->context = ctxt;
 
      [kpqcr->patch startRendering:ctxt options:nil];
   }
 
   _QCRendererPrivate = kpqcr;
 
   return self;
}
 
@end

This chunk of code creates a new method for QCRenderer, which eats an NSData object for init instead of an NSString for a file.

You might need to define other classes before you use it. I can't promise that it works, but it works within QC itself when plugged into the ImageWithComposition patch, at least for me.

Have a lot of fun :)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

cwright's picture
Performing this in Leopard

This is now a supported operation in QC Leopard. You'll do a [QCComposition compositionWithData:(NSData)myData], and then [QCRenderer initWith... composition:myComposition]; Pretty cool. Too bad I didn't read the release notes closely towards the end, or it would have saved me all the time it took to track this down by disassembling :) *oh well

(Later Edit:)

Ok, so the loading by composition stuff, according to the documentation, is not supposed to allow 'consumer patches' in the patch when loading like this. I've not seen any problems doing this though, but I might be cheating a bit :)

The code to do it is non-obvious (or at least, to me it wasn't), so here's what I came up with over the weekend to pull it off in Leopard

QCComposition *qcc = [QCComposition compositionWithData:data];
if(qcc)
{
   // I honestly have no idea what QCGetIndexedColorSpace(7) is for
   // I just know that QC does it
   // when loading from a file, following this exact pattern.
   [self initWithCGLContext: [context CGLContextObj]
         pixelFormat: [format CGLPixelFormatObj]
         colorSpace: (struct CGColorSpace*)QCGetIndexedColorSpace(7)
         composition: qcc];
 
   return self;
}
return nil;

This is straight from the initWith... file: method, so it should work identically. The only hairy part is the QCGetIndexedColorSpace guy... shrugs more exploring to do I guess :)

gtoledo3's picture
Could you elaborate on

Could you elaborate on this... In particular, I am wondering if this method could be used to utilize a qtz file as a resource for an actual plug-in without it obviously being a qtz. Or, what is it that you find this to be most useful for, situation-wise?

cwright's picture
embedding and encryption

This is useful if you want to store compositions in a secret way (either packed into a file somewhere, generated dynamically, or encrypted on disk) -- for secrecy, you don't want to have the file laying around for users to peek at/edit, so this allows you to store the compositions in other ways.

Or, you can just store them encrypted, and decrypt them to a hidden folder, and load as usual. And hope the user doesn't force-quit the app and find the files laying around (you may laugh, but I've seen at least one app do this... rather comical).

If you have lots of valuable assets in your composition (fancy shaders, fancy images, etc), and you don't want them stolen, this is helpful.

(We don't employ this anywhere ourselves, but should the need arise, I figured it might be handy for others)

psonice's picture
initWithComposition

The most obvious use (or at least where I've instantly found use for it) is using a composition in an app without including the .qtz in the bundle. If you don't want the .qtz file to be instantly accessable and editable, that's pretty essential (I actually put registration/serial number handling inside a .qtz before :)

franz's picture
serial numbering ?

could you elaborate on this serial numbering ? Is it done through the QC editor ?

gtoledo3's picture
Cool, that is exactly what I

Cool, that is exactly what I was thinking then... I just wanted to make sure I wasn't misreading the potential use scenarios. That is a nice discovery.

This is interesting. I think that I have run into an app that works this way(a year or so ago), where the files weren't qtz's but something similar (qta's?... qtx's?), and I had determined that they WERE qtz's and using a method like this (probably this exact method)... I WISH I could remember the name of that thing. (.... the encryption setup was the most memorable part of the app).

psonice's picture
extremely weak protection

Kind of. I just wrote a basic thing in javascript... you enter a serial number through a published port, it checks if it's divisible by a large prime number. If it is, watermarking etc. gets disabled. It's very hard to get a working serial by randomly guessing, but it'd be super easy for anyone with a bit of knowledge I expect. But then I wanted mild encouragement for people to pay, not fort knox :)

gtoledo3's picture
props

Extremely weak maybe, but also extremely clever.