178 lines
8.4 KiB
C++
178 lines
8.4 KiB
C++
#pragma once
|
||
#include <string>
|
||
#include <filesystem>
|
||
#include <vector>
|
||
#include <cstdlib>
|
||
#include <cstdio>
|
||
#include <windows.h>
|
||
|
||
namespace fs = std::filesystem;
|
||
|
||
struct EblanProject {
|
||
std::string title = "EBLAN_GAME";
|
||
std::string author = "Developer";
|
||
std::string version = "1.0.0";
|
||
std::string startupMap = "assets/map.dat";
|
||
std::string windowTitle = "EBLAN ENGINE 3D - Standalone Retro Game";
|
||
int defaultWidth = 1280;
|
||
int defaultHeight = 960;
|
||
|
||
bool Save(const std::string& path) const {
|
||
HANDLE hFile = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (hFile == INVALID_HANDLE_VALUE) return false;
|
||
char numBuf[32];
|
||
std::string content = "[Project]\r\n";
|
||
content += "Title=" + title + "\r\n";
|
||
content += "Author=" + author + "\r\n";
|
||
content += "Version=" + version + "\r\n";
|
||
content += "StartupMap=" + startupMap + "\r\n";
|
||
content += "WindowTitle=" + windowTitle + "\r\n";
|
||
snprintf(numBuf, sizeof(numBuf), "%d", defaultWidth);
|
||
content += std::string("DefaultWidth=") + numBuf + "\r\n";
|
||
snprintf(numBuf, sizeof(numBuf), "%d", defaultHeight);
|
||
content += std::string("DefaultHeight=") + numBuf + "\r\n";
|
||
DWORD written = 0;
|
||
WriteFile(hFile, content.data(), static_cast<DWORD>(content.size()), &written, nullptr);
|
||
CloseHandle(hFile);
|
||
return true;
|
||
}
|
||
|
||
bool Load(const std::string& path) {
|
||
HANDLE hFile = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (hFile == INVALID_HANDLE_VALUE) return false;
|
||
DWORD size = GetFileSize(hFile, nullptr);
|
||
if (size == INVALID_FILE_SIZE) { CloseHandle(hFile); return false; }
|
||
std::string buffer(size, '\0');
|
||
DWORD read = 0;
|
||
ReadFile(hFile, buffer.data(), size, &read, nullptr);
|
||
CloseHandle(hFile);
|
||
|
||
size_t start = 0;
|
||
while (start < buffer.size()) {
|
||
size_t end = buffer.find('\n', start);
|
||
std::string line = (end == std::string::npos) ? buffer.substr(start) : buffer.substr(start, end - start);
|
||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||
|
||
if (!line.empty() && line[0] != ';' && line[0] != '#') {
|
||
auto eq = line.find('=');
|
||
if (eq != std::string::npos) {
|
||
std::string key = line.substr(0, eq);
|
||
std::string val = line.substr(eq + 1);
|
||
if (key == "Title") title = val;
|
||
else if (key == "Author") author = val;
|
||
else if (key == "Version") version = val;
|
||
else if (key == "StartupMap") startupMap = val;
|
||
else if (key == "WindowTitle") windowTitle = val;
|
||
else if (key == "DefaultWidth") defaultWidth = std::atoi(val.c_str());
|
||
else if (key == "DefaultHeight") defaultHeight = std::atoi(val.c_str());
|
||
}
|
||
}
|
||
|
||
if (end == std::string::npos) break;
|
||
start = end + 1;
|
||
}
|
||
return true;
|
||
}
|
||
};
|
||
|
||
class StandaloneExporter {
|
||
public:
|
||
static bool ExportGame(const EblanProject& project, const std::string& destDir, const std::string& projectRoot, std::string& outMessage) {
|
||
std::error_code ec;
|
||
fs::path root(projectRoot);
|
||
fs::path dest(destDir);
|
||
|
||
if (!fs::exists(dest, ec)) {
|
||
fs::create_directories(dest, ec);
|
||
if (ec) {
|
||
outMessage = "Ошибка создания папки экспорта: " + ec.message();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 1. Locate engine.exe
|
||
fs::path srcExe = root / "engine.exe";
|
||
if (!fs::exists(srcExe, ec)) {
|
||
srcExe = root.parent_path() / "engine.exe";
|
||
}
|
||
if (!fs::exists(srcExe, ec)) {
|
||
outMessage = "Не найден скомпилированный движок engine.exe. Сначала выполните компиляцию (F7).";
|
||
return false;
|
||
}
|
||
|
||
// 2. Sanitize executable name
|
||
std::string safeName = project.title;
|
||
for (char& c : safeName) {
|
||
if (c == ' ' || c == '\\' || c == '/' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') {
|
||
c = '_';
|
||
}
|
||
}
|
||
if (safeName.empty()) safeName = "Game";
|
||
|
||
fs::path targetExe = dest / (safeName + ".exe");
|
||
if (!CopyFileA(srcExe.string().c_str(), targetExe.string().c_str(), FALSE)) {
|
||
outMessage = "Ошибка копирования исполняемого файла игры.";
|
||
return false;
|
||
}
|
||
|
||
// 3. Copy assets directory
|
||
fs::path srcAssets = root / "assets";
|
||
if (!fs::exists(srcAssets, ec)) {
|
||
srcAssets = root.parent_path() / "assets";
|
||
}
|
||
if (fs::exists(srcAssets, ec)) {
|
||
fs::path targetAssets = dest / "assets";
|
||
fs::create_directories(targetAssets, ec);
|
||
for (const auto& entry : fs::recursive_directory_iterator(srcAssets, ec)) {
|
||
if (ec) break;
|
||
auto rel = fs::relative(entry.path(), srcAssets, ec);
|
||
fs::path targetPath = targetAssets / rel;
|
||
if (entry.is_directory()) {
|
||
fs::create_directories(targetPath, ec);
|
||
} else if (entry.is_regular_file()) {
|
||
fs::create_directories(targetPath.parent_path(), ec);
|
||
CopyFileA(entry.path().string().c_str(), targetPath.string().c_str(), FALSE);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. Create launcher script PlayGame.bat
|
||
fs::path launcherPath = dest / "PlayGame.bat";
|
||
HANDLE hBat = CreateFileA(launcherPath.string().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (hBat != INVALID_HANDLE_VALUE) {
|
||
std::string batContent = "@echo off\r\ntitle " + safeName + "\r\ncd /d \"%~dp0\"\r\nstart \"\" \"" + safeName + ".exe\"\r\n";
|
||
DWORD written = 0;
|
||
WriteFile(hBat, batContent.data(), static_cast<DWORD>(batContent.size()), &written, nullptr);
|
||
CloseHandle(hBat);
|
||
}
|
||
|
||
// 5. Create README.txt
|
||
fs::path readmePath = dest / "README.txt";
|
||
HANDLE hReadme = CreateFileA(readmePath.string().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (hReadme != INVALID_HANDLE_VALUE) {
|
||
std::string readme = "==============================================================================\r\n";
|
||
readme += project.title + " (v" + project.version + ")\r\n";
|
||
readme += "Created with EblanStudio & EBLAN ENGINE 3D [Pure x86 Assembly]\r\n";
|
||
readme += "Author: " + project.author + "\r\n";
|
||
readme += "==============================================================================\r\n\r\n";
|
||
readme += "УПРАВЛЕНИЕ:\r\n";
|
||
readme += " W, A, S, D - Перемещение / Стрейф\r\n";
|
||
readme += " Стрелки - Поворот камеры\r\n";
|
||
readme += " Shift - Бег / Спринт\r\n";
|
||
readme += " ПРОБЕЛ/Ctrl - Огонь / Стрельба\r\n";
|
||
readme += " E - Использование / Открытие дверей / Переключатели\r\n";
|
||
readme += " M - Радар / Карта\r\n";
|
||
readme += " C - Перезапуск кат-сцены\r\n";
|
||
readme += " R - Перезапуск уровня при гибели\r\n";
|
||
readme += " F12 - Сделать скриншот (screenshot.bmp)\r\n";
|
||
readme += " ESC - Выход\r\n\r\n";
|
||
readme += "Zero External Dependencies: работает на любой версии Windows без DirectX/C++ runtimes!\r\n";
|
||
DWORD written = 0;
|
||
WriteFile(hReadme, readme.data(), static_cast<DWORD>(readme.size()), &written, nullptr);
|
||
CloseHandle(hReadme);
|
||
}
|
||
|
||
outMessage = "Игра успешно экспортирована в:\n" + dest.string() + "\n\nСоздан автономный пакет с исполняемым файлом " + safeName + ".exe, ассетами и лаунчером.";
|
||
return true;
|
||
}
|
||
};
|