Задать вопрос
@igreklpofrss

Как скомпилировать рабочую dll библиотеку?

Приветствую!

Сперва объясню всю суть происходящего.
Есть старая игра-гонка на ПК. В ней официально всего 10 карт. Но, с помощью магии программирования, получилось сделать еще 30 карт(3 вариации на каждую карту). Отображение карт в меню игры очень "вшито" в код. Функции, которые ищут описание карты, миникарту, скриншоты карты по ID карты, проверяются строгими условиями от 0 до 9. Т.е. данные карты с id 10 игра никак не найдет. Решить вопрос с реализацией добавления карт поможет только масштабное изменение инструкций в exe файле, что слишком сложно и можно сказать невозможно, да и assembler не на столько хорошо знаю.. НО игра поддерживает подключение dll библиотеки и сохраняет из нее адрес одной функции в память. Библиотека использовалась разработчиками игры для дебага. Также надо добавить, что exe файл поддерживает экспорт функций.

Мой план был таков:
1. Изменить значение условия, которое обходит подключение библиотеки и подключить ее.
2. Написать свою DLL с единственной импортируемую функцией и сделать из нее pipe-обертку, которая по ID будет возвращать другие функции, функции-обертки, подправив и расширив функционал старых функций.
3. Изменить инструкции assembler под вызов функций оберток.
4. Перенести описание новых карт в свой конфиг, чтобы вообще не ограничиваться количеством карт и извлекать данные мб из JSON-файла.

Я застрял на пункте 3. В частности, в данный момент, я нашел функцию, которая ищет описание карт. Написал свою функцию в dll, изменил инструкции для вызова функции через функции обертки и тестирую. Пока что задача такова, чтобы игра работала через мой dll и не крашилась.
Прикладываю скрины инструкций. Красным подсветил адрес, в котором начал изменять инструкции.
Оригинальные инструкции.
69273d031bf00387764135.jpeg

Измененные инструкции для вызова функции из dll
69273dc135569686784261.jpeg

Новая функция из dll
692741622980c353342257.jpeg

К сожалению, программа крашится.. А причина в том, что, непонятно зачем, компилятор C++, перед последним вызовом функции crsGetString(x, x, x, x), который и так работает, добавил инструкцию add esp, 40h(красная рамка на скрине), смещая указатель стека так, что дальнейшая команда retn не может вернуться на адрес, на котором функция была вызвана, ибо стек сместился.
Что с этим можно сделать? Почему компилятор может добавлять это смещение?
Библиотеку пишу в Visual Studio 2026. Пишу в шаблонных настройках dll от VS. Вот исходник:
Исходный код
//dllmain.cpp
// dllmain.cpp : Определяет точку входа для приложения DLL.
#include "pch.h"
#include "crsWrapper.h"
#include <windows.h>


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        crsInitDll();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

// crsWrapper.h
#pragma once

void crsInitDll();

extern "C" __declspec(dllexport) void* crsPipeWrapper(int fnId);

// crsWrapper.cpp
#include <iostream>
#include "pch.h"
#include "crsWrapper.h"
#include <windows.h>

// объявим тип данных функции
typedef int (*crsGetStringType)(void *, int, char *Destination, int Count);
typedef int (*GetMapDescriptionsType)(int mapCode, char *destMapKey, char* destMapName, char* destMapPoem1, char* destMapDesc, char* destMapPoem2);

crsGetStringType crsGetString = NULL;


int GetMapDescriptions(int mapCode, char *destMapKey, char *destMapName, char *destMapPoem1, char *destMapDesc, char *destMapPoem2) {
    if (mapCode >= 0 && mapCode <= 9) {
        int result = 0;
        switch (mapCode) {
            case 0:
                crsGetString(0, 40050, destMapKey, 32);
                crsGetString(0, 40000, destMapName, 32);
                crsGetString(0, 40020, destMapPoem1, 256);
                crsGetString(0, 40030, destMapDesc, 256);
                crsGetString(0, 40040, destMapPoem2, 256);
                result = 1;
                break;
            case 1:
                crsGetString(0, 40051, destMapKey, 32);
                crsGetString(0, 40001, destMapName, 32);
                crsGetString(0, 40021, destMapPoem1, 256);
                crsGetString(0, 40031, destMapDesc, 256);
                crsGetString(0, 40041, destMapPoem2, 256);
                result = 1;
                break;
            case 2:
                crsGetString(0, 40052, destMapKey, 32);
                crsGetString(0, 40002, destMapName, 32);
                crsGetString(0, 40022, destMapPoem1, 256);
                crsGetString(0, 40032, destMapDesc, 256);
                crsGetString(0, 40042, destMapPoem2, 256);
                result = 1;
                break;
            case 3:
                crsGetString(0, 40053, destMapKey, 32);
                crsGetString(0, 40003, destMapName, 32);
                crsGetString(0, 40023, destMapPoem1, 256);
                crsGetString(0, 40033, destMapDesc, 256);
                crsGetString(0, 40043, destMapPoem2, 256);
                result = 1;
                break;
            case 4:
                crsGetString(0, 40054, destMapKey, 32);
                crsGetString(0, 40004, destMapName, 32);
                crsGetString(0, 40024, destMapPoem1, 256);
                crsGetString(0, 40034, destMapDesc, 256);
                crsGetString(0, 40044, destMapPoem2, 256);
                result = 1;
                break;
            case 5:
                crsGetString(0, 40055, destMapKey, 32);
                crsGetString(0, 40005, destMapName, 32);
                crsGetString(0, 40025, destMapPoem1, 256);
                crsGetString(0, 40035, destMapDesc, 256);
                crsGetString(0, 40045, destMapPoem2, 256);
                result = 1;
                break;
            case 6:
                crsGetString(0, 40056, destMapKey, 32);
                crsGetString(0, 40006, destMapName, 32);
                crsGetString(0, 40026, destMapPoem1, 256);
                crsGetString(0, 40036, destMapDesc, 256);
                crsGetString(0, 40046, destMapPoem2, 256);
                result = 1;
                break;
            case 7:
                crsGetString(0, 40057, destMapKey, 32);
                crsGetString(0, 40007, destMapName, 32);
                crsGetString(0, 40027, destMapPoem1, 256);
                crsGetString(0, 40037, destMapDesc, 256);
                crsGetString(0, 40047, destMapPoem2, 256);
                result = 1;
                break;
            case 8:
                crsGetString(0, 40058, destMapKey, 32);
                crsGetString(0, 40008, destMapName, 32);
                crsGetString(0, 40028, destMapPoem1, 256);
                crsGetString(0, 40038, destMapDesc, 256);
                crsGetString(0, 40048, destMapPoem2, 256);
                result = 1;
                break;
            case 9:
                crsGetString(0, 40059, destMapKey, 32);
                crsGetString(0, 40009, destMapName, 32);
                crsGetString(0, 40029, destMapPoem1, 256);
                crsGetString(0, 40039, destMapDesc, 256);
                crsGetString(0, 40049, destMapPoem2, 256);
                result = 1;
                break;
        }
        return result;
    } 
    else 
    {
		return 0;
	}
}

void* crsPipeWrapper(int fnId) {
    switch (fnId) {
        case 1:
            return reinterpret_cast<void*>(GetMapDescriptions);
        default:
            return nullptr;
    }
}

void _initExeImportFunctions() {
	HMODULE hModule = GetModuleHandle(NULL);
	if (hModule == NULL) {
		MessageBoxA(NULL, "crs.dll can't import exe file functions. This is bad!=(", "Info", MB_OK);
		return;
	}
    char crsGetStringName[] = "_crsGetString@16";
    crsGetString = (crsGetStringType)GetProcAddress(hModule, crsGetStringName);
	if (crsGetString == NULL) {
		MessageBoxA(NULL, "Функция _crsGetString@16 не найдена! Ошибка неизбежна..", "Info", MB_OK);
	}
}

void crsInitDll() {
	MessageBoxA(NULL, "Is succefully load crs.dll! This is good!=)", "Info", MB_OK);
	__initExeImportFunctions();
}
  • Вопрос задан
  • 53 просмотра
Подписаться 2 Простой 8 комментариев
Помогут разобраться в теме Все курсы
  • Яндекс Практикум
    Разработчик C++
    9 месяцев
    Далее
  • Нетология
    Специалист по информационной безопасности + нейросети
    12 месяцев
    Далее
  • Компьютерная академия «TOP»
    Учебная программа “Разработка программного обеспечения”
    30 месяцев
    Далее
Решения вопроса 1
@igreklpofrss Автор вопроса
Как оказалось не учел соглашение о вызовах.
__cdecl: очищает стек после каждого вызова (add esp, X после каждого вызова)
__stdcall: очищает стек внутри функции(retn X)

После добавления __stdcall компилятор решил проблему.
typedef int (__stdcall *crsGetStringType)(void *, int, char *Destination, int Count);

На Ассемблере проще писать, чем на С++ с учетом кучи тонкостей..
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы