//
//  macspeedhack.c
//  
//
//  Created by Eric Heijnen on 19/11/2023.
//  Copyright Cheat Engine
//

#include <celog.h>
#include <stdint.h>


struct timeval
{
  unsigned long long tv_sec;
  unsigned int tv_usec;
};
//unsigned char buffer[4096]={0};

#define _PTHREAD_ONCE_SIG_init        0x30B1BCBA
#define __PTHREAD_ONCE_SIZE__           8
struct _opaque_pthread_once_t {
        long __sig;
        char __opaque[__PTHREAD_ONCE_SIZE__];
};
typedef struct _opaque_pthread_once_t pthread_once_t;

typedef void* pthread_key_t;
pthread_once_t tls_calledonce={_PTHREAD_ONCE_SIG_init,{0}};
pthread_key_t speedhackkey;

int phase1, phase2, phase3, phase4, phase5;
int phase;

int pthread_key_create(pthread_key_t *key, void* destructor);
int pthread_once(pthread_once_t *once, void* initroutine);
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, void* newvalue);

float speedhack_wantedspeed=1.0f;


int lastc;

void tls_calledonceinit(void)
{
 // debug_log("speedhack initonce");
  phase=3;
  pthread_key_create(&speedhackkey, (void*)0);
  phase=4;
}

void initspeedhacktls()
{
  phase=2;
  pthread_once(&tls_calledonce, tls_calledonceinit);
  phase=5;
}

int insideSpeedFunction()
{
  int c;
  phase=1;
  initspeedhacktls();
  phase=6;

  c=(int)pthread_getspecific(speedhackkey);
  phase=7;
  return c;
}

int enterFunction()
{
  initspeedhacktls();

  int c=(int)pthread_getspecific(speedhackkey);
  lastc=c;

  c++;
  pthread_setspecific(speedhackkey, (void*)c);

  return c;
}

int leaveFunction()
{
  int c=(int)pthread_getspecific(speedhackkey);
  if (c)
    c--;
  pthread_setspecific(speedhackkey, (void*)c);

  return c;
}

int insidecount;

typedef uint64_t (*MACH_ABSOLUTE_TIME)(void);
MACH_ABSOLUTE_TIME oldmach_absolute_time;
float mat_speed=1.0f;
unsigned long long mat_initialtime;
unsigned long long mat_initialoffset;
uint64_t newmach_absolute_time(void)
{
    uint64_t r;
    uint64_t newr;
    if (insideSpeedFunction())
    {
      insidecount++;
      return oldmach_absolute_time();
    }
    
    enterFunction();
    r=oldmach_absolute_time(); //could theoretically call another function that is also hooked which is why the tls is used

    
    if (mat_initialtime==0)
    {
        mat_initialtime=r;
        mat_initialoffset=r;
    }
    
    uint64_t timepassed;
    
    timepassed=r-mat_initialtime;
    
    newr=timepassed*mat_speed;
    newr=newr+mat_initialoffset;
 
    if (mat_speed!=speedhack_wantedspeed)
    {
      //the user wants to change the speed
      mat_initialoffset=newr;
      mat_initialtime=r;
    }
    
    leaveFunction();

    return newr;
}

typedef int (*GETTIMEOFDAY)(struct timeval *tv, struct timezone *tz);
GETTIMEOFDAY oldgettimeofday; //call this for the non-hooked variant
float gtod_speed=1.0f;
unsigned long long gtod_initialtime_sec;
unsigned long long gtod_initialtime_usec; //between 0 and 9999999
unsigned long long gtod_initialoffset_sec;
unsigned long long gtod_initialoffset_usec; //between 0 and 9999999

int newgettimeofday(struct timeval *tv, struct timezone *tz)
{
    int r;
    
  phase1=1;

  if (insideSpeedFunction())
  {
    insidecount++;
    return oldgettimeofday(tv, tz);
  }

  phase2=2;

  struct timeval currenttime;

  enterFunction();
  phase3=3;
    
  r=oldgettimeofday(tv,tz); //could theoretically call another function that is also hooked which is why the tls is used

  currenttime=*tv;
  phase4=4;
  if ((r==0) && (tv))
  {
    //adjust the result
 
    int showlog=0;//(gtod_speed!=1.0f);

    if (showlog)
    {
      debug_log("Speedhack active (speed=%f)",gtod_speed);
      debug_log("currenttime=%u : %u", tv->tv_sec, tv->tv_usec);
    }


    if (gtod_initialtime_sec==0)
    {
      gtod_initialtime_sec=tv->tv_sec;
      gtod_initialtime_usec=tv->tv_usec;
      gtod_initialoffset_sec=tv->tv_sec;
      gtod_initialoffset_usec=tv->tv_usec;
    }

    unsigned long long newtime_usec; //always increases
    int newtime_sec; //can rollback from 999999 to 0

    unsigned long long timepassed_sec;
    int timepassed_usec;
    timepassed_sec=tv->tv_sec-gtod_initialtime_sec;
    timepassed_usec=tv->tv_usec-gtod_initialtime_usec;

    if (showlog)
      debug_log("timepassed orig=%u: %u",timepassed_sec, timepassed_usec);

    if (timepassed_usec>999999)
    {
      //add this to timepassed_sec
      timepassed_sec+=timepassed_usec / 1000000;
      timepassed_usec=timepassed_usec % 1000000;
    }

    while (timepassed_usec<0)
    {
      timepassed_sec--;
      timepassed_usec+=1000000;
    }


    if (showlog)
      debug_log("timepassed fixed=%u: %u",timepassed_sec, timepassed_usec);

    newtime_usec=timepassed_usec*gtod_speed;
    newtime_sec=timepassed_sec*gtod_speed;

    if (showlog)
      debug_log("timepassed according to speedhack=%u: %u",newtime_sec, newtime_usec);


    newtime_usec=newtime_usec+gtod_initialoffset_usec;
    newtime_sec=newtime_sec+gtod_initialoffset_sec;

    if (showlog)
      debug_log("newtime=%u: %u",newtime_sec, newtime_usec);


    int secondsextra=(newtime_usec / 1000000);

    if (showlog)
      debug_log("secondsextra=%u",secondsextra);

    newtime_sec=newtime_sec+secondsextra; //normalize the results
    newtime_usec=newtime_usec % 1000000;

    if (showlog)
      debug_log("newtime after normalization=%u: %u",newtime_sec, newtime_usec);



    tv->tv_sec=newtime_sec;
    tv->tv_usec=newtime_usec;


    if (gtod_speed!=speedhack_wantedspeed)
    {
      //the user wants to change the speed
      gtod_initialoffset_sec=newtime_sec;
      gtod_initialoffset_usec=newtime_usec;

      gtod_initialtime_sec=currenttime.tv_sec;
      gtod_initialtime_usec=currenttime.tv_usec;
      gtod_speed=speedhack_wantedspeed;
    }
   
    phase5=5;
  }

  leaveFunction();

    phase1=2;
  return r;
}
