2
\$\begingroup\$

https://github.com/speedrun-program/load_extender

This is the previous post: Lengthening the time it takes to access files using function hooking

I tried adding everything from the previous post's answer, but I decided to stay with printf for output because it was pointed out that std::format makes the exe size large. I also decided to avoid std::filesystem::exists because it causes an 8-10 KB exe size increase.

To compile this on Windows, you need to install EasyHook.

load_extender_injector.cpp (only used on Windows because LD_PRELOAD and DYLD_INSERT_LIBRARIES can be used on Linux and Mac OS)

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

void getExitInput()
{
    int ch = 0;

    for (; ch != '\n'; ch = std::getchar());

    std::printf("Press Enter to exit\n");
    ch = std::getchar();
}

int _tmain(int argc, _TCHAR* argv[])
{
    WCHAR* dllToInject32 = nullptr;
    WCHAR* dllToInject64 = nullptr;
    _TCHAR* applicationName = argv[0];
    DWORD binaryType = 0;
    BOOL getBinaryTypeResult = GetBinaryType(applicationName, &binaryType);

    if (getBinaryTypeResult == 0 || (binaryType != 0 && binaryType != 6))
    {
        std::printf("ERROR: This exe wasn't identified as 32-bit or as 64-bit\n");
        getExitInput();
        return 0;
    }
    else if (binaryType == 0)
    {
        dllToInject32 = (WCHAR*)L"load_extender_32.dll";
    }
    else
    {
        dllToInject64 = (WCHAR*)L"load_extender_64.dll";
    }

    std::printf("Enter the process Id: ");
    DWORD pid = 0;
    std::cin >> pid;

    NTSTATUS errorCode = RhInjectLibrary(
        pid,                     // The process to inject into
        0,                       // ThreadId to wake up upon injection
        EASYHOOK_INJECT_DEFAULT,
        dllToInject32,           // 32-bit
        dllToInject64,           // 64-bit
        nullptr,                 // data to send to injected DLL entry point
        0                        // size of data to send
    );

    if (errorCode != 0)
    {
        std::printf("RhInjectLibrary failed with error code = %d\n", errorCode);
        PWCHAR errorMessage = RtlGetLastErrorString();
        std::printf("%ls\n", errorMessage);
        getExitInput();
        return 0;
    }

    std::printf("Library injected successfully.\n");
    getExitInput();
    return 0;
}

load_extender.cpp

#include <climits>
#include <mutex>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <string_view>
#include <unordered_map>

#ifdef _WIN32
// easyhook.h installed with NuGet
// https://easyhook.github.io/documentation.html
#include <easyhook.h>
#include <Windows.h>
using wcharOrChar = wchar_t; // file paths are UTF-16LE on Windows
using strType = std::wstring;
using svType = std::wstring_view;
#else
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dlfcn.h>
using wcharOrChar = char;
using strType = std::string;
using svType = std::string_view;
#endif

// using multiple cpp files made exe bigger, so definitions are in this header
#include "shared.h"

static MapAndMutex mapAndMutexObject;

#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)
{
    const wchar_t* path = (const wchar_t*)(ObjectAttributes->ObjectName->Buffer);
    int pathEndIndex = (ObjectAttributes->ObjectName->Length) / sizeof(wchar_t);
    int filenameIndex = pathEndIndex;

    for (; filenameIndex >= 0 && path[filenameIndex] != '\\'; filenameIndex--);

    filenameIndex++; // moving past '\\' character or to 0 if no '\\' was found
    auto it = mapAndMutexObject.fileMap.find(
        svType(path + filenameIndex,
        (size_t)pathEndIndex - filenameIndex)
    );

    if (it != mapAndMutexObject.fileMap.end())
    {
        mapAndMutexObject.delayFile(it->second);
    }

    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 = { nullptr };
    HMODULE moduleHandle = GetModuleHandle(TEXT("ntdll"));

    if (moduleHandle)
    {
        LhInstallHook(
            GetProcAddress(moduleHandle, "NtCreateFile"),
            NtCreateFileHook,
            nullptr,
            &hHook1
        );
    }

    ULONG ACLEntries[1] = { 0 };
    LhSetExclusiveACL(ACLEntries, 1, &hHook1);
}
#else
static auto originalFopen = reinterpret_cast<FILE * (*)(const char* path, const char* mode)>(dlsym(RTLD_NEXT, "fopen"));

FILE* fopen(const char* path, const char* mode)
{
    int filenameIndex = -1;
    int pathEndIndex = 0;

    for (; path[pathEndIndex] != '\0'; pathEndIndex++)
    {
        if (path[pathEndIndex] == '/')
        {
            filenameIndex = pathEndIndex;
        }
    }
    
    filenameIndex++; // moving past '/' character or to 0 if no '/' was found
    auto it = mapAndMutexObject.fileMap.find(
        svType(path + filenameIndex,
        (size_t)pathEndIndex - filenameIndex)
    );
    
    if (it != mapAndMutexObject.fileMap.end())
    {
        delayFile(it->second);
    }

    return original_fopen(path, mode);
}
#endif

shared.h

#ifndef DEBUG
#define printf(...) (0)
#endif

class FileHelper
{
public:
    FileHelper(const FileHelper& fhelper) = delete;
    FileHelper& operator=(FileHelper other) = delete;
    FileHelper(FileHelper&&) = delete;
    FileHelper& operator=(FileHelper&&) = delete;

    FileHelper() {}

    ~FileHelper()
    {
        if (_f)
        {
            std::fclose(_f);
        }
    }

    void tryToOpenFile(const char* filename)
    {
#ifdef _WIN32
        if (fopen_s(&_f, filename, "rb") != 0 || !_f)
#else
        if (!(_f = std::fopen(filePath, "rb")))
#endif
        {
            throw "FileHelper fopen failure in tryToOpenFile";
        }
    }

    bool checkIfFileExists(std::string&& filename)
    {
#ifdef _WIN32
        bool exists = fopen_s(&_fcheck, filename.c_str(), "rb") == 0;
#else
        bool exists = (_fcheck = std::fopen(filename.c_str(), "rb"));
#endif

        if (_fcheck)
        {
            std::fclose(_fcheck);
            _fcheck = nullptr;
        }

        return exists;
    }

    bool getCharacter(wcharOrChar& ch)
    {
        if (_bufferPosition == _charactersRead)
        {
            _bufferPosition = 0;
            _charactersRead = (int)std::fread(_buffer.data(), sizeof(wcharOrChar), _buffer.size(), _f);

            if (!_charactersRead)
            {
                return false;
            }
        }

        ch = _buffer.at(_bufferPosition);
        _bufferPosition++;

        return true;
    }

    void resetFile() // used in tests
    {
        if (std::fseek(_f, 0, SEEK_SET) != 0)
        {
            throw "FileHelper fseek failure in resetFile";
        }

        _bufferPosition = 0;
        _charactersRead = 0;
    }

private:
    FILE* _f = nullptr;
    FILE* _fcheck = nullptr;
    std::vector<wcharOrChar> _buffer = std::vector<wcharOrChar>(8192 / sizeof(wcharOrChar));
    int _bufferPosition = 0;
    int _charactersRead = 0;
};

struct MapValue
{
    std::vector<int> delays;
    size_t position = 0;
    unsigned int fullResetCheckNumber = 0;
    bool reset = false;
    bool resetAll = false;

    MapValue(bool& textRemaining, std::vector<int>& delaysVector, FileHelper& fhelper)
    {
        wcharOrChar ch = '\0';
        long long int delay = 0;

        for (
            textRemaining = fhelper.getCharacter(ch);
            ch != '\n' && textRemaining;
            textRemaining = fhelper.getCharacter(ch))
        {
            if (ch >= '0' && ch <= '9')
            {
                ch = ch - '0';
                delay *= 10;
                delay += ch;

                if (delay > INT_MAX)
                {
                    throw "delays can't be larger than INT_MAX";
                }
            }
            else if (ch == '-')
            {
                if (delaysVector.empty())
                {
                    resetAll = true;
                }
                else
                {
                    reset = true;
                }

                break;
            }
            else if (ch == '/')
            {
                delaysVector.push_back((int)delay);
                delay = 0;
            }
        }

        if (delay != 0 && !reset && !resetAll)
        {
            delaysVector.push_back((int)delay);
        }

        // make sure to go to end of line
        for (; ch != '\n' && textRemaining; textRemaining = fhelper.getCharacter(ch));
    }
};

struct KeyCmp
{
    using is_transparent = void;

    bool operator()(const svType sv1, const svType sv2) const
    {
        return sv1 == sv2;
    }
};

struct KeyHash
{
    using is_transparent = void;

    size_t operator()(const svType sv) const
    {
        return _hashObject(sv);
    }

private:
    std::hash<svType> _hashObject = std::hash<svType>();
};

using myMapType = std::unordered_map<strType, MapValue, KeyHash, KeyCmp>;

class MapAndMutex
{
public:
    std::mutex mutexForMap;
    myMapType fileMap;

    MapAndMutex()
    {
        try
        {
            std::wstring keyStr;
            std::vector<int> delaysVector;
            FileHelper fhelper;
            std::string filename("files_and_delays.txt");
            size_t fileNumber = 0;

            // std::filesystem::exists caused 10 KB exe size increase
            for (
                ;
                fhelper.checkIfFileExists("files_and_delays" + std::to_string(fileNumber) + ".txt");
                fileNumber++);

            if (fileNumber > 0)
            {
                filename = "files_and_delays" + std::to_string(fileNumber - 1) + ".txt";
            }

            fhelper.tryToOpenFile(filename.c_str());
#ifdef _WIN32
            wchar_t byteOrderMark = '\0';

            if (!fhelper.getCharacter(byteOrderMark))
            {
                printf(
                    "files_and_delays.txt byte order mark is missing\n\
                make sure files_and_delays.txt is saved as UTF-16 LE\n"
                );
            }
            else if (byteOrderMark != 0xFEFF) // not 0xFFFE due to how wchar_t is read
            {
                printf(
                    "files_and_delays.txt byte order mark isn't marked as UTF-16 LE\n\
                make sure files_and_delays.txt is saved as UTF-16 LE\n"
                );
            }
#endif

            while (addMapPair(fileMap, keyStr, delaysVector, fhelper));
        }
        catch (char const* e)
        {
            char const* resolveC4101Warning = e;
            printf("%s\n", resolveC4101Warning);
            fileMap.clear(); // clear map so failure is more obvious
        }
    }

    bool addMapPair(myMapType& fileMap, strType& keyStr, std::vector<int>& delaysVector, FileHelper& fhelper)
    {
        keyStr.clear();
        delaysVector.clear();
        wcharOrChar ch = '\0';
        bool keepWhitespace = false;
        bool textRemaining = fhelper.getCharacter(ch);

        if (ch == '\n' || !textRemaining)
        {
            return textRemaining;
        }
        else if (ch == '/')
        {
            keepWhitespace = true;
        }
        else if (ch != ' ' && ch != '\f' && ch != '\r' && ch != '\t' && ch != '\v')
        {
            keyStr.push_back(ch);
        }

        for (
            textRemaining = fhelper.getCharacter(ch);
            ch != '\n' && ch != '/' && textRemaining;
            textRemaining = fhelper.getCharacter(ch))
        {
            if (keepWhitespace || (ch != ' ' && ch != '\f' && ch != '\r' && ch != '\t' && ch != '\v'))
            {
                keyStr.push_back(ch);
            }
        }

        if (textRemaining && ch == '/')
        {
            MapValue fileMapValue(textRemaining, delaysVector, fhelper);

            if (!keyStr.empty() && (!delaysVector.empty() || fileMapValue.resetAll))
            {
                fileMapValue.delays = delaysVector;
                strType keyStrCopy(keyStr);
                fileMapValue.delays.shrink_to_fit();
                keyStrCopy.shrink_to_fit();
                fileMap.emplace(std::move(keyStrCopy), std::move(fileMapValue));
            }
        }

        return textRemaining;
    }

    void delayFile(MapValue& fileMapValue)
    {
#ifndef DEBUG // this needs to be reset in the test, so it's a global variable instead
        static unsigned int fullResetCount = 0;
#endif

        printf("fullResetCount: %zu\n", fullResetCount);
        int delay = 0;

        {
            std::lock_guard<std::mutex> mutexForMapLock(mutexForMap);

            if (fileMapValue.fullResetCheckNumber < fullResetCount)
            {
                fileMapValue.position = 0;
                fileMapValue.fullResetCheckNumber = fullResetCount;
                printf("this delay sequence reset due to prior full reset\n");
            }

            if (fileMapValue.resetAll)
            {
                if (fullResetCount == UINT_MAX) // this probably won't ever happen
                {
                    fullResetCount = 0;

                    for (auto& it : fileMap)
                    {
                        it.second.fullResetCheckNumber = 0;
                    }

                    printf("fullResetCount reset\n");
                }

                fullResetCount++;
                printf("fullResetCount set to %zu, all sequences will be reset\n", fullResetCount);
            }
            else if (fileMapValue.position == fileMapValue.delays.size())
            {
                if (fileMapValue.reset)
                {
                    fileMapValue.position = 0;
                    printf("this delay sequence reset\n");
                }
                else
                {
                    printf("delay sequence already finished\n");
                }
            }

            if (fileMapValue.position < fileMapValue.delays.size())
            {
                delay = fileMapValue.delays.at(fileMapValue.position);
                fileMapValue.position++;
            }

            printf("delay is %d millisecond(s)\n\n", delay);
        }

        if (delay > 0)
        {
#ifndef DEBUG
            std::this_thread::sleep_for(std::chrono::milliseconds(delay));
#endif
        }
    }
};

load_extender_test.cpp

#include <climits>
#include <mutex>
#include <vector>
#include <string>
#include <unordered_map>

#ifdef _WIN32
using wcharOrChar = wchar_t; // file paths are UTF-16LE on Windows
using strType = std::wstring;
using svType = std::wstring_view;
#else
using wcharOrChar = char;
using strType = std::string;
using svType = std::string_view;
#endif

#define DEBUG
unsigned int fullResetCount = 0;

// using multiple cpp files made exe bigger, so definitions are in this header
#include "shared.h"

void windowsHookFunction(MapAndMutex& mapAndMutexObject, strType& pathStr)
{
    if (!pathStr.empty() && pathStr.back() == '\r')
    {
        pathStr.pop_back();
    }

    const wcharOrChar* path = pathStr.c_str();

    // in the actual program, this will always be less than INT_MAX
    int pathEndIndex = pathStr.size() < INT_MAX ? (int)pathStr.size() : INT_MAX;
    int filenameIndex = pathEndIndex;

    for (; filenameIndex >= 0 && path[filenameIndex] != '\\'; filenameIndex--);

    filenameIndex++; // moving past '\\' character or to 0 if no '\\' was found
    auto it = mapAndMutexObject.fileMap.find(
        svType(path + filenameIndex,
        (size_t)pathEndIndex - filenameIndex)
    );

    if (it != mapAndMutexObject.fileMap.end())
    {
#ifdef _WIN32
        printf("\n%ls found in map\n", pathStr.c_str());
#else
        printf("\n%s found in map\n", pathStr.c_str());
#endif
        mapAndMutexObject.delayFile(it->second);
    }
    else
    {
#ifdef _WIN32
        printf("\n%ls not found in map\n", pathStr.c_str());
#else
        printf("\n%s not found in map\n", pathStr.c_str());
#endif
    }
}

void unixHookFunction(MapAndMutex& mapAndMutexObject, strType& pathStr)
{
    if (!pathStr.empty() && pathStr.back() == '\r')
    {
        pathStr.pop_back();
    }

    const wcharOrChar* path = pathStr.c_str();

    int filenameIndex = -1;
    int pathEndIndex = 0;

    for (; path[pathEndIndex] != '\0'; pathEndIndex++)
    {
        if (path[pathEndIndex] == '/')
        {
            filenameIndex = pathEndIndex;
        }
    }

    filenameIndex++; // moving past '/' character or to 0 if no '/' was found
    auto it = mapAndMutexObject.fileMap.find(
        svType(path + filenameIndex,
        (size_t)pathEndIndex - filenameIndex)
    );

    if (it != mapAndMutexObject.fileMap.end())
    {
#ifdef _WIN32
        printf("\n%ls found in map\n", pathStr.c_str());
#else
        printf("\n%s found in map\n", pathStr.c_str());
#endif
        mapAndMutexObject.delayFile(it->second);
    }
    else
    {
#ifdef _WIN32
        printf("\n%ls not found in map\n", pathStr.c_str());
#else
        printf("\n%s not found in map\n", pathStr.c_str());
#endif
    }
}

void printMap(const myMapType& fileMap)
{
    for (auto& it : fileMap)
    {
        printf(
#ifdef _WIN32
            "%ls : fullResetCheckNumber %zu : position %zu : ",
#else
            "%s : fullResetCheckNumber %zu : position %zu : ",
#endif
            it.first.c_str(),
            it.second.fullResetCheckNumber,
            it.second.position
        );

        for (size_t i = 0; i < it.second.delays.size(); i++)
        {
            printf("%d / ", it.second.delays.at(i));
        }

        if (it.second.reset)
        {
            printf("RESET");
        }
        else if (it.second.resetAll)
        {
            printf("RESET ALL");
        }

        printf("\n");
    }
}

void testFunctions(MapAndMutex& mapAndMutexObject, FileHelper& fhelper, bool testUnix)
{
    strType pathStr;
    wcharOrChar ch = '\0';

    while (fhelper.getCharacter(ch))
    {
        if (ch == '\n')
        {
            if (testUnix)
            {
                unixHookFunction(mapAndMutexObject, pathStr);
            }
            else
            {
                windowsHookFunction(mapAndMutexObject, pathStr);
            }

            printMap(mapAndMutexObject.fileMap);
            pathStr.clear();
        }
        else
        {
            pathStr.push_back(ch);
        }
    }

    if (testUnix)
    {
        unixHookFunction(mapAndMutexObject, pathStr);
    }
    else
    {
        windowsHookFunction(mapAndMutexObject, pathStr);
    }
}

void testInputs(MapAndMutex& mapAndMutexObject)
{
    FileHelper fhelper;
    fhelper.tryToOpenFile("test_input.txt");
    wcharOrChar byteOrderMark = '\0';
#ifdef _WIN32

    if (!fhelper.getCharacter(byteOrderMark))
    {
        printf(
            "test_input.txt byte order mark is missing\n\
            save test_input.txt as UTF-16 LE\n\n"
        );

        return;
    }
    else if (byteOrderMark != 0xFEFF) // not 0xFFFE due to how wchar_t is read
    {
        printf(
            "test_input.txt byte order mark isn't marked as UTF-16 LE\n\
            make sure files_and_delays.txt is saved as UTF-16 LE\n\n"
        );
    }
#endif
    printf("\ntesting UNIX\n\n - - - - - - - - - -\n\n");
    printMap(mapAndMutexObject.fileMap);
    testFunctions(mapAndMutexObject, fhelper, true);
    printMap(mapAndMutexObject.fileMap);
    fullResetCount = 0;
    fhelper.resetFile();
#ifdef _WIN32
    fhelper.getCharacter(byteOrderMark);
#endif

    for (auto& it : mapAndMutexObject.fileMap)
    {
        it.second.position = 0;
        it.second.fullResetCheckNumber = 0;
    }

    printf("\n\ntesting Windows\n\n - - - - - - - - - -\n\n");
    printMap(mapAndMutexObject.fileMap);
    testFunctions(mapAndMutexObject, fhelper, false);
    printMap(mapAndMutexObject.fileMap);
}

int main()
{
    try
    {
        printf("\ntest start\n\n");
        MapAndMutex mapAndMutexObject;
        testInputs(mapAndMutexObject);
    }
    catch (char const* e)
    {
        printf("%s", e);
    }

    printf("\ntest finished, press Enter to exit\n");
    char ch = getchar();

    return 0;
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Be consistent

Some parts of your code use std::printf(), others just printf(). Be consistent, and prefer using std::printf() in C++ code, especially if you #include <cstdio>, which you should remember to do.

Parsing integers

In the constructor of MapValue you declare a long long int delay, but in the end it is going to be pushed into delaysVector as an int. It looks like you do this to catch mistakes where the value being read is larger than an int can hold, but of course this check itself will fail if the value is larger than a long long int. Consider using std::from_chars(). In particular, this can read into an int and will return a std::errc:result_out_of_range error code if the number in the input didn't fit into an int.

Use EXIT_SUCCESS and EXIT_FAILURE for exit codes

It is a bit nicer to use EXIT_SUCCESS and EXIT_FAILURE instead of numeric exit codes.

Make even more use of C++

You already improved on the original code, but there are still a lot of things where you use the plain old C way of doing things, but where you should use C++ features, as C++ provides much better (type) safety than C.

In particular, string manipulation is an area where C++ is much better, especially C++17. If you have a C string and want to find, say, the last '/' in it, you can use std::string_view to efficiently do that:

FILE* fopen(const char* path, const char* mode)
{
    svType path_view(path);
    auto last_slash = path_view.find_last_of('/');
    auto filename = (last_slash == path_view.npos) ? path_view : path_view.substr(last_slash + 1);
    ...
}

Of course, it's even easier to do path manipulations by using std::filesystem::path.

Counting the number of files matching a pattern

In the constructor of MapAndMutex you are trying to find the file named files_and_delaysNNN.txt with the highest value of NNN, but you do this by trying to open each file in turn. This is quite inefficient. Instead, you can just ask for a directory listing. In C++17 you would use the cross-platform std::filesystem::directory_iterator, in C you would use opendir() and related functions on POSIX platforms, and on Windows of you have to use FindFirstFile() and related functions.

Use structured bindings

In C++17 you can use structured bindings to make code working with std::pairs and std::tuples more readable. For example:

for (auto& [key, value] : fileMap)
{
    value.fullResetCheckNumber = 0;
}

FileHelper is badly designed

The design of FileHelper is not robust against misuse. For example, consider calling tryToOpenFile() twice on the same FileHelper object, both times on files that do exist. The second call will overwrite _f, thus leaking a file descriptor. Why not make it so the file is always opened in the constructor, and no other functions can open a file? This will follow the RAII principle.

The member function checkIfFileExists() should either be a free function or a static function, and the member variable _fcheck should instead just be a local variable inside checkIfFileExists() itself.

Apart from that, the only thing it does is some buffering and rewinding the file. Those are all functions that <cstdio> functions do themselves already. You could also use a std::ifstream instead. That brings me to:

Parsing files

Just like you do a lot of manual string manipulation, you do the same with file input. You can rely a lot more on standard library functions, either from <cstdio> or <fstream>. Since the format of files_and_delays.txt is line-based, I suggest reading whole lines at ones using std::getline() (or std::fgets() in C), and then further parse each line.

Code size

As you already mentioned in the previous version, some C++ functions will increase the size of the library significantly. This is holding you back from using some very convenient functionality, and instead spending a lot of time writing low-level code. Consider whether code size is important to begin with. Note that code size doesn't necessarily correlate with efficiency, and that where C++ will inline a lot of STL functions, C functions cannot do that but will instead be stored in the C standard libary, which will be loaded dynamically and thus still be in memory, even if they are not part of the .dll/.so file you are creating.

If code size is really the most important thing, then you should probably not use C++ at all, but just go for plain C.

Don't throw raw strings

Avoid throwing raw string literals. Prefer throwing exceptions that are derived from std::exception or any of the derived types the standard library provides. For example, if you can't open a file:

throw std::runtime_error("fopen failure in tryToOpenFile");

Having a proper exception type makes it easier for code that catches exceptions to distinguish between exception types, without having to parse strings for example.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for reviewing again. I’d like the size to be small in places where speed won’t matter, like the part where the right file is found, but I think I should try to figure out how to avoid including the easyhook DLLs before focusing on that since they’re so big. \$\endgroup\$ May 5 at 23:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.