#pragma once #include #include #include #include #include #include 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(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(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(readme.size()), &written, nullptr); CloseHandle(hReadme); } outMessage = "Игра успешно экспортирована в:\n" + dest.string() + "\n\nСоздан автономный пакет с исполняемым файлом " + safeName + ".exe, ассетами и лаунчером."; return true; } };