/* FinalSun/FinalAlert 2 Mission Editor Copyright (C) 1999-2024 Electronic Arts, Inc. Authored by Matthias Wagner This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // // This is the application code. Mainly shows the main dialog and initializes some application global stuff // #include "stdafx.h" #include "FinalSun.h" #include "FinalSunDlg.h" #include "structs.h" #include "mapdata.h" #include "variables.h" #include "functions.h" #include "inlines.h" #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define APPID "FinalSun App" extern char AppPath[]; extern ACTIONDATA AD; ///////////////////////////////////////////////////////////////////////////// // CFinalSunApp BEGIN_MESSAGE_MAP(CFinalSunApp, CWinApp) //{{AFX_MSG_MAP(CFinalSunApp) //}}AFX_MSG_MAP ON_COMMAND(ID_HELP, CWinApp::OnHelp) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CFinalSunApp construction void __cdecl term_func() { if (MessageBox(0, "Fatal error. Exit FinalSun?", "Fatal error", MB_YESNO) == IDCANCEL) return; exit(-1); } CFinalSunApp::CFinalSunApp() { m_cf = RegisterClipboardFormat("FINAL*FORMAT"); if (!m_cf) MessageBox(0, "Failed to register clipboard format, clipboard functions not available", "", 0); tiledata_count = &t_tiledata_count; tiledata = &t_tiledata; // first: set up global variable AppPath // [02/16/2000] using GetModuleFileName() instead of GetCurrentDirectory(): always the correct path wchar_t AppPathUtf16[MAX_PATH] = { 0 }; GetModuleFileNameW(NULL, AppPathUtf16, MAX_PATH); strcpy_s(AppPath, utf16ToUtf8(AppPathUtf16).c_str()); *(strrchr(AppPath, '\\') + 1) = 0; // Initialize AppData const std::wstring AppDataPathFolder = utf8ToUtf16(u8AppDataPath.substr(0, u8AppDataPath.size() - 1)); auto create_dir_res = SHCreateDirectoryExW(NULL, AppDataPathFolder.c_str(), nullptr); if (ERROR_SUCCESS != create_dir_res && ERROR_ALREADY_EXISTS != create_dir_res) { wchar_t err_msg[1025] = { 0 }; FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, create_dir_res, 0, err_msg, 1024, NULL); MessageBoxW(NULL, err_msg, (std::wstring(L"Failed to open ") + AppDataPathFolder).c_str(), 0); exit(1); } if ((GetFileAttributesW(AppDataPathFolder.c_str()) & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) { MessageBoxW(NULL, (AppDataPathFolder + L" must be a directory, not a file").c_str(), (std::wstring(L"Failed to open ") + AppDataPathFolder).c_str(), 0); exit(1); } /*memset(t_tilepics, 0, sizeof(TILEPICDATA)*10000); memset(s_tilepics, 0, sizeof(TILEPICDATA)*10000);*/ m_Options.LanguageName = "English"; m_Options.bFlat = FALSE; m_Options.bEasy = FALSE; m_Options.bSupportMarbleMadness = FALSE; m_Options.bMarbleMadness = FALSE; auto log = u8AppDataPath; #ifdef TS_MODE log += "finalsunlog.txt"; #else log += "finalalert2log.txt"; #endif m_u8LogFileName = log; errstream.open(m_u8LogFileName, ios_base::trunc); errstream << "\uFEFF"; // BOM #ifdef TS_MODE errstream << "FinalSun log file" << std::endl << "----------------------" << std::endl << std::endl; #else errstream << "FinalAlert 2 log file" << std::endl << "----------------------" << std::endl << std::endl; #endif errstream << "CFinalSunApp::CFinalSunApp() called" << std::endl; errstream << "App Path: " << AppPath << std::endl; errstream << "AppData Path: " << u8AppDataPath << std::endl; errstream << "Locale: " << setlocale(LC_ALL, nullptr) << std::endl; errstream << "Windows ACP: " << GetACP() << std::endl; INITCOMMONCONTROLSEX ctr; ctr.dwSize = sizeof(INITCOMMONCONTROLSEX); ctr.dwICC = ICC_STANDARD_CLASSES; if (!InitCommonControlsEx(&ctr)) { errstream << "Error: Common controls could not be initialized" << std::endl; MessageBox(0, "Common controls could not be initialized.", "Error", 0); } errstream << std::endl << std::endl << std::endl; errstream.flush(); } ///////////////////////////////////////////////////////////////////////////// // CFinalSunApp initialization BOOL CFinalSunApp::InitInstance() { m_hAccel = LoadAccelerators(this->m_hInstance, MAKEINTRESOURCE(IDR_MAIN)); #ifndef NOSURFACES if (GetDeviceCaps(GetDC(GetDesktopWindow()), BITSPIXEL) <= 8) { MessageBox(0, "You currently only have 8 bit color mode enabled. This is not recommended. You can continue, but this will cause a significant slowdown while loading graphics, and result in poor graphics quality", "Error", 0); //exit(0); } #else if (GetDeviceCaps(GetDC(GetDesktopWindow()), BITSPIXEL) <= 8) { MessageBox(0, "You currently only have 8 bit color mode enabled. FinalSun/FinalAlert 2 will not work in 8 bit color mode. See readme.txt for further information!", "Error", 0); exit(0); } #endif ParseCommandLine(); // Load application data std::string datafile = AppPath; #ifdef TS_MODE datafile += "\\FSData.ini"; #else datafile += "\\FAData.ini"; #endif g_data.LoadFile(datafile); // Load language data std::string languagefile = AppPath; #ifndef RA2_MODE languagefile += "\\FSLanguage.ini"; #else languagefile += "\\FALanguage.ini"; #endif language.LoadFile(languagefile); if (language.sections.size() == 0) { MessageBox(0, "FALanguage.ini does not exist or is not valid (download corrupt?)", "", 0); exit(0); } #ifndef RA2_MODE const std::string iniName = "FinalSun.ini"; const std::string defaultIniName = "FinalSunDefaults.ini"; #else const std::string iniName = "FinalAlert.ini"; const std::string defaultIniName = "FinalAlertDefaults.ini"; #endif // ok lets get some options CIniFile optini; std::string iniFile = u8AppDataPath + iniName; std::string templateIniFile = std::string(AppPath) + defaultIniName; bool copiedDefaultFile = false; if (!DoesFileExist(iniFile.c_str())) { if (CopyFileW(utf8ToUtf16(templateIniFile).c_str(), utf8ToUtf16(iniFile).c_str(), TRUE)) copiedDefaultFile = true; } optini.LoadFile(iniFile); #ifdef RA2_MODE CString game = "RA2"; CString app = "FinalAlert"; #else CString game = "TS"; CString app = "FinalSun"; #endif std::wstring key; #ifdef RA2_MODE key = L"Software\\Westwood\\Red Alert 2"; #else key = L"Software\\Westwood\\Tiberian Sun"; #endif auto& opts = m_Options; HKEY hKey = 0; int res; res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_EXECUTE/*KEY_ALL_ACCESS*/, &hKey); if (res != ERROR_SUCCESS) { std::wstring s = L"Failed to access registry. Using manual setting. Error was:\n"; wchar_t c[1024] = { 0 }; FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, c, 1023, NULL); MessageBoxW(0, (s + c).c_str(), L"Error", 0); opts.TSExe = optini.sections[game].values["Exe"]; } else { // key opened wchar_t path[MAX_PATH + 1] = { 0 }; DWORD pathsize = MAX_PATH; DWORD type = REG_SZ; if ((res = RegQueryValueExW(hKey, L"InstallPath", 0, &type, (unsigned char*)path, &pathsize)) != ERROR_SUCCESS) { std::wstring s = L"Failed to access registry. Using manual setting. Error was:\n"; wchar_t c[1024] = { 0 }; FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, c, 1023, NULL); MessageBoxW(0, (s + c).c_str(), L"Error", 0); opts.TSExe = optini.sections[game].values["Exe"]; } else opts.TSExe = path; } if (copiedDefaultFile || optini.sections.size() == 0 || opts.TSExe.GetLength() == 0 || optini.sections[app].values["Language"].GetLength() == 0 || optini.sections[app].values.find("FileSearchLikeGame") == optini.sections[app].values.end() || optini.sections[app].values.find("PreferLocalTheaterFiles") == optini.sections[app].values.end()) { opts.bSearchLikeTS = TRUE; bOptionsStartup = TRUE; ShowOptionsDialog(); bOptionsStartup = FALSE; } else { opts.LanguageName = optini.sections[app].values["Language"]; if (optini.sections[app].values["FileSearchLikeGame"] != "no") opts.bSearchLikeTS = TRUE; else opts.bSearchLikeTS = FALSE; } opts.bPreferLocalTheaterFiles = optini.sections[app].values.emplace("PreferLocalTheaterFiles", opts.bPreferLocalTheaterFiles ? "1" : "0").first->second == "1"; opts.bDoNotLoadAircraftGraphics = optini.sections["Graphics"].values["NoAircraftGraphics"] == "1"; opts.bDoNotLoadVehicleGraphics = optini.sections["Graphics"].values["NoVehicleGraphics"] == "1"; opts.bDoNotLoadBuildingGraphics = optini.sections["Graphics"].values["NoBuildingGraphics"] == "1"; opts.bDoNotLoadInfantryGraphics = optini.sections["Graphics"].values["NoInfantryGraphics"] == "1"; opts.bDoNotLoadTreeGraphics = optini.sections["Graphics"].values["NoTreeGraphics"] == "1"; opts.bDoNotLoadSnowGraphics = optini.sections["Graphics"].values["NoSnowGraphics"] == "1"; opts.bDoNotLoadTemperateGraphics = optini.sections["Graphics"].values["NoTemperateGraphics"] == "1"; opts.bDoNotLoadBMPs = optini.sections["Graphics"].values["NoBMPs"] == "1"; opts.bDoNotLoadOverlayGraphics = optini.sections["Graphics"].values["NoOverlayGraphics"] == "1"; opts.bVSync = optini.sections["Graphics"].values.emplace("VSync", opts.bVSync ? "1" : "0").first->second == "1"; opts.bDisableAutoShore = optini.sections["UserInterface"].values["DisableAutoShore"] == "1"; opts.bDisableAutoLat = optini.sections["UserInterface"].values["DisableAutoLat"] == "1"; opts.bNoSounds = optini.sections["UserInterface"].values["Sounds"] != "1"; opts.bDisableSlopeCorrection = optini.sections["UserInterface"].values["DisableSlopeCorrection"] == "1"; opts.fLoadScreenDelayInSeconds = static_cast(atof(optini.sections["UserInterface"].values.emplace("LoadScreenDelay", std::to_string(opts.fLoadScreenDelayInSeconds).c_str()).first->second)); opts.bShowStats = optini.sections["UserInterface"].values.emplace("ShowStats", opts.bShowStats ? "1" : "0").first->second == "1"; opts.bHighResUI = optini.sections["UserInterface"].values.emplace("HighRes", opts.bHighResUI ? "1" : "0").first->second == "1"; opts.useDefaultMouseCursor = optini.sections["UserInterface"].values.emplace("UseDefaultMouseCursor", opts.useDefaultMouseCursor ? "1" : "0").first->second == "1"; opts.fMiniMapScale = static_cast(atof(optini.sections["MiniMap"].values.emplace("Scale", std::to_string(opts.fMiniMapScale).c_str()).first->second)); auto defaultViewSteps = CString(Join(",", opts.viewScaleSteps | std::views::transform([](auto v) {return std::to_string(v); })).c_str()); auto viewScaleStepsRange = SplitParams(optini.sections["UserInterface"].values.emplace("ViewScaleSteps", defaultViewSteps).first->second) | std::views::transform([](auto v) { return static_cast(std::atof(v)); }); opts.viewScaleSteps.assign(viewScaleStepsRange.begin(), viewScaleStepsRange.end()); opts.viewScaleUseSteps = optini.sections["UserInterface"].values.emplace("ViewScaleUseSteps", opts.viewScaleUseSteps ? "1" : "0").first->second == "1"; opts.viewScaleSpeed = static_cast(atof(optini.sections["UserInterface"].values.emplace("ViewScaleSpeed", std::to_string(opts.viewScaleSpeed).c_str()).first->second)); // MW 07/19/01 opts.bShowCells = optini.sections["UserInterface"].values["ShowBuildingCells"] == "1"; optini.SaveFile(iniFile); // MW 07/20/01: Load file list int i; for (i = 0;i < 4;i++) { char c[50]; itoa(i, c, 10); opts.prev_maps[i] = optini.sections["Files"].values[c]; } if (opts.bDoNotLoadTemperateGraphics && opts.bDoNotLoadSnowGraphics) { MessageBox(0, "You have turned off loading of both snow and temperate terrain in 'FinalAlert.ini'. At least one of these must be loaded. The application will now quit.", "Error", 0); exit(-982); } int EasyView; if (optini.sections["UserInterface"].FindName("EasyView") < 0) { MessageBox(0, GetLanguageStringACP("ExplainEasyView"), GetLanguageStringACP("ExplainEasyViewCap"), 0); EasyView = 1; optini.LoadFile(iniFile); optini.sections["UserInterface"].values["EasyView"] = "1"; optini.SaveFile(iniFile); } else { EasyView = atoi(optini.sections["UserInterface"].values["EasyView"]); } if (EasyView != 0) theApp.m_Options.bEasy = TRUE; CString cTSPath = theApp.m_Options.TSExe; auto lastSlash = cTSPath.ReverseFind('\\'); if (lastSlash >= 0) cTSPath.SetAt(lastSlash + 1, 0); strcpy(TSPath, cTSPath); // MW 01/23/2013: changed the global CMapData Map to a global CMapData* to get rid of static initialization/shutdown problems { std::unique_ptr mapData(new CMapData()); Map = mapData.get(); CLoading loading(NULL); m_loading = &loading; CFinalSunDlg dlg; m_pMainWnd = &dlg; dlg.DoModal(); } // Map and dialog closed, do further work if required return FALSE; } void CFinalSunApp::ParseCommandLine() { #ifdef 0 // Removed as it can conflict with Steam game arguments! -LF 23.02.2024 char data[MAX_PATH + 30]; strcpy(data, theApp.m_lpCmdLine); if (strlen(data) == 0) { strcpy(currentMapFile, ""); return; } strcpy(currentMapFile, data); #endif } void CFinalSunApp::ShowTipAtStartup(void) { CTipDlg dlg; if (dlg.m_bStartup) dlg.DoModal(); } void CFinalSunApp::ShowTipOfTheDay(void) { CTipDlg dlg; dlg.DoModal(); } int CFinalSunApp::Run() { return CWinApp::Run(); } BOOL CFinalSunApp::ProcessMessageFilter(int code, LPMSG lpMsg) { if (lpMsg->message == WM_KEYDOWN) { /*if(lpMsg->wParam==VK_F1) { if(ShellExecute(0, NULL, (CString)AppPath+"/help/index.htm", NULL, NULL, SW_NORMAL)==0) { MessageBox(0,(CString)"Could not open manual! Try opening "+(CString)AppPath+(CString)"/help/index.htm manually","Error",0); } return TRUE; }*/ // check AD mode if (lpMsg->wParam == VK_PRIOR || lpMsg->wParam == VK_NEXT) { if (lpMsg->wParam == VK_PRIOR) { AD.z_data += 1; } if (lpMsg->wParam == VK_NEXT) { AD.z_data -= 1; } if (AD.mode == ACTIONMODE_SETTILE || AD.mode == ACTIONMODE_PASTE) { CIsoView* v = ((CFinalSunDlg*)theApp.m_pMainWnd)->m_view.m_isoview; CTerrainDlg& tdlg = ((CFinalSunDlg*)theApp.m_pMainWnd)->m_view.m_browser->m_bar; WORD isox, isoy; RECT r; POINT p; v->GetWindowRect(&r); GetCursorPos(&p); isox = p.x - r.left; isoy = p.y - r.top; if (isox > r.right - r.left || isoy > r.bottom - r.top) { } else { LPARAM lParam; memcpy(&lParam, &isox, 2); memcpy((BYTE*)&lParam + 2, &isoy, 2); v->SendMessage(WM_MOUSEMOVE, 0, lParam); } } } if (AD.mode == ACTIONMODE_SETTILE) { CIsoView* v = ((CFinalSunDlg*)theApp.m_pMainWnd)->m_view.m_isoview; CTerrainDlg& tdlg = ((CFinalSunDlg*)theApp.m_pMainWnd)->m_view.m_browser->m_bar; WORD isox, isoy; RECT r; POINT p; v->GetWindowRect(&r); GetCursorPos(&p); isox = p.x - r.left; isoy = p.y - r.top; if (isox > r.right - r.left || isoy > r.bottom - r.top) { } else { LPARAM lParam; memcpy(&lParam, &isox, 2); memcpy((BYTE*)&lParam + 2, &isoy, 2); int searchedTileSet; int z; switch (lpMsg->wParam) { case VK_LEFT: if (AD.type > 0 && (*tiledata)[AD.type].wTileSet == (*tiledata)[AD.type - 1].wTileSet) { AD.type -= 1; AD.z_data = 0; } v->SendMessage(WM_MOUSEMOVE, 0, lParam); return 0; break; case VK_RIGHT: if (AD.type < (*tiledata_count) - 1 && (*tiledata)[AD.type].wTileSet == (*tiledata)[AD.type + 1].wTileSet) { AD.type += 1; AD.z_data = 0; } v->SendMessage(WM_MOUSEMOVE, 0, lParam); return 0; break; case VK_UP: searchedTileSet = (*tiledata)[AD.type].wTileSet - 1; case VK_DOWN: if (lpMsg->wParam == VK_DOWN) searchedTileSet = (*tiledata)[AD.type].wTileSet + 1; if (searchedTileSet >= 0 && searchedTileSet < tilesets_start.size()) { int i; int s = 1; int start = 0; int count = tilesets_start.size() - searchedTileSet; if (searchedTileSet < (*tiledata)[AD.type].wTileSet) { s = -1; start = 0; count = searchedTileSet + 1; } for (i = start;i < count;i += 1) { if (searchedTileSet + i * s < 0 || searchedTileSet + i * s >= tilesets_start.size()) continue; int pos = tilesets_start[searchedTileSet + i * s]; int e; CComboBox* cb = ((CComboBox*)tdlg.GetDlgItem(IDC_TILESET)); BOOL bFound = FALSE; for (e = 0;e < cb->GetCount();e++) { if (cb->GetItemData(e) == searchedTileSet + i * s) { cb->SendMessage(CB_SETCURSEL, e, 0); bFound = TRUE; break; } } if (bFound) { AD.type = tilesets_start[searchedTileSet + i * s]; AD.data = 0; AD.data2 = 0; AD.data3 = 0; AD.data_s = ""; v->SendMessage(WM_MOUSEMOVE, 0, lParam); return 0; } } return 0; break; } return 0; break; } } } } return CWinApp::ProcessMessageFilter(code, lpMsg); } LRESULT CFinalSunApp::ProcessWndProcException(CException* e, const MSG* pMsg) { // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen e->ReportError(MB_YESNO); return TRUE; //return CWinApp::ProcessWndProcException(e, pMsg); } BOOL CFinalSunApp::PreTranslateMessage(MSG* pMsg) { if (!TranslateAccelerator( *this->m_pMainWnd, // handle to receiving window m_hAccel, // handle to active accelerator table pMsg)) // message data return CWinApp::PreTranslateMessage(pMsg); return FALSE; } BOOL CFinalSunApp::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { return CWinApp::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } BOOL CFinalSunApp::OnIdle(LONG lCount) { ((CFinalSunDlg*)m_pMainWnd)->m_view.m_isoview->UpdateWindow(); //MessageBox(0,"Idled","",0); return CWinApp::OnIdle(lCount); }