Descent3/manage/manage.cpp
Jason Yundt f5d2a43863 Add -aditionaldir option
Before this change, Descent 3 would look for all of its game data files
in a single directory. This change allows users to spread out Descent
3’s game data over multiple directories.

Building Descent 3 produces multiple files that can be freely
redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.).
Running Descent 3 requires those files and several additional files that
cannot be freely redistributed. Before this change, the files that were
redistributable had to be in the same directory as the files that were
not redistributable. This change makes it so that they can be in
separate directories.

The main motivation behind this change is to allow people to package
Descent 3 for Linux in a reasonable manner. For the most part, binary
packages for Descent 3 will contain all of the freely redistributable
components. Package managers will copy those components into system
directories that are owned by root and that users probably shouldn’t
edit manually. Users will then create a new directory and copy the game
data from their copy of Descent 3 into that new directory. Users will
then be able to run:

  Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files>

The -additionaldir option can also be used to support more complicated
scenarios. For example, if the user is using Debian’s
game-data-packager [1], then they would do something like this:

  Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files>

The -additionaldir option can also be used to load a mod that replaces
.hog files:

  Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files>

[1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-09-29 14:07:53 -04:00

3066 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 = mem_rmalloc<mngs_Pagelock>(MAX_LOCKLIST_ELEMENTS);
Num_locklist = mng_GetListOfLocks(LockList, MAX_LOCKLIST_ELEMENTS, TableUser);
OldFiles = mem_rmalloc<old_file>(MAX_OLDFILE_ELEMENTS);
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.
auto writable_base_directory_string = cf_GetWritableBaseDirectory().u8string();
strncpy(LocalD3Dir, writable_base_directory_string.c_str(), sizeof LocalD3Dir);
LocalD3Dir[sizeof LocalD3Dir - 1] = '\0';
if (strlen(LocalD3Dir) != strlen(writable_base_directory_string.c_str())) {
LOG_WARNING << "cf_GetWritableBaseDirectory() is too long to fit in LocalD3Dir, so LocalD3Dir was truncated.";
}
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;
}
auto local_tracklocks = mem_rmalloc<mngs_track_lock>(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 = mem_rmalloc<uint8_t>(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 = mem_rmalloc<uint8_t>(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 =
mem_rmalloc<mngs_track_lock>(MAX_ADDON_TRACKLOCKS);
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);
}
}