1
\$\begingroup\$

https://github.com/speedrun-program/load-extender

This is the previous post: C++ programs for Windows and Linux which let you lengthen the time it takes to access files REVISED VERSION

To compile this, you need to download and include robin_hood.h, and on Windows you also need to install EasyHook.

I tried to add everything mentioned in the answer there except using an obj / o file because I read that you have to include something twice if it’s used in both files, so it seemed like it would make the size a lot bigger because the file for robin_hood map is big. I also would have had to put some of the functions in their own file because I want them to be compiled without printf calls unless it’s the testing program. And I also couldn’t figure out how to get it to work, the main file couldn’t see the things from the other file. But I’ll try again to get it to work if someone says it would still really be a good idea.

I made this because it helps us save non-load time in a speedrun.

I want this to be revised as well as possible because I don’t have Mac OS, so I need to ask someone else to compile it for that OS. So I want it to be written as well as I can get it so I won’t have to ask people to compile it multiple times due to revisions.

class_and_functions.h:

#include <stdint.h>
#include <iostream>
#include <fstream>
#include <string>
#include <atomic>
#include <vector>
#include <exception>

class delay_sequence
{
public:
    std::atomic<std::size_t> index;
    std::vector<std::chrono::milliseconds> delays;
    bool repeat;
    bool reset_all;

    delay_sequence() { index = 0; repeat = false; reset_all = false; }
    delay_sequence(const delay_sequence& copy_sequence)
    {
        index = copy_sequence.index.load();
        delays = copy_sequence.delays;
        repeat = copy_sequence.repeat;
        reset_all = copy_sequence.reset_all;
    }
    delay_sequence& operator=(delay_sequence copy_sequence)
    {
        index = copy_sequence.index.load();
        delays = copy_sequence.delays;
        repeat = copy_sequence.repeat;
        reset_all = copy_sequence.reset_all;
        return *this;
    }
};

// find out how much space to reserve for delays vector
static std::size_t setup_delay_sequence(delay_sequence& current_sequence, std::wstring& line, std::size_t start_position)
{
    std::size_t delay_sequence_length = 1;
    for (std::size_t line_position = start_position; line_position < line.length(); line_position++)
    {
        if (line[line_position] == L'/')
        {
            ++delay_sequence_length;
        }
        else if (line[line_position] == L'-') // no more delays past reset point
        {
            --delay_sequence_length; // the last slash was before the reset point, so it won't go in the vector
            if (delay_sequence_length == 0)
            {
                current_sequence.reset_all = true;
                break;
            }
            current_sequence.repeat = true;
            break;
        }
    }
    return delay_sequence_length;
}

// this will let the user write something like "1,500"
static void remove_non_digits(std::wstring& delay_substr)
{
    std::size_t position_to_write = 0;
    for (std::size_t i = 0; i < delay_substr.length(); i++)
    {
        if (isdigit(delay_substr[i]))
        {
            delay_substr[position_to_write] = delay_substr[i];
            ++position_to_write;
        }
    }
    delay_substr.erase(position_to_write);
}

static void set_delays(std::vector<std::chrono::milliseconds>& delays, std::wstring& line, std::size_t delay_sequence_length, std::size_t slash_position)
{
    static bool too_big_delay_seen = false; // static so function will remember if it already printed the message in a previous call
    delays.reserve(delay_sequence_length);
    unsigned long long max_delay = std::chrono::milliseconds::max().count();
    while (slash_position != std::wstring::npos && delays.size() != delay_sequence_length)
    {
        std::size_t next_slash_position = line.find(L'/', slash_position + 1);
        std::wstring delay_substr = line.substr(slash_position + 1, next_slash_position - slash_position - 1);
        remove_non_digits(delay_substr);
        if (delay_substr.length() == 0) // no numbers were written
        {
            delays.push_back(std::chrono::milliseconds(0));
            slash_position = next_slash_position;
            continue;
        }
        try
        {
            unsigned long long delay_time = std::stoull(delay_substr);
            if (delay_time > max_delay)
            {
                throw std::out_of_range(NULL);
            }
            delays.push_back(std::chrono::milliseconds(delay_time));
        }
        catch (const std::out_of_range& oor)
        {
            if (!too_big_delay_seen)
            {
                printf("delay time can only be %llu milliseconds\n", max_delay);
                too_big_delay_seen = true;
            }
            delays.push_back(std::chrono::milliseconds(max_delay));
        }
        slash_position = next_slash_position;
    }
}

static void set_key_and_value(rh::unordered_flat_map<std::wstring, delay_sequence>& my_rh_map, std::wstring& line)
{
    std::size_t slash_position = line.find(L'/');
    if (slash_position == std::wstring::npos) // no slash so no delay sequence so line isn't valid
    {
        return;
    }
    std::size_t position_before_whitespace = line.find_last_not_of(L" \t\f\v\r/", slash_position);
    if (position_before_whitespace == std::wstring::npos) // the entire file name was whitespace
    {
        return;
    }
    std::wstring file_name = line.substr(0, position_before_whitespace + 1);
    delay_sequence current_sequence;
    std::size_t delay_sequence_length = setup_delay_sequence(current_sequence, line, slash_position + 1);
    set_delays(current_sequence.delays, line, delay_sequence_length, slash_position);
    my_rh_map[file_name] = current_sequence;
}

static bool rh_map_setup(rh::unordered_flat_map<std::wstring, delay_sequence>& my_rh_map)
{
    std::size_t line_count = 1;
    std::wstring line;
    std::wifstream my_file;
    my_file.open("files_and_delays.txt");
    if (my_file.fail())
    {
        printf("couldn't open files_and_delays.txt\n");
        return false;
    }
    while (std::getline(my_file, line) && line_count < SIZE_MAX)
    {
        ++line_count;
    }
    if (line_count == SIZE_MAX)
    {
        printf("can't store %zu delays\n", SIZE_MAX);
        return false;
    }
    my_rh_map.reserve(line_count); // overestimates slightly due to extra newlines and starting at 1
    my_file.clear();
    if (!my_file.seekg(0, std::ios::beg))
    {
        printf("seekg on files_and_delays.txt failed\n");
        return false;
    }
    line.clear();
    while (std::getline(my_file, line))
    {
        set_key_and_value(my_rh_map, line);
        line.clear();
    }
    my_file.close();
    return true;
}

static void delay_file(rh::unordered_flat_map<std::wstring, delay_sequence>& my_rh_map, std::wstring& file_name)
{
    rh::unordered_flat_map<std::wstring, delay_sequence>::iterator rh_map_iter = my_rh_map.find(file_name);
    if (rh_map_iter != my_rh_map.end()) // key found
    {
        printf("%ls successfully found in hash map\n", file_name.c_str());
        delay_sequence& found_sequence = rh_map_iter->second;
        if (found_sequence.reset_all) // reset all delay sequences
        {
            for (auto& it : my_rh_map)
            {
                it.second.index = 0;
            }
            printf("all delay sequences reset\n");
        }
        else
        {
            if (found_sequence.repeat) // reset delay sequence
            {
                if (found_sequence.index.load() > 0 && found_sequence.index.load() % found_sequence.delays.size() == 0)
                {
                    printf("%ls delay sequence reset\n", file_name.c_str());
                }
                found_sequence.index = found_sequence.index.load() % found_sequence.delays.size();
            }
            if (found_sequence.index.load() < found_sequence.delays.size())
            {
                // this is defined in the main file so it won't sleep if being used with load_extender_test.exe
                #ifdef THIS_IS_NOT_THE_TEST_PROGRAM
                    std::this_thread::sleep_for(found_sequence.delays[found_sequence.index.load()]);
                #else
                    unsigned long long total_milliseconds = found_sequence.delays[found_sequence.index.load()].count();
                    unsigned long long total_seconds = total_milliseconds / 1000;
                    short remaining_milliseconds = total_milliseconds % 1000;
                    printf("sleep for %llu second(s) and %d millisecond(s)\n", total_seconds, remaining_milliseconds);
                #endif
                found_sequence.index += 1;
            }
            else // delay sequence already finished
            {
                printf("%ls delay sequence already finished\n", file_name.c_str());
            }
        }
    }
    else // key not found
    {
        printf("%ls not found in hash map\n", file_name.c_str());
    }
}

load_extender_test.cpp:

#include <chrono>
#include <thread>
#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;

#include "class_and_functions.h" // included here so robin_hood alias can be used

static void print_rh_map(rh::unordered_flat_map<std::wstring, delay_sequence>& my_rh_map)
{
    printf("\n---------- hash map current state ----------\n");
    for (auto& it : my_rh_map)
    {
        printf("%ls / ", it.first.c_str());
        delay_sequence& sequence_to_print = it.second;
        for (std::size_t i = 0; i < sequence_to_print.delays.size(); i++)
        {
            // casted to unsigned long long to get rid of warning message
            // visual studio on windows wants %lu, but gcc on linux wants %llu
            printf("%llu ", (unsigned long long)sequence_to_print.delays[i].count());
        }
        if (sequence_to_print.reset_all)
        {
            printf("RESET_ALL ");
        }
        else if (sequence_to_print.repeat)
        {
            printf("REPEAT ");
        }
        printf(": INDEX %zu", sequence_to_print.index.load());
        printf("\n");
    }
    printf("---------------------------------------------\n\n");
}

static void test_all_inputs()
{
    #ifdef _WIN32
        std::wstring path_separator = L"\\";
    #else
        std::wstring path_separator = L"/";
    #endif
    std::wstring test_path;
    std::wifstream input_test_file;
    input_test_file.open("test_input.txt");
    if (input_test_file.fail())
    {
        printf("couldn't open test_input.txt\n");
        return;
    }
    rh::unordered_flat_map<std::wstring, delay_sequence> my_rh_map;
    bool setup_succeeded = rh_map_setup(my_rh_map);
    if (setup_succeeded)
    {
        print_rh_map(my_rh_map);
        while (std::getline(input_test_file, test_path))
        {
            printf("testing path: %ls\n", test_path.c_str());
            // +1 so separator isn't included
            // If separator isn't found, the whole wstring is checked because npos is -1
            std::wstring test_file = test_path.substr(test_path.rfind(path_separator) + 1);
            delay_file(my_rh_map, test_file);
            print_rh_map(my_rh_map);
            test_path.clear();
        }
    }
    else
    {
        printf("setup failed\n");
    }
    input_test_file.close();
}

int main(int argc, char* argv[])
{
    printf("\ntest start\n");
    test_all_inputs();
    printf("test finished, press Enter to exit\n");
    std::wstring input;
    std::getline(std::wcin, input);
    return 0;
}

load_extender.cpp

#include <chrono>
#include <thread>
#ifdef _WIN32
    #define NOMINMAX // allows std::chrono::milliseconds::max().count();
    #include <Windows.h>
    // easyhook.h installed with NuGet
    // https://easyhook.github.io/documentation.html
    #include <easyhook.h>
#else
    #ifndef _GNU_SOURCE
        #define _GNU_SOURCE
    #endif
    #include <stdio.h>
    #include <dlfcn.h>
#endif
#include "robin_hood.h"
// thank you for making this hash map, martinus.
// here's his github: https://github.com/martinus/robin-hood-hashing

#ifndef THIS_IS_NOT_THE_TEST_PROGRAM
    #define THIS_IS_NOT_THE_TEST_PROGRAM
#endif

#ifdef THIS_IS_NOT_THE_TEST_PROGRAM
    #define printf(fmt, ...) (0)
#endif

namespace rh = robin_hood;

#include "class_and_functions.h" // included here so robin_hood alias can be used

#ifdef _WIN32
    static NTSTATUS WINAPI NtCreateFileHook(
        PHANDLE            FileHandle,
        ACCESS_MASK        DesiredAccess,
        POBJECT_ATTRIBUTES ObjectAttributes,
        PIO_STATUS_BLOCK   IoStatusBlock,
        PLARGE_INTEGER     AllocationSize,
        ULONG              FileAttributes,
        ULONG              ShareAccess,
        ULONG              CreateDisposition,
        ULONG              CreateOptions,
        PVOID              EaBuffer,
        ULONG              EaLength)
    {
        static rh::unordered_flat_map<std::wstring, delay_sequence> my_rh_map;
        static bool setup_succeeded = rh_map_setup(my_rh_map);
        if (setup_succeeded)
        {
            std::wstring file_path = ObjectAttributes->ObjectName->Buffer;
            // +1 so '\' isn't included. If '\' isn't found, the whole wstring is checked because npos is -1
            std::wstring file_name = file_path.substr(file_path.rfind(L"\\") + 1);
            delay_file(my_rh_map, file_name);
        }
        return NtCreateFile(
            FileHandle,
            DesiredAccess,
            ObjectAttributes,
            IoStatusBlock,
            AllocationSize,
            FileAttributes,
            ShareAccess,
            CreateDisposition,
            CreateOptions,
            EaBuffer,
            EaLength);
    }

    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")), "NtCreateFile"),
            NtCreateFileHook,
            NULL,
            &hHook1);
        ULONG ACLEntries[1] = { 0 };
        LhSetExclusiveACL(ACLEntries, 1, &hHook1);
    }
#else
    FILE* fopen(const char* path, const char* mode)
    {
        static auto original_fopen = reinterpret_cast<FILE * (*)(const char* path, const char* mode)>(dlsym(RTLD_NEXT, "fopen"));
        static rh::unordered_flat_map<std::wstring, delay_sequence> my_rh_map;
        static bool setup_succeeded = rh_map_setup(my_rh_map);
        if (setup_succeeded)
        {
            // finding part of path which shows file name
            std::size_t file_name_index = SIZE_MAX; // overflows to 0 and checks entire path if no slash is found
            std::size_t end_index = 0;
            for (; path[end_index] != '\0'; end_index++)
            {
                if (path[end_index] == '/')
                {
                    file_name_index = end_index;
                }
            }
            std::wstring file_name(&path[file_name_index + 1], &path[end_index]);
            delay_file(my_rh_map, file_name);
        }
        return original_fopen(path, mode);
    }
#endif

load_extender_injector.cpp (only used on Windows, LD_PRELOAD and DYLD_INSERT_LIBRARIES can be used on Linux and Max OS):

#include <tchar.h>
#include <iostream>
#include <string>
#include <Windows.h>
// easyhook.h installed with NuGet
// https://easyhook.github.io/documentation.html
#include <easyhook.h>

void get_exit_input()
{
    std::wcout << "Press Enter to exit";
    std::wstring input;
    std::getline(std::wcin, 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;
}
\$\endgroup\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.