Skip to content

Instantly share code, notes, and snippets.

@SamL98
Created June 6, 2019 00:01
Show Gist options
  • Save SamL98/0cd20b00951b9a5cca6b5c9380ec5642 to your computer and use it in GitHub Desktop.
Save SamL98/0cd20b00951b9a5cca6b5c9380ec5642 to your computer and use it in GitHub Desktop.
Hooks and accompanying code for Skiptracing
// MARK: SkipManager
#define NUM_SKIP_BYTES 10
#define BYTES_PER_LINE 34
#define SKIP_FILE_PATH "/Users/samlerner/Documents/Spotify/skipped.csv"
// SkipManager class to handle writing skips to files
class SkipManager {
public:
SkipManager();
void push(char * _Nonnull);
void pop();
private:
int bytesInHeader;
int numSkippedInSession;
int numSkipped;
void openSkipFile();
int readNumSkipped();
void writeNumSkipped();
};
FILE *_Nullable skipFile;
void close()
{
if (skipFile)
fclose(skipFile);
}
SkipManager::SkipManager()
{
this->bytesInHeader = 0;
this->numSkipped = 0;
this->numSkippedInSession = 0;
this->openSkipFile();
// Read the number of songs previously skipped, or writer 0 if the file is empty
this->numSkipped = this->readNumSkipped();
atexit(close);
}
void SkipManager::openSkipFile()
{
skipFile = fopen(SKIP_FILE_PATH, "r+");
if (!skipFile)
{
skipFile = fopen(SKIP_FILE_PATH, "w+");
if (!skipFile)
{
perror("Could not open skip file");
exit(-1);
}
}
}
int SkipManager::readNumSkipped()
{
if (!skipFile)
return 0;
char numSkipStr[NUM_SKIP_BYTES];
// If this read fails (the file is empty), then write 0 on the first line
if (!fgets(numSkipStr, NUM_SKIP_BYTES, skipFile))
{
this->writeNumSkipped();
return 0;
}
this->bytesInHeader = strlen(numSkipStr) + 1;
return atoi(numSkipStr);
}
void SkipManager::writeNumSkipped()
{
if (!skipFile)
return;
char skipStr[NUM_SKIP_BYTES];
sprintf(skipStr, "%d\n", this->numSkipped);
this->bytesInHeader = strlen(skipStr);
// Write the total number of skips to the beginning of the file
fseek(skipFile, 0, SEEK_SET);
fputs(skipStr, skipFile);
fflush(skipFile);
}
void SkipManager::push(char * _Nonnull tid)
{
if (!skipFile)
return;
// Increment both num skipped counters
++this->numSkippedInSession;
++this->numSkipped;
this->writeNumSkipped();
char line[BYTES_PER_LINE];
sprintf(line, "%s,%lu\n", tid, (unsigned long)time(NULL));
fseek(skipFile, this->bytesInHeader + (this->numSkipped - 1) * BYTES_PER_LINE, SEEK_SET);
fputs(line, skipFile);
fflush(skipFile);
}
void SkipManager::pop()
{
// Don't decrement the skipped counters if we haven't skipped any in this session
if (!skipFile || !this->numSkippedInSession)
return;
--this->numSkippedInSession;
--this->numSkipped;
this->writeNumSkipped();
fflush(skipFile);
}
// Global skipmanager
SkipManager skipman;
// MARK: AppleScript Hooks
// Hook to get the Spotify URL of the track pointed to by track_ptr
NSString*(*sub_1000EC4F0_caller)(void *track_ptr) = NULL;
static NSString *Hooked_sub_1000EC4F0(void *track_ptr)
{
NSString *url= sub_1000EC4F0_caller(track_ptr);
reset_hook(reinterpret_cast<void*>(Hooked_sub_1000EC4F0));
return url;
}
// Hook to get the player position
NSNumber*(*sub_1000EB760_caller)() = NULL;
static NSNumber *Hooked_sub_1000EB760()
{
NSNumber *position= sub_1000EB760_caller();
reset_hook(reinterpret_cast<void*>(Hooked_sub_1000EB760));
return position;
}
// Hook to get the duration of the current track
NSNumber*(*sub_1000EC430_caller)(void *track_ptr) = NULL;
static NSNumber *Hooked_sub_1000EC430(void *track_ptr)
{
NSNumber *duration= sub_1000EC430_caller(track_ptr);
reset_hook(reinterpret_cast<void*>(Hooked_sub_1000EC430));
return duration;
}
// Hook to get a pointer to the current track
void*(*sub_1000EB2B0_caller)() = NULL;
static void *Hooked_sub_1000EB2B0()
{
void *track_ptr = sub_1000EB2B0_caller();
reset_hook(reinterpret_cast<void*>(Hooked_sub_1000EB2B0));
return track_ptr;
}
// MARK: Helper functions
void getTID(char *tid) {
void *track_ptr = Hooked_sub_1000EB2B0();
NSString *track_url = Hooked_sub_1000EC4F0(track_ptr);
strncpy(tid, [track_url UTF8String] + 14, 22);
}
int getPlaybackPosition() {
NSNumber *position = Hooked_sub_1000EB760();
reset_hook(reinterpret_cast<void*>(Hooked_sub_1000EB760));
return [position intValue];
}
int getDuration() {
void *track_ptr = Hooked_sub_1000EB2B0();
NSNumber *durationMillis = Hooked_sub_1000EC430(track_ptr);
return (int)[durationMillis longValue]/1000;
}
BOOL shouldHandleMediaKey() {
int position = getPlaybackPosition();
int duration = getDuration();
return position <= duration/2;
}
// MARK: Playback hooks
// Next hook
void (*sub_100CC2E20_caller)(void *p1, void *p2, void *p3) = NULL;
static void Hooked_sub_100CC2E20(void *p1, void *p2, void *p3)
{
char tid[23];
getTID(tid);
BOOL shdSkip = shouldHandleMediaKey();
sub_100CC2E20_caller(p1, p2, p3);
reset_hook(reinterpret_cast<void*>(Hooked_sub_100CC2E20));
if (shdSkip && *tid)
skipman.push(tid);
}
// Prev hook
void (*sub_100CC2D20_caller)(void *p1, void *p2, void *p3) = NULL;
static void Hooked_sub_100CC2D20(void *p1, void *p2, void *p3)
{
char oldTid[23];
getTID(oldTid);
BOOL shdPop = shouldHandleMediaKey();
sub_100CC2D20_caller(p1, p2, p3);
reset_hook(reinterpret_cast<void*>(Hooked_sub_100CC2D20));
char newTid[23];
getTID(newTid);
if (shdPop && strncmp(oldTid, newTid, 22))
skipman.pop();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment