Descent3/manage/manage.cpp
2024-09-10 03:00:25 +03:00

3061 lines
94 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* 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 <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/manage/manage.cpp $
* $Revision: 103 $
* $Date: 10/10/01 11:32a $
* $Author: Matt $
*
* Jason should put something here
*
* $Log: /DescentIII/Main/manage/manage.cpp $
*
* 103 10/10/01 11:32a Matt
* Added system to check for errors when reading in add-on data.
*
* 102 10/08/01 1:50p Matt
* Added a case for gamefile pagetype to avoid int3
*
* 101 4/19/00 5:07p Matt
* From Duane for 1.4
* Added checks, asserts, and fixes for bad return values
*
* 100 3/20/00 12:27p Matt
* Merge of Duane's post-1.3 changes.
* Mac pilot directory stuff.
*
* 99 10/26/99 3:30p Jeff
* handle extra.gam addon tablefile
*
* 98 10/20/99 6:27p Jeff
* sped up addon page popping (by saving page offsets)
*
* 97 10/19/99 9:14p Chris
* Fixed a memory free bug
*
* 96 8/11/99 5:32p Jeff
* changes to fix addon tablefile support so it works correctly
*
* 95 7/28/99 2:29p Kevin
* Added macintosh DLL extentions (msl)
*
* 94 5/14/99 12:45p Matt
* Removed yet more static data
*
* 93 5/14/99 12:33p Matt
* Fixed another case of too much local data for the Mac.
*
* 92 5/13/99 8:36p Matt
* Made some local variables global to get around the 32K local variable
* limit on the Mac.
*
* 91 5/12/99 3:01p Matt
* Declared one texpage structure statically for all the functions that
* need it, because the Mac limits local data to 32K.
*
* 90 4/30/99 8:53p Matt
* Added a "voice" directory for gamefiles.
*
* 89 4/22/99 3:26p Jason
* added transferring of pagelocks
*
* 88 4/20/99 12:06a Jeff
* added so files to data/scripts search path
*
* 87 4/15/99 5:21p Jason
* sped up table file loading
*
* 86 4/14/99 10:46a Kevin
* Removed OutrageMessageBox from release builds
*
* 85 4/14/99 1:33a Jeff
* fixed case mismatched #includes
*
* 84 4/12/99 3:05p Jason
* changes for 256 textures
*
* 83 3/05/99 10:42a Jason
* more deletion of pagelocls
*
* 82 3/04/99 1:46p Jason
* fixed some manage problems
*
* 81 2/27/99 5:15p Jason
* fixed search path bug
*
* 80 2/17/99 12:11p Jason
* added music directory to searchable list
*
* 79 2/16/99 11:35a Samir
* added art directory.
*
* 78 2/10/99 3:47p Jason
* before doing a backup, makes sure that the tablefile version is the
* same on the net and on the local machine
*
* 77 1/29/99 6:29p Jason
* first pass at adding bumpmaps
*
* 76 1/21/99 11:16p Jeff
* pulled out some structs and defines from header files and moved them
* into separate header files so that multiplayer dlls don't require major
* game headers, just those new headers. Side effect is a shorter build
* time. Also cleaned up some header file #includes that weren't needed.
* This affected polymodel.h, object.h, player.h, vecmat.h, room.h,
* manage.h and multi.h
*
* 75 1/13/99 2:49p Jeff
* added .msg to the search path for data\scripts
*
* 74 1/13/99 7:08a Jeff
* put some #ifdef's around some window's specific code (really only used
* in the editor, but EDITOR is never defined when building manage) so it
* builds in linux
*
* 73 12/30/98 6:52p Matt
* Fixed compile warnings
*
* 72 12/29/98 4:30p Jason
* added add-on data functionality
*
* 71 12/13/98 7:51p Jeff
* only check the script directory for cpp,dll and def files
*
* 70 12/11/98 5:50p Jeff
* implemented and added changes regarding Level&Scripting manage system
* and compiler interface
*
* 69 11/28/98 2:19p Jason
* fixed stupid filecopy bug
*
* 68 11/18/98 11:02a Jason
* temp fix for table problems
*
* 67 11/16/98 3:49p Jason
* changes for manage system
*
* 66 11/16/98 2:43p Jason
* better file checking for old files
*
* 65 11/13/98 12:30p Jason
* fixed reordered pages bug
*
* 64 11/13/98 12:30p Jason
* changes for weapons
*
* 63 11/06/98 6:00p Josh
* fixed dumb bug
*
* 62 11/06/98 5:28p Josh
* FROM JASON:upped tracklock limit
*
* 61 11/06/98 12:35p Jason
* more speedups for manage system
*
* 60 11/05/98 7:55p Jason
* changes for new manage system
*
* 59 11/04/98 11:02a Jason
* added levels and briefing directories to new "old files" update method
*
* 58 11/02/98 6:35p Jason
* changes for filter
*
* 57 11/02/98 6:02p Jason
* made yes network updates much faster
*
* 56 10/15/98 8:48a Matt
* Changed some errors to use Error() instead of OutrageMessageBox()
*
* 55 10/14/98 5:15p Jason
* added version checking to the table file
*
* 54 10/12/98 11:38p Jeff
* wrapped all the Object_info[].description whenever freed...trying to
* find an obscure bug. Added icon_name to manage page of Generic
*
* 53 10/12/98 10:31a Jason
* don't seach data directories if release
*
* 52 10/09/98 4:39p Jason
* fixed local table file message
*
* 51 10/09/98 2:27p Jason
* reorganized table file system
*
* 50 10/09/98 2:40a Jason
* fixed table file issues with demo
*
* 49 10/08/98 10:03p Jason
* more filtered table file stuff
*
* 48 10/08/98 7:05p Jason
* added file filter support
*
* 47 9/28/98 6:53p Kevin
* localized some multiplayer menus
*
* 46 9/25/98 4:37p Jason
* fixed dedicated server printing out progress messages
*
* 45 9/25/98 2:53p Jason
* added progress bar
*
* 44 9/25/98 12:24p Samir
* fixed bugs for release version.
*
* 43 9/24/98 6:22p Jason
* fixed RELEASE version asking to update network files
*
* 42 9/18/98 3:58p Jason
* change weapon reordering to do countermeasure weapons after generics
*
* 41 9/15/98 4:31p Jason
* added more functionality for the dedicated server
*
* 40 9/14/98 6:28p Jason
* first pass at getting dedicated server working
*
* 39 8/25/98 3:42p Jason
* fixed generic object problems
*
* 38 8/25/98 3:25p Jason
* turned off fast load trick
*
* 37 8/17/98 4:00p Jason
* Added mprintf
*
* 36 8/15/98 5:17p Matt
* Added new Base_directory variable. Got rid of D3_LOCAL check and
* 'local directory' registry variable.
*
* 35 8/13/98 6:34p Jason
* made table file loading much faster
*
* 34 8/10/98 1:49p Samir
* added music directory.
*
* 33 8/03/98 6:44p Jason
* set custom graphics in the search path
*
* 32 7/27/98 6:25p Jeff
* added creation of custom directories
*
* 31 6/23/98 2:43p Matt
* Changed calls to OutrageMessageBox() & Debug_MessageBox() to deal with
* int return value (instead of bool).
*
* 30 6/12/98 1:06p Jason
* added smart loading from local table file
*
* 29 5/04/98 5:00p Keneta
* FROM JASON:Fixed copyfile bug
*
* 28 5/04/98 4:42p Jason
* even better error checking
*
* 26 5/04/98 4:24p Jason
* upped MAX_TRIES
*
* 25 5/04/98 4:18p Jason
* added assert to prevent table file problems
*
* 24 3/31/98 3:49p Jason
* added memory lib
*
* 23 3/19/98 3:51p Samir
* added misc data directory.
*
* 22 2/23/98 2:00p Jason
* Pop up a message box when table file couldn't be opened
*
* 21 2/06/98 12:15p Jason
* upped max times program will try to delete the table file before
* bailing
*
* 20 2/04/98 11:47a Jason
* added dynamic description field to generic pages
*
* 19 1/26/98 11:32a Jason
* upped the number of times the system will try to delete a table file
*
* 18 1/22/98 2:49p Samir
* Added D3 Local Dir to the search path.
*
* 17 1/15/98 6:22p Jason
* added safety checks so the network won't copy over a primitive you have
* held locally
*
* 16 1/15/98 4:54p Mark
* FROM JASON:Do switcheroo a few times before giving up
*
* 15 12/22/97 3:50p Chris
*
* 14 11/17/97 4:16p Jason
* added briefings directory
*
* 13 9/09/97 4:07p Matt
* Added mprintf()
*
* 12 9/04/97 2:53p Samir
* Added gamefile and generic page strings to PageNames array.
*
* 11 8/12/97 12:47p Matt
* Only copy pagefile from net if different from local copy.
* When loading pages, print different char for each type
* Show how long it took to load the pagefile
*
* 10 8/11/97 1:54p Matt
* Ripped out robot & powerup pages, and added generic page
*
* 9 8/08/97 5:17p Jason
* made it so that when you update from the network it doesn't halt other
* users
*
* 8 8/08/97 3:44p Jason
* added code to support new generic page
*
* 7 8/08/97 1:57p Matt
* Took out error message now handled by mng_MakeLocker()
*
* 6 7/29/97 12:07p Jason
* added gamefile page for auto retrieval of non-standard page types
*
* 50 6/27/97 3:11p Jason
* added room directories
*
*
* 49 6/11/97 1:07p Samir
* The removal of gameos and replaced with oeApplication, oeDatabase
*
* 48 6/10/97 5:08p Jason
* added reorderpages menu item
*
* 47 6/05/97 2:52p Jason
* added megacell functions
*
* 46 5/30/97 11:41a Jason
* made a better error message if someone already has the table file
* locked upon startup
*
* 45 5/22/97 3:08p Jason
* added the ReorderPage function
*
* 44 5/16/97 3:53p Jason
* added filepage dialog
*
* 43 5/15/97 5:56 PM Jeremy
* made initlocaltable files check if the file exists by using cfexist
* rather than trying to open the file and checking the error code
*
* 42 5/14/97 6:38p Jason
* fixed a plethora of potential problems by locking the table file when
* someone is starting up.
*
* 41 5/14/97 6:44 PM Jeremy
* fixed a bug where local dir backup directory was not being set
* correctly in init local table files
*
* 40 5/13/97 3:41p Jason
* made all manage code work with the new device independent database
*
* 39 5/08/97 12:41p Jason
* made manage system work with device dependant path names
*
* 38 4/29/97 5:07p Samir
* Added levels directory to search path
*
* 37 4/25/97 6:16p Jason
* added switcheroo function
*
* 36 4/01/97 2:13p Jason
* changes for sound page functionality
*
* 35 3/31/97 4:35p Jason
* added player ship and weapon pages
*
* 34 3/25/97 3:10p Jason
* added robots and robot page functionality
*
* 33 3/17/97 4:27p Jason
* added sounds directory to path list
*
* 32 3/14/97 7:18p Matt
* Added missing include
*
* 31 3/14/97 6:13 PM Jeremy
* changed calls to windows "MessageBox" to OutrageMessageBox, changed
* call of GetUserName to os_database->get_user_name, #included descent.h
* in order to refer to the OS_database object, unincluded <windows.h> and
* <direct.h>
*
* 30 3/13/97 7:39p Matt
* Changed code to use getenv() (ANSI-standard) instead of
* GetEnvironmentVariable()
*
* 29 3/13/97 12:34p Matt
* Changed code to not leave directory changed after checking for presence
* of a directory.
*
* 28 3/13/97 11:37a Samir
* Changed os file functions to ddio file functions
*
* 27 3/10/97 2:23p Jason
* added auto creation of models directory
*
* 26 3/07/97 1:02p Jason
* Now uses Samir's OS specific directory functions
*
* 25 3/05/97 3:10p Jason
* added more door functionality
*
* 24 3/05/97 12:16p Jason
* added code to support our new 3d doors
*
* 23 3/03/97 6:21p Matt
* Changed cfile functions to use D3 naming convention
*
* $NoKeywords: $
*/
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include "chrono_timer.h"
#include "descent.h"
#include "manage.h"
#include "pserror.h"
#include "gametexture.h"
#include "texpage.h"
#include "doorpage.h"
#include "soundpage.h"
#include "megapage.h"
#include "shippage.h"
#include "weaponpage.h"
#include "gamefilepage.h"
#include "log.h"
#include "object.h"
#include "ddio.h"
#include "cfile.h"
#include "appdatabase.h"
#include "genericpage.h"
#include "mem.h"
#include "dedicated_server.h"
#include "init.h"
#include "stringtable.h"
#include "args.h"
#include "vclip.h"
#include "polymodel.h"
int Old_table_method = 0;
void mng_WriteNewUnknownPage(CFILE *outfile);
// This is for levels
char LocalLevelsDir[TABLE_NAME_LEN];
// This is for pages
std::filesystem::path TableLockFilename;
char TableFilename[TABLE_NAME_LEN];
char TempTableLockFilename[TABLE_NAME_LEN], TempTableFilename[TABLE_NAME_LEN];
char LocalTableFilename[TABLE_NAME_LEN], LocalTempTableFilename[TABLE_NAME_LEN];
char BackupTableFilename[TABLE_NAME_LEN], BackupLockFilename[TABLE_NAME_LEN];
std::filesystem::path ManageGraphicsDir, LocalManageGraphicsDir;
std::filesystem::path LocalModelsDir, NetModelsDir;
std::filesystem::path LocalSoundsDir, NetSoundsDir;
std::filesystem::path LocalRoomsDir, NetRoomsDir;
std::filesystem::path LocalBriefingDir, NetBriefingDir;
char LocalScriptDir[TABLE_NAME_LEN], NetScriptDir[TABLE_NAME_LEN];
std::filesystem::path LocalMiscDir, NetMiscDir;
std::filesystem::path LocalArtDir, NetArtDir;
std::filesystem::path LocalMusicDir, NetMusicDir;
std::filesystem::path LocalVoiceDir, NetVoiceDir;
std::filesystem::path NetTableDir, LocalTableDir;
char LocalD3Dir[TABLE_NAME_LEN], NetD3Dir[TABLE_NAME_LEN];
char LocalCustomGraphicsDir[TABLE_NAME_LEN];
char LocalCustomSoundsDir[TABLE_NAME_LEN];
std::filesystem::path LockerFile;
std::filesystem::path VersionFile;
char TableUser[TABLE_NAME_LEN];
char ErrorString[INFO_STRING_LEN], InfoString[INFO_STRING_LEN];
mngs_track_lock GlobalTrackLocks[MAX_TRACKLOCKS];
bool Use_old_update_method = false;
char *TablefileNameOverride = NULL;
// Only valid when first starting the editor
#define MAX_LOCKLIST_ELEMENTS 1000
mngs_Pagelock *LockList;
int Num_locklist, Starting_editor = 0, Loading_locals = 0, Fast_load_trick = 0;
#define PRIMTYPE_OOF 0
#define PRIMTYPE_OGF 1
#define PRIMTYPE_WAV 2
#define PRIMTYPE_OAF 3
#define PRIMTYPE_FILE 4
#if defined(WIN32)
FILETIME TableTimeThreshold;
// Builds a list of old files so we know which ones to update
// Searches through all our netdirectories for old files
void BuildOldFileList(FILETIME threshold);
#endif
struct old_file {
uint8_t type;
char name[PAGENAME_LEN];
};
#define MAX_OLDFILE_ELEMENTS 10000
int Num_old_files = 0;
old_file *OldFiles;
const char *PageNames[] = {"Unknown", "Texture", "Weapon", "Robot", "Powerup", "Door",
"Player ship", "Sound", "Megacell", "Files", "Generic objects"};
#ifndef RELEASE
int Network_up = 1;
int Stand_alone = 0;
#else
int Network_up = 0;
int Stand_alone = 1;
#endif
void mng_BackupTableFile();
// returns 1 if network is up, 0 if down
int mng_IsNetworkUp() {
char dir[100];
if (Stand_alone)
return 0;
char net_dir[255] = {0};
int dirlen = 255;
Database->read("net directory", net_dir, &dirlen);
if (net_dir[0] == 0)
return 0;
ddio_MakePath(dir, net_dir, "data", NULL);
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (!ec) {
char old_dir[100];
ddio_GetWorkingDir(old_dir, 100);
if (!ddio_SetWorkingDir(dir))
return 0; // network down
else {
ddio_SetWorkingDir(old_dir); // restore directory
return 1; // directory is already there
}
}
return 1;
}
void ReorderPages(int);
// #define JASONS_REORDERING
void Read256TextureNames();
// Sets up our table files, get their filenames, etc.
// Returns 1 on success, zero on error
int mng_InitTableFiles() {
size_t size = TABLE_NAME_LEN;
int answer;
Database->get_user_name(TableUser, &size);
if (FindArg("-filter"))
Use_old_update_method = true;
// Read256TextureNames ();
if (FindArg("-oldmethod"))
Use_old_update_method = true;
if (mng_IsNetworkUp()) {
#ifndef RELEASE
answer = OutrageMessageBox(MBOX_YESNO, "Do you wish to update your data files from the network?\n(If NO is "
"selected then you will have to restart to use networking functions)");
#else
answer = IDNO;
#endif
if (answer == IDNO)
Network_up = 0;
else {
Network_up = 1;
#ifndef RELEASE
#if defined(WIN32)
if (cfexist("c:\\edload"))
Use_old_update_method = true;
else {
CFILE *fp = cfopen("c:\\edload", "wt");
cfclose(fp);
}
#endif
#endif
}
} else {
LOG_WARNING << "Network is down...";
Network_up = 0;
}
if (Network_up == 0) {
mng_InitLocalTables();
mng_InitLocalDirectories();
mng_CheckToCreateLocalTables();
mng_InitTrackLocks();
} else {
// Do locals
mng_InitLocalTables();
mng_InitLocalDirectories();
mng_CheckToCreateLocalTables();
// Do network
mng_InitNetTables();
mng_InitNetDirectories();
mng_CheckToCreateNetTables();
mng_BackupTableFile();
mng_InitPagelocks();
mng_InitTrackLocks();
#ifdef JASONS_REORDERING
ReorderPages(0);
return 0;
#endif
}
return 1;
}
// Loads our tables
int mng_LoadTableFiles(int show_progress) {
if (Network_up) {
LockList = (mngs_Pagelock *)mem_malloc(MAX_LOCKLIST_ELEMENTS * sizeof(mngs_Pagelock));
Num_locklist = mng_GetListOfLocks(LockList, MAX_LOCKLIST_ELEMENTS, TableUser);
OldFiles = (old_file *)mem_malloc(MAX_OLDFILE_ELEMENTS * sizeof(old_file));
Num_old_files = 0;
ASSERT(OldFiles);
#if defined(WIN32)
if (TableTimeThreshold.dwHighDateTime != -1)
BuildOldFileList(TableTimeThreshold);
#endif
Starting_editor = 1;
}
int ret1, ret2;
if (Fast_load_trick && !FindArg("-filter"))
Network_up = 0;
ret1 = mng_LoadNetPages(show_progress);
if (Fast_load_trick) {
Network_up = 1;
Fast_load_trick = 0;
}
ret2 = mng_LoadLocalPages();
if (Network_up) {
Starting_editor = 0;
Num_locklist = 0;
Num_old_files = 0;
mem_free(OldFiles);
mem_free(LockList);
#ifndef RELEASE
#if defined(WIN32)
remove("c:\\edload");
#endif
#endif
}
RemapEverything();
if (!ret1 || !ret2)
return 0;
return 1;
}
// This is for initting tables on STAND_ALONE, if the network is down, or if
// the user doesn't want network support
int mng_InitLocalTables() {
// Set the local table directory from the base directory
strcpy(LocalD3Dir, Base_directory);
LOG_INFO << "Local dir: " << LocalD3Dir;
// Make the CFILE system first look at our local directories. If the goods aren't
// found there, try out on the network
// TODO: temp variable until LocalD3Dir will be std::fs::path
std::filesystem::path localdir = std::filesystem::path(LocalD3Dir);
LocalTableDir = localdir / "data" / "tables";
LocalManageGraphicsDir = localdir / "data" / "graphics";
LocalModelsDir = localdir / "data" / "models";
LocalSoundsDir = localdir / "data" / "sounds";
ddio_MakePath(LocalCustomSoundsDir, LocalD3Dir, "custom", "sounds", NULL);
ddio_MakePath(LocalCustomGraphicsDir, LocalD3Dir, "custom", "graphics", NULL);
LocalRoomsDir = localdir / "data" / "rooms";
LocalBriefingDir = localdir / "data" / "briefings";
ddio_MakePath(LocalScriptDir, LocalD3Dir, "data", "scripts", NULL);
LocalMiscDir = localdir / "data" / "misc";
LocalArtDir = localdir / "data" / "art";
LocalMusicDir = localdir / "data" / "music";
LocalVoiceDir = localdir / "data" / "voice";
ddio_MakePath(LocalLevelsDir, LocalD3Dir, "data", "levels", NULL);
cf_SetSearchPath(LocalD3Dir);
#ifndef RELEASE
cf_SetSearchPath(LocalLevelsDir);
cf_SetSearchPath(LocalTableDir); // Local table directory
cf_SetSearchPath(LocalManageGraphicsDir);
cf_SetSearchPath(LocalModelsDir);
cf_SetSearchPath(LocalSoundsDir);
cf_SetSearchPath(LocalRoomsDir);
cf_SetSearchPath(LocalBriefingDir);
cf_SetSearchPath(LocalScriptDir, {".cpp", ".dll", ".def", ".msg", ".so", ".msl", ".dylib"});
cf_SetSearchPath(LocalMiscDir);
cf_SetSearchPath(LocalArtDir);
cf_SetSearchPath(LocalMusicDir);
cf_SetSearchPath(LocalVoiceDir);
#endif
if (Network_up) {
ddio_MakePath(LocalTableFilename, LocalTableDir.u8string().c_str(), LOCAL_TABLE, NULL);
ddio_MakePath(LocalTempTableFilename, LocalTableDir.u8string().c_str(), TEMP_LOCAL_TABLE, NULL);
} else {
strcpy(LocalTableFilename, LOCAL_TABLE);
strcpy(LocalTempTableFilename, TEMP_LOCAL_TABLE);
}
return 1;
}
int mng_InitNetTables() {
char dir[255];
int dirlen = 255;
Database->read("net directory", dir, &dirlen);
if (dir[0] == 0)
Error("D3_DIR environment variable not set.");
strcpy(NetD3Dir, dir);
LOG_INFO << "Net dir: " << NetD3Dir;
// TODO: temp variable until NetD3Dir will be std::fs::path
std::filesystem::path netdir = std::filesystem::path(NetD3Dir);
NetModelsDir = netdir / "data" / "models";
NetSoundsDir = netdir / "data" / "sounds";
NetRoomsDir = netdir / "data" / "rooms";
NetBriefingDir = netdir / "data" / "briefings";
ddio_MakePath(NetScriptDir, NetD3Dir, "data", "scripts", NULL);
NetMiscDir = netdir / "data" / "misc";
ManageGraphicsDir = netdir / "data" / "graphics";
NetTableDir = netdir / "data" / "tables";
NetArtDir = netdir / "data" / "art";
NetMusicDir = netdir / "data" / "music";
NetVoiceDir = netdir / "data" / "voice";
TableLockFilename = NetTableDir / "table.lok";
ddio_MakePath(BackupLockFilename, NetTableDir.u8string().c_str(), "tablelok.bak", NULL);
ddio_MakePath(BackupTableFilename, NetTableDir.u8string().c_str(), "table.bak", NULL);
ddio_MakePath(TableFilename, NetTableDir.u8string().c_str(), NET_TABLE, NULL);
ddio_MakePath(TempTableLockFilename, NetTableDir.u8string().c_str(), "lock.tmp", NULL);
ddio_MakePath(TempTableFilename, NetTableDir.u8string().c_str(), TEMP_NET_TABLE, NULL);
LockerFile = NetTableDir / "locker";
VersionFile = NetTableDir / "TableVersion";
cf_SetSearchPath(ManageGraphicsDir);
cf_SetSearchPath(NetModelsDir);
cf_SetSearchPath(NetSoundsDir);
cf_SetSearchPath(NetRoomsDir);
cf_SetSearchPath(NetMiscDir);
cf_SetSearchPath(NetMusicDir);
cf_SetSearchPath(NetVoiceDir);
return 1;
}
void mng_CheckToCreateNetTables() {
CFILE *infile, *outfile;
ASSERT(Stand_alone != 1);
infile = (CFILE *)cfopen(TableFilename, "rb");
if (infile == NULL) {
if (errno == ENOENT) {
outfile = (CFILE *)cfopen(TableFilename, "wb");
if (!outfile) {
LOG_WARNING << "Error creating table file! The network must be down...";
Network_up = 0;
} else {
mng_WriteNewUnknownPage(outfile);
cfclose(outfile);
}
} else {
LOG_WARNING << "Error creating table file! The network must be down...";
Network_up = 0;
}
}
if (infile)
cfclose(infile);
}
// Checks to see if there is a table file...if not, create one with a dummy page
void mng_CheckToCreateLocalTables() {
CFILE *outfile;
if (!Network_up) {
strcpy(TableFilename, NET_TABLE);
LOG_DEBUG << "table filename = " << TableFilename;
return;
}
if (!cfexist(LocalTableFilename)) {
outfile = (CFILE *)cfopen(LocalTableFilename, "wb");
if (!outfile) {
Error("Error creating local table file!");
return;
} else {
mng_WriteNewUnknownPage(outfile);
cfclose(outfile);
}
}
}
// Creates directories if needed
void mng_InitLocalDirectories() {
std::filesystem::path dir = LocalD3Dir;
std::error_code ec;
std::filesystem::create_directories(dir / "custom", ec);
std::filesystem::create_directories(dir / "custom" / "graphics", ec);
std::filesystem::create_directories(dir / "custom" / "sounds", ec);
std::filesystem::create_directories(dir / "custom" / "settings", ec);
cf_SetSearchPath(LocalCustomGraphicsDir);
cf_SetSearchPath(LocalCustomSoundsDir);
if (Network_up) {
std::filesystem::create_directories(dir / "data", ec);
std::filesystem::create_directories(dir / "data" / "tables", ec);
std::filesystem::create_directories(dir / "data" / "graphics", ec);
std::filesystem::create_directories(dir / "data" / "sounds", ec);
std::filesystem::create_directories(dir / "data" / "rooms", ec);
std::filesystem::create_directories(dir / "data" / "levels", ec);
std::filesystem::create_directories(dir / "data" / "models", ec);
std::filesystem::create_directories(dir / "data" / "briefings", ec);
std::filesystem::create_directories(dir / "data" / "scripts", ec);
std::filesystem::create_directories(dir / "data" / "misc", ec);
std::filesystem::create_directories(dir / "data" / "art", ec);
std::filesystem::create_directories(dir / "data" / "music", ec);
std::filesystem::create_directories(dir / "data" / "voice", ec);
}
}
void mng_InitNetDirectories() {
std::filesystem::path dir = NetD3Dir;
std::error_code ec;
if (Stand_alone)
return;
std::filesystem::create_directories(dir / "data", ec);
std::filesystem::create_directories(dir / "data" / "tables", ec);
std::filesystem::create_directories(dir / "data" / "graphics", ec);
std::filesystem::create_directories(dir / "data" / "sounds", ec);
std::filesystem::create_directories(dir / "data" / "rooms", ec);
std::filesystem::create_directories(dir / "data" / "levels", ec);
std::filesystem::create_directories(dir / "data" / "models", ec);
std::filesystem::create_directories(dir / "data" / "briefings", ec);
std::filesystem::create_directories(dir / "data" / "scripts", ec);
std::filesystem::create_directories(dir / "data" / "misc", ec);
std::filesystem::create_directories(dir / "data" / "art", ec);
std::filesystem::create_directories(dir / "data" / "music", ec);
std::filesystem::create_directories(dir / "data" / "voice", ec);
}
extern int TableVersionCurrent();
#if !defined(WIN32)
void mng_BackupTableFile() {}
#else
void mng_BackupTableFile() {
Fast_load_trick = 0;
if (!TableVersionCurrent()) {
Error("You must do a source update and recompile. The data on the network is newer that your sourcecode.");
return;
}
std::filesystem::path str = LocalTableDir / NET_TABLE;
if (!cfexist(str)) {
TableTimeThreshold.dwHighDateTime = 0;
TableTimeThreshold.dwLowDateTime = 0;
} else {
WIN32_FIND_DATA filedata;
HANDLE filehandle = FindFirstFile(str.u8string().c_str(), &filedata);
if (filehandle == INVALID_HANDLE_VALUE) {
Error("Couldn't open net table file for some reason!");
return;
}
TableTimeThreshold = filedata.ftLastWriteTime;
FindClose(filehandle);
}
if (!cfexist(str) || cf_Diff(str, TableFilename)) {
LOG_INFO << "Making local copy of table file.";
if (!cf_CopyFile(str, TableFilename, 1))
Error("There was an error making a backup copy of the table file.\n");
str = LocalTableDir / "tablelok.loc";
if (!cf_CopyFile(str, TableLockFilename, 1))
Error("There was an error making a backup copy of the locker table file.\n");
} else {
LOG_INFO << "Local table file same as network copy.";
TableTimeThreshold.dwHighDateTime = -1;
Fast_load_trick = 1;
}
}
#endif
void mng_WriteUnknownPage(CFILE *outfile) {
// Function for writing out "undefined" page...useful for placeholding
cf_WriteByte(outfile, PAGETYPE_UNKNOWN);
}
void mng_WriteNewUnknownPage(CFILE *outfile) {
// Function for writing out "undefined" page...useful for placeholding
int offset = StartManagePage(outfile, PAGETYPE_UNKNOWN);
EndManagePage(outfile, offset);
}
// Clear out tracklocks
void mng_InitTrackLocks() {
for (int i = 0; i < MAX_TRACKLOCKS; i++) {
GlobalTrackLocks[i].used = 0;
GlobalTrackLocks[i].pagetype = PAGETYPE_UNKNOWN;
GlobalTrackLocks[i].name[0] = 0;
}
}
// Given a name, returns the index of the tracklock with that name
// -1 indicates that it wasn't found
int mng_FindTrackLock(char *name, int pagetype) {
int i;
for (i = 0; i < MAX_TRACKLOCKS; i++) {
if (GlobalTrackLocks[i].used && GlobalTrackLocks[i].pagetype == pagetype &&
!stricmp(name, GlobalTrackLocks[i].name))
return i;
}
return -1;
}
// Searches through global array of tracklocks and returns first free one
// Sets the tracklock to be named "name" and its type "pagetype"
// returns -1 if none free
int mng_AllocTrackLock(char *name, int pagetype) {
int i;
for (i = 0; i < MAX_TRACKLOCKS; i++)
if (GlobalTrackLocks[i].used == 0) {
strcpy(GlobalTrackLocks[i].name, name);
GlobalTrackLocks[i].pagetype = pagetype;
GlobalTrackLocks[i].used = 1;
LOG_DEBUG.printf("Tracklock %s allocated.", name);
return i;
}
Error("Couldn't get a free tracklock!");
return -1;
}
// Frees a tracklock
void mng_FreeTrackLock(int n) {
LOG_DEBUG.printf("Tracklock %s freed.", GlobalTrackLocks[n].name);
GlobalTrackLocks[n].pagetype = PAGETYPE_UNKNOWN;
GlobalTrackLocks[n].used = 0;
GlobalTrackLocks[n].name[0] = 0;
}
// Displays all the network locks of "name"
void mng_DisplayLockList(char *name) {
mngs_Pagelock list[100];
char temp[200];
int max = 100;
int num, i;
int length = 0;
char str[5000];
#ifndef RELEASE
// Get the list
if ((num = mng_GetListOfLocks(list, max, name)) < 0) {
OutrageMessageBox(MBOX_OK, ErrorString);
return;
} else if (num == 0) {
OutrageMessageBox(MBOX_OK, "User has no pages locked.");
return;
}
// Make a large string with all the info in it
snprintf(str, sizeof(str), "User %s has the following pages locked:\n\n", TableUser);
for (i = 0; i < num; i++) {
snprintf(temp, sizeof(temp), "%s:%s", PageNames[list[i].pagetype], list[i].name);
strncat(str, temp, sizeof(str) - strlen(str) - 1);
strncat(str, "\n", sizeof(str) - strlen(str) - 1);
length += strlen(temp);
if (length > 5000 - 100)
break;
}
// Display that string
OutrageMessageBox(MBOX_OK, str);
#endif
}
// Declare these here because it's too big to put on the stack on the Mac
static mngs_texture_page texpage;
static mngs_door_page doorpage;
static mngs_generic_page genericpage;
static mngs_sound_page soundpage;
static mngs_megacell_page megacellpage;
static mngs_ship_page shippage;
static mngs_weapon_page weaponpage;
static mngs_gamefile_page gamefilepage;
// IF YOU ADD ANY NEW PAGETYPE YOU MUST CHANGE THE FUNCTIONS LISTED UNDER THIS LINE
// TO DEAL WITH YOUR PAGE TYPE. IF YOU FORGET, YOU CAN CORRUPT THE PAGEFILE!!!!!
//------------------------------------------------------------------------------
// Given a pagetype, reads it in but discards it. Useful for parsing.
void mng_ReadDummyPage(CFILE *infile, uint8_t pagetype) {
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_ReadNewTexturePage(infile, &texpage);
break;
case PAGETYPE_POWERUP:
case PAGETYPE_ROBOT:
Error("Your local table file is invalid. You must update from the network.");
break;
case PAGETYPE_DOOR:
mng_ReadNewDoorPage(infile, &doorpage);
break;
case PAGETYPE_GENERIC:
mng_ReadNewGenericPage(infile, &genericpage);
if (genericpage.objinfo_struct.description != NULL) {
mem_free(genericpage.objinfo_struct.description);
genericpage.objinfo_struct.description = NULL;
}
break;
case PAGETYPE_GAMEFILE:
mng_ReadNewGamefilePage(infile, &gamefilepage);
break;
case PAGETYPE_SOUND:
mng_ReadNewSoundPage(infile, &soundpage);
break;
case PAGETYPE_SHIP:
mng_ReadNewShipPage(infile, &shippage);
break;
case PAGETYPE_WEAPON:
mng_ReadNewWeaponPage(infile, &weaponpage);
break;
case PAGETYPE_MEGACELL:
mng_ReadNewMegacellPage(infile, &megacellpage);
break;
case PAGETYPE_UNKNOWN:
break;
default:
Int3(); // unrecognized pagetype
break;
}
}
// Reads a page in that we don't care about, and writes it right back out
// This is useful for replacing a specific page in a file but ignoring others
void mng_ReadWriteDummyPage(CFILE *infile, CFILE *outfile, uint8_t pagetype) {
switch (pagetype) {
case PAGETYPE_TEXTURE:
// Read it in, write it out.
mng_ReadNewTexturePage(infile, &texpage);
mng_WriteNewTexturePage(outfile, &texpage);
break;
case PAGETYPE_DOOR:
// Read it in, write it out.
mng_ReadNewDoorPage(infile, &doorpage);
mng_WriteNewDoorPage(outfile, &doorpage);
break;
case PAGETYPE_GENERIC:
// Read it in, write it out.
mng_ReadNewGenericPage(infile, &genericpage);
mng_WriteNewGenericPage(outfile, &genericpage);
if (genericpage.objinfo_struct.description != NULL) {
mem_free(genericpage.objinfo_struct.description);
genericpage.objinfo_struct.description = NULL;
}
break;
case PAGETYPE_GAMEFILE:
// Read it in, write it out.
mng_ReadNewGamefilePage(infile, &gamefilepage);
mng_WriteNewGamefilePage(outfile, &gamefilepage);
break;
case PAGETYPE_SOUND:
// Read it in, write it out.
mng_ReadNewSoundPage(infile, &soundpage);
mng_WriteNewSoundPage(outfile, &soundpage);
break;
case PAGETYPE_MEGACELL:
// Read it in, write it out.
mng_ReadNewMegacellPage(infile, &megacellpage);
mng_WriteNewMegacellPage(outfile, &megacellpage);
break;
case PAGETYPE_SHIP:
// Read it in, write it out.
mng_ReadNewShipPage(infile, &shippage);
mng_WriteNewShipPage(outfile, &shippage);
break;
case PAGETYPE_WEAPON:
// Read it in, write it out.
mng_ReadNewWeaponPage(infile, &weaponpage);
mng_WriteNewWeaponPage(outfile, &weaponpage);
break;
case PAGETYPE_UNKNOWN:
mng_WriteNewUnknownPage(outfile);
break;
default:
Int3(); // unrecognized pagetype
break;
}
}
// Renames a page on the network
// This function is called when you rename your object, regardless if you check
// it in
int mng_RenamePage(char *oldname, char *newname, int pagetype) {
int l, i;
mngs_Pagelock pl;
char oname[PAGENAME_LEN];
LOG_INFO.printf("Renaming %s to %s...", oldname, newname);
strcpy(oname, oldname);
strcpy(pl.name, oname);
pl.pagetype = pagetype;
// Make sure we own it
l = mng_CheckIfPageOwned(&pl, TableUser);
ASSERT(l == 1);
strcpy(pl.name, newname);
strcpy(pl.holder, TableUser);
// First, change the name of the network pagelock
l = mng_ReplacePagelock(oname, &pl);
ASSERT(l == 1);
switch (pagetype) {
// Find the page type with this name and rename it
case PAGETYPE_TEXTURE:
i = FindTextureName(oname);
ASSERT(i != -1);
strcpy(GameTextures[i].name, newname);
l = mng_ReplacePage(oname, GameTextures[i].name, i, PAGETYPE_TEXTURE, 0);
ASSERT(l == 1);
if (mng_FindTrackLock(oname, PAGETYPE_TEXTURE) != -1)
mng_ReplacePage(oname, GameTextures[i].name, i, PAGETYPE_TEXTURE, 1);
break;
case PAGETYPE_DOOR:
i = FindDoorName(oname);
ASSERT(i != -1);
strcpy(Doors[i].name, newname);
l = mng_ReplacePage(oname, Doors[i].name, i, PAGETYPE_DOOR, 0);
if (mng_FindTrackLock(oname, PAGETYPE_DOOR) != -1)
mng_ReplacePage(oname, Doors[i].name, i, PAGETYPE_DOOR, 1);
ASSERT(l == 1);
break;
case PAGETYPE_GENERIC:
i = FindObjectIDName(oname);
ASSERT(i != -1);
strcpy(Object_info[i].name, newname);
l = mng_ReplacePage(oname, Object_info[i].name, i, PAGETYPE_GENERIC, 0);
if (mng_FindTrackLock(oname, PAGETYPE_GENERIC) != -1)
mng_ReplacePage(oname, Object_info[i].name, i, PAGETYPE_GENERIC, 1);
ASSERT(l == 1);
break;
case PAGETYPE_GAMEFILE:
i = FindGamefileName(oname);
ASSERT(i != -1);
strcpy(Gamefiles[i].name, newname);
l = mng_ReplacePage(oname, Gamefiles[i].name, i, PAGETYPE_GAMEFILE, 0);
if (mng_FindTrackLock(oname, PAGETYPE_GAMEFILE) != -1)
mng_ReplacePage(oname, Gamefiles[i].name, i, PAGETYPE_GAMEFILE, 1);
ASSERT(l == 1);
break;
case PAGETYPE_SOUND:
i = FindSoundName(oname);
ASSERT(i != -1);
strcpy(Sounds[i].name, newname);
l = mng_ReplacePage(oname, Sounds[i].name, i, PAGETYPE_SOUND, 0);
if (mng_FindTrackLock(oname, PAGETYPE_SOUND) != -1)
mng_ReplacePage(oname, Sounds[i].name, i, PAGETYPE_SOUND, 1);
ASSERT(l == 1);
break;
case PAGETYPE_MEGACELL:
i = FindMegacellName(oname);
ASSERT(i != -1);
strcpy(Megacells[i].name, newname);
l = mng_ReplacePage(oname, Megacells[i].name, i, PAGETYPE_MEGACELL, 0);
if (mng_FindTrackLock(oname, PAGETYPE_MEGACELL) != -1)
mng_ReplacePage(oname, Megacells[i].name, i, PAGETYPE_MEGACELL, 1);
ASSERT(l == 1);
break;
case PAGETYPE_SHIP:
i = FindShipName(oname);
ASSERT(i != -1);
strcpy(Ships[i].name, newname);
l = mng_ReplacePage(oname, Ships[i].name, i, PAGETYPE_SHIP, 0);
if (mng_FindTrackLock(oname, PAGETYPE_SHIP) != -1)
mng_ReplacePage(oname, Ships[i].name, i, PAGETYPE_SHIP, 1);
ASSERT(l == 1);
break;
case PAGETYPE_WEAPON:
i = FindWeaponName(oname);
ASSERT(i != -1);
strcpy(Weapons[i].name, newname);
l = mng_ReplacePage(oname, Weapons[i].name, i, PAGETYPE_WEAPON, 0);
if (mng_FindTrackLock(oname, PAGETYPE_WEAPON) != -1)
mng_ReplacePage(oname, Weapons[i].name, i, PAGETYPE_WEAPON, 1);
ASSERT(l == 1);
break;
default:
Int3(); // Unknown type, get Jason
break;
}
return 1;
}
#define PROGRESS_PERCENTAGE_THRESHOLD 20
// This is the function that opens the table files and reads in the individual pages
// If you want your data to be in the game, it must hook into this function
int mng_LoadNetPages(int show_progress) {
CFILE *infile;
uint8_t pagetype;
std::filesystem::path tablename;
float start_time;
int n_pages = 0;
int total_bytes;
int current_byte;
float progress;
int int_progress = 0;
int len;
LOG_INFO << "Loading pages...";
if (Dedicated_server)
show_progress = 0; // turn off progress meter for dedicated server
// If the network is up we still want to read from the local table because it
// will allow others to start the game at the same time
if (Network_up) {
int farg = FindArg("-filter");
if (farg)
tablename = GameArgs[farg + 1];
else {
tablename = LocalTableDir / NET_TABLE;
}
infile = cfopen(tablename, "rb");
} else
infile = cfopen(TableFilename, "rb");
if (!infile) {
LOG_ERROR.printf("Couldn't open table file (%s) to read pages!\n", TableFilename);
Error("Cannot open table file <%s>", TableFilename);
return 0;
}
if (show_progress) {
cfseek(infile, 0, SEEK_END);
total_bytes = cftell(infile);
cfseek(infile, 0, SEEK_SET);
}
start_time = timer_GetTime();
while (!cfeof(infile)) {
// Read in a pagetype. If it is a page we recognize, load it.
// mprintf(0,".");
if (show_progress) {
current_byte = cftell(infile);
progress = (float)current_byte / (float)total_bytes;
progress *= PROGRESS_PERCENTAGE_THRESHOLD;
int temp_int_progress = progress;
if (temp_int_progress > int_progress) {
int_progress = temp_int_progress;
InitMessage(TXT_INITDATA, progress / PROGRESS_PERCENTAGE_THRESHOLD);
}
}
pagetype = cf_ReadByte(infile);
if (!Old_table_method)
len = cf_ReadInt(infile);
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_LoadNetTexturePage(infile);
break;
case PAGETYPE_POWERUP:
case PAGETYPE_ROBOT:
Error("Your local table file is invalid. You must update from the network.");
break;
case PAGETYPE_DOOR:
mng_LoadNetDoorPage(infile);
break;
case PAGETYPE_GENERIC:
mng_LoadNetGenericPage(infile);
break;
case PAGETYPE_GAMEFILE:
mng_LoadNetGamefilePage(infile);
break;
case PAGETYPE_SOUND:
mng_LoadNetSoundPage(infile);
break;
case PAGETYPE_SHIP:
mng_LoadNetShipPage(infile);
break;
case PAGETYPE_WEAPON:
mng_LoadNetWeaponPage(infile);
break;
case PAGETYPE_MEGACELL:
mng_LoadNetMegacellPage(infile);
break;
case PAGETYPE_UNKNOWN:
break;
default:
Int3(); // Unrecognized pagetype, possible corrupt data following
return 0;
break;
}
n_pages++;
}
LOG_INFO.printf("%d pages read in %.1f seconds.", n_pages, timer_GetTime() - start_time);
cfclose(infile);
// attempt to load extra.gam if found
char name_override[256];
strcpy(name_override, "extra.gam");
infile = cfopen(name_override, "rb");
if (!infile)
return 1;
LOG_INFO << "Loading extra.gam";
n_pages = 0;
TablefileNameOverride = name_override;
while (!cfeof(infile)) {
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_LoadNetTexturePage(infile, true);
break;
case PAGETYPE_DOOR:
mng_LoadNetDoorPage(infile, true);
break;
case PAGETYPE_GENERIC:
mng_LoadNetGenericPage(infile, true);
break;
case PAGETYPE_GAMEFILE:
mng_LoadNetGamefilePage(infile, true);
break;
case PAGETYPE_SOUND:
mng_LoadNetSoundPage(infile, true);
break;
case PAGETYPE_SHIP:
mng_LoadNetShipPage(infile, true);
break;
case PAGETYPE_WEAPON:
mng_LoadNetWeaponPage(infile, true);
break;
case PAGETYPE_UNKNOWN:
break;
default:
Int3(); // Unrecognized pagetype, possible corrupt data following
cfclose(infile);
TablefileNameOverride = NULL;
return 0;
break;
}
n_pages++;
}
LOG_INFO.printf("%d extra pages read.", n_pages);
TablefileNameOverride = NULL;
cfclose(infile);
return 1;
}
// Loads and allocs all pages found locally
int mng_LoadLocalPages() {
CFILE *infile;
uint8_t pagetype;
int len;
LOG_INFO << "Overlaying local pages...";
infile = cfopen(LocalTableFilename, "rb");
if (!infile) {
LOG_WARNING.printf("Couldn't open local table file (%s) to read pages!", LocalTableFilename);
return 1;
}
Loading_locals = 1;
while (!cfeof(infile)) {
// Read in a pagetype. If it is a page we recognize, load it.
pagetype = cf_ReadByte(infile);
if (!Old_table_method)
len = cf_ReadInt(infile);
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_LoadLocalTexturePage(infile);
break;
case PAGETYPE_POWERUP:
case PAGETYPE_ROBOT:
Error("Your local table file is invalid. You must update from the network.");
break;
case PAGETYPE_DOOR:
mng_LoadLocalDoorPage(infile);
break;
case PAGETYPE_GENERIC:
mng_LoadLocalGenericPage(infile);
break;
case PAGETYPE_GAMEFILE:
mng_LoadLocalGamefilePage(infile);
break;
case PAGETYPE_SOUND:
mng_LoadLocalSoundPage(infile);
break;
case PAGETYPE_SHIP:
mng_LoadLocalShipPage(infile);
break;
case PAGETYPE_WEAPON:
mng_LoadLocalWeaponPage(infile);
break;
case PAGETYPE_MEGACELL:
mng_LoadLocalMegacellPage(infile);
break;
case PAGETYPE_UNKNOWN:
break;
default:
Int3(); // Unrecognized pagetype, possible corrupt data following
return 0;
break;
}
}
cfclose(infile);
Loading_locals = 0;
return 1;
}
#define MAX_TRIES 10000
// Removes a file, then renames another file to be the removed file. Get it?
// Returns 1 on success, else 0 on fail
int SwitcherooFiles(const char *name, char *tempname) {
/*// If we're changing the net table file, make a backup first!
if ((!stricmp (name,TableFilename)))
{
cf_CopyFile (BackupTableFilename,TableFilename);
cf_CopyFile (BackupLockFilename,TableLockFilename);
}*/
int num_tries = 0;
while (!ddio_DeleteFile(name) && num_tries < MAX_TRIES) {
D3::ChronoTimer::SleepMS(100);
num_tries++;
}
if (num_tries >= MAX_TRIES) {
strcpy(ErrorString, "MANAGE:There was a problem deleting the table file.");
ASSERT(0); // GET JASON IMMEDIATELY
Int3();
return (0);
}
num_tries = 0;
while ((rename(tempname, name)) && num_tries <= MAX_TRIES) {
D3::ChronoTimer::SleepMS(100);
num_tries++;
}
if (num_tries >= MAX_TRIES) {
strcpy(ErrorString, "MANAGE:There was a problem renaming the temp file.");
ASSERT(0); // Get JASON IMMEDIATELY
Int3();
return (0);
}
return 1;
}
void mng_TransferPages() {
CFILE *infile, *outfile;
int pagetype;
int num_tracklocks = 0;
LOG_INFO << "Transferring pages, please wait...";
if (!mng_MakeLocker())
return;
infile = cfopen(TableFilename, "rb");
if (!infile) {
LOG_ERROR << "Couldn't open table file to transfer!";
Int3();
return;
}
mngs_track_lock *local_tracklocks = (mngs_track_lock *)mem_malloc(sizeof(*local_tracklocks) * 5000);
// Do textures
int done = 0;
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
int len = cf_ReadInt(infile);
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_ReadNewTexturePage(infile, &texpage);
strcpy(local_tracklocks[num_tracklocks].name, texpage.tex_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_TEXTURE;
num_tracklocks++;
break;
case PAGETYPE_SOUND:
mng_ReadNewSoundPage(infile, &soundpage);
strcpy(local_tracklocks[num_tracklocks].name, soundpage.sound_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_SOUND;
num_tracklocks++;
break;
case PAGETYPE_WEAPON:
mng_ReadNewWeaponPage(infile, &weaponpage);
strcpy(local_tracklocks[num_tracklocks].name, weaponpage.weapon_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_WEAPON;
num_tracklocks++;
break;
case PAGETYPE_GENERIC:
mng_ReadNewGenericPage(infile, &genericpage);
strcpy(local_tracklocks[num_tracklocks].name, genericpage.objinfo_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_GENERIC;
num_tracklocks++;
break;
case PAGETYPE_DOOR:
mng_ReadNewDoorPage(infile, &doorpage);
strcpy(local_tracklocks[num_tracklocks].name, doorpage.door_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_DOOR;
num_tracklocks++;
break;
case PAGETYPE_SHIP:
mng_ReadNewShipPage(infile, &shippage);
strcpy(local_tracklocks[num_tracklocks].name, shippage.ship_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_SHIP;
num_tracklocks++;
break;
case PAGETYPE_GAMEFILE:
mng_ReadNewGamefilePage(infile, &gamefilepage);
strcpy(local_tracklocks[num_tracklocks].name, gamefilepage.gamefile_struct.name);
local_tracklocks[num_tracklocks].pagetype = PAGETYPE_GAMEFILE;
num_tracklocks++;
break;
case PAGETYPE_MEGACELL:
Int3(); // huh?
break;
default:
break;
}
}
cfclose(infile);
// Now go through and filter out all unused lock files
infile = (CFILE *)cfopen(TableLockFilename, "rb");
if (!infile) {
strcpy(ErrorString, "Couldn't open Table lock file!");
goto done;
}
outfile = (CFILE *)cfopen(TempTableLockFilename, "wb");
if (!outfile) {
cfclose(infile);
strcpy(ErrorString, "Couldn't open temporary table lock file!");
goto done;
}
done = 0;
mngs_Pagelock temp_pl;
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
if (mng_ReadPagelock(infile, &temp_pl)) {
int found = -1;
for (int i = 0; i < num_tracklocks && found == -1; i++) {
if (local_tracklocks[i].pagetype == temp_pl.pagetype && !stricmp(local_tracklocks[i].name, temp_pl.name)) {
found = i;
}
}
if (found != -1)
mng_WritePagelock(outfile, &temp_pl);
else {
LOG_WARNING.printf("Found unused lock file %s", temp_pl.name);
}
} else
done = 1;
}
cfclose(infile);
cfclose(outfile);
if (remove(TableLockFilename)) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem deleting the temp file - errno %d", errno);
goto done;
}
if (rename(TempTableLockFilename, TableLockFilename.u8string().c_str())) {
snprintf(ErrorString, sizeof(ErrorString), "There was a problem renaming the temp file - errno %d", errno);
goto done;
}
mng_EraseLocker();
LOG_INFO << "Done transferring pages...good luck!";
done:;
mem_free(local_tracklocks);
}
// #define DELETING_PAGELOCKS 1
// #define CLEANING_PAGELOCKS 1
// Given a list of names and a pagetype, unlocks the ones already inside the lock file
int mng_UnlockPagelockSeries(const char *names[], int *pagetypes, int num);
// Goes through the pagelock table and deletes all duplicate entries
int mng_DeleteDuplicatePagelocks();
void ReorderPagelocks() {
const char *names[] = {"Lava"};
int types[] = {PAGETYPE_SOUND};
if (!mng_MakeLocker())
return;
mng_UnlockPagelockSeries(names, types, 1);
// mng_DeleteDuplicatePagelocks ();
mng_EraseLocker();
}
// THIS IS A SPECIAL FUNCTION THAT YOU SHOULD ONLY USE IF YOU KNOW WHAT YOU ARE
// DOING...it simply reorders the table file so that the "primitives" are first.
// This helps in the load time of a table file.
void ReorderPages(int local) {
CFILE *infile, *outfile;
uint8_t pagetype;
int done = 0;
int len;
#ifdef CLEANING_PAGELOCKS
mng_TransferPages();
return;
#endif
#ifdef DELETING_PAGELOCKS
ReorderPagelocks();
return;
#endif
LOG_INFO << "Reordering pages, please wait...";
if (local)
infile = cfopen(LocalTableFilename, "rb");
else {
if (!mng_MakeLocker())
return;
infile = cfopen(TableFilename, "rb");
}
if (!infile) {
LOG_ERROR << "Couldn't open table file to reorder!";
Int3();
return;
}
if (local)
outfile = cfopen(LocalTempTableFilename, "wb");
else
outfile = cfopen(TempTableFilename, "wb");
if (!outfile) {
LOG_ERROR << "Couldn't open temp table file to reorder!";
cfclose(infile);
Int3();
return;
}
// Do textures
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_TEXTURE) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewTexturePage(infile, &texpage);
mng_WriteNewTexturePage(outfile, &texpage);
}
// Do sounds
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_SOUND) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewSoundPage(infile, &soundpage);
mng_WriteNewSoundPage(outfile, &soundpage);
}
// Do weapons
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_WEAPON) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewWeaponPage(infile, &weaponpage);
// Ignore counter measure weapons
if (!(weaponpage.weapon_struct.flags & WF_SPAWNS_ROBOT))
mng_WriteNewWeaponPage(outfile, &weaponpage);
}
// Do powerup generics
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_GENERIC) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewGenericPage(infile, &genericpage);
if (genericpage.objinfo_struct.type == OBJ_POWERUP)
mng_WriteNewGenericPage(outfile, &genericpage);
if (genericpage.objinfo_struct.description != NULL) {
mem_free(genericpage.objinfo_struct.description);
genericpage.objinfo_struct.description = NULL;
}
}
// Do standard generics
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_GENERIC) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewGenericPage(infile, &genericpage);
if (genericpage.objinfo_struct.type != OBJ_POWERUP)
mng_WriteNewGenericPage(outfile, &genericpage);
if (genericpage.objinfo_struct.description != NULL) {
mem_free(genericpage.objinfo_struct.description);
genericpage.objinfo_struct.description = NULL;
}
}
// Do countermeasure weapons
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_WEAPON) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewWeaponPage(infile, &weaponpage);
if ((weaponpage.weapon_struct.flags & WF_SPAWNS_ROBOT))
mng_WriteNewWeaponPage(outfile, &weaponpage);
}
// Do doors
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_DOOR) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewDoorPage(infile, &doorpage);
mng_WriteNewDoorPage(outfile, &doorpage);
}
// Do player ships
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_SHIP) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewShipPage(infile, &shippage);
mng_WriteNewShipPage(outfile, &shippage);
}
// Do gamefiles
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_GAMEFILE) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewGamefilePage(infile, &gamefilepage);
mng_WriteNewGamefilePage(outfile, &gamefilepage);
}
// Do megacells
done = 0;
cfseek(infile, 0, SEEK_SET);
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a door page, just read it in and ignore it
if (pagetype != PAGETYPE_MEGACELL) {
cfseek(infile, len - 4, SEEK_CUR);
continue;
}
mng_ReadNewMegacellPage(infile, &megacellpage);
mng_WriteNewMegacellPage(outfile, &megacellpage);
}
cfclose(infile);
cfclose(outfile);
if (local) {
SwitcherooFiles(LocalTableFilename, LocalTempTableFilename);
} else {
SwitcherooFiles(TableFilename, TempTableFilename);
}
mng_EraseLocker();
}
// Returns true if the passed in pagelock is in the LockList, else false
bool InLockList(mngs_Pagelock *pl) {
if (Starting_editor) {
for (int i = 0; i < Num_locklist; i++) {
if (LockList[i].pagetype == pl->pagetype) {
if (!stricmp(LockList[i].name, pl->name))
return true;
}
}
} else {
if ((mng_CheckIfPageOwned(pl, TableUser)) > 0) // DAJ -1FIX
return true;
}
return false;
}
// Given a filename, returns the type of primitive it is
int GetPrimType(const std::filesystem::path &name) {
int primtype;
std::filesystem::path ext = name.extension();
if (!stricmp(".oof", ext.u8string().c_str()))
primtype = PRIMTYPE_OOF;
else if (!stricmp(".ogf", ext.u8string().c_str()))
primtype = PRIMTYPE_OGF;
else if (!stricmp(".oaf", ext.u8string().c_str()))
primtype = PRIMTYPE_OAF;
else if (!stricmp(".wav", ext.u8string().c_str()))
primtype = PRIMTYPE_WAV;
else
primtype = PRIMTYPE_FILE;
return primtype;
}
#if defined(WIN32)
// Builds a list of old files in a path
void BuildOldFilesForDirectory(const std::filesystem::path& path, FILETIME threshold) {
HANDLE filehandle;
WIN32_FIND_DATA filedata;
std::filesystem::path newpath = path / "*.*";
filehandle = FindFirstFile(newpath.u8string().c_str(), &filedata);
bool go_ahead = true;
if (filehandle == INVALID_HANDLE_VALUE)
go_ahead = false;
while (go_ahead) {
bool add_it = false;
// if this file is newer than the last time we updated, add it to the list
if (filedata.ftLastWriteTime.dwHighDateTime > threshold.dwHighDateTime)
add_it = true;
if (filedata.ftLastWriteTime.dwHighDateTime == threshold.dwHighDateTime) {
if (filedata.ftLastWriteTime.dwLowDateTime > threshold.dwLowDateTime)
add_it = true;
}
if (filedata.ftCreationTime.dwHighDateTime > threshold.dwHighDateTime)
add_it = true;
if (filedata.ftCreationTime.dwHighDateTime == threshold.dwHighDateTime) {
if (filedata.ftCreationTime.dwLowDateTime > threshold.dwLowDateTime)
add_it = true;
}
// Add it to the list!
if (add_it) {
int primtype = GetPrimType(filedata.cFileName);
OldFiles[Num_old_files].type = primtype;
strcpy(OldFiles[Num_old_files].name, filedata.cFileName);
Num_old_files++;
}
go_ahead = (FindNextFile(filehandle, &filedata) != 0);
}
if (filehandle != INVALID_HANDLE_VALUE)
FindClose(filehandle);
}
// Builds a list of old files so we know which ones to update
// Searches through all our netdirectories for old files
void BuildOldFileList(FILETIME threshold) {
char str[_MAX_PATH];
LOG_INFO << "Building old files list!";
BuildOldFilesForDirectory(NetModelsDir, threshold);
BuildOldFilesForDirectory(NetSoundsDir, threshold);
BuildOldFilesForDirectory(NetMiscDir, threshold);
BuildOldFilesForDirectory(ManageGraphicsDir, threshold);
BuildOldFilesForDirectory(NetArtDir, threshold);
BuildOldFilesForDirectory(NetMusicDir, threshold);
BuildOldFilesForDirectory(NetVoiceDir, threshold);
ddio_MakePath(str, NetD3Dir, "data", "levels", NULL);
BuildOldFilesForDirectory(str, threshold);
ddio_MakePath(str, NetD3Dir, "data", "briefings", NULL);
BuildOldFilesForDirectory(str, threshold);
ddio_MakePath(str, NetD3Dir, "data", "scripts", NULL);
BuildOldFilesForDirectory(str, threshold);
LOG_INFO.printf("Found %d old files.", Num_old_files);
}
#endif
// Returns true if the passed in primitive is old (ie needs to be updated from the network)
bool IsPrimitiveOld(char *name) {
int primtype = GetPrimType(name);
for (int i = 0; i < Num_old_files; i++) {
if (OldFiles[i].type == primtype && !stricmp(OldFiles[i].name, name))
return true;
}
return false;
}
// Updates a primitive if needed
// Localname = local version of the primname (with path)
// Netname = Network version of the primname (with path)
void UpdatePrimitive(const std::filesystem::path &localname, const std::filesystem::path &netname, char *primname,
int pagetype, char *pagename) {
bool update = false;
if (Starting_editor && !Use_old_update_method) {
if (IsPrimitiveOld(primname))
update = true;
} else {
if (!cfexist(localname) || (cfexist(netname) && cf_Diff(localname, netname)))
update = true;
}
if (update) {
mngs_Pagelock temp_pl;
temp_pl.pagetype = pagetype;
strcpy(temp_pl.name, pagename);
if (!InLockList(&temp_pl)) {
LOG_DEBUG.printf("Making a local copy of %s for next time.", primname);
if (!cf_CopyFile(localname, netname, 1)) {
Int3(); // get Jason
return;
}
}
}
}
// Writes a chunk header. Writes chunk id & placeholder length. Returns chunk start pos
int StartManagePage(CFILE *ofile, uint8_t pagetype) {
int chunk_start_pos;
// Write pagetype
cf_WriteByte(ofile, pagetype);
chunk_start_pos = cftell(ofile);
cf_WriteInt(ofile, 0); // placeholder for chunk len
return chunk_start_pos;
}
// Fill in page length when done writing
void EndManagePage(CFILE *ofile, int chunk_start_pos) {
int save_pos = cftell(ofile);
int len = save_pos - chunk_start_pos;
// seek back to len field and fill in value
cfseek(ofile, chunk_start_pos, SEEK_SET);
cf_WriteInt(ofile, len); // write chunk length
// go back to end of file
cfseek(ofile, save_pos, SEEK_SET);
}
// Assigns a page to its appropriate structure and writes it out
void mng_AssignAndWritePage(int handle, int pagetype, CFILE *outfile) {
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_AssignTextureToTexPage(handle, &texpage);
mng_WriteNewTexturePage(outfile, &texpage);
break;
case PAGETYPE_SOUND:
mng_AssignSoundToSoundPage(handle, &soundpage);
mng_WriteNewSoundPage(outfile, &soundpage);
break;
case PAGETYPE_WEAPON:
mng_AssignWeaponToWeaponPage(handle, &weaponpage);
mng_WriteNewWeaponPage(outfile, &weaponpage);
break;
case PAGETYPE_GENERIC:
mng_AssignObjInfoToGenericPage(handle, &genericpage);
mng_WriteNewGenericPage(outfile, &genericpage);
break;
case PAGETYPE_DOOR:
mng_AssignDoorToDoorPage(handle, &doorpage);
mng_WriteNewDoorPage(outfile, &doorpage);
break;
case PAGETYPE_MEGACELL:
mng_AssignMegacellToMegacellPage(handle, &megacellpage);
mng_WriteNewMegacellPage(outfile, &megacellpage);
break;
case PAGETYPE_SHIP:
mng_AssignShipToShipPage(handle, &shippage);
mng_WriteNewShipPage(outfile, &shippage);
break;
case PAGETYPE_GAMEFILE:
mng_AssignGamefileToGamefilePage(handle, &gamefilepage);
mng_WriteNewGamefilePage(outfile, &gamefilepage);
break;
default:
break;
}
}
#define COPYBUFFER_SIZE 200000
// Given a texture handle, searches the table file and replaces the texture with the same name
// If local=1, then does it to the users local copy
// Returns 0 on error, else 1 if all is good
int mng_ReplacePage(char *srcname, char *destname, int handle, int dest_pagetype, int local) {
CFILE *infile, *outfile;
uint8_t pagetype, replaced = 0;
int done = 0, len;
LOG_DEBUG.printf("Replacing '%s' with '%s' (%s).", srcname, destname, local ? "locally" : "to network");
if (local)
infile = cfopen(LocalTableFilename, "rb");
else
infile = cfopen(TableFilename, "rb");
if (!infile) {
LOG_ERROR.printf("Couldn't open table file to replace page %s!", srcname);
Int3();
return 0;
}
if (local)
outfile = cfopen(LocalTempTableFilename, "wb");
else
outfile = cfopen(TempTableFilename, "wb");
if (!outfile) {
LOG_ERROR.printf("Couldn't open temp table file to replace page %s!", srcname);
cfclose(infile);
Int3();
return 0;
}
// Allocate memory for copying
uint8_t *copybuffer = (uint8_t *)mem_malloc(COPYBUFFER_SIZE);
if (!copybuffer) {
LOG_ERROR.printf("Couldn't allocate memory to replace page %s!", srcname);
cfclose(infile);
cfclose(outfile);
Int3();
return 0;
}
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
// If not a texture page, just read it in and write it right back out
if (pagetype != dest_pagetype) {
ASSERT(len < COPYBUFFER_SIZE);
cf_ReadBytes(copybuffer, len - 4, infile);
cf_WriteByte(outfile, pagetype);
cf_WriteInt(outfile, len);
if (len - 4 > 0)
cf_WriteBytes(copybuffer, len - 4, outfile);
continue;
}
switch (pagetype) {
case PAGETYPE_TEXTURE: {
mng_ReadNewTexturePage(infile, &texpage);
if (!stricmp(srcname, texpage.tex_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewTexturePage(outfile, &texpage);
}
break;
}
case PAGETYPE_SOUND: {
mng_ReadNewSoundPage(infile, &soundpage);
if (!stricmp(srcname, soundpage.sound_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewSoundPage(outfile, &soundpage);
}
break;
}
case PAGETYPE_WEAPON: {
mng_ReadNewWeaponPage(infile, &weaponpage);
if (!stricmp(srcname, weaponpage.weapon_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewWeaponPage(outfile, &weaponpage);
}
break;
}
case PAGETYPE_GENERIC: {
mng_ReadNewGenericPage(infile, &genericpage);
if (!stricmp(srcname, genericpage.objinfo_struct.name)) {
// This is the page we want to replace, so write the new one out.
if (genericpage.objinfo_struct.description != NULL) {
mem_free(genericpage.objinfo_struct.description);
genericpage.objinfo_struct.description = NULL;
}
genericpage.objinfo_struct.icon_name[0] = '\0';
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewGenericPage(outfile, &genericpage);
}
break;
}
case PAGETYPE_DOOR: {
mng_ReadNewDoorPage(infile, &doorpage);
if (!stricmp(srcname, doorpage.door_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewDoorPage(outfile, &doorpage);
}
break;
}
case PAGETYPE_MEGACELL: {
mng_ReadNewMegacellPage(infile, &megacellpage);
if (!stricmp(srcname, megacellpage.megacell_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewMegacellPage(outfile, &megacellpage);
}
break;
}
case PAGETYPE_SHIP: {
mng_ReadNewShipPage(infile, &shippage);
if (!stricmp(srcname, shippage.ship_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewShipPage(outfile, &shippage);
}
break;
}
case PAGETYPE_GAMEFILE: {
mng_ReadNewGamefilePage(infile, &gamefilepage);
if (!stricmp(srcname, gamefilepage.gamefile_struct.name)) {
mng_AssignAndWritePage(handle, pagetype, outfile);
replaced = 1;
} else {
mng_WriteNewGamefilePage(outfile, &gamefilepage);
}
break;
}
default:
break;
}
}
if (!replaced) {
// This is a new page, so append it to the end of file.
mng_AssignAndWritePage(handle, dest_pagetype, outfile);
}
if (replaced)
LOG_DEBUG << "Page replaced.";
else
LOG_DEBUG << "New page added.";
cfclose(infile);
cfclose(outfile);
mem_free(copybuffer);
if (local) {
if (!SwitcherooFiles(LocalTableFilename, LocalTempTableFilename))
return 0;
} else {
if (!SwitcherooFiles(TableFilename, TempTableFilename))
return 0;
}
return 1; // successful!
}
// Given a texture name, finds it in the table file and deletes it
// If local is 1, deletes from the local table file
int mng_DeletePage(char *name, int dest_pagetype, int local) {
CFILE *infile, *outfile;
uint8_t pagetype, replaced = 0;
int done = 0;
int deleted = 0;
LOG_DEBUG.printf("Deleting %s (%s).", name, local ? "locally" : "on network");
if (local)
infile = cfopen(LocalTableFilename, "rb");
else
infile = cfopen(TableFilename, "rb");
if (!infile) {
LOG_ERROR << "Couldn't open table file to delete page!";
Int3();
return 0;
}
if (local)
outfile = cfopen(LocalTempTableFilename, "wb");
else
outfile = cfopen(TempTableFilename, "wb");
if (!outfile) {
LOG_ERROR << "Couldn't open temp table file to delete page!";
cfclose(infile);
Int3();
return 0;
}
// Allocate memory for copying
uint8_t *copybuffer = (uint8_t *)mem_malloc(COPYBUFFER_SIZE);
if (!copybuffer) {
LOG_ERROR << "Couldn't allocate memory to delete page!";
cfclose(infile);
cfclose(outfile);
Int3();
return 0;
}
while (!done) {
if (cfeof(infile)) {
done = 1;
continue;
}
pagetype = cf_ReadByte(infile);
int len = cf_ReadInt(infile);
if (pagetype != dest_pagetype) {
ASSERT(len < COPYBUFFER_SIZE);
cf_ReadBytes(copybuffer, len - 4, infile);
cf_WriteByte(outfile, pagetype);
cf_WriteInt(outfile, len);
if (len - 4 > 0)
cf_WriteBytes(copybuffer, len - 4, outfile);
continue;
}
switch (pagetype) {
case PAGETYPE_TEXTURE: {
mng_ReadNewTexturePage(infile, &texpage);
if (stricmp(name, texpage.tex_struct.name))
mng_WriteNewTexturePage(outfile, &texpage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
case PAGETYPE_DOOR: {
mng_ReadNewDoorPage(infile, &doorpage);
if (stricmp(name, doorpage.door_struct.name))
mng_WriteNewDoorPage(outfile, &doorpage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
case PAGETYPE_GENERIC: {
mng_ReadNewGenericPage(infile, &genericpage);
if (stricmp(name, genericpage.objinfo_struct.name))
mng_WriteNewGenericPage(outfile, &genericpage);
else
deleted = 1; // Don't write out the one we want to delete
if (genericpage.objinfo_struct.description != NULL) {
mem_free(genericpage.objinfo_struct.description);
genericpage.objinfo_struct.description = NULL;
}
genericpage.objinfo_struct.icon_name[0] = '\0';
break;
}
case PAGETYPE_SOUND: {
mng_ReadNewSoundPage(infile, &soundpage);
if (stricmp(name, soundpage.sound_struct.name))
mng_WriteNewSoundPage(outfile, &soundpage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
case PAGETYPE_SHIP: {
mng_ReadNewShipPage(infile, &shippage);
if (stricmp(name, shippage.ship_struct.name))
mng_WriteNewShipPage(outfile, &shippage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
case PAGETYPE_WEAPON: {
mng_ReadNewWeaponPage(infile, &weaponpage);
if (stricmp(name, weaponpage.weapon_struct.name))
mng_WriteNewWeaponPage(outfile, &weaponpage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
case PAGETYPE_MEGACELL: {
mng_ReadNewMegacellPage(infile, &megacellpage);
if (stricmp(name, megacellpage.megacell_struct.name))
mng_WriteNewMegacellPage(outfile, &megacellpage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
case PAGETYPE_GAMEFILE: {
mng_ReadNewGamefilePage(infile, &gamefilepage);
if (stricmp(name, gamefilepage.gamefile_struct.name))
mng_WriteNewGamefilePage(outfile, &gamefilepage);
else
deleted = 1; // Don't write out the one we want to delete
break;
}
default:
break;
}
}
if (!local) {
// It's gotta be there if on the network
ASSERT(deleted == 1);
} else {
if (!deleted) {
LOG_ERROR << "Not found locally?!";
}
}
cfclose(infile);
cfclose(outfile);
mem_free(copybuffer);
// Now, remove our table file and rename the temp file to be the table file
if (local) {
if (!SwitcherooFiles(LocalTableFilename, LocalTempTableFilename))
return 0;
} else {
if (!SwitcherooFiles(TableFilename, TempTableFilename))
return 0;
}
return 1; // successful!
}
// Reads in a physics chunk from an open file
void mng_ReadPhysicsChunk(physics_info *phys_info, CFILE *infile) {
phys_info->mass = cf_ReadFloat(infile);
phys_info->drag = cf_ReadFloat(infile);
phys_info->full_thrust = cf_ReadFloat(infile);
phys_info->flags = cf_ReadInt(infile);
phys_info->rotdrag = cf_ReadFloat(infile);
phys_info->full_rotthrust = cf_ReadFloat(infile);
phys_info->num_bounces = cf_ReadInt(infile);
phys_info->velocity.z = cf_ReadFloat(infile);
phys_info->rotvel.x = cf_ReadFloat(infile);
phys_info->rotvel.y = cf_ReadFloat(infile);
phys_info->rotvel.z = cf_ReadFloat(infile);
phys_info->wiggle_amplitude = cf_ReadFloat(infile);
phys_info->wiggles_per_sec = cf_ReadFloat(infile);
phys_info->coeff_restitution = cf_ReadFloat(infile);
phys_info->hit_die_dot = cf_ReadFloat(infile);
phys_info->max_turnroll_rate = cf_ReadFloat(infile);
phys_info->turnroll_ratio = cf_ReadFloat(infile);
}
// Writes out a physics chunk to an open file
void mng_WritePhysicsChunk(physics_info *phys_info, CFILE *outfile) {
cf_WriteFloat(outfile, phys_info->mass);
cf_WriteFloat(outfile, phys_info->drag);
cf_WriteFloat(outfile, phys_info->full_thrust);
cf_WriteInt(outfile, phys_info->flags);
cf_WriteFloat(outfile, phys_info->rotdrag);
cf_WriteFloat(outfile, phys_info->full_rotthrust);
cf_WriteInt(outfile, phys_info->num_bounces);
cf_WriteFloat(outfile, phys_info->velocity.z);
cf_WriteFloat(outfile, phys_info->rotvel.x);
cf_WriteFloat(outfile, phys_info->rotvel.y);
cf_WriteFloat(outfile, phys_info->rotvel.z);
cf_WriteFloat(outfile, phys_info->wiggle_amplitude);
cf_WriteFloat(outfile, phys_info->wiggles_per_sec);
cf_WriteFloat(outfile, phys_info->coeff_restitution);
cf_WriteFloat(outfile, phys_info->hit_die_dot);
cf_WriteFloat(outfile, phys_info->max_turnroll_rate);
cf_WriteFloat(outfile, phys_info->turnroll_ratio);
}
// Writes out weapon battery info
void mng_WriteWeaponBatteryChunk(otype_wb_info *static_wb, CFILE *outfile) {
int j;
cf_WriteFloat(outfile, static_wb->energy_usage);
cf_WriteFloat(outfile, static_wb->ammo_usage);
for (j = 0; j < MAX_WB_GUNPOINTS; j++) {
cf_WriteShort(outfile, static_wb->gp_weapon_index[j]);
}
for (j = 0; j < MAX_WB_FIRING_MASKS; j++) {
cf_WriteByte(outfile, static_wb->gp_fire_masks[j]);
cf_WriteFloat(outfile, static_wb->gp_fire_wait[j]);
cf_WriteFloat(outfile, static_wb->anim_time[j]);
cf_WriteFloat(outfile, static_wb->anim_start_frame[j]);
cf_WriteFloat(outfile, static_wb->anim_fire_frame[j]);
cf_WriteFloat(outfile, static_wb->anim_end_frame[j]);
}
cf_WriteByte(outfile, static_wb->num_masks);
cf_WriteShort(outfile, static_wb->aiming_gp_index);
cf_WriteByte(outfile, static_wb->aiming_flags);
cf_WriteFloat(outfile, static_wb->aiming_3d_dot);
cf_WriteFloat(outfile, static_wb->aiming_3d_dist);
cf_WriteFloat(outfile, static_wb->aiming_XZ_dot);
cf_WriteShort(outfile, static_wb->flags);
cf_WriteByte(outfile, static_wb->gp_quad_fire_mask);
}
// Reads in weapon battery info
void mng_ReadWeaponBatteryChunk(otype_wb_info *static_wb, CFILE *infile, int version) {
int j;
static_wb->energy_usage = cf_ReadFloat(infile);
static_wb->ammo_usage = cf_ReadFloat(infile);
for (j = 0; j < MAX_WB_GUNPOINTS; j++) {
static_wb->gp_weapon_index[j] = cf_ReadShort(infile);
}
for (j = 0; j < MAX_WB_FIRING_MASKS; j++) {
static_wb->gp_fire_masks[j] = cf_ReadByte(infile);
static_wb->gp_fire_wait[j] = cf_ReadFloat(infile);
static_wb->anim_time[j] = cf_ReadFloat(infile);
static_wb->anim_start_frame[j] = cf_ReadFloat(infile);
static_wb->anim_fire_frame[j] = cf_ReadFloat(infile);
static_wb->anim_end_frame[j] = cf_ReadFloat(infile);
}
static_wb->num_masks = cf_ReadByte(infile);
static_wb->aiming_gp_index = cf_ReadShort(infile);
static_wb->aiming_flags = cf_ReadByte(infile);
static_wb->aiming_3d_dot = cf_ReadFloat(infile);
static_wb->aiming_3d_dist = cf_ReadFloat(infile);
static_wb->aiming_XZ_dot = cf_ReadFloat(infile);
if (version >= 2)
static_wb->flags = cf_ReadShort(infile);
else
static_wb->flags = cf_ReadByte(infile);
static_wb->gp_quad_fire_mask = cf_ReadByte(infile);
}
// Writes a lighting chunk in from an open file
void mng_WriteLightingChunk(light_info *lighting_info, CFILE *outfile) {
cf_WriteFloat(outfile, lighting_info->light_distance);
cf_WriteFloat(outfile, lighting_info->red_light1);
cf_WriteFloat(outfile, lighting_info->green_light1);
cf_WriteFloat(outfile, lighting_info->blue_light1);
cf_WriteFloat(outfile, lighting_info->time_interval);
cf_WriteFloat(outfile, lighting_info->flicker_distance);
cf_WriteFloat(outfile, lighting_info->directional_dot);
cf_WriteFloat(outfile, lighting_info->red_light2);
cf_WriteFloat(outfile, lighting_info->green_light2);
cf_WriteFloat(outfile, lighting_info->blue_light2);
cf_WriteInt(outfile, lighting_info->flags);
cf_WriteInt(outfile, lighting_info->timebits);
cf_WriteByte(outfile, lighting_info->angle);
cf_WriteByte(outfile, lighting_info->lighting_render_type);
}
// Reads a lighting chunk in from an open file
void mng_ReadLightingChunk(light_info *lighting_info, CFILE *infile) {
lighting_info->light_distance = cf_ReadFloat(infile);
lighting_info->red_light1 = cf_ReadFloat(infile);
lighting_info->green_light1 = cf_ReadFloat(infile);
lighting_info->blue_light1 = cf_ReadFloat(infile);
lighting_info->time_interval = cf_ReadFloat(infile);
lighting_info->flicker_distance = cf_ReadFloat(infile);
lighting_info->directional_dot = cf_ReadFloat(infile);
lighting_info->red_light2 = cf_ReadFloat(infile);
lighting_info->green_light2 = cf_ReadFloat(infile);
lighting_info->blue_light2 = cf_ReadFloat(infile);
lighting_info->flags = cf_ReadInt(infile);
lighting_info->timebits = cf_ReadInt(infile);
lighting_info->angle = cf_ReadByte(infile);
lighting_info->lighting_render_type = cf_ReadByte(infile);
}
// Record keeping
AddOnTablefile AddOnDataTables[MAX_ADDON_TABLES];
// the number of addon tables currently in memory
int Num_addon_tables = 0;
// if not -1, then it is the addon table we are working with
int Loading_addon_table = -1;
//----------------------
// Add-on data routines
//----------------------
// Frees all the primitives associated with an page
void mng_FreePagetypePrimitives(int pagetype, char *name, int freetype) {
int n;
switch (pagetype) {
case PAGETYPE_TEXTURE:
n = FindTextureName(name);
ASSERT(n >= 0);
if (GameTextures[n].flags & TF_ANIMATED)
FreeVClip(GameTextures[n].bm_handle);
else
bm_FreeBitmap(GameTextures[n].bm_handle);
if (GameTextures[n].flags & TF_DESTROYABLE && GameTextures[n].destroy_handle >= 0 &&
GameTextures[n].destroy_handle != n) {
int oldn = n;
n = GameTextures[n].destroy_handle;
if (GameTextures[n].flags & TF_ANIMATED)
FreeVClip(GameTextures[n].bm_handle);
else
bm_FreeBitmap(GameTextures[n].bm_handle);
n = oldn;
}
if (freetype)
FreeTexture(n);
break;
case PAGETYPE_SOUND:
n = FindSoundName(name);
ASSERT(n >= 0);
FreeSoundFile(Sounds[n].sample_index);
if (freetype)
FreeSound(n);
break;
case PAGETYPE_WEAPON:
n = FindWeaponName(name);
ASSERT(n >= 0);
// Free weapon images
if (Weapons[n].flags & WF_HUD_ANIMATED)
FreeVClip(Weapons[n].hud_image_handle);
else
bm_FreeBitmap(Weapons[n].hud_image_handle);
if (Weapons[n].fire_image_handle != -1) {
if (Weapons[n].flags & WF_IMAGE_BITMAP)
bm_FreeBitmap(Weapons[n].fire_image_handle);
else if (Weapons[n].flags & WF_IMAGE_VCLIP)
FreeVClip(Weapons[n].fire_image_handle);
else
FreePolyModel(Weapons[n].fire_image_handle);
}
if (freetype)
FreeWeapon(n);
break;
case PAGETYPE_SHIP:
n = FindShipName(name);
ASSERT(n >= 0);
FreePolyModel(Ships[n].model_handle);
if (Ships[n].dying_model_handle != -1)
FreePolyModel(Ships[n].dying_model_handle);
if (Ships[n].med_render_handle != -1)
FreePolyModel(Ships[n].med_render_handle);
if (Ships[n].lo_render_handle != -1)
FreePolyModel(Ships[n].lo_render_handle);
if (freetype)
FreeShip(n);
break;
case PAGETYPE_GENERIC:
n = FindObjectIDName(name);
ASSERT(n >= 0);
FreePolyModel(Object_info[n].render_handle);
if (Object_info[n].med_render_handle != -1)
FreePolyModel(Object_info[n].med_render_handle);
if (Object_info[n].lo_render_handle != -1)
FreePolyModel(Object_info[n].lo_render_handle);
if (freetype)
FreeObjectID(n);
break;
case PAGETYPE_DOOR:
n = FindDoorName(name);
ASSERT(n >= 0);
FreePolyModel(Doors[n].model_handle);
if (freetype)
FreeDoor(n);
break;
default:
break;
}
}
// Takes our addon pages and frees/restores our data to the appropriate pages
void mng_PopAddonPages() {
int i, n, ok;
ASSERT(Num_addon_tables > 0);
if (Num_addon_tables <= 0)
return; // no addon pages to pop off
Num_addon_tables--;
Loading_locals = 0;
AddOnTablefile *addondata = &AddOnDataTables[Num_addon_tables];
for (i = 0; i < addondata->Num_addon_tracklocks; i++) {
LOG_DEBUG.printf("Freeing addon page %s [%s].", addondata->Addon_tracklocks[i].name, addondata->AddOnTableFilename);
// set the Loading_addon_table to the appropriate value...
// it depends on if we are overlaying from a previous tablefile
// or this isn't an overlay at all
// overlay = 0 (not an overlay of anything)
// overlay = 1 (overlay of main tablefile)
// overlay > 1 (overlay of addon table [overlay-2])
if (addondata->Addon_tracklocks[i].overlay > 1) {
// this is an overlay of another table file
Loading_addon_table = addondata->Addon_tracklocks[i].overlay - 2;
} else {
// this is an overlay of the main table file
// or not an overlay at all
Loading_addon_table = -1;
}
if (addondata->Addon_tracklocks[i].overlay > 0) {
// Free this data, then read the old stuff back in
mng_FreePagetypePrimitives(addondata->Addon_tracklocks[i].pagetype, addondata->Addon_tracklocks[i].name, 0);
char *name = addondata->Addon_tracklocks[i].name;
switch (addondata->Addon_tracklocks[i].pagetype) {
case PAGETYPE_TEXTURE: {
n = FindTextureName(name);
ASSERT(n >= 0);
ok = mng_FindSpecificTexPage(name, &texpage, addondata->Addon_tracklocks[i].stack_filepos);
if (ok) {
ok = mng_AssignTexPageToTexture(&texpage, n);
if (!ok)
Error("Error 1 restoring page %s from addon data!", name);
} else
Error("Error 2 restoring page %s from addon data!", name);
break;
}
case PAGETYPE_SOUND: {
n = FindSoundName(name);
ASSERT(n >= 0);
ok = mng_FindSpecificSoundPage(name, &soundpage, addondata->Addon_tracklocks[i].stack_filepos);
if (ok) {
ok = mng_AssignSoundPageToSound(&soundpage, n);
if (!ok)
Error("Error 1 restoring page %s from addon data!", name);
} else
Error("Error 2 restoring page %s from addon data!", name);
break;
}
case PAGETYPE_DOOR: {
n = FindDoorName(name);
ASSERT(n >= 0);
ok = mng_FindSpecificDoorPage(name, &doorpage, addondata->Addon_tracklocks[i].stack_filepos);
if (ok) {
ok = mng_AssignDoorPageToDoor(&doorpage, n);
if (!ok)
Error("Error 1 restoring page %s from addon data!", name);
} else
Error("Error 2 restoring page %s from addon data!", name);
break;
}
case PAGETYPE_GENERIC: {
n = FindObjectIDName(name);
ASSERT(n >= 0);
ok = mng_FindSpecificGenericPage(name, &genericpage, addondata->Addon_tracklocks[i].stack_filepos);
if (ok) {
ok = mng_AssignGenericPageToObjInfo(&genericpage, n);
if (!ok)
Error("Error 1 restoring page %s from addon data!", name);
} else
Error("Error 2 restoring page %s from addon data!", name);
break;
}
case PAGETYPE_SHIP: {
n = FindShipName(name);
ASSERT(n >= 0);
ok = mng_FindSpecificShipPage(name, &shippage, addondata->Addon_tracklocks[i].stack_filepos);
if (ok) {
ok = mng_AssignShipPageToShip(&shippage, n);
if (!ok)
Error("Error 1 restoring page %s from addon data!", name);
} else
Error("Error 2 restoring page %s from addon data!", name);
break;
}
case PAGETYPE_WEAPON: {
n = FindWeaponName(name);
ASSERT(n >= 0);
ok = mng_FindSpecificWeaponPage(name, &weaponpage, addondata->Addon_tracklocks[i].stack_filepos);
if (ok) {
ok = mng_AssignWeaponPageToWeapon(&weaponpage, n);
if (!ok)
Error("Error 1 restoring page %s from addon data!", name);
} else
Error("Error 2 restoring page %s from addon data!", name);
break;
}
case PAGETYPE_GAMEFILE:
// I don't think there's anything we need to do here
break;
default:
Int3(); // bad type in list? Get Jason
break;
}
} else {
// Not overlay, just free this data
mng_FreePagetypePrimitives(addondata->Addon_tracklocks[i].pagetype, addondata->Addon_tracklocks[i].name, 1);
}
}
Loading_addon_table = -1;
}
// Simply sets no addon data to be loaded
void mng_ClearAddonTables() {
int count = Num_addon_tables;
while (count > 0) {
ASSERT(count == Num_addon_tables);
if (AddOnDataTables[count - 1].Addon_tracklocks) {
mng_PopAddonPages();
mem_free(AddOnDataTables[count - 1].Addon_tracklocks);
AddOnDataTables[count - 1].Addon_tracklocks = NULL;
AddOnDataTables[count - 1].Num_addon_tracklocks = 0;
}
count--;
}
}
// Push the given table file as an addon table file
// returns true on success
bool mng_SetAddonTable(char *name) {
ASSERT(Num_addon_tables < MAX_ADDON_TABLES);
if (Num_addon_tables >= MAX_ADDON_TABLES)
return false;
// make sure the table file exists!
if (!cfexist(name))
return false;
strcpy(AddOnDataTables[Num_addon_tables].AddOnTableFilename, name);
AddOnDataTables[Num_addon_tables].Addon_tracklocks =
(mngs_track_lock *)mem_malloc(MAX_ADDON_TRACKLOCKS * sizeof(mngs_track_lock));
AddOnDataTables[Num_addon_tables].Num_addon_tracklocks = 0;
ASSERT(AddOnDataTables[Num_addon_tables].Addon_tracklocks);
memset(AddOnDataTables[Num_addon_tables].Addon_tracklocks, 0, MAX_ADDON_TRACKLOCKS * sizeof(mngs_track_lock));
Num_addon_tables++;
return true;
}
// Pushes an addon pack onto the stack so we can keep track of it
void mng_PushAddonPage(int pagetype, char *name, int overlay) {
ASSERT(Loading_addon_table >= 0 && Loading_addon_table < MAX_ADDON_TABLES);
AddOnTablefile *addon = &AddOnDataTables[Loading_addon_table];
ASSERT(addon->Num_addon_tracklocks < MAX_ADDON_TRACKLOCKS);
// First check to see if this is a redundant page
for (int i = 0; i < addon->Num_addon_tracklocks; i++) {
if (addon->Addon_tracklocks[i].used && addon->Addon_tracklocks[i].pagetype == pagetype) {
if (!stricmp(addon->Addon_tracklocks[i].name, name)) {
Int3();
Error("Redundant addon page '%s' loaded...", name);
return;
}
}
}
LOG_DEBUG.printf("Adding addon page %s [%s] to list.", name, addon->AddOnTableFilename);
addon->Addon_tracklocks[addon->Num_addon_tracklocks].used = 1;
addon->Addon_tracklocks[addon->Num_addon_tracklocks].pagetype = pagetype;
addon->Addon_tracklocks[addon->Num_addon_tracklocks].overlay = overlay;
addon->Addon_tracklocks[addon->Num_addon_tracklocks].stack_filepos = 0;
strcpy(addon->Addon_tracklocks[addon->Num_addon_tracklocks].name, name);
addon->Num_addon_tracklocks++;
}
// Compiles the addon pages. By looking at all the addon pages (after they have been
// loaded) and does some compiling and saving of information to speed up addon page
// freeing
void mng_CompileAddonPages(void) {
if (Num_addon_tables <= 0)
return; // no addon pages to pop off
int curr_tablefile, i, tf, pagetype, len, next_pos, page_pos;
CFILE *file;
char pagename[256];
bool found_page;
for (curr_tablefile = 1; curr_tablefile <= Num_addon_tables; curr_tablefile++) {
// find all the pages that are loaded from this tablefile
// overlay = 0 (not from any tablefile)
// overlay = 1 (from main tablefile)
// overlay > 1 (from addontable[overlay-2]
if (curr_tablefile == 1) {
file = cfopen(TableFilename, "rb");
LOG_DEBUG << "Compiling addon pages of " << TableFilename;
} else {
file = cfopen(AddOnDataTables[curr_tablefile - 2].AddOnTableFilename, "rb");
LOG_DEBUG << "Compiling addon pages of " << AddOnDataTables[curr_tablefile - 2].AddOnTableFilename;
}
ASSERT(file != NULL);
// start reading the pages from this tablefile
// as we come across each page, check to see if it was
// ever overlayed.
while (!cfeof(file)) {
// Read in a pagetype. If it is a page we recognize, load it.
page_pos = cftell(file);
pagetype = cf_ReadByte(file);
len = cf_ReadInt(file);
next_pos = (page_pos + 1) + len;
// get the name of the page
switch (pagetype) {
case PAGETYPE_TEXTURE:
cf_ReadShort(file); // version
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_DOOR:
cf_ReadShort(file); // version
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_GENERIC:
cf_ReadShort(file); // version
cf_ReadByte(file); // type
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_GAMEFILE:
cf_ReadShort(file); // version
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_SOUND:
cf_ReadShort(file); // version
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_SHIP:
cf_ReadShort(file); // version
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_WEAPON:
cf_ReadShort(file); // version
cf_ReadString(pagename, PAGENAME_LEN, file);
break;
case PAGETYPE_UNKNOWN:
continue;
break;
default:
Int3(); // Unrecognized pagetype, possible corrupt data following
break;
}
// now look for all the places where this page is overlayed
found_page = false;
for (tf = 0; tf < Num_addon_tables; tf++) {
for (i = 0; i < AddOnDataTables[tf].Num_addon_tracklocks; i++) {
if (!AddOnDataTables[tf].Addon_tracklocks[i].used)
continue;
if (AddOnDataTables[tf].Addon_tracklocks[i].overlay != curr_tablefile)
continue;
if (AddOnDataTables[tf].Addon_tracklocks[i].pagetype != pagetype)
continue;
if (stricmp(pagename, AddOnDataTables[tf].Addon_tracklocks[i].name))
continue;
// this is it!
LOG_INFO.printf("Compiling: %s[%s] to %d", AddOnDataTables[tf].Addon_tracklocks[i].name,
(curr_tablefile == 1) ? TableFilename : AddOnDataTables[curr_tablefile - 2].AddOnTableFilename,
page_pos);
ASSERT(AddOnDataTables[tf].Addon_tracklocks[i].stack_filepos == 0);
AddOnDataTables[tf].Addon_tracklocks[i].stack_filepos = page_pos;
found_page = true;
break;
}
if (found_page)
break;
}
// move on to the next page
cfseek(file, next_pos, SEEK_SET);
}
// done with this tablefile
cfclose(file);
}
}
// Error checking variables
bool Loading_addon = false;
int Data_error_count = 0;
FILE *Data_error_file = NULL;
char *Addon_filename;
// Loads and allocs all pages found locally
void mng_LoadAddonPages() {
CFILE *infile;
uint8_t pagetype;
int len;
// Set flag & Clear error count
Loading_addon = true;
Data_error_count = 0;
if (Num_addon_tables == 0)
return; // No addons to load
Loading_locals = 0;
int c;
AddOnTablefile *addon;
for (c = 0; c < Num_addon_tables; c++) {
addon = &AddOnDataTables[c];
LOG_INFO.printf("Loading addon pages for %s...", addon->AddOnTableFilename);
Addon_filename = addon->AddOnTableFilename;
infile = cfopen(addon->AddOnTableFilename, "rb");
if (!infile) {
LOG_ERROR.printf("Couldn't addon table file (%s) to read pages!\n", addon->AddOnTableFilename);
return;
}
Loading_addon_table = c;
while (!cfeof(infile)) {
// Read in a pagetype. If it is a page we recognize, load it.
pagetype = cf_ReadByte(infile);
len = cf_ReadInt(infile);
switch (pagetype) {
case PAGETYPE_TEXTURE:
mng_LoadLocalTexturePage(infile);
break;
case PAGETYPE_POWERUP:
case PAGETYPE_ROBOT:
Error("Your local table file is invalid. You must update from the network.");
break;
case PAGETYPE_DOOR:
mng_LoadLocalDoorPage(infile);
break;
case PAGETYPE_GENERIC:
mng_LoadLocalGenericPage(infile);
break;
case PAGETYPE_GAMEFILE:
mng_LoadLocalGamefilePage(infile);
break;
case PAGETYPE_SOUND:
mng_LoadLocalSoundPage(infile);
break;
case PAGETYPE_SHIP:
mng_LoadLocalShipPage(infile);
break;
case PAGETYPE_WEAPON:
mng_LoadLocalWeaponPage(infile);
break;
case PAGETYPE_MEGACELL:
mng_LoadLocalMegacellPage(infile);
break;
case PAGETYPE_UNKNOWN:
break;
default:
Int3(); // Unrecognized pagetype, possible corrupt data following
break;
}
}
cfclose(infile);
}
Loading_locals = 0;
Loading_addon_table = -1;
mng_CompileAddonPages();
// Close error file
if (Data_error_file != NULL) {
fprintf(Data_error_file, "\nTotal errors: %d", Data_error_count);
fclose(Data_error_file);
}
// Clear flag
Loading_addon = false;
}
/*
#define MAX_256s 200
int Num_256s=0;
char Texture256Names[MAX_256s][80];
void Read256TextureNames ()
{
int n=FindArg ("-File256");
if (!n)
return;
CFILE *infile;
infile=(CFILE *)cfopen (GameArgs[n+1],"rt");
if (!infile)
{
mprintf(0,"Couldn't open 256 file!\n");
return;
}
char curline[200];
int done=0;
while (!done)
{
if (cfeof(infile))
{
done=1;
continue;
}
// Read a line and parse it
cf_ReadString (curline,200,infile);
if (curline[0]==';' || curline[1]==';' || curline[0]==' ' || curline[1]==' ')
continue;
if (!(isalnum(curline[0])))
continue;
strcpy (Texture256Names[Num_256s],curline);
Num_256s++;
}
cfclose (infile);
}*/
void DataError(const char *fmt, ...) {
// Got a data error!
// Int3();
// Ignore this if not loading add-on data
if (!Loading_addon)
return;
// Increment error count
Data_error_count++;
// Write to file if switch specified
if (FindArg("-datacheck")) {
static char last_filename[_MAX_PATH];
std::va_list arglist;
char buf[1024];
va_start(arglist, fmt);
std::vsnprintf(buf, sizeof(buf), fmt, arglist);
va_end(arglist);
// Open file if not already open
if (Data_error_file == NULL) {
Data_error_file = fopen("datacheck.out", "wt");
if (Data_error_file == NULL)
return;
last_filename[0] = 0;
}
// If this is a new addon file, print the name
if (strcmp(last_filename, Addon_filename)) {
if (last_filename[0])
fprintf(Data_error_file, "\n\n");
fprintf(Data_error_file, "Errors in addon file <%s>:\n\n", Addon_filename);
strcpy(last_filename, Addon_filename);
}
// Print the message
fprintf(Data_error_file, " ");
fprintf(Data_error_file, "%s", buf);
}
}