Code Monkey home page Code Monkey logo

tdmp-launcher's People

Contributors

dangerkiddy avatar gnalvesteffer avatar metype avatar vulcan-dev avatar

Stargazers

 avatar  avatar

Watchers

 avatar

Forkers

jimmy-sketch

tdmp-launcher's Issues

Add documentation for project

We need to document the launcher to cover topics such as how to build the project, how to deploy, architecture, etc. This can live in the README.md

New Injection Method

I need to get arguments passed into the game but it's not possible with the current injection method.
In C++, I need to hook WinMain, but in order to do that, it needs to be injected instantly.

To do this, you use CreateProcess with the flags CREATE_DEFAULT_ERROR_MODE and CREATE_SUSPENDED. Once done, you allocate memory inside the game and inject the DLL, once injected you can resume threads.

I'll assign this to me and Xorberax. Metype has attempted it but had no luck

C++ Code from my TeardownM Injector

#define WIN32_LEAN_AND_MEAN

#include <Windows.h>
#include <filesystem>
#include <TlHelp32.h>
#include <iostream>
#include <fstream>
#include <string>
#include <regex>

#include "spdlog/spdlog.h"

const char *GetGamePath() {
    char cSteamPath[MAX_PATH];
    HKEY SteamKey;

    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, R"(SOFTWARE\WOW6432Node\Valve\Steam)", 0, KEY_QUERY_VALUE, &SteamKey) ==
        ERROR_SUCCESS) {
        DWORD dwLen = MAX_PATH;
        if (RegQueryValueExA(SteamKey, "InstallPath", nullptr, nullptr, reinterpret_cast<LPBYTE>(&cSteamPath),
                             &dwLen) == ERROR_SUCCESS) {
            cSteamPath[dwLen - 1] = '\0';
        } else {
            return nullptr;
        }

        RegCloseKey(SteamKey);
    } else {
        return nullptr;
    }

    std::string sSteamPath = std::string(cSteamPath);
    if (sSteamPath.empty())
        return nullptr;

    char *cTeardownPath = new char[MAX_PATH];

    std::string sTeardownPath = sSteamPath + R"(\steamapps\common\Teardown)";
    if (std::filesystem::exists(sTeardownPath + "\\teardown.unpacked.exe")) {
        memcpy(cTeardownPath, sTeardownPath.c_str(), MAX_PATH);
        return cTeardownPath;
    }

    // Look at all the other steam directories for the game
    std::ifstream ConfigFile(sSteamPath + "\\steamapps\\libraryfolders.vdf");
    if (!ConfigFile.is_open()) {
        std::cerr << "Failed to open libraryfolders.vdf!" << std::endl;
        return nullptr;
    }

    std::string sConfigContent = std::string(std::istreambuf_iterator<char>(ConfigFile),
                                             std::istreambuf_iterator<char>());
    std::regex DirRegex("\"[^\"]+\"[\\s]+\"([^\"]+)\"\\n", std::regex::ECMAScript);

    std::regex_iterator LibraryFolders = std::sregex_iterator(sConfigContent.begin(), sConfigContent.end(), DirRegex);

    for (std::sregex_iterator Match = LibraryFolders; Match != std::sregex_iterator(); ++Match) {
        sTeardownPath = (*Match)[1].str() + R"(\steamapps\common\Teardown)";

        if (std::filesystem::exists(sTeardownPath)) {
            sTeardownPath.replace(sTeardownPath.find("\\\\"), 2, "\\");

            if (std::filesystem::exists(sTeardownPath + "\\teardown.unpacked.exe")) {
                memcpy(cTeardownPath, sTeardownPath.c_str(), MAX_PATH);
                return cTeardownPath;
            }
        }
    }

    return nullptr;
}

DWORD GetPIDByName(const std::wstring &name) {
    PROCESSENTRY32 pt;
    HANDLE hsnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    pt.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(hsnap, &pt)) {
        do {
            if (!lstrcmpi(pt.szExeFile, name.c_str())) {
                CloseHandle(hsnap);
                return pt.th32ProcessID;
            }
        } while (Process32Next(hsnap, &pt));
    }

    CloseHandle(hsnap);
    return 0;
};

void Shutdown(const std::string &message, int exitCode) {
    spdlog::error(message + "\nPress any key to continue");
    std::cin.get();
    exit(exitCode);
}

void ShutdownLastError(const std::string &message) {
    DWORD dwError = GetLastError();
    LPVOID lpMsgBuf;
    FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr,
            dwError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPSTR) &lpMsgBuf,
            0, nullptr);
    Shutdown(message + ": " + (LPCSTR) lpMsgBuf, 1);
}

int LaunchGame(PROCESS_INFORMATION *ProcInfo, const char *cExePath, const char *cTeardownPath) {
    STARTUPINFOA StartupInfo;
    ZeroMemory(&StartupInfo, sizeof(StartupInfo));

    if (!CreateProcessA(nullptr, const_cast<LPSTR>(cExePath), nullptr, nullptr, TRUE,
                        CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED, nullptr, cTeardownPath, &StartupInfo, ProcInfo)) {
        ShutdownLastError("CreateProcessA Failed");
        return 1;
    }

    return 0;
}

int main() {
#ifdef _DEBUG
    spdlog::set_level(spdlog::level::debug);
#else
    spdlog::set_level(spdlog::level::info);
#endif

    const char *cTeardownPath = GetGamePath();
    char cDLLPath[MAX_PATH];
    char cCurrentPath[MAX_PATH];
    char cExePath[MAX_PATH];

    PROCESS_INFORMATION ProcInfo;
    STARTUPINFOA StartupInfo;

    ZeroMemory(&ProcInfo, sizeof(ProcInfo));
    ZeroMemory(&StartupInfo, sizeof(StartupInfo));

    if (!cTeardownPath) Shutdown("Unable to find installation of teardown", 1);

    GetCurrentDirectoryA(MAX_PATH, cCurrentPath);
    sprintf_s(cDLLPath, "%s\\%s", cCurrentPath, "Teardown.dll");
    const char *cDLLPath2 = cDLLPath;

    char cTempPath[MAX_PATH];
    GetTempPathA(MAX_PATH, cTempPath);
    sprintf_s(cTempPath, "%s\\%s", cTempPath, "tdl.txt");

    // Store the teardown path for quick access (barely, it's instant already but oh well)
    std::ofstream TempFile(cTempPath);
    TempFile << cCurrentPath;
    TempFile.close();

    spdlog::debug("Wrote tdl.txt to {}", cTempPath);

    spdlog::debug("DLL Path: {}", cDLLPath2);
    spdlog::debug("Current Path: {}", cCurrentPath);

    if (!std::filesystem::exists(cDLLPath)) Shutdown("Unable to find Teardown.dll", 1);

    sprintf_s(cExePath, "%s\\%s", cTeardownPath, "teardown.unpacked.exe");
    if (!std::filesystem::exists(cExePath)) Shutdown("Unable to find installation of teardown", 1);

    spdlog::debug("ExePath: {}", cExePath);
    spdlog::debug("Teardown Path: {}", cTeardownPath);

    FILE *TeardownExe;
    fopen_s(&TeardownExe, cExePath, "rb");
    if (!TeardownExe) Shutdown("Failed opening Teardown", 1);

    fseek(TeardownExe, 0, SEEK_END);
    long lFileSize = ftell(TeardownExe);
    rewind(TeardownExe);

    void *pExeBuffer = malloc(lFileSize);
    if (!pExeBuffer) Shutdown("Failed Getting Teardown Filesize", 1);

    fread(pExeBuffer, lFileSize, 1, TeardownExe);
    fclose(TeardownExe);

    SetEnvironmentVariableA("SteamAppId", "1167630"); // Set SteamAppId var to initialize SteamAPI

    const DWORD PID = GetPIDByName(L"teardown.unpacked.exe");
    bool bManuallyLaunched = false;

    if (PID == 0) {
        // Launch the game
        spdlog::info("Launching Teardown");
        LaunchGame(&ProcInfo, cExePath, cTeardownPath);
    } else {
        // Attach to the game
        spdlog::info("Attaching to Teardown");
        ProcInfo.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
        ProcInfo.dwProcessId = PID;
        spdlog::debug("PID: {}", PID);
        bManuallyLaunched = true;
    }

    spdlog::debug("hProcess: {:p}", ProcInfo.hProcess);

    if (!ProcInfo.hProcess) Shutdown("Failed launching/attaching to Teardown", 1);

    const size_t dwDLLPath2Length = strlen(cDLLPath2);

    // Allocate memory for the DLL
    const LPVOID pRemoteDLL = VirtualAllocEx(ProcInfo.hProcess, nullptr, dwDLLPath2Length + 1, MEM_COMMIT,
                                             PAGE_READWRITE);
    spdlog::debug("Allocated {} bytes for DLL", dwDLLPath2Length + 1);
    spdlog::debug("pRemoteDLL: {:p}", pRemoteDLL);
    if (!pRemoteDLL) {
        ShutdownLastError("VirtualAllocEx Failed");
        return 1;
    }

    // Write the DLL to the process
    if (!WriteProcessMemory(ProcInfo.hProcess, pRemoteDLL, cDLLPath2, dwDLLPath2Length + 1, nullptr)) {
        ShutdownLastError("WriteProcessMemory Failed");
        return 1;
    }

    // Get the address of LoadLibraryA
    const auto pLoadLibraryA = reinterpret_cast<LPVOID>(GetProcAddress(GetModuleHandleA("kernel32.dll"),
                                                                       "LoadLibraryA"));
    spdlog::debug("pLoadLibraryA: {:p}", pLoadLibraryA);
    if (!pLoadLibraryA) {
        ShutdownLastError("GetProcAddress Failed");
        return 1;
    }

    if (!ProcInfo.hThread) { // Injector did not launch the game because the end user done it
        ProcInfo.hThread = CreateRemoteThread(ProcInfo.hProcess, nullptr, 0,
                                              reinterpret_cast<LPTHREAD_START_ROUTINE>(pLoadLibraryA), pRemoteDLL, 0,
                                              nullptr);
        ProcInfo.dwThreadId = GetThreadId(ProcInfo.hThread);
        spdlog::warn("The UI will not get reloaded (will add in the future)");
    } else { // The injector launched the game
        CreateRemoteThread(ProcInfo.hProcess, nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(pLoadLibraryA),
                           pRemoteDLL, 0, nullptr);
    }

    spdlog::debug("RemoteThread: {:p}", ProcInfo.hThread);

    if (!ProcInfo.hThread) {
        CloseHandle(ProcInfo.hProcess);
        ShutdownLastError("CreateRemoteThread Failed");
    }

    // Resume the process
    ResumeThread(ProcInfo.hThread);

    Sleep(2000);
    spdlog::info("Teardown Multiplayer has been Loaded! Have fun");

    // TODO: Reload UI if bManuallyLaunched

    WaitForSingleObject(ProcInfo.hProcess, INFINITE);

    CloseHandle(ProcInfo.hProcess);
    CloseHandle(ProcInfo.hThread);

    return 0;
}

Teardown path auto-detection doesn't verify version compatibility

There is a report that a user had where they started up 0.3.1 on a clean install and it detected their Teardown path, but the version check indicator was yellow and they couldn't press Play and had to manually select their teardown.exe for it to verify as a workaround.

image

Add post-build steps to automate deployment artifact

Currently to deploy a new launcher build, one of the steps involves renaming the output directory from net7.0-windows to TDMP-Launcher-x.x.x and then zipping it. We could automate this with post-build steps which we can use to rename the output folder and use the AssemblyVersion of the project in the folder name, and handle zipping. This would make the overall deployment process a bit less tedious.

Update

This project looks like it's been dead for a while so I don't expect and answer, but would just changing the version number in gameversionutility.cs from 1.4.0 to 1.5.0 make it compatible with the newest version? Or would it take a lot more than that?

No license is included in the repo

No license has been included within the repo (and some other repos found in the GitHub org), meaning all code shown here is "all rights reserved" by default and not open source. Legally speaking users cannot fork and contribute changes to the repository as a result. Also keep in mind all contributors must agree to any license changes made to the project.

Add auto-update capability for launcher

I gave this a try in the auto-update branch but didn't get too far. With the rate we're releasing updates, and with how minor some of the updates can be, it may be annoying to users to have to manually update the launcher so often.

Add automated tests

We should add automated tests for the launcher to ensure stability and expected behavior doesn't break when we make changes.

Some TDMP files are missing when playing

Some users are reporting that some files are missing when playing TDMP through the launcher.
We need to investigate why this occurs and fix it. It's possible that it occurs during the uninstall step of the update process.

image

image

Reliably detect when game is fully loaded and ready to inject TDMP into

Currently the launcher just waits a few seconds after detecting the Teardown process to inject, and naively assumes that the game is loaded enough to inject TDMP. Some players systems might not be able to load the game fast enough which results in injection to fail and the game to crash.

We may be able to read process memory and detect if the Game class is initialized and that the game is at the splash or main menu screens.

Automatically detect the current location of teardown.exe

As I was shown, there's way to automatically get the location of the teardown.exe file, and any failure can just not auto-populate.

Essentially just try and find teardown.exe, if we do: treat it as what would currently happen when the user browses to a file. If we fail, present them with what they see now.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.