/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** 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 . */ // MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "mixviewer.h" #include "mainfrm.h" #include "wwstring.h" #include "duplicatecombiner.h" #include "wwfile.h" #include "ffactory.h" #include "mixfile.h" #include "avassetsuck.h" #include "mixpatchmaker.h" #include "ffactory.h" #include "mixfile.h" #include "mixviewerdoc.h" #include "makemixfiledialog.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() ON_COMMAND(IDM_COMBINE_DUPLICATES, OnCombineDuplicates) ON_COMMAND(IDM_CREATE_MIX_FILE, OnCreateMixFile) ON_COMMAND(IDM_EXPORT_FILES, OnExportFiles) ON_COMMAND(IDM_REMOVE_AV_ASSETS, OnRemoveAVAssets) ON_COMMAND(IDM_MAKE_MIX_PATCH, OnMakeMixPatch) ON_WM_DROPFILES() //}}AFX_MSG_MAP END_MESSAGE_MAP() static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, }; ///////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { // TODO: add member initialization code here } CMainFrame::~CMainFrame() { } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar\n"); return -1; // fail to create } if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don't want the toolbar to // be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); return 0; } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef _DEBUG void CMainFrame::AssertValid() const { CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CFrameWnd::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers //////////////////////////////////////////////////////////////////////////// // // Get_Filename_From_Path // //////////////////////////////////////////////////////////////////////////// CString Get_Filename_From_Path (LPCTSTR path) { // Find the last occurance of the directory deliminator LPCTSTR filename = ::strrchr (path, '\\'); if (filename != NULL) { // Increment past the directory deliminator filename ++; } else { filename = path; } // Return the filename part of the path return CString (filename); } //////////////////////////////////////////////////////////////////////////// // // Strip_Filename_From_Path // //////////////////////////////////////////////////////////////////////////// CString Strip_Filename_From_Path (LPCTSTR path) { // Copy the path to a buffer we can modify TCHAR temp_path[MAX_PATH]; ::lstrcpy (temp_path, path); // Find the last occurance of the directory deliminator LPTSTR filename = ::strrchr (temp_path, '\\'); if (filename != NULL) { // Strip off the filename filename[0] = 0; } else if ((temp_path[1] != ':') && (temp_path[1] != '\\')) { temp_path[0] = 0; } // Return the path only return StringClass (temp_path); } ///////////////////////////////////////////////////////////////////////////// // // OnCombineDuplicates // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::OnCombineDuplicates (void) { CFileDialog dialog ( TRUE, ".dat", "always.dat", OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this); dialog.m_ofn.lpstrTitle = "Pick Shared Mix File"; if (dialog.DoModal () == IDOK) { DuplicateRemoverClass combiner; // // Determine what directory to search // StringClass full_path = dialog.GetPathName (); StringClass directory = Strip_Filename_From_Path (full_path); combiner.Set_Destination_File (full_path); WIN32_FIND_DATA find_info = { 0 }; BOOL keep_going = TRUE; CString search_mask = directory + "\\*.mix"; // // Loop over all the mix files in the search directory // int count = 0; for (HANDLE find_handle = ::FindFirstFile (search_mask, &find_info); (find_handle != INVALID_HANDLE_VALUE) && keep_going; keep_going = ::FindNextFile (find_handle, &find_info)) { // // Check to make sure this isn't a directory // if (!(find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { StringClass full_path = directory; full_path += "\\"; full_path += find_info.cFileName; combiner.Add_Mix_File (full_path); count ++; } } // // Close the handle we needed for the file find routines // if (find_handle != INVALID_HANDLE_VALUE) { ::FindClose (find_handle); } // // Combine the files (if necessary) // if (count > 0) { combiner.Process (); } } return ; } ///////////////////////////////////////////////////////////////////////////// // // OnCreateMixFile // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::OnCreateMixFile (void) { MakeMixFileDialogClass dialog( this ); dialog.DoModal(); return ; } ///////////////////////////////////////////////////////////////////////////// // // OnExportFiles // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::OnExportFiles (void) { StringClass current_filename; CDocument * doc = GetActiveDocument(); if ( doc != NULL ) { current_filename = doc->GetPathName(); } if ( !current_filename.Is_Empty() ) { DynamicVectorClass filenames; MixFileFactoryClass mix_factory (current_filename, _TheFileFactory); if ( mix_factory.Build_Filename_List( filenames ) ) { // Make the destination folder StringClass export_path = current_filename; export_path += " Files"; int result = (int)::CreateDirectory( export_path, NULL ); int error = ::GetLastError(); if ( result != 0 || error == ERROR_ALREADY_EXISTS ) { bool read_error = false; bool write_error = false; for (int index = 0; index < filenames.Count (); index ++) { StringClass source_name = filenames[index]; StringClass dest_name; dest_name = export_path; dest_name += "\\"; dest_name += filenames[index]; // If the filename contains a /, create the directory if ( ::strchr( filenames[index], '\\' ) != NULL ) { StringClass dest_folder = dest_name; int length = ::strrchr( dest_folder, '\\' ) - dest_folder; dest_folder.Erase( length, dest_folder.Get_Length() - length ); int result = (int)::CreateDirectory( dest_folder, NULL ); int error = ::GetLastError(); if ( result == 0 && error != ERROR_ALREADY_EXISTS ) { StringClass message; message.Format ("Failed to create folder %s.", dest_folder ); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); return; } } FileClass * source_file = mix_factory.Get_File( source_name ); FileClass * dest_file = _TheFileFactory->Get_File( dest_name ); if ( dest_file ) { if ( dest_file->Open( FileClass::WRITE ) == 0 ) { StringClass message; message.Format ("Failed to open %s for writing.", dest_name ); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); return; } } if ( source_file ) { if ( source_file->Open() == 0 ) { StringClass message; message.Format ("Failed to open %s for reading.", source_name ); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); return; } } if ( source_file && source_file->Is_Available() && dest_file && dest_file->Is_Available() ) { int length = source_file->Size(); unsigned char file_buffer[4096]; while ( length > 0 ) { int amount = min ( (int)length, (int)sizeof( file_buffer ) ); if ( source_file->Read( &(file_buffer[0]), amount ) != amount ) { read_error = true; length = 0; } if ( dest_file->Write( &file_buffer[0], amount ) != amount ) { write_error = true; length = 0; } length -= amount; } } if ( source_file != NULL ) { mix_factory.Return_File( source_file ); } if ( dest_file != NULL ) { _TheFileFactory->Return_File( dest_file ); } if ( read_error ) { StringClass message; message.Format ("Read Error on %s.", source_name ); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); return; } if ( write_error ) { StringClass message; message.Format ("Write Error on %s.", dest_name ); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); return; } // filenames[index]; } } else { StringClass message; message.Format ("Failed to create folder %s.", export_path); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); } } else { StringClass message; message.Format ("Error reading the filename list from %s.", current_filename); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); } } else { StringClass message; message.Format ("No Current Mix File."); MessageBox (message, "Mix File Error", MB_ICONERROR | MB_OK); } return ; } ///////////////////////////////////////////////////////////////////////////// // // OnRemoveAVAssets // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::OnRemoveAVAssets (void) { CFileDialog dialog(true, ".mix", NULL, OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this); dialog.m_ofn.lpstrTitle = "Pick Mix Files"; char *file_name_buffer = new char [65536]; *file_name_buffer = 0; dialog.m_ofn.lpstrFile = file_name_buffer; dialog.m_ofn.nMaxFile = 65535; if (dialog.DoModal () == IDOK) { // // Process each mix file and put the stripped mix file into a 'temp' subdirectory under the current one. // POSITION pos = dialog.GetStartPosition(); while (pos != NULL) { StringClass file_name = dialog.GetNextPathName(pos); // // Get just the path portion. // char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char path[_MAX_PATH]; char name[_MAX_PATH]; char ext[_MAX_PATH]; _splitpath(file_name, drive, dir, name, ext); // // Make sure the temp directory exists. // strcat(dir, "temp\\"); _makepath(path, drive, dir, NULL, NULL); CreateDirectory(path, NULL); // // Make the output file name. // char output_file[_MAX_PATH]; _makepath(output_file, drive, dir, name, ext); AVAssetSuckerClass sucker; sucker.Suck(file_name.Peek_Buffer(), output_file); } } delete [] file_name_buffer; } ///////////////////////////////////////////////////////////////////////////// // // OnMakeMixPatch // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::OnMakeMixPatch(void) { // // Ask for the name of the old source mix file. // CFileDialog dialog(true, ".mix", NULL, OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this); dialog.m_ofn.lpstrTitle = "Pick Old Source Mix File"; char file_name_buffer[1024]; file_name_buffer[0] = 0; dialog.m_ofn.lpstrFile = file_name_buffer; dialog.m_ofn.nMaxFile = 1024; if (dialog.DoModal () == IDOK) { // // Pull the name of the old source mix file. // POSITION pos = dialog.GetStartPosition(); StringClass old_file_name = dialog.GetNextPathName(pos); // // Ask for the name of the new source mix file. // CFileDialog dialog2(true, ".mix", NULL, OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this); dialog2.m_ofn.lpstrTitle = "Pick New Source Mix File"; file_name_buffer[0] = 0; dialog2.m_ofn.lpstrFile = file_name_buffer; dialog2.m_ofn.nMaxFile = 1024; if (dialog2.DoModal () == IDOK) { // // Pull the name of the new source mix file. // pos = dialog2.GetStartPosition(); StringClass new_file_name = dialog2.GetNextPathName(pos); // // Ask for the name of the output mix file. // CFileDialog dialog3(true, ".mix", NULL, OFN_HIDEREADONLY | OFN_EXPLORER, "Mix File (*.dat, *.mix)|*.dat;*.mix||", this); dialog3.m_ofn.lpstrTitle = "Pick Output Patch Mix File"; file_name_buffer[0] = 0; dialog3.m_ofn.lpstrFile = file_name_buffer; dialog3.m_ofn.nMaxFile = 1024; if (dialog3.DoModal () == IDOK) { // // Pull the name of the new source mix file. // pos = dialog3.GetStartPosition(); StringClass out_file_name = dialog3.GetNextPathName(pos); // // Ask for the directory containing the old source art. // char path[_MAX_PATH + 256]; BROWSEINFO binfo; binfo.hwndOwner = NULL; binfo.pidlRoot = NULL; binfo.pszDisplayName = path; binfo.lpszTitle = "Select Old Source Art Directory"; binfo.ulFlags = 0; binfo.lpfn = NULL; binfo.lParam = 0; binfo.iImage = 0; LPITEMIDLIST itemptr = SHBrowseForFolder(&binfo); if (itemptr) { char old_art_dir[_MAX_PATH + 256]; if (SHGetPathFromIDList(itemptr, old_art_dir)) { // // Ask for the directory containing the new source art. // char path[_MAX_PATH + 256]; BROWSEINFO binfo; binfo.hwndOwner = NULL; binfo.pidlRoot = NULL; binfo.pszDisplayName = path; binfo.lpszTitle = "Select New Source Art Directory"; binfo.ulFlags = 0; binfo.lpfn = NULL; binfo.lParam = 0; binfo.iImage = 0; itemptr = SHBrowseForFolder(&binfo); if (itemptr) { char new_art_dir[_MAX_PATH + 256]; if (SHGetPathFromIDList(itemptr, new_art_dir)) { MixPatchMakerClass patcher; patcher.Make_Patch(old_file_name.Peek_Buffer(), new_file_name.Peek_Buffer(), out_file_name.Peek_Buffer(), old_art_dir, new_art_dir); } } } } } } } } ///////////////////////////////////////////////////////////////////////////// // // WindowProc // ///////////////////////////////////////////////////////////////////////////// LRESULT CMainFrame::WindowProc ( UINT message, WPARAM wParam, LPARAM lParam ) { return CFrameWnd::WindowProc(message, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////// // // OnDropFiles // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::OnDropFiles (HDROP hDropInfo) { SetActiveWindow (); CWinApp *win_app = AfxGetApp (); ASSERT (win_app != NULL); // // Get the count of files from the drop query // int file_count = (int)::DragQueryFile (hDropInfo, (UINT)-1, NULL, 0); if (file_count > 0) { CMixViewerDoc *doc = (CMixViewerDoc *)GetActiveDocument (); if (file_count == 1) { // // Get the filename... // TCHAR filename[_MAX_PATH]; ::DragQueryFile (hDropInfo, 0, filename, _MAX_PATH); // // Get the extension from the filename // char extension[_MAX_EXT] = { 0 }; ::_splitpath (filename, NULL, NULL, NULL, extension); // // Is this a mix file, or should we be adding this file to the mix? // if ( ::lstrcmpi (extension, ".mix") == 0 || ::lstrcmpi (extension, ".dat") == 0 || ::lstrcmpi (extension, ".dbs") == 0) { win_app->OpenDocumentFile (filename); } else { Add_To_Mix_File (doc->GetPathName (), filename); doc->Reload_Views (); } } else { DynamicVectorClass file_list; // // Loop over each of the dropped files and add them to the current mix file // for (int index = 0; index < file_count; index ++) { // // Add this file to a list so we can batch add all the files... // TCHAR filename[_MAX_PATH]; ::DragQueryFile (hDropInfo, index, filename, _MAX_PATH); file_list.Add (filename); } ::DragFinish(hDropInfo); // // Add the mix files to the list // Add_To_Mix_File (doc->GetPathName (), file_list); doc->Reload_Views (); } } return ; } ///////////////////////////////////////////////////////////////////////////// // // Add_To_Mix_File // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::Add_To_Mix_File (const char *mix_filename, const char *filename) { DynamicVectorClass file_list; file_list.Add (filename); Add_To_Mix_File (mix_filename, file_list); return ; } ///////////////////////////////////////////////////////////////////////////// // // Add_To_Mix_File // ///////////////////////////////////////////////////////////////////////////// void CMainFrame::Add_To_Mix_File (const char *mix_filename, DynamicVectorClass &new_file_list) { // // Set the current directory... // CString path = Strip_Filename_From_Path (mix_filename); if (path.GetLength () > 0) { ::SetCurrentDirectory (path); } // // Get access to the mix file in qestion // MixFileFactoryClass *mix_factory = new MixFileFactoryClass (mix_filename, _TheFileFactory); if (mix_factory->Is_Valid () && mix_factory->Build_Internal_Filename_List ()) { // // Now add the new files into the mix file... // for (int index = 0; index < new_file_list.Count (); index ++) { CString filename = ::Get_Filename_From_Path (new_file_list[index]); mix_factory->Delete_File (filename); mix_factory->Add_File (new_file_list[index], filename); } // // Do it! // mix_factory->Flush_Changes (); } // // Free the mix factory // delete mix_factory; mix_factory = NULL; return ; }