https://github.com/speedrun-program/load-extender
It’s a pair of programs for extending load times, one for Windows and one for Linux. The Windows version is written in C++, and the Linux version is written in C so it can work with LD_PRELOAD.
I'm mainly interested in making sure I'm not doing anything that could cause a crash since I know these languages have a lot of undefined behavior, but I'll try to fix any other problems that get pointed out too. I'm sure there are problems with how it's written because I have very limited coding experience, and this is the first thing I've ever written in C/C++.
Here's the code for Linux:
fopen_interceptor.c:
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <time.h>
#include <pthread.h>
#include "khash.h"
// thank you for making this hash map, attractivechaos.
// here's his github: https://github.com/attractivechaos
// these are global so fopen and khash_destruction can see them
static pthread_mutex_t lock;
static unsigned char LOCK_SUCCESS = 0; // used so it won't try to destroy lock if it failed to be made
static unsigned char SETUP_SUCCEEDED = 0; // if this is 0 then the fopen interception function skips to calling the real fopen function
static const int khStrInt = 32;
KHASH_MAP_INIT_STR(khStrInt, unsigned int*)
static khash_t(khStrInt) *my_khash_map;
#include "khash_setup_header.h" // included here so it recognizes kh_khStrInt_t
static void __attribute__((constructor)) khash_setup();
static void khash_setup()
{
my_khash_map = kh_init(khStrInt);
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("mutex init failed\n");
return;
}
LOCK_SUCCESS = 1; // LOCK_SUCCESS only changed here
FILE *my_file;
FILE *(*original_fopen)(const char*, const char*);
original_fopen = dlsym(RTLD_NEXT, "fopen");
my_file = original_fopen("./files_and_delays.txt", "r");
if (my_file == NULL)
{
printf("error opening files_and_delays.txt\n");
return;
}
// longest_name, longest_sequence, longest_delay, and line_count made here so they can be printed in fopen_interceptor_test
size_t longest_name = 1; // starts at 1 for null character
size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
unsigned char longest_delay = 1; // starts at 1 for null character
unsigned int line_count = 1; // starts at 1 because there's always at least 1 line. khash map largest value is UINT_MAX
unsigned char khash_succeeded = prepare_to_setup(my_khash_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
fclose(my_file);
if (khash_succeeded == 0) // khash failed to be made. Error message is printed in khash_setup_header in prepare_to_setup.
{
return;
}
SETUP_SUCCEEDED = 1; // SETUP_SUCCEEDED only changed here
}
static void __attribute__((destructor)) khash_destruction();
static void khash_destruction()
{
khiter_t khash_map_iter;
for (khash_map_iter = 0; khash_map_iter < kh_end(my_khash_map); ++khash_map_iter) // kh_destroy doesn't free keys and values, they need to be freed like this
{
if (kh_exist(my_khash_map, khash_map_iter))
{
free((char*)kh_key(my_khash_map, khash_map_iter));
kh_key(my_khash_map, khash_map_iter) = NULL;
free((unsigned int*)kh_val(my_khash_map, khash_map_iter));
kh_val(my_khash_map, khash_map_iter) = NULL;
}
}
kh_destroy(khStrInt, my_khash_map);
if (LOCK_SUCCESS == 1)
{
pthread_mutex_destroy(&lock);
}
}
FILE *fopen(const char *path, const char *mode)
{
if (SETUP_SUCCEEDED == 1) // skip to calling original fopen if setup failed
{
short slash_index = -1;
for (unsigned short i = 0; path[i] != '\0'; i++) // look for start of filename by finding last occurence of '/' character
{
if (path[i] == '/')
{
slash_index = i % SHRT_MAX; // will cause key to not be found, but prevents unsigned int overflow
}
}
// +1 so '/' isn't included. If '/' not found in path, slash_index stays -1 and entire path argument is checked as key since "path + -1 + 1" is the same as just "path"
khiter_t khash_map_iter = kh_get(khStrInt, my_khash_map, path + slash_index + 1);
if (khash_map_iter != kh_end(my_khash_map)) // key found
{
unsigned int *delay_sequence = kh_val(my_khash_map, khash_map_iter);
pthread_mutex_lock(&lock); // don't let other threads change delay sequence when it's being read or changed
if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
{
unsigned int *sequence_to_reset;
kh_foreach_value(my_khash_map, sequence_to_reset, {sequence_to_reset[1] = 2;});
pthread_mutex_unlock(&lock);
}
else if (delay_sequence[1] < delay_sequence[0])
{
if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
{
delay_sequence[1] = 2;
}
unsigned int sleep_time = delay_sequence[delay_sequence[1]];
delay_sequence[1] += 1;
pthread_mutex_unlock(&lock);
long time_nanoseconds = (sleep_time % 1000) * 1000000;
struct timespec ts = {sleep_time / 1000, time_nanoseconds};
nanosleep(&ts, NULL);
}
else // delay sequence already finished
{
pthread_mutex_unlock(&lock);
}
}
}
FILE *(*original_fopen)(const char*, const char*);
original_fopen = dlsym(RTLD_NEXT, "fopen");
return (*original_fopen)(path, mode);
}
fopen_interceptor_test.c:
#define _GNU_SOURCE
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include "khash.h"
// thank you for making this hash map, attractivechaos.
// here's his github: https://github.com/attractivechaos
// these are global so fopen and khash_destruction can see them
static pthread_mutex_t lock;
static unsigned char LOCK_SUCCESS = 0; // used so it won't try to destroy lock if it failed to be made
static unsigned char SETUP_SUCCEEDED = 0; // if this is 0 then the fopen interception function skips to calling the real fopen function
static const int khStrInt = 32;
KHASH_MAP_INIT_STR(khStrInt, unsigned int*)
static khash_t(khStrInt) *my_khash_map;
#include "khash_setup_header.h" // included here so it recognizes kh_khStrInt_t
static void __attribute__((constructor)) khash_setup();
static void khash_setup()
{
my_khash_map = kh_init(khStrInt);
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("mutex init failed\n");
return;
}
LOCK_SUCCESS = 1; // LOCK_SUCCESS only changed here
FILE *my_file;
/*FILE *(*original_fopen)(const char*, const char*);
original_fopen = dlsym(RTLD_NEXT, "fopen");
my_file = original_fopen("./files_and_delays.txt", "r");*/
my_file = fopen("./files_and_delays.txt", "r");
if (my_file == NULL)
{
printf("error opening files_and_delays.txt\n");
return;
}
size_t longest_name = 1; // starts at 1 for null character
size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
unsigned char longest_delay = 1; // starts at 1 for null character
unsigned int line_count = 1; // starts at 1 because there's always at least 1 line. khash map largest value is UINT_MAX
unsigned char khash_succeeded = prepare_to_setup(my_khash_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
fclose(my_file);
if (khash_succeeded == 0) // khash failed to be made. Error message is printed in khash_setup_header in prepare_to_setup.
{
return;
}
printf("\nlongest filename (plus null character and whitespace in file): %zu\n"
"longest delay sequence (+2 for current delay and sequence length): %zu\n"
"most digits in delay (+1 for null character, max delay is UINT_MAX): %u\n"
"lines in file (overestimates slightly): %u\n"
"khash map size (lines should be less than 70%% of this): %zu\n", longest_name, longest_sequence, longest_delay, line_count, kh_n_buckets(my_khash_map));
SETUP_SUCCEEDED = 1; // SETUP_SUCCEEDED only changed here
}
static void __attribute__((destructor)) khash_destruction();
static void khash_destruction()
{
khiter_t khash_map_iter;
for (khash_map_iter = 0; khash_map_iter < kh_end(my_khash_map); ++khash_map_iter) // kh_destroy doesn't free keys and values, they need to be freed like this
{
if (kh_exist(my_khash_map, khash_map_iter))
{
free((char*)kh_key(my_khash_map, khash_map_iter));
kh_key(my_khash_map, khash_map_iter) = NULL;
free((unsigned int*)kh_val(my_khash_map, khash_map_iter));
kh_val(my_khash_map, khash_map_iter) = NULL;
}
}
kh_destroy(khStrInt, my_khash_map);
if (LOCK_SUCCESS == 1)
{
pthread_mutex_destroy(&lock);
}
}
static void fopen_test(const char *path, const char *mode)
{
if (SETUP_SUCCEEDED == 1) // skip to calling original fopen if setup failed
{
short slash_index = -1;
for (unsigned short i = 0; path[i] != '\0'; i++) // look for start of filename by finding last occurence of '/' character
{
if (path[i] == '/')
{
slash_index = i % SHRT_MAX; // will cause key to not be found, but prevents unsigned int overflow
}
}
// +1 so '/' isn't included. If '/' not found in path, slash_index stays -1 and entire path argument is checked as key since "path + -1 + 1" is the same as just "path"
khiter_t khash_map_iter = kh_get(khStrInt, my_khash_map, path + slash_index + 1);
if (khash_map_iter != kh_end(my_khash_map)) // key found
{
unsigned int *delay_sequence = kh_val(my_khash_map, khash_map_iter);
printf("%s successfully found in khash map\n", path + slash_index + 1);
pthread_mutex_lock(&lock); // don't let other threads change delay sequence when it's being read or changed
if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
{
unsigned int *sequence_to_reset;
kh_foreach_value(my_khash_map, sequence_to_reset, {sequence_to_reset[1] = 2;});
pthread_mutex_unlock(&lock);
printf("all delay sequences reset\n");
}
else if (delay_sequence[1] < delay_sequence[0])
{
if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
{
delay_sequence[1] = 2;
printf("%s delay sequence reset\n", path + slash_index + 1);
}
unsigned int sleep_time = delay_sequence[delay_sequence[1]];
delay_sequence[1] += 1;
pthread_mutex_unlock(&lock);
long time_nanoseconds = (sleep_time % 1000) * 1000000;
struct timespec ts = {sleep_time / 1000, time_nanoseconds};
//nanosleep(&ts, NULL);
printf("sleep for %ld second(s) and %ld millisecond(s)\n", ts.tv_sec, ts.tv_nsec / 1000000);
}
else // delay sequence already finished
{
pthread_mutex_unlock(&lock);
printf("%s delay sequence already finished\n", path + slash_index + 1);
}
}
else // key not found
{
printf("%s not found in khash map\n", path + slash_index + 1);
}
}
/*FILE *(*original_fopen)(const char*, const char*);
original_fopen = dlsym(RTLD_NEXT, "fopen");
return (*original_fopen)(path, mode);*/
}
static size_t find_longest_input_length(FILE *test_input) // find how much space to give to array used to hold input
{
size_t longest_input = 1;
size_t current_input = 1; // starts at 1 for null character
char ch = '\0';
while (ch != EOF && current_input < SIZE_MAX)
{
ch = fgetc(test_input);
if (ch == '\n' || ch == EOF)
{
if (current_input > longest_input)
{
longest_input = current_input;
}
current_input = 1;
}
else
{
current_input += 1;
}
}
if (current_input == SIZE_MAX)
{
return SIZE_MAX; // a test path was too big
}
return longest_input;
}
static void print_khash()
{
printf("\n---------- khash map current state ----------\n");
const char *key_to_print;
unsigned int *sequence_to_print;
kh_foreach(my_khash_map, key_to_print, sequence_to_print,
{
printf("%s / ", key_to_print);
for (unsigned int i = 0; i < sequence_to_print[0]; i++)
{
if (sequence_to_print[i] == UINT_MAX) // it's a reset point
{
printf("RESET"); // it doesn't need a space because the line always ends here
}
else
{
printf("%u ", sequence_to_print[i]);
}
}
printf("\n");
});
printf("---------------------------------------------\n\n");
}
static void test_all_inputs(FILE *test_input, size_t longest_input)
{
char *input_array = (char*)malloc(longest_input); // find_longest_input_length accounts for null character. Always greater than 0, shouldn't return NULL
if (input_array == NULL)
{
printf("malloc failed in test_all_inputs\n");
return;
}
size_t ch_position = 0;
char ch = '\0';
while (ch != EOF)
{
ch = fgetc(test_input);
if (ch == '\n' || ch == EOF)
{
input_array[ch_position] = '\0';
ch_position = 0;
if (ch != EOF) // avoids extra test
{
printf("testing fopen input: %s\n", input_array);
fopen_test(input_array, "r");
print_khash();
}
}
else
{
input_array[ch_position] = ch;
ch_position += 1;
}
}
free((char*)input_array);
input_array = NULL;
}
int main()
{
FILE *test_input;
test_input = fopen("./test_input.txt", "r");
if (test_input == NULL)
{
printf("error opening test_input.txt\n");
return 1;
}
size_t longest_input = find_longest_input_length(test_input);
if (longest_input == SIZE_MAX) // check_file will return SIZE_MAX if a test path is too big
{
printf("a test path is too big\n");
fclose(test_input);
return 1;
}
if (fseek(test_input, 0, SEEK_SET) != 0)
{
printf("fseek on test_input.txt failed\n");
fclose(test_input);
return 1;
}
printf("\nlongest test path (+1 for null character): %zu\n", longest_input);
printf("\ntest start\n");
print_khash();
test_all_inputs(test_input, longest_input);
fclose(test_input);
printf("test finish\n\n");
return 0;
}
khash_setup_header.h:
#include <stdint.h>
static unsigned char TOO_BIG_DELAY_SEEN = 0; // used so user won't be alerted more than once that they entered a delay that's too big
// checks line count to know how much space to give to khash
// updates longest_name and longest_sequence to know how much space to give to arrays used to hold key and value
static void check_file(FILE *my_file, size_t *longest_name, size_t *longest_sequence, unsigned char *longest_delay, unsigned int *line_count, unsigned char digits_in_uint)
{
unsigned char checking_sequence = 0; // 0 if checking a filename's length, 1 if checking a delay sequence's length
size_t current_name = 1; // starts at 1 for null character
size_t current_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
unsigned char current_delay = 1; // starts at 1 for null character
char ch = '\0';
// longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
while (ch != EOF && current_name < SIZE_MAX && current_sequence < (SIZE_MAX / sizeof(unsigned int)) && *line_count < UINT_MAX && current_sequence < UINT_MAX)
{
ch = fgetc(my_file);
if (ch == EOF || ch == '\n')
{
if (checking_sequence == 1)
{
if (current_delay > *longest_delay)
{
*longest_delay = current_delay;
}
if (current_sequence > *longest_sequence)
{
*longest_sequence = current_sequence;
}
checking_sequence = 0;
current_sequence = 3;
current_delay = 1;
}
*line_count += 1; // if for some reason there are a lot of hash collisions, the user can put empty lines in files_and_delays.txt to try to fix it
current_name = 1;
}
else if (ch == '/')
{
if (checking_sequence == 0)
{
if (current_name > *longest_name)
{
*longest_name = current_name;
}
checking_sequence = 1;
}
else
{
if (current_delay > *longest_delay)
{
*longest_delay = current_delay;
}
current_sequence += 1;
current_delay = 1;
}
}
else if (checking_sequence == 0)
{
current_name += 1;
}
// ignores non-digits and leading zeros. maximum space is digits_in_uint + 1. +1 is for null character
else if (((ch >= 49 && ch <= 57) || (ch == 48 && current_delay > 1)) && current_delay < digits_in_uint + 1)
{
current_delay += 1;
}
}
if (current_name == SIZE_MAX || current_sequence == (SIZE_MAX / sizeof(unsigned int)) || current_sequence == UINT_MAX)
{
*line_count = UINT_MAX; // something was too big
}
}
// reads the next filename and writes it in name_array
static size_t read_name(FILE *my_file, char *ch, char *name_array)
{
size_t ch_position = 0;
*ch = fgetc(my_file);
while (*ch != EOF && *ch != '\n' && *ch != '/')
{
name_array[ch_position] = *ch;
ch_position += 1;
*ch = fgetc(my_file);
}
name_array[ch_position] = '\0';
if (*ch == '/') // filename given and line didn't end abruptly
{
for (; ch_position > 0 && (name_array[ch_position - 1] == ' ' || name_array[ch_position - 1] == '\t'); ch_position--)
{
name_array[ch_position - 1] = '\0'; // remove whitespace between '/' and end of filename if the user has it written like that
}
}
return ch_position + 1; // +1 for null character
}
static unsigned int my_atoi(char *delay_array, unsigned int max_power_of_ten, unsigned char digits_in_uint, unsigned char number_position)
{
unsigned char delay_too_big = 0;
unsigned int current_power_of_ten = 1;
unsigned int accumulator = 0;
if (delay_array[0] == '\0') // no digits found, assume delay is 0
{
return 0;
}
// delay has more digits than UINT_MAX or it has as many digits and the most significant digit is bigger in the delay than in UINT_MAX
else if (delay_array[0] == '+' || (number_position == digits_in_uint && delay_array[0] - 48 > UINT_MAX / max_power_of_ten))
{
delay_too_big = 1;
}
else
{
number_position -= 1;
for (; number_position > 0; number_position--) // sum everything except most significant digit
{
accumulator += ((delay_array[number_position] - 48) * current_power_of_ten);
current_power_of_ten *= 10;
}
// checking that adding final value won't make accumulator overflow
// delay_array[0] won't be bigger than UINT_MAX / max_power_of_ten here because the other else if block checks for that
if (current_power_of_ten == max_power_of_ten && UINT_MAX - ((delay_array[0] - 48) * max_power_of_ten) - 1 < accumulator)
{
delay_too_big = 1;
}
else
{
accumulator += ((delay_array[0] - 48) * current_power_of_ten);
}
}
if (delay_too_big == 1)
{
if (TOO_BIG_DELAY_SEEN == 0)
{
printf("\nWARNING: maximum delay time is %u\n", UINT_MAX - 1);
TOO_BIG_DELAY_SEEN = 1; // TOO_BIG_DELAY_SEEN only changed here
}
return UINT_MAX - 1;
}
return accumulator;
}
// reads the next delay sequence and writes it in sequence_array
// also returns how many numbers are in the sequence so the right amount will be copied into the array given to khash
// there can't be more than UINT_MAX delays in one sequence
static unsigned int read_sequence(FILE *my_file, char *ch, char *delay_array, unsigned int *sequence_array, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
unsigned char number_position = 0; // next digit in current delay time. Also used in my_atoi to see if delay is too big
size_t sequence_position = 2; // starts at 2 because first two indexes are used to store sequence length and what the next delay is
delay_array[0] = '\0'; // initializing to use in condition
while (*ch != EOF && *ch != '\n')
{
*ch = fgetc(my_file);
if (*ch == EOF || *ch == '\n')
{
delay_array[number_position] = '\0';
unsigned int last_delay = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
if (last_delay != 0) // no point adding 0 as final delay time
{
sequence_array[sequence_position] = last_delay;
sequence_position += 1;
}
}
else if (*ch == '/') // end of current delay, about to read next delay
{
delay_array[number_position] = '\0';
sequence_array[sequence_position] = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
sequence_position += 1;
number_position = 0;
}
// ch is a digit and the digit isn't a leading zero and the maximum delay isn't reached
else if (((*ch >= 49 && *ch <= 57) || (*ch == 48 && number_position > 0)) && delay_array[0] != '+')
{
if (number_position != digits_in_uint) // if it's already equal to digits_in_uint then the delay is too big
{
delay_array[number_position] = *ch;
number_position += 1;
}
else
{
delay_array[0] = '+'; // '+' used to check if the delay is too big in my_atoi
}
}
else if (*ch == '-')
{
sequence_array[sequence_position] = UINT_MAX; // used for saying reset should happen
sequence_position += 1;
break; // no point checking delay times beyond where reset happens
}
}
while (*ch != EOF && *ch != '\n') // finish reading line if it's not already read
{
*ch = fgetc(my_file);
}
if (sequence_array[sequence_position - 1] != UINT_MAX) // numbers entered and not a reset-all-sequences file
{
for (unsigned int i = sequence_position - 1; i > 1 && sequence_array[i] == 0; i--) // don't copy pointless zeros
{
sequence_position -= 1;
}
}
return sequence_position;
}
static unsigned char set_keys_and_values(kh_khStrInt_t *my_khash_map, FILE *my_file, size_t longest_name, size_t longest_sequence, unsigned char longest_delay, unsigned int line_count, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
khiter_t khash_map_iter;
char *name_array = (char*)malloc(longest_name); // array for filename text. Always greater than 0, shouldn't return NULL.
unsigned int *sequence_array = (unsigned int*)malloc(longest_sequence * sizeof(unsigned int)); // array for delay sequence. Always greater than 0, shouldn't return NULL.
char *delay_array = (char*)malloc(longest_delay); // array for delay text. Always greater than 0, shouldn't return NULL.
if (name_array == NULL || sequence_array == NULL || delay_array == NULL)
{
return 0;
}
if ((size_t)(UINT_MAX / 2) < line_count) // khash resizes by powers of two, so if line_count is this big then khash can't resize any bigger
{
kh_resize(khStrInt, my_khash_map, UINT_MAX);
}
else
{
kh_resize(khStrInt, my_khash_map, (unsigned int)((line_count / 0.7) + 1)); // khash map will always be at 70% or less capacity
}
sequence_array[0] = 2; // first index stores length of array
sequence_array[1] = 2; // second index stores what the next delay to use in the sequence is
size_t chars_to_copy;
unsigned int numbers_to_copy;
char ch = '\0';
while (ch != EOF)
{
chars_to_copy = read_name(my_file, &ch, name_array); // accounts for null character
if (ch != EOF && ch != '\n') // checks if the line had a delay sequence. If it didn't, moves on to next line
{
numbers_to_copy = read_sequence(my_file, &ch, delay_array, sequence_array, max_power_of_ten, digits_in_uint);
if (numbers_to_copy > 2) // read_sequence returns 2 if either the sequence was pointless or the user didn't enter anything for it
{
int ret;
sequence_array[0] = numbers_to_copy;
char *key_for_khash = (char*)malloc(chars_to_copy); // always greater than 0, shouldn't return NULL
unsigned int *value_for_khash = (unsigned int*)malloc(numbers_to_copy * sizeof(unsigned int)); // always greater than 0, shouldn't return NULL
if (key_for_khash == NULL || value_for_khash == NULL)
{
return 0;
}
for (size_t i = 0; i < chars_to_copy; i++)
{
key_for_khash[i] = name_array[i];
}
for (unsigned int i = 0; i < numbers_to_copy; i++)
{
value_for_khash[i] = sequence_array[i];
}
khash_map_iter = kh_put(khStrInt, my_khash_map, key_for_khash, &ret);
kh_val(my_khash_map, khash_map_iter) = value_for_khash;
}
}
sequence_array[0] = 2;
sequence_array[1] = 2;
}
free((char*)name_array);
name_array = NULL;
free((unsigned int*)sequence_array);
sequence_array = NULL;
free((char*)delay_array);
delay_array = NULL;
return 1;
}
static unsigned char prepare_to_setup(kh_khStrInt_t *my_khash_map, FILE *my_file, size_t *longest_name, size_t *longest_sequence, unsigned char *longest_delay, unsigned int *line_count)
{
unsigned int max_power_of_ten = 1; // biggest power of 10 less than UINT_MAX, used in my_atoi
unsigned char digits_in_uint = 1; // how many digits are in UINT_MAX, used for helping to see if a delay is too big
for (; UINT_MAX / max_power_of_ten > 9; max_power_of_ten *= 10)
{
digits_in_uint += 1;
}
check_file(my_file, longest_name, longest_sequence, longest_delay, line_count, digits_in_uint);
if (fseek(my_file, 0, SEEK_SET) != 0)
{
printf("fseek on files_and_delays.txt failed\n");
return 0;
}
// longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
if (*line_count == UINT_MAX) // check_file will return UINT_MAX if something is too big
{
printf("filename, delay, delay sequence, and/or number of files is too big\n");
return 0;
}
unsigned char khash_succeeded = set_keys_and_values(my_khash_map, my_file, *longest_name, *longest_sequence, *longest_delay, *line_count, max_power_of_ten, digits_in_uint);
if (khash_succeeded == 0) // set_keys_and_values returns 0 if malloc fails
{
printf("malloc failed in set_keys_and_values\n");
return 0;
}
return 1;
}
Here's the code for Windows:
load_extender_exe.cpp:
#include <tchar.h>
#include <iostream>
#include <string>
#include <Windows.h>
#include <easyhook.h>
void get_exit_input()
{
std::wcout << "Press Enter to exit";
std::wstring input;
std::getline(std::wcin, input);
}
int _tmain(int argc, _TCHAR* argv[])
{
WCHAR* dllToInject32 = NULL;
WCHAR* dllToInject64 = NULL;
LPCWSTR lpApplicationName = argv[0];
DWORD lpBinaryType;
if (GetBinaryType(lpApplicationName, &lpBinaryType) == 0 || (lpBinaryType != 0 && lpBinaryType != 6))
{
std::wcout << "ERROR: This exe wasn't identified as 32-bit or as 64-bit";
get_exit_input();
return 1;
}
else if (lpBinaryType == 0)
{
dllToInject32 = (WCHAR*)L"load_extender_32.dll";
}
else
{
dllToInject64 = (WCHAR*)L"load_extender_64.dll";
}
DWORD processId;
std::wcout << "Enter the target process Id: ";
std::cin >> processId;
wprintf(L"Attempting to inject dll\n\n");
// Inject dllToInject into the target process Id, passing
// freqOffset as the pass through data.
NTSTATUS nt = RhInjectLibrary(
processId, // The process to inject into
0, // ThreadId to wake up upon injection
EASYHOOK_INJECT_DEFAULT,
dllToInject32, // 32-bit
dllToInject64, // 64-bit
NULL, // data to send to injected DLL entry point
0 // size of data to send
);
if (nt != 0)
{
printf("RhInjectLibrary failed with error code = %d\n", nt);
PWCHAR err = RtlGetLastErrorString();
std::wcout << err << "\n";
get_exit_input();
return 1;
}
else
{
std::wcout << L"Library injected successfully.\n";
}
get_exit_input();
return 0;
}
load_extender_dll.cpp:
#include <string>
#include <mutex>
#include <Windows.h>
#include <easyhook.h>
#include "robin_hood.h"
// thank you for making this hash map, martinus.
// here's his github: https://github.com/martinus/robin-hood-hashing
namespace rh = robin_hood;
// included here so alias can be used
#include "rh_map_setup_header.h"
static class rh_map_handler // this is used to free all the unsigned int array memory when the program ends
{
public:
rh::unordered_flat_map<std::wstring, unsigned int*> obj_map;
~rh_map_handler()
{
for (auto it = obj_map.begin(); it != obj_map.end();)
{
free((unsigned int*)it->second);
it->second = NULL;
it = obj_map.erase(it); // erasing so obj_map's destructor doesn't have to do as much
}
}
};
// my_map_handler is global so it will free all the unsigned int arrays when the program ends
// my_rh_map and mtx are global so the NtOpenFile hook function can see them
static rh_map_handler my_map_handler;
static rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map = my_map_handler.obj_map;
static std::mutex mtx;
static unsigned char rh_map_setup()
{
FILE* my_file = NULL;
if (fopen_s(&my_file, ".\\files_and_delays.txt", "r") != 0)
{
return 0;
}
size_t longest_name = 1; // starts at 1 for null character
size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
unsigned char longest_delay = 1; // starts at 1 for null character
size_t line_count = 1; // starts at 1 because there's always at least 1 line
unsigned char rh_map_succeeded = prepare_to_setup(my_rh_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
if (my_file != NULL) // this makes a warning go away
{
fclose(my_file);
}
if (rh_map_succeeded == 0) // rh map failed to be made. Error message is printed in rh_map_setup_header in prepare_to_setup.
{
return 0;
}
return 1;
}
static unsigned char SETUP_SUCCEEDED = rh_map_setup(); // if this is 0 then the hook function skips to calling the real function
static NTSTATUS WINAPI NtOpenFileHook(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions) {
if (SETUP_SUCCEEDED == 1) // skip to calling original NtOpenProcess if setup failed
{
std::wstring file_path = ObjectAttributes->ObjectName->Buffer;
// +1 so '\' isn't included. If '\' not found in path, the whole wstring is checked because npos is -1
std::wstring file_name = file_path.substr(file_path.rfind(L"\\") + 1);
rh::unordered_flat_map<std::wstring, unsigned int*>::const_iterator rh_map_iter = my_rh_map.find(file_name);
if (rh_map_iter != my_rh_map.end()) // key found
{
unsigned int* delay_sequence = rh_map_iter->second;
mtx.lock(); // don't let other threads change delay sequence when it's being read or changed
if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
{
for (auto& it : my_rh_map)
{
it.second[1] = 2;
}
mtx.unlock();
}
else if (delay_sequence[1] < delay_sequence[0])
{
if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
{
delay_sequence[1] = 2;
}
unsigned int sleep_time = delay_sequence[delay_sequence[1]];
delay_sequence[1] += 1;
mtx.unlock();
Sleep(sleep_time);
}
else // delay sequence already finished
{
mtx.unlock();
}
}
}
return NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
}
extern "C" void __declspec(dllexport) __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO * inRemoteInfo);
void __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO* inRemoteInfo) {
HOOK_TRACE_INFO hHook1 = { NULL };
LhInstallHook(
GetProcAddress(GetModuleHandle(TEXT("ntdll")), "NtOpenFile"),
NtOpenFileHook,
NULL,
&hHook1);
ULONG ACLEntries[1] = { 0 };
LhSetExclusiveACL(ACLEntries, 1, &hHook1);
return;
}
load_extender_test.cpp:
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include <string>
#include <mutex>
#include "robin_hood.h"
// thank you for making this hash map, martinus.
// here's his github: https://github.com/martinus/robin-hood-hashing
namespace rh = robin_hood;
// included here so alias can be used
#include "rh_map_setup_header.h"
static class rh_map_handler // this is used to free all the unsigned int array memory when the program ends
{
public:
rh::unordered_flat_map<std::wstring, unsigned int*> obj_map;
~rh_map_handler()
{
for (auto it = obj_map.begin(); it != obj_map.end();)
{
free((unsigned int*)it->second);
it->second = NULL;
it = obj_map.erase(it); // erasing so obj_map's destructor doesn't have to do as much
}
}
};
// my_map_handler is global so it will free all the unsigned int arrays when the program ends
// my_rh_map and mtx are global so the NtOpenFile hook function can see them
static rh_map_handler my_map_handler;
static rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map = my_map_handler.obj_map;
static std::mutex mtx;
static unsigned char rh_map_setup()
{
FILE* my_file = NULL;
if (fopen_s(&my_file, ".\\files_and_delays.txt", "r") != 0)
{
printf("error opening files_and_delays.txt\n");
return 0;
}
size_t longest_name = 1; // starts at 1 for null character
size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
unsigned char longest_delay = 1; // starts at 1 for null character
size_t line_count = 1; // starts at 1 because there's always at least 1 line
unsigned char rh_map_succeeded = prepare_to_setup(my_rh_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
if (my_file != NULL) // this makes a warning go away
{
fclose(my_file);
}
if (rh_map_succeeded == 0) // rh map failed to be made. Error message is printed in rh_map_setup_header in prepare_to_setup.
{
return 0;
}
printf("\nlongest filename (plus null character and whitespace in file): %zu\n"
"longest delay sequence (+2 for current delay and sequence length): %zu\n"
"most digits in delay (+1 for null character, max delay is UINT_MAX): %u\n"
"lines in file (overestimates slightly): %zu\n", longest_name, longest_sequence, longest_delay, line_count);
return 1;
}
static unsigned char SETUP_SUCCEEDED = rh_map_setup(); // if this is 0 then the hook function skips to calling the real function
static void NtOpenFile_test(std::wstring file_path)
{
if (SETUP_SUCCEEDED == 1) // skip to calling original NtOpenFile if setup failed
{
// +1 so '\' isn't included. If '\' not found in path, the whole wstring is checked because npos is -1
std::wstring file_name = file_path.substr(file_path.rfind(L"\\") + 1);
rh::unordered_flat_map<std::wstring, unsigned int*>::const_iterator rh_map_iter = my_rh_map.find(file_name);
if (rh_map_iter != my_rh_map.end()) // key found
{
unsigned int* delay_sequence = rh_map_iter->second;
printf("%ls successfully found in hash map\n", file_name.c_str());
mtx.lock(); // don't let other threads change delay sequence when it's being read or changed
if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
{
for (auto& it : my_rh_map)
{
it.second[1] = 2;
}
mtx.unlock();
printf("all delay sequences reset\n");
}
else if (delay_sequence[1] < delay_sequence[0])
{
if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
{
delay_sequence[1] = 2;
printf("%ls delay sequence reset\n", file_name.c_str());
}
unsigned int sleep_time = delay_sequence[delay_sequence[1]];
delay_sequence[1] += 1;
mtx.unlock();
//Sleep(sleep_time);
printf("sleep for %u second(s) and %u millisecond(s)\n", sleep_time / 1000, sleep_time % 1000);
}
else // delay sequence already finished
{
mtx.unlock();
printf("%ls delay sequence already finished\n", file_name.c_str());
}
}
else // key not found
{
printf("%ls not found in khash map\n", file_name.c_str());
}
}
// NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
}
static size_t find_longest_input_length(FILE* test_input) // find how much space to give to array used to hold input
{
size_t longest_input = 1;
size_t current_input = 1; // starts at 1 for null character
wchar_t ch = L'\0';
while (ch != WEOF && current_input < SIZE_MAX)
{
ch = fgetwc(test_input);
if (ch == L'\n' || ch == WEOF)
{
if (current_input > longest_input)
{
longest_input = current_input;
}
current_input = 1;
}
else
{
current_input += 1;
}
}
if (current_input == SIZE_MAX)
{
return SIZE_MAX; // a test path was too big
}
return longest_input;
}
static void print_rh_map()
{
printf("\n---------- khash map current state ----------\n");
for (auto& it : my_rh_map)
{
printf("%ls / ", it.first.c_str());
unsigned int* sequence_to_print = it.second;
for (unsigned int i = 0; i < sequence_to_print[0]; i++)
{
if (sequence_to_print[i] == UINT_MAX) // it's a reset point
{
printf("RESET"); // it doesn't need a space because the line always ends here
}
else
{
printf("%u ", sequence_to_print[i]);
}
}
printf("\n");
}
printf("---------------------------------------------\n\n");
}
static void test_all_inputs(FILE* test_input, size_t longest_input)
{
wchar_t* input_array = (wchar_t*)malloc(longest_input * sizeof(wchar_t)); // find_longest_input_length accounts for null character. Always greater than 0, shouldn't return NULL
if (input_array == NULL)
{
printf("malloc failed in test_all_inputs\n");
return;
}
size_t ch_position = 0;
wchar_t ch = L'\0';
while (ch != WEOF)
{
ch = fgetwc(test_input);
if (ch == L'\n' || ch == WEOF)
{
input_array[ch_position] = L'\0';
ch_position = 0;
if (ch != WEOF) // avoids extra test
{
printf("testing NtOpenFile input: %ls\n", input_array);
NtOpenFile_test((std::wstring)input_array);
print_rh_map();
}
}
else
{
input_array[ch_position] = ch;
ch_position += 1;
}
}
free((wchar_t*)input_array);
input_array = NULL;
}
static void get_exit_input()
{
printf("Press Enter to exit\n");
std::wstring input;
std::getline(std::wcin, input);
}
static int _tmain(int argc, _TCHAR* argv[])
{
LPCWSTR lpApplicationName = argv[0];
DWORD lpBinaryType;
if (GetBinaryType(lpApplicationName, &lpBinaryType) == 0)
{
printf("\nERROR: couldn't read if this file is 32 bit or 64 bit\n");
get_exit_input();
return 1;
}
else if (lpBinaryType != 0)
{
printf("\nError: this file was incorrectly identified as not 32-bit\n");
get_exit_input();
return 1;
}
else
{
printf("\nthis file was correctly identified as 32-bit\n");
}
FILE* test_input;
if (fopen_s(&test_input, ".\\test_input.txt", "r") != 0)
{
printf("error opening test_input.txt\n");
get_exit_input();
return 1;
}
size_t longest_input = find_longest_input_length(test_input);
if (longest_input == SIZE_MAX) // check_file will return SIZE_MAX if a test path is too big
{
printf("a test path is too big\n");
if (test_input != NULL) // this makes a warning go away
{
fclose(test_input);
}
get_exit_input();
return 1;
}
// test_input != NULL check makes warning go away
if (test_input != NULL && fseek(test_input, 0, SEEK_SET) != 0)
{
printf("fseek on test_input.txt failed\n");
if (test_input != NULL) // this makes a warning go away
{
fclose(test_input);
}
get_exit_input();
return 1;
}
printf("\nlongest test path (+1 for null character): %zu\n", longest_input);
printf("\ntest start\n");
print_rh_map();
test_all_inputs(test_input, longest_input);
if (test_input != NULL) // this makes a warning go away
{
fclose(test_input);
}
get_exit_input();
return 0;
}
rh_map_setup_header:
#include <stdint.h>
// printf calls are used for debugging in load_extender_test.exe
static unsigned char TOO_BIG_DELAY_SEEN = 0; // used so user won't be alerted more than once that they entered a delay that's too big
// checks line count to know how much space to give to robin_hood map
// updates longest_name and longest_sequence to know how much space to give to arrays used to hold key and value
static void check_file(FILE* my_file, size_t* longest_name, size_t* longest_sequence, unsigned char* longest_delay, size_t* line_count, unsigned char digits_in_uint, size_t map_max_size)
{
unsigned char checking_sequence = 0; // 0 if checking a filename's length, 1 if checking a delay sequence's length
size_t current_name = 1; // starts at 1 for null character
size_t current_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
unsigned char current_delay = 1; // starts at 1 for null character
wchar_t ch = L'\0';
// longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
while (ch != WEOF && current_name < SIZE_MAX && current_sequence < (SIZE_MAX / sizeof(unsigned int)) && *line_count < SIZE_MAX && current_sequence < UINT_MAX)
{
ch = fgetwc(my_file);
if (ch == WEOF || ch == L'\n')
{
if (checking_sequence == 1)
{
if (current_delay > * longest_delay)
{
*longest_delay = current_delay;
}
if (current_sequence > * longest_sequence)
{
*longest_sequence = current_sequence;
}
checking_sequence = 0;
current_sequence = 3;
current_delay = 1;
}
*line_count += 1; // if for some reason there are a lot of hash collisions, the user can put empty lines in files_and_delays.txt to try to fix it
current_name = 1;
}
else if (ch == L'/')
{
if (checking_sequence == 0)
{
if (current_name > * longest_name)
{
*longest_name = current_name;
}
checking_sequence = 1;
}
else
{
if (current_delay > * longest_delay)
{
*longest_delay = current_delay;
}
current_sequence += 1;
current_delay = 1;
}
}
else if (checking_sequence == 0)
{
current_name += 1;
}
// ignores non-digits and leading zeros. maximum space is digits_in_uint + 1. +1 is for null character
else if (((ch >= 49 && ch <= 57) || (ch == 48 && current_delay > 1)) && current_delay < digits_in_uint + 1)
{
current_delay += 1;
}
}
if (current_name == SIZE_MAX ||
current_sequence == (SIZE_MAX / sizeof(unsigned int)) ||
current_sequence == UINT_MAX
|| *line_count > map_max_size)
{
*line_count = SIZE_MAX; // something was too big
}
}
// reads the next filename and writes it in name_array
static void read_name(FILE* my_file, wchar_t* ch, wchar_t* name_array)
{
size_t ch_position = 0;
*ch = fgetwc(my_file);
while (*ch != WEOF && *ch != L'\n' && *ch != L'/')
{
name_array[ch_position] = *ch;
ch_position += 1;
*ch = fgetwc(my_file);
}
name_array[ch_position] = L'\0';
if (*ch == L'/') // filename given and line didn't end abruptly
{
for (; ch_position > 0 && (name_array[ch_position - 1] == L' ' || name_array[ch_position - 1] == L'\t'); ch_position--)
{
name_array[ch_position - 1] = L'\0'; // remove whitespace between '/' and end of filename if the user has it written like that
}
}
}
static unsigned int my_atoi(wchar_t* delay_array, unsigned int max_power_of_ten, unsigned char digits_in_uint, unsigned char number_position)
{
unsigned char delay_too_big = 0;
unsigned int current_power_of_ten = 1;
unsigned int accumulator = 0;
if (delay_array[0] == L'\0') // no digits found, assume delay is 0
{
return 0;
}
// delay has more digits than UINT_MAX or it has as many digits and the most significant digit is bigger in the delay than in UINT_MAX
else if (delay_array[0] == L'+' || (number_position == digits_in_uint && delay_array[0] - 48 > UINT_MAX / max_power_of_ten))
{
delay_too_big = 1;
}
else
{
number_position -= 1;
for (; number_position > 0; number_position--) // sum everything except most significant digit
{
accumulator += ((delay_array[number_position] - 48) * current_power_of_ten);
current_power_of_ten *= 10;
}
// checking that adding final value won't make accumulator overflow
// delay_array[0] won't be bigger than UINT_MAX / max_power_of_ten here because the other else if block checks for that
if (current_power_of_ten == max_power_of_ten && UINT_MAX - ((delay_array[0] - 48) * max_power_of_ten) - 1 < accumulator)
{
delay_too_big = 1;
}
else
{
accumulator += ((delay_array[0] - 48) * current_power_of_ten);
}
}
if (delay_too_big == 1)
{
if (TOO_BIG_DELAY_SEEN == 0)
{
printf("\nWARNING: maximum delay time is %u\n", UINT_MAX - 1);
TOO_BIG_DELAY_SEEN = 1; // TOO_BIG_DELAY_SEEN only changed here
}
return UINT_MAX - 1;
}
return accumulator;
}
// reads the next delay sequence and writes it in sequence_array
// also returns how many numbers are in the sequence so the right amount will be copied into the array given to robin_hood map
// there can't be more than UINT_MAX delays in one sequence
static unsigned int read_sequence(FILE* my_file, wchar_t* ch, wchar_t* delay_array, unsigned int* sequence_array, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
unsigned char number_position = 0; // next digit in current delay time. Also used in my_atoi to see if delay is too big
size_t sequence_position = 2; // starts at 2 because first two indexes are used to store sequence length and what the next delay is
delay_array[0] = L'\0'; // initializing to use in condition
while (*ch != WEOF && *ch != L'\n')
{
*ch = fgetwc(my_file);
if (*ch == WEOF || *ch == L'\n')
{
delay_array[number_position] = L'\0';
unsigned int last_delay = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
if (last_delay != 0) // no point adding 0 as final delay time
{
sequence_array[sequence_position] = last_delay;
sequence_position += 1;
}
}
else if (*ch == L'/') // end of current delay, about to read next delay
{
delay_array[number_position] = L'\0';
sequence_array[sequence_position] = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
sequence_position += 1;
number_position = 0;
}
// ch is a digit and the digit isn't a leading zero and the maximum delay isn't reached
else if (((*ch >= 49 && *ch <= 57) || (*ch == 48 && number_position > 0)) && delay_array[0] != L'+')
{
if (number_position != digits_in_uint) // if it's already equal to digits_in_uint then the delay is too big
{
delay_array[number_position] = *ch;
number_position += 1;
}
else
{
delay_array[0] = L'+'; // '+' used to check if the delay is too big in my_atoi
}
}
else if (*ch == L'-')
{
sequence_array[sequence_position] = UINT_MAX; // used for saying reset should happen
sequence_position += 1;
break; // no point checking delay times beyond where reset happens
}
}
while (*ch != WEOF && *ch != L'\n') // finish reading line if it's not already read
{
*ch = fgetwc(my_file);
}
if (sequence_array[sequence_position - 1] != UINT_MAX) // numbers entered and not a reset-all-sequences file
{
for (unsigned int i = sequence_position - 1; i > 1 && sequence_array[i] == 0; i--) // don't copy pointless zeros
{
sequence_position -= 1;
}
}
return sequence_position;
}
static unsigned char set_keys_and_values(rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map, FILE* my_file, size_t longest_name, size_t longest_sequence, unsigned char longest_delay, size_t line_count, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
wchar_t* name_array = (wchar_t*)malloc(longest_name * sizeof(wchar_t)); // array for filename text. Always greater than 0, shouldn't return NULL. Null character is accounted for.
unsigned int* sequence_array = (unsigned int*)malloc(longest_sequence * sizeof(unsigned int)); // array for delay sequence. Always greater than 0, shouldn't return NULL.
wchar_t* delay_array = (wchar_t*)malloc(longest_delay * sizeof(wchar_t)); // array for delay text. Always greater than 0, shouldn't return NULL.
if (name_array == NULL || sequence_array == NULL || delay_array == NULL)
{
return 0;
}
my_rh_map.reserve(line_count);
sequence_array[0] = 2; // first index stores length of array
sequence_array[1] = 2; // second index stores what the next delay to use in the sequence is
size_t chars_to_copy;
unsigned int numbers_to_copy;
wchar_t ch = L'\0';
while (ch != WEOF)
{
read_name(my_file, &ch, name_array); // puts characters in name_array
if (ch != WEOF && ch != L'\n') // checks if the line had a delay sequence. If it didn't, moves on to next line
{
numbers_to_copy = read_sequence(my_file, &ch, delay_array, sequence_array, max_power_of_ten, digits_in_uint);
if (numbers_to_copy > 2) // read_sequence returns 2 if either the sequence was pointless or the user didn't enter anything for it
{
sequence_array[0] = numbers_to_copy;
unsigned int* value_for_rh = (unsigned int*)malloc(numbers_to_copy * sizeof(unsigned int)); // always greater than 0, shouldn't return NULL
if (value_for_rh == NULL)
{
return 0;
}
std::wstring key_for_rh(name_array); // null character added to name_array in read_name function
for (unsigned int i = 0; i < numbers_to_copy; i++)
{
value_for_rh[i] = sequence_array[i];
}
my_rh_map[key_for_rh] = value_for_rh;
}
}
sequence_array[0] = 2;
sequence_array[1] = 2;
}
free((wchar_t*)name_array);
name_array = NULL;
free((unsigned int*)sequence_array);
sequence_array = NULL;
free((wchar_t*)delay_array);
delay_array = NULL;
return 1;
}
static unsigned char prepare_to_setup(rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map, FILE* my_file, size_t* longest_name, size_t* longest_sequence, unsigned char* longest_delay, size_t* line_count)
{
unsigned int max_power_of_ten = 1; // biggest power of 10 less than UINT_MAX, used in my_atoi
unsigned char digits_in_uint = 1; // how many digits are in UINT_MAX, used for helping to see if a delay is too big
for (; UINT_MAX / max_power_of_ten > 9; max_power_of_ten *= 10)
{
digits_in_uint += 1;
}
size_t map_max_size = my_rh_map.max_size();
check_file(my_file, longest_name, longest_sequence, longest_delay, line_count, digits_in_uint, map_max_size);
if (fseek(my_file, 0, SEEK_SET) != 0)
{
printf("fseek on files_and_delays.txt failed\n");
return 0;
}
// longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
if (*line_count == SIZE_MAX) // check_file will return UINT_MAX if something is too big
{
printf("filename, delay, delay sequence, and/or number of files is too big\n");
return 0;
}
unsigned char rh_succeeded = set_keys_and_values(my_rh_map, my_file, *longest_name, *longest_sequence, *longest_delay, *line_count, max_power_of_ten, digits_in_uint);
if (rh_succeeded == 0) // set_keys_and_values returns 0 if malloc fails
{
printf("malloc failed in set_keys_and_values\n");
return 0;
}
return 1;
}
```